forked from mirrors/gstreamer-rs
Add the tutorial5 (Gtk video player with informations about streams)
Fixes https://github.com/sdroege/gstreamer-rs/pull/41
This commit is contained in:
parent
08c30a0566
commit
faae914f72
2 changed files with 371 additions and 0 deletions
|
@ -5,7 +5,16 @@ authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
|||
|
||||
[dependencies]
|
||||
glib = { version = "0.3", git = "https://github.com/gtk-rs/glib" }
|
||||
gdk = { version = "0.6", git = "https://github.com/gtk-rs/gdk", optional = true }
|
||||
gtk = { version = "0.2", git = "https://github.com/gtk-rs/gtk", features = ["v3_10"], optional = true }
|
||||
gstreamer = { path = "../gstreamer" }
|
||||
gstreamer-video = { path = "../gstreamer-video" }
|
||||
send-cell = "0.1"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "sdroege/gstreamer-rs", branch = "master" }
|
||||
|
||||
[features]
|
||||
tutorial5 = ["gtk", "gdk"]
|
||||
tutorial5-x11 = ["tutorial5"]
|
||||
tutorial5-quartz = ["tutorial5"]
|
||||
|
|
362
tutorials/src/bin/basic-tutorial-5.rs
Normal file
362
tutorials/src/bin/basic-tutorial-5.rs
Normal file
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
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::<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| 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]");
|
||||
}
|
Loading…
Reference in a new issue