Skip to content

Commit

Permalink
Fix #483: workaround MPV failing to begin playback of track with new …
Browse files Browse the repository at this point in the history
…sample rate on Windows
  • Loading branch information
dweymouth committed Dec 21, 2024
1 parent 7c774c6 commit e8737f4
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 3 deletions.
9 changes: 9 additions & 0 deletions backend/playbackcommands.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
// arg3: bool (shuffle)
cmdLoadItems
cmdLoadRadioStation // arg: *mediaprovider.RadioStation, arg2: InsertQueueMode

cmdForceRestartPlayback
)

type playbackCommand struct {
Expand Down Expand Up @@ -144,6 +146,13 @@ func (c *playbackCommandQueue) LoadItems(items []mediaprovider.MediaItem, insert
c.cmdAvailable.Signal()
}

func (c *playbackCommandQueue) addCommand(command playbackCommand) {
c.mutex.Lock()
c.queue = append(c.queue, command)
c.mutex.Unlock()
c.cmdAvailable.Signal()
}

func (c *playbackCommandQueue) filterCommandsAndAdd(excludeTypes []playbackCommandType, command playbackCommand) {
c.mutex.Lock()
j := 0
Expand Down
38 changes: 35 additions & 3 deletions backend/playbackmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import (
"context"
"log"
"math/rand"
"time"

"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/backend/player"
"github.com/dweymouth/supersonic/backend/player/mpv"
)

// A high-level MediaProvider-aware playback engine, serves as an
// intermediary between the frontend and various Player backends.
type PlaybackManager struct {
engine *playbackEngine
cmdQueue *playbackCommandQueue
cfg *AppConfig
cfg *AppConfig

lastPlayTime float64
}

func NewPlaybackManager(
Expand All @@ -23,19 +27,42 @@ func NewPlaybackManager(
p player.BasePlayer,
scrobbleCfg *ScrobbleConfig,
transcodeCfg *TranscodingConfig,
appCfg *AppConfig,
appCfg *AppConfig,
) *PlaybackManager {
e := NewPlaybackEngine(ctx, s, p, scrobbleCfg, transcodeCfg)
q := NewCommandQueue()
pm := &PlaybackManager{
engine: e,
cmdQueue: q,
cfg: appCfg,
cfg: appCfg,
}
pm.workaroundWindowsPlaybackIssue()
go pm.runCmdQueue(ctx)
return pm
}

func (p *PlaybackManager) workaroundWindowsPlaybackIssue() {
// See https://github.com/dweymouth/supersonic/issues/483
// On Windows, MPV sometimes fails to start playback when switching to a track
// with a different sample rate than the previous. If this is detected,
// send a command to the MPV player to force restart playback.
p.OnPlayTimeUpdate(func(curTime, _ float64, _ bool) {
p.lastPlayTime = curTime
})
p.OnSongChange(func(mediaprovider.MediaItem, *mediaprovider.Track) {
if p.NowPlayingIndex() != len(p.engine.playQueue) && p.PlayerStatus().State == player.Playing {
p.lastPlayTime = 0
go func() {
time.Sleep(300 * time.Millisecond)
if p.lastPlayTime == 0 {
log.Println("Play stall detected!")
p.cmdQueue.addCommand(playbackCommand{Type: cmdForceRestartPlayback})
}
}()
}
})
}

func (p *PlaybackManager) CurrentPlayer() player.BasePlayer {
return p.engine.CurrentPlayer()
}
Expand Down Expand Up @@ -430,6 +457,11 @@ func (p *PlaybackManager) runCmdQueue(ctx context.Context) {
c.Arg.(*mediaprovider.RadioStation),
c.Arg2.(InsertQueueMode),
)
case cmdForceRestartPlayback:
if mpv, ok := p.engine.CurrentPlayer().(*mpv.Player); ok {
log.Println("Force-restarting MPV playback")
mpv.ForceRestartPlayback()
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions backend/player/mpv/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ func (p *Player) Continue() error {
return nil
}

func (p *Player) ForceRestartPlayback() error {
p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, true)
return p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, false)
}

// Get the current status of the player.
func (p *Player) GetStatus() player.Status {
if !p.initialized {
Expand Down

0 comments on commit e8737f4

Please sign in to comment.