From 469bb4f5bf6053bb587a7cfb86ba5694511b2d63 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Mon, 11 May 2026 02:02:19 +0300 Subject: [PATCH 01/11] Pass streaming server URL to WebUI --- src/stremio_app/app.rs | 44 ++++++++++++++++++++++++++++++++++-- src/stremio_app/constants.rs | 9 ++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index 024272e..e4f6dec 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -16,7 +16,10 @@ use url::Url; use winapi::um::{winbase::CREATE_BREAKAWAY_FROM_JOB, winuser::WS_EX_TOPMOST}; use crate::stremio_app::{ - constants::{APP_NAME, UPDATE_ENDPOINT, UPDATE_INTERVAL, WINDOW_MIN_HEIGHT, WINDOW_MIN_WIDTH}, + constants::{ + web_endpoint_with_streaming_server, APP_NAME, UPDATE_ENDPOINT, UPDATE_INTERVAL, + WEB_ENDPOINT, WINDOW_MIN_HEIGHT, WINDOW_MIN_WIDTH, + }, ipc::{RPCRequest, RPCResponse}, splash::SplashImage, stremio_player::Player, @@ -29,6 +32,40 @@ use crate::stremio_app::{ use super::stremio_server::StremioServer; +fn webui_url_with_streaming_server(webui_url: &str) -> String { + if webui_url.trim_end_matches('/') != WEB_ENDPOINT.trim_end_matches('/') { + return webui_url.to_string(); + } + + find_streaming_server_url() + .map(|server_url| web_endpoint_with_streaming_server(&server_url)) + .unwrap_or_else(|| webui_url.to_string()) +} + +fn find_streaming_server_url() -> Option { + let client = reqwest::blocking::Client::builder() + .timeout(time::Duration::from_millis(500)) + .build() + .ok()?; + + (11470..=11474).find_map(|port| { + let server_url = format!("http://127.0.0.1:{port}"); + let settings_url = format!("{server_url}/settings"); + let response = client.get(settings_url).send().ok()?; + + if !response.status().is_success() { + return None; + } + + let body = response.text().ok()?; + if body.contains("\"baseUrl\"") && body.contains("\"values\"") { + Some(server_url) + } else { + None + } + }) +} + #[derive(Default, NwgUi)] pub struct MainWindow { pub command: String, @@ -126,7 +163,10 @@ impl MainWindow { } } fn on_init(&self) { - self.webview.endpoint.set(self.webui_url.clone()).ok(); + self.webview + .endpoint + .set(webui_url_with_streaming_server(&self.webui_url)) + .ok(); self.webview.dev_tools.set(self.dev_tools).ok(); if let Some(hwnd) = self.window.handle.hwnd() { if let Ok(mut saved_style) = self.saved_window_style.try_borrow_mut() { diff --git a/src/stremio_app/constants.rs b/src/stremio_app/constants.rs index 27f8317..4cd6e9d 100644 --- a/src/stremio_app/constants.rs +++ b/src/stremio_app/constants.rs @@ -15,3 +15,12 @@ pub const STREMIO_SERVER_DEV_MODE: &str = "STREMIO_SERVER_DEV_MODE"; pub const SRV_BUFFER_SIZE: usize = 1024; pub const SERVER_IPC_KEY: &str = "SERVER_IPC_KEY"; pub const SRV_LOG_SIZE: usize = 20; + +pub fn web_endpoint_with_streaming_server(server_url: &str) -> String { + let server_url = server_url.trim_end_matches('/'); + let streaming_server_url = + url::form_urlencoded::byte_serialize(server_url.as_bytes()).collect::(); + let web_endpoint = WEB_ENDPOINT.trim_end_matches('/'); + + format!("{web_endpoint}/#/?streamingServerUrl={streaming_server_url}") +} From 5d119459a1158dda53cae2214bf5cd6fb359e64e Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Mon, 11 May 2026 01:43:19 +0300 Subject: [PATCH 02/11] Player: switch to gpu-next defaults --- src/stremio_app/stremio_player/player.rs | 43 ++++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/stremio_app/stremio_player/player.rs b/src/stremio_app/stremio_player/player.rs index 06501b5..154ab6f 100644 --- a/src/stremio_app/stremio_player/player.rs +++ b/src/stremio_app/stremio_player/player.rs @@ -19,6 +19,28 @@ struct ObserveProperty { format: Format, } +fn with_gpu_next_fallback(vo: String) -> String { + let mut outputs = vo + .split(',') + .filter(|output| !output.is_empty()) + .map(String::from) + .collect::>(); + + let has_gpu_next = outputs.iter().any(|output| output == "gpu-next"); + let has_gpu = outputs.iter().any(|output| output == "gpu"); + + if outputs.is_empty() { + outputs.push("gpu-next".to_string()); + outputs.push("gpu".to_string()); + } else if has_gpu_next && !has_gpu { + outputs.push("gpu".to_string()); + } else if has_gpu && !has_gpu_next { + outputs.push("gpu-next".to_string()); + } + + format!("{},", outputs.join(",")) +} + #[derive(Default)] pub struct Player { pub channel: ipc::Channel, @@ -76,7 +98,19 @@ fn create_shareable_mpv(window_handle: HWND) -> Arc { set_property!("msg-level", "all=no"); set_property!("quiet", "yes"); set_property!("hwdec", "auto"); - // set_property!("vo", "gpu-next,"); + // gpu-next: libplacebo VO with modern HDR tone-mapping; gpu, is the fallback. + set_property!("vo", "gpu-next,gpu,"); + for (name, value) in [ + ("tone-mapping", "bt.2390"), + ("dither-depth", "auto"), + ("deband", "yes"), + ("scale", "spline36"), + ("cscale", "spline36"), + ] { + if let Err(error) = initializer.set_property(name, value) { + eprintln!("mpv: cannot set {name}={value}: {error:?}"); + } + } Ok(()) }); Arc::new(mpv.expect("cannot build MPV")) @@ -230,12 +264,7 @@ fn create_message_thread( } InMsg(InMsgFn::MpvSetProp, InMsgArgs::StProp(name, PropVal::Str(value))) => { let value = if name.to_string() == "vo" { - let mut value = value; - if !value.is_empty() && !value.ends_with(',') { - value.push(','); - } - value.push_str("gpu-next,"); - value + with_gpu_next_fallback(value) } else { value }; From bfcd7de6da0fdbb909dabff654a8b90dce297b26 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 12 May 2026 15:12:20 +0200 Subject: [PATCH 03/11] Use server startup URL for WebUI streaming URL --- src/stremio_app/app.rs | 48 +++++------------------- src/stremio_app/stremio_server/server.rs | 21 ++++++++--- 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index e4f6dec..e2455e4 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -32,40 +32,6 @@ use crate::stremio_app::{ use super::stremio_server::StremioServer; -fn webui_url_with_streaming_server(webui_url: &str) -> String { - if webui_url.trim_end_matches('/') != WEB_ENDPOINT.trim_end_matches('/') { - return webui_url.to_string(); - } - - find_streaming_server_url() - .map(|server_url| web_endpoint_with_streaming_server(&server_url)) - .unwrap_or_else(|| webui_url.to_string()) -} - -fn find_streaming_server_url() -> Option { - let client = reqwest::blocking::Client::builder() - .timeout(time::Duration::from_millis(500)) - .build() - .ok()?; - - (11470..=11474).find_map(|port| { - let server_url = format!("http://127.0.0.1:{port}"); - let settings_url = format!("{server_url}/settings"); - let response = client.get(settings_url).send().ok()?; - - if !response.status().is_success() { - return None; - } - - let body = response.text().ok()?; - if body.contains("\"baseUrl\"") && body.contains("\"values\"") { - Some(server_url) - } else { - None - } - }) -} - #[derive(Default, NwgUi)] pub struct MainWindow { pub command: String, @@ -163,10 +129,16 @@ impl MainWindow { } } fn on_init(&self) { - self.webview - .endpoint - .set(webui_url_with_streaming_server(&self.webui_url)) - .ok(); + let webui_url = + if self.webui_url.trim_end_matches('/') == WEB_ENDPOINT.trim_end_matches('/') { + self.server + .server_url() + .map(|server_url| web_endpoint_with_streaming_server(&server_url)) + .unwrap_or_else(|| self.webui_url.clone()) + } else { + self.webui_url.clone() + }; + self.webview.endpoint.set(webui_url).ok(); self.webview.dev_tools.set(self.dev_tools).ok(); if let Some(hwnd) = self.window.handle.hwnd() { if let Ok(mut saved_style) = self.saved_window_style.try_borrow_mut() { diff --git a/src/stremio_app/stremio_server/server.rs b/src/stremio_app/stremio_server/server.rs index c40a672..98efffe 100644 --- a/src/stremio_app/stremio_server/server.rs +++ b/src/stremio_app/stremio_server/server.rs @@ -27,12 +27,17 @@ pub struct StremioServer { parent: nwg::ControlHandle, crash_notice: nwg::Notice, logs: Arc>, + server_url: Arc>>, } impl StremioServer { - pub fn start(&self) { + pub fn server_url(&self) -> Option { + self.server_url.lock().ok().and_then(|url| url.clone()) + } + + pub fn start(&self) -> Option { if self.development { - return; + return None; } let (tx, rx) = flume::unbounded(); let logs = self.logs.clone(); @@ -173,8 +178,12 @@ impl StremioServer { sender.notice(); }); - // Wait for the server to start - rx.recv().unwrap(); + // Wait for the server to start and keep the exact endpoint printed by server.js. + let server_url = rx.recv().unwrap(); + if let Ok(mut stored_url) = self.server_url.lock() { + *stored_url = Some(server_url.clone()); + } + Some(server_url) } } @@ -193,7 +202,7 @@ impl PartialUi for StremioServer { .parent(data.parent) .build(&mut data.crash_notice) .ok(); - data.start(); + let _ = data.start(); println!("Stremio server started"); Ok(()) } @@ -210,7 +219,7 @@ impl PartialUi for StremioServer { "Stremio server crash log", self.logs.lock().unwrap().deref(), ); - self.start(); + let _ = self.start(); } } } From 93849ebd4d87766a4d0bb32c5f12bbbed52eab61 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Mon, 15 Jun 2026 22:27:44 +0300 Subject: [PATCH 04/11] Set D3D11 color-space defaults --- src/stremio_app/stremio_player/player.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/stremio_app/stremio_player/player.rs b/src/stremio_app/stremio_player/player.rs index 24c2ce1..95032d0 100644 --- a/src/stremio_app/stremio_player/player.rs +++ b/src/stremio_app/stremio_player/player.rs @@ -104,6 +104,11 @@ fn create_mpv(window_handle: HWND) -> Mpv { // gpu-next: libplacebo VO with modern HDR tone-mapping; gpu, is the fallback. set_property!("vo", "gpu-next,gpu,"); for (name, value) in [ + ("gpu-context", "d3d11"), + ("d3d11-output-format", "auto"), + ("d3d11-output-csp", "auto"), + ("target-colorspace-hint", "auto"), + ("target-colorspace-hint-mode", "target"), ("tone-mapping", "bt.2390"), ("dither-depth", "auto"), ("deband", "yes"), From 2b32e58d143092181e5ffb850b8a1f32b3e67c3b Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Mon, 15 Jun 2026 22:46:52 +0300 Subject: [PATCH 05/11] Switch output color mode by display --- Cargo.toml | 1 + src/stremio_app/stremio_player/player.rs | 210 ++++++++++++++++++++++- 2 files changed, 210 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 70f6105..b41ffcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ winapi = { version = "0.3.9", features = [ "jobapi2", "wincon", "winuser", + "wingdi", "namedpipeapi", ] } webview2 = "0.1.4" diff --git a/src/stremio_app/stremio_player/player.rs b/src/stremio_app/stremio_player/player.rs index 95032d0..a4a2178 100644 --- a/src/stremio_app/stremio_player/player.rs +++ b/src/stremio_app/stremio_player/player.rs @@ -4,10 +4,28 @@ use flume::{Receiver, Sender}; use libmpv2::{events::Event, Format, Mpv, SetData}; use native_windows_gui::{self as nwg, PartialUi}; use std::{ + mem, ptr, sync::Arc, thread::{self, JoinHandle}, + time::Duration, +}; +use winapi::shared::{ + minwindef::{DWORD, UINT}, + windef::{HMONITOR, HWND}, + winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}, +}; +use winapi::um::{ + wingdi::{ + DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, + DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_HEADER, + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO, + DISPLAYCONFIG_SOURCE_DEVICE_NAME, QDC_ONLY_ACTIVE_PATHS, + }, + winnt::LONG, + winuser::{ + GetMonitorInfoW, MonitorFromWindow, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST, + }, }; -use winapi::shared::windef::HWND; use crate::stremio_app::stremio_player::{ CmdVal, InMsg, InMsgArgs, InMsgFn, PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse, @@ -19,6 +37,24 @@ struct ObserveProperty { format: Format, } +#[link(name = "user32")] +extern "system" { + fn GetDisplayConfigBufferSizes( + flags: UINT, + num_path_array_elements: *mut UINT, + num_mode_info_array_elements: *mut UINT, + ) -> LONG; + fn QueryDisplayConfig( + flags: UINT, + num_path_array_elements: *mut UINT, + path_array: *mut DISPLAYCONFIG_PATH_INFO, + num_mode_info_array_elements: *mut UINT, + mode_info_array: *mut DISPLAYCONFIG_MODE_INFO, + current_topology_id: *mut u32, + ) -> LONG; + fn DisplayConfigGetDeviceInfo(request_packet: *mut DISPLAYCONFIG_DEVICE_INFO_HEADER) -> LONG; +} + fn with_gpu_next_fallback(vo: String) -> String { let mut outputs = vo .split(',') @@ -41,6 +77,19 @@ fn with_gpu_next_fallback(vo: String) -> String { format!("{},", outputs.join(",")) } +#[derive(Clone, Copy, Eq, PartialEq)] +enum DisplayOutputMode { + Hdr, + Sdr, + Auto, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +struct DisplayOutputState { + monitor: isize, + mode: DisplayOutputMode, +} + #[derive(Default)] pub struct Player { pub channel: ipc::Channel, @@ -75,6 +124,8 @@ impl PartialUi for Player { observe_property_receiver, rpc_response_sender, ); + let _display_thread = + create_display_output_thread(Arc::clone(&mpv), window_handle as isize); let _message_thread = create_message_thread(mpv, observe_property_sender, in_msg_receiver); // @TODO implement a mechanism to stop threads on `Player` drop if needed @@ -124,6 +175,163 @@ fn create_mpv(window_handle: HWND) -> Mpv { mpv.expect("cannot build MPV") } +fn create_display_output_thread(mpv: Arc, window_handle: isize) -> JoinHandle<()> { + thread::spawn(move || { + let mut last_state = None; + + loop { + let state = current_display_output_state(window_handle as HWND); + if last_state != Some(state) { + apply_display_output_mode(&mpv, state.mode); + last_state = Some(state); + } + thread::sleep(Duration::from_millis(500)); + } + }) +} + +fn current_display_output_state(window_handle: HWND) -> DisplayOutputState { + let monitor = unsafe { MonitorFromWindow(window_handle, MONITOR_DEFAULTTONEAREST) }; + let mode = match monitor_advanced_color_enabled(monitor) { + Some(true) => DisplayOutputMode::Hdr, + Some(false) => DisplayOutputMode::Sdr, + None => DisplayOutputMode::Auto, + }; + + DisplayOutputState { + monitor: monitor as isize, + mode, + } +} + +fn apply_display_output_mode(mpv: &Mpv, mode: DisplayOutputMode) { + let properties = match mode { + DisplayOutputMode::Hdr | DisplayOutputMode::Auto => [ + ("d3d11-output-csp", "auto"), + ("target-colorspace-hint", "auto"), + ("target-trc", "auto"), + ("target-prim", "auto"), + ], + DisplayOutputMode::Sdr => [ + ("d3d11-output-csp", "srgb"), + ("target-colorspace-hint", "yes"), + ("target-trc", "srgb"), + ("target-prim", "bt.709"), + ], + }; + + for (name, value) in properties { + if let Err(error) = mpv.set_property(name, value) { + eprintln!("mpv: cannot set {name}={value}: {error:?}"); + } + } +} + +fn monitor_advanced_color_enabled(monitor: HMONITOR) -> Option { + if monitor.is_null() { + return None; + } + + let device_name = monitor_device_name(monitor)?; + for path in active_display_paths()? { + let Some(source_name) = display_source_name(&path) else { + continue; + }; + if source_name.viewGdiDeviceName != device_name { + continue; + } + + return display_advanced_color_enabled(&path); + } + + None +} + +fn monitor_device_name(monitor: HMONITOR) -> Option<[u16; 32]> { + let mut monitor_info: MONITORINFOEXW = unsafe { mem::zeroed() }; + monitor_info.cbSize = mem::size_of::() as DWORD; + + let result = + unsafe { GetMonitorInfoW(monitor, &mut monitor_info as *mut _ as *mut MONITORINFO) }; + if result == 0 { + None + } else { + Some(monitor_info.szDevice) + } +} + +fn active_display_paths() -> Option> { + for _ in 0..3 { + let mut path_count = 0; + let mut mode_count = 0; + let buffer_status = unsafe { + GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut path_count, &mut mode_count) + }; + if buffer_status != ERROR_SUCCESS as LONG { + return None; + } + + let mut paths = + vec![unsafe { mem::zeroed::() }; path_count as usize]; + let mut modes = + vec![unsafe { mem::zeroed::() }; mode_count as usize]; + let query_status = unsafe { + QueryDisplayConfig( + QDC_ONLY_ACTIVE_PATHS, + &mut path_count, + paths.as_mut_ptr(), + &mut mode_count, + modes.as_mut_ptr(), + ptr::null_mut(), + ) + }; + + if query_status == ERROR_SUCCESS as LONG { + paths.truncate(path_count as usize); + return Some(paths); + } + if query_status != ERROR_INSUFFICIENT_BUFFER as LONG { + return None; + } + } + + None +} + +fn display_source_name(path: &DISPLAYCONFIG_PATH_INFO) -> Option { + let mut source_name: DISPLAYCONFIG_SOURCE_DEVICE_NAME = unsafe { mem::zeroed() }; + source_name.header = DISPLAYCONFIG_DEVICE_INFO_HEADER { + _type: DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, + size: mem::size_of::() as u32, + adapterId: path.sourceInfo.adapterId, + id: path.sourceInfo.id, + }; + + let status = unsafe { DisplayConfigGetDeviceInfo(&mut source_name.header) }; + if status == ERROR_SUCCESS as LONG { + Some(source_name) + } else { + None + } +} + +fn display_advanced_color_enabled(path: &DISPLAYCONFIG_PATH_INFO) -> Option { + let mut color_info: DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO = unsafe { mem::zeroed() }; + color_info.header = DISPLAYCONFIG_DEVICE_INFO_HEADER { + _type: DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, + size: mem::size_of::() as u32, + adapterId: path.targetInfo.adapterId, + id: path.targetInfo.id, + }; + + let status = unsafe { DisplayConfigGetDeviceInfo(&mut color_info.header) }; + if status == ERROR_SUCCESS as LONG { + Some(color_info.advancedColorEnabled() != 0) + } else { + None + } +} + fn create_event_thread( mut mpv_event_client: Mpv, observe_property_receiver: Receiver, From c36c9d6a9fa726919d276c4bbfd33f29cacd6a2a Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 16 Jun 2026 11:34:43 +0300 Subject: [PATCH 06/11] Gate RTX HDR output by display and setting --- .../stremio_player/communication.rs | 6 +- .../stremio_player/communication_tests.rs | 16 ++++ src/stremio_app/stremio_player/player.rs | 86 ++++++++++++------- 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/stremio_app/stremio_player/communication.rs b/src/stremio_app/stremio_player/communication.rs index 692d288..b11fd38 100644 --- a/src/stremio_app/stremio_player/communication.rs +++ b/src/stremio_app/stremio_player/communication.rs @@ -142,6 +142,7 @@ pub enum InMsgFn { MpvSetProp, MpvCommand, MpvObserveProp, + SetGpuVideoProcessing, } stringable!(InMsgFn); // Bool @@ -191,7 +192,7 @@ stringable!(FpProp); pub enum StrProp { FfmpegVersion, Hwdec, - InputDefaultBindings, + InputDefaultBindings, InputVoKeyboard, Metadata, MpvVersion, @@ -202,7 +203,7 @@ pub enum StrProp { SubBorderColor, SubColor, TrackList, - Vf, + Vf, VideoParams, Vo, } @@ -275,6 +276,7 @@ pub enum InMsgArgs { StProp(PropKey, PropVal), Cmd(CmdVal), ObProp(PropKey), + Flag(bool), } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] diff --git a/src/stremio_app/stremio_player/communication_tests.rs b/src/stremio_app/stremio_player/communication_tests.rs index 85daad0..b38757d 100644 --- a/src/stremio_app/stremio_player/communication_tests.rs +++ b/src/stremio_app/stremio_player/communication_tests.rs @@ -139,6 +139,22 @@ fn set_propr_tokens() { ); } +#[test] +fn set_gpu_video_processing_tokens() { + assert_tokens( + &InMsg(InMsgFn::SetGpuVideoProcessing, InMsgArgs::Flag(true)), + &[ + Token::TupleStruct { + name: "InMsg", + len: 2, + }, + Token::Str("set-gpu-video-processing"), + Token::Bool(true), + Token::TupleStructEnd, + ], + ); +} + #[test] fn command_stop_tokens() { assert_tokens( diff --git a/src/stremio_app/stremio_player/player.rs b/src/stremio_app/stremio_player/player.rs index a4a2178..04c9d07 100644 --- a/src/stremio_app/stremio_player/player.rs +++ b/src/stremio_app/stremio_player/player.rs @@ -5,7 +5,10 @@ use libmpv2::{events::Event, Format, Mpv, SetData}; use native_windows_gui::{self as nwg, PartialUi}; use std::{ mem, ptr, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, thread::{self, JoinHandle}, time::Duration, }; @@ -81,13 +84,6 @@ fn with_gpu_next_fallback(vo: String) -> String { enum DisplayOutputMode { Hdr, Sdr, - Auto, -} - -#[derive(Clone, Copy, Eq, PartialEq)] -struct DisplayOutputState { - monitor: isize, - mode: DisplayOutputMode, } #[derive(Default)] @@ -124,9 +120,19 @@ impl PartialUi for Player { observe_property_receiver, rpc_response_sender, ); - let _display_thread = - create_display_output_thread(Arc::clone(&mpv), window_handle as isize); - let _message_thread = create_message_thread(mpv, observe_property_sender, in_msg_receiver); + let gpu_video_processing = Arc::new(AtomicBool::new(false)); + let _display_thread = create_display_output_thread( + Arc::clone(&mpv), + window_handle as isize, + Arc::clone(&gpu_video_processing), + ); + let _message_thread = create_message_thread( + mpv, + window_handle as isize, + gpu_video_processing, + observe_property_sender, + in_msg_receiver, + ); // @TODO implement a mechanism to stop threads on `Player` drop if needed Ok(()) @@ -175,38 +181,44 @@ fn create_mpv(window_handle: HWND) -> Mpv { mpv.expect("cannot build MPV") } -fn create_display_output_thread(mpv: Arc, window_handle: isize) -> JoinHandle<()> { +fn create_display_output_thread( + mpv: Arc, + window_handle: isize, + gpu_video_processing: Arc, +) -> JoinHandle<()> { thread::spawn(move || { let mut last_state = None; loop { - let state = current_display_output_state(window_handle as HWND); - if last_state != Some(state) { - apply_display_output_mode(&mpv, state.mode); - last_state = Some(state); + let mode = current_display_output_mode(window_handle as HWND); + let gpu = gpu_video_processing.load(Ordering::Relaxed); + if last_state != Some((mode, gpu)) { + apply_display_output_mode(&mpv, mode, gpu); + last_state = Some((mode, gpu)); } thread::sleep(Duration::from_millis(500)); } }) } -fn current_display_output_state(window_handle: HWND) -> DisplayOutputState { +fn current_display_output_mode(window_handle: HWND) -> DisplayOutputMode { let monitor = unsafe { MonitorFromWindow(window_handle, MONITOR_DEFAULTTONEAREST) }; - let mode = match monitor_advanced_color_enabled(monitor) { - Some(true) => DisplayOutputMode::Hdr, + match monitor_advanced_color_enabled(monitor) { Some(false) => DisplayOutputMode::Sdr, - None => DisplayOutputMode::Auto, - }; - - DisplayOutputState { - monitor: monitor as isize, - mode, + _ => DisplayOutputMode::Hdr, } } -fn apply_display_output_mode(mpv: &Mpv, mode: DisplayOutputMode) { - let properties = match mode { - DisplayOutputMode::Hdr | DisplayOutputMode::Auto => [ +fn apply_display_output_mode(mpv: &Mpv, mode: DisplayOutputMode, gpu_video_processing: bool) { + // Target colorspace follows the display so native HDR still passes through; the RTX + // HDR filter is the opt-in part and only makes sense on an HDR display. + let vf = if gpu_video_processing && mode == DisplayOutputMode::Hdr { + "d3d11vpp=scaling-mode=nvidia:format=x2bgr10:nvidia-true-hdr" + } else { + "" + }; + let color = match mode { + DisplayOutputMode::Hdr => [ ("d3d11-output-csp", "auto"), ("target-colorspace-hint", "auto"), ("target-trc", "auto"), @@ -220,7 +232,7 @@ fn apply_display_output_mode(mpv: &Mpv, mode: DisplayOutputMode) { ], }; - for (name, value) in properties { + for (name, value) in std::iter::once(("vf", vf)).chain(color) { if let Err(error) = mpv.set_property(name, value) { eprintln!("mpv: cannot set {name}={value}: {error:?}"); } @@ -392,6 +404,8 @@ fn create_event_thread( fn create_message_thread( mpv: Arc, + window_handle: isize, + gpu_video_processing: Arc, observe_property_sender: Sender, in_msg_receiver: Receiver, ) -> JoinHandle<()> { @@ -451,12 +465,24 @@ fn create_message_thread( set_property(name, value, &mpv); } InMsg(InMsgFn::MpvSetProp, InMsgArgs::StProp(name, PropVal::Str(value))) => { - let value = if name.to_string() == "vo" { + let is_vo = name.to_string() == "vo"; + let value = if is_vo { with_gpu_next_fallback(value) } else { value }; set_property(name, value, &mpv); + // vo reinit reverts color props to defaults; re-assert for the current display. + if is_vo { + apply_display_output_mode( + &mpv, + current_display_output_mode(window_handle as HWND), + gpu_video_processing.load(Ordering::Relaxed), + ); + } + } + InMsg(InMsgFn::SetGpuVideoProcessing, InMsgArgs::Flag(enabled)) => { + gpu_video_processing.store(enabled, Ordering::Relaxed); } InMsg(InMsgFn::MpvCommand, InMsgArgs::Cmd(cmd)) => { send_command(cmd); From ea22f21af73ee2219b5f7885f3643b044fbf0a47 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Thu, 18 Jun 2026 13:54:30 +0300 Subject: [PATCH 07/11] Fix GPU video processing output --- .../stremio_player/communication.rs | 8 +- .../stremio_player/communication_tests.rs | 24 ++-- src/stremio_app/stremio_player/player.rs | 127 ++++++++++++++---- 3 files changed, 115 insertions(+), 44 deletions(-) diff --git a/src/stremio_app/stremio_player/communication.rs b/src/stremio_app/stremio_player/communication.rs index b11fd38..78a33a4 100644 --- a/src/stremio_app/stremio_player/communication.rs +++ b/src/stremio_app/stremio_player/communication.rs @@ -139,10 +139,10 @@ macro_rules! stringable { #[serde(try_from = "String", into = "String")] #[display(style = "kebab-case")] pub enum InMsgFn { - MpvSetProp, - MpvCommand, - MpvObserveProp, - SetGpuVideoProcessing, + MpvSetProp, + MpvCommand, + MpvObserveProp, + MpvSetGpuVideoProcessing, } stringable!(InMsgFn); // Bool diff --git a/src/stremio_app/stremio_player/communication_tests.rs b/src/stremio_app/stremio_player/communication_tests.rs index b38757d..be31492 100644 --- a/src/stremio_app/stremio_player/communication_tests.rs +++ b/src/stremio_app/stremio_player/communication_tests.rs @@ -140,18 +140,18 @@ fn set_propr_tokens() { } #[test] -fn set_gpu_video_processing_tokens() { - assert_tokens( - &InMsg(InMsgFn::SetGpuVideoProcessing, InMsgArgs::Flag(true)), - &[ - Token::TupleStruct { - name: "InMsg", - len: 2, - }, - Token::Str("set-gpu-video-processing"), - Token::Bool(true), - Token::TupleStructEnd, - ], +fn set_gpu_video_processing_tokens() { + assert_tokens( + &InMsg(InMsgFn::MpvSetGpuVideoProcessing, InMsgArgs::Flag(true)), + &[ + Token::TupleStruct { + name: "InMsg", + len: 2, + }, + Token::Str("mpv-set-gpu-video-processing"), + Token::Bool(true), + Token::TupleStructEnd, + ], ); } diff --git a/src/stremio_app/stremio_player/player.rs b/src/stremio_app/stremio_player/player.rs index 04c9d07..1ef09c2 100644 --- a/src/stremio_app/stremio_player/player.rs +++ b/src/stremio_app/stremio_player/player.rs @@ -19,10 +19,9 @@ use winapi::shared::{ }; use winapi::um::{ wingdi::{ - DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_HEADER, - DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO, - DISPLAYCONFIG_SOURCE_DEVICE_NAME, QDC_ONLY_ACTIVE_PATHS, + DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO, DISPLAYCONFIG_SOURCE_DEVICE_NAME, + QDC_ONLY_ACTIVE_PATHS, }, winnt::LONG, winuser::{ @@ -58,6 +57,18 @@ extern "system" { fn DisplayConfigGetDeviceInfo(request_packet: *mut DISPLAYCONFIG_DEVICE_INFO_HEADER) -> LONG; } +const DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2: u32 = 15; +const DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR: u32 = 2; + +#[repr(C)] +struct DisplayconfigGetAdvancedColorInfo2 { + header: DISPLAYCONFIG_DEVICE_INFO_HEADER, + value: u32, + color_encoding: u32, + bits_per_color_channel: u32, + active_color_mode: u32, +} + fn with_gpu_next_fallback(vo: String) -> String { let mut outputs = vo .split(',') @@ -84,6 +95,13 @@ fn with_gpu_next_fallback(vo: String) -> String { enum DisplayOutputMode { Hdr, Sdr, + Auto, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +struct DisplayOutputState { + mode: DisplayOutputMode, + scale_percent: u32, } #[derive(Default)] @@ -190,35 +208,83 @@ fn create_display_output_thread( let mut last_state = None; loop { - let mode = current_display_output_mode(window_handle as HWND); + let state = current_display_output_state(&mpv, window_handle as HWND); let gpu = gpu_video_processing.load(Ordering::Relaxed); - if last_state != Some((mode, gpu)) { - apply_display_output_mode(&mpv, mode, gpu); - last_state = Some((mode, gpu)); + if last_state != Some((state, gpu)) { + apply_display_output_mode(&mpv, state, gpu); + last_state = Some((state, gpu)); } thread::sleep(Duration::from_millis(500)); } }) } -fn current_display_output_mode(window_handle: HWND) -> DisplayOutputMode { - let monitor = unsafe { MonitorFromWindow(window_handle, MONITOR_DEFAULTTONEAREST) }; - match monitor_advanced_color_enabled(monitor) { - Some(false) => DisplayOutputMode::Sdr, - _ => DisplayOutputMode::Hdr, +fn current_display_output_state(mpv: &Mpv, window_handle: HWND) -> DisplayOutputState { + DisplayOutputState { + mode: current_display_output_mode(window_handle), + scale_percent: current_video_filter_scale(mpv, window_handle), } } -fn apply_display_output_mode(mpv: &Mpv, mode: DisplayOutputMode, gpu_video_processing: bool) { +fn current_display_output_mode(window_handle: HWND) -> DisplayOutputMode { + let monitor = unsafe { MonitorFromWindow(window_handle, MONITOR_DEFAULTTONEAREST) }; + match monitor_hdr_active(monitor) { + Some(true) => DisplayOutputMode::Hdr, + Some(false) => DisplayOutputMode::Sdr, + None => DisplayOutputMode::Auto, + } +} + +fn current_video_filter_scale(mpv: &Mpv, window_handle: HWND) -> u32 { + let Some(video_height) = current_video_height(mpv) else { + return 100; + }; + let Some(display_height) = current_monitor_height(window_handle) else { + return 100; + }; + if video_height <= 0.0 || display_height <= video_height { + return 100; + } + + ((display_height / video_height).min(4.0) * 100.0).round() as u32 +} + +fn current_video_height(mpv: &Mpv) -> Option { + let video_params = mpv.get_property::("video-params").ok()?; + let video_params = serde_json::from_str::(&video_params).ok()?; + video_params.get("h").and_then(serde_json::Value::as_f64) +} + +fn current_monitor_height(window_handle: HWND) -> Option { + let monitor = unsafe { MonitorFromWindow(window_handle, MONITOR_DEFAULTTONEAREST) }; + if monitor.is_null() { + return None; + } + + let mut monitor_info: MONITORINFO = unsafe { mem::zeroed() }; + monitor_info.cbSize = mem::size_of::() as DWORD; + if unsafe { GetMonitorInfoW(monitor, &mut monitor_info) } == 0 { + return None; + } + + Some((monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as f64) +} + +fn apply_display_output_mode(mpv: &Mpv, state: DisplayOutputState, gpu_video_processing: bool) { // Target colorspace follows the display so native HDR still passes through; the RTX // HDR filter is the opt-in part and only makes sense on an HDR display. - let vf = if gpu_video_processing && mode == DisplayOutputMode::Hdr { - "d3d11vpp=scaling-mode=nvidia:format=x2bgr10:nvidia-true-hdr" + let vf = if gpu_video_processing { + let scale = state.scale_percent as f64 / 100.0; + let mut vf = format!("d3d11vpp=scaling-mode=nvidia:scale={scale:.2}"); + if state.mode == DisplayOutputMode::Hdr { + vf.push_str(":format=x2bgr10:nvidia-true-hdr"); + } + vf } else { - "" + String::new() }; - let color = match mode { - DisplayOutputMode::Hdr => [ + let color = match state.mode { + DisplayOutputMode::Hdr | DisplayOutputMode::Auto => [ ("d3d11-output-csp", "auto"), ("target-colorspace-hint", "auto"), ("target-trc", "auto"), @@ -232,14 +298,14 @@ fn apply_display_output_mode(mpv: &Mpv, mode: DisplayOutputMode, gpu_video_proce ], }; - for (name, value) in std::iter::once(("vf", vf)).chain(color) { + for (name, value) in std::iter::once(("vf", vf.as_str())).chain(color) { if let Err(error) = mpv.set_property(name, value) { eprintln!("mpv: cannot set {name}={value}: {error:?}"); } } } -fn monitor_advanced_color_enabled(monitor: HMONITOR) -> Option { +fn monitor_hdr_active(monitor: HMONITOR) -> Option { if monitor.is_null() { return None; } @@ -253,7 +319,7 @@ fn monitor_advanced_color_enabled(monitor: HMONITOR) -> Option { continue; } - return display_advanced_color_enabled(&path); + return display_hdr_active(&path); } None @@ -327,18 +393,18 @@ fn display_source_name(path: &DISPLAYCONFIG_PATH_INFO) -> Option Option { - let mut color_info: DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO = unsafe { mem::zeroed() }; +fn display_hdr_active(path: &DISPLAYCONFIG_PATH_INFO) -> Option { + let mut color_info: DisplayconfigGetAdvancedColorInfo2 = unsafe { mem::zeroed() }; color_info.header = DISPLAYCONFIG_DEVICE_INFO_HEADER { - _type: DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, - size: mem::size_of::() as u32, + _type: DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2, + size: mem::size_of::() as u32, adapterId: path.targetInfo.adapterId, id: path.targetInfo.id, }; let status = unsafe { DisplayConfigGetDeviceInfo(&mut color_info.header) }; if status == ERROR_SUCCESS as LONG { - Some(color_info.advancedColorEnabled() != 0) + Some(color_info.active_color_mode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR) } else { None } @@ -476,13 +542,18 @@ fn create_message_thread( if is_vo { apply_display_output_mode( &mpv, - current_display_output_mode(window_handle as HWND), + current_display_output_state(&mpv, window_handle as HWND), gpu_video_processing.load(Ordering::Relaxed), ); } } - InMsg(InMsgFn::SetGpuVideoProcessing, InMsgArgs::Flag(enabled)) => { + InMsg(InMsgFn::MpvSetGpuVideoProcessing, InMsgArgs::Flag(enabled)) => { gpu_video_processing.store(enabled, Ordering::Relaxed); + apply_display_output_mode( + &mpv, + current_display_output_state(&mpv, window_handle as HWND), + enabled, + ); } InMsg(InMsgFn::MpvCommand, InMsgArgs::Cmd(cmd)) => { send_command(cmd); From 813fe01f755fb894869b2a7f2f579d510c023c01 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Thu, 18 Jun 2026 15:03:06 +0300 Subject: [PATCH 08/11] correctly probe for nvidia capabilities support --- Cargo.toml | 5 + src/stremio_app/gpu_video_processing.rs | 191 ++++++++++++++++++ src/stremio_app/ipc.rs | 8 + src/stremio_app/mod.rs | 1 + .../stremio_player/communication.rs | 4 +- .../stremio_player/communication_tests.rs | 2 +- 6 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/stremio_app/gpu_video_processing.rs diff --git a/Cargo.toml b/Cargo.toml index b41ffcb..6ca4c37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,12 @@ native-windows-gui = { git = "https://github.com/Stremio/native-windows-gui", fe ] } native-windows-derive = "1" winapi = { version = "0.3.9", features = [ + "d3d11", + "d3dcommon", "dwmapi", + "dxgi", + "dxgiformat", + "dxgitype", "libloaderapi", "handleapi", "jobapi2", diff --git a/src/stremio_app/gpu_video_processing.rs b/src/stremio_app/gpu_video_processing.rs new file mode 100644 index 0000000..a1bbf8a --- /dev/null +++ b/src/stremio_app/gpu_video_processing.rs @@ -0,0 +1,191 @@ +use once_cell::sync::Lazy; +use std::{ + mem, + ptr::{self, NonNull}, +}; +use winapi::{ + ctypes::c_void, + shared::{dxgitype::DXGI_RATIONAL, guiddef::GUID, minwindef::UINT, winerror::SUCCEEDED}, + um::{ + d3d11::{ + D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11VideoContext, + ID3D11VideoDevice, ID3D11VideoProcessor, ID3D11VideoProcessorEnumerator, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, D3D11_SDK_VERSION, + D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE, D3D11_VIDEO_PROCESSOR_CONTENT_DESC, + D3D11_VIDEO_USAGE_PLAYBACK_NORMAL, + }, + d3dcommon::D3D_DRIVER_TYPE_HARDWARE, + unknwnbase::IUnknown, + }, + Interface, +}; + +const NVIDIA_PPE_INTERFACE_GUID: GUID = GUID { + Data1: 0xd43ce1b3, + Data2: 0x1f4b, + Data3: 0x48ac, + Data4: [0xba, 0xee, 0xc3, 0xc2, 0x53, 0x75, 0xe6, 0xf7], +}; + +const NVIDIA_TRUE_HDR_INTERFACE_GUID: GUID = GUID { + Data1: 0xfdd62bb4, + Data2: 0x620b, + Data3: 0x4fd7, + Data4: [0x9a, 0xb3, 0x1e, 0x59, 0xd0, 0xd5, 0x44, 0xb3], +}; + +static GPU_VIDEO_PROCESSING_SUPPORTED: Lazy = + Lazy::new(detect_gpu_video_processing_supported); + +#[repr(C)] +struct NvidiaRtxSuperResolutionExtension { + version: u32, + method: u32, + enable: u32, +} + +struct ComPtr { + ptr: NonNull, +} + +impl ComPtr { + unsafe fn from_raw(ptr: *mut T) -> Option { + NonNull::new(ptr).map(|ptr| Self { ptr }) + } + + fn as_ptr(&self) -> *mut T { + self.ptr.as_ptr() + } +} + +impl Drop for ComPtr { + fn drop(&mut self) { + unsafe { + (*(self.ptr.as_ptr() as *mut IUnknown)).Release(); + } + } +} + +pub fn gpu_video_processing_supported() -> bool { + *GPU_VIDEO_PROCESSING_SUPPORTED +} + +fn detect_gpu_video_processing_supported() -> bool { + unsafe { detect_gpu_video_processing_supported_inner() } +} + +unsafe fn detect_gpu_video_processing_supported_inner() -> bool { + let mut device = ptr::null_mut::(); + let mut device_context = ptr::null_mut::(); + if !SUCCEEDED(D3D11CreateDevice( + ptr::null_mut(), + D3D_DRIVER_TYPE_HARDWARE, + ptr::null_mut(), + D3D11_CREATE_DEVICE_VIDEO_SUPPORT as UINT, + ptr::null(), + 0, + D3D11_SDK_VERSION, + &mut device, + ptr::null_mut(), + &mut device_context, + )) { + return false; + } + + let Some(device) = ComPtr::from_raw(device) else { + return false; + }; + let Some(device_context) = ComPtr::from_raw(device_context) else { + return false; + }; + let Some(video_device) = query_interface::(device.as_ptr()) else { + return false; + }; + let Some(video_context) = query_interface::(device_context.as_ptr()) + else { + return false; + }; + + let frame_rate = DXGI_RATIONAL { + Numerator: 60, + Denominator: 1, + }; + let content_desc = D3D11_VIDEO_PROCESSOR_CONTENT_DESC { + InputFrameFormat: D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE, + InputFrameRate: frame_rate, + InputWidth: 1280, + InputHeight: 720, + OutputFrameRate: frame_rate, + OutputWidth: 1920, + OutputHeight: 1080, + Usage: D3D11_VIDEO_USAGE_PLAYBACK_NORMAL, + }; + let mut enumerator = ptr::null_mut::(); + if !SUCCEEDED( + (*video_device.as_ptr()).CreateVideoProcessorEnumerator(&content_desc, &mut enumerator), + ) { + return false; + } + let Some(enumerator) = ComPtr::from_raw(enumerator) else { + return false; + }; + + let mut video_processor = ptr::null_mut::(); + if !SUCCEEDED((*video_device.as_ptr()).CreateVideoProcessor( + enumerator.as_ptr(), + 0, + &mut video_processor, + )) { + return false; + } + let Some(video_processor) = ComPtr::from_raw(video_processor) else { + return false; + }; + + nvidia_rtx_super_resolution_supported(&video_context, &video_processor) + || nvidia_true_hdr_supported(&video_context, &video_processor) +} + +unsafe fn query_interface(source: *mut U) -> Option> { + let mut out = ptr::null_mut::(); + let hr = (*(source as *mut IUnknown)).QueryInterface(&T::uuidof(), &mut out); + if SUCCEEDED(hr) { + ComPtr::from_raw(out as *mut T) + } else { + None + } +} + +unsafe fn nvidia_rtx_super_resolution_supported( + video_context: &ComPtr, + video_processor: &ComPtr, +) -> bool { + let mut extension = NvidiaRtxSuperResolutionExtension { + version: 1, + method: 2, + enable: 1, + }; + + SUCCEEDED((*video_context.as_ptr()).VideoProcessorSetStreamExtension( + video_processor.as_ptr(), + 0, + &NVIDIA_PPE_INTERFACE_GUID, + mem::size_of::() as UINT, + &mut extension as *mut _ as *mut c_void, + )) +} + +unsafe fn nvidia_true_hdr_supported( + video_context: &ComPtr, + video_processor: &ComPtr, +) -> bool { + let mut supported = 0u32; + + SUCCEEDED((*video_context.as_ptr()).VideoProcessorGetStreamExtension( + video_processor.as_ptr(), + 0, + &NVIDIA_TRUE_HDR_INTERFACE_GUID, + mem::size_of_val(&supported) as UINT, + &mut supported as *mut _ as *mut c_void, + )) && supported != 0 +} diff --git a/src/stremio_app/ipc.rs b/src/stremio_app/ipc.rs index 9de343b..328ea1e 100644 --- a/src/stremio_app/ipc.rs +++ b/src/stremio_app/ipc.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use serde_json::{self, json}; use std::cell::RefCell; +use crate::stremio_app::gpu_video_processing; + const VERSION: &str = env!("CARGO_PKG_VERSION"); pub type Channel = RefCell, flume::Receiver)>>; @@ -68,6 +70,12 @@ impl RPCResponse { "".to_string(), VERSION.to_string(), ], + vec![ + "".to_string(), + "gpuVideoProcessingSupported".to_string(), + "".to_string(), + gpu_video_processing::gpu_video_processing_supported().to_string(), + ], ], signals: vec![], methods: vec![vec!["onEvent".to_string(), "".to_string()]], diff --git a/src/stremio_app/mod.rs b/src/stremio_app/mod.rs index d3cb534..edd960e 100644 --- a/src/stremio_app/mod.rs +++ b/src/stremio_app/mod.rs @@ -1,6 +1,7 @@ pub mod app; pub use app::MainWindow; pub mod discord; +pub mod gpu_video_processing; pub mod ipc; pub mod stremio_player; pub mod stremio_server; diff --git a/src/stremio_app/stremio_player/communication.rs b/src/stremio_app/stremio_player/communication.rs index 78a33a4..da9f2b4 100644 --- a/src/stremio_app/stremio_player/communication.rs +++ b/src/stremio_app/stremio_player/communication.rs @@ -138,12 +138,12 @@ macro_rules! stringable { #[derive(Display, FromStr, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[serde(try_from = "String", into = "String")] #[display(style = "kebab-case")] -pub enum InMsgFn { +pub enum InMsgFn { MpvSetProp, MpvCommand, MpvObserveProp, MpvSetGpuVideoProcessing, -} +} stringable!(InMsgFn); // Bool #[derive(Display, FromStr, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] diff --git a/src/stremio_app/stremio_player/communication_tests.rs b/src/stremio_app/stremio_player/communication_tests.rs index be31492..558f5f6 100644 --- a/src/stremio_app/stremio_player/communication_tests.rs +++ b/src/stremio_app/stremio_player/communication_tests.rs @@ -139,7 +139,7 @@ fn set_propr_tokens() { ); } -#[test] +#[test] fn set_gpu_video_processing_tokens() { assert_tokens( &InMsg(InMsgFn::MpvSetGpuVideoProcessing, InMsgArgs::Flag(true)), From 82bccabc3d0fa121cb54bae9e4a9c6534bad13fb Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Thu, 18 Jun 2026 15:08:51 +0300 Subject: [PATCH 09/11] fix(gvp): correct the setting name --- src/stremio_app/gpu_video_processing.rs | 2 ++ src/stremio_app/ipc.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stremio_app/gpu_video_processing.rs b/src/stremio_app/gpu_video_processing.rs index a1bbf8a..057468b 100644 --- a/src/stremio_app/gpu_video_processing.rs +++ b/src/stremio_app/gpu_video_processing.rs @@ -142,6 +142,8 @@ unsafe fn detect_gpu_video_processing_supported_inner() -> bool { return false; }; + // Keep this aligned with mpv/video/filter/vf_d3d11vpp.c; it also supports + // Intel VSR, so this probe can extend when shell enables that scaling mode. nvidia_rtx_super_resolution_supported(&video_context, &video_processor) || nvidia_true_hdr_supported(&video_context, &video_processor) } diff --git a/src/stremio_app/ipc.rs b/src/stremio_app/ipc.rs index 328ea1e..125a9ab 100644 --- a/src/stremio_app/ipc.rs +++ b/src/stremio_app/ipc.rs @@ -72,7 +72,7 @@ impl RPCResponse { ], vec![ "".to_string(), - "gpuVideoProcessingSupported".to_string(), + "gpuVideoProcessing".to_string(), "".to_string(), gpu_video_processing::gpu_video_processing_supported().to_string(), ], From 5b1f341dbd9e1959f824436c70aa7410c159f684 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Fri, 19 Jun 2026 13:57:17 +0300 Subject: [PATCH 10/11] bump: release v5.0.23 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ad6f88..538d7c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1624,7 +1624,7 @@ dependencies = [ [[package]] name = "stremio-shell-ng" -version = "5.0.22" +version = "5.0.23" dependencies = [ "anyhow", "bitflags 2.4.2", diff --git a/Cargo.toml b/Cargo.toml index 6ca4c37..4fac471 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stremio-shell-ng" -version = "5.0.22" +version = "5.0.23" edition = "2018" [dependencies] From 6acbee54e5304d9f42c6b08399de56917abd0bcd Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Tue, 23 Jun 2026 11:18:37 +0300 Subject: [PATCH 11/11] Fix upload script --- upload.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upload.ps1 b/upload.ps1 index 70d870a..8483192 100644 --- a/upload.ps1 +++ b/upload.ps1 @@ -1,4 +1,4 @@ -$tag = $(git describe --abbrev=0) +$tag = $(git describe --tags --abbrev=0) foreach ($installer in (get-item .\StremioSetup*.exe)) { if ($tag.StartsWith("v$($installer.VersionInfo.ProductVersion.Trim())")) {