From cc85b79f79eba9695f82f9fa6ca5ac4c710764c7 Mon Sep 17 00:00:00 2001 From: zackslash Date: Fri, 20 Oct 2023 18:04:05 +0100 Subject: [PATCH] Extend MPMedia implementation to handle 'seek' --- backend/mpmedia_mac.go | 21 +++++++++++++++++++-- backend/mpmediabridge.h | 9 ++++++--- backend/mpmediabridge.m | 36 ++++++++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/backend/mpmedia_mac.go b/backend/mpmedia_mac.go index 7855ed2..571f3d2 100644 --- a/backend/mpmedia_mac.go +++ b/backend/mpmedia_mac.go @@ -16,6 +16,7 @@ import ( ) import ( + "fmt" "log" "unsafe" @@ -26,7 +27,7 @@ import ( // 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) { +func os_remote_command_callback(command C.Command, value C.int) { switch command { case C.PLAY: mpMediaEventRecipient.OnCommandPlay() @@ -40,6 +41,8 @@ func os_remote_command_callback(command C.Command) { mpMediaEventRecipient.OnCommandPreviousTrack() case C.NEXT_TRACK: mpMediaEventRecipient.OnCommandNextTrack() + case C.SEEK: + mpMediaEventRecipient.OnCommandSeek(int(value)) default: log.Printf("unknown OS command received: %v", command) } @@ -85,7 +88,9 @@ func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager) cArtURL := C.CString(artURL) defer C.free(unsafe.Pointer(cArtURL)) - C.set_os_now_playing_info(cTitle, cArtist, cArtURL) + cTrackDuration := C.double(track.Duration) + + C.set_os_now_playing_info(cTitle, cArtist, cArtURL, cTrackDuration) } }) @@ -101,6 +106,10 @@ func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager) C.set_os_playback_state_paused() }) + mp.player.OnSeek(func() { + C.update_os_now_playing_info_position(C.double(mp.player.GetStatus().TimePos)) + }) + return mp } @@ -159,3 +168,11 @@ func (mp *MPMediaHandler) OnCommandPreviousTrack() { } mp.player.SeekBackOrPrevious() } + +// MPMediaHandler instance received OS command to 'seek' +func (mp *MPMediaHandler) OnCommandSeek(positionSeconds int) { + if mp == nil || mp.player == nil { + return + } + mp.player.Seek(fmt.Sprintf("%d", positionSeconds), player.SeekAbsolute) +} diff --git a/backend/mpmediabridge.h b/backend/mpmediabridge.h index ece42e0..9e9aac7 100644 --- a/backend/mpmediabridge.h +++ b/backend/mpmediabridge.h @@ -20,7 +20,8 @@ typedef enum { STOP, TOGGLE, NEXT_TRACK, - PREVIOUS_TRACK + PREVIOUS_TRACK, + SEEK } Command; /** @@ -30,15 +31,17 @@ void register_os_remote_commands(); /** * Go-backed callback to static function that is called when OS remote commands are received. +* If a value is anticipated with the specified command, the 'value' argument will be non-zero. */ -void os_remote_command_callback(Command command); +void os_remote_command_callback(Command command, int value); /** * 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 set_os_now_playing_info(const char *title, const char *artist, const char *coverArtFileURL); +void set_os_now_playing_info(const char *title, const char *artist, const char *coverArtFileURL, double trackDuration); +void update_os_now_playing_info_position(double positionSeconds); /** * Setter functions for updating the global playback state. diff --git a/backend/mpmediabridge.m b/backend/mpmediabridge.m index 6cb2a41..ac4892c 100644 --- a/backend/mpmediabridge.m +++ b/backend/mpmediabridge.m @@ -8,32 +8,40 @@ void register_os_remote_commands() { MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; [commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - os_remote_command_callback(PLAY); + os_remote_command_callback(PLAY, 0); return MPRemoteCommandHandlerStatusSuccess; }]; [commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - os_remote_command_callback(PAUSE); + os_remote_command_callback(PAUSE, 0); return MPRemoteCommandHandlerStatusSuccess; }]; [commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - os_remote_command_callback(TOGGLE); + os_remote_command_callback(TOGGLE, 0); return MPRemoteCommandHandlerStatusSuccess; }]; [commandCenter.stopCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - os_remote_command_callback(STOP); + os_remote_command_callback(STOP, 0); return MPRemoteCommandHandlerStatusSuccess; }]; [commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - os_remote_command_callback(NEXT_TRACK); + os_remote_command_callback(NEXT_TRACK, 0); return MPRemoteCommandHandlerStatusSuccess; }]; [commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { - os_remote_command_callback(PREVIOUS_TRACK); + os_remote_command_callback(PREVIOUS_TRACK, 0); + return MPRemoteCommandHandlerStatusSuccess; + }]; + + [commandCenter.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { + MPChangePlaybackPositionCommandEvent *positionChangeEvent = (MPChangePlaybackPositionCommandEvent *)event; + double posSecDouble = positionChangeEvent.positionTime; + int posSeconds = (int)posSecDouble; + os_remote_command_callback(SEEK, posSeconds); return MPRemoteCommandHandlerStatusSuccess; }]; } @@ -41,7 +49,7 @@ void register_os_remote_commands() { /** * 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) { +void set_os_now_playing_info(const char *title, const char *artist, const char *coverArtFileURL, double trackDuration) { NSString *coverArtLocationString = [NSString stringWithUTF8String:coverArtFileURL]; NSURL *coverArtURL = [NSURL URLWithString:coverArtLocationString]; NSImage *coverArtImage = [[NSImage alloc] initWithContentsOfURL:coverArtURL]; @@ -54,12 +62,24 @@ void set_os_now_playing_info(const char *title, const char *artist, const char * NSDictionary *nowPlayingInfo = @{ MPMediaItemPropertyTitle: [NSString stringWithUTF8String:title], MPMediaItemPropertyArtist: [NSString stringWithUTF8String:artist], - MPMediaItemPropertyArtwork: coverArt + MPMediaItemPropertyArtwork: coverArt, + MPMediaItemPropertyPlaybackDuration: @(trackDuration) // Expects 'NSNumber' }; infoCenter.nowPlayingInfo = nowPlayingInfo; } +/** + * C bridge updating the OS playback position. + * creates a mutable copy of the immutable dictionary and writes it back with updated position. + */ +void update_os_now_playing_info_position(double positionSeconds) { + MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; + NSMutableDictionary *updatedInfo = [infoCenter.nowPlayingInfo mutableCopy]; + updatedInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(positionSeconds); + infoCenter.nowPlayingInfo = [updatedInfo copy]; +} + /** * C bridge setting the OS playback state to 'playing'. */