Skip to content

aschey/icy-metadata

Repository files navigation

icy-metadata

crates.io docs.rs license CI codecov GitHub repo size Lines of Code

icy-metadata is a library for reading metadata returned from Icecast-compatible web servers.

Installation

cargo add icy-metadata

Features

  • reqwest - adds convenience methods to set icy metadata headers on reqwest's client builder and request builder.
  • serde - enables serialization/deserialization for metadata structs.

Headers

Parse common Icecast headers from an HTTP response. icy-metadata will look for several common aliases to find the header values.

use icy_metadata::IcyHeaders;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let stream = reqwest::get("https://some-cool-url.com/some-file.mp3").await?;

    let icy_headers = IcyHeaders::parse_from_headers(stream.headers());
    println!("{icy_headers:?}");

    Ok(())
}

Reading information contained within the stream

Some streams have information about the current track contained within the stream itself. Wrapping the stream in an IcyMetadataReader provides an interface to read those values.

use std::error::Error;
use std::num::NonZeroUsize;

use icy_metadata::{IcyHeaders, IcyMetadataReader, RequestIcyMetadata};
use stream_download::http::reqwest::{self, Client};
use stream_download::http::HttpStream;
use stream_download::storage::bounded::BoundedStorageProvider;
use stream_download::storage::memory::MemoryStorageProvider;
use stream_download::{Settings, StreamDownload};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // We need to add a header to tell the Icecast server that we can parse the metadata
    // embedded within the stream itself.
    let client = Client::builder().request_icy_metadata().build()?;
    let stream =
        HttpStream::new(client, "https://some-cool-url.com/some-file.mp3".parse()?).await?;

    let icy_headers = IcyHeaders::parse_from_headers(stream.headers());

    // buffer 5 seconds of audio
    // bitrate (in kilobits) / bits per byte * bytes per kilobyte * 5 seconds
    let prefetch_bytes = icy_headers.bitrate().unwrap() / 8 * 1024 * 5;

    let reader = StreamDownload::from_stream(
        stream,
        // use bounded storage to keep the underlying size from growing indefinitely
        BoundedStorageProvider::new(
            MemoryStorageProvider,
            // be liberal with the buffer size, you need to make sure it holds
            // enough space to prevent any out-of-bounds reads
            NonZeroUsize::new(512 * 1024).unwrap(),
        ),
        Settings::default().prefetch_bytes(prefetch_bytes as u64),
    )
    .await?;

    let metadata_reader = IcyMetadataReader::new(
        reader,
        // Since we requested icy metadata, the metadata interval header should be
        // present in the response. This will allow us to parse the metadata
        // within the stream.
        icy_headers.metadata_interval(),
        // Print the stream metadata whenever we receive new values
        |metadata| println!("{metadata:?}\n"),
    );

    Ok(())
}

Seeking within the stream

Seeking is supported with a few limitations. See the docs for IcyMetadataReader for details.

Supported Rust Versions

The MSRV is currently 1.70.0.