diff options
| author | Yuval Adam <_@yuv.al> | 2024-06-04 11:19:16 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-06-04 11:19:16 +0200 |
| commit | 482a942cd9e62fadee4ee7bc1fd7e8def8a85f71 (patch) | |
| tree | b88c27fd594b1c5be9d3ccdd327343bb80d509b3 | |
| parent | 06a5db8748fd67bf852a09ae48b8f1b0e68dc084 (diff) | |
| parent | 7602f93105510766a5b21878819d6dbc66dd1e01 (diff) | |
Merge pull request #4 from yuvadm/icy-filterv0.5.0
Use icy-metadata filtering
| -rw-r--r-- | Cargo.lock | 21 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | src/audio/rodio.rs | 75 |
3 files changed, 59 insertions, 40 deletions
@@ -623,6 +623,18 @@ dependencies = [ ] [[package]] +name = "icy-metadata" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473e3fafde6c9b9d0753315701d000317db6965085bafcf7369c8d945dd5ef3b" +dependencies = [ + "http", + "reqwest", + "tracing", + "urlencoding", +] + +[[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1451,8 +1463,9 @@ dependencies = [ [[package]] name = "somafm" -version = "0.4.0" +version = "0.5.0" dependencies = [ + "icy-metadata", "inquire", "reqwest", "rodio", @@ -1853,6 +1866,12 @@ dependencies = [ ] [[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1,7 +1,7 @@ [package] name = "somafm" description = "A Rust-based command line player for SomaFM radio" -version = "0.4.0" +version = "0.5.0" authors = ["Yuval Adam <_@yuv.al>"] edition = "2021" license = "MIT" @@ -9,6 +9,7 @@ keywords = ["radio", "somafm", "media"] repository = "https://github.com/yuvadm/somafm.rs" [dependencies] +icy-metadata = { version = "0.1.0" } inquire = "0.7.5" reqwest = { version = "0.12.4", features = ["blocking"] } rodio = { version = "0.18.1", features = ["symphonia", "symphonia-mp3"] } diff --git a/src/audio/rodio.rs b/src/audio/rodio.rs index 3dffa5e..0f9fbb2 100644 --- a/src/audio/rodio.rs +++ b/src/audio/rodio.rs @@ -1,49 +1,32 @@ +use icy_metadata::{IcyHeaders, IcyMetadataReader, RequestIcyMetadata}; +use rodio::{Decoder, OutputStream, Sink}; use std::num::NonZeroUsize; +use stream_download::http::reqwest::Client; +use stream_download::http::HttpStream; +use stream_download::storage::bounded::BoundedStorageProvider; +use stream_download::storage::memory::MemoryStorageProvider; +use stream_download::{Settings, StreamDownload}; -use rodio::{Decoder, OutputStream, Sink}; -use stream_download::{ - http::{reqwest::Client, HttpStream}, - source::SourceStream, - storage::{ - bounded::BoundedStorageProvider, memory::MemoryStorageProvider, temp::TempStorageProvider, - }, - Settings, StreamDownload, -}; +const AUDIO_BUFFER_SECONDS: u32 = 5; pub struct Rodio {} impl Rodio { pub async fn play(&self, url: &str) { - let reader = StreamDownload::new_http( - url.parse().unwrap(), - TempStorageProvider::new(), - Settings::default(), - ) - .await - .unwrap(); + let (_stream, handle) = OutputStream::try_default().unwrap(); + let sink = Sink::try_new(&handle).unwrap(); - let _res = tokio::task::spawn_blocking(move || { - let (_stream, handle) = OutputStream::try_default().unwrap(); - let sink = Sink::try_new(&handle).unwrap(); - sink.append(Decoder::new(reader).unwrap()); - sink.sleep_until_end(); - }) - .await; + // request metadata header + let client = Client::builder().request_icy_metadata().build().unwrap(); - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); - let stream = HttpStream::<Client>::create(url.parse().unwrap()) - .await - .unwrap(); + let stream = HttpStream::new(client, url.parse().unwrap()).await.unwrap(); - println!("content type={:?}", stream.content_type()); - let bitrate: u64 = stream.header("Icy-Br").unwrap().parse().unwrap(); - println!("bitrate={bitrate}"); + let icy_headers = IcyHeaders::parse_from_headers(stream.headers()); + // println!("Icecast headers: {icy_headers:#?}\n"); + // println!("content type={:?}\n", stream.content_type()); - // buffer 5 seconds of audio - // bitrate (in kilobits) / bits per byte * bytes per kilobyte * 5 seconds - let prefetch_bytes = bitrate / 8 * 1024 * 5; - println!("prefetch bytes={prefetch_bytes}"); + // buffer bitrate (in kilobits) / bits per byte * bytes per kilobyte * N seconds + let prefetch_bytes = icy_headers.bitrate().unwrap() / 8 * 1024 * AUDIO_BUFFER_SECONDS; let reader = StreamDownload::from_stream( stream, @@ -54,15 +37,31 @@ impl Rodio { // prevent any out-of-bounds reads NonZeroUsize::new(512 * 1024).unwrap(), ), - Settings::default().prefetch_bytes(prefetch_bytes), + Settings::default().prefetch_bytes(prefetch_bytes as u64), ) .await .unwrap(); - sink.append(rodio::Decoder::new(reader).unwrap()); + + sink.append( + Decoder::new(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(), + |metadata| { + if let Ok(md) = metadata { + if let Some(tr) = md.stream_title() { + println!(" {tr}"); + } + } + }, + )) + .unwrap(), + ); let handle = tokio::task::spawn_blocking(move || { sink.sleep_until_end(); }); - handle.await.unwrap() + handle.await.unwrap(); } } |
