Skip to main content

HlsOriginBuilder

Struct HlsOriginBuilder 

Source
pub struct HlsOriginBuilder { /* private fields */ }
Available on crate feature 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

Source

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?
examples/client_stepping.rs (line 42)
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
Hide additional examples
examples/origin_playlist.rs (line 58)
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}
Source

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?
examples/client_stepping.rs (line 43)
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
Hide additional examples
examples/origin_playlist.rs (line 59)
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}
Source

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.

Source

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?
examples/client_stepping.rs (line 44)
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
Hide additional examples
examples/origin_playlist.rs (line 60)
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}
Source

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?
examples/client_stepping.rs (line 45)
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
Hide additional examples
examples/origin_playlist.rs (line 61)
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}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more