pub struct HlsOriginBuilder { /* private fields */ }std only.Expand description
Fluent builder for HlsOrigin (issue #873) — replaces the old
four-positional HlsOrigin::new (deleted; this crate is at 0.4.0
unpublished, so there is no compatibility burden), which could not
express “classic HLS, no low latency” at all since part_target_ms was a
mandatory positional argument.
let classic_ts = HlsOrigin::builder(Arc::clone(&trunk))
.target_duration_secs(6.0)
.window_segments(nz(4))
.container(Container::MpegTs)
// `.low_latency(..)` omitted entirely -> classic HLS.
.build()
.expect("both required fields were set");Implementations§
Source§impl HlsOriginBuilder
impl HlsOriginBuilder
Sourcepub fn target_duration_secs(self, target_duration_secs: f64) -> Self
pub fn target_duration_secs(self, target_duration_secs: f64) -> Self
#EXT-X-TARGETDURATION’s configured floor (RFC 8216bis §4.4.3.1) —
required; HlsOriginBuilder::build errors if this is never called.
The actually-rendered value is raised to the largest real segment
duration seen, if that ever exceeds this (see render_playlist).
Examples found in repository?
36fn canned_playlist() -> String {
37 let trunk = Trunk::new(TrunkConfig::new(nz(16), nz(4), nz(8), nz(4), nz(16)));
38 let writer = trunk
39 .segment_writer()
40 .expect("first (and only) segment writer");
41 let origin = HlsOrigin::builder(std::sync::Arc::clone(&trunk))
42 .target_duration_secs(1.0)
43 .window_segments(nz(4))
44 .low_latency(500)
45 .build()
46 .expect("both required fields set");
47 origin.set_init(vec![0xAA; 32]);
48
49 writer.publish_part(PartEntry::new(
50 vec![0x01; 16],
51 1,
52 0,
53 Duration::from_millis(500),
54 true,
55 ));
56 writer.publish_segment(SegmentEntry::new(
57 vec![0x02; 32],
58 1,
59 Duration::from_secs(1),
60 Timestamp::from_nanos(0),
61 SegmentMeta {
62 discontinuous: false,
63 },
64 ));
65 writer.publish_part(PartEntry::new(
66 vec![0x03; 16],
67 2,
68 0,
69 Duration::from_millis(500),
70 true,
71 ));
72
73 match origin.resolve(
74 HlsRequest::Playlist {
75 track_id: DEFAULT_TRACK_ID,
76 query: BlockingQuery::default(),
77 },
78 Timestamp::from_nanos(0),
79 AwaitPolicy::new(Timestamp::from_nanos(0)),
80 ) {
81 EgressResponse::Ready {
82 body: HlsBody::Playlist(m),
83 ..
84 } => m,
85 other => panic!("expected Ready(Playlist), got {other:?}"),
86 }
87}More examples
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 // Segment 1 closes with two parts.
66 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 // Segment 2 is still open, with only its first part landed so far.
91 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 // A plain (non-blocking) request is Ready immediately.
118 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 // A blocking-reload request for a segment that hasn't closed yet: with
135 // `await_policy`'s deadline already at `now`, this immediately reports
136 // the awaited condition has run out of patience rather than serving a
137 // fabricated Ready.
138 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 // A `_HLS_msn` unreasonably far beyond the live edge is rejected outright
152 // (RFC 8216bis §6.2.5.2 abuse prevention) rather than ever Await-ing.
153 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 // `Resource`: the init segment and the closed segment are Ready...
167 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 // ...a live part of the still-open segment is Ready too...
186 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 // ...a preload-hinted part not yet produced reports NotFound once this
196 // call's `await_policy` has already expired (a real HTTP adapter would
197 // instead give it a real deadline and block on `Trunk::listen()`)...
198 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 // ...and an unrecognised filename is a plain 404.
212 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}Sourcepub fn window_segments(self, window_segments: NonZeroUsize) -> Self
pub fn window_segments(self, window_segments: NonZeroUsize) -> Self
How many closed segments this origin advertises in a rendered Media
Playlist — required; independent of
media_plane::trunk::TrunkConfig::segment_capacity (the Trunk’s
own retention bound): a caller may legitimately want a shorter
advertised window than the Trunk retains for other consumers (e.g. a
DVR SegmentEgress reading the same Trunk).
Examples found in repository?
36fn canned_playlist() -> String {
37 let trunk = Trunk::new(TrunkConfig::new(nz(16), nz(4), nz(8), nz(4), nz(16)));
38 let writer = trunk
39 .segment_writer()
40 .expect("first (and only) segment writer");
41 let origin = HlsOrigin::builder(std::sync::Arc::clone(&trunk))
42 .target_duration_secs(1.0)
43 .window_segments(nz(4))
44 .low_latency(500)
45 .build()
46 .expect("both required fields set");
47 origin.set_init(vec![0xAA; 32]);
48
49 writer.publish_part(PartEntry::new(
50 vec![0x01; 16],
51 1,
52 0,
53 Duration::from_millis(500),
54 true,
55 ));
56 writer.publish_segment(SegmentEntry::new(
57 vec![0x02; 32],
58 1,
59 Duration::from_secs(1),
60 Timestamp::from_nanos(0),
61 SegmentMeta {
62 discontinuous: false,
63 },
64 ));
65 writer.publish_part(PartEntry::new(
66 vec![0x03; 16],
67 2,
68 0,
69 Duration::from_millis(500),
70 true,
71 ));
72
73 match origin.resolve(
74 HlsRequest::Playlist {
75 track_id: DEFAULT_TRACK_ID,
76 query: BlockingQuery::default(),
77 },
78 Timestamp::from_nanos(0),
79 AwaitPolicy::new(Timestamp::from_nanos(0)),
80 ) {
81 EgressResponse::Ready {
82 body: HlsBody::Playlist(m),
83 ..
84 } => m,
85 other => panic!("expected Ready(Playlist), got {other:?}"),
86 }
87}More examples
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 // Segment 1 closes with two parts.
66 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 // Segment 2 is still open, with only its first part landed so far.
91 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 // A plain (non-blocking) request is Ready immediately.
118 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 // A blocking-reload request for a segment that hasn't closed yet: with
135 // `await_policy`'s deadline already at `now`, this immediately reports
136 // the awaited condition has run out of patience rather than serving a
137 // fabricated Ready.
138 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 // A `_HLS_msn` unreasonably far beyond the live edge is rejected outright
152 // (RFC 8216bis §6.2.5.2 abuse prevention) rather than ever Await-ing.
153 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 // `Resource`: the init segment and the closed segment are Ready...
167 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 // ...a live part of the still-open segment is Ready too...
186 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 // ...a preload-hinted part not yet produced reports NotFound once this
196 // call's `await_policy` has already expired (a real HTTP adapter would
197 // instead give it a real deadline and block on `Trunk::listen()`)...
198 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 // ...and an unrecognised filename is a plain 404.
212 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}Sourcepub fn container(self, container: Container) -> Self
pub fn container(self, container: Container) -> Self
Which container this origin serves segments/parts as. Defaults to
Container::Fmp4 if never called, matching every pre-#873 caller’s
behaviour. Orthogonal to Self::low_latency — all four
{Fmp4, MpegTs} x {classic, low-latency} combinations are valid.
Sourcepub fn low_latency(self, part_target_ms: u32) -> Self
pub fn low_latency(self, part_target_ms: u32) -> Self
Opt into LL-HLS: part_target_ms becomes #EXT-X-PART-INF’s
PART-TARGET (milliseconds). Omit this call entirely for classic
HLS — no #EXT-X-PART/#EXT-X-PART-INF/#EXT-X-SERVER-CONTROL/
#EXT-X-PRELOAD-HINT tags are then rendered, regardless of
Self::container. This is what the old constructor’s mandatory
part_target_ms positional could not express.
Examples found in repository?
36fn canned_playlist() -> String {
37 let trunk = Trunk::new(TrunkConfig::new(nz(16), nz(4), nz(8), nz(4), nz(16)));
38 let writer = trunk
39 .segment_writer()
40 .expect("first (and only) segment writer");
41 let origin = HlsOrigin::builder(std::sync::Arc::clone(&trunk))
42 .target_duration_secs(1.0)
43 .window_segments(nz(4))
44 .low_latency(500)
45 .build()
46 .expect("both required fields set");
47 origin.set_init(vec![0xAA; 32]);
48
49 writer.publish_part(PartEntry::new(
50 vec![0x01; 16],
51 1,
52 0,
53 Duration::from_millis(500),
54 true,
55 ));
56 writer.publish_segment(SegmentEntry::new(
57 vec![0x02; 32],
58 1,
59 Duration::from_secs(1),
60 Timestamp::from_nanos(0),
61 SegmentMeta {
62 discontinuous: false,
63 },
64 ));
65 writer.publish_part(PartEntry::new(
66 vec![0x03; 16],
67 2,
68 0,
69 Duration::from_millis(500),
70 true,
71 ));
72
73 match origin.resolve(
74 HlsRequest::Playlist {
75 track_id: DEFAULT_TRACK_ID,
76 query: BlockingQuery::default(),
77 },
78 Timestamp::from_nanos(0),
79 AwaitPolicy::new(Timestamp::from_nanos(0)),
80 ) {
81 EgressResponse::Ready {
82 body: HlsBody::Playlist(m),
83 ..
84 } => m,
85 other => panic!("expected Ready(Playlist), got {other:?}"),
86 }
87}More examples
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 // Segment 1 closes with two parts.
66 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 // Segment 2 is still open, with only its first part landed so far.
91 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 // A plain (non-blocking) request is Ready immediately.
118 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 // A blocking-reload request for a segment that hasn't closed yet: with
135 // `await_policy`'s deadline already at `now`, this immediately reports
136 // the awaited condition has run out of patience rather than serving a
137 // fabricated Ready.
138 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 // A `_HLS_msn` unreasonably far beyond the live edge is rejected outright
152 // (RFC 8216bis §6.2.5.2 abuse prevention) rather than ever Await-ing.
153 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 // `Resource`: the init segment and the closed segment are Ready...
167 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 // ...a live part of the still-open segment is Ready too...
186 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 // ...a preload-hinted part not yet produced reports NotFound once this
196 // call's `await_policy` has already expired (a real HTTP adapter would
197 // instead give it a real deadline and block on `Trunk::listen()`)...
198 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 // ...and an unrecognised filename is a plain 404.
212 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}Sourcepub fn build(self) -> Result<HlsOrigin, HlsOriginBuildError>
pub fn build(self) -> Result<HlsOrigin, HlsOriginBuildError>
Build the HlsOrigin, subscribing its one SegmentCursor
immediately (so the window starts empty but never misses a segment
published from this point on).
Errors, never silently defaults, if Self::target_duration_secs or
Self::window_segments was never called.
Examples found in repository?
36fn canned_playlist() -> String {
37 let trunk = Trunk::new(TrunkConfig::new(nz(16), nz(4), nz(8), nz(4), nz(16)));
38 let writer = trunk
39 .segment_writer()
40 .expect("first (and only) segment writer");
41 let origin = HlsOrigin::builder(std::sync::Arc::clone(&trunk))
42 .target_duration_secs(1.0)
43 .window_segments(nz(4))
44 .low_latency(500)
45 .build()
46 .expect("both required fields set");
47 origin.set_init(vec![0xAA; 32]);
48
49 writer.publish_part(PartEntry::new(
50 vec![0x01; 16],
51 1,
52 0,
53 Duration::from_millis(500),
54 true,
55 ));
56 writer.publish_segment(SegmentEntry::new(
57 vec![0x02; 32],
58 1,
59 Duration::from_secs(1),
60 Timestamp::from_nanos(0),
61 SegmentMeta {
62 discontinuous: false,
63 },
64 ));
65 writer.publish_part(PartEntry::new(
66 vec![0x03; 16],
67 2,
68 0,
69 Duration::from_millis(500),
70 true,
71 ));
72
73 match origin.resolve(
74 HlsRequest::Playlist {
75 track_id: DEFAULT_TRACK_ID,
76 query: BlockingQuery::default(),
77 },
78 Timestamp::from_nanos(0),
79 AwaitPolicy::new(Timestamp::from_nanos(0)),
80 ) {
81 EgressResponse::Ready {
82 body: HlsBody::Playlist(m),
83 ..
84 } => m,
85 other => panic!("expected Ready(Playlist), got {other:?}"),
86 }
87}More examples
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 // Segment 1 closes with two parts.
66 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 // Segment 2 is still open, with only its first part landed so far.
91 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 // A plain (non-blocking) request is Ready immediately.
118 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 // A blocking-reload request for a segment that hasn't closed yet: with
135 // `await_policy`'s deadline already at `now`, this immediately reports
136 // the awaited condition has run out of patience rather than serving a
137 // fabricated Ready.
138 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 // A `_HLS_msn` unreasonably far beyond the live edge is rejected outright
152 // (RFC 8216bis §6.2.5.2 abuse prevention) rather than ever Await-ing.
153 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 // `Resource`: the init segment and the closed segment are Ready...
167 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 // ...a live part of the still-open segment is Ready too...
186 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 // ...a preload-hinted part not yet produced reports NotFound once this
196 // call's `await_policy` has already expired (a real HTTP adapter would
197 // instead give it a real deadline and block on `Trunk::listen()`)...
198 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 // ...and an unrecognised filename is a plain 404.
212 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}