From aff8aaef589f3d0457148cbb5c17045f62fa36d6 Mon Sep 17 00:00:00 2001 From: KhooLy <73142442+KhooLy@users.noreply.github.com> Date: Wed, 8 Jul 2026 03:18:18 +0300 Subject: [PATCH] Fix player loader blocked by subtitle loading --- .gitignore | 1 + src-tauri/src/mpv_render.rs | 40 ++++++++++++++++++++------- src/components/ReactPlayerOverlay.tsx | 11 +++++++- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index fa9841d..1963a28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ CLAUDE.md +AGENTS.md node_modules/ dist/ src-tauri/target/ diff --git a/src-tauri/src/mpv_render.rs b/src-tauri/src/mpv_render.rs index ac2a8df..273d878 100644 --- a/src-tauri/src/mpv_render.rs +++ b/src-tauri/src/mpv_render.rs @@ -100,6 +100,7 @@ type MpvTerminateDestroy = unsafe extern "C" fn(*mut MpvHandle); type MpvSetOptionString = unsafe extern "C" fn(*mut MpvHandle, *const c_char, *const c_char) -> c_int; type MpvCommandString = unsafe extern "C" fn(*mut MpvHandle, *const c_char) -> c_int; +type MpvCommandAsync = unsafe extern "C" fn(*mut MpvHandle, u64, *const *const c_char) -> c_int; type MpvGetProperty = unsafe extern "C" fn(*mut MpvHandle, *const c_char, c_int, *mut c_void) -> c_int; type MpvFree = unsafe extern "C" fn(*mut c_void); @@ -122,6 +123,7 @@ struct MpvApi { mpv_terminate_destroy: MpvTerminateDestroy, mpv_set_option_string: MpvSetOptionString, mpv_command_string: MpvCommandString, + mpv_command_async: MpvCommandAsync, mpv_get_property: MpvGetProperty, mpv_free: MpvFree, mpv_error_string: MpvErrorString, @@ -158,6 +160,9 @@ impl MpvApi { let mpv_command_string = *library .get::(b"mpv_command_string\0") .map_err(load_error)?; + let mpv_command_async = *library + .get::(b"mpv_command_async\0") + .map_err(load_error)?; let mpv_get_property = *library .get::(b"mpv_get_property\0") .map_err(load_error)?; @@ -194,6 +199,7 @@ impl MpvApi { mpv_terminate_destroy, mpv_set_option_string, mpv_command_string, + mpv_command_async, mpv_get_property, mpv_free, mpv_error_string, @@ -569,12 +575,11 @@ impl MpvRenderer { title: Option<&str>, language: Option<&str>, ) -> Result<(), String> { - let escaped_url = command_quote(url); - let escaped_title = command_quote(title.unwrap_or("Subtitle")); - let escaped_language = command_quote(language.unwrap_or("")); - self.command_string(&format!( - "sub-add {escaped_url} auto {escaped_title} {escaped_language}" - )) + let title = title.unwrap_or("Subtitle"); + match language.filter(|value| !value.is_empty()) { + Some(language) => self.command_async_args(&["sub-add", url, "auto", title, language]), + None => self.command_async_args(&["sub-add", url, "auto", title]), + } } #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] @@ -603,6 +608,25 @@ impl MpvRenderer { } } + fn command_async_args(&self, args: &[&str]) -> Result<(), String> { + let c_args = args + .iter() + .map(|arg| CString::new(*arg).map_err(|error| error.to_string())) + .collect::, _>>()?; + let mut raw_args = c_args.iter().map(|arg| arg.as_ptr()).collect::>(); + raw_args.push(ptr::null()); + + let result = unsafe { (self.api.mpv_command_async)(self.handle, 0, raw_args.as_ptr()) }; + if result < 0 { + Err(format!( + "mpv async command failed: {}", + self.api.error_string(result) + )) + } else { + Ok(()) + } + } + pub fn render_frame(&mut self, width: i32, height: i32) -> Result { if !self.loaded { return Err("player has not loaded media yet".to_string()); @@ -1133,10 +1157,6 @@ impl MpvRenderer { } } -fn command_quote(value: &str) -> String { - format!("\"{}\"", value.replace('\\', "\\\\").replace('"', "\\\"")) -} - // Linux-only OpenGL proc address resolution #[cfg(target_os = "linux")] diff --git a/src/components/ReactPlayerOverlay.tsx b/src/components/ReactPlayerOverlay.tsx index 37be42b..79eb252 100644 --- a/src/components/ReactPlayerOverlay.tsx +++ b/src/components/ReactPlayerOverlay.tsx @@ -486,8 +486,17 @@ export function ReactPlayerOverlay({ closePlayer, onFirstFrame, initialTitle, in if (!firstFrameFiredRef.current && onFirstFrame) { const noVideoTrack = status.trackListReady && !status.hasVideoTrack; + const hasVideoDimensions = + (parseFloat(status.width ?? '0') || 0) > 0 && + (parseFloat(status.height ?? '0') || 0) > 0; + const playbackAdvancing = + status.loaded && + status.pause !== 'yes' && + status.pausedForCache !== 'yes' && + pos > 0.15; const voReady = !noVideoTrack && status.voConfigured === 'yes' && status.framesRendered >= 2; - if (voReady || noVideoTrack) { + const activeVideoPlayback = !noVideoTrack && status.hasVideoTrack && hasVideoDimensions && playbackAdvancing; + if (voReady || activeVideoPlayback || noVideoTrack) { firstFrameFiredRef.current = true; sendCmd('set pause no'); onFirstFrame();