// client/src/input/camera.rs #![forbid(unsafe_code)] use anyhow::Result; use gstreamer as gst; use gstreamer_app as gst_app; use gst::prelude::*; use lesavka_common::lesavka::VideoPacket; pub struct CameraCapture { pipeline: gst::Pipeline, sink: gst_app::AppSink, } impl CameraCapture { pub fn new(device_fragment: Option<&str>) -> anyhow::Result { gst::init().ok(); // Pick device let dev = device_fragment .and_then(Self::find_device) .unwrap_or_else(|| "/dev/video0".into()); let desc = format!( "v4l2src device={dev} io-mode=dmabuf-do-timestamp=true ! \ videoconvert ! videoscale ! video/x-raw,width=1280,height=720 ! \ v4l2h264enc key-int-max=30 \ extra-controls=\"encode,frame_level_rate_control_enable=1,h264_profile=4\" ! \ h264parse config-interval=-1 ! \ appsink name=asink emit-signals=true max-buffers=60 drop=true" ); // --- NEW: propagate the Result and down‑cast with `?` let pipeline: gst::Pipeline = gst::parse::launch(&desc)? .downcast::() .expect("not a pipeline"); // --- NEW: the lookup already returns the concrete AppSink → just unwrap with `?` let sink: gst_app::AppSink = pipeline .by_name("asink") .expect("appsink element not found") .downcast::() .expect("appsink down‑cast"); pipeline.set_state(gst::State::Playing)?; Ok(Self { pipeline, sink }) } pub fn pull(&self) -> Option { let sample = self.sink.pull_sample().ok()?; let buf = sample.buffer()?; let map = buf.map_readable().ok()?; let pts = buf.pts().unwrap_or(gst::ClockTime::ZERO).nseconds() / 1_000; Some(VideoPacket { id: 100, pts, data: map.as_slice().to_vec() }) } /// Fuzzy‑match devices under `/dev/v4l/by-id` fn find_device(substr: &str) -> Option { let dir = std::fs::read_dir("/dev/v4l/by-id").ok()?; for e in dir.flatten() { let p = e.path(); if p.file_name()?.to_string_lossy().contains(substr) { if let Ok(target) = std::fs::read_link(&p) { return Some(format!("/dev/{}", target.file_name()?.to_string_lossy())); } } } None } /// Cheap stub used when the web‑cam is disabled pub fn new_stub() -> Self { let pipeline = gst::Pipeline::new(); // --- NEW: AppSink factory helper (AppSink::new() does not exist) let sink: gst_app::AppSink = gst::ElementFactory::make("appsink") .build() .expect("make appsink") .downcast::() .expect("appsink down‑cast"); Self { pipeline, sink } } }