diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index c5c81281..95666613 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -2867,6 +2867,12 @@ ], "return-type": "GOutputStream", "when": "last" + }, + "new-playlist": { + "action": true, + "args": [], + "return-type": "void", + "when": "last" } } }, diff --git a/net/hlssink3/src/hlsbasesink.rs b/net/hlssink3/src/hlsbasesink.rs index ee7aa456..e6266187 100644 --- a/net/hlssink3/src/hlsbasesink.rs +++ b/net/hlssink3/src/hlsbasesink.rs @@ -307,7 +307,7 @@ impl HlsBaseSink { }); } - fn close_playlist(&self) { + pub fn close_playlist(&self) { let mut state = self.state.lock().unwrap(); if let Some(mut context) = state.context.take() { if context.playlist.is_rendering() { diff --git a/net/hlssink3/src/hlscmafsink/imp.rs b/net/hlssink3/src/hlscmafsink/imp.rs index 28260518..06b90ae0 100644 --- a/net/hlssink3/src/hlscmafsink/imp.rs +++ b/net/hlssink3/src/hlscmafsink/imp.rs @@ -27,6 +27,7 @@ const DEFAULT_SYNC: bool = true; const DEFAULT_LATENCY: gst::ClockTime = gst::ClockTime::from_mseconds((DEFAULT_TARGET_DURATION * 500) as u64); const SIGNAL_GET_INIT_STREAM: &str = "get-init-stream"; +const SIGNAL_NEW_PLAYLIST: &str = "new-playlist"; static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( @@ -208,22 +209,60 @@ impl ObjectImpl for HlsCmafSink { fn signals() -> &'static [glib::subclass::Signal] { static SIGNALS: Lazy> = Lazy::new(|| { - vec![glib::subclass::Signal::builder(SIGNAL_GET_INIT_STREAM) - .param_types([String::static_type()]) - .return_type::>() - .class_handler(|_, args| { - let elem = args[0].get::().expect("signal arg"); - let init_location = args[1].get::().expect("signal arg"); - let imp = elem.imp(); + vec![ + glib::subclass::Signal::builder(SIGNAL_GET_INIT_STREAM) + .param_types([String::static_type()]) + .return_type::>() + .class_handler(|_, args| { + let elem = args[0].get::().expect("signal arg"); + let init_location = args[1].get::().expect("signal arg"); + let imp = elem.imp(); - Some(imp.new_file_stream(&init_location).ok().to_value()) - }) - .accumulator(|_hint, ret, value| { - // First signal handler wins - *ret = value.clone(); - false - }) - .build()] + Some(imp.new_file_stream(&init_location).ok().to_value()) + }) + .accumulator(|_hint, ret, value| { + // First signal handler wins + *ret = value.clone(); + false + }) + .build(), + glib::subclass::Signal::builder(SIGNAL_NEW_PLAYLIST) + .action() + .class_handler(|_token, args| { + // Forces hlscmafsink to finish the current playlist and start a new one. + // Meant to be used after changing output location at runtime, which would + // otherwise require changing playback state to READY to make sure that the + // old playlist is closed correctly and new init segment is written. + let elem = args[0].get::().expect("signal arg"); + let imp = elem.imp(); + + gst::debug!( + CAT, + imp = imp, + "Closing current playlist and starting a new one" + ); + base_imp!(imp).close_playlist(); + + let (target_duration, playlist_type, segment_template, cmafmux) = { + let settings = imp.settings.lock().unwrap(); + ( + settings.target_duration, + settings.playlist_type.clone(), + settings.location.clone(), + settings.cmafmux.clone(), + ) + }; + + let playlist = imp.start(target_duration, playlist_type); + base_imp!(imp).open_playlist(playlist, segment_template); + + // This forces cmafmux to send the init headers again. + cmafmux.emit_by_name::<()>("send-headers", &[]); + + None + }) + .build(), + ] }); SIGNALS.as_ref()