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::Endis 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 of128unless you’re going really far back.
Implementations§
Source§impl<T> IcyMetadataReader<T>
impl<T> IcyMetadataReader<T>
Sourcepub fn new<F>(
inner: T,
icy_metadata_interval: Option<NonZeroUsize>,
on_metadata_read: F,
) -> Self
pub fn new<F>( inner: T, icy_metadata_interval: Option<NonZeroUsize>, on_metadata_read: F, ) -> Self
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
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>
impl<T> IcyMetadataReader<T>
Sourcepub fn metadata_cache_size(self, size: usize) -> Self
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>
impl<T> Debug for IcyMetadataReader<T>
Source§impl<T> Read for IcyMetadataReader<T>where
T: Read,
impl<T> Read for IcyMetadataReader<T>where
T: Read,
Source§fn read(&mut self, buf: &mut [u8]) -> Result<usize>
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>
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize, Error>
Like
read, except that it reads into a slice of buffers. Read moreSource§fn is_read_vectored(&self) -> bool
fn is_read_vectored(&self) -> bool
🔬This is a nightly-only experimental API. (
can_vector)1.0.0 · Source§fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error>
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 more1.0.0 · Source§fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error>
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 more1.6.0 · Source§fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error>
Reads the exact number of bytes required to fill
buf. Read moreSource§fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<(), Error>
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>
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 more1.0.0 · Source§fn by_ref(&mut self) -> &mut Selfwhere
Self: Sized,
fn by_ref(&mut self) -> &mut Selfwhere
Self: Sized,
Creates a “by reference” adaptor for this instance of
Read. Read moreSource§impl<T> Seek for IcyMetadataReader<T>
impl<T> Seek for IcyMetadataReader<T>
Source§fn seek(&mut self, seek_from: SeekFrom) -> Result<u64>
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>
fn rewind(&mut self) -> Result<(), Error>
Rewind to the beginning of a stream. Read more
Source§fn stream_len(&mut self) -> Result<u64, Error>
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
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more