feat(player): keep the display awake while a stream is playing

Currently the Windows shell never tells the OS not to dim the screen
or sleep, so a long uninterrupted playback can hit a screensaver or
display-off timeout mid-movie.

Observe mpv's `pause` property from the player event thread and call
SetThreadExecutionState with ES_DISPLAY_REQUIRED|ES_SYSTEM_REQUIRED
while playing, dropping back to ES_CONTINUOUS only on pause and on
Shutdown. The state is bound to the event thread's lifetime, so OS
auto-resets it when the shell exits.

Test: cargo fmt --all -- --check, cargo clippy --all -- -D warnings.
This commit is contained in:
Claude 2026-05-10 18:04:44 +00:00
parent bbbe882faf
commit f790985541
No known key found for this signature in database

View file

@ -1,13 +1,15 @@
use crate::stremio_app::ipc; use crate::stremio_app::ipc;
use crate::stremio_app::RPCResponse; use crate::stremio_app::RPCResponse;
use flume::{Receiver, Sender}; use flume::{Receiver, Sender};
use libmpv2::{events::Event, events::EventContext, Format, Mpv, SetData}; use libmpv2::{events::Event, events::EventContext, events::PropertyData, Format, Mpv, SetData};
use native_windows_gui::{self as nwg, PartialUi}; use native_windows_gui::{self as nwg, PartialUi};
use std::{ use std::{
sync::Arc, sync::Arc,
thread::{self, JoinHandle}, thread::{self, JoinHandle},
}; };
use winapi::shared::windef::HWND; use winapi::shared::windef::HWND;
use winapi::um::winbase::SetThreadExecutionState;
use winapi::um::winnt::{ES_CONTINUOUS, ES_DISPLAY_REQUIRED, ES_SYSTEM_REQUIRED};
use crate::stremio_app::stremio_player::{ use crate::stremio_app::stremio_player::{
CmdVal, InMsg, InMsgArgs, InMsgFn, PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse, CmdVal, InMsg, InMsgArgs, InMsgFn, PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
@ -92,6 +94,10 @@ fn create_event_thread(
event_context event_context
.disable_deprecated_events() .disable_deprecated_events()
.expect("failed to disable deprecated MPV events"); .expect("failed to disable deprecated MPV events");
// Shell-owned observer so the display stays awake while playing.
if let Err(error) = event_context.observe_property("pause", Format::Flag, 0) {
eprintln!("failed to observe pause: {error:?}");
}
// -- Event handler loop -- // -- Event handler loop --
@ -115,18 +121,26 @@ fn create_event_thread(
// even if you don't do anything with the events, it is still necessary to empty the event loop // even if you don't do anything with the events, it is still necessary to empty the event loop
let player_response = match event { let player_response = match event {
Event::PropertyChange { name, change, .. } => PlayerResponse( Event::PropertyChange { name, change, .. } => {
if name == "pause" {
if let PropertyData::Flag(paused) = change {
set_sleep_inhibit(!paused);
}
}
PlayerResponse(
"mpv-prop-change", "mpv-prop-change",
PlayerEvent::PropChange(PlayerProprChange::from_name_value( PlayerEvent::PropChange(PlayerProprChange::from_name_value(
name.to_string(), name.to_string(),
change, change,
)), )),
), )
}
Event::EndFile(reason) => PlayerResponse( Event::EndFile(reason) => PlayerResponse(
"mpv-event-ended", "mpv-event-ended",
PlayerEvent::End(PlayerEnded::from_end_reason(reason)), PlayerEvent::End(PlayerEnded::from_end_reason(reason)),
), ),
Event::Shutdown => { Event::Shutdown => {
set_sleep_inhibit(false);
break; break;
} }
_ => continue, _ => continue,
@ -139,6 +153,17 @@ fn create_event_thread(
}) })
} }
fn set_sleep_inhibit(playing: bool) {
let flags = if playing {
ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED
} else {
ES_CONTINUOUS
};
unsafe {
SetThreadExecutionState(flags);
}
}
fn create_message_thread( fn create_message_thread(
mpv: Arc<Mpv>, mpv: Arc<Mpv>,
observe_property_sender: Sender<ObserveProperty>, observe_property_sender: Sender<ObserveProperty>,