Initial support for Bluetooth and Touch Bar controls on macOS

This uses a private API to keep the Touch Bar controls active in the foreground
Both iTunes and Safari do the same; Safari source here:
https://github.com/WebKit/webkit/blob/bde8ac5bddc49527396f3adb576d9a101a8e4828/Source/WebCore/platform/audio/mac/MediaSessionManagerMac.mm#L202
This commit is contained in:
Rodger Combs authored and LongChair committed 2018-02-07 20:37:52 +01:00
1 parent 13dc9c10de
commit c02b6869b2
7 files changed
+172 -8

No files matched your search

+2 -1
View File
@@ -4,8 +4,9 @@ find_library(IOKIT IOKit)
find_library(COCOA Cocoa)
find_Library(CARBON Carbon)
find_library(SECURITY Security)
find_library(MEDIAPLAYER MediaPlayer)
set(OS_LIBS ${FOUNDATION} ${APPKIT} ${IOKIT} ${COCOA} ${SECURITY} ${CARBON} spmediakeytap hidremote plistparser letsmove)
set(OS_LIBS ${FOUNDATION} ${APPKIT} ${IOKIT} ${COCOA} ${SECURITY} ${CARBON} ${MEDIAPLAYER} spmediakeytap hidremote plistparser letsmove)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.9 -fno-omit-frame-pointer")
set(WARNINGS "-Wall")
+3 -1
View File
@@ -3,7 +3,9 @@
"idmatcher": "AppleMediaKeys.*",
"mapping":
{
"KEY_PLAY": "play_pause",
"KEY_PLAY": "play",
"KEY_PAUSE": "pause",
"KEY_PLAY_PAUSE": "play_pause",
"KEY_NEXT": "step_forward",
"KEY_PREV": "step_backward",
"KEY_FAST": "step_forward",
+1
View File
@@ -43,6 +43,7 @@ signals:
#define INPUT_KEY_MENU "KEY_MENU"
#define INPUT_KEY_PLAY "KEY_PLAY"
#define INPUT_KEY_PAUSE "KEY_PAUSE"
#define INPUT_KEY_PLAY_PAUSE "KEY_PLAY_PAUSE"
#define INPUT_KEY_STOP "KEY_STOP"
#define INPUT_KEY_DOWN "KEY_DOWN"
#define INPUT_KEY_BACK "KEY_BACK"
+14
View File
@@ -6,6 +6,7 @@
#define KONVERGO_INPUTAPPLEMEDIAKEYS_H
#include "input/InputComponent.h"
#include "player/PlayerComponent.h"
class InputAppleMediaKeys : public InputBase
{
@@ -17,6 +18,19 @@ public:
private:
void* m_delegate;
void handleStateChanged(PlayerComponent::State newState, PlayerComponent::State oldState);
void handlePositionUpdate(quint64 position);
void handleUpdateDuration(qint64 duration);
typedef void (*SetNowPlayingVisibilityFunc)(void* origin, int visibility);
typedef void* (*GetLocalOriginFunc)(void);
typedef void (*SetCanBeNowPlayingApplicationFunc)(int);
SetNowPlayingVisibilityFunc SetNowPlayingVisibility;
GetLocalOriginFunc GetLocalOrigin;
SetCanBeNowPlayingApplicationFunc SetCanBeNowPlayingApplication;
bool m_pendingUpdate;
quint64 m_currentTime;
};
#endif //KONVERGO_INPUTAPPLEMEDIAKEYS_H
+147 -6
View File
@@ -6,6 +6,10 @@
#include "SPMediaKeyTap.h"
#include "QsLog.h"
#import <dlfcn.h>
#import <MediaPlayer/MediaPlayer.h>
@interface MediaKeysDelegate : NSObject
{
SPMediaKeyTap* keyTap;
@@ -21,11 +25,28 @@
self = [super init];
if (self) {
input = input_;
keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self];
if ([SPMediaKeyTap usesGlobalMediaKeyTap])
[keyTap startWatchingMediaKeys];
else
QLOG_WARN() << "Could not grab global media keys";
if (NSClassFromString(@"MPRemoteCommandCenter")) {
MPRemoteCommandCenter* center = [MPRemoteCommandCenter sharedCommandCenter];
#define CONFIG_CMD(name) \
[center.name ## Command addTarget:self action:@selector(gotCommand:)]
CONFIG_CMD(play);
CONFIG_CMD(pause);
CONFIG_CMD(togglePlayPause);
CONFIG_CMD(stop);
CONFIG_CMD(nextTrack);
CONFIG_CMD(previousTrack);
CONFIG_CMD(seekForward);
CONFIG_CMD(seekBackward);
CONFIG_CMD(skipForward);
CONFIG_CMD(skipBackward);
[center.changePlaybackPositionCommand addTarget:self action:@selector(gotPlaybackPosition:)];
} else {
keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self];
if ([SPMediaKeyTap usesGlobalMediaKeyTap])
[keyTap startWatchingMediaKeys];
else
QLOG_WARN() << "Could not grab global media keys";
}
}
return self;
}
@@ -35,6 +56,38 @@
[super dealloc];
}
-(MPRemoteCommandHandlerStatus)gotCommand:(MPRemoteCommandEvent *)event
{
QString keyPressed;
MPRemoteCommand* command = [event command];
#define CMD(name) [MPRemoteCommandCenter sharedCommandCenter].name ## Command
if (command == CMD(play)) {
keyPressed = INPUT_KEY_PLAY;
} else if (command == CMD(pause)) {
keyPressed = INPUT_KEY_PAUSE;
} else if (command == CMD(togglePlayPause)) {
keyPressed = INPUT_KEY_PLAY_PAUSE;
} else if (command == CMD(stop)) {
keyPressed = INPUT_KEY_STOP;
} else if (command == CMD(nextTrack)) {
keyPressed = INPUT_KEY_NEXT;
} else if (command == CMD(previousTrack)) {
keyPressed = INPUT_KEY_PREV;
} else {
return MPRemoteCommandHandlerStatusCommandFailed;
}
emit input->receivedInput("AppleMediaKeys", keyPressed, InputBase::KeyPressed);
return MPRemoteCommandHandlerStatusSuccess;
}
-(MPRemoteCommandHandlerStatus)gotPlaybackPosition:(MPChangePlaybackPositionCommandEvent *)event
{
PlayerComponent::Get().seekTo(event.positionTime * 1000);
return MPRemoteCommandHandlerStatusSuccess;
}
-(void)mediaKeyTap:(SPMediaKeyTap *)keyTap receivedMediaKeyEvent:(NSEvent *)event
{
int keyCode = (([event data1] & 0xFFFF0000) >> 16);
@@ -45,7 +98,7 @@
switch (keyCode) {
case NX_KEYTYPE_PLAY:
keyPressed = INPUT_KEY_PLAY;
keyPressed = INPUT_KEY_PLAY_PAUSE;
break;
case NX_KEYTYPE_FAST:
keyPressed = "KEY_FAST";
@@ -69,9 +122,97 @@
@end
// macOS private enum
enum {
MRNowPlayingClientVisibilityUndefined = 0,
MRNowPlayingClientVisibilityAlwaysVisible,
MRNowPlayingClientVisibilityVisibleWhenBackgrounded,
MRNowPlayingClientVisibilityNeverVisible
};
///////////////////////////////////////////////////////////////////////////////////////////////////
bool InputAppleMediaKeys::initInput()
{
m_currentTime = 0;
m_pendingUpdate = false;
m_delegate = [[MediaKeysDelegate alloc] initWithInput:this];
if (NSClassFromString(@"MPNowPlayingInfoCenter")) {
connect(&PlayerComponent::Get(), &PlayerComponent::stateChanged, this, &InputAppleMediaKeys::handleStateChanged);
connect(&PlayerComponent::Get(), &PlayerComponent::positionUpdate, this, &InputAppleMediaKeys::handlePositionUpdate);
connect(&PlayerComponent::Get(), &PlayerComponent::updateDuration, this, &InputAppleMediaKeys::handleUpdateDuration);
void* lib = dlopen("/System/Library/PrivateFrameworks/MediaRemote.framework/MediaRemote", RTLD_NOW);
if (lib) {
#define LOAD_FUNC(name) \
name = (name ## Func)dlsym(lib, "MRMediaRemote" #name)
LOAD_FUNC(SetNowPlayingVisibility);
LOAD_FUNC(GetLocalOrigin);
LOAD_FUNC(SetCanBeNowPlayingApplication);
if (SetCanBeNowPlayingApplication)
SetCanBeNowPlayingApplication(1);
}
}
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
static MPNowPlayingPlaybackState convertState(PlayerComponent::State newState)
{
switch (newState) {
case PlayerComponent::State::finished:
return MPNowPlayingPlaybackStateStopped;
case PlayerComponent::State::canceled:
case PlayerComponent::State::error:
return MPNowPlayingPlaybackStateInterrupted;
case PlayerComponent::State::buffering:
case PlayerComponent::State::paused:
return MPNowPlayingPlaybackStatePaused;
case PlayerComponent::State::playing:
return MPNowPlayingPlaybackStatePlaying;
default:
return MPNowPlayingPlaybackStateUnknown;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void InputAppleMediaKeys::handleStateChanged(PlayerComponent::State newState, PlayerComponent::State oldState)
{
MPNowPlayingPlaybackState newMPState = convertState(newState);
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
NSMutableDictionary *playingInfo = [NSMutableDictionary dictionaryWithDictionary:center.nowPlayingInfo];
[playingInfo setObject:[NSNumber numberWithDouble:(double)m_currentTime / 1000] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
center.nowPlayingInfo = playingInfo;
[MPNowPlayingInfoCenter defaultCenter].playbackState = newMPState;
if (SetNowPlayingVisibility && GetLocalOrigin) {
if (newState == PlayerComponent::State::finished || newState == PlayerComponent::State::canceled || newState == PlayerComponent::State::error)
SetNowPlayingVisibility(GetLocalOrigin(), MRNowPlayingClientVisibilityNeverVisible);
else if (newState == PlayerComponent::State::paused || newState == PlayerComponent::State::playing || newState == PlayerComponent::State::buffering)
SetNowPlayingVisibility(GetLocalOrigin(), MRNowPlayingClientVisibilityAlwaysVisible);
}
m_pendingUpdate = true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void InputAppleMediaKeys::handlePositionUpdate(quint64 position)
{
m_currentTime = position;
if (m_pendingUpdate) {
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
NSMutableDictionary *playingInfo = [NSMutableDictionary dictionaryWithDictionary:center.nowPlayingInfo];
[playingInfo setObject:[NSNumber numberWithDouble:(double)position / 1000] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
center.nowPlayingInfo = playingInfo;
[MPNowPlayingInfoCenter defaultCenter].playbackState = [MPNowPlayingInfoCenter defaultCenter].playbackState;
m_pendingUpdate = false;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
void InputAppleMediaKeys::handleUpdateDuration(qint64 duration)
{
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
NSMutableDictionary *playingInfo = [NSMutableDictionary dictionaryWithDictionary:center.nowPlayingInfo];
[playingInfo setObject:[NSNumber numberWithDouble:(double)duration / 1000] forKey:MPMediaItemPropertyPlaybackDuration];
center.nowPlayingInfo = playingInfo;
m_pendingUpdate = true;
}
+4
View File
@@ -463,6 +463,7 @@ void PlayerComponent::updatePlaybackState()
emit error(m_playbackError);
break;
}
emit stateChanged(newState, m_state);
m_state = newState;
}
@@ -1139,6 +1140,9 @@ void PlayerComponent::updateVideoAspectSettings()
///////////////////////////////////////////////////////////////////////////////////////////////////
void PlayerComponent::updateVideoSettings()
{
if (!m_mpv)
return;
QVariant syncMode = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "sync_mode");
mpv::qt::set_property(m_mpv, "video-sync", syncMode);
+1
View File
@@ -164,6 +164,7 @@ Q_SIGNALS:
void error(const QString& msg); // playback stopped due to external error
// To be phased out. Raised on finished() and canceled().
void stopped(); // playback finished successfully, or was stopped with stop()
void stateChanged(State newState, State oldState); // all state changes
// true if the video (or music) is actually playing
// false if nothing is loaded, playback is paused, during seeking, or media is being loaded