From d5576dd1bdd0e4af446b7990acab2a4c6a91a3f2 Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Mon, 3 Jun 2024 10:32:37 +0200 Subject: Icy-metadata basic stuff works --- Cargo.lock | 17 ++++++++++++ Cargo.toml | 1 + src/audio/rodio.rs | 77 +++++++++++++++++++++++++++--------------------------- 3 files changed, 57 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e95ecfc..e31c5e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,6 +622,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "icy-metadata" +version = "0.1.0" +dependencies = [ + "http", + "reqwest", + "tracing", + "urlencoding", +] + [[package]] name = "idna" version = "0.5.0" @@ -1453,6 +1463,7 @@ dependencies = [ name = "somafm" version = "0.4.0" dependencies = [ + "icy-metadata", "inquire", "reqwest", "rodio", @@ -1852,6 +1863,12 @@ dependencies = [ "percent-encoding", ] +[[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" diff --git a/Cargo.toml b/Cargo.toml index aafc8c6..9766ed7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ keywords = ["radio", "somafm", "media"] repository = "https://github.com/yuvadm/somafm.rs" [dependencies] +icy-metadata = { version = "0.1.0", path = "../icy-metadata" } 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..520628c 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::::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,33 @@ 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 metadata.is_ok() { + let md = metadata.unwrap(); + if md.track_title().is_some() { + let tr = md.track_title().unwrap(); + println!("{tr}"); + } + } + }, + )) + .unwrap(), + ); let handle = tokio::task::spawn_blocking(move || { sink.sleep_until_end(); }); - handle.await.unwrap() + handle.await.unwrap(); } } -- cgit v1.3.1 From a4ae080d330b8917b22f2f2e017f508f49fecd5e Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Mon, 3 Jun 2024 10:35:22 +0200 Subject: Bump to v0.5.0 and cleanup output for now --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/audio/rodio.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e31c5e0..e1197c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1461,7 +1461,7 @@ dependencies = [ [[package]] name = "somafm" -version = "0.4.0" +version = "0.5.0" dependencies = [ "icy-metadata", "inquire", diff --git a/Cargo.toml b/Cargo.toml index 9766ed7..e93e03a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/audio/rodio.rs b/src/audio/rodio.rs index 520628c..2557ed4 100644 --- a/src/audio/rodio.rs +++ b/src/audio/rodio.rs @@ -53,7 +53,7 @@ impl Rodio { let md = metadata.unwrap(); if md.track_title().is_some() { let tr = md.track_title().unwrap(); - println!("{tr}"); + println!(" {tr}"); } } }, -- cgit v1.3.1 From 7602f93105510766a5b21878819d6dbc66dd1e01 Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Tue, 4 Jun 2024 11:17:38 +0200 Subject: Use published icy-metadata crate --- Cargo.lock | 2 ++ Cargo.toml | 2 +- src/audio/rodio.rs | 6 ++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1197c5..7fd9568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,6 +625,8 @@ dependencies = [ [[package]] name = "icy-metadata" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473e3fafde6c9b9d0753315701d000317db6965085bafcf7369c8d945dd5ef3b" dependencies = [ "http", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index e93e03a..53301d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["radio", "somafm", "media"] repository = "https://github.com/yuvadm/somafm.rs" [dependencies] -icy-metadata = { version = "0.1.0", path = "../icy-metadata" } +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 2557ed4..0f9fbb2 100644 --- a/src/audio/rodio.rs +++ b/src/audio/rodio.rs @@ -49,10 +49,8 @@ impl Rodio { // response. This will allow us to parse the metadata within the stream icy_headers.metadata_interval(), |metadata| { - if metadata.is_ok() { - let md = metadata.unwrap(); - if md.track_title().is_some() { - let tr = md.track_title().unwrap(); + if let Ok(md) = metadata { + if let Some(tr) = md.stream_title() { println!(" {tr}"); } } -- cgit v1.3.1