diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ed068714b..f6d1f124c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -135,7 +135,7 @@ trigger: variables: FDO_DISTRIBUTION_PACKAGES: >- build-essential curl python3-setuptools liborc-0.4-dev libglib2.0-dev - libxml2-dev libgtk-3-dev libegl1-mesa libgles2-mesa libgl1-mesa-dri + libxml2-dev libegl1-mesa libgles2-mesa libgl1-mesa-dri libgl1-mesa-glx libwayland-egl1-mesa xz-utils libssl-dev git wget ca-certificates ninja-build python3-pip flex bison libglib2.0-dev libx11-dev libx11-xcb-dev libsoup2.4-dev libvorbis-dev libogg-dev diff --git a/ci/run-cargo-test.sh b/ci/run-cargo-test.sh index 6a39fca2e..b58bd4fcd 100755 --- a/ci/run-cargo-test.sh +++ b/ci/run-cargo-test.sh @@ -23,7 +23,7 @@ done if [ -n "$EXAMPLES_TUTORIALS" ]; then # Keep in sync with examples/Cargo.toml # List all features except windows/win32 - EXAMPLES_FEATURES="--features=gtksink,gtkvideooverlay,gtkvideooverlay-x11,gtkvideooverlay-quartz,rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18" + EXAMPLES_FEATURES="--features=rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18" cargo build --locked --color=always --manifest-path examples/Cargo.toml --bins --examples "$EXAMPLES_FEATURES" cargo build --locked --color=always --manifest-path tutorials/Cargo.toml --bins --examples --all-features diff --git a/ci/run-clippy.sh b/ci/run-clippy.sh index 9baaeca49..6fd4b462b 100755 --- a/ci/run-clippy.sh +++ b/ci/run-clippy.sh @@ -34,7 +34,7 @@ done # Keep in sync with examples/Cargo.toml # List all features except windows/win32 -EXAMPLES_FEATURES="--features=gtksink,gtkvideooverlay,gtkvideooverlay-x11,gtkvideooverlay-quartz,rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18" +EXAMPLES_FEATURES="--features=rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18" # And also run over all the examples/tutorials cargo clippy --locked --color=always --manifest-path examples/Cargo.toml --all-targets "$EXAMPLES_FEATURES" -- $CLIPPY_LINTS diff --git a/examples/Cargo.toml b/examples/Cargo.toml index cb9512954..d857156d1 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -25,8 +25,6 @@ gst-sdp = { package = "gstreamer-sdp", path = "../gstreamer-sdp", optional = tru gst-rtsp = { package = "gstreamer-rtsp", path = "../gstreamer-rtsp", optional = true } gst-rtsp-server = { package = "gstreamer-rtsp-server", path = "../gstreamer-rtsp-server", optional = true } gst-allocators = { package = "gstreamer-allocators", path = "../gstreamer-allocators", optional = true } -gtk = { git = "https://github.com/gtk-rs/gtk3-rs", optional = true } -gdk = { git = "https://github.com/gtk-rs/gtk3-rs", optional = true } gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true } anyhow = "1.0" derive_more = "0.99.5" @@ -56,10 +54,6 @@ gl_generator = { version = "0.14", optional = true } [features] default = [] -gtksink = ["gtk", "gio"] -gtkvideooverlay = ["gtk", "gdk", "gio"] -gtkvideooverlay-x11 = ["gtkvideooverlay"] -gtkvideooverlay-quartz = ["gtkvideooverlay"] rtsp-server = ["gst-rtsp-server", "gst-rtsp", "gst-sdp"] rtsp-server-record = ["gst-rtsp-server", "gst-rtsp", "gio"] pango-cairo = ["pango", "pangocairo", "cairo-rs"] @@ -94,14 +88,6 @@ name = "encodebin" [[bin]] name = "events" -[[bin]] -name = "gtksink" -required-features = ["gtksink"] - -[[bin]] -name = "gtkvideooverlay" -required-features = ["gtkvideooverlay"] - [[bin]] name = "iterator" diff --git a/examples/src/bin/gtksink.rs b/examples/src/bin/gtksink.rs deleted file mode 100644 index 317bac92c..000000000 --- a/examples/src/bin/gtksink.rs +++ /dev/null @@ -1,179 +0,0 @@ -// This example demonstrates how to use gstreamer in conjunction with the gtk widget toolkit. -// This example shows the video produced by a videotestsrc within a small gtk gui. -// For this, the gtkglsink is used, which creates a gtk widget one can embed the gtk gui. -// For this, there multiple types of widgets. gtkglsink uses OpenGL to render frames, and -// gtksink uses the CPU to render the frames (which is way slower). -// So the example application first tries to use OpenGL, and when that fails, fall back. -// The pipeline looks like the following: - -// gtk-gui: {gtkglsink}-widget -// (|) -// {videotestsrc} - {glsinkbin} - -use std::cell::RefCell; - -use gio::prelude::*; -use gst::prelude::*; -use gtk::prelude::*; - -fn create_ui(app: >k::Application) { - let pipeline = gst::Pipeline::default(); - let src = gst::ElementFactory::make("videotestsrc").build().unwrap(); - // Create the gtk sink and retrieve the widget from it. The sink element will be used - // in the pipeline, and the widget will be embedded in our gui. - // Gstreamer then displays frames in the gtk widget. - // First, we try to use the OpenGL version - and if that fails, we fall back to non-OpenGL. - let (sink, widget) = if let Ok(gtkglsink) = gst::ElementFactory::make("gtkglsink").build() { - // Using the OpenGL widget succeeded, so we are in for a nice playback experience with - // low cpu usage. :) - // The gtkglsink essentially allocates an OpenGL texture on the GPU, that it will display. - // Now we create the glsinkbin element, which is responsible for conversions and for uploading - // video frames to our texture (if they are not already in the GPU). Now we tell the OpenGL-sink - // about our gtkglsink element, form where it will retrieve the OpenGL texture to fill. - let glsinkbin = gst::ElementFactory::make("glsinkbin") - .property("sink", >kglsink) - .build() - .unwrap(); - // The gtkglsink creates the gtk widget for us. This is accessible through a property. - // So we get it and use it later to add it to our gui. - let widget = gtkglsink.property::("widget"); - (glsinkbin, widget) - } else { - // Unfortunately, using the OpenGL widget didn't work out, so we will have to render - // our frames manually, using the CPU. An example why this may fail is, when - // the PC doesn't have proper graphics drivers installed. - let sink = gst::ElementFactory::make("gtksink").build().unwrap(); - // The gtksink creates the gtk widget for us. This is accessible through a property. - // So we get it and use it later to add it to our gui. - let widget = sink.property::("widget"); - (sink, widget) - }; - - pipeline.add_many([&src, &sink]).unwrap(); - src.link(&sink).unwrap(); - - // Create a simple gtk gui window to place our widget into. - let window = gtk::Window::new(gtk::WindowType::Toplevel); - window.set_default_size(320, 240); - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - // Add our widget to the gui - vbox.pack_start(&widget, true, true, 0); - let label = gtk::Label::new(Some("Position: 00:00:00")); - vbox.pack_start(&label, true, true, 5); - window.add(&vbox); - window.show_all(); - - app.add_window(&window); - - // Need to move a new reference into the closure. - // !!ATTENTION!!: - // It might seem appealing to use pipeline.clone() here, because that greatly - // simplifies the code within the callback. What this actually does, however, is creating - // a memory leak. The clone of a pipeline is a new strong reference on the pipeline. - // Storing this strong reference of the pipeline within the callback (we are moving it in!), - // which is in turn stored in another strong reference on the pipeline is creating a - // reference cycle. - // DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK - let pipeline_weak = pipeline.downgrade(); - // Add a timeout to the main loop that will periodically (every 500ms) be - // executed. This will query the current position within the stream from - // the underlying pipeline, and display it in our gui. - // Since this closure is called by the mainloop thread, we are allowed - // to modify the gui widgets here. - let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || { - // Here we temporarily retrieve a strong reference on the pipeline from the weak one - // we moved into this callback. - let pipeline = match pipeline_weak.upgrade() { - Some(pipeline) => pipeline, - None => return glib::ControlFlow::Continue, - }; - - // Query the current playing position from the underlying pipeline. - let position = pipeline.query_position::(); - // Display the playing position in the gui. - label.set_text(&format!("Position: {:.0}", position.display())); - // Tell the callback to continue calling this closure. - 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 app = match app_weak.upgrade() { - Some(app) => app, - None => 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"); - - // Pipeline reference is owned by the closure below, so will be - // destroyed once the app is destroyed - let timeout_id = RefCell::new(Some(timeout_id)); - let pipeline = RefCell::new(Some(pipeline)); - app.connect_shutdown(move |_| { - // Optional, by manually destroying the window here we ensure that - // the gst element is destroyed when shutting down instead of having to wait - // for the process to terminate, allowing us to use the leaks tracer. - unsafe { - window.destroy(); - } - - // GTK will keep the Application alive for the whole process lifetime. - // Wrapping the pipeline in a RefCell> and removing it from it here - // ensures the pipeline is actually destroyed when shutting down, allowing us - // to use the leaks tracer for example. - 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 { - // Initialize gstreamer and the gtk widget toolkit libraries. - gst::init().unwrap(); - gtk::init().unwrap(); - - let res = { - let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE); - - app.connect_activate(create_ui); - app.run() - }; - - // Optional, can be used to detect leaks using the leaks tracer - unsafe { - gst::deinit(); - } - - res -} diff --git a/examples/src/bin/gtkvideooverlay.rs b/examples/src/bin/gtkvideooverlay.rs deleted file mode 100644 index 191561ba2..000000000 --- a/examples/src/bin/gtkvideooverlay.rs +++ /dev/null @@ -1,286 +0,0 @@ -// This example demonstrates another type of combination of gtk and gstreamer, -// in comparison to the gtksink example. -// This example uses regions that are managed by the window system, and uses -// the window system's api to insert a videostream into these regions. -// So essentially, the window system of the system overlays our gui with -// the video frames - within the region that we tell it to use. -// Disadvantage of this method is, that it's highly platform specific, since -// the big platforms all have their own window system. Thus, this example -// has special code to handle differences between platforms. -// Windows could theoretically be supported by this example, but is not yet implemented. -// One of the very few (if not the single one) platform, that can not provide the API -// needed for this are Linux desktops using Wayland. -// TODO: Add Windows support -// In this case, a testvideo is displayed within our gui, using the -// following pipeline: - -// {videotestsrc} - {xvimagesink(on linux)} -// {videotestsrc} - {glimagesink(on mac)} - -use std::{cell::RefCell, os::raw::c_void, process}; - -use gio::prelude::*; -use gst_video::prelude::*; -use gtk::prelude::*; - -#[cfg(all(target_os = "linux", feature = "gtkvideooverlay-x11"))] -fn create_video_sink() -> gst::Element { - // When we are on linux with the Xorg display server, we use the - // X11 protocol's XV extension, which allows to overlay regions - // with video streams. For this, we use the xvimagesink element. - gst::ElementFactory::make("xvimagesink").build().unwrap() -} -#[cfg(all(target_os = "linux", feature = "gtkvideooverlay-x11"))] -fn set_window_handle(video_overlay: &gst_video::VideoOverlay, gdk_window: &gdk::Window) { - let display_type_name = gdk_window.display().type_().name(); - - // Check if we're using X11 or ... - if display_type_name == "GdkX11Display" { - extern "C" { - pub fn gdk_x11_window_get_xid(window: *mut glib::gobject_ffi::GObject) -> *mut c_void; - } - - // This is unsafe because the "window handle" we pass here is basically like a raw pointer. - // If a wrong value were to be passed here (and you can pass any integer), then the window - // system will most likely cause the application to crash. - #[allow(clippy::cast_ptr_alignment)] - unsafe { - // Here we ask gdk what native window handle we got assigned for - // our video region from the window system, and then we will - // pass this unique identifier to the overlay provided by our - // sink - so the sink can then arrange the overlay. - let xid = gdk_x11_window_get_xid(gdk_window.as_ptr() as *mut _); - video_overlay.set_window_handle(xid as usize); - } - } else { - println!("Add support for display type '{display_type_name}'"); - process::exit(-1); - } -} - -#[cfg(all(target_os = "macos", feature = "gtkvideooverlay-quartz"))] -fn create_video_sink() -> gst::Element { - // On Mac, this is done by overlaying a window region with an - // OpenGL-texture, using the glimagesink element. - gst::ElementFactory::make("glimagesink").build().unwrap() -} - -#[cfg(all(target_os = "macos", feature = "gtkvideooverlay-quartz"))] -fn set_window_handle(video_overlay: &gst_video::VideoOverlay, gdk_window: &gdk::Window) { - let display_type_name = gdk_window.display().type_().name(); - - if display_type_name == "GdkQuartzDisplay" { - extern "C" { - pub fn gdk_quartz_window_get_nsview( - window: *mut glib::gobject_ffi::GObject, - ) -> *mut c_void; - } - - // This is unsafe because the "window handle" we pass here is basically like a raw pointer. - // If a wrong value were to be passed here (and you can pass any integer), then the window - // system will most likely cause the application to crash. - #[allow(clippy::cast_ptr_alignment)] - unsafe { - // Here we ask gdk what native window handle we got assigned for - // our video region from the windowing system, and then we will - // pass this unique identifier to the overlay provided by our - // sink - so the sink can then arrange the overlay. - let window = gdk_quartz_window_get_nsview(gdk_window.as_ptr() as *mut _); - video_overlay.set_window_handle(window as usize); - } - } else { - println!("Unsupported display type '{}", display_type_name); - process::exit(-1); - } -} - -fn create_ui(app: >k::Application) { - let pipeline = gst::Pipeline::default(); - let src = gst::ElementFactory::make("videotestsrc").build().unwrap(); - - // Since using the window system to overlay our gui window is making - // direct contact with the windowing system, this is highly platform- - // specific. This example supports Linux and Mac (using X11 and Quartz). - let sink = create_video_sink(); - - pipeline.add_many([&src, &sink]).unwrap(); - src.link(&sink).unwrap(); - - // First, we create our gtk window - which will contain a region where - // our overlaid video will be displayed in. - let window = gtk::Window::new(gtk::WindowType::Toplevel); - window.set_default_size(320, 240); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - - // This creates the widget we will display our overlay in. - // Later, we will try to tell our window system about this region, so - // it can overlay it with our video stream. - let video_window = gtk::DrawingArea::new(); - video_window.set_size_request(320, 240); - - // Use the platform-specific sink to create our overlay. - // Since we only use the video_overlay in the closure below, we need a weak reference. - // !!ATTENTION!!: - // It might seem appealing to use .clone() here, because that greatly - // simplifies the code within the callback. What this actually does, however, is creating - // a memory leak. - let video_overlay = sink - .dynamic_cast::() - .unwrap() - .downgrade(); - // Connect to this widget's realize signal, which will be emitted - // after its display has been initialized. This is necessary, because - // the window system doesn't know about our region until it was initialized. - video_window.connect_realize(move |video_window| { - // Here we temporarily retrieve a strong reference on the video-overlay from the - // weak reference that we moved into the closure. - let video_overlay = match video_overlay.upgrade() { - Some(video_overlay) => video_overlay, - None => return, - }; - - // Gtk uses gdk under the hood, to handle its drawing. Drawing regions are - // called gdk windows. We request this underlying drawing region from the - // widget we will overlay with our video. - let gdk_window = video_window.window().unwrap(); - - // This is where we tell our window system about the drawing-region we - // want it to overlay. Most often, the window system would only know - // about our most outer region (or: our window). - if !gdk_window.ensure_native() { - println!("Can't create native window for widget"); - process::exit(-1); - } - - set_window_handle(&video_overlay, &gdk_window); - }); - - vbox.pack_start(&video_window, true, true, 0); - - let label = gtk::Label::new(Some("Position: 00:00:00")); - vbox.pack_start(&label, true, true, 5); - window.add(&vbox); - - window.show_all(); - - app.add_window(&window); - - // Need to move a new reference into the closure. - // !!ATTENTION!!: - // It might seem appealing to use pipeline.clone() here, because that greatly - // simplifies the code within the callback. What this actually does, however, is creating - // a memory leak. The clone of a pipeline is a new strong reference on the pipeline. - // Storing this strong reference of the pipeline within the callback (we are moving it in!), - // which is in turn stored in another strong reference on the pipeline is creating a - // reference cycle. - // DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK - let pipeline_weak = pipeline.downgrade(); - // Add a timeout to the main loop that will periodically (every 500ms) be - // executed. This will query the current position within the stream from - // the underlying pipeline, and display it in our gui. - // Since this closure is called by the mainloop thread, we are allowed - // to modify the gui widgets here. - let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || { - // Here we temporarily retrieve a strong reference on the pipeline from the weak one - // we moved into this callback. - let pipeline = match pipeline_weak.upgrade() { - Some(pipeline) => pipeline, - None => return glib::ControlFlow::Break, - }; - - // Query the current playing position from the underlying pipeline. - let position = pipeline.query_position::(); - // Display the playing position in the gui. - label.set_text(&format!("Position: {:.0}", position.display())); - // Tell the timeout to continue calling this callback. - 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 app = match app_weak.upgrade() { - Some(app) => app, - None => 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"); - - // Pipeline reference is owned by the closure below, so will be - // destroyed once the app is destroyed - let timeout_id = RefCell::new(Some(timeout_id)); - let pipeline = RefCell::new(Some(pipeline)); - app.connect_shutdown(move |_| { - // Optional, by manually destroying the window here we ensure that - // the gst element is destroyed when shutting down instead of having to wait - // for the process to terminate, allowing us to use the leaks tracer. - unsafe { - window.destroy(); - } - - // GTK will keep the Application alive for the whole process lifetime. - // Wrapping the pipeline in a RefCell> and removing it from it here - // ensures the pipeline is actually destroyed when shutting down, allowing us - // to use the leaks tracer for example. - 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 { - #[cfg(not(unix))] - { - println!("Add support for target platform"); - process::exit(-1); - } - - // Initialize gstreamer and the gtk widget toolkit libraries. - gst::init().unwrap(); - gtk::init().unwrap(); - - let res = { - let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE); - - app.connect_activate(create_ui); - app.run() - }; - - // Optional, can be used to detect leaks using the leaks tracer - unsafe { - gst::deinit(); - } - - res -} diff --git a/tutorials/Cargo.toml b/tutorials/Cargo.toml index 82277a028..08502b143 100644 --- a/tutorials/Cargo.toml +++ b/tutorials/Cargo.toml @@ -8,8 +8,6 @@ rust-version = "1.66" [dependencies] glib = { git = "https://github.com/gtk-rs/gtk-rs-core" } -gdk = { git = "https://github.com/gtk-rs/gtk3-rs", optional = true } -gtk = { git = "https://github.com/gtk-rs/gtk3-rs", optional = true } gst = { package = "gstreamer", path = "../gstreamer" } gst-audio = { package = "gstreamer-audio", path = "../gstreamer-audio" } gst-video = { package = "gstreamer-video", path = "../gstreamer-video", optional = true } @@ -23,11 +21,6 @@ termion = { version = "2", optional = true } cocoa = "0.25" objc = "0.2.7" -[features] -tutorial5 = ["gtk", "gdk", "gst-video"] -tutorial5-x11 = ["tutorial5"] -tutorial5-quartz = ["tutorial5"] - [[bin]] name = "basic-tutorial-13" required-features = ["termion"] diff --git a/tutorials/src/bin/basic-tutorial-5.rs b/tutorials/src/bin/basic-tutorial-5.rs deleted file mode 100644 index d872bc91d..000000000 --- a/tutorials/src/bin/basic-tutorial-5.rs +++ /dev/null @@ -1,393 +0,0 @@ -#[cfg(feature = "tutorial5")] -mod tutorial5 { - use std::{ops, os::raw::c_void, process}; - - use gdk::prelude::*; - use gst_video::prelude::*; - use gtk::prelude::*; - - // Custom struct to keep our window reference alive - // and to store the timeout id so that we can remove - // it from the main context again later and drop the - // references it keeps inside its closures - struct AppWindow { - main_window: gtk::Window, - timeout_id: Option, - } - - impl ops::Deref for AppWindow { - type Target = gtk::Window; - - fn deref(&self) -> >k::Window { - &self.main_window - } - } - - impl Drop for AppWindow { - fn drop(&mut self) { - if let Some(source_id) = self.timeout_id.take() { - source_id.remove(); - } - } - } - - // Extract tags from streams of @stype and add the info in the UI. - fn add_streams_info(playbin: &gst::Element, textbuf: >k::TextBuffer, stype: &str) { - let propname: &str = &format!("n-{stype}"); - let signame: &str = &format!("get-{stype}-tags"); - - let x = playbin.property::(propname); - for i in 0..x { - let tags = playbin.emit_by_name::>(signame, &[&i]); - - if let Some(tags) = tags { - textbuf.insert_at_cursor(&format!("{stype} stream {i}:\n ")); - - if let Some(codec) = tags.get::() { - textbuf.insert_at_cursor(&format!(" codec: {} \n", codec.get())); - } - - if let Some(codec) = tags.get::() { - textbuf.insert_at_cursor(&format!(" codec: {} \n", codec.get())); - } - - if let Some(lang) = tags.get::() { - textbuf.insert_at_cursor(&format!(" language: {} \n", lang.get())); - } - - if let Some(bitrate) = tags.get::() { - textbuf.insert_at_cursor(&format!(" bitrate: {} \n", bitrate.get())); - } - } - } - } - - // Extract metadata from all the streams and write it to the text widget in the GUI - fn analyze_streams(playbin: &gst::Element, textbuf: >k::TextBuffer) { - { - textbuf.set_text(""); - } - - add_streams_info(playbin, textbuf, "video"); - add_streams_info(playbin, textbuf, "audio"); - add_streams_info(playbin, textbuf, "text"); - } - - // This creates all the GTK+ widgets that compose our application, and registers the callbacks - fn create_ui(playbin: &gst::Element) -> AppWindow { - let main_window = gtk::Window::new(gtk::WindowType::Toplevel); - main_window.connect_delete_event(|_, _| { - gtk::main_quit(); - Inhibit(false) - }); - - let play_button = - gtk::Button::from_icon_name(Some("media-playback-start"), gtk::IconSize::SmallToolbar); - let pipeline = playbin.clone(); - play_button.connect_clicked(move |_| { - let pipeline = &pipeline; - pipeline - .set_state(gst::State::Playing) - .expect("Unable to set the pipeline to the `Playing` state"); - }); - - let pause_button = - gtk::Button::from_icon_name(Some("media-playback-pause"), gtk::IconSize::SmallToolbar); - let pipeline = playbin.clone(); - pause_button.connect_clicked(move |_| { - let pipeline = &pipeline; - pipeline - .set_state(gst::State::Paused) - .expect("Unable to set the pipeline to the `Paused` state"); - }); - - let stop_button = - gtk::Button::from_icon_name(Some("media-playback-stop"), gtk::IconSize::SmallToolbar); - let pipeline = playbin.clone(); - stop_button.connect_clicked(move |_| { - let pipeline = &pipeline; - pipeline - .set_state(gst::State::Ready) - .expect("Unable to set the pipeline to the `Ready` state"); - }); - - let slider = gtk::Scale::with_range(gtk::Orientation::Horizontal, 0.0, 100.0, 1.0); - let pipeline = playbin.clone(); - let slider_update_signal_id = slider.connect_value_changed(move |slider| { - let pipeline = &pipeline; - let value = slider.value() as u64; - if pipeline - .seek_simple( - gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT, - value * gst::ClockTime::SECOND, - ) - .is_err() - { - eprintln!("Seeking to {value} failed"); - } - }); - - slider.set_draw_value(false); - let pipeline = playbin.clone(); - let lslider = slider.clone(); - // Update the UI (seekbar) every second - let timeout_id = glib::timeout_add_seconds_local(1, move || { - let pipeline = &pipeline; - let lslider = &lslider; - - if let Some(dur) = pipeline.query_duration::() { - lslider.set_range(0.0, dur.seconds() as f64); - - if let Some(pos) = pipeline.query_position::() { - lslider.block_signal(&slider_update_signal_id); - lslider.set_value(pos.seconds() as f64); - lslider.unblock_signal(&slider_update_signal_id); - } - } - - glib::ControlFlow::Continue - }); - - let controls = gtk::Box::new(gtk::Orientation::Horizontal, 0); - controls.pack_start(&play_button, false, false, 0); - controls.pack_start(&pause_button, false, false, 0); - controls.pack_start(&stop_button, false, false, 0); - controls.pack_start(&slider, true, true, 2); - - let video_window = gtk::DrawingArea::new(); - - let video_overlay = playbin - .clone() - .dynamic_cast::() - .unwrap(); - - video_window.connect_realize(move |video_window| { - let video_overlay = &video_overlay; - let gdk_window = video_window.window().unwrap(); - - if !gdk_window.ensure_native() { - println!("Can't create native window for widget"); - process::exit(-1); - } - - let display_type_name = gdk_window.display().type_().name(); - #[cfg(all(target_os = "linux", feature = "tutorial5-x11"))] - { - // Check if we're using X11 or ... - if display_type_name == "GdkX11Display" { - extern "C" { - pub fn gdk_x11_window_get_xid( - window: *mut glib::gobject_ffi::GObject, - ) -> *mut c_void; - } - - #[allow(clippy::cast_ptr_alignment)] - unsafe { - let xid = gdk_x11_window_get_xid(gdk_window.as_ptr() as *mut _); - video_overlay.set_window_handle(xid as usize); - } - } else { - println!("Add support for display type '{display_type_name}'"); - process::exit(-1); - } - } - #[cfg(all(target_os = "macos", feature = "tutorial5-quartz"))] - { - if display_type_name == "GdkQuartzDisplay" { - extern "C" { - pub fn gdk_quartz_window_get_nsview( - window: *mut glib::gobject_ffi::GObject, - ) -> *mut c_void; - } - - #[allow(clippy::cast_ptr_alignment)] - unsafe { - let window = gdk_quartz_window_get_nsview(gdk_window.as_ptr() as *mut _); - video_overlay.set_window_handle(window as usize); - } - } else { - println!( - "Unsupported display type '{}', compile with `--feature `", - display_type_name - ); - process::exit(-1); - } - } - }); - - let streams_list = gtk::TextView::new(); - streams_list.set_editable(false); - let pipeline_weak = playbin.downgrade(); - let streams_list_weak = glib::SendWeakRef::from(streams_list.downgrade()); - let bus = playbin.bus().unwrap(); - - #[allow(clippy::single_match)] - bus.connect_message(Some("application"), move |_, msg| match msg.view() { - gst::MessageView::Application(application) => { - let pipeline = match pipeline_weak.upgrade() { - Some(pipeline) => pipeline, - None => return, - }; - - let streams_list = match streams_list_weak.upgrade() { - Some(streams_list) => streams_list, - None => return, - }; - - if application.structure().map(|s| s.name().as_str()) == Some("tags-changed") { - let textbuf = streams_list - .buffer() - .expect("Couldn't get buffer from text_view"); - analyze_streams(&pipeline, &textbuf); - } - } - _ => unreachable!(), - }); - - let vbox = gtk::Box::new(gtk::Orientation::Horizontal, 0); - vbox.pack_start(&video_window, true, true, 0); - vbox.pack_start(&streams_list, false, false, 2); - - let main_box = gtk::Box::new(gtk::Orientation::Vertical, 0); - main_box.pack_start(&vbox, true, true, 0); - main_box.pack_start(&controls, false, false, 0); - main_window.add(&main_box); - main_window.set_default_size(640, 480); - - main_window.show_all(); - - AppWindow { - main_window, - timeout_id: Some(timeout_id), - } - } - - // We are possibly in a GStreamer working thread, so we notify the main - // thread of this event through a message in the bus - fn post_app_message(playbin: &gst::Element) { - let _ = playbin.post_message(gst::message::Application::new(gst::Structure::new_empty( - "tags-changed", - ))); - } - - pub fn run() { - // Make sure the right features were activated - #[allow(clippy::eq_op)] - { - if !cfg!(feature = "tutorial5-x11") && !cfg!(feature = "tutorial5-quartz") { - eprintln!( - "No Gdk backend selected, compile with --features tutorial5[-x11][-quartz]." - ); - - return; - } - } - - // Initialize GTK - if let Err(err) = gtk::init() { - eprintln!("Failed to initialize GTK: {err}"); - return; - } - - // Initialize GStreamer - if let Err(err) = gst::init() { - eprintln!("Failed to initialize Gst: {err}"); - return; - } - - let uri = "https://www.freedesktop.org/software/gstreamer-sdk/\ - data/media/sintel_trailer-480p.webm"; - let playbin = gst::ElementFactory::make("playbin") - .property("uri", uri) - .build() - .unwrap(); - - playbin.connect("video-tags-changed", false, |args| { - let pipeline = args[0] - .get::() - .expect("playbin \"video-tags-changed\" args[0]"); - post_app_message(&pipeline); - None - }); - - playbin.connect("audio-tags-changed", false, |args| { - let pipeline = args[0] - .get::() - .expect("playbin \"audio-tags-changed\" args[0]"); - post_app_message(&pipeline); - None - }); - - playbin.connect("text-tags-changed", false, move |args| { - let pipeline = args[0] - .get::() - .expect("playbin \"text-tags-changed\" args[0]"); - post_app_message(&pipeline); - None - }); - - let window = create_ui(&playbin); - - let bus = playbin.bus().unwrap(); - bus.add_signal_watch(); - - let pipeline_weak = playbin.downgrade(); - bus.connect_message(None, move |_, msg| { - let pipeline = match pipeline_weak.upgrade() { - Some(pipeline) => pipeline, - None => return, - }; - - match msg.view() { - // This is called when an End-Of-Stream message is posted on the bus. - // We just set the pipeline to READY (which stops playback). - gst::MessageView::Eos(..) => { - println!("End-Of-Stream reached."); - pipeline - .set_state(gst::State::Ready) - .expect("Unable to set the pipeline to the `Ready` state"); - } - - // This is called when an error message is posted on the bus - gst::MessageView::Error(err) => { - println!( - "Error from {:?}: {} ({:?})", - err.src().map(|s| s.path_string()), - err.error(), - err.debug() - ); - } - // This is called when the pipeline changes states. We use it to - // keep track of the current state. - gst::MessageView::StateChanged(state_changed) => { - if state_changed.src().map(|s| s == &pipeline).unwrap_or(false) { - println!("State set to {:?}", state_changed.current()); - } - } - _ => (), - } - }); - - playbin - .set_state(gst::State::Playing) - .expect("Unable to set the playbin to the `Playing` state"); - - gtk::main(); - window.hide(); - playbin - .set_state(gst::State::Null) - .expect("Unable to set the playbin to the `Null` state"); - - bus.remove_signal_watch(); - } -} - -#[cfg(feature = "tutorial5")] -fn main() { - tutorial5::run(); -} - -#[cfg(not(feature = "tutorial5"))] -fn main() { - println!("Please compile with --features tutorial5[-x11][-quartz]"); -}