// client/src/output/video.rs use gstreamer as gst; use gstreamer_app as gst_app; use gst::prelude::*; use gst_app::prelude::*; use lesavka_common::lesavka::VideoPacket; use winit::window::{Window, WindowAttributes}; use winit::event_loop::EventLoop; pub struct MonitorWindow { id: u32, _window: Window, src: gst_app::AppSrc, } impl MonitorWindow { pub fn new(id: u32, el: &EventLoop<()>) -> anyhow::Result { gst::init()?; let window = el.create_window( WindowAttributes::default() .with_title(format!("Lesavka‑monitor‑{id}")) .with_decorations(false) )?; let caps = gst::Caps::builder("video/x-h264") .field("stream-format", &"byte-stream") .field("alignment", &"au") .build(); let desc = if std::env::var_os("LESAVKA_HW_DEC").is_some() { "appsrc name=src is-live=true format=time do-timestamp=true block=false ! \ capsfilter caps=video/x-h264,stream-format=byte-stream,alignment=au ! \ queue max-size-buffers=0 max-size-bytes=0 max-size-time=0 leaky=downstream ! \ h264parse ! vaapih264dec low-latency=true ! videoconvert ! \ autovideosink sync=false max-lateness=-1" } else { "appsrc name=src is-live=true format=time do-timestamp=true block=false ! \ capsfilter caps=video/x-h264,stream-format=byte-stream,alignment=au ! \ queue max-size-buffers=0 max-size-bytes=0 max-size-time=0 leaky=downstream ! \ h264parse ! decodebin ! videoconvert ! autovideosink sync=false max-lateness=-1" }; let pipeline = gst::parse::launch(desc)? .downcast::() .unwrap(); let src = pipeline.by_name("src").unwrap() .downcast::().unwrap(); src.set_caps(Some(&caps)); // use the dedicated helpers from `AppSrcExt` instead of the generic // `set_property`, which returns `()` (hence the earlier E0277). src.set_format(gst::Format::Time); // timestamps are in running-time // “blocksize=0” → deliver whole access-units (no chunking). The generic // `set_property()` API returns a `Result<(), glib::BoolError>` so we just // unwrap: this property always exists on AppSrc. src.set_property("blocksize", &0u32); src.set_latency(gst::ClockTime::NONE, gst::ClockTime::NONE); pipeline.set_state(gst::State::Playing)?; Ok(Self { id, _window: window, src }) } pub fn push_packet(&self, pkt: VideoPacket) { let mut buf = gst::Buffer::from_slice(pkt.data); buf.get_mut().unwrap() .set_pts(Some(gst::ClockTime::from_useconds(pkt.pts))); let _ = self.src.push_buffer(buf); } }