diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index e06a18f..22d1438 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -221,6 +221,42 @@ func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, appendToQueu return nil } +// Replaces the play queue with the given set of tracks. +// Does not stop playback if the currently playing track is in the new queue, +// but updates the now playing index to point to the first instance of the track in the new queue. +func (p *PlaybackManager) UpdatePlayQueue(tracks []*mediaprovider.Track) error { + newQueue := make([]*mediaprovider.Track, len(tracks)) + for i, tr := range tracks { + // ensure a deep copy of the track info so that we can maintain our own state + // (tracking play count increases, favorite, and rating) without messing up + // other views' track models + track := *tr + newQueue[i] = &track + } + newNowPlayingIdx := -1 + if p.nowPlayingIdx >= 0 { + nowPlayingID := p.playQueue[p.nowPlayingIdx].ID + for i, tr := range newQueue { + if tr.ID == nowPlayingID { + newNowPlayingIdx = i + break + } + } + } + + p.playQueue = newQueue + if p.nowPlayingIdx >= 0 && newNowPlayingIdx == -1 { + return p.Stop() + } + needToUpdateNext := p.nowPlayingIdx >= 0 + p.nowPlayingIdx = newNowPlayingIdx + if needToUpdateNext { + p.setNextTrackAfterQueueUpdate() + } + + return nil +} + func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int, shuffle bool) error { if err := p.LoadAlbum(albumID, false, shuffle); err != nil { return err @@ -563,6 +599,26 @@ func (p *PlaybackManager) setNextTrackBasedOnLoopMode(onLoopModeChange bool) { } } +func (p *PlaybackManager) setNextTrackAfterQueueUpdate() { + switch p.loopMode { + case LoopNone: + if p.nowPlayingIdx < len(p.playQueue)-1 { + p.setNextTrack(p.nowPlayingIdx + 1) + } else { + // need to erase next track + p.setNextTrack(-1) + } + case LoopOne: + p.setNextTrack(p.nowPlayingIdx) + case LoopAll: + if p.nowPlayingIdx >= len(p.playQueue)-1 { + p.setNextTrack(0) + } else { + p.setNextTrack(p.nowPlayingIdx + 1) + } + } +} + func (p *PlaybackManager) setTrack(idx int, next bool) error { if urlP, ok := p.player.(player.URLPlayer); ok { url := "" diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 4e1ad59..92d039d 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -77,6 +77,7 @@ func NewNowPlayingPage( DisablePlaybackMenu: true, DisableRating: !canRate, AuxiliaryMenuItems: []*fyne.MenuItem{ + util.NewReorderTracksSubmenu(a.doSetNewTrackOrder), fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue), }, } @@ -208,6 +209,26 @@ func (a *NowPlayingPage) onRemoveSelectedFromQueue() { a.Reload() } +func (a *NowPlayingPage) doSetNewTrackOrder(op sharedutil.TrackReorderOp) { + // Since the tracklist view may be sorted in a different order than the + // actual running order, we need to get the IDs of the selected tracks + // from the tracklist and convert them to indices in the *original* run order + ids := a.tracklist.SelectedTrackIDs() + idxs := make([]int, 0, len(ids)) + for i, tr := range a.queue { + if sharedutil.SliceContains(ids, tr.ID) { + idxs = append(idxs, i) + } + } + newTracks := sharedutil.ReorderTracks(a.queue, idxs, op) + a.pm.UpdatePlayQueue(newTracks) + + // force-switch back to unsorted view to show new track order + a.tracklist.SetSorting(widgets.TracklistSort{}) + a.tracklist.SetTracks(newTracks) + a.tracklist.UnselectAll() +} + // does not make calls to server - can safely be run in UI callbacks func (a *NowPlayingPage) load(highlightedTrackID string) { a.queue = a.pm.GetPlayQueue() diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index 0ed871c..c9df1a2 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -86,18 +86,13 @@ func newPlaylistPage( a.tracklist.OnVisibleColumnsChanged = func(cols []string) { conf.TracklistColumns = cols } - reorderMenu := fyne.NewMenuItem("Reorder tracks", nil) - reorderMenu.ChildMenu = fyne.NewMenu("", []*fyne.MenuItem{ - fyne.NewMenuItem("Move to top", a.onMoveSelectedToTop), - fyne.NewMenuItem("Move up", a.onMoveSelectedUp), - fyne.NewMenuItem("Move down", a.onMoveSelectedDown), - fyne.NewMenuItem("Move to bottom", a.onMoveSelectedToBottom), - }...) _, canRate := a.sm.Server.(mediaprovider.SupportsRating) a.tracklist.Options = widgets.TracklistOptions{ DisableRating: !canRate, - AuxiliaryMenuItems: []*fyne.MenuItem{reorderMenu, - fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)}, + AuxiliaryMenuItems: []*fyne.MenuItem{ + util.NewReorderTracksSubmenu(a.doSetNewTrackOrder), + fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist), + }, } // connect tracklist actions a.contr.ConnectTracklistActions(a.tracklist) @@ -171,22 +166,6 @@ func renumberTracks(tracks []*mediaprovider.Track) { } } -func (a *PlaylistPage) onMoveSelectedToTop() { - a.doSetNewTrackOrder(sharedutil.MoveToTop) -} - -func (a *PlaylistPage) onMoveSelectedUp() { - a.doSetNewTrackOrder(sharedutil.MoveUp) -} - -func (a *PlaylistPage) onMoveSelectedDown() { - a.doSetNewTrackOrder(sharedutil.MoveDown) -} - -func (a *PlaylistPage) onMoveSelectedToBottom() { - a.doSetNewTrackOrder(sharedutil.MoveToBottom) -} - func (a *PlaylistPage) doSetNewTrackOrder(op sharedutil.TrackReorderOp) { // Since the tracklist view may be sorted in a different order than the // actual running order, we need to get the IDs of the selected tracks diff --git a/ui/util/util.go b/ui/util/util.go index 643c02e..070491b 100644 --- a/ui/util/util.go +++ b/ui/util/util.go @@ -15,6 +15,7 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend/mediaprovider" + "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/layouts" myTheme "github.com/dweymouth/supersonic/ui/theme" "golang.org/x/net/html" @@ -180,6 +181,17 @@ func NewRatingSubmenu(onSetRating func(int)) *fyne.MenuItem { return ratingMenu } +func NewReorderTracksSubmenu(onReorderTracks func(sharedutil.TrackReorderOp)) *fyne.MenuItem { + reorderMenu := fyne.NewMenuItem("Reorder tracks", nil) + reorderMenu.ChildMenu = fyne.NewMenu("", []*fyne.MenuItem{ + fyne.NewMenuItem("Move to top", func() { onReorderTracks(sharedutil.MoveToTop) }), + fyne.NewMenuItem("Move up", func() { onReorderTracks(sharedutil.MoveUp) }), + fyne.NewMenuItem("Move down", func() { onReorderTracks(sharedutil.MoveDown) }), + fyne.NewMenuItem("Move to bottom", func() { onReorderTracks(sharedutil.MoveToBottom) }), + }...) + return reorderMenu +} + func AddHeaderBackground(obj fyne.CanvasObject) *fyne.Container { bgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageHeader) bgrnd.CornerRadiusName = theme.SizeNameInputRadius