1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//! # 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 as [`tag::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:
//! ```
//! # use quick_m3u8::{
//! # HlsLine, Reader,
//! # config::ParsingOptionsBuilder,
//! # tag::hls::{Endlist, Inf, M3u, Targetduration, Version},
//! # };
//! #
//! # 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
//! # "#;
//! 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).
//! ```
//! # use quick_m3u8::{
//! # HlsLine, Reader, Writer,
//! # config::ParsingOptions,
//! # tag::{hls, KnownTag},
//! # };
//! # use std::io;
//! 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())
//! );
//! # Ok::<(), io::Error>(())
//! ```
//!
//! [M3U8]: https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
//! [quick-xml]: https://crates.io/crates/quick-xml
//! [EXT-X-TARGETDURATION]: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-18#section-4.4.3.1
//! [attribute-lists]: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-18#section-4.2
//! [Simple Media Playlist]: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-18#section-9.1
//! [Section 4.1]: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-18#section-4.1
pub use HlsLine;
pub use Reader;
pub use Writer;
// This allows the Rust compiler to validate any Rust snippets in my README, which seems like a very
// cool trick. I saw this technique in clap-rs/clap, for example:
// https://github.com/clap-rs/clap/blob/4d7ab1483cd0f0849668d274aa2fb6358872eca9/clap_complete_nushell/src/lib.rs#L239-L241
;