// Copyright (C) 2022 LTN Global Communications, Inc. // Contact: Jan Alexander Steffens (heftig) // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use gio::prelude::*; use gst::{glib, prelude::*}; use gtk::prelude::*; use std::cell::Cell; struct DroppingProbe(glib::WeakRef, Option); impl DroppingProbe { fn install(pad: &gst::Pad) -> Self { let probe_id = pad .add_probe(gst::PadProbeType::BUFFER, |_, _| gst::PadProbeReturn::Drop) .unwrap(); Self(pad.downgrade(), Some(probe_id)) } } impl Drop for DroppingProbe { fn drop(&mut self) { if let Some((pad, probe_id)) = self.0.upgrade().zip(self.1.take()) { pad.remove_probe(probe_id); } } } fn create_pipeline() -> gst::Pipeline { gst::parse_launch( r#"videotestsrc name=vsrc is-live=1 ! video/x-raw,framerate=60/1,width=800,height=600 ! timeoverlay text="Pre:" ! queue ! livesync latency=50000000 ! videorate ! timeoverlay text="Post:" halignment=right ! queue ! gtk4paintablesink name=vsink audiotestsrc name=asrc is-live=1 ! audio/x-raw,channels=2 ! queue ! livesync latency=50000000 ! audiorate ! queue ! autoaudiosink "#, ) .expect("Failed to create pipeline") .downcast() .unwrap() } fn create_window(app: >k::Application) { let pipeline = create_pipeline(); let video_src_pad = pipeline.by_name("vsrc").unwrap().static_pad("src").unwrap(); let audio_src_pad = pipeline.by_name("asrc").unwrap().static_pad("src").unwrap(); let window = gtk::ApplicationWindow::new(app); window.set_default_size(800, 684); let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); let picture = gtk::Picture::new(); let paintable = pipeline .by_name("vsink") .unwrap() .property::("paintable"); picture.set_paintable(Some(&paintable)); vbox.append(&picture); let action_bar = gtk::ActionBar::new(); vbox.append(&action_bar); let offset_spin = gtk::SpinButton::with_range(0.0, 500.0, 100.0); action_bar.pack_start(&offset_spin); { let video_src_pad = video_src_pad.clone(); let audio_src_pad = audio_src_pad.clone(); offset_spin.connect_value_notify(move |offset_spin| { const MSECOND: f64 = gst::ClockTime::MSECOND.nseconds() as _; let offset = (offset_spin.value() * -MSECOND) as i64; video_src_pad.set_offset(offset); audio_src_pad.set_offset(offset); }); } let drop_button = gtk::ToggleButton::with_label("Drop Signal"); action_bar.pack_end(&drop_button); let drop_ids = Cell::new(None); drop_button.connect_toggled(move |drop_button| { if drop_button.is_active() { let video_probe = DroppingProbe::install(&video_src_pad); let audio_probe = DroppingProbe::install(&audio_src_pad); drop_ids.set(Some((video_probe, audio_probe))); } else { drop_ids.set(None); } }); { let bus = pipeline.bus().unwrap(); let window = window.downgrade(); bus.add_watch_local(move |_, msg| { use gst::MessageView; match msg.view() { MessageView::Eos(..) => { if let Some(window) = window.upgrade() { window.close(); } } MessageView::Error(err) => { eprintln!( "Error from {}: {} ({:?})", msg.src().map(|s| s.path_string()).as_deref().unwrap_or(""), err.error(), err.debug(), ); if let Some(window) = window.upgrade() { window.close(); } } _ => (), }; glib::Continue(true) }) .unwrap(); } { let pipeline = pipeline.clone(); window.connect_realize(move |_| { pipeline .set_state(gst::State::Playing) .expect("Failed to start pipeline"); }); } window.connect_unrealize(move |_| { pipeline .set_state(gst::State::Null) .expect("Failed to stop pipeline"); }); window.set_child(Some(&vbox)); window.show(); } fn main() { let app = gtk::Application::new( Some("gtk-plugins-rs.gtk-livesync"), gio::ApplicationFlags::FLAGS_NONE, ); app.connect_startup(move |_app| { gst::init().expect("Failed to initialize GStreamer"); gstlivesync::plugin_register_static().expect("Failed to register livesync plugin"); gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin"); }); app.connect_activate(create_window); app.run(); }