summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2024-06-03 10:32:37 +0200
committerYuval Adam <_@yuv.al>2024-06-03 10:32:37 +0200
commitd5576dd1bdd0e4af446b7990acab2a4c6a91a3f2 (patch)
tree408143fa72aa020ae1ea931251110aba04a69336
parent06a5db8748fd67bf852a09ae48b8f1b0e68dc084 (diff)
Icy-metadata basic stuff works
-rw-r--r--Cargo.lock17
-rw-r--r--Cargo.toml1
-rw-r--r--src/audio/rodio.rs77
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
@@ -623,6 +623,16 @@ dependencies = [
]
[[package]]
+name = "icy-metadata"
+version = "0.1.0"
+dependencies = [
+ "http",
+ "reqwest",
+ "tracing",
+ "urlencoding",
+]
+
+[[package]]
name = "idna"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1453,6 +1463,7 @@ dependencies = [
name = "somafm"
version = "0.4.0"
dependencies = [
+ "icy-metadata",
"inquire",
"reqwest",
"rodio",
@@ -1853,6 +1864,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"
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::<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,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();
}
}