add status line with media info to bottom of now playing page

This commit is contained in:
Drew Weymouth
2023-06-05 08:55:35 -07:00
parent 6d5900b692
commit fedb9fd70d
5 changed files with 153 additions and 6 deletions
+4
View File
@@ -116,6 +116,10 @@ func (p *PlaybackManager) NowPlaying() *mediaprovider.Track {
return p.playQueue[p.nowPlayingIdx]
}
func (p *PlaybackManager) NowPlayingIndex() int {
return int(p.nowPlayingIdx)
}
// Sets a callback that is notified whenever a new song begins playing.
func (p *PlaybackManager) OnSongChange(cb func(nowPlaying *mediaprovider.Track, justScrobbledIfAny *mediaprovider.Track)) {
p.onSongChange = append(p.onSongChange, cb)
+42
View File
@@ -70,6 +70,25 @@ type AudioDevice struct {
Description string
}
// Media information about the currently playing media.
type MediaInfo struct {
// The sample format as string. This uses the same names as used in other places of mpv.
// NOTE: this is the format that the decoder outputs, NOT necessarily the format of the file.
Format string
// Audio samplerate.
Samplerate int
// The number of channels.
ChannelCount int
// The audio codec.
Codec string
// The average bit rate in bits per second.
Bitrate int
}
// Player encapsulates the mpv instance and provides functions
// to control it and to check its status.
type Player struct {
@@ -413,6 +432,29 @@ func (p *Player) SetAudioDevice(deviceName string) error {
return p.mpv.SetPropertyString("audio-device", deviceName)
}
func (p *Player) GetMediaInfo() (MediaInfo, error) {
var info MediaInfo
n, err := p.mpv.GetProperty("audio-params", mpv.FORMAT_NODE)
if err != nil {
return info, err
}
nodeMap := n.(*mpv.Node).Data.(map[string]*mpv.Node)
info.Format = nodeMap["format"].Data.(string)
info.Samplerate = int(nodeMap["samplerate"].Data.(int64))
info.ChannelCount = int(nodeMap["channel-count"].Data.(int64))
br, err := p.mpv.GetProperty("audio-bitrate", mpv.FORMAT_INT64)
if err == nil {
info.Bitrate = int(br.(int64))
}
codec, err := p.mpv.GetProperty("track-list/0/codec", mpv.FORMAT_STRING)
if err == nil {
info.Codec = codec.(string)
}
return info, nil
}
func (p *Player) getInt64Property(propName string) (int64, error) {
playpos, err := p.mpv.GetProperty(propName, mpv.FORMAT_INT64)
if err != nil {
+14
View File
@@ -39,6 +39,10 @@ type CanShowNowPlaying interface {
OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track)
}
type CanShowPlayTime interface {
OnPlayTimeUpdate(curTime, totalTime float64)
}
type BrowsingPane struct {
widget.BaseWidget
@@ -66,6 +70,7 @@ func NewBrowsingPane(app *backend.App) *BrowsingPane {
b.forward = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), b.GoForward)
b.reload = widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), b.Reload)
b.app.PlaybackManager.OnSongChange(b.onSongChange)
b.app.PlaybackManager.OnPlayTimeUpdate(b.onPlayTimeUpdate)
bkgrnd := myTheme.NewThemedRectangle(myTheme.ColorNamePageBackground)
b.pageContainer = container.NewMax(bkgrnd, layout.NewSpacer())
b.settingsBtn = widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
@@ -175,6 +180,15 @@ func (b *BrowsingPane) onSongChange(song, lastScrobbledIfAny *mediaprovider.Trac
}
}
func (b *BrowsingPane) onPlayTimeUpdate(cur, total float64) {
if b.curPage == nil {
return
}
if p, ok := b.curPage.(CanShowPlayTime); ok {
p.OnPlayTimeUpdate(cur, total)
}
}
func (b *BrowsingPane) addPageToHistory(p Page, truncate bool) {
if truncate {
// allow garbage collection of pages that will be removed from the history
+92 -5
View File
@@ -1,15 +1,23 @@
package browsing
import (
"fmt"
"log"
"strings"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/player"
"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"
)
@@ -18,8 +26,12 @@ type NowPlayingPage struct {
nowPlayingPageState
queue []*mediaprovider.Track
totalTime float64
title *widget.RichText
tracklist *widgets.Tracklist
statusLabel *widget.RichText
nowPlayingID string
container *fyne.Container
}
@@ -28,6 +40,7 @@ type nowPlayingPageState struct {
contr *controller.Controller
conf *backend.NowPlayingPageConfig
pm *backend.PlaybackManager
p *player.Player
}
func NewNowPlayingPage(
@@ -35,9 +48,15 @@ func NewNowPlayingPage(
contr *controller.Controller,
conf *backend.NowPlayingPageConfig,
pm *backend.PlaybackManager,
p *player.Player, // TODO: once other player backends are supported (eg uPnP), refactor
) *NowPlayingPage {
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, pm: pm}}
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, pm: pm, p: p}}
a.ExtendBaseWidget(a)
p.OnPaused(a.formatStatusLine)
p.OnPlaying(a.formatStatusLine)
p.OnStopped(a.formatStatusLine)
a.tracklist = widgets.NewTracklist(nil)
a.tracklist.SetVisibleColumns(conf.TracklistColumns)
a.tracklist.OnVisibleColumnsChanged = func(cols []string) {
@@ -53,8 +72,13 @@ func NewNowPlayingPage(
}
a.title = widget.NewRichTextWithText("Now Playing")
a.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName
a.statusLabel = widget.NewRichTextWithText("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, nil, nil, nil, a.tracklist))
container.NewBorder(a.title, statusLabelCtr, nil, nil, a.tracklist))
a.load(highlightedTrackID)
return a
}
@@ -80,6 +104,8 @@ func (a *NowPlayingPage) SelectAll() {
a.tracklist.SelectAll()
}
var _ CanShowNowPlaying = (*NowPlayingPage)(nil)
func (a *NowPlayingPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) {
if song == nil {
a.nowPlayingID = ""
@@ -90,6 +116,62 @@ func (a *NowPlayingPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Tr
a.tracklist.IncrementPlayCount(sharedutil.TrackIDOrEmptyStr(lastScrobbledIfAny))
}
var _ CanShowPlayTime = (*NowPlayingPage)(nil)
func (a *NowPlayingPage) OnPlayTimeUpdate(_, _ float64) {
a.formatStatusLine()
}
func (a *NowPlayingPage) formatStatusLine() {
playerStats := a.p.GetStatus()
ts := a.statusLabel.Segments[0].(*widget.TextSegment)
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)
if state == "Stopped" {
ts.Text = fmt.Sprintf("%s | Total time: %s", status, util.SecondsToTimeString(a.totalTime))
} else {
audioInfo, err := a.p.GetMediaInfo()
if err != nil {
log.Printf("error getting playback status: %s", err.Error())
}
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.
ts.Text = fmt.Sprintf("%s · %s %g kHz, %d kbps | Total time: %s",
status,
codec,
float64(audioInfo.Samplerate)/1000,
audioInfo.Bitrate/1000,
util.SecondsToTimeString(a.totalTime))
}
a.statusLabel.Refresh()
}
func (a *NowPlayingPage) Reload() {
a.load("")
}
@@ -106,14 +188,19 @@ func (a *NowPlayingPage) onRemoveSelectedFromQueue() {
// does not make calls to server - can safely be run in UI callbacks
func (a *NowPlayingPage) load(highlightedTrackID string) {
queue := a.pm.GetPlayQueue()
a.tracklist.SetTracks(queue)
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.conf, s.pm)
return NewNowPlayingPage("", s.contr, s.conf, s.pm, s.p)
}
+1 -1
View File
@@ -42,7 +42,7 @@ func (r Router) CreatePage(rte controller.Route) Page {
case controller.Genres:
return NewGenresPage(r.Controller, r.App.ServerManager.Server)
case controller.NowPlaying:
return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.PlaybackManager)
return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.PlaybackManager, r.App.Player)
case controller.Playlist:
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
case controller.Playlists: