2025-06-21 05:21:57 -05:00
|
|
|
|
// client/src/output/video.rs
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
use winit::{
|
|
|
|
|
|
event_loop::EventLoop,
|
|
|
|
|
|
window::{Window, WindowBuilder},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
pub struct MonitorWindow {
|
|
|
|
|
|
id: u32,
|
|
|
|
|
|
_window: Window,
|
|
|
|
|
|
src: gst_app::AppSrc,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl MonitorWindow {
|
|
|
|
|
|
pub fn new(id: u32, el: &EventLoop<()>) -> anyhow::Result<Self> {
|
|
|
|
|
|
gst::init()?;
|
|
|
|
|
|
|
|
|
|
|
|
let window = WindowBuilder::new()
|
|
|
|
|
|
.with_title(format!("Lesavka‑monitor‑{id}"))
|
|
|
|
|
|
.with_decorations(false)
|
|
|
|
|
|
.build(el)?;
|
|
|
|
|
|
|
|
|
|
|
|
// appsrc -> decode -> convert -> waylandsink
|
|
|
|
|
|
let pipeline = gst::parse_launch(
|
|
|
|
|
|
"appsrc name=src is-live=true format=time do-timestamp=true ! \
|
|
|
|
|
|
queue ! h264parse ! avdec_h264 ! videoconvert ! \
|
|
|
|
|
|
waylandsink name=sink sync=false",
|
|
|
|
|
|
)?
|
|
|
|
|
|
.downcast::<gst::Pipeline>()?;
|
|
|
|
|
|
|
|
|
|
|
|
let src = pipeline.by_name("src").unwrap().downcast::<gst_app::AppSrc>()?;
|
|
|
|
|
|
src.set_latency(gst::ClockTime::NONE);
|
|
|
|
|
|
src.set_property_format(gst::Format::Time);
|
|
|
|
|
|
|
|
|
|
|
|
pipeline.set_state(gst::State::Playing)?;
|
|
|
|
|
|
Ok(Self { id, _window: window, src })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn push_packet(&self, pkt: VideoPacket) {
|
|
|
|
|
|
if let Ok(mut buf) = gst::Buffer::from_mut_slice(pkt.data) {
|
|
|
|
|
|
buf.set_pts(gst::ClockTime::from_microseconds(pkt.pts));
|
|
|
|
|
|
let _ = self.src.push_buffer(buf);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|