add old mediaremote way of getting playback data, as osascript way only works on sonoma and newer.

This commit is contained in:
EinTim23 2025-05-03 22:59:52 +02:00
parent 349058c32d
commit bccf4c6100
3 changed files with 114 additions and 25 deletions

View File

@ -30,6 +30,14 @@ if(WIN32)
list(APPEND LIBRARIES WindowsApp) list(APPEND LIBRARIES WindowsApp)
target_link_options(PlayerLink PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") target_link_options(PlayerLink PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup")
elseif(APPLE) elseif(APPLE)
set(MEDIAREMOTE_FRAMEWORK_PATH "/System/Library/PrivateFrameworks")
find_library(MEDIAREMOTE_LIBRARY MediaRemote PATHS ${MEDIAREMOTE_FRAMEWORK_PATH})
if (MEDIAREMOTE_LIBRARY)
message(STATUS "Found MediaRemote: ${MEDIAREMOTE_LIBRARY}")
list(APPEND LIBRARIES ${MEDIAREMOTE_LIBRARY})
else()
message(FATAL_ERROR "MediaRemote framework not found.")
endif()
set_target_properties(PlayerLink PROPERTIES MACOSX_BUNDLE TRUE) set_target_properties(PlayerLink PROPERTIES MACOSX_BUNDLE TRUE)
set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/MediaRemote.js PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/MediaRemote.js PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")

17
src/MediaRemote.hpp Normal file
View File

@ -0,0 +1,17 @@
#ifdef __APPLE__
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoTitle;
FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoAlbum;
FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoArtist;
FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoDuration;
FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoElapsedTime;
FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoArtworkData;
FOUNDATION_EXPORT CFStringRef _Nullable kMRMediaRemoteNowPlayingInfoPlaybackRate;
typedef void (^ MRMediaRemoteGetNowPlayingInfoCompletion)(CFDictionaryRef _Nullable information);
typedef void (^ MRMediaRemoteGetNowPlayingApplicationPIDCompletion)(int PID);
FOUNDATION_EXPORT void MRMediaRemoteGetNowPlayingApplicationPID(dispatch_queue_t _Nullable queue, MRMediaRemoteGetNowPlayingApplicationPIDCompletion _Nullable completion);
FOUNDATION_EXPORT void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t _Nullable queue, MRMediaRemoteGetNowPlayingInfoCompletion _Nullable completion);
#endif

View File

@ -8,6 +8,7 @@
#include <nlohmann-json/single_include/nlohmann/json.hpp> #include <nlohmann-json/single_include/nlohmann/json.hpp>
#include <fstream> #include <fstream>
#include "../MediaRemote.hpp"
#include "../backend.hpp" #include "../backend.hpp"
void hideDockIcon(bool shouldHide) { void hideDockIcon(bool shouldHide) {
@ -17,12 +18,12 @@ void hideDockIcon(bool shouldHide) {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
} }
NSString* getFilePathFromBundle(NSString* fileName, NSString* fileType) { NSString *getFilePathFromBundle(NSString *fileName, NSString *fileType) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType]; NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType];
return filePath; return filePath;
} }
NSString* executeCommand(NSString* command, NSArray* arguments) { NSString *executeCommand(NSString *command, NSArray *arguments) {
NSTask *task = [[NSTask alloc] init]; NSTask *task = [[NSTask alloc] init];
task.launchPath = command; task.launchPath = command;
task.arguments = arguments; task.arguments = arguments;
@ -39,34 +40,97 @@ NSString* executeCommand(NSString* command, NSArray* arguments) {
} }
std::shared_ptr<MediaInfo> backend::getMediaInformation() { std::shared_ptr<MediaInfo> backend::getMediaInformation() {
static NSString* script = getFilePathFromBundle(@"MediaRemote", @"js"); // apple decided to prevent apps not signed by them to use media remote, so we use an apple script instead. But that script only works on Sonoma or newer and the other one is arguably better, so keep the old method as well
NSString* output = executeCommand(@"/usr/bin/osascript", @[@"-l", @"JavaScript", script]); if (@available(macOS 15.0, *)) {
nlohmann::json j = nlohmann::json::parse([output UTF8String]); static NSString *script = getFilePathFromBundle(@"MediaRemote", @"js");
NSString *output = executeCommand(@"/usr/bin/osascript", @[ @"-l", @"JavaScript", script ]);
nlohmann::json j = nlohmann::json::parse([output UTF8String]);
std::string appName = j["player"].get<std::string>(); std::string appName = j["player"].get<std::string>();
if (appName == "none") if (appName == "none")
return nullptr; return nullptr;
bool paused = j["playbackStatus"].get<int>() == 0; bool paused = j["playbackStatus"].get<int>() == 0;
std::string songTitle = j["title"].get<std::string>(); std::string songTitle = j["title"].get<std::string>();
std::string songAlbum = j["album"].get<std::string>(); std::string songAlbum = j["album"].get<std::string>();
std::string songArtist = j["artist"].get<std::string>(); std::string songArtist = j["artist"].get<std::string>();
int64_t elapsedTimeMs = 0; int64_t elapsedTimeMs = 0;
int64_t durationMs = 0; int64_t durationMs = 0;
try { try {
double durationNumber = j["duration"].get<double>(); double durationNumber = j["duration"].get<double>();
durationMs = static_cast<int64_t>(durationNumber * 1000); durationMs = static_cast<int64_t>(durationNumber * 1000);
double elapsedTimeNumber = j["elapsed"].get<double>(); double elapsedTimeNumber = j["elapsed"].get<double>();
elapsedTimeMs = static_cast<int64_t>(elapsedTimeNumber * 1000); elapsedTimeMs = static_cast<int64_t>(elapsedTimeNumber * 1000);
} catch (...) {} } catch (...) {
}
return std::make_shared<MediaInfo>(paused, songTitle, songArtist, songAlbum, appName, "", return std::make_shared<MediaInfo>(paused, songTitle, songArtist, songAlbum, appName, "", durationMs,
durationMs, elapsedTimeMs); elapsedTimeMs);
} else {
__block NSString *appName = nil;
__block NSDictionary *playingInfo = nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
MRMediaRemoteGetNowPlayingApplicationPID(dispatch_get_main_queue(), ^(pid_t pid) {
if (pid > 0) {
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
if (app)
appName = [[app.bundleIdentifier copy] retain];
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(CFDictionaryRef result) {
if (result)
playingInfo = [[(__bridge NSDictionary *)result copy] retain];
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
if (appName == nil || playingInfo == nil)
return nullptr;
bool paused = [playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoPlaybackRate] intValue] == 0;
NSString *title = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoTitle];
std::string songTitle = title ? [title UTF8String] : "";
NSString *album = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoAlbum];
std::string songAlbum = album ? [album UTF8String] : "";
NSString *artist = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtist];
std::string songArtist = artist ? [artist UTF8String] : "";
NSData *artworkData = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtworkData];
std::string thumbnailData;
if (artworkData)
thumbnailData = std::string((const char *)[artworkData bytes], [artworkData length]);
NSNumber *elapsedTimeNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoElapsedTime];
NSNumber *durationNumber = playingInfo[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoDuration];
int64_t elapsedTimeMs = elapsedTimeNumber ? static_cast<int64_t>([elapsedTimeNumber doubleValue] * 1000) : 0;
int64_t durationMs = durationNumber ? static_cast<int64_t>([durationNumber doubleValue] * 1000) : 0;
std::string appNameString = appName.UTF8String;
[appName release];
[playingInfo release];
return std::make_shared<MediaInfo>(paused, songTitle, songArtist, songAlbum, appNameString, thumbnailData,
durationMs, elapsedTimeMs);
}
} }
std::filesystem::path backend::getConfigDirectory() { std::filesystem::path backend::getConfigDirectory() {
@ -87,7 +151,7 @@ bool backend::toggleAutostart(bool enabled) {
} }
NSString *binaryPath = [[[NSProcessInfo processInfo] arguments][0] stringByStandardizingPath]; NSString *binaryPath = [[[NSProcessInfo processInfo] arguments][0] stringByStandardizingPath];
//I would also like to use std::format here, but well I also want to support older mac os versions. // I would also like to use std::format here, but well I also want to support older mac os versions.
std::string formattedPlist = std::string formattedPlist =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n <dict>\n\n " "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n <dict>\n\n "