use std::num::NonZeroUsize;
use std::time::Duration;
use broadcast_common::Timestamp;
use hls_runtime::server::{
BlockingQuery, DEFAULT_TRACK_ID, HlsBody, HlsOrigin, HlsRequest, master_playlist_m3u8,
};
use media_plane::egress::{AwaitPolicy, EgressResponse, ServedEgress};
use media_plane::trunk::{PartEntry, SegmentEntry, Trunk, TrunkConfig};
use transmux::SegmentMeta;
const TARGET_DURATION_SECS: f64 = 1.0;
const PART_TARGET_MS: u32 = 500;
const WINDOW_SEGMENTS: usize = 4;
fn nz(n: usize) -> NonZeroUsize {
NonZeroUsize::new(n).expect("example capacity must be non-zero")
}
fn resolve(origin: &HlsOrigin, request: HlsRequest) -> EgressResponse<HlsBody> {
origin.resolve(
request,
Timestamp::from_nanos(0),
AwaitPolicy::new(Timestamp::from_nanos(0)),
)
}
fn main() {
let trunk = Trunk::new(TrunkConfig::new(nz(16), nz(4), nz(8), nz(4), nz(16)));
let writer = trunk
.segment_writer()
.expect("first (and only) segment writer");
let origin = HlsOrigin::builder(std::sync::Arc::clone(&trunk))
.target_duration_secs(TARGET_DURATION_SECS)
.window_segments(nz(WINDOW_SEGMENTS))
.low_latency(PART_TARGET_MS)
.build()
.expect("both required fields set");
origin.set_init(vec![0xAA; 32]);
writer.publish_part(PartEntry::new(
vec![0x01; 16],
1,
0,
Duration::from_millis(500),
true,
));
writer.publish_part(PartEntry::new(
vec![0x02; 16],
1,
1,
Duration::from_millis(500),
false,
));
writer.publish_segment(SegmentEntry::new(
vec![0x03; 32],
1,
Duration::from_secs(1),
Timestamp::from_nanos(0),
SegmentMeta {
discontinuous: false,
},
));
writer.publish_part(PartEntry::new(
vec![0x04; 16],
2,
0,
Duration::from_millis(500),
true,
));
println!("--- master.m3u8 ---");
println!("{}", master_playlist_m3u8("media.m3u8"));
println!("--- media.m3u8 ---");
match resolve(
&origin,
HlsRequest::Playlist {
track_id: DEFAULT_TRACK_ID,
query: BlockingQuery::default(),
},
) {
EgressResponse::Ready {
body: HlsBody::Playlist(m),
..
} => println!("{m}"),
other => panic!("expected Ready(Playlist), got {other:?}"),
}
let outcome = resolve(
&origin,
HlsRequest::Playlist {
track_id: DEFAULT_TRACK_ID,
query: BlockingQuery::default(),
},
);
assert!(matches!(
outcome,
EgressResponse::Ready {
body: HlsBody::Playlist(_),
..
}
));
println!("resolve(Playlist, no query) -> Ready");
let outcome = resolve(
&origin,
HlsRequest::Playlist {
track_id: DEFAULT_TRACK_ID,
query: BlockingQuery {
hls_msn: Some(5),
hls_part: None,
},
},
);
assert_eq!(outcome, EgressResponse::NotFound);
println!("resolve(Playlist, _HLS_msn=5) -> NotFound (Await's patience already expired)");
let outcome = resolve(
&origin,
HlsRequest::Playlist {
track_id: DEFAULT_TRACK_ID,
query: BlockingQuery {
hls_msn: Some(999),
hls_part: None,
},
},
);
assert!(matches!(outcome, EgressResponse::BadRequest { .. }));
println!("resolve(Playlist, _HLS_msn=999) -> BadRequest (abuse bound)");
match resolve(
&origin,
HlsRequest::Resource {
name: "init-1.mp4".to_string(),
},
) {
EgressResponse::Ready { .. } => println!("resolve(Resource, init-1.mp4) -> Ready"),
other => panic!("expected Ready, got {other:?}"),
}
match resolve(
&origin,
HlsRequest::Resource {
name: "seg-1-1.m4s".to_string(),
},
) {
EgressResponse::Ready { .. } => println!("resolve(Resource, seg-1-1.m4s) -> Ready"),
other => panic!("expected Ready, got {other:?}"),
}
match resolve(
&origin,
HlsRequest::Resource {
name: "part-1-2.0.m4s".to_string(),
},
) {
EgressResponse::Ready { .. } => println!("resolve(Resource, part-1-2.0.m4s) -> Ready"),
other => panic!("expected Ready, got {other:?}"),
}
match resolve(
&origin,
HlsRequest::Resource {
name: "part-1-2.1.m4s".to_string(),
},
) {
EgressResponse::NotFound => {
println!(
"resolve(Resource, part-1-2.1.m4s) -> NotFound (Await's patience already expired)"
)
}
other => panic!("expected NotFound, got {other:?}"),
}
match resolve(
&origin,
HlsRequest::Resource {
name: "nope.txt".to_string(),
},
) {
EgressResponse::NotFound => println!("resolve(Resource, nope.txt) -> NotFound"),
other => panic!("expected NotFound, got {other:?}"),
}
}