From a86533b42436374271115bce8e31249b16d50d97 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 3 Mar 2024 11:47:57 -0800 Subject: [PATCH] connect up PlayQueueList actions and remove old NowPlayingPage --- ui/bottompanel.go | 2 +- ui/browsing/fullscreenpage.go | 68 ++++++--- ui/browsing/nowplayingpage.go | 257 ---------------------------------- ui/browsing/router.go | 4 +- ui/controller/routes.go | 4 - ui/util/widgetpool.go | 2 +- ui/widgets/playqueuelist.go | 7 + 7 files changed, 57 insertions(+), 287 deletions(-) delete mode 100644 ui/browsing/nowplayingpage.go diff --git a/ui/bottompanel.go b/ui/bottompanel.go index a4210f3..5332624 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -53,7 +53,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, contr *controller.Controller) * bp.NowPlaying = widgets.NewNowPlayingCard() bp.NowPlaying.OnCoverTapped = func() { - contr.NavigateTo(controller.FullscreenRoute()) + contr.NavigateTo(controller.NowPlayingRoute("")) } bp.NowPlaying.OnSetFavorite = func(fav bool) { contr.SetTrackFavorites([]string{pm.NowPlaying().ID}, fav) diff --git a/ui/browsing/fullscreenpage.go b/ui/browsing/fullscreenpage.go index d0b53b6..5dd938e 100644 --- a/ui/browsing/fullscreenpage.go +++ b/ui/browsing/fullscreenpage.go @@ -25,10 +25,10 @@ import ( "fyne.io/fyne/v2/widget" ) -type FullscreenPage struct { +type NowPlayingPage struct { widget.BaseWidget - fullscreenPageState + nowPlayingPageState queue []*mediaprovider.Track queueList *widgets.PlayQueueList @@ -41,7 +41,7 @@ type FullscreenPage struct { container *fyne.Container } -type fullscreenPageState struct { +type nowPlayingPageState struct { contr *controller.Controller pool *util.WidgetPool pm *backend.PlaybackManager @@ -49,14 +49,14 @@ type fullscreenPageState struct { canRate bool } -func NewFullscreenPage( +func NewNowPlayingPage( contr *controller.Controller, pool *util.WidgetPool, im *backend.ImageManager, pm *backend.PlaybackManager, canRate bool, -) *FullscreenPage { - a := &FullscreenPage{fullscreenPageState: fullscreenPageState{ +) *NowPlayingPage { + a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{ contr: contr, pool: pool, im: im, pm: pm, canRate: canRate, }} a.ExtendBaseWidget(a) @@ -81,19 +81,29 @@ func NewFullscreenPage( } a.queueList = widgets.NewPlayQueueList(a.im) + a.queueList.OnReorderTracks = a.doSetNewTrackOrder + a.queueList.OnDownload = contr.ShowDownloadDialog + a.queueList.OnSetRating = contr.SetTrackRatings + a.queueList.OnSetFavorite = contr.SetTrackFavorites + a.queueList.OnAddToPlaylist = contr.DoAddTracksToPlaylistWorkflow a.queueList.OnPlayTrackAt = func(tracknum int) { _ = a.pm.PlayTrackAt(tracknum) } a.queueList.OnShowArtistPage = func(artistID string) { a.contr.NavigateTo(controller.ArtistRoute(artistID)) } + a.queueList.OnRemoveFromQueue = func(trackIDs []string) { + a.queueList.UnselectAll() + a.pm.RemoveTracksFromQueue(trackIDs) + } + a.statusLabel = widget.NewLabel("Stopped") a.Reload() return a } -func (a *FullscreenPage) CreateRenderer() fyne.WidgetRenderer { +func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer { if a.container == nil { paddedLayout := &layouts.PercentPadLayout{ LeftRightObjectPercent: .8, @@ -121,22 +131,22 @@ func (a *FullscreenPage) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(a.container) } -func (a *FullscreenPage) Save() SavedPage { +func (a *NowPlayingPage) Save() SavedPage { if a.imageLoadCancel != nil { a.imageLoadCancel() } - nps := a.fullscreenPageState - a.pool.Release(util.WidgetTypeFullscreenPage, a) + nps := a.nowPlayingPageState + a.pool.Release(util.WidgetTypeNowPlayingPage, a) return &nps } -func (a *FullscreenPage) Route() controller.Route { +func (a *NowPlayingPage) Route() controller.Route { return controller.NowPlayingRoute("") } -var _ CanShowNowPlaying = (*FullscreenPage)(nil) +var _ CanShowNowPlaying = (*NowPlayingPage)(nil) -func (a *FullscreenPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) { +func (a *NowPlayingPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) { if a.imageLoadCancel != nil { a.imageLoadCancel() } @@ -158,11 +168,11 @@ func (a *FullscreenPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Tr }) } -func (a *FullscreenPage) OnPlayQueueChange() { +func (a *NowPlayingPage) OnPlayQueueChange() { a.Reload() } -func (a *FullscreenPage) Reload() { +func (a *NowPlayingPage) Reload() { a.queue = a.pm.GetPlayQueue() a.queueList.SetTracks(a.queue) a.totalTime = 0.0 @@ -172,21 +182,35 @@ func (a *FullscreenPage) Reload() { a.formatStatusLine() } -func (s *fullscreenPageState) Restore() Page { - if page := s.pool.Obtain(util.WidgetTypeFullscreenPage).(*FullscreenPage); page != nil { +func (s *nowPlayingPageState) Restore() Page { + if page := s.pool.Obtain(util.WidgetTypeNowPlayingPage).(*NowPlayingPage); page != nil { page.Reload() return page } - return NewFullscreenPage(s.contr, s.pool, s.im, s.pm, s.canRate) + return NewNowPlayingPage(s.contr, s.pool, s.im, s.pm, s.canRate) } -var _ CanShowPlayTime = (*FullscreenPage)(nil) +var _ CanShowPlayTime = (*NowPlayingPage)(nil) -func (a *FullscreenPage) OnPlayTimeUpdate(_, _ float64) { +func (a *NowPlayingPage) OnPlayTimeUpdate(_, _ float64) { a.formatStatusLine() } -func (a *FullscreenPage) formatStatusLine() { +func (a *NowPlayingPage) doSetNewTrackOrder(trackIDs []string, 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 + idxs := make([]int, 0, len(trackIDs)) + for i, tr := range a.queue { + if sharedutil.SliceContains(trackIDs, tr.ID) { + idxs = append(idxs, i) + } + } + newTracks := sharedutil.ReorderTracks(a.queue, idxs, op) + a.pm.UpdatePlayQueue(newTracks) +} + +func (a *NowPlayingPage) formatStatusLine() { curPlayer := a.pm.CurrentPlayer() playerStats := curPlayer.GetStatus() lastStatus := a.statusLabel.Text @@ -227,7 +251,7 @@ func (a *FullscreenPage) formatStatusLine() { } } -func (a *FullscreenPage) formatMediaInfoStr(player player.BasePlayer) string { +func (a *NowPlayingPage) formatMediaInfoStr(player player.BasePlayer) string { mpv, ok := player.(*mpv.Player) if !ok { return "" diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go deleted file mode 100644 index 552fce0..0000000 --- a/ui/browsing/nowplayingpage.go +++ /dev/null @@ -1,257 +0,0 @@ -package browsing - -import ( - "fmt" - "log" - "strings" - - "github.com/dweymouth/supersonic/backend" - "github.com/dweymouth/supersonic/backend/mediaprovider" - "github.com/dweymouth/supersonic/backend/player" - "github.com/dweymouth/supersonic/backend/player/mpv" - "github.com/dweymouth/supersonic/sharedutil" - "github.com/dweymouth/supersonic/ui/controller" - "github.com/dweymouth/supersonic/ui/layouts" - myTheme "github.com/dweymouth/supersonic/ui/theme" - "github.com/dweymouth/supersonic/ui/util" - "github.com/dweymouth/supersonic/ui/widgets" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" -) - -type NowPlayingPage struct { - widget.BaseWidget - - nowPlayingPageState - - queue []*mediaprovider.Track - totalTime float64 - - title *widget.RichText - tracklist *widgets.Tracklist - statusLabel *widget.Label - nowPlayingID string - container *fyne.Container -} - -type nowPlayingPageState struct { - contr *controller.Controller - pool *util.WidgetPool - conf *backend.NowPlayingPageConfig - pm *backend.PlaybackManager - canRate bool -} - -func NewNowPlayingPage( - highlightedTrackID string, - contr *controller.Controller, - pool *util.WidgetPool, - conf *backend.NowPlayingPageConfig, - pm *backend.PlaybackManager, - canRate bool, -) *NowPlayingPage { - a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{ - contr: contr, pool: pool, conf: conf, pm: pm, canRate: canRate, - }} - a.ExtendBaseWidget(a) - - pm.OnPaused(a.formatStatusLine) - pm.OnPlaying(a.formatStatusLine) - pm.OnStopped(a.formatStatusLine) - - if t := a.pool.Obtain(util.WidgetTypeTracklist); t != nil { - a.tracklist = t.(*widgets.Tracklist) - a.tracklist.Reset() - } else { - a.tracklist = widgets.NewTracklist(nil) - } - a.tracklist.SetVisibleColumns(conf.TracklistColumns) - a.tracklist.OnVisibleColumnsChanged = func(cols []string) { - a.conf.TracklistColumns = cols - } - remove := fyne.NewMenuItem("Remove from queue", a.onRemoveSelectedFromQueue) - remove.Icon = theme.ContentRemoveIcon() - a.tracklist.Options = widgets.TracklistOptions{ - AutoNumber: true, - DisablePlaybackMenu: true, - DisableRating: !canRate, - AuxiliaryMenuItems: []*fyne.MenuItem{ - util.NewReorderTracksSubmenu(a.doSetNewTrackOrder), - remove, - }, - } - contr.ConnectTracklistActions(a.tracklist) - // override the default OnPlayTrackAt handler b/c we don't need to re-load the tracks into the queue - a.tracklist.OnPlayTrackAt = a.onPlayTrackAt - a.title = widget.NewRichTextWithText("Now Playing") - a.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName - a.statusLabel = widget.NewLabel("Stopped") - statusLabelCtr := container.New(&layouts.VboxCustomPadding{ExtraPad: -5}, - myTheme.NewThemedRectangle(theme.ColorNameInputBorder), - a.statusLabel, - ) - a.container = container.New(&layouts.MaxPadLayout{PadLeft: 15, PadRight: 15, PadTop: 5, PadBottom: 15}, - container.NewBorder(a.title, statusLabelCtr, nil, nil, a.tracklist)) - a.load(highlightedTrackID) - return a -} - -func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(a.container) -} - -func (a *NowPlayingPage) Save() SavedPage { - a.tracklist.Clear() - a.pool.Release(util.WidgetTypeTracklist, a.tracklist) - nps := a.nowPlayingPageState - return &nps -} - -func (a *NowPlayingPage) Route() controller.Route { - return controller.NowPlayingRoute("") -} - -func (a *NowPlayingPage) Tapped(*fyne.PointEvent) { - a.tracklist.UnselectAll() -} - -func (a *NowPlayingPage) SelectAll() { - a.tracklist.SelectAll() -} - -var _ CanShowNowPlaying = (*NowPlayingPage)(nil) - -func (a *NowPlayingPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) { - a.nowPlayingID = sharedutil.TrackIDOrEmptyStr(song) - a.tracklist.SetNowPlaying(a.nowPlayingID) - a.tracklist.IncrementPlayCount(sharedutil.TrackIDOrEmptyStr(lastScrobbledIfAny)) -} - -var _ CanShowPlayTime = (*NowPlayingPage)(nil) - -func (a *NowPlayingPage) OnPlayTimeUpdate(_, _ float64) { - a.formatStatusLine() -} - -func (a *NowPlayingPage) formatStatusLine() { - curPlayer := a.pm.CurrentPlayer() - playerStats := curPlayer.GetStatus() - lastStatus := a.statusLabel.Text - state := "Stopped" - switch playerStats.State { - case player.Paused: - state = "Paused" - case player.Playing: - state = "Playing" - } - - dur := 0.0 - if np := a.pm.NowPlaying(); np != nil { - dur = float64(np.Duration) - } - statusSuffix := "" - trackNum := 0 - if state != "Stopped" { - trackNum = a.pm.NowPlayingIndex() + 1 - statusSuffix = fmt.Sprintf(" %s/%s", - util.SecondsToTimeString(playerStats.TimePos), - util.SecondsToTimeString(dur)) - } - status := fmt.Sprintf("%s (%d/%d)%s", state, trackNum, - len(a.queue), statusSuffix) - - mediaInfo := "" - if state != "Stopped" { - mediaInfo = a.formatMediaInfoStr(curPlayer) - } - if mediaInfo != "" { - mediaInfo = " ยท " + mediaInfo - } - - a.statusLabel.Text = fmt.Sprintf("%s%s | Total time: %s", status, mediaInfo, util.SecondsToTimeString(a.totalTime)) - if lastStatus != a.statusLabel.Text { - a.statusLabel.Refresh() - } -} - -func (a *NowPlayingPage) formatMediaInfoStr(player player.BasePlayer) string { - mpv, ok := player.(*mpv.Player) - if !ok { - return "" - } - audioInfo, err := mpv.GetMediaInfo() - if err != nil { - log.Printf("error getting playback status: %s", err.Error()) - return "" - } - codec := audioInfo.Codec - if len(codec) <= 4 && !strings.EqualFold(codec, "opus") { - codec = strings.ToUpper(codec) // FLAC, MP3, AAC, etc - } - - // Note: bit depth intentionally omitted since MPV reports the decoded bit depth - // i.e. 24 bit files get reported as 32 bit. Also b/c bit depth isn't meaningful for lossy. - return fmt.Sprintf("%s %g kHz, %d kbps", codec, float64(audioInfo.Samplerate)/1000, audioInfo.Bitrate/1000) -} - -func (a *NowPlayingPage) Reload() { - a.load("") -} - -var _ Scrollable = (*NowPlayingPage)(nil) - -func (a *NowPlayingPage) Scroll(scrollAmt float32) { - a.tracklist.Scroll(scrollAmt) -} - -func (a *NowPlayingPage) onPlayTrackAt(tracknum int) { - _ = a.pm.PlayTrackAt(tracknum) -} - -func (a *NowPlayingPage) onRemoveSelectedFromQueue() { - a.pm.RemoveTracksFromQueue(a.tracklist.SelectedTrackIDs()) - a.tracklist.UnselectAll() - 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() - a.tracklist.SetTracks(a.queue) - a.tracklist.SetNowPlaying(a.nowPlayingID) - if highlightedTrackID != "" { - a.tracklist.SelectAndScrollToTrack(highlightedTrackID) - } - a.totalTime = 0.0 - for _, tr := range a.queue { - a.totalTime += float64(tr.Duration) - } - a.formatStatusLine() -} - -func (s *nowPlayingPageState) Restore() Page { - return NewNowPlayingPage("", s.contr, s.pool, s.conf, s.pm, s.canRate) -} diff --git a/ui/browsing/router.go b/ui/browsing/router.go index f515a16..900ce7e 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -43,13 +43,13 @@ func (r Router) CreatePage(rte controller.Route) Page { case controller.Favorites: return NewFavoritesPage(&r.App.Config.FavoritesPage, r.widgetPool, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager) case controller.Fullscreen: - return NewFullscreenPage(r.Controller, r.widgetPool, r.App.ImageManager, r.App.PlaybackManager, canRate) + return NewNowPlayingPage(r.Controller, r.widgetPool, r.App.ImageManager, r.App.PlaybackManager, canRate) case controller.Genre: return NewGenrePage(rte.Arg, r.widgetPool, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) case controller.Genres: return NewGenresPage(r.Controller, r.App.ServerManager.Server) case controller.NowPlaying: - return NewNowPlayingPage(rte.Arg, r.Controller, r.widgetPool, &r.App.Config.NowPlayingPage, r.App.PlaybackManager, canRate) + return NewNowPlayingPage(r.Controller, r.widgetPool, r.App.ImageManager, r.App.PlaybackManager, canRate) case controller.Playlist: return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.widgetPool, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager) case controller.Playlists: diff --git a/ui/controller/routes.go b/ui/controller/routes.go index 172175a..a351b8f 100644 --- a/ui/controller/routes.go +++ b/ui/controller/routes.go @@ -39,10 +39,6 @@ func FavoritesRoute() Route { return Route{Page: Favorites} } -func FullscreenRoute() Route { - return Route{Page: Fullscreen} -} - func GenreRoute(genre string) Route { return Route{Page: Genre, Arg: genre} } diff --git a/ui/util/widgetpool.go b/ui/util/widgetpool.go index 0db2ef0..b8c574e 100644 --- a/ui/util/widgetpool.go +++ b/ui/util/widgetpool.go @@ -15,7 +15,7 @@ const ( WidgetTypeGridView WidgetTypePlaylistPageHeader WidgetTypeTracklist - WidgetTypeFullscreenPage + WidgetTypeNowPlayingPage // keep at bottom numWidgetTypes diff --git a/ui/widgets/playqueuelist.go b/ui/widgets/playqueuelist.go index 84989a6..c3bb707 100644 --- a/ui/widgets/playqueuelist.go +++ b/ui/widgets/playqueuelist.go @@ -118,6 +118,13 @@ func (p *PlayQueueList) SetNowPlaying(trackID string) { } } +func (p *PlayQueueList) UnselectAll() { + p.tracksMutex.RLock() + util.UnselectAllTracks(p.tracks) + p.tracksMutex.RUnlock() + p.Refresh() +} + func (p *PlayQueueList) lenTracks() int { p.tracksMutex.RLock() defer p.tracksMutex.RUnlock()