Merge branch 'main' of https://github.com/Stremio/stremio-shell-ng into feat/streamurl-cache

This commit is contained in:
Aztup 2026-06-24 17:08:47 +02:00
commit bc22074aee
12 changed files with 623 additions and 28 deletions

2
Cargo.lock generated
View file

@ -1624,7 +1624,7 @@ dependencies = [
[[package]]
name = "stremio-shell-ng"
version = "5.0.22"
version = "5.0.23"
dependencies = [
"anyhow",
"bitflags 2.4.2",

View file

@ -1,6 +1,6 @@
[package]
name = "stremio-shell-ng"
version = "5.0.22"
version = "5.0.23"
edition = "2018"
[dependencies]
@ -14,12 +14,18 @@ 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",
"wincon",
"winuser",
"wingdi",
"namedpipeapi",
] }
webview2 = "0.1.4"

View file

@ -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,
@ -130,7 +133,16 @@ impl MainWindow {
}
}
fn on_init(&self) {
self.webview.endpoint.set(self.webui_url.clone()).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() {

View file

@ -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::<String>();
let web_endpoint = WEB_ENDPOINT.trim_end_matches('/');
format!("{web_endpoint}/#/?streamingServerUrl={streaming_server_url}")
}

View file

@ -0,0 +1,193 @@
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<bool> =
Lazy::new(detect_gpu_video_processing_supported);
#[repr(C)]
struct NvidiaRtxSuperResolutionExtension {
version: u32,
method: u32,
enable: u32,
}
struct ComPtr<T> {
ptr: NonNull<T>,
}
impl<T> ComPtr<T> {
unsafe fn from_raw(ptr: *mut T) -> Option<Self> {
NonNull::new(ptr).map(|ptr| Self { ptr })
}
fn as_ptr(&self) -> *mut T {
self.ptr.as_ptr()
}
}
impl<T> Drop for ComPtr<T> {
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::<ID3D11Device>();
let mut device_context = ptr::null_mut::<ID3D11DeviceContext>();
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::<ID3D11VideoDevice, _>(device.as_ptr()) else {
return false;
};
let Some(video_context) = query_interface::<ID3D11VideoContext, _>(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::<ID3D11VideoProcessorEnumerator>();
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::<ID3D11VideoProcessor>();
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;
};
// 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)
}
unsafe fn query_interface<T: Interface, U>(source: *mut U) -> Option<ComPtr<T>> {
let mut out = ptr::null_mut::<c_void>();
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<ID3D11VideoContext>,
video_processor: &ComPtr<ID3D11VideoProcessor>,
) -> 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::<NvidiaRtxSuperResolutionExtension>() as UINT,
&mut extension as *mut _ as *mut c_void,
))
}
unsafe fn nvidia_true_hdr_supported(
video_context: &ComPtr<ID3D11VideoContext>,
video_processor: &ComPtr<ID3D11VideoProcessor>,
) -> 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
}

View file

@ -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<Option<(flume::Sender<String>, flume::Receiver<String>)>>;
@ -68,6 +70,12 @@ impl RPCResponse {
"".to_string(),
VERSION.to_string(),
],
vec![
"".to_string(),
"gpuVideoProcessing".to_string(),
"".to_string(),
gpu_video_processing::gpu_video_processing_supported().to_string(),
],
],
signals: vec![],
methods: vec![vec!["onEvent".to_string(), "".to_string()]],

View file

@ -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;

View file

@ -141,11 +141,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 {
MpvSetProp,
MpvCommand,
MpvObserveProp,
}
pub enum InMsgFn {
MpvSetProp,
MpvCommand,
MpvObserveProp,
MpvSetGpuVideoProcessing,
}
stringable!(InMsgFn);
// Bool
#[derive(Display, FromStr, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
@ -194,7 +195,7 @@ stringable!(FpProp);
pub enum StrProp {
FfmpegVersion,
Hwdec,
InputDefaultBindings,
InputDefaultBindings,
InputVoKeyboard,
Metadata,
MpvVersion,
@ -278,6 +279,7 @@ pub enum InMsgArgs {
StProp(PropKey, PropVal),
Cmd(CmdVal),
ObProp(PropKey),
Flag(bool),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]

View file

@ -165,6 +165,22 @@ fn set_propr_tokens() {
);
}
#[test]
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,
],
);
}
#[test]
fn command_stop_tokens() {
assert_tokens(

View file

@ -4,10 +4,30 @@ use flume::{Receiver, Sender};
use libmpv2::{events::Event, Format, Mpv, SetData};
use native_windows_gui::{self as nwg, PartialUi};
use std::{
sync::Arc,
mem, ptr,
sync::{
atomic::{AtomicBool, Ordering},
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_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_HEADER,
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 +39,71 @@ 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;
}
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(',')
.filter(|output| !output.is_empty())
.map(String::from)
.collect::<Vec<String>>();
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(Clone, Copy, Eq, PartialEq)]
enum DisplayOutputMode {
Hdr,
Sdr,
Auto,
}
#[derive(Clone, Copy, Eq, PartialEq)]
struct DisplayOutputState {
mode: DisplayOutputMode,
scale_percent: u32,
}
#[derive(Default)]
pub struct Player {
pub channel: ipc::Channel,
@ -53,7 +138,19 @@ impl PartialUi for Player {
observe_property_receiver,
rpc_response_sender,
);
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(())
@ -97,12 +194,240 @@ fn create_mpv(window_handle: HWND) -> Mpv {
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 [
("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"),
("scale", "spline36"),
("cscale", "spline36"),
] {
if let Err(error) = initializer.set_property(name, value) {
eprintln!("mpv: cannot set {name}={value}: {error:?}");
}
}
Ok(())
});
mpv.expect("cannot build MPV")
}
fn create_display_output_thread(
mpv: Arc<Mpv>,
window_handle: isize,
gpu_video_processing: Arc<AtomicBool>,
) -> JoinHandle<()> {
thread::spawn(move || {
let mut last_state = None;
loop {
let state = current_display_output_state(&mpv, window_handle as HWND);
let gpu = gpu_video_processing.load(Ordering::Relaxed);
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_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 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<f64> {
let video_params = mpv.get_property::<String>("video-params").ok()?;
let video_params = serde_json::from_str::<serde_json::Value>(&video_params).ok()?;
video_params.get("h").and_then(serde_json::Value::as_f64)
}
fn current_monitor_height(window_handle: HWND) -> Option<f64> {
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::<MONITORINFO>() 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 {
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 state.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 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_hdr_active(monitor: HMONITOR) -> Option<bool> {
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_hdr_active(&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::<MONITORINFOEXW>() 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<Vec<DISPLAYCONFIG_PATH_INFO>> {
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::<DISPLAYCONFIG_PATH_INFO>() }; path_count as usize];
let mut modes =
vec![unsafe { mem::zeroed::<DISPLAYCONFIG_MODE_INFO>() }; 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<DISPLAYCONFIG_SOURCE_DEVICE_NAME> {
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::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() 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_hdr_active(path: &DISPLAYCONFIG_PATH_INFO) -> Option<bool> {
let mut color_info: DisplayconfigGetAdvancedColorInfo2 = unsafe { mem::zeroed() };
color_info.header = DISPLAYCONFIG_DEVICE_INFO_HEADER {
_type: DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2,
size: mem::size_of::<DisplayconfigGetAdvancedColorInfo2>() 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.active_color_mode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR)
} else {
None
}
}
fn create_event_thread(
mut mpv_event_client: Mpv,
observe_property_receiver: Receiver<ObserveProperty>,
@ -163,6 +488,8 @@ fn create_event_thread(
fn create_message_thread(
mpv: Arc<Mpv>,
window_handle: isize,
gpu_video_processing: Arc<AtomicBool>,
observe_property_sender: Sender<ObserveProperty>,
in_msg_receiver: Receiver<String>,
) -> JoinHandle<()> {
@ -228,17 +555,29 @@ 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 mut value = value;
if !value.is_empty() && !value.ends_with(',') {
value.push(',');
}
value.push_str("gpu-next,");
value
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_state(&mpv, window_handle as HWND),
gpu_video_processing.load(Ordering::Relaxed),
);
}
}
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);

View file

@ -27,12 +27,17 @@ pub struct StremioServer {
parent: nwg::ControlHandle,
crash_notice: nwg::Notice,
logs: Arc<Mutex<String>>,
server_url: Arc<Mutex<Option<String>>>,
}
impl StremioServer {
pub fn start(&self) {
pub fn server_url(&self) -> Option<String> {
self.server_url.lock().ok().and_then(|url| url.clone())
}
pub fn start(&self) -> Option<String> {
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();
}
}
}

View file

@ -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())")) {