diff --git a/backend/playbackcommands.go b/backend/playbackcommands.go index 2d40a1b..2632fef 100644 --- a/backend/playbackcommands.go +++ b/backend/playbackcommands.go @@ -61,6 +61,12 @@ func (c *playbackCommandQueue) C() <-chan playbackCommand { return c.nextChan } +func (c *playbackCommandQueue) Clear() { + c.mutex.Lock() + c.queue = nil + c.mutex.Unlock() +} + func (c *playbackCommandQueue) Stop() { c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop}, playbackCommand{Type: cmdStop}) diff --git a/backend/playbackengine.go b/backend/playbackengine.go index 352ac25..52b6e78 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -3,7 +3,6 @@ package backend import ( "context" "errors" - "fmt" "log" "math/rand" "time" @@ -157,15 +156,15 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) error { case player.Stopped: // nothing case player.Playing: - if err := p.Pause(); err != nil { - return fmt.Errorf("failed to pause: %v", err) - } needToUnpause = true fallthrough case player.Paused: p.pendingPlayerChangeTimePos = stat.TimePos p.pendingPlayerChange = true } + if err := p.player.Stop(true); err != nil { + log.Printf("failed to stop player: %v", err) + } oldVol := p.player.GetVolume() if _, isMPV := p.player.(*mpv.Player); !isMPV { @@ -293,7 +292,7 @@ func (p *playbackEngine) IsSeeking() bool { } func (p *playbackEngine) Stop() error { - return p.player.Stop() + return p.player.Stop(false) } func (p *playbackEngine) Pause() error { @@ -332,7 +331,7 @@ func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMo func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueueMode InsertQueueMode, shuffle bool) error { if insertQueueMode == Replace { - p.player.Stop() + p.player.Stop(false) p.nowPlayingIdx = -1 p.playQueue = nil } @@ -358,7 +357,7 @@ func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueu func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, insertMode InsertQueueMode) { if insertMode == Replace { - p.player.Stop() + p.player.Stop(false) p.nowPlayingIdx = -1 p.playQueue = nil } @@ -384,7 +383,7 @@ func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, ins // Stop playback and clear the play queue. func (p *playbackEngine) StopAndClearPlayQueue() { changed := len(p.playQueue) > 0 - p.player.Stop() + p.player.Stop(false) p.playQueue = nil p.nowPlayingIdx = -1 if changed { diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 057ea8a..a6eb122 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -139,6 +139,7 @@ func (p *PlaybackManager) CurrentRemotePlayer() *RemotePlaybackDevice { } func (p *PlaybackManager) SetRemotePlayer(rp *RemotePlaybackDevice) error { + p.cmdQueue.Clear() if rp == nil { if err := p.engine.SetPlayer(p.localPlayer); err != nil { return err diff --git a/backend/player/dlna/dlnaplayer.go b/backend/player/dlna/dlnaplayer.go index 3f9c168..939824f 100644 --- a/backend/player/dlna/dlnaplayer.go +++ b/backend/player/dlna/dlnaplayer.go @@ -41,7 +41,8 @@ type proxyMapEntry struct { type DLNAPlayer struct { player.BasePlayerCallbackImpl - destroyed bool + destroyed bool + cancelRequest context.CancelFunc avTransport *avtransport.Client renderControl *renderingcontrol.Client @@ -122,15 +123,31 @@ func NewDLNAPlayer(device *device.MediaRenderer) (*DLNAPlayer, error) { } func (d *DLNAPlayer) SetVolume(vol int) error { - return d.renderControl.SetVolume(context.Background(), vol) + if d.destroyed { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + d.cancelRequest = cancel + defer cancel() + return d.renderControl.SetVolume(ctx, vol) } func (d *DLNAPlayer) GetVolume() int { - vol, _ := d.renderControl.GetVolume(context.Background()) + if d.destroyed { + return 0 + } + ctx, cancel := context.WithCancel(context.Background()) + d.cancelRequest = cancel + defer cancel() + vol, _ := d.renderControl.GetVolume(ctx) return vol } func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadata, startTime float64) error { + if d.destroyed { + return nil + } + d.ensureSetupProxy() d.metaLock.Lock() @@ -179,17 +196,25 @@ func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadat } func (d *DLNAPlayer) playAVTransportMedia(media *avtransport.MediaItem) error { - err := d.avTransport.SetAVTransportMedia(context.Background(), media) + ctx, cancel := context.WithCancel(context.Background()) + d.cancelRequest = cancel + defer cancel() + + err := d.avTransport.SetAVTransportMedia(ctx, media) if err != nil { return err } - if err := d.avTransport.Play(context.Background()); err != nil { + if err := d.avTransport.Play(ctx); err != nil { return err } return nil } func (d *DLNAPlayer) SetNextFile(url string, meta mediaprovider.MediaItemMetadata) error { + if d.destroyed { + return nil + } + var media *avtransport.MediaItem d.metaLock.Lock() d.nextTrackMeta = meta @@ -203,7 +228,11 @@ func (d *DLNAPlayer) SetNextFile(url string, meta mediaprovider.MediaItemMetadat Title: meta.Name, } } - err := d.avTransport.SetNextAVTransportMedia(context.Background(), media) + + ctx, cancel := context.WithCancel(context.Background()) + d.cancelRequest = cancel + defer cancel() + err := d.avTransport.SetNextAVTransportMedia(ctx, media) if err != nil { d.metaLock.Lock() d.failedToSetNext = true @@ -214,19 +243,23 @@ func (d *DLNAPlayer) SetNextFile(url string, meta mediaprovider.MediaItemMetadat } func (d *DLNAPlayer) Continue() error { - if d.state == playing { + if d.destroyed || d.state == playing { return nil } + ctx, cancel := context.WithCancel(context.Background()) + d.cancelRequest = cancel + defer cancel() + if d.pendingSeek { d.pendingSeek = false - err := d.avTransport.Seek(context.Background(), int(d.pendingSeekSecs)) + err := d.avTransport.Seek(ctx, int(d.pendingSeekSecs)) if err != nil { return err } } - if err := d.avTransport.Play(context.Background()); err != nil { + if err := d.avTransport.Play(ctx); err != nil { return err } d.metaLock.Lock() @@ -240,11 +273,14 @@ func (d *DLNAPlayer) Continue() error { } func (d *DLNAPlayer) Pause() error { - if d.state != playing { + if d.destroyed || d.state != playing { return nil } - if err := d.avTransport.Pause(context.Background()); err != nil { + ctx, cancel := context.WithCancel(context.Background()) + d.cancelRequest = cancel + defer cancel() + if err := d.avTransport.Pause(ctx); err != nil { return err } d.setTrackChangeTimer(0) @@ -254,12 +290,29 @@ func (d *DLNAPlayer) Pause() error { return nil } -func (d *DLNAPlayer) Stop() error { +func (d *DLNAPlayer) Stop(force bool) error { + if d.destroyed { + return nil + } + if force && d.cancelRequest != nil { + d.cancelRequest() + } + switch d.state { case stopped: return nil case playing: - if err := d.avTransport.Pause(context.Background()); err != nil { + var ctx context.Context + var cancel context.CancelFunc + if force { + ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second) + } else { + ctx, cancel = context.WithCancel(context.Background()) + } + d.cancelRequest = cancel + defer cancel() + + if err := d.avTransport.Pause(ctx); err != nil { return err } fallthrough @@ -276,6 +329,10 @@ func (d *DLNAPlayer) Stop() error { } func (d *DLNAPlayer) SeekSeconds(secs float64) error { + if d.destroyed { + return nil + } + if d.state == paused { d.pendingSeek = true d.pendingSeekSecs = secs @@ -309,7 +366,9 @@ func (d *DLNAPlayer) SeekSeconds(secs float64) error { func (d *DLNAPlayer) sendSeekCmd(secs float64) error { d.seeking = true - if err := d.avTransport.Seek(context.Background(), int(secs)); err != nil { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := d.avTransport.Seek(ctx, int(secs)); err != nil { d.seeking = false return err } @@ -346,8 +405,16 @@ func (d *DLNAPlayer) curPlayPos() time.Duration { func (d *DLNAPlayer) Destroy() { d.destroyed = true + d.setTrackChangeTimer(0) + if d.cancelRequest != nil { + d.cancelRequest() + } + if d.proxyServer != nil { - go d.proxyServer.Shutdown(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + go d.proxyServer.Shutdown(ctx) + d.proxyServer = nil } } diff --git a/backend/player/jukebox/jukeboxplayer.go b/backend/player/jukebox/jukeboxplayer.go index 176f43d..0dd63b8 100644 --- a/backend/player/jukebox/jukeboxplayer.go +++ b/backend/player/jukebox/jukeboxplayer.go @@ -68,7 +68,7 @@ func (j *JukeboxPlayer) Pause() error { return nil } -func (j *JukeboxPlayer) Stop() error { +func (j *JukeboxPlayer) Stop(_ bool) error { if j.state == stopped { return nil } diff --git a/backend/player/mpv/player.go b/backend/player/mpv/player.go index bef95fe..3c5965a 100644 --- a/backend/player/mpv/player.go +++ b/backend/player/mpv/player.go @@ -165,7 +165,7 @@ func (p *Player) PlayFile(url string, _ mediaprovider.MediaItemMetadata, startTi } // Stops playback and clears the play queue. -func (p *Player) Stop() error { +func (p *Player) Stop(_ bool) error { if !p.initialized { return ErrUnitialized } diff --git a/backend/player/player.go b/backend/player/player.go index 201e549..877fcea 100644 --- a/backend/player/player.go +++ b/backend/player/player.go @@ -1,6 +1,8 @@ package player -import "github.com/dweymouth/supersonic/backend/mediaprovider" +import ( + "github.com/dweymouth/supersonic/backend/mediaprovider" +) type URLPlayer interface { BasePlayer @@ -17,7 +19,7 @@ type TrackPlayer interface { type BasePlayer interface { Continue() error Pause() error - Stop() error + Stop(force bool) error SeekSeconds(secs float64) error IsSeeking() bool