Terminate all MPV instances when stopping Navidrome (#3008)

* Terminate all mpv instances when stopping Navidrome

* Exit trackSwitcher goroutine when terminating

* Remove potential race condition when starting the Playback device

* Fix lint error

* Removed unused and unneeded vars/functions

* Use device short name in log

* Small refactor

* Small nitpick

* Make start functions more uniform
This commit is contained in:
Deluan Quintão
2024-05-09 06:57:24 -04:00
committed by GitHub
parent 677d9947f3
commit 6408dda948
6 changed files with 59 additions and 79 deletions
+30 -26
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"sync"
"github.com/navidrome/navidrome/core/playback/mpv"
"github.com/navidrome/navidrome/log"
@@ -22,6 +23,7 @@ type Track interface {
}
type playbackDevice struct {
serviceCtx context.Context
ParentPlaybackServer PlaybackServer
Default bool
User string
@@ -31,7 +33,7 @@ type playbackDevice struct {
Gain float32
PlaybackDone chan bool
ActiveTrack Track
TrackSwitcherStarted bool
startTrackSwitcher sync.Once
}
type DeviceStatus struct {
@@ -43,8 +45,6 @@ type DeviceStatus struct {
const DefaultGain float32 = 1.0
var EmptyStatus = DeviceStatus{CurrentIndex: -1, Playing: false, Gain: DefaultGain, Position: 0}
func (pd *playbackDevice) getStatus() DeviceStatus {
pos := 0
if pd.ActiveTrack != nil {
@@ -61,8 +61,9 @@ func (pd *playbackDevice) getStatus() DeviceStatus {
// NewPlaybackDevice creates a new playback device which implements all the basic Jukebox mode commands defined here:
// http://www.subsonic.org/pages/api.jsp#jukeboxControl
// Starts the trackSwitcher goroutine for the device.
func NewPlaybackDevice(playbackServer PlaybackServer, name string, deviceName string) *playbackDevice {
func NewPlaybackDevice(ctx context.Context, playbackServer PlaybackServer, name string, deviceName string) *playbackDevice {
return &playbackDevice{
serviceCtx: ctx,
ParentPlaybackServer: playbackServer,
User: "",
Name: name,
@@ -70,7 +71,6 @@ func NewPlaybackDevice(playbackServer PlaybackServer, name string, deviceName st
Gain: DefaultGain,
PlaybackQueue: NewQueue(),
PlaybackDone: make(chan bool),
TrackSwitcherStarted: false,
}
}
@@ -103,14 +103,13 @@ func (pd *playbackDevice) Set(ctx context.Context, ids []string) (DeviceStatus,
func (pd *playbackDevice) Start(ctx context.Context) (DeviceStatus, error) {
log.Debug(ctx, "Processing Start action", "device", pd)
if !pd.TrackSwitcherStarted {
pd.startTrackSwitcher.Do(func() {
log.Info(ctx, "Starting trackSwitcher goroutine")
// Start one trackSwitcher goroutine with each device
go func() {
pd.trackSwitcherGoroutine()
}()
pd.TrackSwitcherStarted = true
}
})
if pd.ActiveTrack != nil {
if pd.isPlaying() {
@@ -255,25 +254,30 @@ func (pd *playbackDevice) isPlaying() bool {
func (pd *playbackDevice) trackSwitcherGoroutine() {
log.Debug("Started trackSwitcher goroutine", "device", pd)
for {
<-pd.PlaybackDone
log.Debug("Track switching detected")
if pd.ActiveTrack != nil {
pd.ActiveTrack.Close()
pd.ActiveTrack = nil
}
if !pd.PlaybackQueue.IsAtLastElement() {
pd.PlaybackQueue.IncreaseIndex()
log.Debug("Switching to next song", "queue", pd.PlaybackQueue.String())
err := pd.switchActiveTrackByIndex(pd.PlaybackQueue.Index)
if err != nil {
log.Error("Error switching track", err)
}
select {
case <-pd.PlaybackDone:
log.Debug("Track switching detected")
if pd.ActiveTrack != nil {
pd.ActiveTrack.Unpause()
pd.ActiveTrack.Close()
pd.ActiveTrack = nil
}
} else {
log.Debug("There is no song left in the playlist. Finish.")
if !pd.PlaybackQueue.IsAtLastElement() {
pd.PlaybackQueue.IncreaseIndex()
log.Debug("Switching to next song", "queue", pd.PlaybackQueue.String())
err := pd.switchActiveTrackByIndex(pd.PlaybackQueue.Index)
if err != nil {
log.Error("Error switching track", err)
}
if pd.ActiveTrack != nil {
pd.ActiveTrack.Unpause()
}
} else {
log.Debug("There is no song left in the playlist. Finish.")
}
case <-pd.serviceCtx.Done():
log.Debug("Stopping trackSwitcher goroutine", "device", pd.Name)
return
}
}
}
@@ -285,7 +289,7 @@ func (pd *playbackDevice) switchActiveTrackByIndex(index int) error {
return errors.New("could not get current track")
}
track, err := mpv.NewTrack(pd.PlaybackDone, pd.DeviceName, *currentTrack)
track, err := mpv.NewTrack(pd.serviceCtx, pd.PlaybackDone, pd.DeviceName, *currentTrack)
if err != nil {
return err
}