Merge pull request #357 from tyrossel/play-next

feat: add "Play next" MenuItem
This commit is contained in:
Drew Weymouth
2024-04-11 16:23:11 -05:00
committed by GitHub
9 changed files with 81 additions and 41 deletions
+1 -1
View File
@@ -324,7 +324,7 @@ func (a *App) LoadSavedPlayQueue() error {
return nil 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 return err
} }
if queue.TrackIndex >= 0 { if queue.TrackIndex >= 0 {
+17 -4
View File
@@ -20,6 +20,14 @@ var (
ReplayGainAuto = "Auto" ReplayGainAuto = "Auto"
) )
type InsertQueueMode int
const (
Replace InsertQueueMode = iota
InsertNext
Append
)
// The playback loop mode (LoopNone, LoopAll, LoopOne). // The playback loop mode (LoopNone, LoopAll, LoopOne).
type LoopMode int type LoopMode int
@@ -200,19 +208,24 @@ func (p *playbackEngine) Continue() error {
// Load tracks into the play queue. // Load tracks into the play queue.
// If replacing the current queue (!appendToQueue), playback will be stopped. // If replacing the current queue (!appendToQueue), playback will be stopped.
func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, appendToQueue, shuffle bool) error { func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMode InsertQueueMode, shuffle bool) error {
if !appendToQueue { if insertQueueMode == Replace {
p.player.Stop() p.player.Stop()
p.nowPlayingIdx = -1 p.nowPlayingIdx = -1
p.playQueue = nil 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) newTracks := p.deepCopyTrackSlice(tracks)
if shuffle { if shuffle {
rand.Shuffle(len(newTracks), func(i, j int) { newTracks[i], newTracks[j] = newTracks[j], newTracks[i] }) 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 { if needToSetNext {
p.setNextTrack(p.nowPlayingIdx + 1) p.setNextTrack(p.nowPlayingIdx + 1)
+10 -10
View File
@@ -100,27 +100,27 @@ func (p *PlaybackManager) OnPlaying(cb func()) {
} }
// Loads the specified album into the play queue. // 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) album, err := p.engine.sm.Server.GetAlbum(albumID)
if err != nil { if err != nil {
return err return err
} }
return p.LoadTracks(album.Tracks, appendToQueue, shuffle) return p.LoadTracks(album.Tracks, insertQueueMode, shuffle)
} }
// Loads the specified playlist into the play queue. // 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) playlist, err := p.engine.sm.Server.GetPlaylist(playlistID)
if err != nil { if err != nil {
return err return err
} }
return p.LoadTracks(playlist.Tracks, appendToQueue, shuffle) return p.LoadTracks(playlist.Tracks, insertQueueMode, shuffle)
} }
// Load tracks into the play queue. // Load tracks into the play queue.
// If replacing the current queue (!appendToQueue), playback will be stopped. // If replacing the current queue (!appendToQueue), playback will be stopped.
func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, appendToQueue, shuffle bool) error { func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, insertQueueMode InsertQueueMode, shuffle bool) error {
return p.engine.LoadTracks(tracks, appendToQueue, shuffle) return p.engine.LoadTracks(tracks, insertQueueMode, shuffle)
} }
// Replaces the play queue with the given set of tracks. // 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 { 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 return err
} }
if p.engine.replayGainCfg.Mode == ReplayGainAuto { 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 { 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 return err
} }
if p.engine.replayGainCfg.Mode == ReplayGainAuto { if p.engine.replayGainCfg.Mode == ReplayGainAuto {
@@ -155,7 +155,7 @@ func (p *PlaybackManager) PlayTrack(trackID string) error {
if err != nil { if err != nil {
return err return err
} }
p.LoadTracks([]*mediaprovider.Track{tr}, false, false) p.LoadTracks([]*mediaprovider.Track{tr}, Replace, false)
if p.engine.replayGainCfg.Mode == ReplayGainAuto { if p.engine.replayGainCfg.Mode == ReplayGainAuto {
p.SetReplayGainMode(player.ReplayGainTrack) p.SetReplayGainMode(player.ReplayGainTrack)
} }
@@ -186,7 +186,7 @@ func (p *PlaybackManager) fetchAndPlayTracks(fetchFn func() ([]*mediaprovider.Tr
if songs, err := fetchFn(); err != nil { if songs, err := fetchFn(); err != nil {
log.Printf("error fetching tracks: %s", err.Error()) log.Printf("error fetching tracks: %s", err.Error())
} else { } else {
p.LoadTracks(songs, false, false) p.LoadTracks(songs, Replace, false)
if p.engine.replayGainCfg.Mode == ReplayGainAuto { if p.engine.replayGainCfg.Mode == ReplayGainAuto {
p.SetReplayGainMode(player.ReplayGainTrack) p.SetReplayGainMode(player.ReplayGainTrack)
} }
+2 -2
View File
@@ -226,7 +226,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
go a.page.pm.PlayAlbum(a.page.albumID, 0, false) go a.page.pm.PlayAlbum(a.page.albumID, 0, false)
}) })
shuffleBtn := widget.NewButtonWithIcon("Shuffle", myTheme.ShuffleIcon, func() { 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() a.page.pm.PlayFromBeginning()
}) })
var pop *widget.PopUpMenu var pop *widget.PopUpMenu
@@ -234,7 +234,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
menuBtn.OnTapped = func() { menuBtn.OnTapped = func() {
if pop == nil { if pop == nil {
queue := fyne.NewMenuItem("Add to queue", func() { 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() queue.Icon = theme.ContentAddIcon()
playlist := fyne.NewMenuItem("Add to playlist...", func() { playlist := fyne.NewMenuItem("Add to playlist...", func() {
+8 -4
View File
@@ -253,19 +253,23 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader {
}) })
a.editButton.Hidden = true a.editButton.Hidden = true
playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() { 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() a.page.pm.PlayFromBeginning()
}) })
shuffleBtn := widget.NewButtonWithIcon("Shuffle", myTheme.ShuffleIcon, func() { 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() a.page.pm.PlayFromBeginning()
}) })
var pop *widget.PopUpMenu var pop *widget.PopUpMenu
menuBtn := widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil) menuBtn := widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil)
menuBtn.OnTapped = func() { menuBtn.OnTapped = func() {
if pop == nil { 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() { 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() queue.Icon = theme.ContentAddIcon()
playlist := fyne.NewMenuItem("Add to playlist...", func() { 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()) a.page.contr.ShowDownloadDialog(a.page.tracks, a.titleLabel.String())
}) })
download.Icon = theme.DownloadIcon() 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)) pop = widget.NewPopUpMenu(menu, fyne.CurrentApp().Driver().CanvasForObject(a))
} }
pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(menuBtn) pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(menuBtn)
+1 -1
View File
@@ -123,7 +123,7 @@ func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) {
go a.contr.App.PlaybackManager.PlayPlaylist(id, 0, shuffle) go a.contr.App.PlaybackManager.PlayPlaylist(id, 0, shuffle)
} }
a.gridView.OnAddToQueue = func(id string) { 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.OnShowItemPage = a.showPlaylistPage
a.gridView.OnShowSecondaryPage = nil a.gridView.OnShowSecondaryPage = nil
+17 -8
View File
@@ -113,18 +113,21 @@ func (m *Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) {
func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widgets.Tracklist, mode player.ReplayGainMode) { func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widgets.Tracklist, mode player.ReplayGainMode) {
tracklist.OnAddToPlaylist = m.DoAddTracksToPlaylistWorkflow tracklist.OnAddToPlaylist = m.DoAddTracksToPlaylistWorkflow
tracklist.OnPlaySelectionNext = func(tracks []*mediaprovider.Track) {
m.App.PlaybackManager.LoadTracks(tracks, backend.InsertNext, false)
}
tracklist.OnAddToQueue = func(tracks []*mediaprovider.Track) { 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) { 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 { if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto {
m.App.PlaybackManager.SetReplayGainMode(mode) m.App.PlaybackManager.SetReplayGainMode(mode)
} }
m.App.PlaybackManager.PlayTrackAt(idx) m.App.PlaybackManager.PlayTrackAt(idx)
} }
tracklist.OnPlaySelection = func(tracks []*mediaprovider.Track, shuffle bool) { 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 { if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto {
m.App.PlaybackManager.SetReplayGainMode(mode) m.App.PlaybackManager.SetReplayGainMode(mode)
} }
@@ -152,7 +155,7 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget
log.Println("Error getting song radio: ", err) log.Println("Error getting song radio: ", err)
return return
} }
m.App.PlaybackManager.LoadTracks(tracks, false, false) m.App.PlaybackManager.LoadTracks(tracks, backend.Replace, false)
if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto { if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto {
m.App.PlaybackManager.SetReplayGainMode(mode) m.App.PlaybackManager.SetReplayGainMode(mode)
} }
@@ -163,7 +166,10 @@ func (m *Controller) connectTracklistActionsWithReplayGainMode(tracklist *widget
func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
grid.OnAddToQueue = func(albumID string) { 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) { grid.OnPlay = func(albumID string, shuffle bool) {
go m.App.PlaybackManager.PlayAlbum(albumID, 0, shuffle) 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) { func (m *Controller) ConnectArtistGridActions(grid *widgets.GridView) {
grid.OnShowItemPage = func(id string) { m.NavigateTo(ArtistRoute(id)) } 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.OnPlay = func(artistID string, shuffle bool) { go m.PlayArtistDiscography(artistID, shuffle) }
grid.OnAddToQueue = func(artistID string) { 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) { grid.OnAddToPlaylist = func(artistID string) {
go m.DoAddTracksToPlaylistWorkflow( go m.DoAddTracksToPlaylistWorkflow(
@@ -250,7 +259,7 @@ func (m *Controller) GetArtistTracks(artistID string) []*mediaprovider.Track {
} }
func (m *Controller) PlayArtistDiscography(artistID string, shuffleTracks bool) { 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 m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto {
if shuffleTracks { if shuffleTracks {
m.App.PlaybackManager.SetReplayGainMode(player.ReplayGainTrack) m.App.PlaybackManager.SetReplayGainMode(player.ReplayGainTrack)
@@ -275,7 +284,7 @@ func (m *Controller) ShuffleArtistAlbums(artistID string) {
}) })
m.App.PlaybackManager.StopAndClearPlayQueue() m.App.PlaybackManager.StopAndClearPlayQueue()
for _, al := range artist.Albums { 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 { if m.App.Config.ReplayGain.Mode == backend.ReplayGainAuto {
+8 -1
View File
@@ -117,6 +117,7 @@ type GridViewState struct {
DisableSharing bool DisableSharing bool
OnPlay func(id string, shuffle bool) OnPlay func(id string, shuffle bool)
OnPlayNext func(id string)
OnAddToQueue func(id string) OnAddToQueue func(id string)
OnAddToPlaylist func(id string) OnAddToPlaylist func(id string)
OnDownload func(id string) OnDownload func(id string)
@@ -403,6 +404,12 @@ func (g *GridView) showContextMenu(card *GridViewItem, pos fyne.Position) {
play.Icon = theme.MediaPlayIcon() play.Icon = theme.MediaPlayIcon()
shuffle := fyne.NewMenuItem("Shuffle", func() { g.onPlay(g.menuGridViewItemId, true) }) shuffle := fyne.NewMenuItem("Shuffle", func() { g.onPlay(g.menuGridViewItemId, true) })
shuffle.Icon = myTheme.ShuffleIcon 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() { queue := fyne.NewMenuItem("Add to queue", func() {
if g.OnAddToQueue != nil { if g.OnAddToQueue != nil {
g.OnAddToQueue(g.menuGridViewItemId) g.OnAddToQueue(g.menuGridViewItemId)
@@ -425,7 +432,7 @@ func (g *GridView) showContextMenu(card *GridViewItem, pos fyne.Position) {
g.OnShare(g.menuGridViewItemId) g.OnShare(g.menuGridViewItemId)
}) })
g.shareMenuItem.Icon = myTheme.ShareIcon 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)) fyne.CurrentApp().Driver().CanvasForObject(g))
} }
g.shareMenuItem.Disabled = g.DisableSharing g.shareMenuItem.Disabled = g.DisableSharing
+8 -1
View File
@@ -86,6 +86,7 @@ type Tracklist struct {
// user action callbacks // user action callbacks
OnPlayTrackAt func(int) OnPlayTrackAt func(int)
OnPlaySelection func(tracks []*mediaprovider.Track, shuffle bool) OnPlaySelection func(tracks []*mediaprovider.Track, shuffle bool)
OnPlaySelectionNext func(trackIDs []*mediaprovider.Track)
OnAddToQueue func(trackIDs []*mediaprovider.Track) OnAddToQueue func(trackIDs []*mediaprovider.Track)
OnAddToPlaylist func(trackIDs []string) OnAddToPlaylist func(trackIDs []string)
OnSetFavorite func(trackIDs []string, fav bool) OnSetFavorite func(trackIDs []string, fav bool)
@@ -535,6 +536,12 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
} }
}) })
shuffle.Icon = myTheme.ShuffleIcon 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() { add := fyne.NewMenuItem("Add to queue", func() {
if t.OnPlaySelection != nil { if t.OnPlaySelection != nil {
t.OnAddToQueue(t.selectedTracks()) t.OnAddToQueue(t.selectedTracks())
@@ -546,7 +553,7 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) {
}) })
t.songRadioMenuItem.Icon = myTheme.BroadcastIcon t.songRadioMenuItem.Icon = myTheme.BroadcastIcon
t.ctxMenu.Items = append(t.ctxMenu.Items, 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() { playlist := fyne.NewMenuItem("Add to playlist...", func() {
if t.OnAddToPlaylist != nil { if t.OnAddToPlaylist != nil {