stream/
stream.rs

1use std::error::Error;
2use std::num::NonZeroUsize;
3
4use icy_metadata::{IcyHeaders, IcyMetadataReader, RequestIcyMetadata};
5use stream_download::http::HttpStream;
6use stream_download::http::reqwest::Client;
7use stream_download::source::DecodeError;
8use stream_download::storage::bounded::BoundedStorageProvider;
9use stream_download::storage::memory::MemoryStorageProvider;
10use stream_download::{Settings, StreamDownload};
11
12#[tokio::main]
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}