diff --git a/server/ctrlsubsonic/handlers_common.go b/server/ctrlsubsonic/handlers_common.go index 438e6e4..c39b41b 100644 --- a/server/ctrlsubsonic/handlers_common.go +++ b/server/ctrlsubsonic/handlers_common.go @@ -348,17 +348,14 @@ func (c *Controller) ServeJukebox(r *http.Request) *spec.Response { c.Jukebox.Start() case "skip": index, err := params.GetInt("index") - var skipCurrent bool if err != nil { - skipCurrent = true + return spec.NewError(10, "please provide an index for skip actions") } - c.Jukebox.Skip(index, skipCurrent) + c.Jukebox.Skip(index) case "get": sub := spec.NewResponse() sub.JukeboxPlaylist = c.Jukebox.GetTracks() return sub - default: - return spec.NewError(10, "unknown value `%s` for parameter 'action'", act) } // all actions except get are expected to return a status sub := spec.NewResponse() diff --git a/server/jukebox/jukebox.go b/server/jukebox/jukebox.go index a6eecf6..e6dad3e 100644 --- a/server/jukebox/jukebox.go +++ b/server/jukebox/jukebox.go @@ -29,18 +29,42 @@ type Jukebox struct { playing bool sr beep.SampleRate // used to notify the player to re read the members - updates chan struct{} + updates chan update quit chan struct{} done chan bool info *strmInfo + speaker chan updateSpeaker sync.Mutex } +type updateType string + +const ( + set updateType = "set" + clear updateType = "clear" + skip updateType = "skip" + add updateType = "add" + remove updateType = "remove" + stop updateType = "stop" + start updateType = "start" +) + +type update struct { + action updateType + index int + tracks []*db.Track +} + +type updateSpeaker struct { + index int +} + func New(musicPath string) *Jukebox { return &Jukebox{ musicPath: musicPath, sr: beep.SampleRate(48000), - updates: make(chan struct{}), + updates: make(chan update), + speaker: make(chan updateSpeaker, 1), done: make(chan bool), quit: make(chan struct{}), } @@ -54,8 +78,10 @@ func (j *Jukebox) Listen() error { select { case <-j.quit: return nil - case <-j.updates: - j.doUpdate() + case update := <-j.updates: + j.doUpdate(update) + case speaker := <-j.speaker: + j.doUpdateSpeaker(speaker) } } } @@ -64,125 +90,131 @@ func (j *Jukebox) Quit() { j.quit <- struct{}{} } -func (j *Jukebox) doUpdate() { - var streamer beep.Streamer - var format beep.Format - if j.index >= len(j.playlist) { - j.Lock() - j.index = 0 - j.playing = false - j.Unlock() - return - } +func (j *Jukebox) doUpdate(u update) { j.Lock() + switch u.action { + case set: + if j.playing { + speaker.Clear() + } + speaker.Clear() + if len(u.tracks) == 0 { + j.playlist = []*db.Track{} + j.Unlock() + return + } + j.playlist = u.tracks + j.index = 0 + j.playing = true + j.Unlock() + j.speaker <- updateSpeaker{j.index} + case clear: + speaker.Clear() + j.playing = false + j.playlist = []*db.Track{} + j.Unlock() + case skip: + speaker.Clear() + j.index = u.index + j.Unlock() + j.speaker <- updateSpeaker{j.index} + case add: + j.playlist = append(j.playlist, u.tracks...) + case remove: + if u.index < 0 || u.index > len(j.playlist) { + return + } + j.playlist = append(j.playlist[:u.index], j.playlist[u.index+1:]...) + j.Unlock() + case stop: + j.playing = false + j.info.ctrlStrmr.Paused = true + j.Unlock() + case start: + j.playing = true + j.info.ctrlStrmr.Paused = false + j.Unlock() + } +} + +func (j *Jukebox) doUpdateSpeaker(su updateSpeaker) error { + if su.index > len(j.playlist)-1 { + j.playing = false + return nil + } f, err := os.Open(path.Join( j.musicPath, - j.playlist[j.index].RelPath(), + j.playlist[su.index].RelPath(), )) - j.Unlock() if err != nil { - j.incIndex() - return + return err } - switch j.playlist[j.index].Ext() { + var streamer beep.Streamer + var format beep.Format + switch j.playlist[su.index].Ext() { case "mp3": streamer, format, err = mp3.Decode(f) case "flac": streamer, format, err = flac.Decode(f) - default: - j.incIndex() - return } if err != nil { - j.incIndex() - return + return err } - if j.playing { - j.Lock() - { - j.info = &strmInfo{} - j.info.strm = streamer.(beep.StreamSeekCloser) - j.info.ctrlStrmr.Streamer = beep.Resample( - 4, format.SampleRate, - j.sr, j.info.strm, - ) - j.info.format = format - } - j.Unlock() - speaker.Play(beep.Seq(&j.info.ctrlStrmr, beep.Callback(func() { - j.done <- false - }))) - if v := <-j.done; v { - return - } - j.Lock() - j.index++ - if j.index >= len(j.playlist) { - j.index = 0 - j.playing = false - j.Unlock() - return - } - j.Unlock() - // in a go routine as otherwise this hangs as the - go func() { - j.updates <- struct{}{} - }() - } -} - -func (j *Jukebox) incIndex() { j.Lock() - defer j.Unlock() - j.index++ + { + j.info = &strmInfo{} + j.info.strm = streamer.(beep.StreamSeekCloser) + j.info.ctrlStrmr.Streamer = beep.Resample( + 4, format.SampleRate, + j.sr, j.info.strm, + ) + j.info.format = format + } + j.Unlock() + speaker.Play(beep.Seq(&j.info.ctrlStrmr, beep.Callback(func() { + j.speaker <- updateSpeaker{su.index + 1} + }))) + return nil } func (j *Jukebox) SetTracks(tracks []*db.Track) { - j.Lock() - defer j.Unlock() - j.index = 0 - if len(tracks) == 0 { - if j.playing { - j.done <- true - } - j.playing = false - j.playlist = []*db.Track{} - speaker.Clear() - return + j.updates <- update{ + action: set, + tracks: tracks, } - if j.playing { - j.playlist = tracks - j.done <- true - speaker.Clear() - j.updates <- struct{}{} - return - } - j.playlist = tracks - j.playing = true - j.updates <- struct{}{} } func (j *Jukebox) AddTracks(tracks []*db.Track) { - j.Lock() - j.playlist = append(j.playlist, tracks...) - j.Unlock() -} - -func (j *Jukebox) ClearTracks() { - j.Lock() - j.index = 0 - j.playing = false - j.playlist = []*db.Track{} - j.Unlock() + j.updates <- update{ + action: add, + tracks: tracks, + } } func (j *Jukebox) RemoveTrack(i int) { - j.Lock() - defer j.Unlock() - if i < 0 || i > len(j.playlist) { - return + j.updates <- update{ + action: remove, + index: i, } - j.playlist = append(j.playlist[:i], j.playlist[i+1:]...) +} + +func (j *Jukebox) Skip(i int) { + j.updates <- update{ + action: skip, + index: i, + } +} + +func (j *Jukebox) ClearTracks() { + j.updates <- update{action: clear} +} + +func (j *Jukebox) Stop() { + j.updates <- update{action: stop} +} + +func (j *Jukebox) Start() { + j.updates <- update{action: start} } func (j *Jukebox) Status() *spec.JukeboxStatus { @@ -217,35 +249,3 @@ func (j *Jukebox) GetTracks() *spec.JukeboxPlaylist { } return jb } - -func (j *Jukebox) Stop() { - j.Lock() - j.playing = false - j.info.ctrlStrmr.Paused = true - j.Unlock() -} - -func (j *Jukebox) Start() { - j.Lock() - j.playing = true - j.info.ctrlStrmr.Paused = false - j.Unlock() -} - -func (j *Jukebox) Skip(i int, skipCurrent bool) { - j.Lock() - defer j.Unlock() - if i == j.index { - return - } - if skipCurrent { - j.index++ - } else { - j.index = i - } - speaker.Clear() - if j.playing { - j.done <- true - } - j.updates <- struct{}{} -}