From 944491d1333709df8706a2e9f4f179166939504a Mon Sep 17 00:00:00 2001 From: zackslash Date: Wed, 18 Oct 2023 23:25:57 +0100 Subject: [PATCH] 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); +}