diff --git a/backend/playbackengine.go b/backend/playbackengine.go index 37f315d..8f67546 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -210,26 +210,37 @@ func (p *playbackEngine) Continue() error { return p.player.Continue() } +// Load items into the play queue. +// If replacing the current queue (!appendToQueue), playback will be stopped. +func (p *playbackEngine) LoadItems(items []mediaprovider.MediaItem, insertQueueMode InsertQueueMode, shuffle bool) error { + newItems := p.deepCopyMediaItemSlice(items) + return p.doLoaditems(newItems, insertQueueMode, shuffle) +} + // Load tracks into the play queue. // If replacing the current queue (!appendToQueue), playback will be stopped. func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMode InsertQueueMode, shuffle bool) error { + newTracks := p.copyTrackSliceToMediaItemSlice(tracks) + return p.doLoaditems(newTracks, insertQueueMode, shuffle) +} + +func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueueMode InsertQueueMode, shuffle bool) error { if insertQueueMode == Replace { p.player.Stop() p.nowPlayingIdx = -1 p.playQueue = nil } - needToSetNext := len(tracks) > 0 && (insertQueueMode == InsertNext || (insertQueueMode == Append && p.nowPlayingIdx == len(p.playQueue)-1)) + needToSetNext := len(items) > 0 && (insertQueueMode == InsertNext || (insertQueueMode == Append && p.nowPlayingIdx == len(p.playQueue)-1)) - newTracks := p.copyTrackSliceToMediaItemSlice(tracks) if shuffle { - rand.Shuffle(len(newTracks), func(i, j int) { newTracks[i], newTracks[j] = newTracks[j], newTracks[i] }) + rand.Shuffle(len(items), func(i, j int) { items[i], items[j] = items[j], items[i] }) } insertIdx := len(p.playQueue) if insertQueueMode == InsertNext { insertIdx = p.nowPlayingIdx + 1 } - p.playQueue = append(p.playQueue[:insertIdx], append(newTracks, p.playQueue[insertIdx:]...)...) + p.playQueue = append(p.playQueue[:insertIdx], append(items, p.playQueue[insertIdx:]...)...) if needToSetNext { p.setNextTrack(p.nowPlayingIdx + 1) diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 9d06e67..95005ad 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -123,6 +123,12 @@ func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, insertQueueM return p.engine.LoadTracks(tracks, insertQueueMode, shuffle) } +// Load items into the play queue. +// If replacing the current queue (!appendToQueue), playback will be stopped. +func (p *PlaybackManager) LoadItems(items []mediaprovider.MediaItem, insertQueueMode InsertQueueMode, shuffle bool) error { + return p.engine.LoadItems(items, insertQueueMode, shuffle) +} + // 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. diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 6831bfc..a0f81e8 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -127,8 +127,8 @@ func NewNowPlayingPage( a.contr.SetTrackRatings([]string{a.nowPlayingID}, rating) } - a.queueList = widgets.NewPlayQueueList(a.im) - a.relatedList = widgets.NewPlayQueueList(a.im) + a.queueList = widgets.NewPlayQueueList(a.im, false) + a.relatedList = widgets.NewPlayQueueList(a.im, true) a.queueList.OnReorderItems = a.doSetNewTrackOrder a.queueList.OnDownload = contr.ShowDownloadDialog a.queueList.OnShare = func(tracks []*mediaprovider.Track) { @@ -137,7 +137,7 @@ func NewNowPlayingPage( } } a.queueList.OnAddToPlaylist = contr.DoAddTracksToPlaylistWorkflow - a.queueList.OnPlayTrackAt = func(tracknum int) { + a.queueList.OnPlayItemAt = func(tracknum int) { _ = a.pm.PlayTrackAt(tracknum) } a.queueList.OnShowArtistPage = func(artistID string) { @@ -163,14 +163,22 @@ func NewNowPlayingPage( a.relatedList.OnShare = a.queueList.OnShare a.relatedList.OnAddToPlaylist = a.queueList.OnAddToPlaylist a.relatedList.OnShowArtistPage = a.queueList.OnShowArtistPage - a.relatedList.OnRemoveFromQueue = a.queueList.OnRemoveFromQueue a.relatedList.OnSetRating = a.queueList.OnSetRating a.relatedList.OnSetFavorite = a.queueList.OnSetFavorite - a.relatedList.OnPlayTrackAt = func(idx int) { - a.pm.LoadTracks(a.related, backend.Replace, false) + a.relatedList.OnPlayItemAt = func(idx int) { + a.pm.LoadTracks([]*mediaprovider.Track{a.related[idx]}, backend.Replace, false) a.pm.PlayTrackAt(idx) } - + a.relatedList.OnAddToQueue = func(items []mediaprovider.MediaItem) { + a.pm.LoadItems(items, backend.Append, false) + } + a.relatedList.OnPlaySelection = func(items []mediaprovider.MediaItem, shuffle bool) { + a.pm.LoadItems(items, backend.Replace, shuffle) + a.pm.PlayFromBeginning() + } + a.relatedList.OnPlaySelectionNext = func(items []mediaprovider.MediaItem) { + a.pm.LoadItems(items, backend.InsertNext, false) + } a.lyricsViewer = widgets.NewLyricsViewer() a.statusLabel = widget.NewLabel("Stopped") @@ -458,13 +466,29 @@ func (a *NowPlayingPage) OnPlayTimeUpdate(curTime, _ float64, seeked bool) { var _ CanSelectAll = (*NowPlayingPage)(nil) func (a *NowPlayingPage) SelectAll() { - a.queueList.SelectAll() + if a.tabs == nil { + return + } + switch a.tabs.SelectedIndex() { + case 0: /*queue*/ + a.queueList.SelectAll() + case 2: /*related*/ + a.relatedList.SelectAll() + } } var _ fyne.Tappable = (*NowPlayingPage)(nil) func (a *NowPlayingPage) Tapped(*fyne.PointEvent) { - a.queueList.UnselectAll() + if a.tabs == nil { + return + } + switch a.tabs.SelectedIndex() { + case 0: /*queue*/ + a.queueList.UnselectAll() + case 2: /*related*/ + a.relatedList.UnselectAll() + } } func (a *NowPlayingPage) Refresh() { diff --git a/ui/widgets/playqueuelist.go b/ui/widgets/playqueuelist.go index 402fb61..ed9f60c 100644 --- a/ui/widgets/playqueuelist.go +++ b/ui/widgets/playqueuelist.go @@ -35,31 +35,35 @@ type PlayQueueList struct { DisableSharing bool // user action callbacks - OnAddToPlaylist func(trackIDs []string) - OnSetFavorite func(trackIDs []string, fav bool) - OnSetRating func(trackIDs []string, rating int) - OnRemoveFromQueue func(itemIDs []string) - OnDownload func(tracks []*mediaprovider.Track, downloadName string) - OnShare func(tracks []*mediaprovider.Track) - OnShowArtistPage func(artistID string) - OnPlayTrackAt func(idx int) - OnReorderItems func(itemIDs []string, op sharedutil.TrackReorderOp) + OnPlayItemAt func(idx int) + OnPlaySelection func(items []mediaprovider.MediaItem, shuffle bool) + OnPlaySelectionNext func(items []mediaprovider.MediaItem) + OnAddToQueue func(items []mediaprovider.MediaItem) + OnAddToPlaylist func(trackIDs []string) + OnSetFavorite func(trackIDs []string, fav bool) + OnSetRating func(trackIDs []string, rating int) + OnRemoveFromQueue func(itemIDs []string) + OnDownload func(tracks []*mediaprovider.Track, downloadName string) + OnShare func(tracks []*mediaprovider.Track) + OnShowArtistPage func(artistID string) + OnReorderItems func(itemIDs []string, op sharedutil.TrackReorderOp) - list *FocusList - menu *widget.PopUpMenu // ctx menu for when only tracks are selected - radiosMenu *widget.PopUpMenu // ctx menu for when selection contains radios - ratingSubmenu *fyne.MenuItem - shareMenuItem *fyne.MenuItem + useNonQueueMenu bool + menu *widget.PopUpMenu // ctx menu for when only tracks are selected + radiosMenu *widget.PopUpMenu // ctx menu for when selection contains radios + ratingSubmenu *fyne.MenuItem + shareMenuItem *fyne.MenuItem nowPlayingID string - colLayout *layouts.ColumnsLayout + list *FocusList + colLayout *layouts.ColumnsLayout tracksMutex sync.RWMutex items []*util.TrackListModel } -func NewPlayQueueList(im *backend.ImageManager) *PlayQueueList { - p := &PlayQueueList{} +func NewPlayQueueList(im *backend.ImageManager, useNonQueueMenu bool) *PlayQueueList { + p := &PlayQueueList{useNonQueueMenu: useNonQueueMenu} p.ExtendBaseWidget(p) // #, Cover, Title/Artist, Time @@ -164,8 +168,8 @@ func (t *PlayQueueList) onArtistTapped(artistID string) { } func (p *PlayQueueList) onPlayTrackAt(idx int) { - if p.OnPlayTrackAt != nil { - p.OnPlayTrackAt(idx) + if p.OnPlayItemAt != nil { + p.OnPlayItemAt(idx) } } @@ -233,6 +237,12 @@ func (p *PlayQueueList) ensureTracksMenu() { if p.menu != nil { return } + var menuItems []*fyne.MenuItem + if p.useNonQueueMenu { + menuItems = append(menuItems, p.createQueueActionMenuItems()...) + menuItems = append(menuItems, fyne.NewMenuItemSeparator()) + } + playlist := fyne.NewMenuItem("Add to playlist...", func() { if p.OnAddToPlaylist != nil { p.OnAddToPlaylist(p.selectedItemIDs()) @@ -268,31 +278,36 @@ func (p *PlayQueueList) ensureTracksMenu() { p.OnSetRating(p.selectedItemIDs(), rating) } }) - remove := fyne.NewMenuItem("Remove from queue", func() { - if p.OnRemoveFromQueue != nil { - p.OnRemoveFromQueue(p.selectedItemIDs()) - } - }) - remove.Icon = theme.ContentRemoveIcon() - reorder := util.NewReorderTracksSubmenu(func(tro sharedutil.TrackReorderOp) { - if p.OnReorderItems != nil { - p.OnReorderItems(p.selectedItemIDs(), tro) - } - }) + menuItems = append(menuItems, + playlist, + download, + p.shareMenuItem, + fyne.NewMenuItemSeparator(), + favorite, + unfavorite, + p.ratingSubmenu, + ) + + if !p.useNonQueueMenu { + remove := fyne.NewMenuItem("Remove from queue", func() { + if p.OnRemoveFromQueue != nil { + p.OnRemoveFromQueue(p.selectedItemIDs()) + } + }) + remove.Icon = theme.ContentRemoveIcon() + reorder := util.NewReorderTracksSubmenu(func(tro sharedutil.TrackReorderOp) { + if p.OnReorderItems != nil { + p.OnReorderItems(p.selectedItemIDs(), tro) + } + }) + menuItems = append(menuItems, + fyne.NewMenuItemSeparator(), + remove, + reorder) + } p.menu = widget.NewPopUpMenu( - fyne.NewMenu("", - playlist, - download, - p.shareMenuItem, - fyne.NewMenuItemSeparator(), - favorite, - unfavorite, - p.ratingSubmenu, - fyne.NewMenuItemSeparator(), - reorder, - remove, - ), + fyne.NewMenu("", menuItems...), fyne.CurrentApp().Driver().CanvasForObject(p), ) } @@ -318,6 +333,34 @@ func (p *PlayQueueList) ensureRadiosMenu() { ) } +func (p *PlayQueueList) createQueueActionMenuItems() []*fyne.MenuItem { + play := fyne.NewMenuItem("Play", func() { + if p.OnPlaySelection != nil { + p.OnPlaySelection(p.selectedItems(), false) + } + }) + play.Icon = theme.MediaPlayIcon() + shuffle := fyne.NewMenuItem("Shuffle", func() { + if p.OnPlaySelection != nil { + p.OnPlaySelection(p.selectedItems(), true) + } + }) + shuffle.Icon = myTheme.ShuffleIcon + playNext := fyne.NewMenuItem("Play next", func() { + if p.OnPlaySelection != nil { + p.OnPlaySelectionNext(p.selectedItems()) + } + }) + playNext.Icon = myTheme.PlayNextIcon + add := fyne.NewMenuItem("Add to queue", func() { + if p.OnPlaySelection != nil { + p.OnAddToQueue(p.selectedItems()) + } + }) + add.Icon = theme.ContentAddIcon() + return []*fyne.MenuItem{play, shuffle, playNext, add} +} + func (t *PlayQueueList) selectedItems() []mediaprovider.MediaItem { t.tracksMutex.RLock() defer t.tracksMutex.RUnlock()