From de468bd278e5cfc80962189fa44f3c97808b427d Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 29 Sep 2026 12:32:13 +0300 Subject: [PATCH] Send mpv controls asynchronously in order --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/app/video/imp.rs | 115 ++++++++++++++++++++++++++++++++++++++++--- src/app/video/mod.rs | 8 +-- 4 files changed, 112 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9524712..8d1abe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1696,7 +1696,7 @@ dependencies = [ [[package]] name = "libmpv2" version = "5.0.3" -source = "git+https://github.com/Stremio/libmpv2-rs#9e19e7436a933c5eb1ca1b2b16bb9fbe86479576" +source = "git+https://github.com/Stremio/libmpv2-rs?rev=b0c168c7f0efabac85d4961db086340c9d1382b9#b0c168c7f0efabac85d4961db086340c9d1382b9" dependencies = [ "libmpv2-sys", ] @@ -1704,7 +1704,7 @@ dependencies = [ [[package]] name = "libmpv2-sys" version = "4.0.1" -source = "git+https://github.com/Stremio/libmpv2-rs#9e19e7436a933c5eb1ca1b2b16bb9fbe86479576" +source = "git+https://github.com/Stremio/libmpv2-rs?rev=b0c168c7f0efabac85d4961db086340c9d1382b9#b0c168c7f0efabac85d4961db086340c9d1382b9" [[package]] name = "libredox" diff --git a/Cargo.toml b/Cargo.toml index 2432544..a9deade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ tracing-subscriber = "0.3.23" serde = "1.0.229" serde_json = "1.0.151" itertools = "0.15.0" -libmpv2 = { version = "5.0.3", git = "https://github.com/Stremio/libmpv2-rs" } +libmpv2 = { version = "5.0.3", git = "https://github.com/Stremio/libmpv2-rs", rev = "b0c168c7f0efabac85d4961db086340c9d1382b9" } image = "0.25.10" chrono = "0.4.45" anyhow = "1.0.104" diff --git a/src/app/video/imp.rs b/src/app/video/imp.rs index b9cc5a2..28c7273 100644 --- a/src/app/video/imp.rs +++ b/src/app/video/imp.rs @@ -11,7 +11,13 @@ use libmpv2::{ mpv_end_file_reason, render::{OpenGLInitParams, RenderContext, RenderParam, RenderParamApiType}, }; -use std::{cell::RefCell, env, os::raw::c_void, sync::OnceLock}; +use std::{ + cell::{Cell, RefCell}, + collections::VecDeque, + env, + os::raw::c_void, + sync::OnceLock, +}; use tracing::error; use crate::spawn_local; @@ -25,11 +31,28 @@ enum EventCallback { Events, } +type Submit = Box libmpv2::Result<()>>; + +struct Control { + name: String, + submit: Submit, +} + +impl Control { + fn is_subtitle(&self) -> bool { + matches!(self.name.as_str(), "sub-add" | "sub-remove" | "sid") + } +} + #[derive(Properties)] #[properties(wrapper_type = super::Video)] pub struct Video { mpv: RefCell, render_context: RefCell>, + controls: RefCell>, + pending_control: RefCell>, + pending_subtitle: Cell>, + next_request: Cell, } impl Default for Video { @@ -62,6 +85,10 @@ impl Default for Video { Self { mpv: RefCell::new(mpv), render_context: Default::default(), + controls: Default::default(), + pending_control: Default::default(), + pending_subtitle: Default::default(), + next_request: Default::default(), } } } @@ -82,22 +109,86 @@ impl Video { } } - pub fn send_command(&self, name: &str, args: &[&str]) { - if let Err(e) = self.mpv.borrow().command(name, args) { - error!("Failed to send command {name}: {e}"); + fn dispatch(&self) { + while self.pending_control.borrow().is_none() { + let control = { + let mut controls = self.controls.borrow_mut(); + let subtitle_pending = self.pending_subtitle.get().is_some(); + controls + .iter() + .position(|control| !subtitle_pending || !control.is_subtitle()) + .and_then(|index| controls.remove(index)) + }; + let Some(control) = control else { + break; + }; + + let id = self.next_request.get(); + self.next_request.set(id + 1); + + match (control.submit)(&self.mpv.borrow(), id) { + Ok(()) if control.name == "sub-add" => self.pending_subtitle.set(Some(id)), + Ok(()) => *self.pending_control.borrow_mut() = Some((id, control.name)), + Err(e) => error!("Failed to send {}: {e}", control.name), + } } } + fn enqueue(&self, control: Control) { + self.controls.borrow_mut().push_back(control); + self.dispatch(); + } + + fn on_reply(&self, id: u64, result: libmpv2::Result<()>) { + let name = if self.pending_subtitle.get() == Some(id) { + self.pending_subtitle.set(None); + Some("sub-add".to_owned()) + } else { + self.pending_control + .borrow_mut() + .take_if(|(pending, _)| *pending == id) + .map(|(_, name)| name) + }; + + if let (Some(name), Err(e)) = (name, result) { + error!("Failed to send {name}: {e}"); + } + + self.dispatch(); + } + + pub fn send_command(&self, name: String, args: Vec) { + if matches!(name.as_str(), "loadfile" | "stop") { + self.controls + .borrow_mut() + .retain(|control| !control.is_subtitle()); + if let Some(id) = self.pending_subtitle.get() { + self.mpv.borrow().abort_async_command(id); + } + } + + let command = name.clone(); + self.enqueue(Control { + name, + submit: Box::new(move |mpv, id| { + let args: Vec<_> = args.iter().map(String::as_str).collect(); + mpv.command_async(&command, &args, id) + }), + }); + } + pub fn observe_property(&self, name: &str, format: Format) { if let Err(e) = self.mpv.borrow().observe_property(name, format, 0) { error!("Failed to observe property {name}: {e}"); } } - pub fn set_property(&self, name: &str, value: T) { - if let Err(e) = self.mpv.borrow().set_property(name, value) { - error!("Failed to set property {name}: {e}"); - } + pub fn set_property(&self, name: &str, value: T) { + let property = name.to_owned(); + self.enqueue(Control { + name: name.to_owned(), + submit: Box::new(move |mpv, id| mpv.set_property_async(&property, value, id)), + }); } } @@ -181,6 +272,14 @@ impl WidgetImpl for Video { object.emit_by_name::<()>("playback-ended", &[&reason]); video.unobserve_properties(); } + Event::CommandReply { + reply_userdata, + result, + } + | Event::SetPropertyReply { + reply_userdata, + result, + } => video.on_reply(reply_userdata, result), _ => {} }); } diff --git a/src/app/video/mod.rs b/src/app/video/mod.rs index 68423f9..dca4ba0 100644 --- a/src/app/video/mod.rs +++ b/src/app/video/mod.rs @@ -3,7 +3,6 @@ mod imp; use adw::subclass::prelude::ObjectSubclassIsExt; use gtk::glib::{self, Variant, closure_local, object::ObjectExt}; -use itertools::Itertools; use libmpv2::Format; use serde_json::{Number, Value}; use tracing::warn; @@ -68,10 +67,7 @@ impl Video { } pub fn send_mpv_command(&self, name: String, args: Vec) { - let widget = self.imp(); - - let args = args.iter().map(String::as_ref).collect_vec(); - widget.send_command(&name, &args); + self.imp().send_command(name, args); } pub fn observe_mpv_property(&self, name: String) { @@ -107,7 +103,7 @@ impl Video { } name if STRING_PROPERTIES.contains(&name) => { if let Some(value) = value.as_str() { - widget.set_property(name, value); + widget.set_property(name, value.to_owned()); } } name => warn!("Failed to set property {name}: Unsupported"),