Struct IcyMetadataReader

Source
pub struct IcyMetadataReader<T> { /* private fields */ }
Expand description

Reads icy metadata contained within a stream.

Seeking within the stream is supported with the following limitations:

  • SeekFrom::End is not supported since seeking from the end of a stream conceptually doesn’t make sense.
  • Seeking backwards is limited by the size of the metadata cache. Since the metadata values have dynamic sizes, we need to know the size of the previous metadata value to seek past it. In order to prevent unbounded memory growth, we cap the number of previous metadata sizes we keep track of. You can change this limit using Self::metadata_cache_size. In practice, most metadata is 0-sized except for at the start of each track. We use rudimentary compression so consecutive metadata entries of the same size don’t take up additional slots in the array. This means you shouldn’t exceed the default value of 128 unless you’re going really far back.

Implementations§

Source§

impl<T> IcyMetadataReader<T>

Source

pub fn new<F>( inner: T, icy_metadata_interval: Option<NonZeroUsize>, on_metadata_read: F, ) -> Self
where F: Fn(Result<IcyMetadata, MetadataParseError>) + Send + Sync + 'static,

Creates a new IcyMetadataReader. icy_metadata_interval is required in order to figure out the location of the metadata blocks. If icy_metadata_interval is None, it will treat the stream as though the metadata is absent. You can retrieve the value from IcyHeaders::metadata_interval or by extracting the value from the headers manually.

Examples found in repository?
examples/stream.rs (lines 48-55)
13async fn main() -> Result<(), Box<dyn Error>> {
14    let (_stream, handle) = rodio::OutputStream::try_default()?;
15    let sink = rodio::Sink::try_new(&handle)?;
16
17    // We need to add a header to tell the Icecast server that we can parse the metadata embedded
18    // within the stream itself.
19    let client = Client::builder().request_icy_metadata().build()?;
20
21    let stream =
22        HttpStream::new(client, "https://ice6.somafm.com/insound-128-mp3".parse()?).await?;
23
24    let icy_headers = IcyHeaders::parse_from_headers(stream.headers());
25    println!("Icecast headers: {icy_headers:#?}\n");
26    println!("content type={:?}\n", stream.content_type());
27
28    // buffer 5 seconds of audio
29    // bitrate (in kilobits) / bits per byte * bytes per kilobyte * 5 seconds
30    let prefetch_bytes = icy_headers.bitrate().unwrap() / 8 * 1024 * 5;
31
32    let reader = match StreamDownload::from_stream(
33        stream,
34        // use bounded storage to keep the underlying size from growing indefinitely
35        BoundedStorageProvider::new(
36            MemoryStorageProvider,
37            // be liberal with the buffer size, you need to make sure it holds enough space to
38            // prevent any out-of-bounds reads
39            NonZeroUsize::new(512 * 1024).unwrap(),
40        ),
41        Settings::default().prefetch_bytes(prefetch_bytes as u64),
42    )
43    .await
44    {
45        Ok(reader) => reader,
46        Err(e) => return Err(e.decode_error().await)?,
47    };
48    sink.append(rodio::Decoder::new(IcyMetadataReader::new(
49        reader,
50        // Since we requested icy metadata, the metadata interval header should be present in the
51        // response. This will allow us to parse the metadata within the stream
52        icy_headers.metadata_interval(),
53        // Print the stream metadata whenever we receive new values
54        |metadata| println!("{metadata:#?}\n"),
55    ))?);
56
57    let handle = tokio::task::spawn_blocking(move || {
58        sink.sleep_until_end();
59    });
60    handle.await?;
61    Ok(())
62}
More examples
Hide additional examples
examples/seek.rs (lines 50-57)
15async fn main() -> Result<(), Box<dyn Error>> {
16    let (_stream, handle) = rodio::OutputStream::try_default()?;
17    let sink = rodio::Sink::try_new(&handle)?;
18
19    // We need to add a header to tell the Icecast server that we can parse the metadata embedded
20    // within the stream itself.
21    let client = Client::builder().request_icy_metadata().build()?;
22
23    let stream =
24        HttpStream::new(client, "https://ice6.somafm.com/insound-128-mp3".parse()?).await?;
25
26    let icy_headers = IcyHeaders::parse_from_headers(stream.headers());
27    println!("Icecast headers: {icy_headers:#?}\n");
28    println!("content type={:?}\n", stream.content_type());
29
30    // buffer 5 seconds of audio
31    // bitrate (in kilobits) / bits per byte * bytes per kilobyte * 5 seconds
32    let prefetch_bytes = icy_headers.bitrate().unwrap() / 8 * 1024 * 5;
33
34    let reader = match StreamDownload::from_stream(
35        stream,
36        // use bounded storage to keep the underlying size from growing indefinitely
37        BoundedStorageProvider::new(
38            MemoryStorageProvider,
39            // be liberal with the buffer size, you need to make sure it holds enough space to
40            // prevent any out-of-bounds reads
41            NonZeroUsize::new(512 * 1024).unwrap(),
42        ),
43        Settings::default().prefetch_bytes(prefetch_bytes as u64),
44    )
45    .await
46    {
47        Ok(reader) => reader,
48        Err(e) => return Err(e.decode_error().await)?,
49    };
50    sink.append(rodio::Decoder::new(IcyMetadataReader::new(
51        reader,
52        // Since we requested icy metadata, the metadata interval header should be present in the
53        // response. This will allow us to parse the metadata within the stream
54        icy_headers.metadata_interval(),
55        // Print the stream metadata whenever we receive new values
56        |metadata| println!("{metadata:#?}\n"),
57    ))?);
58
59    let handle = tokio::task::spawn_blocking(move || {
60        println!("seeking in 5 seconds\n");
61        std::thread::sleep(Duration::from_secs(5));
62        println!("seeking to beginning\n");
63        sink.try_seek(Duration::from_secs(0))?;
64        std::thread::sleep(Duration::from_secs(5));
65        println!("seeking to 10 seconds\n");
66        sink.try_seek(Duration::from_secs(10))?;
67        sink.sleep_until_end();
68        Ok::<_, SeekError>(())
69    });
70    handle.await??;
71    Ok(())
72}
examples/retry_on_failure.rs (lines 71-80)
15async fn main() -> Result<(), Box<dyn Error>> {
16    let (_stream, handle) = rodio::OutputStream::try_default()?;
17
18    let restart = Arc::new(AtomicBool::new(true));
19
20    loop {
21        if !restart.swap(false, Ordering::Relaxed) {
22            return Ok(());
23        }
24
25        let sink = rodio::Sink::try_new(&handle)?;
26
27        // We need to add a header to tell the Icecast server that we can parse the metadata
28        // embedded within the stream itself.
29        let client = Client::builder().request_icy_metadata().build()?;
30
31        let stream =
32            HttpStream::new(client, "https://ice6.somafm.com/insound-128-mp3".parse()?).await?;
33
34        let icy_headers = IcyHeaders::parse_from_headers(stream.headers());
35        println!("Icecast headers: {icy_headers:#?}\n");
36        println!("content type={:?}\n", stream.content_type());
37
38        // buffer 5 seconds of audio
39        // bitrate (in kilobits) / bits per byte * bytes per kilobyte * 5 seconds
40        let prefetch_bytes = icy_headers.bitrate().unwrap() / 8 * 1024 * 5;
41
42        let reader = match StreamDownload::from_stream(
43            stream,
44            // use bounded storage to keep the underlying size from growing indefinitely
45            BoundedStorageProvider::new(
46                MemoryStorageProvider,
47                // be liberal with the buffer size, you need to make sure it holds enough space to
48                // prevent any out-of-bounds reads
49                NonZeroUsize::new(512 * 1024).unwrap(),
50            ),
51            Settings::default()
52                .prefetch_bytes(prefetch_bytes as u64)
53                .on_reconnect({
54                    let restart = restart.clone();
55                    move |_stream, cancellation_token| {
56                        // If the stream reconnects after a network failure, the internal state of
57                        // the metadata parser is likely invalid.
58                        // We should cancel the current download task and re-instantiate it in order
59                        // to reset everything.
60                        cancellation_token.cancel();
61                        restart.store(true, Ordering::Relaxed);
62                    }
63                }),
64        )
65        .await
66        {
67            Ok(reader) => reader,
68            Err(e) => return Err(e.decode_error().await)?,
69        };
70
71        sink.append(rodio::Decoder::new(IcyMetadataReader::new(
72            reader,
73            // Since we requested icy metadata, the metadata interval header should be present in
74            // the response. This will allow us to parse the metadata within the stream
75            icy_headers.metadata_interval(),
76            // Print the stream metadata whenever we receive new values
77            |metadata| {
78                println!("{metadata:#?}\n");
79            },
80        ))?);
81
82        let handle = tokio::task::spawn_blocking(move || {
83            sink.sleep_until_end();
84        });
85        handle.await?;
86    }
87}
Source§

impl<T> IcyMetadataReader<T>

Source

pub fn metadata_cache_size(self, size: usize) -> Self

Set the size of the metadata cache.

Trait Implementations§

Source§

impl<T> Debug for IcyMetadataReader<T>

Source§

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

Formats the value using the given formatter. Read more
Source§

impl<T> Read for IcyMetadataReader<T>
where T: Read,

Source§

fn read(&mut self, buf: &mut [u8]) -> Result<usize>

Pull some bytes from this source into the specified buffer, returning how many bytes were read. Read more
1.36.0 · Source§

fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize, Error>

Like read, except that it reads into a slice of buffers. Read more
Source§

fn is_read_vectored(&self) -> bool

🔬This is a nightly-only experimental API. (can_vector)
Determines if this Reader has an efficient read_vectored implementation. Read more
1.0.0 · Source§

fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error>

Reads all bytes until EOF in this source, placing them into buf. Read more
1.0.0 · Source§

fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error>

Reads all bytes until EOF in this source, appending them to buf. Read more
1.6.0 · Source§

fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>

Reads the exact number of bytes required to fill buf. Read more
Source§

fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<(), Error>

🔬This is a nightly-only experimental API. (read_buf)
Pull some bytes from this source into the specified buffer. Read more
Source§

fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<(), Error>

🔬This is a nightly-only experimental API. (read_buf)
Reads the exact number of bytes required to fill cursor. Read more
1.0.0 · Source§

fn by_ref(&mut self) -> &mut Self
where Self: Sized,

Creates a “by reference” adaptor for this instance of Read. Read more
1.0.0 · Source§

fn bytes(self) -> Bytes<Self>
where Self: Sized,

Transforms this Read instance to an Iterator over its bytes. Read more
1.0.0 · Source§

fn chain<R>(self, next: R) -> Chain<Self, R>
where R: Read, Self: Sized,

Creates an adapter which will chain this stream with another. Read more
1.0.0 · Source§

fn take(self, limit: u64) -> Take<Self>
where Self: Sized,

Creates an adapter which will read at most limit bytes from it. Read more
Source§

impl<T> Seek for IcyMetadataReader<T>
where T: Read + Seek,

Source§

fn seek(&mut self, seek_from: SeekFrom) -> Result<u64>

Seek to an offset, in bytes, in a stream. Read more
1.55.0 · Source§

fn rewind(&mut self) -> Result<(), Error>

Rewind to the beginning of a stream. Read more
Source§

fn stream_len(&mut self) -> Result<u64, Error>

🔬This is a nightly-only experimental API. (seek_stream_len)
Returns the length of this stream (in bytes). Read more
1.51.0 · Source§

fn stream_position(&mut self) -> Result<u64, Error>

Returns the current seek position from the start of the stream. Read more
1.80.0 · Source§

fn seek_relative(&mut self, offset: i64) -> Result<(), Error>

Seeks relative to the current position. Read more

Auto Trait Implementations§

§

impl<T> Freeze for IcyMetadataReader<T>
where T: Freeze,

§

impl<T> !RefUnwindSafe for IcyMetadataReader<T>

§

impl<T> Send for IcyMetadataReader<T>
where T: Send,

§

impl<T> Sync for IcyMetadataReader<T>
where T: Sync,

§

impl<T> Unpin for IcyMetadataReader<T>
where T: Unpin,

§

impl<T> !UnwindSafe for IcyMetadataReader<T>

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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, 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.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> MaybeSendSync for T