seek to correct position when switching players

This commit is contained in:
Drew Weymouth
2025-03-29 15:57:34 -07:00
parent 084aa6cdba
commit f9c9f290bd
5 changed files with 63 additions and 30 deletions
+11 -10
View File
@@ -171,10 +171,7 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) error {
p.registerPlayerCallbacks(pl) p.registerPlayerCallbacks(pl)
if needToUnpause { if needToUnpause {
p.PlayTrackAt(p.nowPlayingIdx) p.playTrackAt(p.nowPlayingIdx, p.pendingPlayerChangeTimePos)
// TODO - find a better way to wait until the players are ready
time.Sleep(200 * time.Millisecond)
p.SeekSeconds(p.pendingPlayerChangeTimePos)
p.pendingPlayerChange = false p.pendingPlayerChange = false
} }
p.invokeNoArgCallbacks(p.onPlayerChange) p.invokeNoArgCallbacks(p.onPlayerChange)
@@ -188,6 +185,10 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) error {
} }
func (p *playbackEngine) PlayTrackAt(idx int) 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) { if idx < 0 || idx >= len(p.playQueue) {
return errors.New("track index out of range") return errors.New("track index out of range")
} }
@@ -195,7 +196,7 @@ func (p *playbackEngine) PlayTrackAt(idx int) error {
p.checkScrobble() p.checkScrobble()
p.alreadyScrobbled = true p.alreadyScrobbled = true
p.pendingTrackChangeNum = idx p.pendingTrackChangeNum = idx
err := p.setTrack(idx, false) err := p.setTrack(idx, false, startTime)
return err return err
} }
@@ -475,7 +476,7 @@ func (p *playbackEngine) RemoveTracksFromQueue(idxs []int) {
p.Stop() p.Stop()
} else { } else {
p.nowPlayingIdx -= 1 // will be incremented in newtrack callback from player 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 // setNextTrack and onSongChange callbacks will be handled
// when we receive new track event from player // 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 { if urlP, ok := p.player.(player.URLPlayer); ok {
url := "" url := ""
var meta mediaprovider.MediaItemMetadata var meta mediaprovider.MediaItemMetadata
@@ -637,7 +638,7 @@ func (p *playbackEngine) setTrack(idx int, next bool) error {
if next { if next {
return urlP.SetNextFile(url, meta) 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 { } else if trP, ok := p.player.(player.TrackPlayer); ok {
var track *mediaprovider.Track var track *mediaprovider.Track
if idx >= 0 { if idx >= 0 {
@@ -649,13 +650,13 @@ func (p *playbackEngine) setTrack(idx int, next bool) error {
if next { if next {
return trP.SetNextTrack(track) return trP.SetNextTrack(track)
} }
return trP.PlayTrack(track) return trP.PlayTrack(track, startTime)
} }
panic("Unsupported player type") panic("Unsupported player type")
} }
func (p *playbackEngine) setNextTrack(idx int) error { func (p *playbackEngine) setNextTrack(idx int) error {
return p.setTrack(idx, true) return p.setTrack(idx, true, 0)
} }
// call BEFORE updating p.nowPlayingIdx // call BEFORE updating p.nowPlayingIdx
+24 -7
View File
@@ -123,7 +123,7 @@ func (d *DLNAPlayer) GetVolume() int {
return vol 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.ensureSetupProxy()
d.metaLock.Lock() d.metaLock.Lock()
@@ -139,13 +139,23 @@ func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadat
if err := d.playAVTransportMedia(&media); err != nil { if err := d.playAVTransportMedia(&media); err != nil {
return err return err
} }
if startTime > 0 {
// TODO: do something better than this!!
time.Sleep(2 * time.Second)
d.sendSeekCmd(startTime)
}
d.state = playing 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.Reset()
d.stopwatch.Start() d.stopwatch.Start()
d.lastStartTime = 0 d.lastStartTime = int(startTime)
d.InvokeOnPlaying() d.InvokeOnPlaying()
d.InvokeOnTrackChange() d.InvokeOnTrackChange()
if startTime > 0 {
d.InvokeOnSeek()
}
return nil return nil
} }
@@ -251,12 +261,9 @@ func (d *DLNAPlayer) SeekSeconds(secs float64) error {
d.pendingSeek = true d.pendingSeek = true
d.pendingSeekSecs = secs d.pendingSeekSecs = secs
} else { } else {
d.seeking = true if err := d.sendSeekCmd(secs); err != nil {
if err := d.avTransport.Seek(context.Background(), int(secs)); err != nil {
d.seeking = false
return err return err
} }
d.seeking = false
} }
d.lastStartTime = int(secs) d.lastStartTime = int(secs)
@@ -274,6 +281,16 @@ func (d *DLNAPlayer) SeekSeconds(secs float64) error {
return nil 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 { func (d *DLNAPlayer) IsSeeking() bool {
return d.seeking return d.seeking
} }
+1 -1
View File
@@ -80,7 +80,7 @@ func (j *JukeboxPlayer) Stop() error {
return nil 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 { if err := j.provider.JukeboxSet(track.ID); err != nil {
return err return err
} }
+22 -7
View File
@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"math" "math"
"strconv" "strconv"
"sync"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/backend/player" "github.com/dweymouth/supersonic/backend/player"
@@ -68,6 +69,9 @@ type Player struct {
equalizer Equalizer equalizer Equalizer
peaksEnabled bool peaksEnabled bool
fileLoadedLock sync.Mutex
fileLoadedSig *sync.Cond
bgCancel context.CancelFunc bgCancel context.CancelFunc
} }
@@ -80,10 +84,12 @@ func New() *Player {
// Same as New, but sets the application name that mpv // Same as New, but sets the application name that mpv
// reports to the system audio API. // reports to the system audio API.
func NewWithClientName(c string) *Player { func NewWithClientName(c string) *Player {
return &Player{ p := &Player{
vol: -1, // use 100 in Init vol: -1, // use 100 in Init
clientName: c, clientName: c,
} }
p.fileLoadedSig = sync.NewCond(&p.fileLoadedLock)
return p
} }
// Initializes the Player and makes it ready for playback. // 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. // 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 { if !p.initialized {
return ErrUnitialized return ErrUnitialized
} }
err := p.mpv.Command([]string{"loadfile", url, "replace"}) err := p.mpv.Command([]string{"loadfile", url, "replace"})
if err == nil { if err != nil {
return err
}
p.lenPlaylist = 1 p.lenPlaylist = 1
if p.status.State == player.Paused { if p.status.State == player.Paused {
return p.Continue() err = p.Continue()
} } else {
p.setState(player.Playing) p.setState(player.Playing)
} }
if startTime > 0 {
p.fileLoadedLock.Lock()
p.fileLoadedSig.Wait()
p.fileLoadedLock.Unlock()
p.SeekSeconds(startTime)
}
return err return err
} }
@@ -481,10 +496,9 @@ func (p *Player) eventHandler(ctx context.Context) {
} }
switch e.Event_Id { switch e.Event_Id {
case mpv.EVENT_PLAYBACK_RESTART: case mpv.EVENT_PLAYBACK_RESTART:
if p.seeking {
p.seeking = false p.seeking = false
}
case mpv.EVENT_SEEK: case mpv.EVENT_SEEK:
p.seeking = false
p.InvokeOnSeek() p.InvokeOnSeek()
case mpv.EVENT_FILE_LOADED: case mpv.EVENT_FILE_LOADED:
p.curPlaylistPos, _ = p.getInt64Property("playlist-pos") p.curPlaylistPos, _ = p.getInt64Property("playlist-pos")
@@ -494,6 +508,7 @@ func (p *Player) eventHandler(ctx context.Context) {
p.InvokeOnSeek() p.InvokeOnSeek()
} }
p.InvokeOnTrackChange() p.InvokeOnTrackChange()
p.fileLoadedSig.Signal()
case mpv.EVENT_IDLE: case mpv.EVENT_IDLE:
p.status.Duration = 0 p.status.Duration = 0
p.status.TimePos = 0 p.status.TimePos = 0
+2 -2
View File
@@ -4,13 +4,13 @@ import "github.com/dweymouth/supersonic/backend/mediaprovider"
type URLPlayer interface { type URLPlayer interface {
BasePlayer BasePlayer
PlayFile(url string, metadata mediaprovider.MediaItemMetadata) error PlayFile(url string, metadata mediaprovider.MediaItemMetadata, startTime float64) error
SetNextFile(url string, metadata mediaprovider.MediaItemMetadata) error SetNextFile(url string, metadata mediaprovider.MediaItemMetadata) error
} }
type TrackPlayer interface { type TrackPlayer interface {
BasePlayer BasePlayer
PlayTrack(track *mediaprovider.Track) error PlayTrack(track *mediaprovider.Track, startTime float64) error
SetNextTrack(track *mediaprovider.Track) error SetNextTrack(track *mediaprovider.Track) error
} }