use clap::{Parser, Subcommand}; use gst::glib; use gst::prelude::*; use once_cell::sync::Lazy; use std::str::FromStr; static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( "gst-hlssink4", gst::DebugColorFlags::empty(), Some("gst-hlssink4"), ) }); #[derive(Parser)] #[command(name = "gst-hlssink4")] #[command(version = "0.1")] #[command(about = "Code for testing hlssink4", long_about = None)] struct Cli { media_file_path: String, #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { MultipleAudioRenditionMultipleVideoVariant, MultipleAudioRenditionMultipleVideoVariantWithIframe, MultipleAudioRenditionSingleVideoVariant, SingleAudioRenditionMultipleVideoVariant, SingleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed, } impl std::fmt::Debug for Commands { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Commands::MultipleAudioRenditionMultipleVideoVariant => { write!(f, "Multiple Audio Rendition Multiple Video Variant") } Commands::MultipleAudioRenditionMultipleVideoVariantWithIframe => write!( f, "Multiple Audio Rendition Multiple Video Variant with I-Frame" ), Commands::MultipleAudioRenditionSingleVideoVariant => { write!(f, "Multiple Audio Rendition Single Video Variant") } Commands::SingleAudioRenditionMultipleVideoVariant => { write!(f, "Single Audio Rendition Multiple Video Variant") } Commands::SingleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed => write!( f, "Single Audio only Variant Multiple Video Variants with Audio Video muxed" ), } } } fn file_source_bin( pipeline: &gst::Pipeline, media_file_path: String, ) -> (gst::Element, gst::Element) { let filesrc = gst::ElementFactory::make("filesrc").build().unwrap(); let decodebin = gst::ElementFactory::make("decodebin").build().unwrap(); let audiotee = gst::ElementFactory::make("tee") .name("audio-tee") .build() .unwrap(); let videotee = gst::ElementFactory::make("tee") .name("video-tee") .build() .unwrap(); filesrc.set_property("location", media_file_path); let pipeline_weak = pipeline.downgrade(); decodebin.connect("pad-added", false, move |args| { let pipeline = match pipeline_weak.upgrade() { Some(self_) => self_, None => return None, }; let pad = args[1] .get::() .expect("Second argument to decodebin pad-added must be pad"); let caps = pad.current_caps().unwrap(); let s = caps.structure(0).unwrap(); let name = s.name(); if name.starts_with("video") { let videotee = pipeline.by_name("video-tee").unwrap(); let sinkpad = videotee.static_pad("sink").unwrap(); pad.link(&sinkpad).unwrap(); } else if name.starts_with("audio") { let audiotee = pipeline.by_name("audio-tee").unwrap(); let sinkpad = audiotee.static_pad("sink").unwrap(); pad.link(&sinkpad).unwrap(); } None }); pipeline .add_many([&filesrc, &decodebin, &audiotee, &videotee]) .unwrap(); filesrc.link(&decodebin).unwrap(); (audiotee, videotee) } fn video_bin(width: u32, height: u32, fps: u32, bitrate: u32, iframe_only: bool) -> gst::Bin { let bin = gst::Bin::new(); let clocksync = gst::ElementFactory::make("clocksync").build().unwrap(); let videoconvert = gst::ElementFactory::make("videoconvert").build().unwrap(); let videoscale = gst::ElementFactory::make("videoscale").build().unwrap(); let videorate = gst::ElementFactory::make("videorate").build().unwrap(); let capsfilter = gst::ElementFactory::make("capsfilter").build().unwrap(); let x264enc = gst::ElementFactory::make("x264enc").build().unwrap(); let h264_capsfilter = gst::ElementFactory::make("capsfilter").build().unwrap(); let h264parse = gst::ElementFactory::make("h264parse").build().unwrap(); let queue = gst::ElementFactory::make("queue").build().unwrap(); let caps = gst::Caps::from_str(format!("video/x-raw,width={width},height={height},framerate={fps}/1,pixel-aspect-ratio=1/1,format=I420").as_str()).unwrap(); capsfilter.set_property("caps", caps); let caps = gst::Caps::from_str("video/x-h264").unwrap(); h264_capsfilter.set_property("caps", caps); x264enc.set_property("bitrate", bitrate); x264enc.set_property_from_str("speed-preset", "ultrafast"); x264enc.set_property_from_str("tune", "zerolatency"); if iframe_only { x264enc.set_property("key-int-max", 1); } else { x264enc.set_property("key-int-max", fps * 2); } bin.add_many([ &clocksync, &videoconvert, &videoscale, &videorate, &capsfilter, &x264enc, &h264_capsfilter, &h264parse, &queue, ]) .expect("failed to add the elements"); gst::Element::link_many([ &clocksync, &videoconvert, &videoscale, &videorate, &capsfilter, &x264enc, &h264_capsfilter, &h264parse, &queue, ]) .expect("failed to link the elements"); let queue_srcpad = queue.static_pad("src").unwrap(); let srcpad = gst::GhostPad::builder(gst::PadDirection::Src) .name("src") .build(); srcpad.set_target(Some(&queue_srcpad)).unwrap(); let clocksync_sinkpad = clocksync.static_pad("sink").unwrap(); let sinkpad = gst::GhostPad::builder(gst::PadDirection::Sink) .name("sink") .build(); sinkpad.set_target(Some(&clocksync_sinkpad)).unwrap(); bin.add_pad(&srcpad).unwrap(); bin.add_pad(&sinkpad).unwrap(); bin } fn audio_bin(bitrate: u32) -> gst::Bin { let bin = gst::Bin::new(); let clocksync = gst::ElementFactory::make("clocksync").build().unwrap(); let audioconvert = gst::ElementFactory::make("audioconvert").build().unwrap(); let audiorate = gst::ElementFactory::make("audiorate").build().unwrap(); let capsfilter = gst::ElementFactory::make("capsfilter").build().unwrap(); let audioenc = gst::ElementFactory::make("avenc_aac").build().unwrap(); let aacparse = gst::ElementFactory::make("aacparse").build().unwrap(); let caps = gst::Caps::from_str("audio/x-raw,channels=2,rate=48000,format=F32LE").unwrap(); capsfilter.set_property("caps", caps); audioenc.set_property("bitrate", bitrate as i32); bin.add_many([ &clocksync, &audioconvert, &audiorate, &capsfilter, &audioenc, &aacparse, ]) .expect("failed to add the elements"); gst::Element::link_many([ &clocksync, &audioconvert, &audiorate, &capsfilter, &audioenc, &aacparse, ]) .expect("failed to link the elements"); let aacparse_srcpad = aacparse.static_pad("src").unwrap(); let srcpad = gst::GhostPad::builder(gst::PadDirection::Src) .name("src") .build(); srcpad.set_target(Some(&aacparse_srcpad)).unwrap(); let clocksync_sinkpad = clocksync.static_pad("sink").unwrap(); let sinkpad = gst::GhostPad::builder(gst::PadDirection::Sink) .name("sink") .build(); sinkpad.set_target(Some(&clocksync_sinkpad)).unwrap(); bin.add_pad(&srcpad).unwrap(); bin.add_pad(&sinkpad).unwrap(); bin } fn multiple_audio_rendition_multiple_video_variant( pipeline: &gst::Pipeline, media_file_path: String, iframe: bool, ) { let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); hlssink.set_property_from_str("playlist-type", "event"); hlssink.set_property("target-duration", 10u32); if iframe { hlssink.set_property_from_str("muxer-type", "mpegts") } pipeline.add(&hlssink).unwrap(); let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); let audio_bin1 = audio_bin(256000); pipeline.add(&audio_bin1).unwrap(); let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let r = gst::Structure::builder("audio1") .field("media_type", "AUDIO") .field("uri", "hi-audio/audio.m3u8") .field("group_id", "aac") .field("language", "en") .field("name", "English") .field("default", true) .field("autoselect", false) .build(); audio1_pad.set_property("alternate-rendition", r); audio_bin1_pad.link(&audio1_pad).unwrap(); let audio_bin2 = audio_bin(128000); pipeline.add(&audio_bin2).unwrap(); let audio_bin2_sinkpad = audio_bin2.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin2_sinkpad).unwrap(); let audio_bin2_pad = audio_bin2.static_pad("src").unwrap(); let audio2_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let r = gst::Structure::builder("audio2") .field("media_type", "AUDIO") .field("uri", "mid-audio/audio.m3u8") .field("group_id", "aac") .field("language", "fr") .field("name", "French") .field("default", false) .field("autoselect", true) .build(); audio2_pad.set_property("alternate-rendition", r); audio_bin2_pad.link(&audio2_pad).unwrap(); let video_bin1 = video_bin(1920, 1080, 30, 2500, false); pipeline.add(&video_bin1).unwrap(); let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); let video_bin1_pad = video_bin1.static_pad("src").unwrap(); let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video1") .field("uri", "hi/video.m3u8") .field("bandwidth", 2500) .field("audio", "aac") .build(); video1_pad.set_property("variant", v); video_bin1_pad.link(&video1_pad).unwrap(); let video_bin2 = video_bin(1280, 720, 30, 1500, false); pipeline.add(&video_bin2).unwrap(); let video_bin2_sinkpad = video_bin2.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin2_sinkpad).unwrap(); let video_bin2_pad = video_bin2.static_pad("src").unwrap(); let video2_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video2") .field("uri", "mid/video.m3u8") .field("bandwidth", 1500) .field("audio", "aac") .build(); video2_pad.set_property("variant", v); video_bin2_pad.link(&video2_pad).unwrap(); let video_bin3 = video_bin(640, 360, 24, 700, false); pipeline.add(&video_bin3).unwrap(); let video_bin3_sinkpad = video_bin3.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin3_sinkpad).unwrap(); let video_bin3_pad = video_bin3.static_pad("src").unwrap(); let video3_pad = hlssink.request_pad_simple("video_%u").unwrap(); let mut v = gst::Structure::builder("video3") .field("uri", "low/video.m3u8") .field("bandwidth", 700) .build(); if !iframe { v.set_value("audio", "aac".to_send_value()); } else { v.set_value("is-i-frame", true.to_send_value()); } video3_pad.set_property("variant", v); video_bin3_pad.link(&video3_pad).unwrap(); } fn multiple_audio_rendition_single_video_variant( pipeline: &gst::Pipeline, media_file_path: String, ) { let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); hlssink.set_property_from_str("playlist-type", "event"); hlssink.set_property("target-duration", 10u32); pipeline.add(&hlssink).unwrap(); let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); let audio_bin1 = audio_bin(256000); pipeline.add(&audio_bin1).unwrap(); let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let r = gst::Structure::builder("audio1") .field("media_type", "AUDIO") .field("uri", "hi-audio/audio.m3u8") .field("group_id", "aac") .field("language", "en") .field("name", "English") .field("default", true) .field("autoselect", false) .build(); audio1_pad.set_property("alternate-rendition", r); audio_bin1_pad.link(&audio1_pad).unwrap(); let audio_bin2 = audio_bin(128000); pipeline.add(&audio_bin2).unwrap(); let audio_bin2_sinkpad = audio_bin2.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin2_sinkpad).unwrap(); let audio_bin2_pad = audio_bin2.static_pad("src").unwrap(); let audio2_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let r = gst::Structure::builder("audio2") .field("media_type", "AUDIO") .field("uri", "mid-audio/audio.m3u8") .field("group_id", "aac") .field("language", "fr") .field("name", "French") .field("default", false) .field("autoselect", true) .build(); audio2_pad.set_property("alternate-rendition", r); audio_bin2_pad.link(&audio2_pad).unwrap(); let audio_bin3 = audio_bin(64000); pipeline.add(&audio_bin3).unwrap(); let audio_bin3_sinkpad = audio_bin3.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin3_sinkpad).unwrap(); let audio_bin3_pad = audio_bin3.static_pad("src").unwrap(); let audio3_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let r = gst::Structure::builder("audio3") .field("media_type", "AUDIO") .field("uri", "low-audio/audio.m3u8") .field("group_id", "aac") .field("language", "da") .field("name", "Danish") .field("default", false) .field("autoselect", true) .build(); audio3_pad.set_property("alternate-rendition", r); audio_bin3_pad.link(&audio3_pad).unwrap(); let video_bin1 = video_bin(1920, 1080, 30, 2500, false); pipeline.add(&video_bin1).unwrap(); let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); let video_bin1_pad = video_bin1.static_pad("src").unwrap(); let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video1") .field("uri", "hi/video.m3u8") .field("bandwidth", 2500) .field("audio", "aac") .build(); video1_pad.set_property("variant", v); video_bin1_pad.link(&video1_pad).unwrap(); } fn single_audio_rendition_multiple_video_variant( pipeline: &gst::Pipeline, media_file_path: String, ) { let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); hlssink.set_property_from_str("playlist-type", "event"); hlssink.set_property("target-duration", 10u32); pipeline.add(&hlssink).unwrap(); let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); let audio_bin1 = audio_bin(256000); pipeline.add(&audio_bin1).unwrap(); let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let r = gst::Structure::builder("audio1") .field("media_type", "AUDIO") .field("uri", "hi-audio/audio.m3u8") .field("group_id", "aac") .field("language", "en") .field("name", "English") .field("default", true) .field("autoselect", false) .build(); audio1_pad.set_property("alternate-rendition", r); audio_bin1_pad.link(&audio1_pad).unwrap(); let video_bin1 = video_bin(1920, 1080, 30, 2500, false); pipeline.add(&video_bin1).unwrap(); let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); let video_bin1_pad = video_bin1.static_pad("src").unwrap(); let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video1") .field("uri", "hi/video.m3u8") .field("bandwidth", 2500) .field("audio", "aac") .build(); video1_pad.set_property("variant", v); video_bin1_pad.link(&video1_pad).unwrap(); let video_bin2 = video_bin(1280, 720, 30, 1500, false); pipeline.add(&video_bin2).unwrap(); let video_bin2_sinkpad = video_bin2.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin2_sinkpad).unwrap(); let video_bin2_pad = video_bin2.static_pad("src").unwrap(); let video2_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video2") .field("uri", "mid/video.m3u8") .field("bandwidth", 1500) .field("audio", "aac") .build(); video2_pad.set_property("variant", v); video_bin2_pad.link(&video2_pad).unwrap(); let video_bin3 = video_bin(640, 360, 24, 700, false); pipeline.add(&video_bin3).unwrap(); let video_bin3_sinkpad = video_bin3.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin3_sinkpad).unwrap(); let video_bin3_pad = video_bin3.static_pad("src").unwrap(); let video3_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video3") .field("uri", "low/video.m3u8") .field("audio", "aac") .field("bandwidth", 700) .build(); video3_pad.set_property("variant", v); video_bin3_pad.link(&video3_pad).unwrap(); } fn single_audio_only_variant_multiple_video_variant_with_audio_video_muxed( pipeline: &gst::Pipeline, media_file_path: String, ) { let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); hlssink.set_property_from_str("playlist-type", "event"); hlssink.set_property("target-duration", 10u32); hlssink.set_property_from_str("muxer-type", "mpegts"); pipeline.add(&hlssink).unwrap(); let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); let video_bin1 = video_bin(1920, 1080, 30, 2500, false); pipeline.add(&video_bin1).unwrap(); let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); let video_bin1_pad = video_bin1.static_pad("src").unwrap(); let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video1") .field("uri", "hi/video.m3u8") .field("bandwidth", 2500) .field("audio", "aac") .build(); video1_pad.set_property("variant", v); video_bin1_pad.link(&video1_pad).unwrap(); let video_bin2 = video_bin(1280, 720, 30, 1500, false); pipeline.add(&video_bin2).unwrap(); let video_bin2_sinkpad = video_bin2.static_pad("sink").unwrap(); let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); videotee_srcpad.link(&video_bin2_sinkpad).unwrap(); let video_bin2_pad = video_bin2.static_pad("src").unwrap(); let video2_pad = hlssink.request_pad_simple("video_%u").unwrap(); let v = gst::Structure::builder("video2") .field("uri", "mid/video.m3u8") .field("bandwidth", 1500) .field("audio", "aac") .build(); video2_pad.set_property("variant", v); video_bin2_pad.link(&video2_pad).unwrap(); let audio_bin1 = audio_bin(256000); pipeline.add(&audio_bin1).unwrap(); let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let v = gst::Structure::builder("audio1") .field("uri", "hi/video.m3u8") .field("bandwidth", 256000) .build(); audio1_pad.set_property("variant", v); audio_bin1_pad.link(&audio1_pad).unwrap(); let audio_bin2 = audio_bin(128000); pipeline.add(&audio_bin2).unwrap(); let audio_bin2_sinkpad = audio_bin2.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin2_sinkpad).unwrap(); let audio_bin2_pad = audio_bin2.static_pad("src").unwrap(); let audio2_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let v = gst::Structure::builder("audio2") .field("uri", "mid-audio/audio.m3u8") .field("bandwidth", 128000) .build(); audio2_pad.set_property("variant", v); audio_bin2_pad.link(&audio2_pad).unwrap(); let audio_bin3 = audio_bin(64000); pipeline.add(&audio_bin3).unwrap(); let audio_bin3_sinkpad = audio_bin3.static_pad("sink").unwrap(); let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); audiotee_srcpad.link(&audio_bin3_sinkpad).unwrap(); let audio_bin3_pad = audio_bin3.static_pad("src").unwrap(); let audio3_pad = hlssink.request_pad_simple("audio_%u").unwrap(); let v = gst::Structure::builder("audio3") .field("uri", "low-audio/audio-only.m3u8") .field("bandwidth", 64000) .build(); audio3_pad.set_property("variant", v); audio_bin3_pad.link(&audio3_pad).unwrap(); } fn main() { let cli = Cli::parse(); gst::init().unwrap(); let pipeline = gst::Pipeline::new(); let context = glib::MainContext::default(); let main_loop = glib::MainLoop::new(Some(&context), false); match cli.command { Some(c @ Commands::MultipleAudioRenditionMultipleVideoVariant) => { println!("Generating HLS playlist for: {:?}", c); multiple_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path, false); } Some(c @ Commands::MultipleAudioRenditionMultipleVideoVariantWithIframe) => { println!("Generating HLS playlist for: {:?}", c); multiple_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path, true); } Some(c @ Commands::MultipleAudioRenditionSingleVideoVariant) => { println!("Generating HLS playlist for: {:?}", c); multiple_audio_rendition_single_video_variant(&pipeline, cli.media_file_path); } Some(c @ Commands::SingleAudioRenditionMultipleVideoVariant) => { println!("Generating HLS playlist for: {:?}", c); single_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path); } Some(c @ Commands::SingleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed) => { println!("Generating HLS playlist for: {:?}", c); single_audio_only_variant_multiple_video_variant_with_audio_video_muxed( &pipeline, cli.media_file_path, ); } None => { println!( "Generating HLS playlist for: {:?}", Commands::MultipleAudioRenditionMultipleVideoVariant ); multiple_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path, false); } } pipeline.set_state(gst::State::Playing).unwrap(); let weak_pipeline = pipeline.downgrade(); // Capture pipeline graph 5 secs later to correctly capture STATE changes. glib::timeout_add_seconds_once(5, move || { let pipeline = match weak_pipeline.upgrade() { Some(pipeline) => pipeline, None => return, }; pipeline.debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), "gst-hlssink4-pipeline"); }); let bus = pipeline.bus().unwrap(); let l_clone = main_loop.clone(); let _bus_watch = bus .add_watch(move |_, msg| { use gst::MessageView; match msg.view() { MessageView::Eos(..) => { println!("\nReceived End of Stream, quitting..."); l_clone.quit(); } MessageView::Error(err) => { gst::error!(CAT, obj: &err.src().unwrap(), "Error from {:?}: {} ({:?})", err.src().map(|s| s.path_string()), err.error(), err.debug() ); l_clone.quit(); } _ => (), }; glib::ControlFlow::Continue }) .expect("Failed to add bus watch"); let l_clone = main_loop.clone(); ctrlc::set_handler({ let pipeline_weak = pipeline.downgrade(); move || { let pipeline = pipeline_weak.upgrade().unwrap(); pipeline.set_state(gst::State::Null).unwrap(); println!("\nReceived Ctrl-c, quitting..."); l_clone.quit(); } }) .unwrap(); main_loop.run(); pipeline.set_state(gst::State::Null).unwrap(); }