-
Code Injection (Requires SIP to be disabled)
Use MediaRemoteWizard to inject code into mediaremoted, overriding core methods to return YES, thereby allowing any client to connect.
-
AppleScript / JavaScript for Automation (Does not require SIP to be disabled)
See macOS 15.4+ for more information.
This library provides bindings for Apple's private framework, MediaRemote. It is primarily designed to access information about media that is currently playing. Therefore, not all methods from the MediaRemote framework are included in these bindings.
This library should be safe to use. However, it is the first attempt at building these bindings, so there is a high chance of unexpected errors. If you encounter any issues, please report them in the issue tracker or submit a pull request to help improve the library.
Warning: Since MediaRemote is a private Apple framework, using it may introduce compatibility or stability issues, and your app may not be approved for distribution on the App Store. Use this library at your own risk.
To get started, first ensure that the library is installed.
use media_remote::prelude::*;
fn main() {
// Create an instance of NowPlaying to interact with the media remote.
let now_playing = NowPlaying::new();
// Use a guard lock to safely access media information within this block.
// The guard should be released as soon as possible to avoid blocking.
{
let guard = now_playing.get_info();
let info = guard.as_ref();
// If information is available, print the title of the currently playing media.
if let Some(info) = info {
println!("Currently playing: {:?}", info.title);
}
}
// Toggle the play/pause state of the media.
now_playing.toggle();
}
This is a brief documentation. More detailed documentation, including examples, is written inside the code documentation. Hover over the function to check the documentation.
High Level API
Creates a new instance of NowPlaying
and registers for playback notifications.
-
Returns:
NowPlaying
: A new instance of theNowPlaying
struct.
Retrieves the latest now playing information.
-
Returns:
RwLockReadGuard<'_, Option<NowPlayingInfo>>
: A guard to the now playing metadata.
-
Note:
- The lock should be released as soon as possible to minimize blocking time.
NowPlaying::subscribe<F: Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync + 'static>(&self, listener: F) -> ListenerToken
Subscribes a listener to receive updates when the "Now Playing" information changes.
-
Arguments:
listener
: A function or closure that accepts aRwLockReadGuard<'_, Option<NowPlayingInfo>>
.
-
Returns:
ListenerToken
: A token representing the listener, which can later be used to unsubscribe.
Unsubscribes a previously registered listener using the provided ListenerToken
.
-
Arguments:
token
: TheListenerToken
returned when the listener was subscribed.
pub struct NowPlayingInfo {
pub is_playing: Option<bool>,
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub album_cover: Option<DynamicImage>,
pub elapsed_time: Option<f64>,
pub duration: Option<f64>,
pub info_update_time: Option<SystemTime>,
pub bundle_id: Option<String>,
pub bundle_name: Option<String>,
pub bundle_icon: Option<DynamicImage>,
}
These functions allow you to control the currently playing media. To use these functions, import media_remote::Controller
.
-
NowPlaying::toggle(&self) -> bool
Toggles between play and pause states.
-
NowPlaying::play(&self) -> bool
Starts playing the media.
-
NowPlaying::pause(&self) -> bool
Pauses the media.
-
NowPlaying::next(&self) -> bool
Skips to the next track.
-
NowPlaying::previous(&self) -> bool
Goes back to the previous track.
Low Level API
Checks whether the currently playing media application is actively playing.
-
Returns:
Some(true)
: If a media application is playing.Some(false)
: If no media is currently playing.None
: If the function times out (e.g., due to an API failure or missing response).
Retrieves the current "now playing" client ID (which is a reference).
-
Returns:
Some(Id)
: If a valid client ID is found.None
: If no client ID is found or the request times out.
-
Note:
- This function should not be used as the returned ID is short-lived and may cause undefined behavior when used outside of the block.
Retrieves the current "now playing" application PID.
-
Returns:
Some(PID)
: If a valid application PID is found.None
: If no application PID is found or the request times out.
Retrieves the currently playing media information as a HashMap<String, InfoTypes>
. The function interacts with Apple's CoreFoundation API to extract metadata related to the currently playing media.
-
Returns:
Some(HashMap<String, InfoTypes>)
: If metadata is successfully retrieved.None
: If no metadata is available or retrieval fails.
Retrieves the bundle identifier of the parent app for the current "now playing" client.
-
Returns:
Some(String)
: The bundle identifier of the parent app if successfully retrieved.None
: If the client ID is invalid, the bundle identifier is null, or retrieval fails.
Retrieves the bundle identifier of the current "now playing" client.
-
Returns:
Some(String)
: The bundle identifier of the client app if successfully retrieved.None
: If the client ID is invalid, the bundle identifier is null, or retrieval fails.
Sends a media command to the currently active media client.
-
Arguments:
command
: The Command to be sent, representing an action like play, pause, skip, etc.
-
Returns:
true
: If the command was successfully sent and processed.false
: If the operation failed or the command was not recognized.
-
Notes:
- The
useInfo
argument is not supported by this function and is not used in the current implementation. - If no media is currently playing, this function may open iTunes (or the default media player) to handle the command.
- The
Sets the playback speed of the currently active media client.
-
Arguments:
speed
: The playback speed multiplier.
-
Note:
- Playback speed changes typically do not work most of the time. Depending on the media client or content, setting the playback speed may not have the desired effect.
Sets the elapsed time of the currently playing media.
-
Arguments:
elapsed_time
: The elapsed time in seconds to set the current position of the media.
-
Note:
- Setting the elapsed time can often cause the media to pause. Be cautious when using this function, as the playback might be interrupted and require manual resumption.
Registers the caller for "Now Playing" notifications.
- Note:
- Must be called before adding observers to ensure notifications are received.
Unregisters the caller for "Now Playing" notifications.
-
Note:
- Should be called when notifications are no longer needed to free resources.
Helper Functions
Adds an observer for a specific media notification.
-
Arguments:
notification
: The Notification type representing the event to observe.closure
: A closure to execute when the notification is received.
-
Returns:
- An Observer handle that can be used to remove the observer later.
-
Note:
register_for_now_playing_notifications()
must be called before using this function, or notifications may not be received.
Removes a previously added observer.
-
Arguments:
observer
: The Observer handle returned from add_observer().
Retrieves information about an application based on its bundle identifier, including the application's name and icon.
-
Arguments:
id
: A string slice representing the bundle identifier of the application.
-
Returns:
Some(BundleInfo)
: If the application is found, containing the application's name and icon.None
: If the application cannot be found, or if there is an error retrieving the information.
For macOS 15.4+, use NowPlayingJAX
instead of NowPlaying
. The API is identical, except that NowPlayingJAX::new
requires an update_interval
parameter. However, due to limitations of JavaScript for Automation (JAX), it is not possible to retrieve the artwork image, and there may be a delay—up to update_interval
—before the listener is triggered on updates.
If you want to use JAX directly, use the get_raw_info
function for unprocessed data, or the formatted version, get_info
. Note that both functions may return None if an error occurs.