-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the tutorial5 (Gtk video player with informations about streams)
- Loading branch information
Thibault Saunier
committed
Oct 11, 2017
1 parent
4b77c18
commit ec79ffd
Showing
2 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,362 @@ | ||
#[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<gtk::TextBuffer>, | ||
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::<gst::TagList>() { | ||
textbuf.insert_at_cursor(&format!("{} stream {}:\n ", stype, i)); | ||
|
||
if let Some(codec) = tags.get::<gst::tags::VideoCodec>() { | ||
textbuf | ||
.insert_at_cursor(&format!(" codec: {} \n", codec.get().unwrap())); | ||
} | ||
|
||
if let Some(codec) = tags.get::<gst::tags::AudioCodec>() { | ||
textbuf | ||
.insert_at_cursor(&format!(" codec: {} \n", codec.get().unwrap())); | ||
} | ||
|
||
if let Some(lang) = tags.get::<gst::tags::LanguageCode>() { | ||
textbuf | ||
.insert_at_cursor(&format!(" language: {} \n", lang.get().unwrap())); | ||
} | ||
|
||
if let Some(bitrate) = tags.get::<gst::tags::Bitrate>() { | ||
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<gtk::TextBuffer>) { | ||
{ | ||
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); | ||
} | ||
}) | ||
.to_glib(); | ||
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; | ||
signal::signal_handler_block(lslider, from_glib(slider_update_signal_id)); | ||
lslider.set_value(seconds as f64); | ||
signal::signal_handler_unblock(lslider, from_glib(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::<gst_video::VideoOverlay>() | ||
.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| { | ||
//let streams_view = &streams_view; | ||
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| { | ||
//let streams_view = &streams_view; | ||
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]"); | ||
} |