Skip to main content

mimick/library/video_player/
pipeline.rs

1use std::sync::Arc;
2
3use gstreamer as gst;
4use gstreamer_play as gst_play;
5use gtk::prelude::*;
6use parking_lot::Mutex;
7
8pub(super) fn build_play(
9    volume: f64,
10    muted: bool,
11    api_key: Arc<Mutex<Option<String>>>,
12) -> Result<(gst_play::Play, gtk::gdk::Paintable), String> {
13    let (sink, paintable) = video_sink()?;
14    let play = gst_play::Play::new(None::<gst_play::PlayVideoRenderer>);
15    let mut config = play.config();
16    config.set_position_update_interval(250);
17    config.set_seek_accurate(true);
18    play.set_config(config).map_err(|e| e.to_string())?;
19    let pipeline = play.pipeline();
20    pipeline.set_property("video-sink", &sink);
21    if let Ok(tempo) = gst::ElementFactory::make("scaletempo").build() {
22        pipeline.set_property("audio-filter", &tempo);
23    }
24    connect_api_key_header(&pipeline, api_key);
25    play.set_volume(volume);
26    play.set_mute(muted);
27    Ok((play, paintable))
28}
29
30pub(super) fn frame_duration(numer: i32, denom: i32) -> Option<gst::ClockTime> {
31    if numer <= 0 || denom <= 0 {
32        return None;
33    }
34    Some(gst::ClockTime::from_nseconds(
35        1_000_000_000 * denom as u64 / numer as u64,
36    ))
37}
38
39fn video_sink() -> Result<(gst::Element, gtk::gdk::Paintable), String> {
40    let gtk_sink = gst::ElementFactory::make("gtk4paintablesink")
41        .build()
42        .map_err(|_| "The GStreamer GTK 4 video sink (gst-plugin-gtk4) is not installed.")?;
43    let paintable = gtk_sink.property::<gtk::gdk::Paintable>("paintable");
44    if !paintable.has_property("gl-context")
45        || paintable
46            .property::<Option<gtk::gdk::GLContext>>("gl-context")
47            .is_none()
48    {
49        return Ok((gtk_sink, paintable));
50    }
51    let gl_sink = gst::ElementFactory::make("glsinkbin")
52        .property("sink", &gtk_sink)
53        .build()
54        .map_err(|e| e.to_string())?;
55    Ok((gl_sink, paintable))
56}
57
58fn connect_api_key_header(pipeline: &gst::Element, api_key: Arc<Mutex<Option<String>>>) {
59    pipeline.connect("source-setup", false, move |args| {
60        let source = args[1].get::<gst::Element>().ok()?;
61        let key = api_key.lock().clone()?;
62        if source.has_property("extra-headers") {
63            let headers = gst::Structure::builder("extra-headers")
64                .field("x-api-key", key)
65                .build();
66            source.set_property("extra-headers", headers);
67        }
68        None
69    });
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn frame_duration_handles_ntsc_and_unknown_rates() {
78        assert_eq!(
79            frame_duration(30, 1),
80            Some(gst::ClockTime::from_nseconds(33_333_333))
81        );
82        assert_eq!(
83            frame_duration(30000, 1001),
84            Some(gst::ClockTime::from_nseconds(33_366_666))
85        );
86        assert_eq!(frame_duration(0, 1), None);
87    }
88}