#[cfg(feature = "tutorial5")] mod tutorial5 { use std::process; use std::os::raw::c_void; extern crate glib; use self::glib::translate::*; use self::glib::*; extern crate gdk; use self::gdk::prelude::*; extern crate gtk; use self::gtk::*; extern crate send_cell; use self::send_cell::SendCell; extern crate gstreamer as gst; extern crate gstreamer_video as gst_video; use self::gst_video::prelude::*; // Extract tags from streams of @stype and add the info in the UI. fn add_streams_info( playbin: &gst::Element, textbufcell: &SendCell, stype: &str, ) { let textbuf = textbufcell.borrow(); let propname: &str = &format!("n-{}", stype); let signame: &str = &format!("get-{}-tags", stype); match playbin.get_property(propname).unwrap().get() { Some(x) => for i in 0..x { let tags = playbin.emit(signame, &[&i]).unwrap().unwrap(); if let Some(tags) = tags.get::() { textbuf.insert_at_cursor(&format!("{} stream {}:\n ", stype, i)); if let Some(codec) = tags.get::() { textbuf .insert_at_cursor(&format!(" codec: {} \n", codec.get().unwrap())); } if let Some(codec) = tags.get::() { textbuf .insert_at_cursor(&format!(" codec: {} \n", codec.get().unwrap())); } if let Some(lang) = tags.get::() { textbuf .insert_at_cursor(&format!(" language: {} \n", lang.get().unwrap())); } if let Some(bitrate) = tags.get::() { textbuf.insert_at_cursor( &format!(" bitrate: {} \n", bitrate.get().unwrap()), ); } } }, None => { eprintln!("Could not get {}!", propname); } } } // Extract metadata from all the streams and write it to the text widget in the GUI fn analyze_streams(playbin: &gst::Element, textbufcell: &SendCell) { { let textbuf = textbufcell.borrow(); textbuf.set_text(""); } add_streams_info(playbin, textbufcell, "video"); add_streams_info(playbin, textbufcell, "audio"); add_streams_info(playbin, textbufcell, "text"); } // This creates all the GTK+ widgets that compose our application, and registers the callbacks pub fn create_ui(playbin: &gst::Element) { let main_window = Window::new(WindowType::Toplevel); main_window.connect_delete_event(|_, _| { gtk::main_quit(); Inhibit(false) }); let play_button = gtk::Button::new_from_icon_name( "media-playback-start", gtk::IconSize::SmallToolbar.into(), ); let pipeline = playbin.clone(); play_button.connect_clicked(move |_| { let pipeline = &pipeline; pipeline.set_state(gst::State::Playing); }); let pause_button = gtk::Button::new_from_icon_name( "media-playback-pause", gtk::IconSize::SmallToolbar.into(), ); let pipeline = playbin.clone(); pause_button.connect_clicked(move |_| { let pipeline = &pipeline; pipeline.set_state(gst::State::Paused); }); let stop_button = gtk::Button::new_from_icon_name( "media-playback-stop", gtk::IconSize::SmallToolbar.into(), ); let pipeline = playbin.clone(); stop_button.connect_clicked(move |_| { let pipeline = &pipeline; pipeline.set_state(gst::State::Ready); }); let slider = gtk::Scale::new_with_range( gtk::Orientation::Horizontal, 0.0 as f64, 100.0 as f64, 1.0 as f64, ); let pipeline = playbin.clone(); let slider_update_signal_id = slider.connect_value_changed(move |slider| { let pipeline = &pipeline; let value = slider.get_value() as u64; if let Err(_) = pipeline.seek_simple( gst::Format::Time, gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT, ((value * gst::SECOND) as i64), ) { eprintln!("Seeking to {} failed", value); } }); slider.set_draw_value(false); let pipeline = playbin.clone(); let lslider = slider.clone(); // Update the UI (seekbar) every second gtk::timeout_add_seconds(1, move || { let pipeline = &pipeline; let lslider = &lslider; if let Some(dur) = pipeline.query_duration(gst::Format::Time) { let seconds = (dur as u64) / gst::SECOND; lslider.set_range(0.0, seconds as f64); } let position = pipeline.query_position(gst::Format::Time); if let Some(position) = position { let seconds = (position as u64) / gst::SECOND; lslider.block_signal(&slider_update_signal_id); lslider.set_value(seconds as f64); lslider.unblock_signal(&slider_update_signal_id); } Continue(true) }); let controls = Box::new(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 = DrawingArea::new(); video_window.set_double_buffered(false); 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.get_window().unwrap(); if !gdk_window.ensure_native() { println!("Can't create native window for widget"); process::exit(-1); } let display_type_name = gdk_window.get_display().get_type().name(); if cfg!(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::object::GObject, ) -> *mut c_void; } unsafe { let xid = gdk_x11_window_get_xid(gdk_window.to_glib_none().0); video_overlay.set_window_handle(xid as usize); } } else { println!("Add support for display type '{}'", display_type_name); process::exit(-1); } } else if cfg!(feature = "tutorial5-quartz") { if display_type_name == "GdkQuartzDisplay" { extern "C" { pub fn gdk_quartz_window_get_nsview( window: *mut glib::object::GObject, ) -> *mut c_void; } unsafe { let window = gdk_quartz_window_get_nsview(gdk_window.to_glib_none().0); 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 = playbin.clone(); let textbuf = SendCell::new( streams_list .get_buffer() .expect("Couldn't get buffer from text_view"), ); playbin .get_bus() .unwrap() .connect_message(move |_, msg| match msg.view() { gst::MessageView::Application(_) => { if msg.get_structure().get_name() == "tags-changed" { analyze_streams(&pipeline, &textbuf); } } _ => (), }); let vbox = Box::new(Orientation::Horizontal, 0); vbox.pack_start(&video_window, true, true, 0); vbox.pack_start(&streams_list, false, false, 2); let main_box = Box::new(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(); } // 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 mbuilder = gst::Message::new_application(gst::Structure::new_empty("tags-changed")); playbin.post_message(&mbuilder.build()); } pub fn run() { // Make sure the right features were activated 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", None).unwrap(); playbin .set_property("uri", &glib::Value::from(uri)) .unwrap(); let pipeline = playbin.clone(); playbin .connect("video-tags-changed", false, move |_| { post_app_message(&pipeline); None }) .unwrap(); let pipeline = playbin.clone(); playbin .connect("audio-tags-changed", false, move |_| { post_app_message(&pipeline); None }) .unwrap(); let pipeline = playbin.clone(); playbin .connect("text-tags-changed", false, move |_| { post_app_message(&pipeline); None }) .unwrap(); create_ui(&playbin); playbin.set_state(gst::State::Playing); let bus = playbin.get_bus().unwrap(); let pipeline = playbin.clone(); bus.add_signal_watch(); playbin.get_bus().unwrap().connect_message(move |_, msg| { 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); } // This is called when an error message is posted on the bus gst::MessageView::Error(err) => { println!( "Error from {}: {} ({:?})", msg.get_src().get_path_string(), err.get_error(), err.get_debug() ); } // This is called when the pipeline changes states. We use it to // keep track of the current state. gst::MessageView::StateChanged(view) => if msg.get_src() == pipeline { println!("State set to {:?}", view.get_current()); }, _ => (), } }); gtk::main(); playbin.set_state(gst::State::Null); } } #[cfg(feature = "tutorial5")] fn main() { tutorial5::run(); } #[cfg(not(feature = "tutorial5"))] fn main() { println!("Please compile with --features tutorial5[-x11][-quartz]"); }