Merge pull request #260 from zackslash/main
Extends MacOS MPMedia implementation to support 'seek' & 'duration'
This commit is contained in:
+21
-2
@@ -16,6 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ import (
|
|||||||
// os_remote_command_callback is called by Objective-C when incoming OS media commands are received.
|
// os_remote_command_callback is called by Objective-C when incoming OS media commands are received.
|
||||||
//
|
//
|
||||||
//export os_remote_command_callback
|
//export os_remote_command_callback
|
||||||
func os_remote_command_callback(command C.Command) {
|
func os_remote_command_callback(command C.Command, value C.double) {
|
||||||
switch command {
|
switch command {
|
||||||
case C.PLAY:
|
case C.PLAY:
|
||||||
mpMediaEventRecipient.OnCommandPlay()
|
mpMediaEventRecipient.OnCommandPlay()
|
||||||
@@ -40,6 +41,8 @@ func os_remote_command_callback(command C.Command) {
|
|||||||
mpMediaEventRecipient.OnCommandPreviousTrack()
|
mpMediaEventRecipient.OnCommandPreviousTrack()
|
||||||
case C.NEXT_TRACK:
|
case C.NEXT_TRACK:
|
||||||
mpMediaEventRecipient.OnCommandNextTrack()
|
mpMediaEventRecipient.OnCommandNextTrack()
|
||||||
|
case C.SEEK:
|
||||||
|
mpMediaEventRecipient.OnCommandSeek(float64(value))
|
||||||
default:
|
default:
|
||||||
log.Printf("unknown OS command received: %v", command)
|
log.Printf("unknown OS command received: %v", command)
|
||||||
}
|
}
|
||||||
@@ -85,7 +88,9 @@ func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager)
|
|||||||
cArtURL := C.CString(artURL)
|
cArtURL := C.CString(artURL)
|
||||||
defer C.free(unsafe.Pointer(cArtURL))
|
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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -93,12 +98,18 @@ func NewMPMediaHandler(player *player.Player, playbackManager *PlaybackManager)
|
|||||||
C.set_os_playback_state_stopped()
|
C.set_os_playback_state_stopped()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mp.player.OnSeek(func() {
|
||||||
|
C.update_os_now_playing_info_position(C.double(mp.player.GetStatus().TimePos))
|
||||||
|
})
|
||||||
|
|
||||||
mp.player.OnPlaying(func() {
|
mp.player.OnPlaying(func() {
|
||||||
C.set_os_playback_state_playing()
|
C.set_os_playback_state_playing()
|
||||||
|
C.update_os_now_playing_info_position(C.double(mp.player.GetStatus().TimePos))
|
||||||
})
|
})
|
||||||
|
|
||||||
mp.player.OnPaused(func() {
|
mp.player.OnPaused(func() {
|
||||||
C.set_os_playback_state_paused()
|
C.set_os_playback_state_paused()
|
||||||
|
C.update_os_now_playing_info_position(C.double(mp.player.GetStatus().TimePos))
|
||||||
})
|
})
|
||||||
|
|
||||||
return mp
|
return mp
|
||||||
@@ -159,3 +170,11 @@ func (mp *MPMediaHandler) OnCommandPreviousTrack() {
|
|||||||
}
|
}
|
||||||
mp.player.SeekBackOrPrevious()
|
mp.player.SeekBackOrPrevious()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MPMediaHandler instance received OS command to 'seek'
|
||||||
|
func (mp *MPMediaHandler) OnCommandSeek(positionSeconds float64) {
|
||||||
|
if mp == nil || mp.player == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mp.player.Seek(fmt.Sprintf("%0.2f", positionSeconds), player.SeekAbsolute)
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ typedef enum {
|
|||||||
STOP,
|
STOP,
|
||||||
TOGGLE,
|
TOGGLE,
|
||||||
NEXT_TRACK,
|
NEXT_TRACK,
|
||||||
PREVIOUS_TRACK
|
PREVIOUS_TRACK,
|
||||||
|
SEEK
|
||||||
} Command;
|
} 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.
|
* 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, double value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the "Now Playing" information on macOS for media playback
|
* Updates the "Now Playing" information on macOS for media playback
|
||||||
* using the MPNowPlayingInfoCenter API to set the metadata
|
* using the MPNowPlayingInfoCenter API to set the metadata
|
||||||
* for the currently playing media in the system's "Now Playing" interface.
|
* 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.
|
* Setter functions for updating the global playback state.
|
||||||
|
|||||||
+27
-8
@@ -8,32 +8,38 @@
|
|||||||
void register_os_remote_commands() {
|
void register_os_remote_commands() {
|
||||||
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
|
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
|
||||||
[commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
[commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||||
os_remote_command_callback(PLAY);
|
os_remote_command_callback(PLAY, 0);
|
||||||
return MPRemoteCommandHandlerStatusSuccess;
|
return MPRemoteCommandHandlerStatusSuccess;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
[commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||||
os_remote_command_callback(PAUSE);
|
os_remote_command_callback(PAUSE, 0);
|
||||||
return MPRemoteCommandHandlerStatusSuccess;
|
return MPRemoteCommandHandlerStatusSuccess;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||||
os_remote_command_callback(TOGGLE);
|
os_remote_command_callback(TOGGLE, 0);
|
||||||
return MPRemoteCommandHandlerStatusSuccess;
|
return MPRemoteCommandHandlerStatusSuccess;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[commandCenter.stopCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
[commandCenter.stopCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||||
os_remote_command_callback(STOP);
|
os_remote_command_callback(STOP, 0);
|
||||||
return MPRemoteCommandHandlerStatusSuccess;
|
return MPRemoteCommandHandlerStatusSuccess;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
[commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||||
os_remote_command_callback(NEXT_TRACK);
|
os_remote_command_callback(NEXT_TRACK, 0);
|
||||||
return MPRemoteCommandHandlerStatusSuccess;
|
return MPRemoteCommandHandlerStatusSuccess;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
[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;
|
||||||
|
os_remote_command_callback(SEEK, positionChangeEvent.positionTime);
|
||||||
return MPRemoteCommandHandlerStatusSuccess;
|
return MPRemoteCommandHandlerStatusSuccess;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -41,7 +47,7 @@ void register_os_remote_commands() {
|
|||||||
/**
|
/**
|
||||||
* C bridge setting "Now Playing" information on macOS for media playback using the native APIs.
|
* 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];
|
NSString *coverArtLocationString = [NSString stringWithUTF8String:coverArtFileURL];
|
||||||
NSURL *coverArtURL = [NSURL URLWithString:coverArtLocationString];
|
NSURL *coverArtURL = [NSURL URLWithString:coverArtLocationString];
|
||||||
NSImage *coverArtImage = [[NSImage alloc] initWithContentsOfURL:coverArtURL];
|
NSImage *coverArtImage = [[NSImage alloc] initWithContentsOfURL:coverArtURL];
|
||||||
@@ -54,12 +60,25 @@ void set_os_now_playing_info(const char *title, const char *artist, const char *
|
|||||||
NSDictionary *nowPlayingInfo = @{
|
NSDictionary *nowPlayingInfo = @{
|
||||||
MPMediaItemPropertyTitle: [NSString stringWithUTF8String:title],
|
MPMediaItemPropertyTitle: [NSString stringWithUTF8String:title],
|
||||||
MPMediaItemPropertyArtist: [NSString stringWithUTF8String:artist],
|
MPMediaItemPropertyArtist: [NSString stringWithUTF8String:artist],
|
||||||
MPMediaItemPropertyArtwork: coverArt
|
MPMediaItemPropertyArtwork: coverArt,
|
||||||
|
MPNowPlayingInfoPropertyElapsedPlaybackTime: @(0),
|
||||||
|
MPMediaItemPropertyPlaybackDuration: @(trackDuration) // Expects 'NSNumber'
|
||||||
};
|
};
|
||||||
|
|
||||||
infoCenter.nowPlayingInfo = nowPlayingInfo;
|
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'.
|
* C bridge setting the OS playback state to 'playing'.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user