diff --git a/backend/playbackengine.go b/backend/playbackengine.go index 2ed3eb8..13a08d1 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -171,10 +171,7 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) error { p.registerPlayerCallbacks(pl) if needToUnpause { - p.PlayTrackAt(p.nowPlayingIdx) - // TODO - find a better way to wait until the players are ready - time.Sleep(200 * time.Millisecond) - p.SeekSeconds(p.pendingPlayerChangeTimePos) + p.playTrackAt(p.nowPlayingIdx, p.pendingPlayerChangeTimePos) p.pendingPlayerChange = false } p.invokeNoArgCallbacks(p.onPlayerChange) @@ -188,6 +185,10 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) error { } func (p *playbackEngine) PlayTrackAt(idx int) error { + return p.playTrackAt(idx, 0) +} + +func (p *playbackEngine) playTrackAt(idx int, startTime float64) error { if idx < 0 || idx >= len(p.playQueue) { return errors.New("track index out of range") } @@ -195,7 +196,7 @@ func (p *playbackEngine) PlayTrackAt(idx int) error { p.checkScrobble() p.alreadyScrobbled = true p.pendingTrackChangeNum = idx - err := p.setTrack(idx, false) + err := p.setTrack(idx, false, startTime) return err } @@ -475,7 +476,7 @@ func (p *playbackEngine) RemoveTracksFromQueue(idxs []int) { p.Stop() } else { p.nowPlayingIdx -= 1 // will be incremented in newtrack callback from player - p.setTrack(newNowPlaying, false) + p.setTrack(newNowPlaying, false, 0) } // setNextTrack and onSongChange callbacks will be handled // when we receive new track event from player @@ -617,7 +618,7 @@ func (p *playbackEngine) setNextTrackAfterQueueUpdate() { } } -func (p *playbackEngine) setTrack(idx int, next bool) error { +func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error { if urlP, ok := p.player.(player.URLPlayer); ok { url := "" var meta mediaprovider.MediaItemMetadata @@ -637,7 +638,7 @@ func (p *playbackEngine) setTrack(idx int, next bool) error { if next { return urlP.SetNextFile(url, meta) } - return urlP.PlayFile(url, meta) + return urlP.PlayFile(url, meta, startTime) } else if trP, ok := p.player.(player.TrackPlayer); ok { var track *mediaprovider.Track if idx >= 0 { @@ -649,13 +650,13 @@ func (p *playbackEngine) setTrack(idx int, next bool) error { if next { return trP.SetNextTrack(track) } - return trP.PlayTrack(track) + return trP.PlayTrack(track, startTime) } panic("Unsupported player type") } func (p *playbackEngine) setNextTrack(idx int) error { - return p.setTrack(idx, true) + return p.setTrack(idx, true, 0) } // call BEFORE updating p.nowPlayingIdx diff --git a/backend/player/dlna/dlnaplayer.go b/backend/player/dlna/dlnaplayer.go index cb28591..dd87e34 100644 --- a/backend/player/dlna/dlnaplayer.go +++ b/backend/player/dlna/dlnaplayer.go @@ -123,7 +123,7 @@ func (d *DLNAPlayer) GetVolume() int { return vol } -func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadata) error { +func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadata, startTime float64) error { d.ensureSetupProxy() d.metaLock.Lock() @@ -139,13 +139,23 @@ func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadat if err := d.playAVTransportMedia(&media); err != nil { return err } + if startTime > 0 { + // TODO: do something better than this!! + time.Sleep(2 * time.Second) + d.sendSeekCmd(startTime) + } d.state = playing - d.setTrackChangeTimer(time.Duration(meta.Duration) * time.Second) + remainingDur := meta.Duration - int(startTime) + d.setTrackChangeTimer(time.Duration(remainingDur) * time.Second) d.stopwatch.Reset() d.stopwatch.Start() - d.lastStartTime = 0 + d.lastStartTime = int(startTime) d.InvokeOnPlaying() d.InvokeOnTrackChange() + if startTime > 0 { + d.InvokeOnSeek() + } + return nil } @@ -251,12 +261,9 @@ func (d *DLNAPlayer) SeekSeconds(secs float64) error { d.pendingSeek = true d.pendingSeekSecs = secs } else { - d.seeking = true - if err := d.avTransport.Seek(context.Background(), int(secs)); err != nil { - d.seeking = false + if err := d.sendSeekCmd(secs); err != nil { return err } - d.seeking = false } d.lastStartTime = int(secs) @@ -274,6 +281,16 @@ func (d *DLNAPlayer) SeekSeconds(secs float64) error { return nil } +func (d *DLNAPlayer) sendSeekCmd(secs float64) error { + d.seeking = true + if err := d.avTransport.Seek(context.Background(), int(secs)); err != nil { + d.seeking = false + return err + } + d.seeking = false + return nil +} + func (d *DLNAPlayer) IsSeeking() bool { return d.seeking } diff --git a/backend/player/jukebox/jukeboxplayer.go b/backend/player/jukebox/jukeboxplayer.go index c3db780..176f43d 100644 --- a/backend/player/jukebox/jukeboxplayer.go +++ b/backend/player/jukebox/jukeboxplayer.go @@ -80,7 +80,7 @@ func (j *JukeboxPlayer) Stop() error { return nil } -func (j *JukeboxPlayer) PlayTrack(track *mediaprovider.Track) error { +func (j *JukeboxPlayer) PlayTrack(track *mediaprovider.Track, _ float64) error { if err := j.provider.JukeboxSet(track.ID); err != nil { return err } diff --git a/backend/player/mpv/player.go b/backend/player/mpv/player.go index 4b2ab55..9eb785b 100644 --- a/backend/player/mpv/player.go +++ b/backend/player/mpv/player.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "strconv" + "sync" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/player" @@ -68,6 +69,9 @@ type Player struct { equalizer Equalizer peaksEnabled bool + fileLoadedLock sync.Mutex + fileLoadedSig *sync.Cond + bgCancel context.CancelFunc } @@ -80,10 +84,12 @@ func New() *Player { // Same as New, but sets the application name that mpv // reports to the system audio API. func NewWithClientName(c string) *Player { - return &Player{ + p := &Player{ vol: -1, // use 100 in Init clientName: c, } + p.fileLoadedSig = sync.NewCond(&p.fileLoadedLock) + return p } // Initializes the Player and makes it ready for playback. @@ -134,18 +140,27 @@ func (p *Player) Init(maxCacheMB int) error { } // Plays the specified file, clearing the previous play queue, if any. -func (p *Player) PlayFile(url string, _ mediaprovider.MediaItemMetadata) error { +func (p *Player) PlayFile(url string, _ mediaprovider.MediaItemMetadata, startTime float64) error { if !p.initialized { return ErrUnitialized } err := p.mpv.Command([]string{"loadfile", url, "replace"}) - if err == nil { - p.lenPlaylist = 1 - if p.status.State == player.Paused { - return p.Continue() - } + if err != nil { + return err + } + p.lenPlaylist = 1 + if p.status.State == player.Paused { + err = p.Continue() + } else { p.setState(player.Playing) } + if startTime > 0 { + p.fileLoadedLock.Lock() + p.fileLoadedSig.Wait() + p.fileLoadedLock.Unlock() + p.SeekSeconds(startTime) + } + return err } @@ -481,10 +496,9 @@ func (p *Player) eventHandler(ctx context.Context) { } switch e.Event_Id { case mpv.EVENT_PLAYBACK_RESTART: - if p.seeking { - p.seeking = false - } + p.seeking = false case mpv.EVENT_SEEK: + p.seeking = false p.InvokeOnSeek() case mpv.EVENT_FILE_LOADED: p.curPlaylistPos, _ = p.getInt64Property("playlist-pos") @@ -494,6 +508,7 @@ func (p *Player) eventHandler(ctx context.Context) { p.InvokeOnSeek() } p.InvokeOnTrackChange() + p.fileLoadedSig.Signal() case mpv.EVENT_IDLE: p.status.Duration = 0 p.status.TimePos = 0 diff --git a/backend/player/player.go b/backend/player/player.go index 9c941a5..201e549 100644 --- a/backend/player/player.go +++ b/backend/player/player.go @@ -4,13 +4,13 @@ import "github.com/dweymouth/supersonic/backend/mediaprovider" type URLPlayer interface { BasePlayer - PlayFile(url string, metadata mediaprovider.MediaItemMetadata) error + PlayFile(url string, metadata mediaprovider.MediaItemMetadata, startTime float64) error SetNextFile(url string, metadata mediaprovider.MediaItemMetadata) error } type TrackPlayer interface { BasePlayer - PlayTrack(track *mediaprovider.Track) error + PlayTrack(track *mediaprovider.Track, startTime float64) error SetNextTrack(track *mediaprovider.Track) error }