fileplaylist.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /*
  2. * DudelDu
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. /*
  11. Package playlist contains the default playlist implementation.
  12. FilePlaylistFactory
  13. FilePlaylistFactory is a PlaylistFactory which reads its definition from
  14. a file. The definition file is expected to be a JSON encoded datastructure of the form:
  15. {
  16. <web path> : [
  17. {
  18. "artist" : <artist>
  19. "title" : <title>
  20. "path" : <file path / url>
  21. }
  22. ]
  23. }
  24. The web path is the absolute path which may be requested by the streaming
  25. client (e.g. /foo/bar would be http://myserver:1234/foo/bar).
  26. The path is either a physical file or a web url reachable by the server process.
  27. The file ending determines the content type which is send to the client.
  28. */
  29. package playlist
  30. import (
  31. "bytes"
  32. "crypto/tls"
  33. "encoding/json"
  34. "io"
  35. "io/ioutil"
  36. "math/rand"
  37. "net/http"
  38. "net/url"
  39. "os"
  40. "path/filepath"
  41. "sync"
  42. "time"
  43. "devt.de/krotik/common/stringutil"
  44. "devt.de/krotik/dudeldu"
  45. )
  46. /*
  47. FileExtContentTypes maps file extensions to content types
  48. */
  49. var FileExtContentTypes = map[string]string{
  50. ".mp3": "audio/mpeg",
  51. ".flac": "audio/flac",
  52. ".aac": "audio/x-aac",
  53. ".mp4a": "audio/mp4",
  54. ".mp4": "video/mp4",
  55. ".nsv": "video/nsv",
  56. ".ogg": "audio/ogg",
  57. ".spx": "audio/ogg",
  58. ".opus": "audio/ogg",
  59. ".oga": "audio/ogg",
  60. ".ogv": "video/ogg",
  61. ".weba": "audio/webm",
  62. ".webm": "video/webm",
  63. ".axa": "audio/annodex",
  64. ".axv": "video/annodex",
  65. }
  66. /*
  67. FrameSize is the frame size which is used by the playlists
  68. */
  69. var FrameSize = dudeldu.FrameSize
  70. /*
  71. FilePlaylistFactory data structure
  72. */
  73. type FilePlaylistFactory struct {
  74. data map[string][]map[string]string
  75. itemPathPrefix string
  76. }
  77. /*
  78. NewFilePlaylistFactory creates a new FilePlaylistFactory from a given definition
  79. file.
  80. */
  81. func NewFilePlaylistFactory(path string, itemPathPrefix string) (*FilePlaylistFactory, error) {
  82. // Try to read the playlist file
  83. pl, err := ioutil.ReadFile(path)
  84. if err != nil {
  85. return nil, err
  86. }
  87. // Unmarshal json
  88. ret := &FilePlaylistFactory{
  89. data: nil,
  90. itemPathPrefix: itemPathPrefix,
  91. }
  92. err = json.Unmarshal(pl, &ret.data)
  93. if err != nil {
  94. // Try again and strip out comments
  95. pl = stringutil.StripCStyleComments(pl)
  96. err = json.Unmarshal(pl, &ret.data)
  97. }
  98. if err != nil {
  99. return nil, err
  100. }
  101. return ret, nil
  102. }
  103. /*
  104. Playlist returns a playlist for a given path.
  105. */
  106. func (fp *FilePlaylistFactory) Playlist(path string, shuffle bool) dudeldu.Playlist {
  107. if data, ok := fp.data[path]; ok {
  108. // Check if the playlist should be shuffled
  109. if shuffle {
  110. r := rand.New(rand.NewSource(time.Now().UnixNano()))
  111. shuffledData := make([]map[string]string, len(data), len(data))
  112. for i, j := range r.Perm(len(data)) {
  113. shuffledData[i] = data[j]
  114. }
  115. data = shuffledData
  116. }
  117. return &FilePlaylist{path, fp.itemPathPrefix, 0, data, nil, false,
  118. &sync.Pool{New: func() interface{} { return make([]byte, FrameSize, FrameSize) }}}
  119. }
  120. return nil
  121. }
  122. /*
  123. FilePlaylist data structure
  124. */
  125. type FilePlaylist struct {
  126. path string // Path of this playlist
  127. pathPrefix string // Prefix for all paths
  128. current int // Pointer to the current playing item
  129. data []map[string]string // Playlist items
  130. stream io.ReadCloser // Current open stream
  131. finished bool // Flag if this playlist has finished
  132. framePool *sync.Pool // Pool for byte arrays
  133. }
  134. /*
  135. currentItem returns the current playlist item
  136. */
  137. func (fp *FilePlaylist) currentItem() map[string]string {
  138. if fp.current < len(fp.data) {
  139. return fp.data[fp.current]
  140. }
  141. return fp.data[len(fp.data)-1]
  142. }
  143. /*
  144. Name is the name of the playlist.
  145. */
  146. func (fp *FilePlaylist) Name() string {
  147. return fp.path
  148. }
  149. /*
  150. ContentType returns the content type of this playlist e.g. audio/mpeg.
  151. */
  152. func (fp *FilePlaylist) ContentType() string {
  153. ext := filepath.Ext(fp.currentItem()["path"])
  154. if ctype, ok := FileExtContentTypes[ext]; ok {
  155. return ctype
  156. }
  157. return "audio"
  158. }
  159. /*
  160. Artist returns the artist which is currently playing.
  161. */
  162. func (fp *FilePlaylist) Artist() string {
  163. return fp.currentItem()["artist"]
  164. }
  165. /*
  166. Title returns the title which is currently playing.
  167. */
  168. func (fp *FilePlaylist) Title() string {
  169. return fp.currentItem()["title"]
  170. }
  171. /*
  172. Frame returns the current audio frame which is playing.
  173. */
  174. func (fp *FilePlaylist) Frame() ([]byte, error) {
  175. var err error
  176. var frame []byte
  177. if fp.finished {
  178. return nil, dudeldu.ErrPlaylistEnd
  179. }
  180. if fp.stream == nil {
  181. // Make sure first file is loaded
  182. err = fp.nextFile()
  183. }
  184. if err == nil {
  185. // Get new byte array from a pool
  186. frame = fp.framePool.Get().([]byte)
  187. n := 0
  188. nn := 0
  189. for n < len(frame) && err == nil {
  190. nn, err = fp.stream.Read(frame[n:])
  191. n += nn
  192. // Check if we need to read the next file
  193. if n < len(frame) || err == io.EOF {
  194. err = fp.nextFile()
  195. }
  196. }
  197. // Make sure the frame has no old data if it was only partially filled
  198. if n == 0 {
  199. // Special case we reached the end of the playlist
  200. frame = nil
  201. if err != nil {
  202. err = dudeldu.ErrPlaylistEnd
  203. }
  204. } else if n < len(frame) {
  205. // Resize frame if we have less data
  206. frame = frame[:n]
  207. }
  208. }
  209. if err == dudeldu.ErrPlaylistEnd {
  210. fp.finished = true
  211. }
  212. return frame, err
  213. }
  214. /*
  215. nextFile jumps to the next file for the playlist.
  216. */
  217. func (fp *FilePlaylist) nextFile() error {
  218. var err error
  219. var stream io.ReadCloser
  220. // Except for the first call advance the current pointer
  221. if fp.stream != nil {
  222. fp.current++
  223. fp.stream.Close()
  224. fp.stream = nil
  225. // Return special error if the end of the playlist has been reached
  226. if fp.current >= len(fp.data) {
  227. return dudeldu.ErrPlaylistEnd
  228. }
  229. }
  230. // Check if a file is already open
  231. if fp.stream == nil {
  232. item := fp.pathPrefix + fp.currentItem()["path"]
  233. if _, err = url.ParseRequestURI(item); err == nil {
  234. var resp *http.Response
  235. // We got an url - access it without SSL verification
  236. client := &http.Client{Transport: &http.Transport{
  237. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  238. }}
  239. if resp, err = client.Get(item); err == nil {
  240. buf := &StreamBuffer{}
  241. buf.ReadFrom(resp.Body)
  242. stream = buf
  243. }
  244. } else {
  245. // Open a new file
  246. stream, err = os.Open(item)
  247. }
  248. if err != nil {
  249. // Jump to the next file if there is an error
  250. fp.current++
  251. return err
  252. }
  253. fp.stream = stream
  254. }
  255. return err
  256. }
  257. /*
  258. ReleaseFrame releases a frame which has been written to the client.
  259. */
  260. func (fp *FilePlaylist) ReleaseFrame(frame []byte) {
  261. if len(frame) == FrameSize {
  262. fp.framePool.Put(frame)
  263. }
  264. }
  265. /*
  266. Finished returns if the playlist has finished playing.
  267. */
  268. func (fp *FilePlaylist) Finished() bool {
  269. return fp.finished
  270. }
  271. /*
  272. Close any open files by this playlist and reset the current pointer. After this
  273. call the playlist can be played again.
  274. */
  275. func (fp *FilePlaylist) Close() error {
  276. if fp.stream != nil {
  277. fp.stream.Close()
  278. fp.stream = nil
  279. }
  280. fp.current = 0
  281. fp.finished = false
  282. return nil
  283. }
  284. /*
  285. StreamBuffer is a buffer which implements io.ReadCloser and can be used to stream
  286. one stream into another. The buffer detects a potential underflow and waits
  287. until enough bytes were read from the source stream.
  288. */
  289. type StreamBuffer struct {
  290. bytes.Buffer // Buffer which is used to hold the data
  291. readFromOngoing bool
  292. }
  293. func (b *StreamBuffer) Read(p []byte) (int, error) {
  294. if b.readFromOngoing && b.Buffer.Len() < len(p) {
  295. // Prevent buffer underflow and wait until we got enough data for
  296. // the next read
  297. time.Sleep(10 * time.Millisecond)
  298. return b.Read(p)
  299. }
  300. n, err := b.Buffer.Read(p)
  301. // Return EOF if the buffer is empty
  302. if err == nil {
  303. if _, err = b.ReadByte(); err == nil {
  304. b.UnreadByte()
  305. }
  306. }
  307. return n, err
  308. }
  309. /*
  310. ReadFrom reads the source stream into the buffer.
  311. */
  312. func (b *StreamBuffer) ReadFrom(r io.Reader) (int64, error) {
  313. b.readFromOngoing = true
  314. go func() {
  315. b.Buffer.ReadFrom(r)
  316. b.readFromOngoing = false
  317. }()
  318. return 0, nil
  319. }
  320. /*
  321. Close does nothing but must be there to implement io.ReadCloser.
  322. */
  323. func (b *StreamBuffer) Close() error {
  324. // We are in memory so no need to close anything
  325. return nil
  326. }