71 lines
2.8 KiB
Rust
Raw Normal View History

2025-06-29 03:46:34 -05:00
// client/src/output/video.rs
use anyhow::Context;
2025-06-21 05:21:57 -05:00
use gstreamer as gst;
use gstreamer_app as gst_app;
use gst::prelude::*;
2025-06-23 07:18:26 -05:00
use lesavka_common::lesavka::VideoPacket;
2025-06-21 05:21:57 -05:00
2025-06-29 03:46:34 -05:00
/* ---------- pipeline ----------------------------------------------------
* H.264/AU Decoded
* AppSrc decodebin glimagesink
* (autoplug) (overlay) |
* ----------------------------------------------------------------------*/
const PIPELINE_DESC: &str = concat!(
2025-06-27 19:31:46 -05:00
"appsrc name=src is-live=true format=time do-timestamp=true block=false ! ",
2025-06-29 03:46:34 -05:00
"queue leaky=downstream ! ",
2025-06-27 19:31:46 -05:00
"capsfilter caps=video/x-h264,stream-format=byte-stream,alignment=au ! ",
2025-06-29 03:46:34 -05:00
"h264parse disable-passthrough=true ! decodebin ! videoconvert ! ",
"glimagesink name=sink sync=false"
2025-06-27 19:31:46 -05:00
);
2025-06-21 05:21:57 -05:00
pub struct MonitorWindow {
2025-06-29 03:46:34 -05:00
_pipeline: gst::Pipeline,
src: gst_app::AppSrc,
2025-06-21 05:21:57 -05:00
}
impl MonitorWindow {
2025-06-29 03:46:34 -05:00
pub fn new(_id: u32) -> anyhow::Result<Self> {
gst::init().context("initialising GStreamer")?;
2025-06-26 16:17:31 -05:00
2025-06-29 03:46:34 -05:00
// --- Build pipeline ------------------------------------------------
let pipeline: gst::Pipeline = gst::parse::launch(PIPELINE_DESC)?
.downcast::<gst::Pipeline>()
.expect("not a pipeline");
2025-06-26 16:17:31 -05:00
2025-06-29 03:46:34 -05:00
// Optional: turn the sink fullscreen 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}
}
}
2025-06-24 23:48:06 -05:00
2025-06-29 03:46:34 -05:00
/* ---------- AppSrc ------------------------------------------------- */
let src: gst_app::AppSrc = pipeline
.by_name("src")
.unwrap()
.downcast::<gst_app::AppSrc>()
.unwrap();
2025-06-26 16:17:31 -05:00
2025-06-29 03:46:34 -05:00
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);
2025-06-21 05:21:57 -05:00
pipeline.set_state(gst::State::Playing)?;
2025-06-24 23:48:06 -05:00
2025-06-29 03:46:34 -05:00
Ok(Self { _pipeline: pipeline, src })
2025-06-21 05:21:57 -05:00
}
2025-06-29 03:46:34 -05:00
/// Feed one access-unit to the decoder.
2025-06-21 05:21:57 -05:00
pub fn push_packet(&self, pkt: VideoPacket) {
2025-06-27 22:51:50 -05:00
let mut buf = gst::Buffer::from_slice(pkt.data);
2025-06-29 03:46:34 -05:00
buf.get_mut()
.unwrap()
.set_pts(Some(gst::ClockTime::from_useconds(pkt.pts)));
let _ = self.src.push_buffer(buf); // ignore Eos/flushing
2025-06-21 05:21:57 -05:00
}
}