Merge pull request #793 from Tim-Kaiser/ISSUE/787

Fixes #787 : StopAfterCurrent shouldn't clear the queue
This commit is contained in:
Drew Weymouth
2026-01-06 16:15:30 -08:00
committed by GitHub
12 changed files with 75 additions and 66 deletions
+2 -2
View File
@@ -598,8 +598,8 @@ func (a *App) checkFlagsAndSendIPCMsg(cli *ipc.Client) error {
return cli.SeekNext() return cli.SeekNext()
case *FlagStop: case *FlagStop:
return cli.Stop() return cli.Stop()
case *FlagStopAfterCurrent: case *FlagPauseAfterCurrent:
return cli.StopAfterCurrent() return cli.PauseAfterCurrent()
case *FlagShow: case *FlagShow:
return cli.Show() return cli.Show()
case VolumeCLIArg >= 0: case VolumeCLIArg >= 0:
+12 -12
View File
@@ -22,18 +22,18 @@ var (
SearchPlaylistCLIArg string = "" SearchPlaylistCLIArg string = ""
SearchTrackCLIArg string = "" SearchTrackCLIArg string = ""
FlagPlay = flag.Bool("play", false, "unpause or begin playback") FlagPlay = flag.Bool("play", false, "unpause or begin playback")
FlagPause = flag.Bool("pause", false, "pause playback") FlagPause = flag.Bool("pause", false, "pause playback")
FlagPlayPause = flag.Bool("play-pause", false, "toggle play/pause state") FlagPlayPause = flag.Bool("play-pause", false, "toggle play/pause state")
FlagPrevious = flag.Bool("previous", false, "seek to previous track or beginning of current") FlagPrevious = flag.Bool("previous", false, "seek to previous track or beginning of current")
FlagNext = flag.Bool("next", false, "seek to next track") FlagNext = flag.Bool("next", false, "seek to next track")
FlagStop = flag.Bool("stop", false, "stop playback") FlagStop = flag.Bool("stop", false, "stop playback")
FlagStopAfterCurrent = flag.Bool("stop-after-current", false, "stop playback after current track") FlagPauseAfterCurrent = flag.Bool("pause-after-current", false, "pause playback after current track")
FlagStartMinimized = flag.Bool("start-minimized", false, "start app minimized") FlagStartMinimized = flag.Bool("start-minimized", false, "start app minimized")
FlagShow = flag.Bool("show", false, "show minimized app") FlagShow = flag.Bool("show", false, "show minimized app")
FlagShuffle = flag.Bool("shuffle", false, "shuffle the tracklist (to be used with either -play-album-by-id or -play-playlist-by-id)") FlagShuffle = flag.Bool("shuffle", false, "shuffle the tracklist (to be used with either -play-album-by-id or -play-playlist-by-id)")
FlagVersion = flag.Bool("version", false, "print app version and exit") FlagVersion = flag.Bool("version", false, "print app version and exit")
FlagHelp = flag.Bool("help", false, "print command line options and exit") FlagHelp = flag.Bool("help", false, "print command line options and exit")
FlagPlayAlbum *bool FlagPlayAlbum *bool
FlagPlayPlaylist *bool FlagPlayPlaylist *bool
+20 -20
View File
@@ -7,26 +7,26 @@ import (
) )
const ( const (
PingPath = "/ping" PingPath = "/ping"
PlayPath = "/transport/play" PlayPath = "/transport/play"
PlayAlbumPath = "/transport/play-album" // ?id=<album ID>&t=<firstTrack>&s=<shuffle> PlayAlbumPath = "/transport/play-album" // ?id=<album ID>&t=<firstTrack>&s=<shuffle>
PlayPlaylistPath = "/transport/play-playlist" // ?id=<playlist ID>&t=<firstTrack>&s=<shuffle> PlayPlaylistPath = "/transport/play-playlist" // ?id=<playlist ID>&t=<firstTrack>&s=<shuffle>
PlayTrackPath = "/transport/play-track" // ?id=<track ID> PlayTrackPath = "/transport/play-track" // ?id=<track ID>
SearchAlbumPath = "/transport/search-album" // ?s=<searchQuery> SearchAlbumPath = "/transport/search-album" // ?s=<searchQuery>
SearchPlaylistPath = "/transport/search-playlist" // ?s=<searchQuery> SearchPlaylistPath = "/transport/search-playlist" // ?s=<searchQuery>
SearchTrackPath = "/transport/search-track" // ?s=<searchQuery> SearchTrackPath = "/transport/search-track" // ?s=<searchQuery>
PlayPausePath = "/transport/playpause" PlayPausePath = "/transport/playpause"
PausePath = "/transport/pause" PausePath = "/transport/pause"
StopPath = "/transport/stop" StopPath = "/transport/stop"
StopAfterCurrentPath = "/transport/stop-after-current" PauseAfterCurrentPath = "/transport/pause-after-current"
PreviousPath = "/transport/previous" PreviousPath = "/transport/previous"
NextPath = "/transport/next" NextPath = "/transport/next"
TimePosPath = "/transport/timepos" // ?s=<seconds> TimePosPath = "/transport/timepos" // ?s=<seconds>
SeekByPath = "/transport/seek-by" // ?s=<+/- seconds> SeekByPath = "/transport/seek-by" // ?s=<+/- seconds>
VolumePath = "/volume" // ?v=<vol> VolumePath = "/volume" // ?v=<vol>
VolumeAdjustPath = "/volume/adjust" // ?pct=<+/- percentage> VolumeAdjustPath = "/volume/adjust" // ?pct=<+/- percentage>
ShowPath = "/window/show" ShowPath = "/window/show"
QuitPath = "/window/quit" QuitPath = "/window/quit"
) )
type Response struct { type Response struct {
+2 -2
View File
@@ -83,8 +83,8 @@ func (c *Client) Stop() error {
return err return err
} }
func (c *Client) StopAfterCurrent() error { func (c *Client) PauseAfterCurrent() error {
_, err := c.sendRequest(StopAfterCurrentPath) _, err := c.sendRequest(PauseAfterCurrentPath)
return err return err
} }
+3 -3
View File
@@ -21,7 +21,7 @@ type PlaybackHandler interface {
Continue() Continue()
SeekBackOrPrevious() SeekBackOrPrevious()
SeekNext() SeekNext()
SetStopAfterCurrent(bool) SetPauseAfterCurrent(bool)
SeekSeconds(float64) SeekSeconds(float64)
SeekBySeconds(float64) SeekBySeconds(float64)
Volume() int Volume() int
@@ -83,8 +83,8 @@ func (s *serverImpl) createHandler() http.Handler {
m.HandleFunc(PausePath, s.makeSimpleEndpointHandler(s.pbHandler.Pause)) m.HandleFunc(PausePath, s.makeSimpleEndpointHandler(s.pbHandler.Pause))
m.HandleFunc(PlayPausePath, s.makeSimpleEndpointHandler(s.pbHandler.PlayPause)) m.HandleFunc(PlayPausePath, s.makeSimpleEndpointHandler(s.pbHandler.PlayPause))
m.HandleFunc(StopPath, s.makeSimpleEndpointHandler(s.pbHandler.Stop)) m.HandleFunc(StopPath, s.makeSimpleEndpointHandler(s.pbHandler.Stop))
m.HandleFunc(StopAfterCurrentPath, s.makeSimpleEndpointHandler(func() { m.HandleFunc(PauseAfterCurrentPath, s.makeSimpleEndpointHandler(func() {
s.pbHandler.SetStopAfterCurrent(true) s.pbHandler.SetPauseAfterCurrent(true)
})) }))
m.HandleFunc(PreviousPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekBackOrPrevious)) m.HandleFunc(PreviousPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekBackOrPrevious))
m.HandleFunc(NextPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekNext)) m.HandleFunc(NextPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekNext))
+11 -10
View File
@@ -66,7 +66,7 @@ type playbackEngine struct {
isRadio bool isRadio bool
loopMode LoopMode loopMode LoopMode
stopAfterCurrent bool // flag to stop playback after current track ends pauseAfterCurrent bool // flag to pause playback after current track ends
// flags for handleOnTrackChange / handleOnStopped callbacks - reset to false in the callbacks // flags for handleOnTrackChange / handleOnStopped callbacks - reset to false in the callbacks
wasStopped bool // true iff player was stopped before handleOnTrackChange invocation wasStopped bool // true iff player was stopped before handleOnTrackChange invocation
@@ -337,13 +337,8 @@ func (p *playbackEngine) Stop() error {
return p.player.Stop(false) return p.player.Stop(false)
} }
func (p *playbackEngine) SetStopAfterCurrent(stopAfterCurrent bool) { func (p *playbackEngine) SetPauseAfterCurrent(pauseAfterCurrent bool) {
p.stopAfterCurrent = stopAfterCurrent p.pauseAfterCurrent = pauseAfterCurrent
if p.stopAfterCurrent {
p.setNextTrack(-1) // clear next playing track from internal player, if any
} else if p.loopMode != LoopNone || p.nowPlayingIdx < len(p.playQueue)-1 {
p.needToSetNextTrack = true // need to restore next track to internal player queue
}
} }
func (p *playbackEngine) Pause() error { func (p *playbackEngine) Pause() error {
@@ -632,6 +627,12 @@ func (p *playbackEngine) handleOnTrackChange() {
p.invokeOnSongChangeCallbacks() p.invokeOnSongChangeCallbacks()
p.handleTimePosUpdate(false) p.handleTimePosUpdate(false)
p.handleNextTrackUpdated() p.handleNextTrackUpdated()
if p.pauseAfterCurrent {
p.Pause()
p.SetPauseAfterCurrent(false)
}
} }
func (p *playbackEngine) handleOnStopped() { func (p *playbackEngine) handleOnStopped() {
@@ -646,7 +647,7 @@ func (p *playbackEngine) handleOnStopped() {
p.alreadyScrobbled = false p.alreadyScrobbled = false
p.wasStopped = true p.wasStopped = true
p.nowPlayingIdx = -1 p.nowPlayingIdx = -1
p.stopAfterCurrent = false p.pauseAfterCurrent = false
} }
// to be invoked as soon as the next item in the queue that should play changes // to be invoked as soon as the next item in the queue that should play changes
@@ -868,7 +869,7 @@ func (p *playbackEngine) handleTimePosUpdate(seeked bool) {
meta = np.Metadata() meta = np.Metadata()
} }
isNearEnd := meta.Type != mediaprovider.MediaItemTypeRadioStation && s.TimePos > meta.Duration.Seconds()-10 isNearEnd := meta.Type != mediaprovider.MediaItemTypeRadioStation && s.TimePos > meta.Duration.Seconds()-10
if p.needToSetNextTrack && !p.stopAfterCurrent && isNearEnd { if p.needToSetNextTrack && isNearEnd {
p.needToSetNextTrack = false p.needToSetNextTrack = false
p.setNextTrack(p.nextPlayingIndex()) p.setNextTrack(p.nextPlayingIndex())
} }
+12 -5
View File
@@ -687,12 +687,12 @@ func (p *PlaybackManager) PlayPause() {
} }
} }
func (p *PlaybackManager) SetStopAfterCurrent(stopAfterCurrent bool) { func (p *PlaybackManager) SetPauseAfterCurrent(pauseAfterCurrent bool) {
p.engine.SetStopAfterCurrent(stopAfterCurrent) p.engine.SetPauseAfterCurrent(pauseAfterCurrent)
} }
func (p *PlaybackManager) IsStopAfterCurrent() bool { func (p *PlaybackManager) IsPauseAfterCurrent() bool {
return p.engine.stopAfterCurrent return p.engine.pauseAfterCurrent
} }
func (p *PlaybackManager) enqueueAutoplayTracks() { func (p *PlaybackManager) enqueueAutoplayTracks() {
@@ -827,7 +827,14 @@ func (p *PlaybackManager) runCmdQueue(ctx context.Context) {
case cmdForceRestartPlayback: case cmdForceRestartPlayback:
if mpv, ok := p.engine.CurrentPlayer().(*mpv.Player); ok { if mpv, ok := p.engine.CurrentPlayer().(*mpv.Player); ok {
log.Println("Force-restarting MPV playback") log.Println("Force-restarting MPV playback")
mpv.ForceRestartPlayback()
// restart player, but perserve the state
isPaused := false
stat := p.engine.CurrentPlayer().GetStatus()
if stat.State == player.Paused {
isPaused = true
}
mpv.ForceRestartPlayback(isPaused)
} }
} }
if c.OnDone != nil { if c.OnDone != nil {
+3 -2
View File
@@ -349,9 +349,10 @@ func (p *Player) Continue() error {
return nil return nil
} }
func (p *Player) ForceRestartPlayback() error { func (p *Player) ForceRestartPlayback(isPaused bool) error {
p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, true) p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, true)
return p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, false) p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, false)
return p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, isPaused)
} }
// Get the current status of the player. // Get the current status of the player.
+1
View File
@@ -229,6 +229,7 @@
"Unset favorite": "Von Favoriten entfernen", "Unset favorite": "Von Favoriten entfernen",
"Remove from queue": "Aus Wiedergabeliste entfernen", "Remove from queue": "Aus Wiedergabeliste entfernen",
"Play song radio": "Liedradio abspielen", "Play song radio": "Liedradio abspielen",
"Pause after current track": "Nach dem aktuellen Lied pausieren",
"to": "bis", "to": "bis",
"by": "von", "by": "von",
"Jan": "Jan", "Jan": "Jan",
+1 -1
View File
@@ -228,7 +228,7 @@
"Soundtrack": "Soundtrack", "Soundtrack": "Soundtrack",
"Spoken Word": "Spoken Word", "Spoken Word": "Spoken Word",
"Startup page": "Startup page", "Startup page": "Startup page",
"Stop after current track": "Stop after current track", "Pause after current track": "Pause after current track",
"Stopped": "Stopped", "Stopped": "Stopped",
"Success": "Success", "Success": "Success",
"Successfully created playlist": "Successfully created playlist", "Successfully created playlist": "Successfully created playlist",
+2 -3
View File
@@ -219,7 +219,7 @@
"Soundtrack": "Bande originale", "Soundtrack": "Bande originale",
"Spoken Word": "Spoken Word", "Spoken Word": "Spoken Word",
"Startup page": "Page de démarrage", "Startup page": "Page de démarrage",
"Stop after current track": "Arrêter après la piste en cours", "Pause after current track": "Arrêter après la piste en cours",
"Stopped": "Arrêté", "Stopped": "Arrêté",
"Success": "Succès", "Success": "Succès",
"Support the project": "Soutenir le projet", "Support the project": "Soutenir le projet",
@@ -283,9 +283,8 @@
"Oct": "Oct", "Oct": "Oct",
"Nov": "Nov", "Nov": "Nov",
"Dec": "Déc", "Dec": "Déc",
"playlist.addedtracks": { "playlist.addedtracks": {
"one": "Une piste ajoutée à la liste de lecture", "one": "Une piste ajoutée à la liste de lecture",
"other": "{{.trackCount}} pistes ajoutées à la liste de lecture" "other": "{{.trackCount}} pistes ajoutées à la liste de lecture"
} }
} }
+6 -6
View File
@@ -59,7 +59,7 @@ type Controller struct {
popUpQueue *widget.PopUp popUpQueue *widget.PopUp
popUpQueueList *widgets.PlayQueueList popUpQueueList *widgets.PlayQueueList
stopAfterCurrent *widget.Check pauseAfterCurrent *widget.Check
popUpQueueLastUsed int64 popUpQueueLastUsed int64
escapablePopUp fyne.CanvasObject escapablePopUp fyne.CanvasObject
haveModal bool haveModal bool
@@ -198,10 +198,10 @@ func (m *Controller) ShowPopUpPlayQueue() {
title := widget.NewRichTextWithText(lang.L("Play Queue")) title := widget.NewRichTextWithText(lang.L("Play Queue"))
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true
m.stopAfterCurrent = widget.NewCheck(lang.L("Stop after current track"), func(b bool) { m.pauseAfterCurrent = widget.NewCheck(lang.L("Pause after current track"), func(b bool) {
m.App.PlaybackManager.SetStopAfterCurrent(b) m.App.PlaybackManager.SetPauseAfterCurrent(b)
}) })
bottomRow := container.NewHBox(layout.NewSpacer(), m.stopAfterCurrent) bottomRow := container.NewHBox(layout.NewSpacer(), m.pauseAfterCurrent)
ctr := container.NewBorder(title, bottomRow, nil, nil, ctr := container.NewBorder(title, bottomRow, nil, nil,
container.NewPadded(m.popUpQueueList), container.NewPadded(m.popUpQueueList),
) )
@@ -231,7 +231,7 @@ func (m *Controller) ShowPopUpPlayQueue() {
fynetooltip.DestroyPopUpToolTipLayer(m.popUpQueue) fynetooltip.DestroyPopUpToolTipLayer(m.popUpQueue)
m.popUpQueue = nil m.popUpQueue = nil
m.popUpQueueList = nil m.popUpQueueList = nil
m.stopAfterCurrent = nil m.pauseAfterCurrent = nil
m.popUpQueueLastUsed = 0 m.popUpQueueLastUsed = 0
t.Stop() t.Stop()
return return
@@ -260,7 +260,7 @@ func (m *Controller) ShowPopUpPlayQueue() {
)) ))
pop.Resize(size) pop.Resize(size)
popUpQueueList.ScrollToNowPlaying() // must come after resize popUpQueueList.ScrollToNowPlaying() // must come after resize
m.stopAfterCurrent.SetChecked(m.App.PlaybackManager.IsStopAfterCurrent()) m.pauseAfterCurrent.SetChecked(m.App.PlaybackManager.IsPauseAfterCurrent())
pop.ShowAtPosition(fyne.NewPos( pop.ShowAtPosition(fyne.NewPos(
canvasSize.Width-size.Width-10, canvasSize.Width-size.Width-10,
canvasSize.Height-size.Height-100, canvasSize.Height-size.Height-100,