Writer

Struct Writer 

Source
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,

Source

pub const fn new(inner: W) -> Writer<W>

Creates a Writer from a generic writer.

Source

pub fn into_inner(self) -> W

Consumes this Writer, returning the underlying writer.

Source

pub fn get_mut(&mut self) -> &mut W

Get a mutable reference to the underlying writer.

Source

pub const fn get_ref(&self) -> &W

Get a reference to the underlying writer.

Source

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.

Source

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());
Source

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());
Source

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());
Source

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()
);
Source

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--
    };
}

Trait Implementations§

Source§

impl<W> Clone for Writer<W>
where W: Write + Clone,

Source§

fn clone(&self) -> Writer<W>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<W> Debug for Writer<W>
where W: Write + Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<W> Freeze for Writer<W>
where W: Freeze,

§

impl<W> RefUnwindSafe for Writer<W>
where W: RefUnwindSafe,

§

impl<W> Send for Writer<W>
where W: Send,

§

impl<W> Sync for Writer<W>
where W: Sync,

§

impl<W> Unpin for Writer<W>
where W: Unpin,

§

impl<W> UnwindSafe for Writer<W>
where W: UnwindSafe,

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<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.