From b4955b58e622f56a0575470f687c656af5e44e31 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 18:28:03 +0000 Subject: [PATCH] feat: add play-external IPC for launching media in an external player Mirrors stremio-glutin-shell's play-external action (PR Stremio/stremio-glutin-shell#2) so the Windows shell can hand a stream off to mpv, VLC or PotPlayer when the user picks an external player in the WebUI. - Allowlist of Windows player URL schemes: mpv://, vlc://, potplayer://. iOS/macOS-only schemes (iina, infuse, outplayer, open-vidhub) have no Windows handlers and are intentionally not listed. - mpv has no Windows URL handler: spawn the binary directly with `--` separating options from the stream URL. Tries common install paths (Program Files, scoop's %LOCALAPPDATA%\Programs\mpv, %LOCALAPPDATA%\mpv) before falling back to PATH lookup. - Spawned with CREATE_BREAKAWAY_FROM_JOB so the JobObject's KILL_ON_JOB_CLOSE doesn't kill mpv when the shell exits. - vlc:// and potplayer:// are routed through `open::that` to use the OS protocol handler the user already has registered. Test: cargo fmt --all -- --check, cargo clippy --all -- -D warnings. --- src/stremio_app/app.rs | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index 024272e..c5e53dd 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -298,6 +298,52 @@ impl MainWindow { } } } + Some("play-external") => { + if let Some(arg) = msg.get_params() { + let arg = arg.as_str().unwrap_or(""); + let arg_lc = arg.to_lowercase(); + const ALLOWED_SCHEMES: &[&str] = &["mpv://", "vlc://", "potplayer://"]; + let allowed = ALLOWED_SCHEMES.iter().any(|s| arg_lc.starts_with(s)); + if !arg.is_empty() && allowed { + if let Some(stream_url) = + arg_lc.starts_with("mpv://").then(|| &arg[6..]) + { + // `--` ends mpv's option parsing; the stream URL can't smuggle flags. + let mpv_paths: Vec = vec![ + std::env::var("ProgramFiles") + .ok() + .map(|v| format!("{v}\\mpv\\mpv.exe")), + std::env::var("ProgramFiles(x86)") + .ok() + .map(|v| format!("{v}\\mpv\\mpv.exe")), + std::env::var("LOCALAPPDATA") + .ok() + .map(|v| format!("{v}\\Programs\\mpv\\mpv.exe")), + std::env::var("LOCALAPPDATA") + .ok() + .map(|v| format!("{v}\\mpv\\mpv.exe")), + Some("mpv.exe".to_string()), + ] + .into_iter() + .flatten() + .collect(); + for path in &mpv_paths { + if Command::new(path) + .arg("--") + .arg(stream_url) + .creation_flags(CREATE_BREAKAWAY_FROM_JOB) + .spawn() + .is_ok() + { + break; + } + } + } else { + open::that(arg).ok(); + } + } + } + } Some("win-focus") => { focus_sender.notice(); }