MRMediaRemoteGetNowPlayingInfo return nil in latest MacOS
System: MacOS 15.3 and 15.4 beta Chip: M1 pro and M4
user program seams no long have permision for MRMediaRemoteGetNowPlayingInfofunction in latest MacOS. MRMediaRemoteCommand still work, which means still can pause and play music through MRMediaRemote. https://github.com/aviwad/LyricFever/blob/fa3b8b80bf32588207fb632200632c41334bdf64/SpotifyLyricsInMenubar/viewModel.swift#L153-L155 https://github.com/aviwad/LyricFever/blob/fa3b8b80bf32588207fb632200632c41334bdf64/SpotifyLyricsInMenubar/viewModel.swift#L873-L875
debug: Response: playbackQueue<FDA074F5-0F25-4904-A11F-23FB1246F698 Lyric Fever-41656 /M/L/AF/A600.000000x600.000000/R[0:1]> returned with error <Error Domain=kMRMediaRemoteFrameworkErrorDomain Code=3 "Operation not permitted" UserInfo={NSLocalizedDescription=Operation not permitted}> in 0.0115 seconds
Why Apple Why 😭
Maybe removing sandboxing will fix it?
Ok: MRMediaRemoteGetNowPlayingInfo still works on macOS 15.3.2.
Thanks. But this still doesn't help me. I made a tool to get the currently playing music on NetEase Cloud Music. Since it is broken in the latest system, I am looking for a solution. Due to Apple's tightening of permissions, I still haven't solved it.
After a series of research, I found that the 15.4 mediaremoted daemon began checking the entitlements of the connection client. If I'm not mistaken, access to now playing information should only be possible after having these two entitlements (com.apple.mediaremote.now-playing-read-access, com.apple.mediaremote.full-now-playing-read-access). These entitlements were introduced in previous versions, but according to the logs, previous versions did not seem to perform the check. Later versions may need to disable SIP and perform code injection on mediaremoted to bypass the check in order to obtain now playing information.
Therefore, in subsequent versions of macOS, the music currently being played by Apple Music can still be obtained by monitoring com.apple.music, but other software cannot use the GetNowPlayingInfo to obtain it. (
Therefore, in subsequent versions of macOS, the music currently being played by Apple Music can still be obtained by monitoring com.apple.music, but other software cannot use the GetNowPlayingInfo to obtain it. (
Apple Music and Spotify can still use distributed notifications to listen without being affected. I am currently trying to figure out exactly what changed in version 15.4 and see if there are other ways to bypass it. I have checked most of the key functions in versions 15.3.2 and 15.4, and currently found a few suspicious places.
😢 An unfortunate piece of news, I found the key function, and it seems that Apple has blocked all possible paths. Previously, the implementation only required passing MROrigin as nil (MRMediaRemoteGetNowPlayingInfo defaults to passing nil), which would lead to another branch that allowed access. However, the latest system has more robust checks. If MROrigin is passed as localOrigin, it will enter the _shouldDenyAccessToUser method, where the conditions can only be met by the kernel_task process (i.e., both uid and pid are 0, or pid is 0). If not met, it directly returns NO and exits. If it's not localOrigin, it will check entitlements, which will definitely fail.
15.3.2
bool __cdecl -[MRDMediaRemoteClient isAllowedAccessToDataFromPlayerPath:](MRDMediaRemoteClient *self, SEL a2, id a3)
{
id v4; // x19
bool v5; // w20
v4 = objc_retain(a3);
if ( -[MRDMediaRemoteClient isEntitledFor:](self, "isEntitledFor:", 1024) )
v5 = 1;
else
v5 = -[MRDMediaRemoteClient _isAllowedAccessToDataFromPlayerPath:](
self,
"_isAllowedAccessToDataFromPlayerPath:",
v4);
objc_release(v4);
return v5;
}
bool __cdecl -[MRDMediaRemoteClient _isAllowedAccessToDataFromPlayerPath:](MRDMediaRemoteClient *self, SEL a2, id a3)
{
id v4; // x21
void *v5; // x19
void *v6; // x22
unsigned int v7; // w21
bool v8; // w20
v4 = objc_retain(a3);
v5 = objc_retainAutoreleasedReturnValue(objc_msgSend(v4, "client"));
v6 = objc_retainAutoreleasedReturnValue(objc_msgSend(v4, "origin"));
objc_release(v4);
LODWORD(v4) = (unsigned int)objc_msgSend(v6, "isLocal");
objc_release(v6);
if ( (_DWORD)v4
&& (unsigned int)objc_msgSend(v5, "processUserIdentifier")
&& -[MRDMediaRemoteClient euid](self, "euid") )
{
v7 = (unsigned int)objc_msgSend(v5, "processUserIdentifier");
v8 = v7 == -[MRDMediaRemoteClient euid](self, "euid");
}
else
{
v8 = 1;
}
objc_release(v5);
return v8;
}
15.4
bool __cdecl -[MRDMediaRemoteClient isAllowedAccessToDataFromPlayerPath:](MRDMediaRemoteClient *self, SEL a2, id a3)
{
id v4; // x19
bool v5; // w20
void *v6; // x21
void *v7; // x22
unsigned __int8 v8; // w23
void *v9; // x21
void *v10; // x22
unsigned int v11; // w23
unsigned int v12; // w24
v4 = objc_retain(a3);
if ( -[MRDMediaRemoteClient isEntitledFor:](self, "isEntitledFor:", 1024) )
goto LABEL_2;
v6 = objc_retainAutoreleasedReturnValue(objc_msgSend(v4, "origin"));
if ( ((unsigned int)objc_msgSend(v6, "isLocal") & 1) != 0 )
{
v7 = objc_retainAutoreleasedReturnValue(objc_msgSend(v4, "client"));
v8 = -[MRDMediaRemoteClient _shouldDenyAccessToUser:](
self,
"_shouldDenyAccessToUser:",
objc_msgSend(v7, "processUserIdentifier"));
objc_release(v7);
objc_release(v6);
if ( (v8 & 1) != 0 )
{
v5 = 0;
goto LABEL_12;
}
}
else
{
objc_release(v6);
}
v9 = objc_retainAutoreleasedReturnValue(objc_msgSend(v4, "origin"));
if ( ((unsigned int)objc_msgSend(v9, "isLocal") & 1) == 0 )
{
objc_release(v9);
LABEL_11:
v5 = -[MRDMediaRemoteClient isEntitledFor:](self, "isEntitledFor:", 512);
goto LABEL_12;
}
v10 = objc_retainAutoreleasedReturnValue(objc_msgSend(v4, "client"));
v11 = (unsigned int)objc_msgSend(v10, "processIdentifier");
v12 = -[MRDMediaRemoteClient pid](self, "pid");
objc_release(v10);
objc_release(v9);
if ( v11 != v12 )
goto LABEL_11;
LABEL_2:
v5 = 1;
LABEL_12:
objc_release(v4);
return v5;
}
bool __cdecl -[MRDMediaRemoteClient _shouldDenyAccessToUser:](MRDMediaRemoteClient *self, SEL a2, unsigned int a3)
{
unsigned int v5; // w0
if ( a3 )
{
v5 = -[MRDMediaRemoteClient euid](self, "euid");
if ( v5 )
LOBYTE(v5) = -[MRDMediaRemoteClient euid](self, "euid") != a3;
}
else
{
LOBYTE(v5) = 0;
}
return v5;
}
@Mx-Iris Thank you for the research. This is super disappointing. So now there is no way for @spbgzh to get the currently playing song on NetEase, and there's no way for us to get the Apple Music store id for the currently playing song on apple music.
Thanks. But this still doesn't help me. I made a tool to get the currently playing music on NetEase Cloud Music. Since it is broken in the latest system, I am looking for a solution. Due to Apple's tightening of permissions, I still haven't solved it.
I don't know whether it is proper to reply here but, for NetEase Cloud Music on macOS, all music play history will be updated in /Users/{username}/Library/Containers/com.netease.163music/Data/Documents/storage/sqlite_storage.sqlite3, in this database there is a table historyTracks, which will update played music info in real time.
So we may get the currently playing music info of NetEase Cloud Music by polling a query
SELECT * FROM "historyTracks" ORDER BY "playTime" DESC LIMIT 1;
Where jsonStr will provide a detailed info about the playing track in JSON format and playTime tells us when did the user begin to play the track in UNIX timestamp, accurate to milliseconds.
This method is very inefficient and is not possible to get the playing progress or paused or not - but it is still valuable for getting the info of the music playing now.
Hope it helps.
@aviwad @spbgzh I have bypassed it through code injection and need to disable SIP
@CarterLi committed an osascript to work around some of these issues without removing SIP. https://github.com/apocelipes/fastfetch/commit/1557f0c5564a8288604824e55db47508f65e82c9
Some more testing will need to be done to determine if it works for this use case.
I've found a solution to load the MediaRemote framework on macOS 15.4 and above without disabling SIP, without any external dependencies and without forfeiting any MediaRemote functionality: https://github.com/ungive/mediaremote-adapter
This works by creating a custom framework bundle (written in Objective-C) and loading it with a platform binary, e.g. /usr/bin/perl. That will give you full MediaRemote access because platform binaries are privileged to use private frameworks (see earlier discussion in this issue). MRNowPlayingRequest does not have the ability to load artwork data (I couldn't get it to work) and does not provide real-time updates to now playing information, requiring you to poll for changes repeatedly.
My implementation that I linked above also just creates a single process that efficiently streams diff'd updates to stdout, encoded as JSON and base64 which you can decode using native Objective-C methods (see the project readme). From my testing this should also be backwards-compatible with many (if not all) older versions of macOS (tested on macOS 14).
I hope this helps!
On a side note, consider giving the repository a ⭐, for the case when Apple finds it and possibly reads the note at the top of the README. They might break this again in the future and the more people show they want a public API to control media, the better.
https://github.com/user-attachments/assets/8b919d39-7ed9-431a-baea-9d91bbec284e
Fully working MediaRemote commands on macOS 26 Tahoe.
So I may be the reason for this. See CVE-2024-23297