From 560c0955aceea11f364057423cf575c4f9fcfe3f Mon Sep 17 00:00:00 2001 From: zackslash Date: Wed, 18 Oct 2023 23:16:05 +0100 Subject: [PATCH 1/8] WIP macos media bridge --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index fc6e8d8..b087f25 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ supersonic *.dll *.so *.dylib +Supersonic.app # Test binary, built with `go test -c` *.test @@ -26,3 +27,6 @@ vendor/ go.work # End of https://www.toptal.com/developers/gitignore/api/go + +# Platform specific metadata files +.DS_Store From 944491d1333709df8706a2e9f4f179166939504a Mon Sep 17 00:00:00 2001 From: zackslash Date: Wed, 18 Oct 2023 23:25:57 +0100 Subject: [PATCH 2/8] initial MPMedia bridge --- backend/app.go | 7 +++++ backend/mpmedia.go | 70 +++++++++++++++++++++++++++++++++++++++++ backend/mpmediabridge.h | 18 +++++++++++ backend/mpmediabridge.m | 47 +++++++++++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 backend/mpmedia.go create mode 100644 backend/mpmediabridge.h create mode 100644 backend/mpmediabridge.m diff --git a/backend/app.go b/backend/app.go index fab167f..e4f25dc 100644 --- a/backend/app.go +++ b/backend/app.go @@ -38,6 +38,7 @@ type App struct { Player *player.Player UpdateChecker UpdateChecker MPRISHandler *MPRISHandler + MPMediaHandler *MPMediaHandler // UI callbacks to be set in main OnReactivate func() @@ -110,6 +111,7 @@ func StartupApp(appName, displayAppName, appVersionTag, configFile, latestReleas }) a.setupMPRIS(displayAppName) + a.setupMPMedia() return a, nil } @@ -231,6 +233,11 @@ func (a *App) setupMPRIS(mprisAppName string) { a.MPRISHandler.Start() } +func (a *App) setupMPMedia() { + a.MPMediaHandler = NewMPMediaHandler(a.Player, a.PlaybackManager) + a.MPMediaHandler.ArtURLLookup = a.ImageManager.GetCoverArtUrl +} + func (a *App) LoginToDefaultServer(string) error { serverCfg := a.ServerManager.GetDefaultServer() if serverCfg == nil { diff --git a/backend/mpmedia.go b/backend/mpmedia.go new file mode 100644 index 0000000..521bef3 --- /dev/null +++ b/backend/mpmedia.go @@ -0,0 +1,70 @@ +package backend + +/** +* This file handles implementation of MacOS native controls via the native 'MediaPlayer' framework +**/ + +// #cgo CFLAGS: -x objective-c +// #cgo LDFLAGS: -framework Cocoa -framework MediaPlayer +// #include "mpmediabridge.h" +import ( + "C" +) + +import ( + "fmt" + "unsafe" + + "github.com/dweymouth/supersonic/backend/mediaprovider" + "github.com/dweymouth/supersonic/player" +) + +type MPMediaHandler struct { + player *player.Player + playbackManager *PlaybackManager + ArtURLLookup func(trackID string) (string, error) +} + +func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager) *MPMediaHandler { + mp := &MPMediaHandler{ + player: player, + playbackManager: playbackManager, + } + + mp.playbackManager.OnSongChange(func(track, _ *mediaprovider.Track) { + if mp.ArtURLLookup != nil { + mp.ArtURLLookup(track.CoverArtID) + } + + cTitle := C.CString(track.Name) + defer C.free(unsafe.Pointer(cTitle)) + + artist := "" + if len(track.ArtistNames) > 0 { + artist = track.ArtistNames[0] + } + + cArtist := C.CString(artist) + defer C.free(unsafe.Pointer(cArtist)) + + // TODO: pass in local URL for image (can be loaded via NSImage) + C.setNowPlayingInfo(cTitle, cArtist) + }) + + mp.player.OnStopped(func() { + // TODO: media stopped playing + fmt.Println("Stopped") + }) + + mp.player.OnPlaying(func() { + // TODO: media playing + fmt.Println("Playing") + }) + + mp.player.OnPaused(func() { + // TODO: media paused + fmt.Println("Paused") + }) + + return mp +} diff --git a/backend/mpmediabridge.h b/backend/mpmediabridge.h new file mode 100644 index 0000000..80b32e8 --- /dev/null +++ b/backend/mpmediabridge.h @@ -0,0 +1,18 @@ +/** + * mpmediabridge.h + * + * This file provides a C bridge to the Objective-C framework for macOS media playback. + * It offers a simplified interface to interact with the MPNowPlayingInfoCenter and other + * related media playback functionalities in macOS without dealing directly with + * Objective-C code. + */ + +#include +#include + +/** + * Updates the "Now Playing" information on macOS for media playback + * using the MPNowPlayingInfoCenter API to set the metadata + * for the currently playing media in the system's "Now Playing" interface. + */ +void setNowPlayingInfo(const char *title, const char *artist); diff --git a/backend/mpmediabridge.m b/backend/mpmediabridge.m new file mode 100644 index 0000000..d8fce4c --- /dev/null +++ b/backend/mpmediabridge.m @@ -0,0 +1,47 @@ +#import "mpmediabridge.h" + +/** + * Native Objective-C function for setting "Now Playing" information on macOS for media playback using the native APIs. + */ +void setNowPlayingInfoNative(const char *title, const char *artist, double trackDuration, NSImage *artworkImage, double elapsedTime) { + + // TODO: the remove command center logic needs to move + MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; + [commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { + // TODO: Handle play command + return MPRemoteCommandHandlerStatusSuccess; + }]; + + [commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { + // TODO: Handle pause command + return MPRemoteCommandHandlerStatusSuccess; + }]; + + [commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { + // TODO: Handle toggle command + return MPRemoteCommandHandlerStatusSuccess; + }]; + + MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; + + NSDictionary *nowPlayingInfo = @{ + MPMediaItemPropertyTitle: [NSString stringWithUTF8String:title], + MPMediaItemPropertyArtist: [NSString stringWithUTF8String:artist], + MPMediaItemPropertyPlaybackDuration: @(trackDuration), + MPNowPlayingInfoPropertyElapsedPlaybackTime: @(elapsedTime) + }; + + infoCenter.nowPlayingInfo = nowPlayingInfo; + infoCenter.playbackState = MPNowPlayingPlaybackStatePlaying; +} + +/** + * 'C' bridge function for 'setNowPlayingInfoNative' + */ +void setNowPlayingInfo(const char *title, const char *artist) { + const char *imagePath = "/path/to/image.png"; + double trackDuration = 300.0; + double elapsedTime = 120.0; + + setNowPlayingInfoNative(title, artist, trackDuration, NULL, elapsedTime); +} From bfe10f09a491e48771b66571644c787c81051e1c Mon Sep 17 00:00:00 2001 From: zackslash Date: Thu, 19 Oct 2023 15:47:21 +0100 Subject: [PATCH 3/8] Expose Pause & Continue player functions --- player/player.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/player/player.go b/player/player.go index d10e936..32945d4 100644 --- a/player/player.go +++ b/player/player.go @@ -396,23 +396,33 @@ func (p *Player) PlayPause() error { } return nil case Playing: - err := p.setPaused(true) - if err == nil { - p.prePausedState = p.status.State - p.setState(Paused) - } - return err + return p.Pause() case Paused: - err := p.setPaused(false) - if err == nil { - p.setState(p.prePausedState) - } - return err + return p.Continue() default: return errors.New("Unknown player state") } } +// Pause playback and update the player state +func (p *Player) Pause() error { + err := p.setPaused(true) + if err == nil { + p.prePausedState = p.status.State + p.setState(Paused) + } + return err +} + +// Continue playback and update the player state +func (p *Player) Continue() error { + err := p.setPaused(false) + if err == nil { + p.setState(p.prePausedState) + } + return err +} + // Get the loop mode of the player. func (p *Player) GetLoopMode() LoopMode { return p.loopMode From 7e6fd269dbbed44f98dbfc3b4a5acf1937b6685a Mon Sep 17 00:00:00 2001 From: zackslash Date: Thu, 19 Oct 2023 15:47:47 +0100 Subject: [PATCH 4/8] populate cache in MPMedia ArtURLLookup --- backend/app.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/app.go b/backend/app.go index e4f25dc..fb49b1a 100644 --- a/backend/app.go +++ b/backend/app.go @@ -235,7 +235,11 @@ func (a *App) setupMPRIS(mprisAppName string) { func (a *App) setupMPMedia() { a.MPMediaHandler = NewMPMediaHandler(a.Player, a.PlaybackManager) - a.MPMediaHandler.ArtURLLookup = a.ImageManager.GetCoverArtUrl + a.MPMediaHandler.ArtURLLookup = func(coverID string) (string, error) { + // artwork needs to be in cache before playback, so cover is retrieved in advance. + a.ImageManager.GetCoverThumbnail(coverID) + return a.ImageManager.GetCoverArtUrl(coverID) + } } func (a *App) LoginToDefaultServer(string) error { From e938d62cb3874b76367f5607b7f2d24124819086 Mon Sep 17 00:00:00 2001 From: zackslash Date: Thu, 19 Oct 2023 15:48:56 +0100 Subject: [PATCH 5/8] native functionality bridge --- backend/mpmedia.go | 60 +-------------- backend/mpmedia_mac.go | 161 +++++++++++++++++++++++++++++++++++++++ backend/mpmedia_other.go | 10 +++ backend/mpmediabridge.h | 36 ++++++++- backend/mpmediabridge.m | 70 ++++++++++++----- 5 files changed, 257 insertions(+), 80 deletions(-) create mode 100644 backend/mpmedia_mac.go create mode 100644 backend/mpmedia_other.go diff --git a/backend/mpmedia.go b/backend/mpmedia.go index 521bef3..385a113 100644 --- a/backend/mpmedia.go +++ b/backend/mpmedia.go @@ -1,70 +1,12 @@ package backend -/** -* This file handles implementation of MacOS native controls via the native 'MediaPlayer' framework -**/ - -// #cgo CFLAGS: -x objective-c -// #cgo LDFLAGS: -framework Cocoa -framework MediaPlayer -// #include "mpmediabridge.h" import ( - "C" -) - -import ( - "fmt" - "unsafe" - - "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/player" ) +// MPMediaHandler is the handler for MacOS media controls and system events. type MPMediaHandler struct { player *player.Player playbackManager *PlaybackManager ArtURLLookup func(trackID string) (string, error) } - -func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager) *MPMediaHandler { - mp := &MPMediaHandler{ - player: player, - playbackManager: playbackManager, - } - - mp.playbackManager.OnSongChange(func(track, _ *mediaprovider.Track) { - if mp.ArtURLLookup != nil { - mp.ArtURLLookup(track.CoverArtID) - } - - cTitle := C.CString(track.Name) - defer C.free(unsafe.Pointer(cTitle)) - - artist := "" - if len(track.ArtistNames) > 0 { - artist = track.ArtistNames[0] - } - - cArtist := C.CString(artist) - defer C.free(unsafe.Pointer(cArtist)) - - // TODO: pass in local URL for image (can be loaded via NSImage) - C.setNowPlayingInfo(cTitle, cArtist) - }) - - mp.player.OnStopped(func() { - // TODO: media stopped playing - fmt.Println("Stopped") - }) - - mp.player.OnPlaying(func() { - // TODO: media playing - fmt.Println("Playing") - }) - - mp.player.OnPaused(func() { - // TODO: media paused - fmt.Println("Paused") - }) - - return mp -} diff --git a/backend/mpmedia_mac.go b/backend/mpmedia_mac.go new file mode 100644 index 0000000..7855ed2 --- /dev/null +++ b/backend/mpmedia_mac.go @@ -0,0 +1,161 @@ +//go:build darwin + +package backend + +/** +* This file handles implementation of MacOS native controls via the native 'MediaPlayer' framework +**/ + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework MediaPlayer +#include "mpmediabridge.h" +*/ +import ( + "C" +) + +import ( + "log" + "unsafe" + + "github.com/dweymouth/supersonic/backend/mediaprovider" + "github.com/dweymouth/supersonic/player" +) + +// os_remote_command_callback is called by Objective-C when incoming OS media commands are received. +// +//export os_remote_command_callback +func os_remote_command_callback(command C.Command) { + switch command { + case C.PLAY: + mpMediaEventRecipient.OnCommandPlay() + case C.PAUSE: + mpMediaEventRecipient.OnCommandPause() + case C.STOP: + mpMediaEventRecipient.OnCommandStop() + case C.TOGGLE: + mpMediaEventRecipient.OnCommandTogglePlayPause() + case C.PREVIOUS_TRACK: + mpMediaEventRecipient.OnCommandPreviousTrack() + case C.NEXT_TRACK: + mpMediaEventRecipient.OnCommandNextTrack() + default: + log.Printf("unknown OS command received: %v", command) + } +} + +// global recipient for Object-C callbacks from command center. +// This is global so that it can be called from 'os_remote_command_callback' to avoid passing Go pointers into C. +var mpMediaEventRecipient *MPMediaHandler + +// NewMPMediaHandler creates a new MPMediaHandler instances and sets it as the current recipient +// for incoming system events. +func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager) *MPMediaHandler { + mp := &MPMediaHandler{ + player: player, + playbackManager: playbackManager, + } + + // register remote commands and set callback target + mpMediaEventRecipient = mp + C.register_os_remote_commands() + + mp.playbackManager.OnSongChange(func(track, _ *mediaprovider.Track) { + if track != nil && track.ID != "" { + var artURL string + if mp.ArtURLLookup != nil { + var err error + if artURL, err = mp.ArtURLLookup(track.CoverArtID); err != nil { + log.Printf("error fetching art url: %s", err.Error()) + } + } + + cTitle := C.CString(track.Name) + defer C.free(unsafe.Pointer(cTitle)) + + var artist string + if len(track.ArtistNames) > 0 { + artist = track.ArtistNames[0] + } + + cArtist := C.CString(artist) + defer C.free(unsafe.Pointer(cArtist)) + + cArtURL := C.CString(artURL) + defer C.free(unsafe.Pointer(cArtURL)) + + C.set_os_now_playing_info(cTitle, cArtist, cArtURL) + } + }) + + mp.player.OnStopped(func() { + C.set_os_playback_state_stopped() + }) + + mp.player.OnPlaying(func() { + C.set_os_playback_state_playing() + }) + + mp.player.OnPaused(func() { + C.set_os_playback_state_paused() + }) + + return mp +} + +/** +* Handle incoming OS commands. +**/ + +// MPMediaHandler instance received OS command 'pause' +func (mp *MPMediaHandler) OnCommandPause() { + if mp == nil || mp.player == nil { + return + } + mp.player.Pause() +} + +// MPMediaHandler instance received OS command 'play' +func (mp *MPMediaHandler) OnCommandPlay() { + if mp == nil || mp.player == nil { + return + } + mp.player.Continue() +} + +// MPMediaHandler instance received OS command 'stop' +func (mp *MPMediaHandler) OnCommandStop() { + if mp == nil || mp.player == nil { + return + } + mp.player.Stop() +} + +// MPMediaHandler instance received OS command 'toggle' +func (mp *MPMediaHandler) OnCommandTogglePlayPause() { + if mp == nil || mp.player == nil { + return + } + if mp.player.GetStatus().State == player.Playing { + mp.OnCommandPause() + } else { + mp.OnCommandPlay() + } +} + +// MPMediaHandler instance received OS command 'next track' +func (mp *MPMediaHandler) OnCommandNextTrack() { + if mp == nil || mp.player == nil { + return + } + mp.player.SeekNext() +} + +// MPMediaHandler instance received OS command 'previous track' +func (mp *MPMediaHandler) OnCommandPreviousTrack() { + if mp == nil || mp.player == nil { + return + } + mp.player.SeekBackOrPrevious() +} diff --git a/backend/mpmedia_other.go b/backend/mpmedia_other.go new file mode 100644 index 0000000..1c0c275 --- /dev/null +++ b/backend/mpmedia_other.go @@ -0,0 +1,10 @@ +//go:build !darwin + +package backend + +import "github.com/dweymouth/supersonic/player" + +func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager) *MPMediaHandler { + // MPMediaHandler only supports macOS. + return nil +} diff --git a/backend/mpmediabridge.h b/backend/mpmediabridge.h index 80b32e8..b3902a3 100644 --- a/backend/mpmediabridge.h +++ b/backend/mpmediabridge.h @@ -1,18 +1,46 @@ /** * mpmediabridge.h * - * This file provides a C bridge to the Objective-C framework for macOS media playback. + * This file provides a C bridge to the Objective-C framework for macOS media playback & events. * It offers a simplified interface to interact with the MPNowPlayingInfoCenter and other - * related media playback functionalities in macOS without dealing directly with - * Objective-C code. + * related media playback functionalities in macOS without dealing directly with Objective-C code. */ #include #include +/** +* OS remote command enumeration, accepted by 'os_remote_command_callback'. +*/ +typedef enum { + PLAY, + PAUSE, + STOP, + TOGGLE, + NEXT_TRACK, + PREVIOUS_TRACK +} Command; + +/** +* registers the 'os_remote_command_callback' to receive OS media commands. +*/ +void register_os_remote_commands(); + +/** +* Go-backed callback to static function that is called when OS remote commands are received. +*/ +void os_remote_command_callback(Command command); + /** * Updates the "Now Playing" information on macOS for media playback * using the MPNowPlayingInfoCenter API to set the metadata * for the currently playing media in the system's "Now Playing" interface. */ -void setNowPlayingInfo(const char *title, const char *artist); +void set_os_now_playing_info(const char *title, const char *artist, const char *coverArtFileURL); + +/** + * Setter functions for updating the global playback state. + */ +void set_os_playback_state_playing(); +void set_os_playback_state_paused(); +void set_os_playback_state_stopped(); diff --git a/backend/mpmediabridge.m b/backend/mpmediabridge.m index d8fce4c..4bf538d 100644 --- a/backend/mpmediabridge.m +++ b/backend/mpmediabridge.m @@ -1,47 +1,83 @@ #import "mpmediabridge.h" /** - * Native Objective-C function for setting "Now Playing" information on macOS for media playback using the native APIs. + * C bridge registering callbacks for media playback events using the native CommandCenter API. */ -void setNowPlayingInfoNative(const char *title, const char *artist, double trackDuration, NSImage *artworkImage, double elapsedTime) { - - // TODO: the remove command center logic needs to move +void register_os_remote_commands() { MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; [commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - // TODO: Handle play command + os_remote_command_callback(PLAY); return MPRemoteCommandHandlerStatusSuccess; }]; [commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - // TODO: Handle pause command + os_remote_command_callback(PAUSE); return MPRemoteCommandHandlerStatusSuccess; }]; [commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - // TODO: Handle toggle command + os_remote_command_callback(TOGGLE); return MPRemoteCommandHandlerStatusSuccess; }]; - MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; + [commandCenter.stopCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { + os_remote_command_callback(STOP); + return MPRemoteCommandHandlerStatusSuccess; + }]; + [commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { + os_remote_command_callback(NEXT_TRACK); + return MPRemoteCommandHandlerStatusSuccess; + }]; + + [commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { + os_remote_command_callback(PREVIOUS_TRACK); + return MPRemoteCommandHandlerStatusSuccess; + }]; +} + +/** + * C bridge setting "Now Playing" information on macOS for media playback using the native APIs. + */ +void set_os_now_playing_info(const char *title, const char *artist, const char *coverArtFileURL) { + NSString *coverArtLocationString = [NSString stringWithUTF8String:coverArtFileURL]; + NSURL *coverArtURL = [NSURL URLWithString:coverArtLocationString]; + NSImage *coverArtImage = [[NSImage alloc] initWithContentsOfURL:coverArtURL]; + + MPMediaItemArtwork *coverArt = [[MPMediaItemArtwork alloc] initWithBoundsSize:coverArtImage.size requestHandler:^NSImage * _Nonnull(CGSize size) { + return coverArtImage; + }]; + + MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; NSDictionary *nowPlayingInfo = @{ MPMediaItemPropertyTitle: [NSString stringWithUTF8String:title], MPMediaItemPropertyArtist: [NSString stringWithUTF8String:artist], - MPMediaItemPropertyPlaybackDuration: @(trackDuration), - MPNowPlayingInfoPropertyElapsedPlaybackTime: @(elapsedTime) + MPMediaItemPropertyArtwork: coverArt }; infoCenter.nowPlayingInfo = nowPlayingInfo; +} + +/** + * C bridge setting the OS playback state to 'playing'. + */ +void set_os_playback_state_playing() { + MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; infoCenter.playbackState = MPNowPlayingPlaybackStatePlaying; } /** - * 'C' bridge function for 'setNowPlayingInfoNative' + * C bridge setting the OS playback state to 'paused'. */ -void setNowPlayingInfo(const char *title, const char *artist) { - const char *imagePath = "/path/to/image.png"; - double trackDuration = 300.0; - double elapsedTime = 120.0; - - setNowPlayingInfoNative(title, artist, trackDuration, NULL, elapsedTime); +void set_os_playback_state_paused() { + MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; + infoCenter.playbackState = MPNowPlayingPlaybackStatePaused; +} + +/** + * C bridge setting the OS playback state to 'stopped'. + */ +void set_os_playback_state_stopped() { + MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; + infoCenter.playbackState = MPNowPlayingPlaybackStateStopped; } From e191bf0d9f3dc60ccc5f04de9e593b8608f47255 Mon Sep 17 00:00:00 2001 From: zackslash Date: Thu, 19 Oct 2023 17:59:24 +0100 Subject: [PATCH 6/8] exclude C source files from non-darwin targets --- backend/app.go | 10 ++++++---- backend/mpmediabridge.h | 2 ++ backend/mpmediabridge.m | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/app.go b/backend/app.go index fb49b1a..bbf421d 100644 --- a/backend/app.go +++ b/backend/app.go @@ -235,10 +235,12 @@ func (a *App) setupMPRIS(mprisAppName string) { func (a *App) setupMPMedia() { a.MPMediaHandler = NewMPMediaHandler(a.Player, a.PlaybackManager) - a.MPMediaHandler.ArtURLLookup = func(coverID string) (string, error) { - // artwork needs to be in cache before playback, so cover is retrieved in advance. - a.ImageManager.GetCoverThumbnail(coverID) - return a.ImageManager.GetCoverArtUrl(coverID) + if a.MPMediaHandler != nil { + a.MPMediaHandler.ArtURLLookup = func(coverID string) (string, error) { + // artwork needs to be in cache before playback, so cover is retrieved in advance. + a.ImageManager.GetCoverThumbnail(coverID) + return a.ImageManager.GetCoverArtUrl(coverID) + } } } diff --git a/backend/mpmediabridge.h b/backend/mpmediabridge.h index b3902a3..ece42e0 100644 --- a/backend/mpmediabridge.h +++ b/backend/mpmediabridge.h @@ -1,3 +1,5 @@ +//go:build darwin + /** * mpmediabridge.h * diff --git a/backend/mpmediabridge.m b/backend/mpmediabridge.m index 4bf538d..6cb2a41 100644 --- a/backend/mpmediabridge.m +++ b/backend/mpmediabridge.m @@ -1,3 +1,5 @@ +//go:build darwin + #import "mpmediabridge.h" /** From 5175c5cae414c36eaf6f691e0c3de04f468232db Mon Sep 17 00:00:00 2001 From: zackslash Date: Thu, 19 Oct 2023 20:03:31 +0100 Subject: [PATCH 7/8] Pause() check state prior to execution --- player/player.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/player/player.go b/player/player.go index 32945d4..d2aa2f1 100644 --- a/player/player.go +++ b/player/player.go @@ -406,6 +406,9 @@ func (p *Player) PlayPause() error { // Pause playback and update the player state func (p *Player) Pause() error { + if p.status.State != Playing { + return nil + } err := p.setPaused(true) if err == nil { p.prePausedState = p.status.State From 90ecdf242f5e4049232ab1da654e269ec918ce3a Mon Sep 17 00:00:00 2001 From: zackslash Date: Fri, 20 Oct 2023 10:24:36 +0100 Subject: [PATCH 8/8] Update 'Continue' implementation --- player/player.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/player/player.go b/player/player.go index d2aa2f1..b33c9c3 100644 --- a/player/player.go +++ b/player/player.go @@ -419,11 +419,17 @@ func (p *Player) Pause() error { // Continue playback and update the player state func (p *Player) Continue() error { - err := p.setPaused(false) - if err == nil { - p.setState(p.prePausedState) + if p.status.State == Paused { + err := p.setPaused(false) + if err == nil { + p.setState(p.prePausedState) + } + return err + } else if p.status.State == Stopped { + return p.PlayFromBeginning() } - return err + + return nil } // Get the loop mode of the player.