// client/src/output/video.rs use anyhow::Context; use gstreamer as gst; use gstreamer_app as gst_app; use gst::prelude::*; use lesavka_common::lesavka::VideoPacket; /* ---------- pipeline ---------------------------------------------------- * ┌────────────┐ H.264/AU ┌─────────┐ Decoded ┌─────────────┐ * │ AppSrc │────────────► decodebin ├──────────► glimagesink │ * └────────────┘ (autoplug) (overlay) | * ----------------------------------------------------------------------*/ const PIPELINE_DESC: &str = concat!( "appsrc name=src is-live=true format=time do-timestamp=true block=false ! ", "queue leaky=downstream ! ", "capsfilter caps=video/x-h264,stream-format=byte-stream,alignment=au ! ", "h264parse disable-passthrough=true ! decodebin ! videoconvert ! ", "glimagesink name=sink sync=false" ); pub struct MonitorWindow { _pipeline: gst::Pipeline, src: gst_app::AppSrc, } impl MonitorWindow { pub fn new(_id: u32) -> anyhow::Result { gst::init().context("initialising GStreamer")?; // --- Build pipeline ------------------------------------------------ let pipeline: gst::Pipeline = gst::parse::launch(PIPELINE_DESC)? .downcast::() .expect("not a pipeline"); // Optional: turn the sink full‑screen when LESAVKA_FULLSCREEN=1 if std::env::var("LESAVKA_FULLSCREEN").is_ok() { if let Some(sink) = pipeline.by_name("sink") { sink.set_property_from_str("fullscreen", "true"); sink.set_property("force-aspect-ratio", &true); // Wayland & GL sinks accept it :contentReference[oaicite:1]{index=1} } } /* ---------- AppSrc ------------------------------------------------- */ let src: gst_app::AppSrc = pipeline .by_name("src") .unwrap() .downcast::() .unwrap(); src.set_caps(Some(&gst::Caps::builder("video/x-h264") .field("stream-format", &"byte-stream") .field("alignment", &"au") .build())); src.set_format(gst::Format::Time); pipeline.set_state(gst::State::Playing)?; Ok(Self { _pipeline: pipeline, src }) } /// Feed one access-unit to the decoder. 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); // ignore Eos/flushing } }