seek to correct position when switching players
This commit is contained in:
+11
-10
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user