// This example demonstrates GStreamer's playbin element. // This element takes an arbitrary URI as parameter, and if there is a source // element within gstreamer, that supports this uri, the playbin will try // to automatically create a pipeline that properly plays this media source. // For this, the playbin internally relies on more bin elements, like the // autovideosink and the decodebin. // Essentially, this element is a single-element pipeline able to play // any format from any uri-addressable source that gstreamer supports. // Much of the playbin's behavior can be controlled by so-called flags, as well // as the playbin's properties and signals. use std::env; use gst::prelude::*; #[path = "../examples-common.rs"] mod examples_common; fn example_main() { gst::init().unwrap(); let args: Vec<_> = env::args().collect(); let uri: &str = if args.len() == 2 { args[1].as_ref() } else { println!("Usage: playbin uri"); std::process::exit(-1) }; // Create a new playbin element, and tell it what uri to play back. let playbin = gst::ElementFactory::make("playbin") .property("uri", uri) .build() .unwrap(); // For flags handling // With flags, one can configure playbin's behavior such as whether it // should play back contained video streams, or if it should render subtitles. // let flags = playbin.get_property("flags").unwrap(); // let flags_class = FlagsClass::new(flags.type_()).unwrap(); // let flags = flags_class.builder_with_value(flags).unwrap() // .unset_by_nick("text") // .unset_by_nick("video") // .build() // .unwrap(); // playbin.set_property_from_value("flags", &flags).unwrap(); // The playbin also provides any kind of metadata that it found in the played stream. // For this, the playbin provides signals notifying about changes in the metadata. // Doing this with a signal makes sense for multiple reasons. // - The metadata is only found after the pipeline has been started // - Live streams (such as internet radios) update this metadata during the stream // Note that this signal will be emitted from the streaming threads usually, // not the application's threads! playbin.connect("audio-tags-changed", false, |values| { // The metadata of any of the contained audio streams changed // In the case of a live-stream from an internet radio, this could for example // mark the beginning of a new track, or a new DJ. let playbin = values[0] .get::() .expect("playbin \"audio-tags-changed\" signal values[1]"); // This gets the index of the stream that changed. This is neccessary, since // there could e.g. be multiple audio streams (english, spanish, ...). let idx = values[1] .get::() .expect("playbin \"audio-tags-changed\" signal values[1]"); println!("audio tags of audio stream {idx} changed:"); // HELP: is this correct? // We were only notified about the change of metadata. If we want to do // something with it, we first need to actually query the metadata from the playbin. // We do this by facilliating the get-audio-tags action-signal on playbin. // Sending an action-signal to an element essentially is a function call on the element. // It is done that way, because elements do not have their own function API, they are // relying on GStreamer and GLib's API. The only way an element can communicate with an // application is via properties, signals or action signals (or custom messages, events, queries). // So what the following code does, is essentially asking playbin to tell us its already // internally stored tag list for this stream index. let tags = playbin.emit_by_name::>("get-audio-tags", &[&idx]); if let Some(tags) = tags { if let Some(artist) = tags.get::() { println!(" Artist: {}", artist.get()); } if let Some(title) = tags.get::() { println!(" Title: {}", title.get()); } if let Some(album) = tags.get::() { println!(" Album: {}", album.get()); } } None }); // The playbin element itself is a playbin, so it can be used as one, despite being // created from an element factory. let bus = playbin.bus().unwrap(); playbin .set_state(gst::State::Playing) .expect("Unable to set the pipeline to the `Playing` state"); for msg in bus.iter_timed(gst::ClockTime::NONE) { use gst::MessageView; match msg.view() { MessageView::Eos(..) => break, MessageView::Error(err) => { println!( "Error from {:?}: {} ({:?})", err.src().map(|s| s.path_string()), err.error(), err.debug() ); break; } MessageView::StateChanged(state_changed) => // We are only interested in state-changed messages from playbin { if state_changed.src().map(|s| s == &playbin).unwrap_or(false) && state_changed.current() == gst::State::Playing { // Generate a dot graph of the pipeline to GST_DEBUG_DUMP_DOT_DIR if defined let bin_ref = playbin.downcast_ref::().unwrap(); bin_ref.debug_to_dot_file(gst::DebugGraphDetails::all(), "PLAYING"); } } _ => (), } } playbin .set_state(gst::State::Null) .expect("Unable to set the pipeline to the `Null` state"); } 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) examples_common::run(example_main); }