From 63bdfb5f8703d4fd63110bbb314493d01edaa382 Mon Sep 17 00:00:00 2001 From: Tanguy Rossel Date: Wed, 3 Apr 2024 17:37:22 +0200 Subject: [PATCH] WIP: feat: add "Play next" MenuItem --- backend/app.go | 2 +- backend/playbackengine.go | 21 +++++++++++++++++---- backend/playbackmanager.go | 20 ++++++++++---------- ui/browsing/albumpage.go | 4 ++-- ui/browsing/playlistpage.go | 12 ++++++++---- ui/browsing/playlistspage.go | 2 +- ui/controller/controller.go | 25 +++++++++++++++++-------- ui/widgets/gridview.go | 9 ++++++++- ui/widgets/tracklist.go | 27 +++++++++++++++++---------- 9 files changed, 81 insertions(+), 41 deletions(-) diff --git a/backend/app.go b/backend/app.go index 9034890..ac5e0f5 100644 --- a/backend/app.go +++ b/backend/app.go @@ -324,7 +324,7 @@ func (a *App) LoadSavedPlayQueue() error { return nil } - if err := a.PlaybackManager.LoadTracks(queue.Tracks, false, false); err != nil { + if err := a.PlaybackManager.LoadTracks(queue.Tracks, Replace, false); err != nil { return err } if queue.TrackIndex >= 0 { diff --git a/backend/playbackengine.go b/backend/playbackengine.go index 04eaed9..3cb61ac 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -20,6 +20,14 @@ var ( ReplayGainAuto = "Auto" ) +type InsertQueueMode int + +const ( + Replace InsertQueueMode = iota + InsertNext + Append +) + // The playback loop mode (LoopNone, LoopAll, LoopOne). type LoopMode int @@ -200,19 +208,24 @@ func (p *playbackEngine) Continue() error { // Load tracks into the play queue. // If replacing the current queue (!appendToQueue), playback will be stopped. -func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, appendToQueue, shuffle bool) error { - if !appendToQueue { +func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMode InsertQueueMode, shuffle bool) error { + if insertQueueMode == Replace { p.player.Stop() p.nowPlayingIdx = -1 p.playQueue = nil } - needToSetNext := appendToQueue && len(tracks) > 0 && p.nowPlayingIdx == len(p.playQueue)-1 + needToSetNext := len(tracks) > 0 && (insertQueueMode == InsertNext || (insertQueueMode == Append && p.nowPlayingIdx == len(p.playQueue)-1)) newTracks := p.deepCopyTrackSlice(tracks) if shuffle { rand.Shuffle(len(newTracks), func(i, j int) { newTracks[i], newTracks[j] = newTracks[j], newTracks[i] }) } - p.playQueue = append(p.playQueue, newTracks...) + + insertIdx := len(p.playQueue) + if insertQueueMode == InsertNext { + insertIdx = p.nowPlayingIdx + 1 + } + p.playQueue = append(p.playQueue[:insertIdx], append(newTracks, p.playQueue[insertIdx:]...)...) if needToSetNext { p.setNextTrack(p.nowPlayingIdx + 1) diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index ae1c70d..4f577c2 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -100,27 +100,27 @@ func (p *PlaybackManager) OnPlaying(cb func()) { } // Loads the specified album into the play queue. -func (p *PlaybackManager) LoadAlbum(albumID string, appendToQueue bool, shuffle bool) error { +func (p *PlaybackManager) LoadAlbum(albumID string, insertQueueMode InsertQueueMode, shuffle bool) error { album, err := p.engine.sm.Server.GetAlbum(albumID) if err != nil { return err } - return p.LoadTracks(album.Tracks, appendToQueue, shuffle) + return p.LoadTracks(album.Tracks, insertQueueMode, shuffle) } // Loads the specified playlist into the play queue. -func (p *PlaybackManager) LoadPlaylist(playlistID string, appendToQueue bool, shuffle bool) error { +func (p *PlaybackManager) LoadPlaylist(playlistID string, insertQueueMode InsertQueueMode, shuffle bool) error { playlist, err := p.engine.sm.Server.GetPlaylist(playlistID) if err != nil { return err } - return p.LoadTracks(playlist.Tracks, appendToQueue, shuffle) + return p.LoadTracks(playlist.Tracks, insertQueueMode, shuffle) } // Load tracks into the play queue. // If replacing the current queue (!appendToQueue), playback will be stopped. -func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, appendToQueue, shuffle bool) error { - return p.engine.LoadTracks(tracks, appendToQueue, shuffle) +func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, insertQueueMode InsertQueueMode, shuffle bool) error { + return p.engine.LoadTracks(tracks, insertQueueMode, shuffle) } // Replaces the play queue with the given set of tracks. @@ -131,7 +131,7 @@ func (p *PlaybackManager) UpdatePlayQueue(tracks []*mediaprovider.Track) error { } func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int, shuffle bool) error { - if err := p.LoadAlbum(albumID, false, shuffle); err != nil { + if err := p.LoadAlbum(albumID, Replace, shuffle); err != nil { return err } if p.engine.replayGainCfg.Mode == ReplayGainAuto { @@ -141,7 +141,7 @@ func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int, shuffle bool } func (p *PlaybackManager) PlayPlaylist(playlistID string, firstTrack int, shuffle bool) error { - if err := p.LoadPlaylist(playlistID, false, shuffle); err != nil { + if err := p.LoadPlaylist(playlistID, Replace, shuffle); err != nil { return err } if p.engine.replayGainCfg.Mode == ReplayGainAuto { @@ -155,7 +155,7 @@ func (p *PlaybackManager) PlayTrack(trackID string) error { if err != nil { return err } - p.LoadTracks([]*mediaprovider.Track{tr}, false, false) + p.LoadTracks([]*mediaprovider.Track{tr}, Replace, false) if p.engine.replayGainCfg.Mode == ReplayGainAuto { p.SetReplayGainMode(player.ReplayGainTrack) } @@ -186,7 +186,7 @@ func (p *PlaybackManager) fetchAndPlayTracks(fetchFn func() ([]*mediaprovider.Tr if songs, err := fetchFn(); err != nil { log.Printf("error fetching tracks: %s", err.Error()) } else { - p.LoadTracks(songs, false, false) + p.LoadTracks(songs, Replace, false) if p.engine.replayGainCfg.Mode == ReplayGainAuto { p.SetReplayGainMode(player.ReplayGainTrack) } diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index ec61989..d564168 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -226,7 +226,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { go a.page.pm.PlayAlbum(a.page.albumID, 0, false) }) shuffleBtn := widget.NewButtonWithIcon("Shuffle", myTheme.ShuffleIcon, func() { - a.page.pm.LoadTracks(a.page.tracklist.GetTracks(), false, true) + a.page.pm.LoadTracks(a.page.tracklist.GetTracks(), backend.Replace, true) a.page.pm.PlayFromBeginning() }) var pop *widget.PopUpMenu @@ -234,7 +234,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { menuBtn.OnTapped = func() { if pop == nil { queue := fyne.NewMenuItem("Add to queue", func() { - go a.page.pm.LoadAlbum(a.albumID, true /*append*/, false /*shuffle*/) + go a.page.pm.LoadAlbum(a.albumID, backend.Append, false /*shuffle*/) }) queue.Icon = theme.ContentAddIcon() playlist := fyne.NewMenuItem("Add to playlist...", func() { diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index a4d6adc..66346f6 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -253,19 +253,23 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader { }) a.editButton.Hidden = true playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { - a.page.pm.LoadTracks(a.page.tracks, false, false) + a.page.pm.LoadTracks(a.page.tracks, backend.Replace, false) a.page.pm.PlayFromBeginning() }) shuffleBtn := widget.NewButtonWithIcon("Shuffle", myTheme.ShuffleIcon, func() { - a.page.pm.LoadTracks(a.page.tracks, false /*append*/, true /*shuffle*/) + a.page.pm.LoadTracks(a.page.tracks, backend.Replace, true) a.page.pm.PlayFromBeginning() }) var pop *widget.PopUpMenu menuBtn := widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil) menuBtn.OnTapped = func() { if pop == nil { + queue_next := fyne.NewMenuItem("Play next", func() { + go a.page.pm.LoadPlaylist(a.page.playlistID, backend.InsertNext, false) + }) + queue_next.Icon = theme.ContentAddIcon() queue := fyne.NewMenuItem("Add to queue", func() { - go a.page.pm.LoadPlaylist(a.page.playlistID, true /*append*/, false /*shuffle*/) + go a.page.pm.LoadPlaylist(a.page.playlistID, backend.Append, false) }) queue.Icon = theme.ContentAddIcon() playlist := fyne.NewMenuItem("Add to playlist...", func() { @@ -277,7 +281,7 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader { a.page.contr.ShowDownloadDialog(a.page.tracks, a.titleLabel.String()) }) download.Icon = theme.DownloadIcon() - menu := fyne.NewMenu("", queue, playlist, download) + menu := fyne.NewMenu("", queue_next, queue, playlist, download) pop = widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(a)) } pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(menuBtn) diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index d65cfde..1e0aeb3 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -123,7 +123,7 @@ func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) { go a.contr.App.PlaybackManager.PlayPlaylist(id, 0, shuffle) } a.gridView.OnAddToQueue = func(id string) { - go a.contr.App.PlaybackManager.LoadPlaylist(id, true, false) + go a.contr.App.PlaybackManager.LoadPlaylist(id, backend.Append, false) } a.gridView.OnShowItemPage = a.showPlaylistPage a.gridView.OnShowSecondaryPage = nil diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 44ad556..b598ace 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -113,18 +113,21 @@ func (m *Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) { func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widgets.Tracklist, mode player.ReplayGainMode) { tracklist.OnAddToPlaylist = m.DoAddTracksToPlaylistWorkflow + tracklist.OnPlaySelectionNext = func(tracks []*mediaprovider.Track) { + m.App.PlaybackManager.LoadTracks(tracks, backend.InsertNext, false) + } tracklist.OnAddToQueue = func(tracks []*mediaprovider.Track) { - m.App.PlaybackManager.LoadTracks(tracks, true, false) + m.App.PlaybackManager.LoadTracks(tracks, backend.Append, false) } tracklist.OnPlayTrackAt = func(idx int) { - m.App.PlaybackManager.LoadTracks(tracklist.GetTracks(), false, false) + m.App.PlaybackManager.LoadTracks(tracklist.GetTracks(), backend.Replace, false) if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto { m.App.PlaybackManager.SetReplayGainMode(mode) } m.App.PlaybackManager.PlayTrackAt(idx) } tracklist.OnPlaySelection = func(tracks []*mediaprovider.Track, shuffle bool) { - m.App.PlaybackManager.LoadTracks(tracks, false, shuffle) + m.App.PlaybackManager.LoadTracks(tracks, backend.Replace, shuffle) if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto { m.App.PlaybackManager.SetReplayGainMode(mode) } @@ -152,7 +155,7 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget log.Println("Error getting song radio: ", err) return } - m.App.PlaybackManager.LoadTracks(tracks, false, false) + m.App.PlaybackManager.LoadTracks(tracks, backend.Replace, false) if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto { m.App.PlaybackManager.SetReplayGainMode(mode) } @@ -163,7 +166,10 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { grid.OnAddToQueue = func(albumID string) { - go m.App.PlaybackManager.LoadAlbum(albumID, true, false) + go m.App.PlaybackManager.LoadAlbum(albumID, backend.Append, false) + } + grid.OnPlayNext = func(albumID string) { + go m.App.PlaybackManager.LoadAlbum(albumID, backend.InsertNext, false) } grid.OnPlay = func(albumID string, shuffle bool) { go m.App.PlaybackManager.PlayAlbum(albumID, 0, shuffle) @@ -201,9 +207,12 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { func (m *Controller) ConnectArtistGridActions(grid *widgets.GridView) { grid.OnShowItemPage = func(id string) { m.NavigateTo(ArtistRoute(id)) } + grid.OnPlayNext = func(artistID string) { + go m.App.PlaybackManager.LoadTracks(m.GetArtistTracks(artistID), backend.InsertNext, false) + } grid.OnPlay = func(artistID string, shuffle bool) { go m.PlayArtistDiscography(artistID, shuffle) } grid.OnAddToQueue = func(artistID string) { - go m.App.PlaybackManager.LoadTracks(m.GetArtistTracks(artistID), true /*append*/, false /*shuffle*/) + go m.App.PlaybackManager.LoadTracks(m.GetArtistTracks(artistID), backend.Append, false) } grid.OnAddToPlaylist = func(artistID string) { go m.DoAddTracksToPlaylistWorkflow( @@ -250,7 +259,7 @@ func (m *Controller) GetArtistTracks(artistID string) []*mediaprovider.Track { } func (m *Controller) PlayArtistDiscography(artistID string, shuffleTracks bool) { - m.App.PlaybackManager.LoadTracks(m.GetArtistTracks(artistID), false, shuffleTracks) + m.App.PlaybackManager.LoadTracks(m.GetArtistTracks(artistID), backend.Replace, shuffleTracks) if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto { if shuffleTracks { m.App.PlaybackManager.SetReplayGainMode(player.ReplayGainTrack) @@ -275,7 +284,7 @@ func (m *Controller) ShuffleArtistAlbums(artistID string) { }) m.App.PlaybackManager.StopAndClearPlayQueue() for _, al := range artist.Albums { - m.App.PlaybackManager.LoadAlbum(al.ID, true /*append*/, false /*shuffle*/) + m.App.PlaybackManager.LoadAlbum(al.ID, backend.Append, false) } if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto { diff --git a/ui/widgets/gridview.go b/ui/widgets/gridview.go index bfaae02..1ef6290 100644 --- a/ui/widgets/gridview.go +++ b/ui/widgets/gridview.go @@ -117,6 +117,7 @@ type GridViewState struct { DisableSharing bool OnPlay func(id string, shuffle bool) + OnPlayNext func(id string) OnAddToQueue func(id string) OnAddToPlaylist func(id string) OnDownload func(id string) @@ -403,6 +404,12 @@ func (g *GridView) showContextMenu(card *GridViewItem, pos fyne.Position) { play.Icon = theme.MediaPlayIcon() shuffle := fyne.NewMenuItem("Shuffle", func() { g.onPlay(g.menuGridViewItemId, true) }) shuffle.Icon = myTheme.ShuffleIcon + queue_next := fyne.NewMenuItem("Play next", func() { + if g.OnPlayNext != nil { + g.OnPlayNext(g.menuGridViewItemId) + } + }) + queue_next.Icon = theme.ContentAddIcon() queue := fyne.NewMenuItem("Add to queue", func() { if g.OnAddToQueue != nil { g.OnAddToQueue(g.menuGridViewItemId) @@ -425,7 +432,7 @@ func (g *GridView) showContextMenu(card *GridViewItem, pos fyne.Position) { g.OnShare(g.menuGridViewItemId) }) g.shareMenuItem.Icon = myTheme.ShareIcon - g.menu = widget.NewPopUpMenu(fyne.NewMenu("", play, shuffle, queue, playlist, download, g.shareMenuItem), + g.menu = widget.NewPopUpMenu(fyne.NewMenu("", play, shuffle, queue_next, queue, playlist, download, g.shareMenuItem), fyne.CurrentApp().Driver().CanvasForObject(g)) } g.shareMenuItem.Disabled = g.DisableSharing diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 4f0dbf6..f9d4746 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -84,15 +84,16 @@ type Tracklist struct { Options TracklistOptions // user action callbacks - OnPlayTrackAt func(int) - OnPlaySelection func(tracks []*mediaprovider.Track, shuffle bool) - OnAddToQueue func(trackIDs []*mediaprovider.Track) - OnAddToPlaylist func(trackIDs []string) - OnSetFavorite func(trackIDs []string, fav bool) - OnSetRating func(trackIDs []string, rating int) - OnDownload func(tracks []*mediaprovider.Track, downloadName string) - OnShare func(trackID string) - OnPlaySongRadio func(track *mediaprovider.Track) + OnPlayTrackAt func(int) + OnPlaySelection func(tracks []*mediaprovider.Track, shuffle bool) + OnPlaySelectionNext func(trackIDs []*mediaprovider.Track) + OnAddToQueue func(trackIDs []*mediaprovider.Track) + OnAddToPlaylist func(trackIDs []string) + OnSetFavorite func(trackIDs []string, fav bool) + OnSetRating func(trackIDs []string, rating int) + OnDownload func(tracks []*mediaprovider.Track, downloadName string) + OnShare func(trackID string) + OnPlaySongRadio func(track *mediaprovider.Track) OnShowArtistPage func(artistID string) OnShowAlbumPage func(albumID string) @@ -535,6 +536,12 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { } }) shuffle.Icon = myTheme.ShuffleIcon + play_next := fyne.NewMenuItem("Play next", func() { + if t.OnPlaySelection != nil { + t.OnPlaySelectionNext(t.selectedTracks()) + } + }) + play_next.Icon = theme.ContentAddIcon() add := fyne.NewMenuItem("Add to queue", func() { if t.OnPlaySelection != nil { t.OnAddToQueue(t.selectedTracks()) @@ -546,7 +553,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { }) t.songRadioMenuItem.Icon = myTheme.BroadcastIcon t.ctxMenu.Items = append(t.ctxMenu.Items, - play, shuffle, add, t.songRadioMenuItem) + play, shuffle, play_next, add, t.songRadioMenuItem) } playlist := fyne.NewMenuItem("Add to playlist...", func() { if t.OnAddToPlaylist != nil {