use std::collections::VecDeque;
use std::convert::Infallible;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::Duration;
use broadcast_common::{Demand, Stage, Timestamp};
use media_plane::ingress::{
Dialer, HandshakePolicy, IngestDriver, IngestSession, ProgramId, SessionEvent,
};
use media_plane::trunk::{RetentionClass, TrunkConfig};
use multimux::config::{Config, InputSpec};
use multimux::registry::InputCtx;
use multimux::source::{DriverProgress, advance_route};
use multimux::{Backoff, RouteHandle, SchemeRegistry, supervise_driver};
use transmux::avc_config_from_sprop;
use transmux::pipeline::{CodecConfig, Sample, TrackSpec};
const SPROP: &str = "Z0IAKeKQFAe2AtwEBAaQeJEV,aM48gA==";
const VIDEO_TIMESCALE: u32 = 90_000;
const FRAME_DUR: u32 = VIDEO_TIMESCALE / 30;
const FRAME_COUNT: u32 = 60;
const SYNC_INTERVAL_FRAMES: u32 = 30;
fn track_spec() -> TrackSpec {
let config = avc_config_from_sprop(SPROP).expect("valid sprop");
TrackSpec::new(
1,
VIDEO_TIMESCALE,
CodecConfig::Avc {
config,
width: 0,
height: 0,
},
)
}
struct DemoSession {
pending: VecDeque<SessionEvent>,
sent: bool,
}
impl DemoSession {
fn new() -> Self {
let mut pending = VecDeque::new();
pending.push_back(SessionEvent::Established);
DemoSession {
pending,
sent: false,
}
}
}
impl Stage for DemoSession {
type In<'a> = &'a [u8];
type Out = SessionEvent;
type Error = Infallible;
fn demand(&self) -> Demand {
Demand::new(1)
}
fn feed(&mut self, _input: &[u8], _now: Timestamp) -> Result<(), Infallible> {
if !self.sent {
self.sent = true;
self.pending.push_back(SessionEvent::NewProgram {
program: ProgramId(0),
tracks: vec![track_spec()],
});
for i in 0..FRAME_COUNT {
let is_sync = i % SYNC_INTERVAL_FRAMES == 0;
let data = vec![0xAAu8.wrapping_add((i % 251) as u8); 32];
let sample = Sample::new(
data,
Some(i64::from(i) * i64::from(FRAME_DUR)),
Some(i64::from(i) * i64::from(FRAME_DUR)),
Some(FRAME_DUR),
is_sync,
);
self.pending.push_back(SessionEvent::Sample {
program: ProgramId(0),
track_id: 1,
retention: RetentionClass::Timed,
sample,
});
}
}
Ok(())
}
fn poll(&mut self) -> Option<SessionEvent> {
self.pending.pop_front()
}
fn next_deadline(&self) -> Option<Timestamp> {
None
}
fn on_deadline(&mut self, _now: Timestamp) {}
fn finish(&mut self) -> Result<(), Infallible> {
Ok(())
}
}
impl IngestSession for DemoSession {
type Request = Infallible;
}
#[derive(Clone, Copy, Debug, Default)]
struct DemoDialer;
impl Dialer for DemoDialer {
type Session = DemoSession;
type Error = Infallible;
fn dial(&mut self) -> Result<DemoSession, Infallible> {
Ok(DemoSession::new())
}
}
fn nz(n: usize) -> NonZeroUsize {
NonZeroUsize::new(n).expect("non-zero capacity")
}
async fn run_demo(route_handle: Arc<RouteHandle>) -> multimux::Result<()> {
let mut dialer = DemoDialer;
let session = dialer.dial().unwrap_or_else(|never| match never {});
let trunk_config = TrunkConfig::new(nz(64), nz(16), nz(8), nz(64), nz(64));
let handshake = HandshakePolicy::establish_by(Timestamp::from_nanos(u64::MAX));
let mut driver: IngestDriver<DemoSession> = IngestDriver::new(
session,
trunk_config,
handshake,
media_plane::DEFAULT_MAX_PROGRAMS,
);
let mut progress = DriverProgress::new();
driver.feed(&[], Timestamp::from_nanos(0));
advance_route(&driver, &route_handle, &mut progress);
driver.finish();
advance_route(&driver, &route_handle, &mut progress);
Ok(())
}
fn build_registry() -> SchemeRegistry {
let mut registry = SchemeRegistry::new();
registry.register_input(
"demo",
Arc::new(|ctx: InputCtx| {
Ok(tokio::spawn(supervise_driver(
run_demo,
ctx.store,
Backoff::production_default(),
ctx.name,
ctx.shutdown_rx,
)))
}),
);
registry
}
#[tokio::main]
async fn main() {
let registry = build_registry();
assert!(registry.input("demo").is_some());
assert!(registry.input("nope").is_none());
let json = r#"{
"routes": [
{
"name": "cam1",
"input": { "type": "custom", "type_tag": "demo", "params": {} }
}
]
}"#;
let config: Config = serde_json::from_str(json).expect("valid JSON");
config
.validate()
.expect("a Custom input is always structurally valid");
match &config.routes[0].input {
InputSpec::Custom { type_tag, .. } => assert_eq!(type_tag, "demo"),
other => panic!("expected InputSpec::Custom, got {other:?}"),
}
let store = Arc::new(RouteHandle::new(1.0, 500, 8));
let (_shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
let factory = registry.input("demo").expect("registered above");
let handle = factory(InputCtx {
name: "cam1".to_string(),
params: serde_json::Value::Null,
store: store.clone(),
target_duration_secs: 1.0,
part_target_ms: 500,
shutdown_rx,
})
.expect("factory must succeed");
let landed = tokio::time::timeout(Duration::from_secs(60), async {
loop {
if store.init_bytes(ProgramId(0)).is_some()
&& !store.window_segments(ProgramId(0)).is_empty()
{
break;
}
tokio::time::sleep(Duration::from_millis(5)).await;
}
})
.await
.is_ok();
assert!(
landed,
"the \"demo\" scheme's synthetic media must land real init bytes AND at least one \
closed segment in the store"
);
handle.abort();
println!(
"custom_scheme: registered a \"demo\" input scheme with zero multimux edits; \
its factory drove a real Dialer/IngestSession through supervise_driver and \
landed real, servable LL-HLS media in the route store."
);
}