diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 94ea457..334f869 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -161,6 +161,20 @@ func (p *PlaybackManager) PlayFromBeginning() error { return p.player.PlayFromBeginning() } +func (p *PlaybackManager) PlayTrackAt(idx int) error { + return p.player.PlayTrackAt(idx) +} + +func (p *PlaybackManager) GetPlayQueue() []*subsonic.Child { + pq := make([]*subsonic.Child, len(p.playQueue)) + copy(pq, p.playQueue) + return pq +} + +func (p *PlaybackManager) RemoveTracksFromQueue(trackIdxs []int) { + // TODO +} + func (p *PlaybackManager) checkScrobble(playDur time.Duration) { if playDur.Seconds() < 0.1 || p.curTrackTime < 0.1 { return diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go new file mode 100644 index 0000000..70a0fab --- /dev/null +++ b/ui/browsing/nowplayingpage.go @@ -0,0 +1,102 @@ +package browsing + +import ( + "supersonic/backend" + "supersonic/ui/controller" + "supersonic/ui/layouts" + "supersonic/ui/widgets" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + "github.com/dweymouth/go-subsonic/subsonic" +) + +type NowPlayingPage struct { + widget.BaseWidget + + nowPlayingPageState + + title *widget.RichText + tracklist *widgets.Tracklist + nowPlayingID string + container *fyne.Container +} + +type nowPlayingPageState struct { + contr controller.Controller + sm *backend.ServerManager + pm *backend.PlaybackManager + nav func(Route) +} + +func NewNowPlayingPage( + contr controller.Controller, + sm *backend.ServerManager, + pm *backend.PlaybackManager, + nav func(Route), +) *NowPlayingPage { + a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, sm: sm, pm: pm, nav: nav}} + a.ExtendBaseWidget(a) + a.tracklist = widgets.NewTracklist(nil) + a.tracklist.AutoNumber = true + a.tracklist.DisablePlaybackMenu = true + a.tracklist.OnPlayTrackAt = a.onPlayTrackAt + a.tracklist.OnAddToPlaylist = a.contr.DoAddTracksToPlaylistWorkflow + a.title = widget.NewRichTextWithText("Now Playing") + a.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName + a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, + container.NewBorder(a.title, nil, nil, nil, a.tracklist)) + a.loadAsync() + return a +} + +func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(a.container) +} + +func (a *NowPlayingPage) Save() SavedPage { + nps := a.nowPlayingPageState + return &nps +} + +func (a *NowPlayingPage) Route() Route { + return NowPlayingRoute() +} + +func (a *NowPlayingPage) Tapped(*fyne.PointEvent) { + a.tracklist.UnselectAll() +} + +func (a *NowPlayingPage) SelectAll() { + a.tracklist.SelectAll() +} + +func (a *NowPlayingPage) OnSongChange(song *subsonic.Child) { + if song == nil { + a.nowPlayingID = "" + } else { + a.nowPlayingID = song.ID + } + a.tracklist.SetNowPlaying(a.nowPlayingID) +} + +func (a *NowPlayingPage) Reload() { + a.loadAsync() +} + +func (a *NowPlayingPage) onPlayTrackAt(tracknum int) { + _ = a.pm.PlayTrackAt(tracknum) +} + +func (a *NowPlayingPage) loadAsync() { + go func() { + queue := a.pm.GetPlayQueue() + a.tracklist.Tracks = queue + a.tracklist.SetNowPlaying(a.nowPlayingID) + }() +} + +func (s *nowPlayingPageState) Restore() Page { + return NewNowPlayingPage(s.contr, s.sm, s.pm, s.nav) +} diff --git a/ui/browsing/router.go b/ui/browsing/router.go index b3d8cc5..ae006b2 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -3,7 +3,6 @@ package browsing import ( "supersonic/backend" "supersonic/ui/controller" - "supersonic/ui/util" ) type PageName int @@ -17,6 +16,7 @@ const ( Genre Genres Favorites + NowPlaying Playlist Playlists ) @@ -61,6 +61,10 @@ func ArtistsRoute() Route { return Route{Page: Artists} } +func NowPlayingRoute() Route { + return Route{Page: NowPlaying} +} + type NavigationHandler interface { SetPage(Page) } @@ -69,8 +73,6 @@ type Router struct { App *backend.App Controller controller.Controller Nav NavigationHandler - - pop util.PopUpProvider } func NewRouter(app *backend.App, controller controller.Controller, nav NavigationHandler) Router { @@ -98,6 +100,8 @@ func (r Router) CreatePage(rte Route) Page { return NewGenrePage(rte.Arg, r.App.LibraryManager, r.App.ImageManager, r.OpenRoute) case Genres: return NewArtistsGenresPage(true, r.App.ServerManager, r.OpenRoute) + case NowPlaying: + return NewNowPlayingPage(r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.OpenRoute) case Playlist: return NewPlaylistPage(rte.Arg, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager, r.OpenRoute) case Playlists: diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 642d022..44a5966 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -82,6 +82,9 @@ func (m *MainWindow) PromptForFirstServer(cb func(string, string, string, string } func (m *MainWindow) addNavigationButtons() { + m.BrowsingPane.AddNavigationButton(res.ResHeadphonesInvertPng, func() { + m.Router.OpenRoute(browsing.NowPlayingRoute()) + }) m.BrowsingPane.AddNavigationButton(res.ResHeartFilledInvertPng, func() { m.Router.OpenRoute(browsing.FavoritesRoute()) }) diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index bef4e8d..d7b14aa 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -119,7 +119,8 @@ type Tracklist struct { Tracks []*subsonic.Child AutoNumber bool // must be set before the context menu is shown for the first time - AuxiliaryMenuItems []*fyne.MenuItem + AuxiliaryMenuItems []*fyne.MenuItem + DisablePlaybackMenu bool // user action callbacks OnPlayTrackAt func(int) @@ -216,23 +217,27 @@ func (t *Tracklist) onShowContextMenu(e *fyne.PointEvent, trackIdx int) { t.selectionMgr.Select(trackIdx) t.Refresh() if t.ctxMenu == nil { - t.ctxMenu = fyne.NewMenu("", - fyne.NewMenuItem("Play", func() { - if t.OnPlaySelection != nil { - t.OnPlaySelection(t.selectedTracks()) - } - }), - fyne.NewMenuItem("Add to queue", func() { - if t.OnPlaySelection != nil { - t.OnAddToQueue(t.selectedTracks()) - } - }), + t.ctxMenu = fyne.NewMenu("") + if !t.DisablePlaybackMenu { + t.ctxMenu.Items = append(t.ctxMenu.Items, + fyne.NewMenuItem("Play", func() { + if t.OnPlaySelection != nil { + t.OnPlaySelection(t.selectedTracks()) + } + })) + t.ctxMenu.Items = append(t.ctxMenu.Items, + fyne.NewMenuItem("Add to queue", func() { + if t.OnPlaySelection != nil { + t.OnAddToQueue(t.selectedTracks()) + } + })) + } + t.ctxMenu.Items = append(t.ctxMenu.Items, fyne.NewMenuItem("Add to playlist...", func() { if t.OnAddToPlaylist != nil { t.OnAddToPlaylist(t.selectedTrackIDs()) } - }), - ) + })) if len(t.AuxiliaryMenuItems) > 0 { t.ctxMenu.Items = append(t.ctxMenu.Items, fyne.NewMenuItemSeparator()) t.ctxMenu.Items = append(t.ctxMenu.Items, t.AuxiliaryMenuItems...)