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 {
143 hls_msn: Some(5),
144 hls_part: None,
145 },
146 },
147 );
148 assert_eq!(outcome, EgressResponse::NotFound);
149 println!("resolve(Playlist, _HLS_msn=5) -> NotFound (Await's patience already expired)");
150
151 let outcome = resolve(
154 &origin,
155 HlsRequest::Playlist {
156 track_id: DEFAULT_TRACK_ID,
157 query: BlockingQuery {
158 hls_msn: Some(999),
159 hls_part: None,
160 },
161 },
162 );
163 assert!(matches!(outcome, EgressResponse::BadRequest { .. }));
164 println!("resolve(Playlist, _HLS_msn=999) -> BadRequest (abuse bound)");
165
166 match resolve(
168 &origin,
169 HlsRequest::Resource {
170 name: "init-1.mp4".to_string(),
171 },
172 ) {
173 EgressResponse::Ready { .. } => println!("resolve(Resource, init-1.mp4) -> Ready"),
174 other => panic!("expected Ready, got {other:?}"),
175 }
176 match resolve(
177 &origin,
178 HlsRequest::Resource {
179 name: "seg-1-1.m4s".to_string(),
180 },
181 ) {
182 EgressResponse::Ready { .. } => println!("resolve(Resource, seg-1-1.m4s) -> Ready"),
183 other => panic!("expected Ready, got {other:?}"),
184 }
185 match resolve(
187 &origin,
188 HlsRequest::Resource {
189 name: "part-1-2.0.m4s".to_string(),
190 },
191 ) {
192 EgressResponse::Ready { .. } => println!("resolve(Resource, part-1-2.0.m4s) -> Ready"),
193 other => panic!("expected Ready, got {other:?}"),
194 }
195 match resolve(
199 &origin,
200 HlsRequest::Resource {
201 name: "part-1-2.1.m4s".to_string(),
202 },
203 ) {
204 EgressResponse::NotFound => {
205 println!(
206 "resolve(Resource, part-1-2.1.m4s) -> NotFound (Await's patience already expired)"
207 )
208 }
209 other => panic!("expected NotFound, got {other:?}"),
210 }
211 match resolve(
213 &origin,
214 HlsRequest::Resource {
215 name: "nope.txt".to_string(),
216 },
217 ) {
218 EgressResponse::NotFound => println!("resolve(Resource, nope.txt) -> NotFound"),
219 other => panic!("expected NotFound, got {other:?}"),
220 }
221}