diff --git a/tutorials/Cargo.toml b/tutorials/Cargo.toml index 3833077c2..b3dd6ba22 100644 --- a/tutorials/Cargo.toml +++ b/tutorials/Cargo.toml @@ -26,3 +26,7 @@ tutorial5-quartz = ["tutorial5"] [[bin]] name = "basic-tutorial-13" required-features = ["termion"] + +[[bin]] +name = "playback-tutorial-1" +required-features = ["termion"] diff --git a/tutorials/src/bin/playback-tutorial-1.rs b/tutorials/src/bin/playback-tutorial-1.rs new file mode 100644 index 000000000..d8d306ef8 --- /dev/null +++ b/tutorials/src/bin/playback-tutorial-1.rs @@ -0,0 +1,217 @@ +use glib::FlagsClass; +use gst::prelude::*; + +use anyhow::Error; + +use termion::event::Key; +use termion::input::TermRead; + +use std::{thread, time}; + +#[path = "../tutorials-common.rs"] +mod tutorials_common; + +fn analyze_streams(playbin: &gst::Element) { + let n_video = playbin.property("n-video").unwrap().get::().unwrap(); + let n_audio = playbin.property("n-audio").unwrap().get::().unwrap(); + let n_text = playbin.property("n-text").unwrap().get::().unwrap(); + println!( + "{} video stream(s), {} audio stream(s), {} text stream(s)", + n_video, n_audio, n_text + ); + + for i in 0..n_video { + let tags = playbin + .emit_by_name("get-video-tags", &[&i]) + .unwrap() + .unwrap(); + + if let Ok(tags) = tags.get::() { + println!("video stream {}:", i); + if let Some(codec) = tags.get::() { + println!(" codec: {}", codec.get()); + } + } + } + + for i in 0..n_audio { + let tags = playbin + .emit_by_name("get-audio-tags", &[&i]) + .unwrap() + .unwrap(); + + if let Ok(tags) = tags.get::() { + println!("audio stream {}:", i); + if let Some(codec) = tags.get::() { + println!(" codec: {}", codec.get()); + } + if let Some(codec) = tags.get::() { + println!(" language: {}", codec.get()); + } + if let Some(codec) = tags.get::() { + println!(" bitrate: {}", codec.get()); + } + } + } + + for i in 0..n_text { + let tags = playbin + .emit_by_name("get-text-tags", &[&i]) + .unwrap() + .unwrap(); + + if let Ok(tags) = tags.get::() { + println!("subtitle stream {}:", i); + if let Some(codec) = tags.get::() { + println!(" language: {}", codec.get()); + } + } + } + + let current_video = playbin + .property("current-video") + .unwrap() + .get::() + .unwrap(); + let current_audio = playbin + .property("current-audio") + .unwrap() + .get::() + .unwrap(); + let current_text = playbin + .property("current-text") + .unwrap() + .get::() + .unwrap(); + println!( + "Currently playing video stream {}, audio stream {}, text stream {}", + current_video, current_audio, current_text + ); + println!("Type any number and hit ENTER to select a different audio stream"); +} + +fn handle_keyboard(playbin: &gst::Element, main_loop: &glib::MainLoop) { + let mut stdin = termion::async_stdin().keys(); + + loop { + if let Some(Ok(input)) = stdin.next() { + match input { + Key::Char(index) => { + if let Some(index) = index.to_digit(10) { + // Here index can only be 0-9 + let index = index as i32; + let n_audio = playbin.property("n-audio").unwrap().get::().unwrap(); + + if index < n_audio { + println!("Setting current audio stream to {}", index); + playbin.set_property("current-audio", index).unwrap(); + } else { + eprintln!("Index out of bounds"); + } + } + } + Key::Ctrl('c') => { + main_loop.quit(); + break; + } + _ => continue, + }; + } + thread::sleep(time::Duration::from_millis(50)); + } +} + +fn tutorial_main() -> Result<(), Error> { + // Set up main loop + let main_loop = glib::MainLoop::new(None, false); + + // Initialize GStreamer + gst::init()?; + + // Create PlayBin element + let playbin = gst::ElementFactory::make("playbin", Some("playbin"))?; + + // Set URI to play + let uri = + "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_cropped_multilingual.webm"; + playbin.set_property("uri", uri)?; + + // Set flags to show Audio and Video but ignore Subtitles + let flags = playbin.property("flags")?; + let flags_class = FlagsClass::new(flags.type_()).unwrap(); + + let flags = flags_class + .builder_with_value(flags) + .unwrap() + .set_by_nick("audio") + .set_by_nick("video") + .unset_by_nick("text") + .build() + .unwrap(); + playbin.set_property_from_value("flags", &flags)?; + + // Set connection speed. This will affect some internal decisions of playbin + playbin.set_property("connection-speed", 56u64)?; + + // Handle keyboard input + let playbin_clone = playbin.clone(); + let main_loop_clone = main_loop.clone(); + thread::spawn(move || handle_keyboard(&playbin_clone, &main_loop_clone)); + + // Add a bus watch, so we get notified when a message arrives + let playbin_clone = playbin.clone(); + let main_loop_clone = main_loop.clone(); + let bus = playbin.bus().unwrap(); + bus.add_watch(move |_bus, message| { + use gst::MessageView; + match message.view() { + MessageView::Error(err) => { + eprintln!( + "Error received from element {:?} {}", + err.src().map(|s| s.path_string()), + err.error() + ); + eprintln!("Debugging information: {:?}", err.debug()); + main_loop_clone.quit(); + Continue(false) + } + MessageView::StateChanged(state_changed) => { + if state_changed + .src() + .map(|s| s == playbin_clone) + .unwrap_or(false) + && state_changed.current() == gst::State::Playing + { + analyze_streams(&playbin_clone); + } + Continue(true) + } + MessageView::Eos(..) => { + println!("Reached end of stream"); + main_loop_clone.quit(); + Continue(false) + } + _ => Continue(true), + } + })?; + + // Set to PLAYING + playbin.set_state(gst::State::Playing)?; + + // Set GLib mainlooop to run + main_loop.run(); + + // Clean up + playbin.set_state(gst::State::Null)?; + + Ok(()) +} + +fn main() { + // tutorials_common::run is only required to set up the application environment on macOS + // (but not necessary in normal Cocoa applications where this is set up automatically) + match tutorials_common::run(tutorial_main) { + Ok(_) => {} + Err(err) => eprintln!("Failed: {}", err), + }; +}