From 1ce844213803f63b69a7307dbdc20541ce6b094a Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 1 Jul 2023 14:21:59 -0700 Subject: [PATCH 01/11] start evaluating quarckster/go-mpris-server --- backend/app.go | 5 ++ backend/mpris.go | 172 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 4 files changed, 180 insertions(+) create mode 100644 backend/mpris.go diff --git a/backend/app.go b/backend/app.go index acb6948..b1d1366 100644 --- a/backend/app.go +++ b/backend/app.go @@ -37,6 +37,7 @@ type App struct { PlaybackManager *PlaybackManager Player *player.Player UpdateChecker UpdateChecker + MPRISHandler *MPRISHandler OnReactivate func() appName string @@ -103,6 +104,9 @@ func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*A _, _ = a.ImageManager.GetCoverThumbnail(coverID) }) + a.MPRISHandler = NewMPRISHandler(a.Player, a.PlaybackManager) + a.MPRISHandler.Start() + return a, nil } @@ -221,6 +225,7 @@ func (a *App) DeleteServerCacheDir(serverID uuid.UUID) error { } func (a *App) Shutdown() { + a.MPRISHandler.Shutdown() a.PlaybackManager.DisableCallbacks() a.Player.Stop() // will trigger scrobble check a.Config.LocalPlayback.Volume = a.Player.GetVolume() diff --git a/backend/mpris.go b/backend/mpris.go new file mode 100644 index 0000000..3ecf1c8 --- /dev/null +++ b/backend/mpris.go @@ -0,0 +1,172 @@ +package backend + +import ( + "errors" + + "github.com/dweymouth/supersonic/player" + "github.com/quarckster/go-mpris-server/pkg/events" + "github.com/quarckster/go-mpris-server/pkg/server" + "github.com/quarckster/go-mpris-server/pkg/types" +) + +var ( + _ types.OrgMprisMediaPlayer2Adapter = (*MPRISHandler)(nil) + _ types.OrgMprisMediaPlayer2PlayerAdapter = (*MPRISHandler)(nil) +) +var ( + notImplemented = errors.New("not implemented") +) + +type MPRISHandler struct { + p *player.Player + pm *PlaybackManager + s *server.Server + evt *events.EventHandler +} + +func NewMPRISHandler(p *player.Player, pm *PlaybackManager) *MPRISHandler { + m := &MPRISHandler{p: p, pm: pm} + m.s = server.NewServer("Supersonic", m, m) + m.evt = events.NewEventHandler(m.s) + return m +} + +func (m *MPRISHandler) Start() { + go m.s.Listen() +} + +func (m *MPRISHandler) Shutdown() { + m.s.Stop() +} + +// OrgMprisMediaPlayer2Adapter implementation + +func (m *MPRISHandler) Identity() (string, error) { + return "supersonic", nil +} + +func (m *MPRISHandler) CanQuit() (bool, error) { + return false, nil +} + +func (m *MPRISHandler) Quit() error { + return errors.New("not implemented") +} + +func (m *MPRISHandler) CanRaise() (bool, error) { + return false, nil +} + +func (m *MPRISHandler) Raise() error { + return errors.New("not implemented") +} + +func (m *MPRISHandler) HasTrackList() (bool, error) { + return false, nil +} + +func (m *MPRISHandler) SupportedUriSchemes() ([]string, error) { + return nil, nil +} + +func (m *MPRISHandler) SupportedMimeTypes() ([]string, error) { + return nil, nil +} + +// OrgMprisMediaPlayer2PlayerAdapter implementation + +func (m *MPRISHandler) Next() error { + return m.p.SeekNext() +} + +func (m *MPRISHandler) Previous() error { + return m.p.SeekBackOrPrevious() +} + +func (m *MPRISHandler) Pause() error { + return notImplemented +} + +func (m *MPRISHandler) PlayPause() error { + return m.p.PlayPause() +} + +func (m *MPRISHandler) Stop() error { + return notImplemented +} + +func (m *MPRISHandler) Play() error { + return notImplemented +} + +func (m *MPRISHandler) Seek(offset types.Microseconds) error { + return notImplemented +} + +func (m *MPRISHandler) SetPosition(trackId string, position types.Microseconds) error { + return notImplemented +} + +func (m *MPRISHandler) OpenUri(uri string) error { + return notImplemented +} + +func (m *MPRISHandler) PlaybackStatus() (types.PlaybackStatus, error) { + return "", notImplemented +} + +func (m *MPRISHandler) Rate() (float64, error) { + return 0, notImplemented +} + +func (m *MPRISHandler) SetRate(float64) error { + return notImplemented +} + +func (m *MPRISHandler) Metadata() (types.Metadata, error) { + return types.Metadata{}, notImplemented +} + +func (m *MPRISHandler) Volume() (float64, error) { + return 0, notImplemented +} + +func (m *MPRISHandler) SetVolume(float64) error { + return notImplemented +} + +func (m *MPRISHandler) Position() (int64, error) { + return 0, notImplemented +} + +func (m *MPRISHandler) MinimumRate() (float64, error) { + return 0, notImplemented +} + +func (m *MPRISHandler) MaximumRate() (float64, error) { + return 0, notImplemented +} + +func (m *MPRISHandler) CanGoNext() (bool, error) { + return false, notImplemented +} + +func (m *MPRISHandler) CanGoPrevious() (bool, error) { + return false, notImplemented +} + +func (m *MPRISHandler) CanPlay() (bool, error) { + return false, notImplemented +} + +func (m *MPRISHandler) CanPause() (bool, error) { + return false, notImplemented +} + +func (m *MPRISHandler) CanSeek() (bool, error) { + return false, notImplemented +} + +func (m *MPRISHandler) CanControl() (bool, error) { + return false, notImplemented +} diff --git a/go.mod b/go.mod index e427fcf..58c4d55 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quarckster/go-mpris-server v1.0.0 // indirect github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect github.com/stretchr/testify v1.8.3 // indirect diff --git a/go.sum b/go.sum index 5420507..0087109 100644 --- a/go.sum +++ b/go.sum @@ -265,6 +265,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/quarckster/go-mpris-server v1.0.0 h1:9EKh8Ink7d3OigBLXGlRJ0jDJi+O6T/XMroA5Gl4c5Q= +github.com/quarckster/go-mpris-server v1.0.0/go.mod h1:2b4IdrpnEoEfU+6fQKjYhAgdvsiz4JxmTpDAUrMJVO4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From 2a1771896904ec85f1a53fd96177d5831c88d5b4 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 1 Jul 2023 15:13:08 -0700 Subject: [PATCH 02/11] add implementations for several mpris methods --- backend/app.go | 4 +- backend/mpris.go | 121 +++++++++++++++++++++++++++++++++++++---------- main.go | 2 +- 3 files changed, 98 insertions(+), 29 deletions(-) diff --git a/backend/app.go b/backend/app.go index b1d1366..1fd9e02 100644 --- a/backend/app.go +++ b/backend/app.go @@ -51,7 +51,7 @@ func (a *App) VersionTag() string { return a.appVersionTag } -func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*App, error) { +func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleaseURL string) (*App, error) { sessionPath := configdir.LocalConfig(appName, sessionDir) if _, err := os.Stat(path.Join(sessionPath, sessionLockFile)); err == nil { log.Println("Another instance is running. Reactivating it...") @@ -104,7 +104,7 @@ func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*A _, _ = a.ImageManager.GetCoverThumbnail(coverID) }) - a.MPRISHandler = NewMPRISHandler(a.Player, a.PlaybackManager) + a.MPRISHandler = NewMPRISHandler(displayAppName, a.Player, a.PlaybackManager) a.MPRISHandler.Start() return a, nil diff --git a/backend/mpris.go b/backend/mpris.go index 3ecf1c8..4f72af4 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -2,13 +2,18 @@ package backend import ( "errors" + "fmt" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/player" + "github.com/godbus/dbus/v5" "github.com/quarckster/go-mpris-server/pkg/events" "github.com/quarckster/go-mpris-server/pkg/server" "github.com/quarckster/go-mpris-server/pkg/types" ) +const dbusTrackIDPrefix = "/Supersonic/Track/" + var ( _ types.OrgMprisMediaPlayer2Adapter = (*MPRISHandler)(nil) _ types.OrgMprisMediaPlayer2PlayerAdapter = (*MPRISHandler)(nil) @@ -18,23 +23,36 @@ var ( ) type MPRISHandler struct { - p *player.Player - pm *PlaybackManager - s *server.Server - evt *events.EventHandler + // Function called if the player is requested to quit through MPRIS. + // Should *asynchronously* start shutdown and return immediately true if a shutdown will happen. + OnQuit func() error + + // Function called if the player is requested to bring its UI to the front. + OnRaise func() error + + // Function to look up the artwork URL for a given track ID + ArtURLLookup func(trackID string) string + + playerName string + p *player.Player + pm *PlaybackManager + s *server.Server + evt *events.EventHandler } -func NewMPRISHandler(p *player.Player, pm *PlaybackManager) *MPRISHandler { - m := &MPRISHandler{p: p, pm: pm} - m.s = server.NewServer("Supersonic", m, m) +func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) *MPRISHandler { + m := &MPRISHandler{playerName: playerName, p: p, pm: pm} + m.s = server.NewServer(playerName, m, m) m.evt = events.NewEventHandler(m.s) return m } +// Starts listening for MPRIS events. func (m *MPRISHandler) Start() { go m.s.Listen() } +// Stops listening for MPRIS events and releases any D-Bus resources. func (m *MPRISHandler) Shutdown() { m.s.Stop() } @@ -42,23 +60,29 @@ func (m *MPRISHandler) Shutdown() { // OrgMprisMediaPlayer2Adapter implementation func (m *MPRISHandler) Identity() (string, error) { - return "supersonic", nil + return m.playerName, nil } func (m *MPRISHandler) CanQuit() (bool, error) { - return false, nil + return m.OnQuit != nil, nil } func (m *MPRISHandler) Quit() error { - return errors.New("not implemented") + if m.OnQuit != nil { + return m.OnQuit() + } + return errors.New("no quit handler added") } func (m *MPRISHandler) CanRaise() (bool, error) { - return false, nil + return m.OnRaise != nil, nil } func (m *MPRISHandler) Raise() error { - return errors.New("not implemented") + if m.OnRaise != nil { + return m.OnRaise() + } + return errors.New("no raise handler added") } func (m *MPRISHandler) HasTrackList() (bool, error) { @@ -84,7 +108,10 @@ func (m *MPRISHandler) Previous() error { } func (m *MPRISHandler) Pause() error { - return notImplemented + if m.p.GetStatus().State == player.Playing { + return m.p.PlayPause() + } + return nil } func (m *MPRISHandler) PlayPause() error { @@ -92,15 +119,21 @@ func (m *MPRISHandler) PlayPause() error { } func (m *MPRISHandler) Stop() error { - return notImplemented + return m.p.Stop() } func (m *MPRISHandler) Play() error { - return notImplemented + switch m.p.GetStatus().State { + case player.Paused: + return m.p.PlayPause() + case player.Stopped: + return m.p.PlayFromBeginning() + } + return nil } func (m *MPRISHandler) Seek(offset types.Microseconds) error { - return notImplemented + return m.p.Seek(fmt.Sprintf("%0.2f", float64(offset)/1_000_000), player.SeekRelative) } func (m *MPRISHandler) SetPosition(trackId string, position types.Microseconds) error { @@ -112,11 +145,19 @@ func (m *MPRISHandler) OpenUri(uri string) error { } func (m *MPRISHandler) PlaybackStatus() (types.PlaybackStatus, error) { - return "", notImplemented + switch m.p.GetStatus().State { + case player.Playing: + return types.PlaybackStatusPlaying, nil + case player.Paused: + return types.PlaybackStatusPaused, nil + case player.Stopped: + return types.PlaybackStatusStopped, nil + } + return "", errors.New("unknown playback status") } func (m *MPRISHandler) Rate() (float64, error) { - return 0, notImplemented + return 1, nil } func (m *MPRISHandler) SetRate(float64) error { @@ -124,11 +165,35 @@ func (m *MPRISHandler) SetRate(float64) error { } func (m *MPRISHandler) Metadata() (types.Metadata, error) { - return types.Metadata{}, notImplemented + status := m.p.GetStatus() + var tr mediaprovider.Track + if np := m.pm.NowPlaying(); np != nil && status.State != player.Stopped { + tr = *np + } + var trackID string + if tr.ID != "" { + trackID = dbusTrackIDPrefix + tr.ID + } + var artURL string + if tr.ID != "" && m.ArtURLLookup != nil { + artURL = m.ArtURLLookup(tr.ID) + } + return types.Metadata{ + TrackId: dbus.ObjectPath(trackID), + Length: types.Microseconds(status.Duration), + Title: tr.Name, + Album: tr.Album, + Artist: tr.ArtistNames, + DiscNumber: tr.DiscNumber, + TrackNumber: tr.TrackNumber, + Genre: []string{tr.Genre}, + UserRating: float64(tr.Rating) / 5, + ArtUrl: artURL, + }, nil } func (m *MPRISHandler) Volume() (float64, error) { - return 0, notImplemented + return float64(m.p.GetVolume()) / 100, nil } func (m *MPRISHandler) SetVolume(float64) error { @@ -136,7 +201,7 @@ func (m *MPRISHandler) SetVolume(float64) error { } func (m *MPRISHandler) Position() (int64, error) { - return 0, notImplemented + return int64(secondsToMicroseconds(m.p.GetStatus().TimePos)), nil } func (m *MPRISHandler) MinimumRate() (float64, error) { @@ -148,19 +213,19 @@ func (m *MPRISHandler) MaximumRate() (float64, error) { } func (m *MPRISHandler) CanGoNext() (bool, error) { - return false, notImplemented + return true, nil } func (m *MPRISHandler) CanGoPrevious() (bool, error) { - return false, notImplemented + return true, nil } func (m *MPRISHandler) CanPlay() (bool, error) { - return false, notImplemented + return true, nil } func (m *MPRISHandler) CanPause() (bool, error) { - return false, notImplemented + return true, nil } func (m *MPRISHandler) CanSeek() (bool, error) { @@ -168,5 +233,9 @@ func (m *MPRISHandler) CanSeek() (bool, error) { } func (m *MPRISHandler) CanControl() (bool, error) { - return false, notImplemented + return true, nil +} + +func secondsToMicroseconds(s float64) types.Microseconds { + return types.Microseconds(s * 1_000_000) } diff --git a/main.go b/main.go index 60b73c2..6cc4fd2 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ const ( ) func main() { - myApp, err := backend.StartupApp(appname, appVersionTag, configFile, latestReleaseURL) + myApp, err := backend.StartupApp(appname, displayName, appVersionTag, configFile, latestReleaseURL) if err != nil { log.Fatalf("fatal startup error: %v", err.Error()) } From 61d5ad917d7e63f5088d3de694e4db50b6d219fc Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 1 Jul 2023 16:38:11 -0700 Subject: [PATCH 03/11] more mpris features --- backend/app.go | 35 +++++++++++++++++++++++++++++------ backend/imagemanager.go | 10 ++++++++++ backend/mpris.go | 16 +++++++++++----- main.go | 12 ++++++++++-- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/backend/app.go b/backend/app.go index 1fd9e02..37a662f 100644 --- a/backend/app.go +++ b/backend/app.go @@ -38,7 +38,10 @@ type App struct { Player *player.Player UpdateChecker UpdateChecker MPRISHandler *MPRISHandler - OnReactivate func() + + // UI callbacks to be set in main + OnReactivate func() + OnExit func() appName string appVersionTag string @@ -104,8 +107,7 @@ func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleas _, _ = a.ImageManager.GetCoverThumbnail(coverID) }) - a.MPRISHandler = NewMPRISHandler(displayAppName, a.Player, a.PlaybackManager) - a.MPRISHandler.Start() + a.setupMPRIS(displayAppName) return a, nil } @@ -138,9 +140,7 @@ func (a *App) startSessionWatcher(sessionPath string) { activatePath := path.Join(sessionPath, sessionActivateFile) if _, err := os.Stat(activatePath); err == nil { os.Remove(path.Join(sessionPath, sessionActivateFile)) - if a.OnReactivate != nil { - a.OnReactivate() - } + a.callOnReactivate() } } } @@ -148,6 +148,12 @@ func (a *App) startSessionWatcher(sessionPath string) { } } +func (a *App) callOnReactivate() { + if a.OnReactivate != nil { + a.OnReactivate() + } +} + func (a *App) initMPV() error { p := player.NewWithClientName(a.appName) c := a.Config.LocalPlayback @@ -206,6 +212,23 @@ func (a *App) setupMPV() error { return nil } +func (a *App) setupMPRIS(mprisAppName string) { + a.MPRISHandler = NewMPRISHandler(mprisAppName, a.Player, a.PlaybackManager) + a.MPRISHandler.ArtURLLookup = a.ImageManager.GetCoverArtUrl + a.MPRISHandler.OnRaise = func() error { a.callOnReactivate(); return nil } + a.MPRISHandler.OnQuit = func() error { + if a.OnExit == nil { + return errors.New("no quit handler registered") + } + go func() { + time.Sleep(10 * time.Millisecond) + a.OnExit() + }() + return nil + } + a.MPRISHandler.Start() +} + func (a *App) LoginToDefaultServer(string) error { serverCfg := a.ServerManager.GetDefaultServer() if serverCfg == nil { diff --git a/backend/imagemanager.go b/backend/imagemanager.go index c515f90..b5d4721 100644 --- a/backend/imagemanager.go +++ b/backend/imagemanager.go @@ -88,6 +88,16 @@ func (i *ImageManager) GetFullSizeCoverArt(coverID string) (image.Image, error) return im, nil } +func (i *ImageManager) GetCoverArtUrl(coverID string) (string, error) { + path := i.filePathForCover(coverID) + if _, err := os.Stat(path); err == nil { + // this is probably broken for Windows but it's currently only used + // for MPRIS, so we are OK for now + return fmt.Sprintf("file://%s", path), nil + } + return "", errors.New("cover not found") +} + func (i *ImageManager) GetCachedArtistImage(artistID string) (image.Image, bool) { return i.loadLocalImage(i.filePathForArtistImage(artistID)) } diff --git a/backend/mpris.go b/backend/mpris.go index 4f72af4..e5f1aae 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -31,7 +31,7 @@ type MPRISHandler struct { OnRaise func() error // Function to look up the artwork URL for a given track ID - ArtURLLookup func(trackID string) string + ArtURLLookup func(trackID string) (string, error) playerName string p *player.Player @@ -44,6 +44,10 @@ func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) * m := &MPRISHandler{playerName: playerName, p: p, pm: pm} m.s = server.NewServer(playerName, m, m) m.evt = events.NewEventHandler(m.s) + m.p.OnSeek(func() { + pos := secondsToMicroseconds(m.p.GetStatus().TimePos) + m.evt.Player.OnSeek(pos) + }) return m } @@ -176,7 +180,9 @@ func (m *MPRISHandler) Metadata() (types.Metadata, error) { } var artURL string if tr.ID != "" && m.ArtURLLookup != nil { - artURL = m.ArtURLLookup(tr.ID) + if u, err := m.ArtURLLookup(tr.ID); err == nil { + artURL = u + } } return types.Metadata{ TrackId: dbus.ObjectPath(trackID), @@ -205,11 +211,11 @@ func (m *MPRISHandler) Position() (int64, error) { } func (m *MPRISHandler) MinimumRate() (float64, error) { - return 0, notImplemented + return 1, nil } func (m *MPRISHandler) MaximumRate() (float64, error) { - return 0, notImplemented + return 1, nil } func (m *MPRISHandler) CanGoNext() (bool, error) { @@ -229,7 +235,7 @@ func (m *MPRISHandler) CanPause() (bool, error) { } func (m *MPRISHandler) CanSeek() (bool, error) { - return false, notImplemented + return true, nil } func (m *MPRISHandler) CanControl() (bool, error) { diff --git a/main.go b/main.go index 6cc4fd2..01e09b1 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,10 @@ func main() { } mainWindow := ui.NewMainWindow(fyneApp, appname, displayName, appVersion, myApp, fyne.NewSize(w, h)) myApp.OnReactivate = mainWindow.Show + myApp.OnExit = func() { + saveWindowPosition(myApp.Config, mainWindow.Window) + fyneApp.Quit() + } go func() { // TODO: There is a race condition with laying out the window before the @@ -61,8 +65,7 @@ func main() { mainWindow.Show() mainWindow.Window.SetCloseIntercept(func() { - myApp.Config.Application.WindowHeight = int(mainWindow.Canvas().Size().Height) - myApp.Config.Application.WindowWidth = int(mainWindow.Canvas().Size().Width) + saveWindowPosition(myApp.Config, mainWindow.Window) if myApp.Config.Application.CloseToSystemTray && mainWindow.HaveSystemTray() { mainWindow.Window.Hide() @@ -75,3 +78,8 @@ func main() { log.Println("Running shutdown tasks...") myApp.Shutdown() } + +func saveWindowPosition(config *backend.Config, window fyne.Window) { + config.Application.WindowHeight = int(window.Canvas().Size().Height) + config.Application.WindowWidth = int(window.Canvas().Size().Width) +} From 3b204572b1f04f0e42f0d72b7fc6d888ec748703 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 1 Jul 2023 16:56:42 -0700 Subject: [PATCH 04/11] mpris events --- backend/mpris.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/mpris.go b/backend/mpris.go index e5f1aae..0a3b157 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -48,6 +48,13 @@ func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) * pos := secondsToMicroseconds(m.p.GetStatus().TimePos) m.evt.Player.OnSeek(pos) }) + m.pm.OnSongChange(func(_, _ *mediaprovider.Track) { + m.evt.Player.OnTitle() + }) + emitPlayStatus := func() { m.evt.Player.OnPlayPause() } + m.p.OnStopped(emitPlayStatus) + m.p.OnPlaying(emitPlayStatus) + m.p.OnPaused(emitPlayStatus) return m } From a9bafd76c73a40c8625e18a8a294cd2fe599e211 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 1 Jul 2023 17:08:09 -0700 Subject: [PATCH 05/11] emitting events is crashing for some reason; comment out for now --- backend/mpris.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/backend/mpris.go b/backend/mpris.go index 0a3b157..12abd32 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -44,17 +44,19 @@ func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) * m := &MPRISHandler{playerName: playerName, p: p, pm: pm} m.s = server.NewServer(playerName, m, m) m.evt = events.NewEventHandler(m.s) - m.p.OnSeek(func() { - pos := secondsToMicroseconds(m.p.GetStatus().TimePos) - m.evt.Player.OnSeek(pos) - }) - m.pm.OnSongChange(func(_, _ *mediaprovider.Track) { - m.evt.Player.OnTitle() - }) - emitPlayStatus := func() { m.evt.Player.OnPlayPause() } - m.p.OnStopped(emitPlayStatus) - m.p.OnPlaying(emitPlayStatus) - m.p.OnPaused(emitPlayStatus) + /* + m.p.OnSeek(func() { + pos := secondsToMicroseconds(m.p.GetStatus().TimePos) + m.evt.Player.OnSeek(pos) + }) + m.pm.OnSongChange(func(_, _ *mediaprovider.Track) { + m.evt.Player.OnTitle() + }) + emitPlayStatus := func() { m.evt.Player.OnPlayPause() } + m.p.OnStopped(emitPlayStatus) + m.p.OnPlaying(emitPlayStatus) + m.p.OnPaused(emitPlayStatus) + */ return m } From 8a1a3795bcdb05df5644ccaec5a0671e782e0184 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 12 Jul 2023 18:14:20 -0700 Subject: [PATCH 06/11] uncomment events and fix artURL - will crash unless building against pull #4 in quarckster/go-mpris-server --- backend/mpris.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/backend/mpris.go b/backend/mpris.go index 12abd32..252b5ec 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -44,19 +44,21 @@ func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) * m := &MPRISHandler{playerName: playerName, p: p, pm: pm} m.s = server.NewServer(playerName, m, m) m.evt = events.NewEventHandler(m.s) - /* - m.p.OnSeek(func() { - pos := secondsToMicroseconds(m.p.GetStatus().TimePos) - m.evt.Player.OnSeek(pos) - }) - m.pm.OnSongChange(func(_, _ *mediaprovider.Track) { - m.evt.Player.OnTitle() - }) - emitPlayStatus := func() { m.evt.Player.OnPlayPause() } - m.p.OnStopped(emitPlayStatus) - m.p.OnPlaying(emitPlayStatus) - m.p.OnPaused(emitPlayStatus) - */ + + m.p.OnSeek(func() { + pos := secondsToMicroseconds(m.p.GetStatus().TimePos) + m.evt.Player.OnSeek(pos) + }) + m.pm.OnSongChange(func(_, _ *mediaprovider.Track) { + m.evt.Player.OnTitle() + }) + emitPlayStatus := func() { + m.evt.Player.OnPlayPause() + } + m.p.OnStopped(emitPlayStatus) + m.p.OnPlaying(emitPlayStatus) + m.p.OnPaused(emitPlayStatus) + return m } @@ -189,7 +191,7 @@ func (m *MPRISHandler) Metadata() (types.Metadata, error) { } var artURL string if tr.ID != "" && m.ArtURLLookup != nil { - if u, err := m.ArtURLLookup(tr.ID); err == nil { + if u, err := m.ArtURLLookup(tr.CoverArtID); err == nil { artURL = u } } From 41053a22a87905057f1de8a382f0795183fa8d59 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 13 Jul 2023 12:51:19 -0700 Subject: [PATCH 07/11] upgrade go-mpris-server; gracefully handle no dbus connection --- backend/mpris.go | 39 +++++++++++++++++++++++++++------------ go.mod | 4 ++-- go.sum | 4 ++-- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/backend/mpris.go b/backend/mpris.go index 252b5ec..edd9e40 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -18,8 +18,9 @@ var ( _ types.OrgMprisMediaPlayer2Adapter = (*MPRISHandler)(nil) _ types.OrgMprisMediaPlayer2PlayerAdapter = (*MPRISHandler)(nil) ) + var ( - notImplemented = errors.New("not implemented") + errNotImplemented = errors.New("not implemented") ) type MPRISHandler struct { @@ -33,6 +34,7 @@ type MPRISHandler struct { // Function to look up the artwork URL for a given track ID ArtURLLookup func(trackID string) (string, error) + connErr error playerName string p *player.Player pm *PlaybackManager @@ -41,19 +43,25 @@ type MPRISHandler struct { } func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) *MPRISHandler { - m := &MPRISHandler{playerName: playerName, p: p, pm: pm} + m := &MPRISHandler{playerName: playerName, p: p, pm: pm, connErr: errors.New("not started")} m.s = server.NewServer(playerName, m, m) m.evt = events.NewEventHandler(m.s) m.p.OnSeek(func() { - pos := secondsToMicroseconds(m.p.GetStatus().TimePos) - m.evt.Player.OnSeek(pos) + if m.connErr == nil { + pos := secondsToMicroseconds(m.p.GetStatus().TimePos) + m.evt.Player.OnSeek(pos) + } }) m.pm.OnSongChange(func(_, _ *mediaprovider.Track) { - m.evt.Player.OnTitle() + if m.connErr == nil { + m.evt.Player.OnTitle() + } }) emitPlayStatus := func() { - m.evt.Player.OnPlayPause() + if m.connErr == nil { + m.evt.Player.OnPlayPause() + } } m.p.OnStopped(emitPlayStatus) m.p.OnPlaying(emitPlayStatus) @@ -64,12 +72,19 @@ func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) * // Starts listening for MPRIS events. func (m *MPRISHandler) Start() { - go m.s.Listen() + m.connErr = nil + go func() { + // exits early with err if unable to establish D-Bus connection + m.connErr = m.s.Listen() + }() } // Stops listening for MPRIS events and releases any D-Bus resources. func (m *MPRISHandler) Shutdown() { - m.s.Stop() + if m.connErr == nil { + m.s.Stop() + m.connErr = errors.New("stopped") + } } // OrgMprisMediaPlayer2Adapter implementation @@ -152,11 +167,11 @@ func (m *MPRISHandler) Seek(offset types.Microseconds) error { } func (m *MPRISHandler) SetPosition(trackId string, position types.Microseconds) error { - return notImplemented + return errNotImplemented } func (m *MPRISHandler) OpenUri(uri string) error { - return notImplemented + return errNotImplemented } func (m *MPRISHandler) PlaybackStatus() (types.PlaybackStatus, error) { @@ -176,7 +191,7 @@ func (m *MPRISHandler) Rate() (float64, error) { } func (m *MPRISHandler) SetRate(float64) error { - return notImplemented + return errNotImplemented } func (m *MPRISHandler) Metadata() (types.Metadata, error) { @@ -214,7 +229,7 @@ func (m *MPRISHandler) Volume() (float64, error) { } func (m *MPRISHandler) SetVolume(float64) error { - return notImplemented + return errNotImplemented } func (m *MPRISHandler) Position() (int64, error) { diff --git a/go.mod b/go.mod index 58c4d55..3c532d7 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee github.com/dweymouth/go-subsonic v0.0.0-20230614154319-792d18c75fb4 github.com/fsnotify/fsnotify v1.6.0 + github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.3.0 github.com/pelletier/go-toml/v2 v2.0.8 + github.com/quarckster/go-mpris-server v1.0.1 github.com/zalando/go-keyring v0.2.1 golang.org/x/net v0.0.0-20220722155237-a158d28d115b ) @@ -27,13 +29,11 @@ require ( github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quarckster/go-mpris-server v1.0.0 // indirect github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect github.com/stretchr/testify v1.8.3 // indirect diff --git a/go.sum b/go.sum index 0087109..c23e519 100644 --- a/go.sum +++ b/go.sum @@ -265,8 +265,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/quarckster/go-mpris-server v1.0.0 h1:9EKh8Ink7d3OigBLXGlRJ0jDJi+O6T/XMroA5Gl4c5Q= -github.com/quarckster/go-mpris-server v1.0.0/go.mod h1:2b4IdrpnEoEfU+6fQKjYhAgdvsiz4JxmTpDAUrMJVO4= +github.com/quarckster/go-mpris-server v1.0.1 h1:JAbVBJK1ijjwEH4gfaOtwfElDVuaBPnWyi8rZJEHOxc= +github.com/quarckster/go-mpris-server v1.0.1/go.mod h1:2b4IdrpnEoEfU+6fQKjYhAgdvsiz4JxmTpDAUrMJVO4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From a12b6dd276270fa50bc42d9e7543c4157c3a1e54 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 13 Jul 2023 13:12:57 -0700 Subject: [PATCH 08/11] expose repeat mode through MPRIS --- backend/mpris.go | 29 +++++++++++++++++++++++++++-- backend/playbackmanager.go | 30 +++++++++++++++++++++++++++--- ui/bottompanel.go | 2 +- ui/widgets/auxcontrols.go | 10 ++++++---- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/backend/mpris.go b/backend/mpris.go index edd9e40..1b89376 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -15,8 +15,9 @@ import ( const dbusTrackIDPrefix = "/Supersonic/Track/" var ( - _ types.OrgMprisMediaPlayer2Adapter = (*MPRISHandler)(nil) - _ types.OrgMprisMediaPlayer2PlayerAdapter = (*MPRISHandler)(nil) + _ types.OrgMprisMediaPlayer2Adapter = (*MPRISHandler)(nil) + _ types.OrgMprisMediaPlayer2PlayerAdapter = (*MPRISHandler)(nil) + _ types.OrgMprisMediaPlayer2PlayerAdapterLoopStatus = (*MPRISHandler)(nil) ) var ( @@ -186,6 +187,30 @@ func (m *MPRISHandler) PlaybackStatus() (types.PlaybackStatus, error) { return "", errors.New("unknown playback status") } +func (m *MPRISHandler) LoopStatus() (types.LoopStatus, error) { + switch m.pm.LoopMode() { + case LoopModeAll: + return types.LoopStatusPlaylist, nil + case LoopModeOne: + return types.LoopStatusTrack, nil + case LoopModeNone: + return types.LoopStatusNone, nil + } + return "", errors.New("unknown loop status") +} + +func (m *MPRISHandler) SetLoopStatus(status types.LoopStatus) error { + switch status { + case types.LoopStatusPlaylist: + return m.pm.SetLoopMode(LoopModeAll) + case types.LoopStatusTrack: + return m.pm.SetLoopMode(LoopModeOne) + case types.LoopStatusNone: + return m.pm.SetLoopMode(LoopModeNone) + } + return errors.New("unknown loop status") +} + func (m *MPRISHandler) Rate() (float64, error) { return 1, nil } diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index fb121bf..8622db8 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -17,6 +17,14 @@ const ( ReplayGainTrack = string(player.ReplayGainTrack) ) +type LoopMode int + +const ( + LoopModeNone LoopMode = LoopMode(player.LoopNone) + LoopModeAll LoopMode = LoopMode(player.LoopAll) + LoopModeOne LoopMode = LoopMode(player.LoopOne) +) + // A high-level Subsonic-aware playback backend. // Manages loading tracks into the Player queue, // sending callbacks on play time updates and track changes. @@ -40,7 +48,7 @@ type PlaybackManager struct { onSongChange []func(nowPlaying, justScrobbledIfAny *mediaprovider.Track) onPlayTimeUpdate []func(float64, float64) - onLoopModeChange []func(string) + onLoopModeChange []func(LoopMode) } func NewPlaybackManager( @@ -130,7 +138,7 @@ func (p *PlaybackManager) OnPlayTimeUpdate(cb func(float64, float64)) { } // Registers a callback that is notified whenever the loop mode changes. -func (p *PlaybackManager) OnLoopModeChange(cb func(string)) { +func (p *PlaybackManager) OnLoopModeChange(cb func(LoopMode)) { p.onLoopModeChange = append(p.onLoopModeChange, cb) } @@ -305,12 +313,28 @@ func (p *PlaybackManager) SetNextLoopMode() error { } for _, cb := range p.onLoopModeChange { - cb(p.player.GetLoopMode().String()) + cb(LoopMode(p.player.GetLoopMode())) } return nil } +func (p *PlaybackManager) SetLoopMode(loopMode LoopMode) error { + if err := p.player.SetLoopMode(player.LoopMode(loopMode)); err != nil { + return err + } + + for _, cb := range p.onLoopModeChange { + cb(loopMode) + } + + return nil +} + +func (p *PlaybackManager) LoopMode() LoopMode { + return LoopMode(p.player.GetLoopMode()) +} + // call BEFORE updating p.nowPlayingIdx func (p *PlaybackManager) checkScrobble() { if !p.scrobbleCfg.Enabled || len(p.playQueue) == 0 || p.nowPlayingIdx < 0 { diff --git a/ui/bottompanel.go b/ui/bottompanel.go index 36ad8cc..f451667 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -54,7 +54,7 @@ func NewBottomPanel(p *player.Player, pm *backend.PlaybackManager, contr *contro p.OnStopped(func() { bp.Controls.SetPlaying(false) }) - pm.OnLoopModeChange(func(mode string) { + pm.OnLoopModeChange(func(mode backend.LoopMode) { bp.AuxControls.SetLoopMode(mode) }) diff --git a/ui/widgets/auxcontrols.go b/ui/widgets/auxcontrols.go index 0119e23..0589438 100644 --- a/ui/widgets/auxcontrols.go +++ b/ui/widgets/auxcontrols.go @@ -7,6 +7,7 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "github.com/dweymouth/supersonic/backend" myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" ) @@ -67,14 +68,15 @@ func (a *AuxControls) OnChangeLoopMode(f func()) { a.loop.OnTapped = f } -func (a *AuxControls) SetLoopMode(mode string) { - if mode == "all" { +func (a *AuxControls) SetLoopMode(mode backend.LoopMode) { + switch mode { + case backend.LoopModeAll: a.loop.Importance = widget.HighImportance a.loop.Icon = myTheme.RepeatIcon - } else if mode == "one" { + case backend.LoopModeOne: a.loop.Importance = widget.HighImportance a.loop.Icon = myTheme.RepeatOneIcon - } else { + case backend.LoopModeNone: a.loop.Importance = widget.MediumImportance a.loop.Icon = myTheme.RepeatIcon } From e754b40a0982f0eeae48d568655415f8ebb178f8 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 13 Jul 2023 14:41:11 -0700 Subject: [PATCH 09/11] implement MPRIS SetVolume --- backend/mpris.go | 9 +++++++-- backend/playbackmanager.go | 21 +++++++++++++++++++++ ui/bottompanel.go | 9 ++++----- ui/mainwindow.go | 8 ++++---- ui/widgets/auxcontrols.go | 32 +++++++++++++++++++++++--------- 5 files changed, 59 insertions(+), 20 deletions(-) diff --git a/backend/mpris.go b/backend/mpris.go index 1b89376..83243d9 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -59,6 +59,11 @@ func NewMPRISHandler(playerName string, p *player.Player, pm *PlaybackManager) * m.evt.Player.OnTitle() } }) + m.pm.OnVolumeChange(func(vol int) { + if m.connErr == nil { + m.evt.Player.OnVolume() + } + }) emitPlayStatus := func() { if m.connErr == nil { m.evt.Player.OnPlayPause() @@ -253,8 +258,8 @@ func (m *MPRISHandler) Volume() (float64, error) { return float64(m.p.GetVolume()) / 100, nil } -func (m *MPRISHandler) SetVolume(float64) error { - return errNotImplemented +func (m *MPRISHandler) SetVolume(v float64) error { + return m.pm.SetVolume(int(v * 100)) } func (m *MPRISHandler) Position() (int64, error) { diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 8622db8..97de5af 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -49,6 +49,7 @@ type PlaybackManager struct { onSongChange []func(nowPlaying, justScrobbledIfAny *mediaprovider.Track) onPlayTimeUpdate []func(float64, float64) onLoopModeChange []func(LoopMode) + onVolumeChange []func(int) } func NewPlaybackManager( @@ -142,6 +143,11 @@ func (p *PlaybackManager) OnLoopModeChange(cb func(LoopMode)) { p.onLoopModeChange = append(p.onLoopModeChange, cb) } +// Registers a callback that is notified whenever the volume changes. +func (p *PlaybackManager) OnVolumeChange(cb func(int)) { + p.onVolumeChange = append(p.onVolumeChange, cb) +} + // Loads the specified album into the play queue. func (p *PlaybackManager) LoadAlbum(albumID string, appendToQueue bool, shuffle bool) error { album, err := p.sm.Server.GetAlbum(albumID) @@ -335,6 +341,21 @@ func (p *PlaybackManager) LoopMode() LoopMode { return LoopMode(p.player.GetLoopMode()) } +func (p *PlaybackManager) SetVolume(vol int) error { + vol = clamp(vol, 0, 100) + if err := p.player.SetVolume(vol); err != nil { + return err + } + for _, cb := range p.onVolumeChange { + cb(vol) + } + return nil +} + +func (p *PlaybackManager) Volume() int { + return p.player.GetVolume() +} + // call BEFORE updating p.nowPlayingIdx func (p *PlaybackManager) checkScrobble() { if !p.scrobbleCfg.Enabled || len(p.playQueue) == 0 || p.nowPlayingIdx < 0 { diff --git a/ui/bottompanel.go b/ui/bottompanel.go index f451667..85162d9 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -54,9 +54,7 @@ func NewBottomPanel(p *player.Player, pm *backend.PlaybackManager, contr *contro p.OnStopped(func() { bp.Controls.SetPlaying(false) }) - pm.OnLoopModeChange(func(mode backend.LoopMode) { - bp.AuxControls.SetLoopMode(mode) - }) + pm.OnLoopModeChange(bp.AuxControls.SetLoopMode) bp.NowPlaying = widgets.NewNowPlayingCard() bp.NowPlaying.OnShowCoverImage = func() { @@ -100,8 +98,9 @@ func NewBottomPanel(p *player.Player, pm *backend.PlaybackManager, contr *contro }) bp.AuxControls = widgets.NewAuxControls(p.GetVolume()) - bp.AuxControls.VolumeControl.OnVolumeChanged = func(v int) { - _ = p.SetVolume(v) + pm.OnVolumeChange(bp.AuxControls.VolumeControl.SetVolume) + bp.AuxControls.VolumeControl.OnSetVolume = func(v int) { + _ = bp.playbackManager.SetVolume(v) } bp.AuxControls.OnChangeLoopMode(func() { bp.playbackManager.SetNextLoopMode() diff --git a/ui/mainwindow.go b/ui/mainwindow.go index be0d54e..0baec49 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -163,15 +163,15 @@ func (m *MainWindow) SetupSystemTrayMenu(appName string, fyneApp fyne.App) { }), fyne.NewMenuItemSeparator(), fyne.NewMenuItem("Volume +10%", func() { - vol := m.App.Player.GetVolume() + vol := m.App.PlaybackManager.Volume() vol = vol + int(float64(vol)*0.1) // will clamp to range for us - m.BottomPanel.AuxControls.VolumeControl.SetVolume(vol) + m.App.PlaybackManager.SetVolume(vol) }), fyne.NewMenuItem("Volume -10%", func() { - vol := m.App.Player.GetVolume() + vol := m.App.PlaybackManager.Volume() vol = vol - int(float64(vol)*0.1) - m.BottomPanel.AuxControls.VolumeControl.SetVolume(vol) + m.App.PlaybackManager.SetVolume(vol) }), fyne.NewMenuItemSeparator(), fyne.NewMenuItem("Show", m.Window.Show), diff --git a/ui/widgets/auxcontrols.go b/ui/widgets/auxcontrols.go index 0589438..e426157 100644 --- a/ui/widgets/auxcontrols.go +++ b/ui/widgets/auxcontrols.go @@ -162,7 +162,7 @@ type VolumeControl struct { icon *TappableIcon slider *volumeSlider - OnVolumeChanged func(int) + OnSetVolume func(int) muted bool lastVol int @@ -185,24 +185,35 @@ func NewVolumeControl(initialVol int) *VolumeControl { return v } +// Sets the volume that is displayed in the slider. +// Does not invoke OnSetVolume callback. +func (v *VolumeControl) SetVolume(vol int) { + if (vol == v.lastVol && !v.muted) || (v.muted && vol == 0) { + return + } + v.lastVol = vol + v.muted = false + v.setDisplayedVolume(vol) +} + func (v *VolumeControl) onChanged(volume float64) { vol := int(volume) v.lastVol = vol v.muted = false v.updateIconForVolume(vol) - if v.OnVolumeChanged != nil { - v.OnVolumeChanged(vol) - } + v.invokeOnVolumeChange(vol) } func (v *VolumeControl) toggleMute() { if !v.muted { v.muted = true v.lastVol = int(v.slider.Value) - v.SetVolume(0) + v.setDisplayedVolume(0) + v.invokeOnVolumeChange(0) } else { v.muted = false - v.SetVolume(v.lastVol) + v.setDisplayedVolume(v.lastVol) + v.invokeOnVolumeChange(v.lastVol) } } @@ -211,12 +222,15 @@ func (v *VolumeControl) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(v.container) } -func (v *VolumeControl) SetVolume(vol int) { +func (v *VolumeControl) setDisplayedVolume(vol int) { v.slider.Value = float64(vol) v.slider.Refresh() v.updateIconForVolume(vol) - if v.OnVolumeChanged != nil { - v.OnVolumeChanged(vol) +} + +func (v *VolumeControl) invokeOnVolumeChange(vol int) { + if v.OnSetVolume != nil { + v.OnSetVolume(vol) } } From 0dad2461245627673819d4530b4b70c5a3d6619a Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 13 Jul 2023 18:21:42 -0700 Subject: [PATCH 10/11] temporarily move to patched fork --- go.mod | 2 ++ go.sum | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 3c532d7..a3a4aa5 100644 --- a/go.mod +++ b/go.mod @@ -48,3 +48,5 @@ require ( ) replace fyne.io/fyne/v2 v2.3.5 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230505012127-ca61c153b2a5 + +replace github.com/quarckster/go-mpris-server v1.0.1 => github.com/dweymouth/go-mpris-server v0.0.0-20230714011337-8bc0b52618a5 diff --git a/go.sum b/go.sum index c23e519..1e26d28 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230505012127-ca61c153b2a5 h1:uXbHzg9HfefA7OVHu9gahobCP5B43df3L1MwU0GbdRs= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230505012127-ca61c153b2a5/go.mod h1:X2+NrR+62mvAiAt2fwKT7035zQsE77KVV1NlvWo4vW8= +github.com/dweymouth/go-mpris-server v0.0.0-20230714011337-8bc0b52618a5 h1:QhBf0iHPPUSxalHjhls+9LFqMuNT12U3Mo/AFw4Ornc= +github.com/dweymouth/go-mpris-server v0.0.0-20230714011337-8bc0b52618a5/go.mod h1:2b4IdrpnEoEfU+6fQKjYhAgdvsiz4JxmTpDAUrMJVO4= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0= github.com/dweymouth/go-subsonic v0.0.0-20230614154319-792d18c75fb4 h1:cuyvB4GjTMBHKJwMJ/LFpuKfSVXuCheNj6fgxBa3m1Y= @@ -265,8 +267,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/quarckster/go-mpris-server v1.0.1 h1:JAbVBJK1ijjwEH4gfaOtwfElDVuaBPnWyi8rZJEHOxc= -github.com/quarckster/go-mpris-server v1.0.1/go.mod h1:2b4IdrpnEoEfU+6fQKjYhAgdvsiz4JxmTpDAUrMJVO4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From 9004e002f227547090f752c8c67245724012da76 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 13 Jul 2023 18:43:18 -0700 Subject: [PATCH 11/11] fix loopmode crash regression --- ui/bottompanel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/bottompanel.go b/ui/bottompanel.go index 85162d9..69fe61d 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -54,7 +54,6 @@ func NewBottomPanel(p *player.Player, pm *backend.PlaybackManager, contr *contro p.OnStopped(func() { bp.Controls.SetPlaying(false) }) - pm.OnLoopModeChange(bp.AuxControls.SetLoopMode) bp.NowPlaying = widgets.NewNowPlayingCard() bp.NowPlaying.OnShowCoverImage = func() { @@ -98,6 +97,7 @@ func NewBottomPanel(p *player.Player, pm *backend.PlaybackManager, contr *contro }) bp.AuxControls = widgets.NewAuxControls(p.GetVolume()) + pm.OnLoopModeChange(bp.AuxControls.SetLoopMode) pm.OnVolumeChange(bp.AuxControls.VolumeControl.SetVolume) bp.AuxControls.VolumeControl.OnSetVolume = func(v int) { _ = bp.playbackManager.SetVolume(v)