Closed yuvadm closed 3 months ago
Hey @yuvadm, thanks for your issue. Yeah, providing a callback interface to query the stream info periodcally should be pretty easy. I've been meaning to add that for download progress. I can include a reference to the stream in there as well. Just curious though, do you have an example of a stream that uses icy-title
? I don't know much about Icecast, but I don't see a reference to that header in this document and my understanding was that you need to parse the current song info from the stream itself rather than the headers. The termusic project has an example of that here. Let me know if there's a stream I can test that uses icy-title
and I can see if it works.
I believe SomaFM streams use icy-title
(although not entirely sure), maybe try e.g. https://ice5.somafm.com/groovesalad-128-mp3
?
I don't believe so. It has icy-name
which has the stream name, but not the song title.
❯ http GET https://ice5.somafm.com/groovesalad-128-mp3 Icy-MetaData:1
HTTP/1.0 200 OK
Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type, Icy-MetaData
Access-Control-Allow-Methods: GET, OPTIONS, SOURCE, PUT, HEAD, STATS
Access-Control-Allow-Origin: *
Cache-Control: no-cache, no-store
Connection: Close
Content-Type: audio/mpeg
Date: Fri, 31 May 2024 18:15:40 GMT
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Server: Icecast 2.4.0-kh15
icy-br: 128
icy-genre: Ambient Chill
icy-metaint: 16000
icy-name: Groove Salad: a nicely chilled plate of ambient beats and grooves. [SomaFM]
icy-notice1: <BR>This stream requires <a href="http://www.winamp.com/">Winamp</a><BR>
icy-notice2: SHOUTcast Distributed Network Audio Server/Linux v1.9.5<BR>
icy-pub: 0
icy-url: http://somafm.com
Hmm, then I might be misunderstanding something.
I know for sure that this stream updates song titles as they change, how might that be implemented?
Edit: ok this might just be mpeg metadata within the stream, this is how mpv
displays it:
$ mpv --msg-level=all=info https://ice5.somafm.com/groovesalad-128-mp3
(+) Audio --aid=1 (mp3 2ch 44100Hz)
AO: [pipewire] 44100Hz stereo 2ch floatp
File tags:
icy-title: Normandie - Moon and Sun (Sine Remix)
Take a look at the link I posted in my first comment here. There's Icecast-specific metadata in the stream itself. You can adapt that code to your needs and wrap the stream reader returned by stream-download
with something like that to parse the metadata.
Thinking about it some more, it may be nice to add something like this as a storage provider since this is probably a common use case. I'll take a look and see if it makes sense to add that as on optional feature.
@aschey interesting suggestion, I tried to take that code and add a impl Seek
for it. Something naive like:
impl<T: Read + Seek, F: Fn(&str)> Seek for FilterOutIcyMetadata<T, F> {
fn seek(&mut self, relative_position: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(relative_position)
}
}
doesn't seem to cut it. Any ideas how to easily do that?
If you don't need to support seeking (and you probably don't for a radio stream), you can wrap it in a ReadOnlySource
like they do here. If you do need seek to work for some reason, you would probably need to clear the state of remaining_bytes
on seek and restart it on the next read.
huh, weird. I was under the impression that I can't connect a ReadOnlySource
- https://github.com/RustAudio/rodio/issues/580
This is what got me to use stream-download-rs in the first place :)
Are you using Rodio with the default features? Try with default-features = false, features - ["symphonia-all"]
. The crash in your issue there is because Rodio tries to instantiate a separate decoder for each audio type and seeks back to the beginning if it fails until it finds a valid one. If you only enable Symphonia, it will skip all that and let Symphonia figure out the correct format.
One caveat though, Symphonia requires a field on the source that tells it whether it's seekable or not. Rodio currently hardcodes this to true
which means Symphonia may try to perform a seek operation during instantiation if you hit certain cases. So far in my testing, this hasn't come up though. It would probably be good for Rodio to have a custom constructor for Symphonia that allows passing in a different source with is_seekable = false
though.
Alright, I've been successfully nerdsniped :smile:. I made an Icecast metadata parser as a separate crate: https://github.com/aschey/icy-metadata. I took a pass at seek support here. Seeking from the end of a stream doesn't make sense conceptually so that will still return an error, but other types of seeks should work. I'll add some examples and publish it once I do some more testing.
heh :rofl: glad I managed to pique your interest! Great to see this coming along, I'm still struggling to understand the basics of hooking everything together, but will happily await some properly tested examples in that crate for reference. Thanks!
Understandable, audio streaming is deceptively complicated. Seeking ended up being more difficult than I thought. I have it partially working for seeks less than 2x the metadata interval. I'll publish it once I implement the rest of the logic there. In the meantime, I added an example here if you want to add the repo as a git dependency and give it a try. If you use Rodio like I mentioned above (default-features = false, features - ["symphonia-all"]
), it shouldn't trigger any seek requests.
@aschey ok this approach definitely does the trick, I'm using the new crate and following your example and things are working! FWIW I don't require seeking on the actual stream, but of course it would be nice to have for more elaborate use cases.
Nice, good to hear. Yeah, I could maybe see a feature where you let people go back one song or something. Crate is now published here https://crates.io/crates/icy-metadata. Seeking should be functional with a few minor limitations, explained here https://docs.rs/icy-metadata/latest/icy_metadata/struct.IcyMetadataReader.html.
I'm gonna go ahead and close this now, feel free to open another issue if anything else comes up.
Possibly a dupe of #18 - is is possible to receive updates when stream headers change?
e.g. in media streams I'd like to poll
stream.header("icy-title")
for the currently playing song, and notice when it changes.