mediaplayer 0.4.0

Safe Rust bindings for MediaPlayer.framework on macOS — now playing, remote commands, artwork, and explicit iOS-only stubs
Documentation

mediaplayer-rs

Safe Rust bindings for Apple's MediaPlayer.framework on macOS.

Status: v0.3.0 adds a Tier-2 async stream module (async feature) wrapping six notification / delegate / command-target surfaces as executor-agnostic BoundedAsyncStream<T> event streams. v0.2.0 covers the macOS-available now-playing, remote-command, language-option, and artwork APIs, and exposes explicit macOS-unavailable wrappers for the iOS-only MPMediaLibrary, MPMediaQuery, MPMusicPlayer, MPMediaItem, MPMediaItemCollection, MPMediaPlaylist, MPVolumeView, MPSystemMusicPlayer, and MPPlayableContentDataSource areas.

Async streams (async feature)

Enable with features = ["async"]:

mediaplayer = { version = "0.3", features = ["async"] }
Stream type Apple surface
NowPlayingItemChangeStream MPMusicPlayerControllerNowPlayingItemDidChangeNotification
PlaybackStateChangeStream MPMusicPlayerControllerPlaybackStateDidChangeNotification
VolumeChangeStream MPMusicPlayerControllerVolumeDidChangeNotification
MediaLibraryChangeStream MPMediaLibraryDidChangeNotification
RemoteCommandStream MPRemoteCommandCenter command targets (20 commands)
NowPlayingSessionStream MPNowPlayingSession delegate (stub; unavailable on macOS)
# #[cfg(feature = "async")]
# async fn run() {
use mediaplayer::async_api::RemoteCommandStream;
use mediaplayer::remote_commands::Command;

let stream = RemoteCommandStream::subscribe(Command::Play, 16);
while let Some(event) = stream.next().await {
    println!("play at t={:.3}", event.timestamp);
}
# }

Quick start

use std::time::UNIX_EPOCH;

use mediaplayer::prelude::*;

fn main() {
    let subtitles = LanguageOption::new(
        LanguageOptionType::Legible,
        Some("en"),
        &["public.legible"],
        "English Subtitles",
        "subtitles-en",
    )
    .unwrap();

    let subtitle_group = LanguageOptionGroup::new(&[subtitles.clone()], Some(0), true).unwrap();

    let center = NowPlayingInfoCenter::default_center();
    let info = NowPlayingInfo::new()
        .title("My Song")
        .artist("doom-fish")
        .album_title("Tests")
        .playback_duration(300.0)
        .elapsed_playback_time(0.0)
        .playback_rate(1.0)
        .default_playback_rate(1.0)
        .playback_queue_index(0)
        .playback_queue_count(1)
        .available_language_option_groups(vec![subtitle_group])
        .current_language_options(vec![subtitles])
        .current_playback_date(UNIX_EPOCH)
        .media_type(NowPlayingMediaType::Audio);

    center.set_now_playing_info(&info);
    center.set_playback_state(PlaybackState::Playing);

    let remote = RemoteCommandCenter::shared();
    remote.play_command().set_enabled(true);
    remote.skip_forward_command().set_preferred_intervals(&[15.0, 30.0]);
    remote.change_playback_rate_command().set_supported_playback_rates(&[1.0, 1.5, 2.0]);

    let _play = remote.on_play(|_| HandlerStatus::Success);
    let _rating = remote.on_rating(|event| {
        println!("rating event = {:?}", event.rating);
        HandlerStatus::Success
    });

    center.clear();
}

Highlights

  • NowPlayingInfoCenter — fluent NowPlayingInfo builder covering queue state, playback progress, language options, service identifiers, live-stream flags, and playback dates.
  • LanguageOption / LanguageOptionGroup — wrappers for MPNowPlayingInfoLanguageOption and MPNowPlayingInfoLanguageOptionGroup.
  • RemoteCommandCenter — zero-cost command handles for base, skip-interval, feedback, rating, playback-rate, shuffle, repeat, and language-option commands.
  • CommandToken — RAII guard that deregisters closures on drop.
  • ArtworkMPMediaItemArtwork from file paths, plus bounds inspection.
  • Explicit macOS stubsMediaLibrary, MediaQuery, MusicPlayer, MediaItem, MediaItemCollection, MediaPlaylist, VolumeView, SystemMusicPlayer, and PlayableContentDataSource all report the Apple availability reason instead of failing mysteriously.

Example matrix

cargo run --example 01_now_playing_smoke
cargo run --example 02_remote_command_center_smoke
cargo run --example 03_artwork_smoke
cargo run --example 04_media_library_unavailable
cargo run --example 05_media_query_unavailable
cargo run --example 06_music_player_unavailable
cargo run --example 07_media_item_unavailable
cargo run --example 08_media_item_collection_unavailable
cargo run --example 09_media_playlist_unavailable
cargo run --example 10_volume_view_unavailable
cargo run --example 11_system_music_player_unavailable
cargo run --example 12_playable_content_data_source_unavailable

Verification

cargo clippy --all-targets -- -D warnings
cargo test
for ex in examples/*.rs; do cargo run --example "$(basename "$ex" .rs)"; done

License

Licensed under either of Apache-2.0 or MIT at your option.