1use std::num::NonZeroUsize;
19use std::time::Duration;
20
21use broadcast_common::Timestamp;
22use hls_runtime::server::{
23 BlockingQuery, DEFAULT_TRACK_ID, HlsBody, HlsOrigin, HlsRequest, master_playlist_m3u8,
24};
25use media_plane::egress::{AwaitPolicy, EgressResponse, ServedEgress};
26use media_plane::trunk::{PartEntry, SegmentEntry, Trunk, TrunkConfig};
27use transmux::SegmentMeta;
28
29const TARGET_DURATION_SECS: f64 = 1.0;
31const PART_TARGET_MS: u32 = 500;
33const WINDOW_SEGMENTS: usize = 4;
36
37fn nz(n: usize) -> NonZeroUsize {
38 NonZeroUsize::new(n).expect("example capacity must be non-zero")
39}
40
41fn resolve(origin: &HlsOrigin, request: HlsRequest) -> EgressResponse<HlsBody> {
45 origin.resolve(
46 request,
47 Timestamp::from_nanos(0),
48 AwaitPolicy::new(Timestamp::from_nanos(0)),
49 )
50}
51
52fn main() {
53 let trunk = Trunk::new(TrunkConfig::new(nz(16), nz(4), nz(8), nz(4), nz(16)));
54 let writer = trunk
55 .segment_writer()
56 .expect("first (and only) segment writer");
57 let origin = HlsOrigin::builder(std::sync::Arc::clone(&trunk))
58 .target_duration_secs(TARGET_DURATION_SECS)
59 .window_segments(nz(WINDOW_SEGMENTS))
60 .low_latency(PART_TARGET_MS)
61 .build()
62 .expect("both required fields set");
63 origin.set_init(vec![0xAA; 32]);
64
65 writer.publish_part(PartEntry::new(
67 vec![0x01; 16],
68 1,
69 0,
70 Duration::from_millis(500),
71 true,
72 ));
73 writer.publish_part(PartEntry::new(
74 vec![0x02; 16],
75 1,
76 1,
77 Duration::from_millis(500),
78 false,
79 ));
80 writer.publish_segment(SegmentEntry::new(
81 vec![0x03; 32],
82 1,
83 Duration::from_secs(1),
84 Timestamp::from_nanos(0),
85 SegmentMeta {
86 discontinuous: false,
87 },
88 ));
89
90 writer.publish_part(PartEntry::new(
92 vec![0x04; 16],
93 2,
94 0,
95 Duration::from_millis(500),
96 true,
97 ));
98
99 println!("--- master.m3u8 ---");
100 println!("{}", master_playlist_m3u8("media.m3u8"));
101
102 println!("--- media.m3u8 ---");
103 match resolve(
104 &origin,
105 HlsRequest::Playlist {
106 track_id: DEFAULT_TRACK_ID,
107 query: BlockingQuery::default(),
108 },
109 ) {
110 EgressResponse::Ready {
111 body: HlsBody::Playlist(m),
112 ..
113 } => println!("{m}"),
114 other => panic!("expected Ready(Playlist), got {other:?}"),
115 }
116
117 let outcome = resolve(
119 &origin,
120 HlsRequest::Playlist {
121 track_id: DEFAULT_TRACK_ID,
122 query: BlockingQuery::default(),
123 },
124 );
125 assert!(matches!(
126 outcome,
127 EgressResponse::Ready {
128 body: HlsBody::Playlist(_),
129 ..
130 }
131 ));
132 println!("resolve(Playlist, no query) -> Ready");
133
134 let outcome = resolve(
139 &origin,
140 HlsRequest::Playlist {
141 track_id: DEFAULT_TRACK_ID,
142 query: BlockingQuery {
148 hls_msn: Some(3),
149 hls_part: None,
150 },
151 },
152 );
153 assert_eq!(outcome, EgressResponse::NotFound);
154 println!("resolve(Playlist, _HLS_msn=3) -> NotFound (Await's patience already expired)");
155
156 let outcome = resolve(
159 &origin,
160 HlsRequest::Playlist {
161 track_id: DEFAULT_TRACK_ID,
162 query: BlockingQuery {
163 hls_msn: Some(999),
164 hls_part: None,
165 },
166 },
167 );
168 assert!(matches!(outcome, EgressResponse::BadRequest { .. }));
169 println!("resolve(Playlist, _HLS_msn=999) -> BadRequest (abuse bound)");
170
171 match resolve(
173 &origin,
174 HlsRequest::Resource {
175 name: "init-1.mp4".to_string(),
176 },
177 ) {
178 EgressResponse::Ready { .. } => println!("resolve(Resource, init-1.mp4) -> Ready"),
179 other => panic!("expected Ready, got {other:?}"),
180 }
181 match resolve(
182 &origin,
183 HlsRequest::Resource {
184 name: "seg-1-1.m4s".to_string(),
185 },
186 ) {
187 EgressResponse::Ready { .. } => println!("resolve(Resource, seg-1-1.m4s) -> Ready"),
188 other => panic!("expected Ready, got {other:?}"),
189 }
190 match resolve(
192 &origin,
193 HlsRequest::Resource {
194 name: "part-1-2.0.m4s".to_string(),
195 },
196 ) {
197 EgressResponse::Ready { .. } => println!("resolve(Resource, part-1-2.0.m4s) -> Ready"),
198 other => panic!("expected Ready, got {other:?}"),
199 }
200 match resolve(
204 &origin,
205 HlsRequest::Resource {
206 name: "part-1-2.1.m4s".to_string(),
207 },
208 ) {
209 EgressResponse::NotFound => {
210 println!(
211 "resolve(Resource, part-1-2.1.m4s) -> NotFound (Await's patience already expired)"
212 )
213 }
214 other => panic!("expected NotFound, got {other:?}"),
215 }
216 match resolve(
218 &origin,
219 HlsRequest::Resource {
220 name: "nope.txt".to_string(),
221 },
222 ) {
223 EgressResponse::NotFound => println!("resolve(Resource, nope.txt) -> NotFound"),
224 other => panic!("expected NotFound, got {other:?}"),
225 }
226}