2021-10-15 14:57:55 +00:00
|
|
|
// Copyright (C) 2021 OneStream Live <guillaume.desmottes@onestream.live>
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
|
|
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
|
|
|
// <https://mozilla.org/MPL/2.0/>.
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
path::Path,
|
|
|
|
sync::{Arc, Mutex},
|
|
|
|
};
|
|
|
|
|
2022-09-27 10:30:38 +00:00
|
|
|
use clap::Parser;
|
2021-10-15 14:57:55 +00:00
|
|
|
use gst::prelude::*;
|
|
|
|
|
2022-09-27 10:30:38 +00:00
|
|
|
#[derive(Debug, Parser)]
|
2022-09-29 06:48:53 +00:00
|
|
|
#[clap(version, author, about = "An example of uriplaylistbin usage.")]
|
2021-10-15 14:57:55 +00:00
|
|
|
struct Opt {
|
2022-09-29 06:48:53 +00:00
|
|
|
#[clap(short, default_value = "1")]
|
2021-10-15 14:57:55 +00:00
|
|
|
iterations: u32,
|
|
|
|
uris: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_pipeline(uris: Vec<String>, iterations: u32) -> anyhow::Result<gst::Pipeline> {
|
2022-10-22 16:06:29 +00:00
|
|
|
let pipeline = gst::Pipeline::default();
|
2022-10-19 16:18:43 +00:00
|
|
|
let playlist = gst::ElementFactory::make("uriplaylistbin")
|
|
|
|
.property("uris", &uris)
|
|
|
|
.property("iterations", &iterations)
|
|
|
|
.build()?;
|
2021-10-15 14:57:55 +00:00
|
|
|
|
|
|
|
pipeline.add(&playlist)?;
|
|
|
|
|
|
|
|
let sink_bins = Arc::new(Mutex::new(HashMap::new()));
|
|
|
|
let sink_bins_clone = sink_bins.clone();
|
|
|
|
|
|
|
|
let pipeline_weak = pipeline.downgrade();
|
|
|
|
playlist.connect_pad_added(move |_playlist, src_pad| {
|
|
|
|
let pipeline = match pipeline_weak.upgrade() {
|
|
|
|
None => return,
|
|
|
|
Some(pipeline) => pipeline,
|
|
|
|
};
|
|
|
|
let pad_name = src_pad.name();
|
|
|
|
|
|
|
|
let sink = if pad_name.starts_with("audio") {
|
|
|
|
gst::parse_bin_from_description("audioconvert ! audioresample ! autoaudiosink", true)
|
|
|
|
.unwrap()
|
|
|
|
} else if pad_name.starts_with("video") {
|
|
|
|
gst::parse_bin_from_description("videoconvert ! autovideosink", true).unwrap()
|
|
|
|
} else {
|
|
|
|
unimplemented!();
|
|
|
|
};
|
|
|
|
|
|
|
|
pipeline.add(&sink).unwrap();
|
|
|
|
sink.sync_state_with_parent().unwrap();
|
|
|
|
|
|
|
|
let sink_pad = sink.static_pad("sink").unwrap();
|
|
|
|
src_pad.link(&sink_pad).unwrap();
|
|
|
|
|
|
|
|
sink_bins.lock().unwrap().insert(pad_name, sink);
|
|
|
|
});
|
|
|
|
|
|
|
|
let pipeline_weak = pipeline.downgrade();
|
|
|
|
playlist.connect_pad_removed(move |_playlist, pad| {
|
|
|
|
let pipeline = match pipeline_weak.upgrade() {
|
|
|
|
None => return,
|
|
|
|
Some(pipeline) => pipeline,
|
|
|
|
};
|
|
|
|
|
|
|
|
// remove sink bin that was handling the pad
|
|
|
|
let sink_bins = sink_bins_clone.lock().unwrap();
|
|
|
|
let sink = sink_bins.get(&pad.name()).unwrap();
|
|
|
|
pipeline.remove(sink).unwrap();
|
|
|
|
let _ = sink.set_state(gst::State::Null);
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(pipeline)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
|
|
gst::init().unwrap();
|
|
|
|
gsturiplaylistbin::plugin_register_static().expect("Failed to register uriplaylistbin plugin");
|
|
|
|
|
2022-09-27 10:30:38 +00:00
|
|
|
let opt = Opt::parse();
|
2021-10-15 14:57:55 +00:00
|
|
|
if opt.uris.is_empty() {
|
|
|
|
anyhow::bail!("Need at least one URI to play");
|
|
|
|
}
|
|
|
|
|
|
|
|
let uris = opt
|
|
|
|
.uris
|
|
|
|
.into_iter()
|
|
|
|
.map(|uri| {
|
|
|
|
let p = Path::new(&uri);
|
|
|
|
match p.canonicalize() {
|
2021-12-02 20:31:52 +00:00
|
|
|
Ok(p) => format!("file://{}", p.to_str().unwrap()),
|
2021-10-15 14:57:55 +00:00
|
|
|
_ => uri,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
{
|
|
|
|
let pipeline = create_pipeline(uris, opt.iterations)?;
|
|
|
|
|
|
|
|
pipeline
|
|
|
|
.set_state(gst::State::Playing)
|
|
|
|
.expect("Unable to set the pipeline to the `Playing` state");
|
|
|
|
|
|
|
|
let bus = pipeline.bus().unwrap();
|
|
|
|
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
|
|
|
use gst::MessageView;
|
|
|
|
match msg.view() {
|
|
|
|
MessageView::Error(err) => {
|
|
|
|
eprintln!(
|
|
|
|
"Error received from element {:?}: {}",
|
|
|
|
err.src().map(|s| s.path_string()),
|
|
|
|
err.error()
|
|
|
|
);
|
|
|
|
eprintln!("Debugging information: {:?}", err.debug());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
MessageView::Eos(..) => break,
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pipeline
|
|
|
|
.set_state(gst::State::Null)
|
|
|
|
.expect("Unable to set the pipeline to the `Null` state");
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
gst::deinit();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|