broadcast-hls
HLS (M3U8) playlist syntax, RFC 8216 and RFC 8216bis (Low-Latency HLS): typed Media and Multivariant (Master) Playlist parse and serialize, no_std.
[]
= "0.6"
What it does / what it does not
Does: parse a playlist into MediaPlaylist or MasterPlaylist structs and render it back with to_m3u8(), covering every one of the 32 tags in RFC 8216bis §4.4 (28 as typed struct fields, 4 preserved verbatim), the Low-Latency additions (partial segments, blocking reload, delta updates, preload hints, rendition reports), I-frame trick-play, discontinuity signalling, and the EXT-X-KEY line for CENC/CBCS protected CMAF. EXT-X-VERSION is computed from the content, never trusted from a caller. It was extracted from transmux (issue #878) so a crate that only needs playlist syntax (media-doctor, an HLS-pull client, a fuzz target) does not pull in a container-muxing hub.
Does not: fetch anything or run a client (that is hls-runtime), produce segments (transmux's ts_hls and ll_hls segmenters stay there because they emit container bytes), validate cross-file semantic rules (media-doctor), or give typed field access to EXT-X-KEY and EXT-X-DATERANGE (they round-trip losslessly as tag lines; read the key in effect with MediaPlaylist::segment_keys). EXT-X-MEDIA is now typed (MasterPlaylist::media). HLS Interstitials (CLASS="com.apple.hls.interstitial" date ranges) are therefore built by the caller (ssai-runtime renders them) as EXT-X-DATERANGE lines, and the interstitial spec (private/specs-verified/hls/ietf/rfc8216bis-interstitials.md) is transcribed but not typed here.
Standards supported
| Standard | Edition | Scope | Status | Notes |
|---|---|---|---|---|
IETF HLS 2nd edition, draft-pantos-hls-rfc8216bis |
-22, 1 May 2026 (private repo: private/specs-verified/ietf/specs/ietf_draft_pantos_hls_rfc8216bis.txt) |
§4.4 tags (all 32 recognised), §8 protocol version derivation, §9 examples (fixtures), Low-Latency directives | Partial | 28 of 32 tags typed; cross-tag and cross-file MUSTs are not enforced; a draft, not a ratified RFC |
| IETF RFC 8216 HLS | RFC 8216 (private repo: private/specs-verified/ietf/specs/ietf_rfc8216_hls.txt) |
§4.2 attribute-list grammar and quoted strings, §4.3 tags including I-frame variants | Full for the syntax | Superseded in intent by the draft above |
| Apple HLS timed metadata | private repo: private/specs-verified/hls/apple/apple_hls_timed_metadata.html |
EXT-X-DATERANGE carried as opaque tag lines |
Typed-opaque | timed-metadata builds the lines |
| ISO/IEC 23001-7 Common Encryption | Edition not in the vendored index | cenc_ext_x_key: cbcs becomes METHOD=SAMPLE-AES, cenc becomes METHOD=SAMPLE-AES-CTR with no IV (draft §4.4.4.4) |
Partial | Scheme identity is broadcast_common::cenc::CencScheme, shared with transmux |
Features
| Feature | Default | Description |
|---|---|---|
std |
yes | Links the standard library. Without it the crate is #![no_std] and needs only alloc (it builds for thumbv7em-none-eabi). |
serde |
no | serde::Serialize on the public types. |
Quick start
Parse a real Apple BipBop media playlist (a committed fixture) and render it back:
use MediaPlaylist;
let text = read_to_string.unwrap;
let playlist = parse.unwrap;
assert!;
// to_m3u8 is the symmetric inverse of parse: a second parse equals the first.
let rendered = playlist.to_m3u8.unwrap;
assert_eq!;
Build a playlist from fields (DecimalSeconds rejects NaN and infinity, to_m3u8 is fallible):
use ;
let playlist = MediaPlaylist ;
let m3u8 = playlist.to_m3u8.unwrap;
assert!;
// The renderer computed `#EXT-X-VERSION` from the content; parsing reads it back.
let reparsed = parse.unwrap;
assert_eq!;
assert_eq!;
Runnable programs: cargo run -p broadcast-hls --example build_playlist and --example parse_playlist.
What is implemented
MediaPlaylistandMediaSegment: segments withtitle,map(carry-forward on parse, deduplicated on render),bitrate,gap,discontinuous, byte ranges, andpre_tags(segment-defining tags kept in place), plus playlist-levelextra_tagsfor unrecognised tags.MasterPlaylist,Variant,IFrameVariant: multivariant playlists withEXT-X-I-FRAME-STREAM-INFtrick-play (RFC 8216 §4.3.4.3) andMasterPlaylist::extra_tags.- The 28 typed tags: including
EXT-X-INDEPENDENT-SEGMENTS,EXT-X-START(StartPoint),EXT-X-DEFINE(Define),EXT-X-PLAYLIST-TYPE(PlaylistType),EXT-X-GAP,EXT-X-BITRATE,EXT-X-SESSION-DATA(SessionData),EXT-X-SESSION-KEY(SessionKey,EncryptionMethod) andEXT-X-CONTENT-STEERING(ContentSteering).tests/it/hls_tag_completeness.rsparses a fixture carrying all 32 and asserts typed tags populate their field and the four opaque ones survive verbatim. - Low-Latency HLS (opt-in, so a plain playlist is unchanged):
LowLatencyConfig,PartSpec,OpenSegment(the in-progress segment at the live edge),MapTag,ByteRange,PreloadHintType,RenditionReport,SkipInfo.PART-HOLD-BACKmust be at least three part targets. - Discontinuities:
EXT-X-DISCONTINUITYandEXT-X-DISCONTINUITY-SEQUENCE, andmark_init_discontinuities(detect an init-segment change across a run). - Version derivation:
MediaPlaylist::computed_versionandMasterPlaylist::computed_versionapply the draft's §8 table (private/specs-verified/hls/ietf/rfc8216bis-version-compatibility.md). A playlist needing nothing gets noEXT-X-VERSIONtag. Theversionfield is a floor: raised to the computed value, never lowered. - Attribute lists:
parse_attribute_list,render_attribute_list,AttrValue(validated quoted or bare value), and unknown attributes on every modelled tag kept inextra_attrsand re-emitted after the known ones. - Durations:
DecimalSecondsandSignedDecimalSecondsround-trip bit-exactly (including9.9766and2.00004). Round your own durations before building if you want compact playlists, since an unrounded division renders every digit. - CENC:
cenc_ext_x_key,CENC_KEYFORMAT,CENC_KEYFORMATVERSIONS. - Segment keys (RFC 8216 §4.3.2.4):
MediaPlaylist::segment_keysresolves the#EXT-X-KEYin effect for each closed segment (carry-forward until the next key of the sameKEYFORMAT,METHOD=NONEresets, only theidentityformat participates) asMediaKey(an alias ofSessionKey), andopen_segment_keydoes the same for the open segment. A key with noURIisError::KeyWithoutUri; a segment covered only by a non-identityKEYFORMATisError::UnsupportedKeyFormat. - Codec and range signalling:
SUPPLEMENTAL-CODECS(round-trips, including the HDR10+/cdm4brand) and the typedVideoRange(SDR,HLG,PQ), strict attribute-list parsing (parse_attribute_list_strict) that reports a malformed list instead of half-parsing it.
Round-trip fidelity
The workspace text-format rule is that a second parse of the rendered output equals the first parse, not that the text is byte-identical. What changes on re-emit:
- Tag order is canonical, not input order (for example
INDEPENDENT-SEGMENTS,DEFINE,STARTfollow the version line;SESSION-KEY,SESSION-DATA,CONTENT-STEERINGprecede the variants). No tag is lost. - Blank lines, trailing whitespace and
#comment lines are dropped. Line-continuation backslashes (used for wrapping in spec examples) are never emitted. to_m3u8()is fallible: a quoted-string or URI field containing", CR or LF (Error::InvalidQuotedString,Error::InvalidUri) is rejected instead of rendered, which prevents tag-line injection. The same applies to anEXT-X-SKIPremoved-daterange id.- Not enforced:
EXT-X-BITRATEnot applying to a byte-ranged segment,EXT-X-DEFINEIMPORTandQUERYPARAMresolution,EXT-X-SESSION-KEYMETHODnot beingNONE, "must not appear more than once" rules. Those need cross-file context and belong tomedia-doctor. - A non-segment-defining unrecognised tag lands in
extra_tagsand loses its interleaved position (it renders as one block before the segments). Segment-defining tags (EXT-X-KEY,EXT-X-DATERANGE,EXT-X-CUE-*) attach to the following segment'spre_tagsand keep their place.#EXT-X-PROGRAM-DATE-TIMEis now a typed field (MediaSegment::program_date_time) rather than an opaque pre-tag. - A hand-built playlist whose segments'
mapfields do not form a valid carry-forward sequence cannot round-trip, because the wire has no "stop applying the map" short of a discontinuity plus a new map.
Design notes
Zero dependencies beyond broadcast-common and thiserror, no_std + alloc, symmetric parse and render, #[non_exhaustive] on Error and the open enums, label-coverage tests for spec enums, fixture corpus (tests/it/hls_fixture_corpus.rs) over Apple's BipBop playlists and every RFC 8216bis §9 example (provenance in fixtures/hls/PROVENANCE.md), and a fuzz target.
Related crates
Depends on broadcast-common. Consumed by hls-runtime (client parses, origin renders), transmux (the HLS segmenters), ssai-runtime (interstitial playlists), media-doctor (conformance checks), timed-metadata (daterange lines) and multimux.
MSRV and licence
MSRV 1.95.0. Licensed under MIT OR Apache-2.0.