Expand description
§quick-m3u8
quick-m3u8 aims to be a flexible and highly performant M3U8 reader and writer. The API is
event-driven (like SAX for XML) rather than serializing into a complete object model (like DOM
for XML). The syntax (and name) is inspired by quick-xml. The Reader attempts to be almost
zero-copy while still supporting mutation of the parsed data by utilizing std::borrow::Cow
(Copy On Write) as much as possible. The library provides flexibility via support for custom tag
registration (tag::CustomTag), which gives the user the ability to support parsing of tags
not part of the main HLS specification, or overwrite parsing behavior of tags provided by the
library.
When parsing M3U8 data quick-m3u8 aims to be very lenient when it comes to validation. The philosophy is that the library does not want to get in the way of extracting meaningful information from the input data. If the library took a strict approach to validating adherence to all of the HLS specification, then there could be cases where a playlist is rejected, but a client may have accepted it. For example, consider the requirement for segment duration rounding declared by the EXT-X-TARGETDURATION tag:
The EXTINF duration of each Media Segment in a Playlist file, when rounded to the nearest integer, MUST be less than or equal to the Target Duration.
Despite this requirement, many players will tolerate several long-running segments in a playlist just fine, so interpreting this rule very strictly could lead to the manifest being rejected before it even reaches the video player (assuming this library is being used in a server implementation).
Therefore, validating the sanity of the parsed values is deliberately left to the user of the library. Some examples of values that are not validated are:
- URI lines are not validated as being valid URIs (any line that isn’t blank and does not start
with
#is considered to be a URI line). - Enumerated strings (within attribute-lists) are not validated to have no whitespace.
- A tag with a known name that fails the
TryFrom<ParsedTag>conversion does not fail the line and instead is presented astag::UnknownTag.
With that being said, the library does validate proper UTF-8 conversion from &[u8] input,
enumerated strings and enumerated string lists are wrapped in convenience types
(tag::hls::EnumeratedString, tag::hls::EnumeratedStringList) that expose strongly typed
enumerations when the value is valid, and the TryFrom<ParsedTag> implementation for all of the
HLS tags supported by quick-m3u8 ensure that the required attributes are present for each tag.
§Usage
Usage is broken up into reading and writing.
§Reading
The main entry point for using the library is the Reader. This provides an interface for
reading lines from an input data source. For example, consider the Simple Media Playlist:
const EXAMPLE_MANIFEST: &str = r#"#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXTINF:9.009,
first.ts
#EXTINF:9.009,
second.ts
#EXTINF:3.003,
third.ts
#EXT-X-ENDLIST
"#;We can use the Reader to read information about each line in the playlist as such:
let mut reader = Reader::from_str(
EXAMPLE_MANIFEST,
ParsingOptionsBuilder::new()
.with_parsing_for_all_tags()
.build(),
);
assert_eq!(reader.read_line(), Ok(Some(HlsLine::from(M3u))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::from(Targetduration::new(10)))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::from(Version::new(3)))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::from(Inf::new(9.009, "")))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::uri("first.ts"))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::from(Inf::new(9.009, "")))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::uri("second.ts"))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::from(Inf::new(3.003, "")))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::uri("third.ts"))));
assert_eq!(reader.read_line(), Ok(Some(HlsLine::from(Endlist))));
assert_eq!(reader.read_line(), Ok(None));The example is basic but demonstrates a few points already. Firstly, HlsLine (the result of
read_line) is an enum, which is in line with Section 4.1 that states:
Each line is a URI, is blank, or starts with the character ‘#’. Lines that start with the character ‘#’ are either comments or tags. Tags begin with #EXT.
Each case of the HlsLine is documented thoroughly; however, it’s worth mentioning that in
addition to what the HLS specification defines, the library also allows for UnknownTag (which
is a tag, based on the #EXT prefix, but not one that we know about), and also allows
CustomTag. The tag::CustomTag is a means for the user of the library to define support for
their own custom tag specification in addition to what is provided via the HLS specification.
The documentation for CustomTag provides more details on how that is achieved.
The Reader also takes a configuration that allows the user to select what HLS tags the reader
should parse. config::ParsingOptions provides more details, but in short, better performance
can be squeezed out by only parsing the tags that you need.
§Writing
The other component to quick-m3u8 is Writer. This allows the user to write to a given
std::io::Write the parsed (or constructed) HLS lines. It should be noted that when writing,
if no mutation of a tag has occurred, then the original reference slice of the line will be
used. This allows us to avoid unnecessary allocations.
A common use-case for reading and then writing is to modify a HLS playlist, perhaps in transit, in a proxy layer. Below is a toy example; however, the repo benchmark demonstrates a more complex example of how one may implement a HLS delta update (acting as a proxy layer).
let input_lines = concat!(
"#EXTINF:4.00008,\n",
"fileSequence268.mp4\n",
"#EXTINF:4.00008,\n",
"fileSequence269.mp4\n",
);
let mut reader = Reader::from_str(input_lines, ParsingOptions::default());
let mut writer = Writer::new(Vec::new());
let mut added_hello = false;
while let Ok(Some(line)) = reader.read_line() {
match line {
HlsLine::KnownTag(KnownTag::Hls(hls::Tag::Inf(mut inf))) => {
if added_hello {
inf.set_title(" World!");
} else {
inf.set_title(" Hello,");
added_hello = true;
}
writer.write_line(HlsLine::from(inf))?
}
line => writer.write_line(line)?,
};
}
let expected_output_lines = concat!(
"#EXTINF:4.00008, Hello,\n",
"fileSequence268.mp4\n",
"#EXTINF:4.00008, World!\n",
"fileSequence269.mp4\n",
);
assert_eq!(
expected_output_lines,
String::from_utf8_lossy(&writer.into_inner())
);Modules§
- config
- Configuration for reading HLS lines
- custom_
parsing - Methods and types used by the library for parsing M3U8.
- date
- Constructs to reason about date and time in HLS
- error
- All error types exposed by the library.
- tag
- Container module for all HLS tag related modules, types, and methods.
Macros§
Structs§
Enums§
- HlsLine
- A parsed line from a HLS playlist.