From e7781986f575a3e5bf4fb772e1d249ce5b1bd5a8 Mon Sep 17 00:00:00 2001 From: Lionel DARNIS Date: Thu, 9 Jul 2026 11:51:07 +0200 Subject: [PATCH] Fix cosmic-player file launch and video playback --- build.rs | 2 +- res/com.system76.CosmicPlayer.desktop | 2 +- src/main.rs | 33 +++++++++++++++------------ src/video.rs | 31 +++++++++++++++++++++++-- vendor/iced_video_player/src/video.rs | 21 ++++++++++++++--- 5 files changed, 67 insertions(+), 22 deletions(-) diff --git a/build.rs b/build.rs index c354dda..ee69a95 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,5 @@ -use vergen::EmitBuilder; use std::{env, fs, path::PathBuf}; +use vergen::EmitBuilder; use xdgen::{App, Context, FluentString}; fn main() { diff --git a/res/com.system76.CosmicPlayer.desktop b/res/com.system76.CosmicPlayer.desktop index d12fdc4..45897d2 100644 --- a/res/com.system76.CosmicPlayer.desktop +++ b/res/com.system76.CosmicPlayer.desktop @@ -5,7 +5,7 @@ Exec=cosmic-player %U Terminal=false Type=Application StartupNotify=true -MimeType=application/mxf;application/ram;application/sdp;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.ms-wpl;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;application/x-extension-m4a;application/x-extension-mp4;application/x-flash-video;application/x-matroska;application/x-netshow-channel;application/x-quicktimeplayer;application/x-shorten;image/vnd.rn-realpix;image/x-pict;misc/ultravox;text/x-google-video-pointer;video/3gp;video/3gpp;video/3gpp2;video/dv;video/divx;video/fli;video/flv;video/mp2t;video/mp4;video/mp4v-es;video/mpeg;video/mpeg-system;video/msvideo;video/ogg;video/quicktime;video/vivo;video/vnd.divx;video/vnd.mpegurl;video/vnd.rn-realvideo;video/vnd.vivo;video/webm;video/x-anim;video/x-avi;video/x-flc;video/x-fli;video/x-flic;video/x-flv;video/x-m4v;video/x-matroska;video/x-mjpeg;video/x-mpeg;video/x-mpeg2;video/x-ms-asf;video/x-ms-asf-plugin;video/x-ms-asx;video/x-msvideo;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvx;video/x-nsv;video/x-ogm+ogg;video/x-theora;video/x-theora+ogg;video/x-totem-stream;audio/x-pn-realaudio;application/smil;application/smil+xml;application/x-quicktime-media-link;application/x-smil;text/google-video-pointer;x-content/video-dvd;x-scheme-handler/pnm;x-scheme-handler/mms;x-scheme-handler/net;x-scheme-handler/rtp;x-scheme-handler/rtmp;x-scheme-handler/rtsp;x-scheme-handler/mmsh;x-scheme-handler/uvox;x-scheme-handler/icy;x-scheme-handler/icyx; +MimeType=application/mxf;application/ram;application/sdp;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.ms-wpl;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;application/x-extension-m4a;application/x-extension-mp4;application/x-flash-video;application/x-matroska;application/x-netshow-channel;application/x-quicktimeplayer;application/x-shorten;image/vnd.rn-realpix;image/x-pict;misc/ultravox;text/x-google-video-pointer;video/3gp;video/3gpp;video/3gpp2;video/dv;video/divx;video/fli;video/flv;video/matroska;video/mp2t;video/mp4;video/mp4v-es;video/mpeg;video/mpeg-system;video/msvideo;video/ogg;video/quicktime;video/vivo;video/vnd.divx;video/vnd.mpegurl;video/vnd.rn-realvideo;video/vnd.vivo;video/webm;video/x-anim;video/x-avi;video/x-flc;video/x-fli;video/x-flic;video/x-flv;video/x-m4v;video/x-matroska;video/x-mjpeg;video/x-mpeg;video/x-mpeg2;video/x-ms-asf;video/x-ms-asf-plugin;video/x-ms-asx;video/x-msvideo;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvx;video/x-nsv;video/x-ogm+ogg;video/x-theora;video/x-theora+ogg;video/x-totem-stream;audio/x-pn-realaudio;application/smil;application/smil+xml;application/x-quicktime-media-link;application/x-smil;text/google-video-pointer;x-content/video-dvd;x-scheme-handler/pnm;x-scheme-handler/mms;x-scheme-handler/net;x-scheme-handler/rtp;x-scheme-handler/rtmp;x-scheme-handler/rtsp;x-scheme-handler/mmsh;x-scheme-handler/uvox;x-scheme-handler/icy;x-scheme-handler/icyx; Icon=com.system76.CosmicPlayer Categories=COSMIC;AudioVideo;Player;Video; Keywords=Audio;Film;Movie;Music;Sound;Video; diff --git a/src/main.rs b/src/main.rs index 285ac5b..823b4ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,10 @@ use cosmic::iced::event::{self, Event}; use cosmic::iced::keyboard::key::Physical; use cosmic::iced::keyboard::{Event as KeyEvent, Key, Modifiers}; use cosmic::iced::mouse::{Event as MouseEvent, ScrollDelta}; +use cosmic::iced::window::set_mode; use cosmic::iced::{ Alignment, Background, Border, Color, ContentFit, Length, Limits, Subscription, exit, window, }; -use cosmic::iced::window::set_mode; use cosmic::widget::menu::action::MenuAction; use cosmic::widget::{self, Slider, about::About, nav_bar, segmented_button}; use cosmic::{Application, ApplicationExt, Element, action, cosmic_theme, executor, font, theme}; @@ -107,16 +107,6 @@ fn main() -> Result<(), Box> { } } - #[cfg(all(unix, not(target_os = "redox")))] - match fork::daemon(true, true) { - Ok(fork::Fork::Child) => (), - Ok(fork::Fork::Parent(_child_pid)) => process::exit(0), - Err(err) => { - eprintln!("failed to daemonize: {:?}", err); - process::exit(1); - } - } - localize::localize(); let config = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) { @@ -169,6 +159,18 @@ fn main() -> Result<(), Box> { Ok(()) } +fn stream_tags(pipeline: &gst::Pipeline, signal: &str, stream: i32) -> Option { + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + pipeline.emit_by_name::>(signal, &[&stream]) + })) { + Ok(tags) => tags, + Err(_) => { + log::warn!("failed to read {signal} for stream {stream}"); + None + } + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { About, @@ -417,14 +419,15 @@ impl App { let n_video = pipeline.property::("n-video"); for i in 0..n_video { - let tags: gst::TagList = pipeline.emit_by_name("get-video-tags", &[&i]); - log::info!("video stream {i}: {tags:#?}"); + if let Some(tags) = stream_tags(&pipeline, "get-video-tags", i) { + log::info!("video stream {i}: {tags:#?}"); + } } let n_audio = pipeline.property::("n-audio"); self.audio_codes = Vec::with_capacity(n_audio as usize); for i in 0..n_audio { - let tags: gst::TagList = pipeline.emit_by_name("get-audio-tags", &[&i]); + let tags = stream_tags(&pipeline, "get-audio-tags", i).unwrap_or_default(); log::info!("audio stream {i}: {tags:#?}"); self.audio_codes .push(if let Some(title) = tags.get::() { @@ -446,7 +449,7 @@ impl App { name: fl!("off"), }); for i in 0..n_text { - let tags: gst::TagList = pipeline.emit_by_name("get-text-tags", &[&i]); + let tags = stream_tags(&pipeline, "get-text-tags", i).unwrap_or_default(); log::info!("text stream {i}: {tags:#?}"); let name = if let Some(title) = tags.get::() { title.get().to_string() diff --git a/src/video.rs b/src/video.rs index 1953fe5..a4d3652 100644 --- a/src/video.rs +++ b/src/video.rs @@ -16,11 +16,17 @@ pub fn new_video( //TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error //TODO: remove unwraps and enable playback of files with only audio. gst::init().unwrap(); + disable_broken_hardware_decoders(); let pipeline = format!( - "playbin uri=\"{}\"{} video-sink=\"videoscale ! videoconvert ! videoflip method=automatic ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"", + "playbin uri=\"{}\"{}{} video-sink=\"videoscale ! videoconvert ! videoflip method=automatic ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"", url.as_str(), - if settings.mute { " mute=true" } else { "" } + if settings.mute { " mute=true" } else { "" }, + if settings.mute { + " audio-sink=fakesink" + } else { + "" + } ); let pipeline = gst::parse::launch(pipeline.as_ref()) .unwrap() @@ -84,3 +90,24 @@ pub fn new_video( } } } + +fn disable_broken_hardware_decoders() { + let registry = gst::Registry::get(); + for decoder in [ + "nvav1dec", + "nvh264dec", + "nvh265dec", + "nvjpegdec", + "nvmpeg2videodec", + "nvmpeg4videodec", + "nvmpegvideodec", + "nvvp8dec", + "nvvp9dec", + ] { + if let Some(feature) = registry.lookup_feature(decoder) { + let rank = feature.rank(); + feature.set_rank(gst::Rank::NONE); + log::debug!("disabled GStreamer decoder {decoder} with rank {rank:?}"); + } + } +} diff --git a/vendor/iced_video_player/src/video.rs b/vendor/iced_video_player/src/video.rs index 6d174e5..32cb224 100644 --- a/vendor/iced_video_player/src/video.rs +++ b/vendor/iced_video_player/src/video.rs @@ -277,9 +277,21 @@ impl Video { // wait for up to 5 seconds until the decoder gets the source capabilities pipeline.state(gst::ClockTime::from_seconds(5)).0?; + let mut initial_sample = None; + let caps = pad.current_caps().or_else(|| { + let sample = video_sink + .try_pull_preroll(gst::ClockTime::from_seconds(2)) + .or_else(|| video_sink.try_pull_sample(gst::ClockTime::from_seconds(2)))?; + let caps = sample.caps().map(|caps| caps.to_owned()); + if sample.buffer().is_some() { + initial_sample = Some(sample); + } + caps + }); + // extract resolution and framerate // TODO(jazzfool): maybe we want to extract some other information too? - let (width, height, framerate, has_video) = if let Some(caps) = pad.current_caps() { + let (width, height, framerate, has_video) = if let Some(caps) = 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)?; @@ -313,8 +325,11 @@ impl Video { let sync_av = pipeline.has_property("av-offset"); // NV12 = 12bpp - let frame = Arc::new(Mutex::new(Frame::new())); - let upload_frame = Arc::new(AtomicBool::new(false)); + let has_initial_sample = initial_sample.is_some(); + let frame = Arc::new(Mutex::new( + initial_sample.map(Frame).unwrap_or_else(Frame::new), + )); + let upload_frame = Arc::new(AtomicBool::new(has_initial_sample)); let alive = Arc::new(AtomicBool::new(true)); let last_frame_time = Arc::new(Mutex::new(Instant::now()));