LyricFever icon indicating copy to clipboard operation
LyricFever copied to clipboard

MRMediaRemoteGetNowPlayingInfo return nil in latest MacOS

Open spbgzh opened this issue 9 months ago • 16 comments

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

spbgzh avatar Mar 22 '25 12:03 spbgzh

Why Apple Why 😭

aviwad avatar Mar 22 '25 18:03 aviwad

Maybe removing sandboxing will fix it?

aviwad avatar Mar 22 '25 18:03 aviwad

Ok: MRMediaRemoteGetNowPlayingInfo still works on macOS 15.3.2.

aviwad avatar Mar 22 '25 18:03 aviwad

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.

spbgzh avatar Mar 23 '25 09:03 spbgzh

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.

Image Image

Mx-Iris avatar Mar 23 '25 11:03 Mx-Iris

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. (

spbgzh avatar Mar 23 '25 12:03 spbgzh

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.

Mx-Iris avatar Mar 23 '25 15:03 Mx-Iris

😢 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 avatar Mar 23 '25 17:03 Mx-Iris

@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.

aviwad avatar Mar 23 '25 17:03 aviwad

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.

izwb003 avatar Apr 11 '25 19:04 izwb003

@aviwad @spbgzh I have bypassed it through code injection and need to disable SIP

Image

Mx-Iris avatar Apr 14 '25 12:04 Mx-Iris

@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.

ejbills avatar May 21 '25 17:05 ejbills

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!

ungive avatar Jun 15 '25 23:06 ungive

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.

ungive avatar Jun 16 '25 13:06 ungive

https://github.com/user-attachments/assets/8b919d39-7ed9-431a-baea-9d91bbec284e

Fully working MediaRemote commands on macOS 26 Tahoe.

ejbills avatar Jun 16 '25 15:06 ejbills

So I may be the reason for this. See CVE-2024-23297

scj643 avatar Aug 13 '25 00:08 scj643