pub struct Writer<W>where
W: Write,{ /* private fields */ }Expand description
A writer of HLS lines.
This structure wraps a Write with methods that make writing parsed (or user constructed) HLS
lines easier. The Writer handles inserting new lines where necessary and formatting for tags.
An important note to make, is that with every tag implementation within crate::tag::hls, the
reference to the original input data is used directly when writing. This means that we avoid
unnecessary allocations unless the data has been mutated. The same is true of
crate::tag::KnownTag::Custom tags (described in crate::tag::CustomTagAccess). Where
necessary, the inner Write can be accessed in any type of ownership semantics (owned via
Self::into_inner, mutable borrow via Self::get_mut, borrow via Self::get_ref).
§Mutate data as proxy
A common use case for using Writer is when implementing a proxy service for a HLS stream that
modifies the playlist. In that case, the crate::Reader is used to extract information from
the upstream bytes, the various tag types can be used to modify the data where necessary, and
the Writer is then used to write the result to data for the body of the HTTP response. Below
we provide a toy example of this (for a more interesting example, the repository includes an
implementation of a HLS delta update in benches/delta_update_bench.rs).
const INPUT: &str = r#"
#EXTINF:4
segment_100.mp4
#EXTINF:4
segment_101.mp4
"#;
let mut reader = Reader::from_str(INPUT, ParsingOptions::default());
let mut writer = Writer::new(Vec::new());
let mut added_hello = false;
loop {
match reader.read_line() {
// In this branch we match the #EXTINF tag and update the title property to add a
// message.
Ok(Some(HlsLine::KnownTag(KnownTag::Hls(hls::Tag::Inf(mut tag))))) => {
if added_hello {
tag.set_title("World!");
} else {
tag.set_title("Hello,");
added_hello = true;
}
writer.write_line(HlsLine::from(tag))?;
}
// For all other lines we just write out what we received as input.
Ok(Some(line)) => {
writer.write_line(line)?;
}
// When we encounter `Ok(None)` it indicates that we have reached the end of the
// playlist and so we break the loop.
Ok(None) => break,
// Even when encountering errors we can access the original problem line, then take a
// mutable borrow on the inner writer, and write out the bytes. In this way we can be a
// very unopinionated proxy. This is completely implementation specific, and other use
// cases may require an implementation that rejects the playlist, or we may also choose
// to implement tracing in such cases. We're just showing the possibility here.
Err(e) => writer.get_mut().write_all(e.errored_line.as_bytes())?,
};
}
const EXPECTED: &str = r#"
#EXTINF:4,Hello,
segment_100.mp4
#EXTINF:4,World!
segment_101.mp4
"#;
assert_eq!(EXPECTED, String::from_utf8_lossy(&writer.into_inner()));§Construct a playlist output
It may also be the case that a user may want to write a complete playlist out without having to parse any data. This is also possible (and may be made easier in the future if we implement a playlist and playlist builder type). And of course, the user can mix and match, parsing some input, mutating where necessary, introducing new lines as needed, and writing it all out. Below is another toy example of how we may construct the 9.4. Multivariant Playlist example provided in the HLS specification.
const EXPECTED: &str = r#"#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000
http://example.com/low.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000
http://example.com/mid.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=7680000,AVERAGE-BANDWIDTH=6000000
http://example.com/hi.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"
http://example.com/audio-only.m3u8
"#;
let mut writer = Writer::new(Vec::new());
writer.write_line(HlsLine::from(M3u))?;
writer.write_line(HlsLine::from(
StreamInf::builder()
.with_bandwidth(1280000)
.with_average_bandwidth(1000000)
.finish(),
))?;
writer.write_uri("http://example.com/low.m3u8")?;
writer.write_line(HlsLine::from(
StreamInf::builder()
.with_bandwidth(2560000)
.with_average_bandwidth(2000000)
.finish(),
))?;
writer.write_uri("http://example.com/mid.m3u8")?;
writer.write_line(HlsLine::from(
StreamInf::builder()
.with_bandwidth(7680000)
.with_average_bandwidth(6000000)
.finish(),
))?;
writer.write_uri("http://example.com/hi.m3u8")?;
writer.write_line(HlsLine::from(
StreamInf::builder()
.with_bandwidth(65000)
.with_codecs("mp4a.40.5")
.finish(),
))?;
writer.write_uri("http://example.com/audio-only.m3u8")?;
assert_eq!(EXPECTED, std::str::from_utf8(&writer.into_inner())?);Implementations§
Source§impl<W> Writer<W>where
W: Write,
impl<W> Writer<W>where
W: Write,
Sourcepub fn into_inner(self) -> W
pub fn into_inner(self) -> W
Consumes this Writer, returning the underlying writer.
Sourcepub fn write_line(&mut self, line: HlsLine<'_>) -> Result<usize>
pub fn write_line(&mut self, line: HlsLine<'_>) -> Result<usize>
Write the HlsLine to the underlying writer. Returns the number of bytes consumed during
writing or an io::Error from the underlying writer.
In this case the CustomTag generic is the default NoCustomTag struct. See Self for
more detailed documentation.
Sourcepub fn write_blank(&mut self) -> Result<usize>
pub fn write_blank(&mut self) -> Result<usize>
Example:
let mut writer = Writer::new(b"#EXTM3U\n".to_vec());
writer.write_blank().unwrap();
writer.write_comment(" Note blank line above.").unwrap();
let expected = r#"#EXTM3U
# Note blank line above.
"#;
assert_eq!(expected.as_bytes(), writer.into_inner());Sourcepub fn write_comment<'a>(
&mut self,
comment: impl Into<Cow<'a, str>>,
) -> Result<usize>
pub fn write_comment<'a>( &mut self, comment: impl Into<Cow<'a, str>>, ) -> Result<usize>
Example:
let mut writer = Writer::new(Vec::new());
writer.write_comment(" This is a comment.").unwrap();
assert_eq!("# This is a comment.\n".as_bytes(), writer.into_inner());Sourcepub fn write_uri<'a>(&mut self, uri: impl Into<Cow<'a, str>>) -> Result<usize>
pub fn write_uri<'a>(&mut self, uri: impl Into<Cow<'a, str>>) -> Result<usize>
Example:
let mut writer = Writer::new(Vec::new());
writer.write_uri("example.m3u8").unwrap();
assert_eq!("example.m3u8\n".as_bytes(), writer.into_inner());Sourcepub fn write_custom_tag<'a, Custom>(&mut self, tag: Custom) -> Result<usize>where
Custom: WritableCustomTag<'a>,
pub fn write_custom_tag<'a, Custom>(&mut self, tag: Custom) -> Result<usize>where
Custom: WritableCustomTag<'a>,
Write a custom tag implementation to the inner writer.
Note that if the custom tag is derived from parsed data (i.e. not user constructed), then
this method should be avoided, as it will allocate data perhaps unnecessarily. In that case
use Self::write_custom_line with crate::tag::CustomTagAccess, as this will use the
original parsed data if no mutation has occurred.
Example:
#[derive(Debug, PartialEq, Clone)]
struct ExampleCustomTag {
answer: u64,
}
impl TryFrom<UnknownTag<'_>> for ExampleCustomTag {
type Error = ValidationError;
fn try_from(tag: UnknownTag) -> Result<Self, Self::Error> {
if tag.name() != "-X-MEANING-OF-LIFE" {
return Err(ValidationError::UnexpectedTagName)
}
Ok(Self {
answer: tag
.value()
.ok_or(ParseTagValueError::UnexpectedEmpty)?
.try_as_decimal_integer()?
})
}
}
impl CustomTag<'_> for ExampleCustomTag {
fn is_known_name(name: &str) -> bool {
name == "-X-MEANING-OF-LIFE"
}
}
impl WritableCustomTag<'_> for ExampleCustomTag {
fn into_writable_tag(self) -> WritableTag<'static> {
WritableTag::new("-X-MEANING-OF-LIFE", self.answer)
}
}
let mut writer = Writer::new(Vec::new());
let custom_tag = ExampleCustomTag { answer: 42 };
writer.write_custom_tag(custom_tag).unwrap();
assert_eq!(
"#EXT-X-MEANING-OF-LIFE:42\n".as_bytes(),
writer.into_inner()
);Sourcepub fn write_custom_line<'a, Custom>(
&mut self,
line: HlsLine<'a, Custom>,
) -> Result<usize>where
Custom: WritableCustomTag<'a>,
pub fn write_custom_line<'a, Custom>(
&mut self,
line: HlsLine<'a, Custom>,
) -> Result<usize>where
Custom: WritableCustomTag<'a>,
Write the HlsLine to the underlying writer. Returns the number of bytes consumed during
writing or an io::Error from the underlying writer. Ultimately, all the other write
methods are wrappers for this method.
This method is necessary to use where the input lines carry a custom tag type (other than
crate::tag::NoCustomTag). For example, say we are parsing some data using a reader that
supports our own custom defined tag (SomeCustomTag).
let mut reader = Reader::with_custom_from_str(
input,
options,
PhantomData::<SomeCustomTag>
);If we tried to use the Self::write_line method, it would fail to compile (as that method
expects that the generic Custom type is crate::tag::NoCustomTag, which is a struct
provided by the library that never succeeds the crate::tag::CustomTag::is_known_name
check so is never parsed). Therefore we must use the write_custom_line method in this case
(even if we are not writing the custom tag itself):
let mut writer = Writer::new(Vec::new());
loop {
match reader.read_line() {
// --snip--
Ok(Some(line)) => {
writer.write_custom_line(line)?;
}
// --snip--
};
}