gstreamr: bus: Add BusWatchGuard to automatically remove watch

Previously, with add_watch()/add_watch_local() you had to remember
calling remove_watch() in order not to leak the bus, the watch source
and two associated file descriptors. Now these methods instead return an
object of type BusWatchGuard that will automatically remove the bus
watch when the object is dropped.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1248>
This commit is contained in:
Johan Sternerup 2023-04-12 10:28:44 +02:00 committed by Sebastian Dröge
parent 5c156737a4
commit e026d922e4
15 changed files with 276 additions and 269 deletions

View file

@ -133,33 +133,34 @@ fn example_main() {
// Every message from the bus is passed through this function. Its returnvalue determines // Every message from the bus is passed through this function. Its returnvalue determines
// whether the handler wants to be called again. If glib::Continue(false) is returned, the // whether the handler wants to be called again. If glib::Continue(false) is returned, the
// handler is removed and will never be called again. The mainloop still runs though. // handler is removed and will never be called again. The mainloop still runs though.
bus.add_watch(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone; let main_loop = &main_loop_clone;
match msg.view() { match msg.view() {
MessageView::Eos(..) => { MessageView::Eos(..) => {
println!("received eos"); println!("received eos");
// An EndOfStream event was sent to the pipeline, so we tell our main loop // An EndOfStream event was sent to the pipeline, so we tell our main loop
// to stop execution here. // to stop execution here.
main_loop.quit() main_loop.quit()
} }
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
main_loop.quit(); main_loop.quit();
} }
_ => (), _ => (),
}; };
// Tell the mainloop to continue executing this callback. // Tell the mainloop to continue executing this callback.
glib::Continue(true) glib::Continue(true)
}) })
.expect("Failed to add bus watch"); .expect("Failed to add bus watch");
// Operate GStreamer's bus, facilitating GLib's mainloop here. // Operate GStreamer's bus, facilitating GLib's mainloop here.
// This function call will block until you tell the mainloop to quit // This function call will block until you tell the mainloop to quit
@ -169,11 +170,6 @@ fn example_main() {
pipeline pipeline
.set_state(gst::State::Null) .set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state"); .expect("Unable to set the pipeline to the `Null` state");
// Remove the watch function from the bus.
// Again: There can always only be one watch function.
// Thus we don't have to tell him which function to remove.
bus.remove_watch().unwrap();
} }
fn main() { fn main() {

View file

@ -329,30 +329,31 @@ fn main() -> Result<()> {
let main_loop_clone = main_loop.clone(); let main_loop_clone = main_loop.clone();
let bus = playbin.bus().unwrap(); let bus = playbin.bus().unwrap();
bus.add_watch(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone; let main_loop = &main_loop_clone;
match msg.view() { match msg.view() {
MessageView::Eos(..) => { MessageView::Eos(..) => {
println!("received eos"); println!("received eos");
main_loop.quit() main_loop.quit()
} }
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
main_loop.quit(); main_loop.quit();
} }
_ => (), _ => (),
}; };
glib::Continue(true) glib::Continue(true)
}) })
.unwrap(); .unwrap();
playbin.set_state(gst::State::Playing).unwrap(); playbin.set_state(gst::State::Playing).unwrap();

View file

@ -87,33 +87,34 @@ fn example_main() {
// Every message from the bus is passed through this function. Its returnvalue determines // Every message from the bus is passed through this function. Its returnvalue determines
// whether the handler wants to be called again. If glib::Continue(false) is returned, the // whether the handler wants to be called again. If glib::Continue(false) is returned, the
// handler is removed and will never be called again. The mainloop still runs though. // handler is removed and will never be called again. The mainloop still runs though.
bus.add_watch(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone; let main_loop = &main_loop_clone;
match msg.view() { match msg.view() {
MessageView::Eos(..) => { MessageView::Eos(..) => {
println!("received eos"); println!("received eos");
// An EndOfStream event was sent to the pipeline, so we tell our main loop // An EndOfStream event was sent to the pipeline, so we tell our main loop
// to stop execution here. // to stop execution here.
main_loop.quit() main_loop.quit()
} }
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
main_loop.quit(); main_loop.quit();
} }
_ => (), _ => (),
}; };
// Tell the mainloop to continue executing this callback. // Tell the mainloop to continue executing this callback.
glib::Continue(true) glib::Continue(true)
}) })
.expect("Failed to add bus watch"); .expect("Failed to add bus watch");
// Operate GStreamer's bus, facilliating GLib's mainloop here. // Operate GStreamer's bus, facilliating GLib's mainloop here.
// This function call will block until you tell the mainloop to quit // This function call will block until you tell the mainloop to quit
@ -123,11 +124,6 @@ fn example_main() {
pipeline pipeline
.set_state(gst::State::Null) .set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state"); .expect("Unable to set the pipeline to the `Null` state");
// Remove the watch function from the bus.
// Again: There can always only be one watch function.
// Thus we don't have to tell him which function to remove.
bus.remove_watch().unwrap();
} }
fn main() { fn main() {

View file

@ -103,31 +103,32 @@ fn create_ui(app: &gtk::Application) {
.expect("Unable to set the pipeline to the `Playing` state"); .expect("Unable to set the pipeline to the `Playing` state");
let app_weak = app.downgrade(); let app_weak = app.downgrade();
bus.add_watch_local(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch_local(move |_, msg| {
use gst::MessageView;
let app = match app_weak.upgrade() { let app = match app_weak.upgrade() {
Some(app) => app, Some(app) => app,
None => return glib::Continue(false), None => return glib::Continue(false),
}; };
match msg.view() { match msg.view() {
MessageView::Eos(..) => app.quit(), MessageView::Eos(..) => app.quit(),
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
app.quit(); app.quit();
} }
_ => (), _ => (),
}; };
glib::Continue(true) glib::Continue(true)
}) })
.expect("Failed to add bus watch"); .expect("Failed to add bus watch");
// Pipeline reference is owned by the closure below, so will be // Pipeline reference is owned by the closure below, so will be
// destroyed once the app is destroyed // destroyed once the app is destroyed
@ -149,7 +150,6 @@ fn create_ui(app: &gtk::Application) {
pipeline pipeline
.set_state(gst::State::Null) .set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state"); .expect("Unable to set the pipeline to the `Null` state");
pipeline.bus().unwrap().remove_watch().unwrap();
} }
if let Some(timeout_id) = timeout_id.borrow_mut().take() { if let Some(timeout_id) = timeout_id.borrow_mut().take() {

View file

@ -204,31 +204,32 @@ fn create_ui(app: &gtk::Application) {
.expect("Unable to set the pipeline to the `Playing` state"); .expect("Unable to set the pipeline to the `Playing` state");
let app_weak = app.downgrade(); let app_weak = app.downgrade();
bus.add_watch_local(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch_local(move |_, msg| {
use gst::MessageView;
let app = match app_weak.upgrade() { let app = match app_weak.upgrade() {
Some(app) => app, Some(app) => app,
None => return glib::Continue(false), None => return glib::Continue(false),
}; };
match msg.view() { match msg.view() {
MessageView::Eos(..) => app.quit(), MessageView::Eos(..) => app.quit(),
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
app.quit(); app.quit();
} }
_ => (), _ => (),
}; };
glib::Continue(true) glib::Continue(true)
}) })
.expect("Failed to add bus watch"); .expect("Failed to add bus watch");
// Pipeline reference is owned by the closure below, so will be // Pipeline reference is owned by the closure below, so will be
// destroyed once the app is destroyed // destroyed once the app is destroyed
@ -250,7 +251,6 @@ fn create_ui(app: &gtk::Application) {
pipeline pipeline
.set_state(gst::State::Null) .set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state"); .expect("Unable to set the pipeline to the `Null` state");
pipeline.bus().unwrap().remove_watch().unwrap();
} }
if let Some(timeout_id) = timeout_id.borrow_mut().take() { if let Some(timeout_id) = timeout_id.borrow_mut().take() {

View file

@ -35,38 +35,34 @@ fn example_main() {
//bus.add_signal_watch(); //bus.add_signal_watch();
//bus.connect_message(None, move |_, msg| { //bus.connect_message(None, move |_, msg| {
bus.add_watch(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone; let main_loop = &main_loop_clone;
match msg.view() { match msg.view() {
MessageView::Eos(..) => main_loop.quit(), MessageView::Eos(..) => main_loop.quit(),
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
main_loop.quit(); main_loop.quit();
} }
_ => (), _ => (),
}; };
glib::Continue(true) glib::Continue(true)
}) })
.expect("Failed to add bus watch"); .expect("Failed to add bus watch");
main_loop.run(); main_loop.run();
pipeline pipeline
.set_state(gst::State::Null) .set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state"); .expect("Unable to set the pipeline to the `Null` state");
// Here we remove the bus watch we added above. This avoids a memory leak, that might
// otherwise happen because we moved a strong reference (clone of main_loop) into the
// callback closure above.
bus.remove_watch().unwrap();
} }
fn main() { fn main() {

View file

@ -94,27 +94,28 @@ fn example_main() {
let main_loop_clone = main_loop.clone(); let main_loop_clone = main_loop.clone();
//bus.add_signal_watch(); //bus.add_signal_watch();
//bus.connect_message(None, move |_, msg| { //bus.connect_message(None, move |_, msg| {
bus.add_watch(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone; let main_loop = &main_loop_clone;
match msg.view() { match msg.view() {
MessageView::Eos(..) => main_loop.quit(), MessageView::Eos(..) => main_loop.quit(),
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
main_loop.quit(); main_loop.quit();
} }
_ => (), _ => (),
}; };
glib::Continue(true) glib::Continue(true)
}) })
.expect("Failed to add bus watch"); .expect("Failed to add bus watch");
main_loop.run(); main_loop.run();
@ -122,7 +123,6 @@ fn example_main() {
.set_state(gst::State::Null) .set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state"); .expect("Unable to set the pipeline to the `Null` state");
bus.remove_watch().unwrap();
timeout_id.remove(); timeout_id.remove();
} }

View file

@ -586,6 +586,7 @@ final_type = true
[[object.function]] [[object.function]]
name = "remove_watch" name = "remove_watch"
visibility = "crate"
[object.function.return] [object.function.return]
bool_return_is_error = "Bus has no event source" bool_return_is_error = "Bus has no event source"

View file

@ -92,7 +92,8 @@ impl Bus {
} }
#[doc(alias = "gst_bus_remove_watch")] #[doc(alias = "gst_bus_remove_watch")]
pub fn remove_watch(&self) -> Result<(), glib::error::BoolError> { #[allow(dead_code)]
pub(crate) fn remove_watch(&self) -> Result<(), glib::error::BoolError> {
unsafe { unsafe {
glib::result_from_gboolean!( glib::result_from_gboolean!(
ffi::gst_bus_remove_watch(self.to_glib_none().0), ffi::gst_bus_remove_watch(self.to_glib_none().0),

View file

@ -13,7 +13,7 @@ use futures_util::{stream::FusedStream, StreamExt};
use glib::{ use glib::{
ffi::{gboolean, gpointer}, ffi::{gboolean, gpointer},
prelude::*, prelude::*,
source::{Continue, Priority, SourceId}, source::{Continue, Priority},
translate::*, translate::*,
}; };
@ -135,7 +135,7 @@ impl Bus {
#[doc(alias = "gst_bus_add_watch")] #[doc(alias = "gst_bus_add_watch")]
#[doc(alias = "gst_bus_add_watch_full")] #[doc(alias = "gst_bus_add_watch_full")]
pub fn add_watch<F>(&self, func: F) -> Result<SourceId, glib::BoolError> pub fn add_watch<F>(&self, func: F) -> Result<BusWatchGuard, glib::BoolError>
where where
F: FnMut(&Bus, &Message) -> Continue + Send + 'static, F: FnMut(&Bus, &Message) -> Continue + Send + 'static,
{ {
@ -151,14 +151,14 @@ impl Bus {
if res == 0 { if res == 0 {
Err(glib::bool_error!("Bus already has a watch")) Err(glib::bool_error!("Bus already has a watch"))
} else { } else {
Ok(from_glib(res)) Ok(BusWatchGuard { bus: self.clone() })
} }
} }
} }
#[doc(alias = "gst_bus_add_watch")] #[doc(alias = "gst_bus_add_watch")]
#[doc(alias = "gst_bus_add_watch_full")] #[doc(alias = "gst_bus_add_watch_full")]
pub fn add_watch_local<F>(&self, func: F) -> Result<SourceId, glib::BoolError> pub fn add_watch_local<F>(&self, func: F) -> Result<BusWatchGuard, glib::BoolError>
where where
F: FnMut(&Bus, &Message) -> Continue + 'static, F: FnMut(&Bus, &Message) -> Continue + 'static,
{ {
@ -179,7 +179,7 @@ impl Bus {
if res == 0 { if res == 0 {
Err(glib::bool_error!("Bus already has a watch")) Err(glib::bool_error!("Bus already has a watch"))
} else { } else {
Ok(from_glib(res)) Ok(BusWatchGuard { bus: self.clone() })
} }
} }
} }
@ -378,6 +378,22 @@ impl FusedStream for BusStream {
} }
} }
// rustdoc-stripper-ignore-next
/// Manages ownership of the bus watch added to a bus with [`Bus::add_watch`] or [`Bus::add_watch_local`]
///
/// When dropped the bus watch is removed from the bus.
#[derive(Debug)]
#[must_use = "if unused the bus watch will immediately be removed"]
pub struct BusWatchGuard {
bus: Bus,
}
impl Drop for BusWatchGuard {
fn drop(&mut self) {
let _ = self.bus.remove_watch();
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};

View file

@ -23,62 +23,62 @@ fn tutorial_main() -> Result<(), Error> {
let main_loop_clone = main_loop.clone(); let main_loop_clone = main_loop.clone();
let pipeline_weak = pipeline.downgrade(); let pipeline_weak = pipeline.downgrade();
let bus = pipeline.bus().expect("Pipeline has no bus"); let bus = pipeline.bus().expect("Pipeline has no bus");
bus.add_watch(move |_, msg| { let _bus_watch = bus
let pipeline = match pipeline_weak.upgrade() { .add_watch(move |_, msg| {
Some(pipeline) => pipeline, let pipeline = match pipeline_weak.upgrade() {
None => return glib::Continue(true), Some(pipeline) => pipeline,
}; None => return glib::Continue(true),
let main_loop = &main_loop_clone; };
match msg.view() { let main_loop = &main_loop_clone;
gst::MessageView::Error(err) => { match msg.view() {
println!( gst::MessageView::Error(err) => {
"Error from {:?}: {} ({:?})", println!(
err.src().map(|s| s.path_string()), "Error from {:?}: {} ({:?})",
err.error(), err.src().map(|s| s.path_string()),
err.debug() err.error(),
); err.debug()
let _ = pipeline.set_state(gst::State::Ready); );
main_loop.quit(); let _ = pipeline.set_state(gst::State::Ready);
} main_loop.quit();
gst::MessageView::Eos(..) => {
// end-of-stream
let _ = pipeline.set_state(gst::State::Ready);
main_loop.quit();
}
gst::MessageView::Buffering(buffering) => {
// If the stream is live, we do not care about buffering
if is_live {
return glib::Continue(true);
} }
gst::MessageView::Eos(..) => {
// end-of-stream
let _ = pipeline.set_state(gst::State::Ready);
main_loop.quit();
}
gst::MessageView::Buffering(buffering) => {
// If the stream is live, we do not care about buffering
if is_live {
return glib::Continue(true);
}
let percent = buffering.percent(); let percent = buffering.percent();
print!("Buffering ({percent}%)\r"); print!("Buffering ({percent}%)\r");
match std::io::stdout().flush() { match std::io::stdout().flush() {
Ok(_) => {} Ok(_) => {}
Err(err) => eprintln!("Failed: {err}"), Err(err) => eprintln!("Failed: {err}"),
}; };
// Wait until buffering is complete before start/resume playing // Wait until buffering is complete before start/resume playing
if percent < 100 { if percent < 100 {
let _ = pipeline.set_state(gst::State::Paused);
} else {
let _ = pipeline.set_state(gst::State::Playing);
}
}
gst::MessageView::ClockLost(_) => {
// Get a new clock
let _ = pipeline.set_state(gst::State::Paused); let _ = pipeline.set_state(gst::State::Paused);
} else {
let _ = pipeline.set_state(gst::State::Playing); let _ = pipeline.set_state(gst::State::Playing);
} }
_ => (),
} }
gst::MessageView::ClockLost(_) => { glib::Continue(true)
// Get a new clock })
let _ = pipeline.set_state(gst::State::Paused); .expect("Failed to add bus watch");
let _ = pipeline.set_state(gst::State::Playing);
}
_ => (),
}
glib::Continue(true)
})
.expect("Failed to add bus watch");
main_loop.run(); main_loop.run();
bus.remove_watch()?;
pipeline.set_state(gst::State::Null)?; pipeline.set_state(gst::State::Null)?;
Ok(()) Ok(())

View file

@ -135,7 +135,7 @@ fn tutorial_main() -> Result<(), Error> {
let playbin_clone = playbin.clone(); let playbin_clone = playbin.clone();
let main_loop_clone = main_loop.clone(); let main_loop_clone = main_loop.clone();
let bus = playbin.bus().unwrap(); let bus = playbin.bus().unwrap();
bus.add_watch(move |_bus, message| { let _bus_watch = bus.add_watch(move |_bus, message| {
use gst::MessageView; use gst::MessageView;
match message.view() { match message.view() {
MessageView::Error(err) => { MessageView::Error(err) => {

View file

@ -140,7 +140,7 @@ fn tutorial_main() -> Result<(), Error> {
let playbin_clone = playbin.clone(); let playbin_clone = playbin.clone();
let main_loop_clone = main_loop.clone(); let main_loop_clone = main_loop.clone();
let bus = playbin.bus().unwrap(); let bus = playbin.bus().unwrap();
bus.add_watch(move |_bus, message| { let _bus_watch = bus.add_watch(move |_bus, message| {
use gst::MessageView; use gst::MessageView;
match message.view() { match message.view() {
MessageView::Error(err) => { MessageView::Error(err) => {

View file

@ -52,54 +52,55 @@ fn tutorial_main() -> Result<(), Error> {
let main_loop_clone = main_loop.clone(); let main_loop_clone = main_loop.clone();
let pipeline_weak = pipeline.downgrade(); let pipeline_weak = pipeline.downgrade();
let bus = pipeline.bus().unwrap(); let bus = pipeline.bus().unwrap();
bus.add_watch(move |_, msg| { let _bus_watch = bus
use gst::MessageView; .add_watch(move |_, msg| {
use gst::MessageView;
let buffering_level = &buffering_level_clone; let buffering_level = &buffering_level_clone;
let pipeline = match pipeline_weak.upgrade() { let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline, Some(pipeline) => pipeline,
None => return glib::Continue(false), None => return glib::Continue(false),
}; };
let main_loop = &main_loop_clone; let main_loop = &main_loop_clone;
match msg.view() { match msg.view() {
MessageView::Error(err) => { MessageView::Error(err) => {
println!( println!(
"Error from {:?}: {} ({:?})", "Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()), err.src().map(|s| s.path_string()),
err.error(), err.error(),
err.debug() err.debug()
); );
main_loop.quit(); main_loop.quit();
}
MessageView::Eos(..) => {
main_loop.quit();
}
MessageView::Buffering(buffering) => {
// If the stream is live, we do not care about buffering.
if is_live {
return glib::Continue(true);
} }
MessageView::Eos(..) => {
main_loop.quit();
}
MessageView::Buffering(buffering) => {
// If the stream is live, we do not care about buffering.
if is_live {
return glib::Continue(true);
}
// Wait until buffering is complete before start/resume playing. // Wait until buffering is complete before start/resume playing.
let percent = buffering.percent(); let percent = buffering.percent();
if percent < 100 { if percent < 100 {
let _ = pipeline.set_state(gst::State::Paused);
} else {
let _ = pipeline.set_state(gst::State::Playing);
}
*buffering_level.lock().unwrap() = percent;
}
MessageView::ClockLost(_) => {
// Get a new clock.
let _ = pipeline.set_state(gst::State::Paused); let _ = pipeline.set_state(gst::State::Paused);
} else {
let _ = pipeline.set_state(gst::State::Playing); let _ = pipeline.set_state(gst::State::Playing);
} }
*buffering_level.lock().unwrap() = percent; _ => (),
} };
MessageView::ClockLost(_) => {
// Get a new clock.
let _ = pipeline.set_state(gst::State::Paused);
let _ = pipeline.set_state(gst::State::Playing);
}
_ => (),
};
glib::Continue(true) glib::Continue(true)
}) })
.expect("Failed to add bus watch"); .expect("Failed to add bus watch");
pipeline.connect("deep-notify::temp-location", false, |args| { pipeline.connect("deep-notify::temp-location", false, |args| {
let download_buffer = args[1].get::<gst::Object>().unwrap(); let download_buffer = args[1].get::<gst::Object>().unwrap();
@ -177,7 +178,6 @@ fn tutorial_main() -> Result<(), Error> {
// Shutdown pipeline // Shutdown pipeline
pipeline.set_state(gst::State::Null)?; pipeline.set_state(gst::State::Null)?;
bus.remove_watch()?;
timeout_id.remove(); timeout_id.remove();
Ok(()) Ok(())

View file

@ -145,7 +145,7 @@ fn tutorial_main() -> Result<(), Error> {
let main_loop_clone = main_loop.clone(); let main_loop_clone = main_loop.clone();
let bus = pipeline.bus().unwrap(); let bus = pipeline.bus().unwrap();
let pipeline_weak = pipeline.downgrade(); let pipeline_weak = pipeline.downgrade();
bus.add_watch(move |_bus, message| { let _bus_watch = bus.add_watch(move |_bus, message| {
use gst::MessageView; use gst::MessageView;
let pipeline = match pipeline_weak.upgrade() { let pipeline = match pipeline_weak.upgrade() {