1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
use gst::prelude::*;
use gtk::prelude::*;
use gtk::{gdk, gio, glib};
use std::cell::RefCell;
fn create_ui(app: >k::Application) {
let pipeline = gst::Pipeline::new();
let overlay = gst::ElementFactory::make("clockoverlay")
.property("font-desc", "Monospace 42")
.build()
.unwrap();
let gtksink = gst::ElementFactory::make("gtk4paintablesink")
.build()
.unwrap();
let paintable = gtksink.property::<gdk::Paintable>("paintable");
// TODO: future plans to provide a bin-like element that works with less setup
let (src, sink) = if paintable
.property::<Option<gdk::GLContext>>("gl-context")
.is_some()
{
let src = gst::ElementFactory::make("gltestsrc").build().unwrap();
let sink = gst::ElementFactory::make("glsinkbin")
.property("sink", >ksink)
.build()
.unwrap();
(src, sink)
} else {
let src = gst::ElementFactory::make("videotestsrc").build().unwrap();
let sink = gst::Bin::default();
let convert = gst::ElementFactory::make("videoconvert").build().unwrap();
sink.add(&convert).unwrap();
sink.add(>ksink).unwrap();
convert.link(>ksink).unwrap();
sink.add_pad(&gst::GhostPad::with_target(&convert.static_pad("sink").unwrap()).unwrap())
.unwrap();
(src, sink.upcast())
};
pipeline.add_many([&src, &overlay, &sink]).unwrap();
let caps = gst_video::VideoCapsBuilder::new()
.width(640)
.height(480)
.any_features()
.build();
src.link_filtered(&overlay, &caps).unwrap();
overlay.link(&sink).unwrap();
let window = gtk::ApplicationWindow::new(app);
window.set_default_size(640, 480);
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
let gst_widget = gstgtk4::RenderWidget::new(>ksink);
vbox.append(&gst_widget);
let label = gtk::Label::new(Some("Position: 00:00:00"));
vbox.append(&label);
window.set_child(Some(&vbox));
window.present();
app.add_window(&window);
let pipeline_weak = pipeline.downgrade();
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || {
let Some(pipeline) = pipeline_weak.upgrade() else {
return glib::ControlFlow::Break;
};
let position = pipeline.query_position::<gst::ClockTime>();
label.set_text(&format!("Position: {:.0}", position.display()));
glib::ControlFlow::Continue
});
let bus = pipeline.bus().unwrap();
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to the `Playing` state");
let app_weak = app.downgrade();
let bus_watch = bus
.add_watch_local(move |_, msg| {
use gst::MessageView;
let Some(app) = app_weak.upgrade() else {
return glib::ControlFlow::Break;
};
match msg.view() {
MessageView::Eos(..) => app.quit(),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
app.quit();
}
_ => (),
};
glib::ControlFlow::Continue
})
.expect("Failed to add bus watch");
let timeout_id = RefCell::new(Some(timeout_id));
let pipeline = RefCell::new(Some(pipeline));
let bus_watch = RefCell::new(Some(bus_watch));
app.connect_shutdown(move |_| {
window.close();
drop(bus_watch.borrow_mut().take());
if let Some(pipeline) = pipeline.borrow_mut().take() {
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
}
if let Some(timeout_id) = timeout_id.borrow_mut().take() {
timeout_id.remove();
}
});
}
fn main() -> glib::ExitCode {
gst::init().unwrap();
gtk::init().unwrap();
gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin");
let app = gtk::Application::new(None::<&str>, gio::ApplicationFlags::FLAGS_NONE);
app.connect_activate(create_ui);
let res = app.run();
unsafe {
gst::deinit();
}
res
}
|