mpv-client-cross-macros 4.0.0

Proc macros for mpv client bindings
Documentation

MPV plugins in Rust

Bindings for libmpv client API that allow you to create plugins for MPV in Rust.

⚠️ About this Fork: This is a maintained fork of the original TheCactusVert/mpv-client.

Key improvements in this fork:

  • Windows Support: via MPV_CPLUGIN_DYNAMIC_SYM linkage-to-libmpv.
  • Android Support: via MPV_CPLUGIN_DYNAMIC_SYM linkage-to-libmpv.
  • No LLVM/Clang required: Uses pregenerated bindings by default, meaning you don't need bindgen or a local LLVM installation during cargo build.
  • Ergonomic #[mpv_client::main] Macro: Removes C-FFI boilerplate by wrapping your main function and automatically generating the underlying mpv_open_cplugin entry point.
  • Built-in Configuration Parsing: Includes a seamless read_options() helper with serde support to automatically merge and parse options from both configuration files (~~/script-opts/) and MPV command-line arguments.
  • Logging: The macro or mp.initialize_logging method automatically initializes a custom global log implementation (MpvLogger) that prints colored messages to stderr and seamlessly matches/synchronizes its timestamps with MPV's native --log-file timeline.

Example

Init plugin

Here is an example for your Cargo.toml:

[package]

name = "mpv-plugin"

version = "0.1.0"

edition = "2024"



[lib]

name = "mpv_plugin"

crate-type = ["cdylib"]



[dependencies]

mpv-client = { version = "4.0", package = "mpv-client-cross" }

And then the code src/lib.rs:

use mpv_client::{mpv_handle, Event, Handle};

#[no_mangle]
unsafe extern "C" fn mpv_open_cplugin(handle: *mut mpv_handle) -> i32 {
  let (mp, event_token) = unsafe { Handle::from_ptr(handle) };
  
  println!("Hello world from Rust plugin {}!", mp.name());
  
  loop {
    match mp.wait_event(&mut event_token, -1.) {
      Event::Shutdown => break,
      event => println!("Got event: {}", event),
    }
  }

  0
}

or

mpv-client = { version = "4.0", package = "mpv-client-cross", features = ["macros"] }

use mpv_client::{Event, Handle, EventQueueToken};

#[mpv_client::main]
fn main(mp: &Handle, mut event_token: EventQueueToken) -> i32 {
  log::info!("Hello world from Rust plugin {}!", mp.name());
  
  loop {
    match mp.wait_event(&mut event_token, -1.) {
      Event::Shutdown => break,
      event => log::info!("Got event: {}", event),
    }
  }

  0
}

⚠️ Important Warning: #[mpv_client::main] macro automatically initializes global logger internally. If your code already sets up a third-party logger (for example, via env_logger::init(), log4rs, or similar crates), make sure to remove or disable its initialization. Attempting to initialize a global logger twice will result in a runtime error (application panic).

Read config options

First, define your configuration structure using serde deserialization. It is highly recommended to use #[serde(default)] so the plugin can gracefully fall back to default values if options are missing from the configuration files.

use serde::Deserialize;
use mpv_client::{Event, Handle, EventQueueToken};

#[derive(Debug, Deserialize, Default)]
#[serde(default)]
pub struct PluginConfig {
    /// Enable or disable the plugin features
    pub enabled: bool,

    /// Maximum number of items to process/display
    pub max_items: u32,

    /// Optional API token or path to a custom asset
    pub auth_token: Option<String>,

    /// Select the visual layout style
    pub layout: LayoutMode,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum LayoutMode {
    #[default]
    Default,
    Compact,
    Expanded,
}

You can call read_options() directly on your Handle instance. The method automatically infers the plugin name and looks up settings from both configuration files and the command line.

#[mpv_client::main]
fn main(mp: &Handle, mut event_token: EventQueueToken) -> i32 {
    // Automatically reads options and falls back to defaults if not found
    let config: PluginConfig = mp.read_options();
    
    println!("Plugin initialized with config: {:#?}", config);
    
    loop {
        match mp.wait_event(&mut event_token, -1.) {
            Event::Shutdown => break,
            event => log::info!("Got event: {}", event),
        }
    }

    0
}

You can find more examples in C and Rust.