From 4c87d13eb8d42a49ce3e187e1ef0bbc7a1eaa290 Mon Sep 17 00:00:00 2001 From: RabbyDevs <67389402+RabbyDevs@users.noreply.github.com> Date: Fri, 9 May 2025 01:20:07 +0300 Subject: [PATCH] some fixing but did try to look into the long stopping it takes... and honestly too hard to fix! current black screen time is acceptable enough. did lower memory usage --- iced_video_player/Cargo.lock | 30 ++--- iced_video_player/Cargo.toml | 6 + iced_video_player/src/video.rs | 161 ++------------------------ iced_video_player/src/video_player.rs | 39 +------ src/main.rs | 21 ++-- 5 files changed, 43 insertions(+), 214 deletions(-) diff --git a/iced_video_player/Cargo.lock b/iced_video_player/Cargo.lock index 8003f4b..083c281 100644 --- a/iced_video_player/Cargo.lock +++ b/iced_video_player/Cargo.lock @@ -526,6 +526,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -867,7 +876,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -1244,12 +1252,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.4.0" @@ -1392,6 +1394,7 @@ dependencies = [ name = "iced_video_player" version = "0.6.0" dependencies = [ + "crossbeam-channel", "glib", "gstreamer", "gstreamer-app", @@ -1399,6 +1402,7 @@ dependencies = [ "iced", "iced_wgpu", "log", + "parking_lot", "thiserror 2.0.12", "url", "yuvutils-rs", @@ -2078,16 +2082,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "num_enum" version = "0.7.3" @@ -2457,7 +2451,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix", "tracing", diff --git a/iced_video_player/Cargo.toml b/iced_video_player/Cargo.toml index 8034dc4..9fc00ab 100644 --- a/iced_video_player/Cargo.toml +++ b/iced_video_player/Cargo.toml @@ -46,3 +46,9 @@ buildInputs = ["libxkbcommon", "gst_all_1.gstreamer", "gst_all_1.gstreamermm", " rustc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"] targets = ["wasm32-unknown-unknown"] + +[profile.release] +strip = true # Automatically strip symbols from the binary. +lto = true # Link-time optimization. +opt-level = 3 # Optimize for speed. +codegen-units = 1 # Maximum size reduction optimizations. \ No newline at end of file diff --git a/iced_video_player/src/video.rs b/iced_video_player/src/video.rs index 9099b66..b974c2e 100644 --- a/iced_video_player/src/video.rs +++ b/iced_video_player/src/video.rs @@ -7,7 +7,7 @@ use std::num::NonZeroU8; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, RwLock}; -use std::time::{Duration, Instant}; +use std::time::Duration; /// Position in the media. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -69,19 +69,12 @@ pub(crate) struct Internal { pub(crate) framerate: f64, pub(crate) duration: Duration, pub(crate) speed: f64, - pub(crate) sync_av: bool, pub(crate) frame: Arc>, pub(crate) upload_frame: Arc, - pub(crate) last_frame_time: Arc>, pub(crate) looping: bool, pub(crate) is_eos: bool, pub(crate) restart_stream: bool, - pub(crate) sync_av_avg: u64, - pub(crate) sync_av_counter: u64, - - pub(crate) subtitle_text: Arc>>, - pub(crate) upload_text: Arc, } impl Internal { @@ -118,9 +111,6 @@ impl Internal { )?, }; - *self.subtitle_text.lock().expect("lock subtitle_text") = None; - self.upload_text.store(true, Ordering::SeqCst); - Ok(()) } @@ -176,19 +166,6 @@ impl Internal { pub(crate) fn paused(&self) -> bool { self.source.state(gst::ClockTime::ZERO).1 == gst::State::Paused } - - /// Syncs audio with video when there is (inevitably) latency presenting the frame. - pub(crate) fn set_av_offset(&mut self, offset: Duration) { - if self.sync_av { - self.sync_av_counter += 1; - self.sync_av_avg = self.sync_av_avg * (self.sync_av_counter - 1) / self.sync_av_counter - + offset.as_nanos() as u64 / self.sync_av_counter; - if self.sync_av_counter % 128 == 0 { - self.source - .set_property("av-offset", -(self.sync_av_avg as i64)); - } - } - } } /// A multimedia video loaded from a URI (e.g., a local file path or HTTP stream). @@ -228,7 +205,7 @@ impl Video { pub fn new(uri: &url::Url) -> Result { gst::init()?; - let pipeline = format!("playbin uri=\"{}\" text-sink=\"appsink name=iced_text sync=true drop=true\" video-sink=\"videoscale ! videoconvert ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"", &uri.as_str()); + let pipeline = format!("playbin uri=\"{}\" video-sink=\"videoscale ! videoconvert ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"", &uri.as_str()); let pipeline = gst::parse::launch(pipeline.as_ref())? .downcast::() .map_err(|_| Error::Cast)?; @@ -244,14 +221,10 @@ impl Video { let video_sink = bin.by_name("iced_video").unwrap(); let video_sink = video_sink.downcast::().unwrap(); - let text_sink: gst::Element = pipeline.property("text-sink"); - let text_sink = text_sink.downcast::().unwrap(); - Self::from_gst_pipeline( // uri, pipeline, - video_sink, - Some(text_sink) + video_sink ) } @@ -267,39 +240,27 @@ impl Video { // uri: url::Url, pipeline: gst::Pipeline, video_sink: gst_app::AppSink, - text_sink: Option, ) -> Result { gst::init()?; static NEXT_ID: AtomicU64 = AtomicU64::new(0); let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); - // We need to ensure we stop the pipeline if we hit an error, - // or else there may be audio left playing in the background. - macro_rules! cleanup { - ($expr:expr) => { - $expr.map_err(|e| { - let _ = pipeline.set_state(gst::State::Null); - e - }) - }; - } - let pad = video_sink.pads().first().cloned().unwrap(); - cleanup!(pipeline.set_state(gst::State::Playing))?; + pipeline.set_state(gst::State::Playing)?; // wait for up to 5 seconds until the decoder gets the source capabilities - cleanup!(pipeline.state(gst::ClockTime::from_seconds(5)).0)?; + pipeline.state(gst::ClockTime::from_seconds(5)).0?; // extract resolution and framerate // TODO(jazzfool): maybe we want to extract some other information too? - let caps = cleanup!(pad.current_caps().ok_or(Error::Caps))?; - let s = cleanup!(caps.structure(0).ok_or(Error::Caps))?; - let width = cleanup!(s.get::("width").map_err(|_| Error::Caps))?; - let height = cleanup!(s.get::("height").map_err(|_| Error::Caps))?; + let caps = pad.current_caps().ok_or(Error::Caps)?; + let s = caps.structure(0).ok_or(Error::Caps)?; + let width = s.get::("width").map_err(|_| Error::Caps)?; + let height = s.get::("height").map_err(|_| Error::Caps)?; // resolution should be mod4 let width = ((width + 4 - 1) / 4) * 4; - let framerate = cleanup!(s.get::("framerate").map_err(|_| Error::Caps))?; + let framerate = s.get::("framerate").map_err(|_| Error::Caps)?; let framerate = framerate.numer() as f64 / framerate.denom() as f64; if framerate.is_nan() @@ -318,28 +279,18 @@ impl Video { .unwrap_or(0), ); - let sync_av = pipeline.has_property("av-offset", None); - // NV12 = 12bpp let frame = Arc::new(Mutex::new(Frame::empty())); let upload_frame = Arc::new(AtomicBool::new(false)); let alive = Arc::new(AtomicBool::new(true)); - let last_frame_time = Arc::new(Mutex::new(Instant::now())); let frame_ref = Arc::clone(&frame); let upload_frame_ref = Arc::clone(&upload_frame); let alive_ref = Arc::clone(&alive); - let last_frame_time_ref = Arc::clone(&last_frame_time); - - let subtitle_text = Arc::new(Mutex::new(None)); - let upload_text = Arc::new(AtomicBool::new(false)); - let subtitle_text_ref = Arc::clone(&subtitle_text); - let upload_text_ref = Arc::clone(&upload_text); let pipeline_ref = pipeline.clone(); let worker = std::thread::spawn(move || { - let mut clear_subtitles_at = None; while alive_ref.load(Ordering::Acquire) { if let Err(gst::FlowError::Error) = (|| -> Result<(), gst::FlowError> { @@ -353,15 +304,6 @@ impl Video { .try_pull_sample(gst::ClockTime::from_mseconds(16)) .ok_or(gst::FlowError::Eos)? }; - - *last_frame_time_ref - .lock() - .map_err(|_| gst::FlowError::Error)? = Instant::now(); - - let frame_segment = sample.segment().cloned().ok_or(gst::FlowError::Error)?; - let buffer = sample.buffer().ok_or(gst::FlowError::Error)?; - let frame_pts = buffer.pts().ok_or(gst::FlowError::Error)?; - let frame_duration = buffer.duration().ok_or(gst::FlowError::Error)?; { let mut frame_guard = frame_ref.lock().map_err(|_| gst::FlowError::Error)?; @@ -370,55 +312,6 @@ impl Video { upload_frame_ref.swap(true, Ordering::SeqCst); - if let Some(at) = clear_subtitles_at { - if frame_pts >= at { - *subtitle_text_ref - .lock() - .map_err(|_| gst::FlowError::Error)? = None; - upload_text_ref.store(true, Ordering::SeqCst); - clear_subtitles_at = None; - } - } - - let text = text_sink - .as_ref() - .and_then(|sink| sink.try_pull_sample(gst::ClockTime::from_seconds(0))); - if let Some(text) = text { - let text_segment = text.segment().ok_or(gst::FlowError::Error)?; - let text = text.buffer().ok_or(gst::FlowError::Error)?; - let text_pts = text.pts().ok_or(gst::FlowError::Error)?; - let text_duration = text.duration().ok_or(gst::FlowError::Error)?; - - let frame_running_time = frame_segment.to_running_time(frame_pts).value(); - let frame_running_time_end = frame_segment - .to_running_time(frame_pts + frame_duration) - .value(); - - let text_running_time = text_segment.to_running_time(text_pts).value(); - let text_running_time_end = text_segment - .to_running_time(text_pts + text_duration) - .value(); - - // see gst-plugins-base/ext/pango/gstbasetextoverlay.c (gst_base_text_overlay_video_chain) - // as an example of how to correctly synchronize the text+video segments - if text_running_time_end > frame_running_time - && frame_running_time_end > text_running_time - { - let duration = text.duration().unwrap_or(gst::ClockTime::ZERO); - let map = text.map_readable().map_err(|_| gst::FlowError::Error)?; - - let text = std::str::from_utf8(map.as_slice()) - .map_err(|_| gst::FlowError::Error)? - .to_string(); - *subtitle_text_ref - .lock() - .map_err(|_| gst::FlowError::Error)? = Some(text); - upload_text_ref.store(true, Ordering::SeqCst); - - clear_subtitles_at = Some(text_pts + duration); - } - } - Ok(()) })() { log::error!("error pulling frame"); @@ -440,19 +333,12 @@ impl Video { framerate, duration, speed: 1.0, - sync_av, frame, upload_frame, - last_frame_time, looping: false, is_eos: false, restart_stream: false, - sync_av_avg: 0, - sync_av_counter: 0, - - subtitle_text, - upload_text, }))) } @@ -478,30 +364,6 @@ impl Video { self.read().framerate } - /// Set the volume multiplier of the audio. - /// `0.0` = 0% volume, `1.0` = 100% volume. - /// - /// This uses a linear scale, for example `0.5` is perceived as half as loud. - pub fn set_volume(&mut self, volume: f64) { - self.get_mut().source.set_property("volume", volume); - self.set_muted(self.muted()); // for some reason gstreamer unmutes when changing volume? - } - - /// Get the volume multiplier of the audio. - pub fn volume(&self) -> f64 { - self.read().source.property("volume") - } - - /// Set if the audio is muted or not, without changing the volume. - pub fn set_muted(&mut self, muted: bool) { - self.get_mut().source.set_property("mute", muted); - } - - /// Get if the audio is muted or not. - pub fn muted(&self) -> bool { - self.read().source.property("mute") - } - /// Get if the stream ended or not. pub fn eos(&self) -> bool { self.read().is_eos @@ -600,11 +462,9 @@ impl Video { let downscale = u8::from(downscale) as u32; let paused = self.paused(); - let muted = self.muted(); let pos = self.position(); self.set_paused(false); - self.set_muted(true); let out = { let inner = self.read(); @@ -631,7 +491,6 @@ impl Video { }; self.set_paused(paused); - self.set_muted(muted); self.seek(pos, true)?; out diff --git a/iced_video_player/src/video_player.rs b/iced_video_player/src/video_player.rs index 8cccb7d..ed54f77 100644 --- a/iced_video_player/src/video_player.rs +++ b/iced_video_player/src/video_player.rs @@ -6,7 +6,7 @@ use iced::{ }; use iced_wgpu::primitive::Renderer as PrimitiveRenderer; use log::error; -use std::{marker::PhantomData, sync::atomic::Ordering}; +use std::{marker::PhantomData, sync::atomic::Ordering, time::Duration}; use std::{sync::Arc, time::Instant}; /// Video player widget which displays the current frame of a [`Video`](crate::Video). @@ -20,7 +20,6 @@ where height: iced::Length, on_end_of_stream: Option, on_new_frame: Option, - on_subtitle_text: Option) -> Message + 'a>>, on_error: Option Message + 'a>>, _phantom: PhantomData<(Theme, Renderer)>, } @@ -38,7 +37,6 @@ where height: iced::Length::Shrink, on_end_of_stream: None, on_new_frame: None, - on_subtitle_text: None, on_error: None, _phantom: Default::default(), } @@ -84,17 +82,6 @@ where } } - /// Message to send when the video receives a new frame. - pub fn on_subtitle_text(self, on_subtitle_text: F) -> Self - where - F: 'static + Fn(Option) -> Message, - { - VideoPlayer { - on_subtitle_text: Some(Box::new(on_subtitle_text)), - ..self - } - } - /// Message to send when the video playback encounters an error. pub fn on_error(self, on_error: F) -> Self where @@ -156,7 +143,7 @@ where _cursor: advanced::mouse::Cursor, _viewport: &iced::Rectangle, ) { - let mut inner = self.video.write(); + let inner = self.video.write(); // bounds based on `Image::draw` let image_size = iced::Size::new(inner.width as f32, inner.height as f32); @@ -183,15 +170,6 @@ where let upload_frame = inner.upload_frame.swap(false, Ordering::SeqCst); - if upload_frame { - let last_frame_time = inner - .last_frame_time - .lock() - .map(|time| *time) - .unwrap_or_else(|_| Instant::now()); - inner.set_av_offset(Instant::now() - last_frame_time); - } - let render = |renderer: &mut Renderer| { renderer.draw_primitive( drawing_bounds, @@ -276,17 +254,12 @@ where } } - if let Some(on_subtitle_text) = &self.on_subtitle_text { - if inner.upload_text.swap(false, Ordering::SeqCst) { - if let Ok(text) = inner.subtitle_text.try_lock() { - shell.publish(on_subtitle_text(text.clone())); - } - } - } - shell.request_redraw(); } else { - shell.request_redraw(); + let frame_time = 1.0 / inner.framerate; + shell.request_redraw_at(iced::window::RedrawRequest::At( + Instant::now() + Duration::from_secs_f64(frame_time), + )); } } } diff --git a/src/main.rs b/src/main.rs index 69657f3..b2aad26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,7 @@ pub fn main() -> iced::Result { let settings = Settings { decorations: false, icon: Some(icon::from_rgba(rgba_vec, icon_image.width(), icon_image.height()).unwrap()), - size: Size::new(2000.0, 1000.0), + size: Size::new(0.0, 0.0), maximized: false, fullscreen: false, position: window::Position::Centered, @@ -63,7 +63,7 @@ pub fn main() -> iced::Result { .window(settings) .font(segoe_assets) .font(main_font) - .window_size((1280.0, 760.0)) + .window_size((1280.0, 720.0)) .run() } @@ -108,14 +108,14 @@ struct State { installed_games: Vec, installed_game_servers: Vec, db_software_installed: bool, - backgrounds: HashMap, + background: Option, splash_images: HashMap, icon_images: HashMap } impl State { fn get_background_element(&self) -> Option> { - if let Some(background) = self.backgrounds.get(&self.selected_game) { + if let Some(background) = &self.background { match background { LauncherBackground::Video(video) => Some(VideoPlayer::new(video).into()), LauncherBackground::Image(img) => Some(image(image::Handle::from_rgba(img.width(), img.height(), img.to_rgba8().into_raw())).into()), @@ -187,7 +187,7 @@ impl State { installed_games: saved_state.installed_games, installed_game_servers: saved_state.installed_game_servers, db_software_installed: saved_state.db_software_installed, - backgrounds: self.backgrounds, + background: self.background, splash_images: self.splash_images, icon_images: self.icon_images, }) @@ -328,12 +328,7 @@ impl Launcher { Task::none() }, Message::LoadBackground(()) => { - let mut bg_map = HashMap::new(); - for game in PossibleGames::iter() { - bg_map.insert(game.clone(), get_game_background(&game)); - } - - state.backgrounds = bg_map; + state.background = Some(get_game_background(&state.selected_game)); let (width, height) = state.selected_game.get_game_preferred_size(); window::get_latest().and_then(move |id: window::Id| { @@ -349,6 +344,8 @@ impl Launcher { }, Message::GameSelected(game) => { state.selected_game = game; + state.background = Some(get_game_background(&state.selected_game)); + let (width, height) = state.selected_game.get_game_preferred_size(); window::get_latest().and_then(move |id: window::Id| { window::resize(id, Size { width: width as f32, height: height as f32 }) @@ -404,7 +401,7 @@ impl Launcher { ..container::Style::default() })) .on_press(Message::DragWindow) - .interaction(Interaction::Grab) + .interaction(Interaction::Move) ]; let bottom_bar = container(row![