Bump libmpv2 crate from 4.1.0 to 5.0.3

The 4.x -> 5.x release folded `EventContext` into `Mpv` and made
`wait_event` take `&mut self`, so the previous design (a single
`Arc<Mpv>` shared between the message and event threads, with the event
thread constructing its own `EventContext` from `mpv.ctx`) no longer
compiles.

Migration:
- Build the main `Mpv` (with `wid`, `title`, etc.) and use it from the
  message thread for commands and `set_property`.
- `create_client(None)` a sub-client `Mpv`, owned by the event thread,
  which calls `disable_deprecated_events`/`observe_property`/`wait_event`
  on itself. Global events (`EndFile`, `Shutdown`) and property changes
  observed by this client land in its event queue.
- Replace the old `MpvExt::wake_up` (which woke the main `Mpv`) with an
  `EventClientWakeup` holding the sub-client's `mpv_handle` pointer, so
  the message thread can interrupt the event thread's blocking
  `wait_event` after pushing a new observe request onto the channel.

Also removed the `PropertyData::Node(_)` match arm in
`communication.rs`: 5.0.0 dropped `mpv_node` support, so the variant no
longer exists and the match is exhaustive without it.

https://claude.ai/code/session_01CuGHFREBZFkf8tfaiRV4K6
This commit is contained in:
Claude 2026-05-05 07:59:26 +00:00
parent e199d7f38c
commit 9b057a0030
No known key found for this signature in database
4 changed files with 34 additions and 27 deletions

6
Cargo.lock generated
View file

@ -880,9 +880,9 @@ checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]] [[package]]
name = "libmpv2" name = "libmpv2"
version = "4.1.0" version = "5.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0c3802b4bb1a18adbf5659b078ce24cb8e16c79ff695557f4e10af2a44722a" checksum = "88dc8432d3de09ed6e343488d6b208467db6801fb41971a1f726fa2248302fe5"
dependencies = [ dependencies = [
"libmpv2-sys", "libmpv2-sys",
] ]
@ -1598,7 +1598,7 @@ dependencies = [
[[package]] [[package]]
name = "stremio-shell-ng" name = "stremio-shell-ng"
version = "5.0.20" version = "5.0.21"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.4.2", "bitflags 2.4.2",

View file

@ -22,7 +22,7 @@ winapi = { version = "0.3.9", features = [
] } ] }
webview2 = "0.1.4" webview2 = "0.1.4"
webview2-sys = "0.1.1" webview2-sys = "0.1.1"
libmpv2 = "4.1.0" libmpv2 = "5.0.3"
libmpv2-sys = "4.0.1" libmpv2-sys = "4.0.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View file

@ -30,7 +30,6 @@ impl PlayerProprChange {
serde_json::Value::String(s.to_string()) serde_json::Value::String(s.to_string())
} }
} }
PropertyData::Node(_) => unimplemented!("`PropertyData::Node` is not supported"),
} }
} }
pub fn from_name_value(name: String, value: PropertyData) -> Self { pub fn from_name_value(name: String, value: PropertyData) -> Self {

View file

@ -1,9 +1,10 @@
use crate::stremio_app::ipc; use crate::stremio_app::ipc;
use crate::stremio_app::RPCResponse; use crate::stremio_app::RPCResponse;
use flume::{Receiver, Sender}; use flume::{Receiver, Sender};
use libmpv2::{events::Event, events::EventContext, Format, Mpv, SetData}; use libmpv2::{events::Event, Format, Mpv, SetData};
use native_windows_gui::{self as nwg, PartialUi}; use native_windows_gui::{self as nwg, PartialUi};
use std::{ use std::{
ptr::NonNull,
sync::Arc, sync::Arc,
thread::{self, JoinHandle}, thread::{self, JoinHandle},
}; };
@ -19,6 +20,18 @@ struct ObserveProperty {
format: Format, format: Format,
} }
// Wakes up the event thread blocked in `mpv_wait_event` on the sub-client.
// `mpv_wakeup` is documented as safe to call from any thread.
#[derive(Clone, Copy)]
struct EventClientWakeup(NonNull<libmpv2_sys::mpv_handle>);
unsafe impl Send for EventClientWakeup {}
unsafe impl Sync for EventClientWakeup {}
impl EventClientWakeup {
fn wake_up(&self) {
unsafe { libmpv2_sys::mpv_wakeup(self.0.as_ptr()) }
}
}
#[derive(Default)] #[derive(Default)]
pub struct Player { pub struct Player {
pub channel: ipc::Channel, pub channel: ipc::Channel,
@ -43,21 +56,26 @@ impl PartialUi for Player {
let (observe_property_sender, observe_property_receiver) = flume::unbounded(); let (observe_property_sender, observe_property_receiver) = flume::unbounded();
data.channel = ipc::Channel::new(Some((in_msg_sender, rpc_response_receiver))); data.channel = ipc::Channel::new(Some((in_msg_sender, rpc_response_receiver)));
let mpv = create_shareable_mpv(window_handle); let mpv = Arc::new(create_mpv(window_handle));
let mpv_event_client = mpv
.create_client(None)
.expect("cannot create MPV event client");
let event_wakeup = EventClientWakeup(mpv_event_client.ctx);
let _event_thread = create_event_thread( let _event_thread = create_event_thread(
Arc::clone(&mpv), mpv_event_client,
observe_property_receiver, observe_property_receiver,
rpc_response_sender, rpc_response_sender,
); );
let _message_thread = create_message_thread(mpv, observe_property_sender, in_msg_receiver); let _message_thread =
create_message_thread(mpv, event_wakeup, observe_property_sender, in_msg_receiver);
// @TODO implement a mechanism to stop threads on `Player` drop if needed // @TODO implement a mechanism to stop threads on `Player` drop if needed
Ok(()) Ok(())
} }
} }
fn create_shareable_mpv(window_handle: HWND) -> Arc<Mpv> { fn create_mpv(window_handle: HWND) -> Mpv {
let mpv = Mpv::with_initializer(|initializer| { let mpv = Mpv::with_initializer(|initializer| {
macro_rules! set_property { macro_rules! set_property {
($name:literal, $value:expr) => { ($name:literal, $value:expr) => {
@ -79,17 +97,16 @@ fn create_shareable_mpv(window_handle: HWND) -> Arc<Mpv> {
// set_property!("vo", "gpu-next,"); // set_property!("vo", "gpu-next,");
Ok(()) Ok(())
}); });
Arc::new(mpv.expect("cannot build MPV")) mpv.expect("cannot build MPV")
} }
fn create_event_thread( fn create_event_thread(
mpv: Arc<Mpv>, mut mpv_event_client: Mpv,
observe_property_receiver: Receiver<ObserveProperty>, observe_property_receiver: Receiver<ObserveProperty>,
rpc_response_sender: Sender<String>, rpc_response_sender: Sender<String>,
) -> JoinHandle<()> { ) -> JoinHandle<()> {
thread::spawn(move || { thread::spawn(move || {
let mut event_context = EventContext::new(mpv.ctx); mpv_event_client
event_context
.disable_deprecated_events() .disable_deprecated_events()
.expect("failed to disable deprecated MPV events"); .expect("failed to disable deprecated MPV events");
@ -97,13 +114,13 @@ fn create_event_thread(
loop { loop {
for ObserveProperty { name, format } in observe_property_receiver.drain() { for ObserveProperty { name, format } in observe_property_receiver.drain() {
event_context mpv_event_client
.observe_property(&name, format, 0) .observe_property(&name, format, 0)
.expect("failed to observer MPV property"); .expect("failed to observer MPV property");
} }
// -1.0 means to block and wait for an event. // -1.0 means to block and wait for an event.
let event = match event_context.wait_event(-1.) { let event = match mpv_event_client.wait_event(-1.) {
Some(Ok(event)) => event, Some(Ok(event)) => event,
Some(Err(error)) => { Some(Err(error)) => {
eprintln!("Event errored: {error:?}"); eprintln!("Event errored: {error:?}");
@ -141,6 +158,7 @@ fn create_event_thread(
fn create_message_thread( fn create_message_thread(
mpv: Arc<Mpv>, mpv: Arc<Mpv>,
event_wakeup: EventClientWakeup,
observe_property_sender: Sender<ObserveProperty>, observe_property_sender: Sender<ObserveProperty>,
in_msg_receiver: Receiver<String>, in_msg_receiver: Receiver<String>,
) -> JoinHandle<()> { ) -> JoinHandle<()> {
@ -151,7 +169,7 @@ fn create_message_thread(
observe_property_sender observe_property_sender
.send(ObserveProperty { name, format }) .send(ObserveProperty { name, format })
.expect("cannot send ObserveProperty"); .expect("cannot send ObserveProperty");
mpv.wake_up(); event_wakeup.wake_up();
}; };
let send_command = |cmd: CmdVal| { let send_command = |cmd: CmdVal| {
@ -252,13 +270,3 @@ fn create_message_thread(
}) })
} }
trait MpvExt {
fn wake_up(&self);
}
impl MpvExt for Mpv {
// @TODO create a PR to the `libmpv` crate and then remove `libmpv-sys` from Cargo.toml?
fn wake_up(&self) {
unsafe { libmpv2_sys::mpv_wakeup(self.ctx.as_ptr()) }
}
}