eshaz / icecast-metadata-js Goto Github PK
View Code? Open in Web Editor NEWBrowser and NodeJS packages for playing and reading Icecast compatible streaming audio with realtime metadata updates.
Browser and NodeJS packages for playing and reading Icecast compatible streaming audio with realtime metadata updates.
A change in iOS 15.5 has broken webaudio playback. Audio playback fails to start. Earlier versions of iOS do not have this issue.
This can be reproduced with a MP3 or Opus stream on the demo page.
Some Icecast servers do not allow the ICY metadata interval to be read from the headers. The Icecast server needs to respond with Access-Control-Expose-Header: Icy-MetaInt
to allow this value to be determined. Some highly used versions of Icecast do not allow configuration of the CORS response. Additionally, users of Icecast versions that do allow this configuration, would have to manually add the configuration to their CORS policy in Icecast.
As a work around, it should be possible to dynamically determine the Ice-MetaInt based on the below criteria:
StreamTitle
value as the first value in the metadata payloadICY metadata could be detected by searching through the response for StreamTitle
and either dynamically returning metadata without regards to the metadata interval, or determining the metadata interval based on the incoming data. This would be an adequate workaround for being unable to read the Icy-MetaInt
from the response header due to an inadequate CORS policy.
Hi
First of all, thank you so much for this library!
It worked flawlessly on a plain HTML/JS page as a proof-of-concept.
But unfortunately, it seems to not work out using Next.js. I get the error:
Module not found: Can't resolve 'icecast-metadata-player'
You can reproduce the error here:
https://codesandbox.io/s/silly-gates-4wppo?file=/pages/index.js
This is probably because Node.js itself cannot import the player, as seen here:
https://codesandbox.io/s/admiring-hugle-64d86?file=/src/index.js
Is there a way to make this work? Maybe by exporting a dummy object, like the HLS player is doing (https://codesandbox.io/s/admiring-hugle-64d86?file=/src/index.js)
I'm having trouble to put two audio players on a single page. No example nor docs provide info for this case. Can anyone share a correct example of IcecastMetadataPlayer() init when there are audio1 and audio2 elements on a page ? I tried:
const audioElement1 = document.getElementById("audio1");
icecastMetadataPlayerEng = new IcecastMetadataPlayer("https://dsmrad.io/stream/isics-all", audioElement: audioElement1);
but this won't work.
It should be possible to add support for desktop playback using the NodeJS Speaker
and decoding the audio with WASM. This could be based on the webaudio
playback method.
Any changes to enable NodeJS playback should not add additional dependencies nor break / modify the existing API in any significant way.
The onStream callback is not being called for IcecastMetadataPlayer. The callback passed in from the options just needs to be invoked at the appropriate places.
stream-recorder needs to be released as a npm package.
Hello, this is a great tool and thank you very much. I use it in my stations.
Is there any way to get/set the volume of the player?
The ogg-opus-decoder
library supports multichannel decoding and this should enable a multichannel Ogg Opus Icecast stream to be played using the webaudio
playback method.
The demo should contain a stream that loops through a multichannel audio track. There should be separate endpoints for 4, 5.1, 7.1, and 2 channel versions of the track.
An option might also be added to support stereo upmixing to the max channels for the AudioContext.
It's me again, with another interesting issue.
I have the IcecastReadableStream
object working flawlessly on every desktop browser I can test with. I'm compiling the javascript using webpack for Node, as opposed to the recently-created standalone JS player, because I'm lazy and don't want to port over all my code.
Anyway, I'm now trying to get my stream player working on mobile devices, and iOS safari is the first browser I'm hitting (since I own apple devices).
Safari on iOS doesn't support MSE, but I believe I have a workaround using the web audio API. That remains to be seen, but I'm getting hung up with getting stream data at all.
I instantiate the reader in what I believe is a normal way:
// fetch the stream
fetch(streamUrl, {
method: "GET",
headers: {
"Icy-MetaData": "1",
},
signal: signal
})
// create the icecast metadata reader
.then(async (response) => {
console.log("creating icecast readable stream");
const icecast = new IcecastReadableStream(
response,
{
onStream: pushStream,
onMetadata: pushMetadata,
metadataTypes: ["icy"]
}
);
await icecast.startReading();
});
However, iOS gives me the following console error (I'm using the remotedebug-ios-webkit-adapter
npm package for remote debugging) when I call icecast.startReading()
:
[native code]:1 Unhandled Promise Rejection: TypeError: icecast.startReading is not a function. (In 'icecast.startReading()', 'icecast.startReading' is undefined)
Again, this same code works flawlessly in desktop browsers. I'm guessing something inside the library doesn't play nice with iOS safari.
This is definitely a "nice to have" but it would be useful for my application to be able to display metadata in mobile browsers.
It should be possible to allow for multiple streams of different bitrates that can be used to switch to lower or higher bitrates depending on connection speed.
If the streams are all of the same underlying audio source and are generally synchronized, they could be switched over seemlessly by syncing the decoded audio. I don't think it would be perfect because two streams are not guaranteed to have the audio aligned on a frame basis.
Perfect syncing might be achieved by decoding both streams entirely so the raw PCM could be spliced at any point regardless of the incoming codec and then fed into the web audio api.
I have 3 stations that don't show metadata. If you have time maybe you can let me know if I can do anything about it.
Thank you!
The 3 stations are:
Radio Schizoid: http://94.130.113.214:8000/chill
Classic FM: https://media-ssl.musicradio.com/ClassicFM
IBGR: http://ibizaglobalradio.streaming-pro.com:8024/
Do you can add a option for browser that not support Regex-Groups and Exponentiation (**) ?
Would it be possible to enable a way to receive metadata without actively playing the audio stream?
I need a simple page displaying data on the current track from my server, to be displayed as a widget on a livestream. For this to work the widget must be always active without any input from the user. This requires forcing some sort of autoplay right now, which is blocked by the chromium based browser being used, meaning no metadata event is received. I've tried setting audioElement: null
when instantiating the player but it did not have the desired effect.
Thank you for your work.
Hey,
newer browsers need a permission to play audio automatically on site load. I've made an audio player that remembers its latest play state and resumes playing on site reload. If the site doesn't have the autoplay enabled, calling play()
will result in an unhandled promise exception:
Console message: Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD
You might want to look into this and handle the exception properly so I can act on it from within my player code.
Thanks!
IcecastMetadataPlayer should continue to play until the audio buffer is emptied when a stream ends successfully (i.e. when there is no error reported from the fetch request).
When a stream disconnects / stops, the audio buffer should be exhausted before the stop
event is called and the playback is stopped. Currently, playback stops immediately after a successful disconnect, and the remaining audio in the buffer is never played.
Several users have expressed interest in using this library to retrieve a stream's metadata without playing. This already exists in Icecast through a JSON api, but it would be nice to offer an implementation that utilizes this. There also may be cases where that API is unavailable, and the only way to retrieve metadata is to use the stream request.
codec-parser
to calculate the timestamps.Hi, I wanted to be able to get metadata only without playing the media url
First of all: thanks for your life-safer library, it really helps me a lot.
Anyway, I found an issue that may have an easy workaround:
Some Icecast versions could send different kinds of character sets than UTF-8 which could happen because of the source of the stream or the Icecast configuration. It makes it harder to decode some Central-European or other kinds of characters, for example in our case the character set is ISO-8859-2, but when the MetadataParser decoding the metadata, it's forced to read it as UTF-8.
As I see it's possible to add a new parameter to be able to set the decoder charset.
In this case, the IcecastMetadataPlayer can be configurable to be able to show accents and other characters properly for other languages as well. By default, it could stay as it is and it should be available to set it up by a parameter in the IcecastMetadataPlayer constructor.
What do you think about it? Do you have another workaround or is it easy to handle as I described?
Thanks for your help!
Web Audio playback stops after playing for a few minutes after the screen is locked on iOS 15 simulator. This issue was not present in iOS 14.8.
Set the playbackMethod
to "html5"
when instantiating.
new IcecastMetadataPlayer("https://example.com/stream", {
playbackMethod: "html5"
});
What differences are there between using this library and fetch "/currentsong?sid=#" ?
In IcecastMetadataPlayer sometimes the audio is not in sync anymore with the stream, and the gap becomes larger over time.
It happens randomly but I've experienced it a few times on Chrome (windows). I don't know if it's related to the browser (I can figure that out with some more testing). In my app I measure the time difference between where the stream (the icecast server) is at and where IcecastMetadataPlayer is at. I listen to the audio with my ears, but more precisely, I record the moment the metadata event is fired, indicating the start of a new song. Over time the time difference with the stream slowly increases and can reach a gap of 1 minute after an hour of playing, meaning the audio is 1 minute behind on the actual stream.
My personal app relies on the switching of songs (ad hoc) and it is important that the audio for every listener to the stream is somewhat in sync (within 10 seconds precision). But some listeners might be out of sync considerably. I've tested with 2 devices side by side. The issue can be fixed by restarting (re-instantiating) the IcecastMetadataPlayer.
I will implement a workaround that will stop-start the audio (or reinstantiate the player object) when the gap becomes more than 10 seconds, but it will not be a smooth transition for the listener. I'm hoping there's a better solution.
The gap never decreases after it has increased (the audio doesn't catch up), but it would be nice if there was a catch-up mechanism. A somewhat smooth re-sync mechanism.
Hopefully this explains the issue well. Let me know if I can provide more info.
It would be helpful to provide a package that does it all i.e. using MSE, isobmff-audio, and icecast-metadata-js to play an icecast stream with metadata. This should be the very similar to the code already existing in the React based demo.
const player = new IcecastMetadataPlayer(endpoint, options)
{
audioElement,
metadataTypes,
icyMetaInt,
icyDetectionTimeout,
onStream,
onMetadata,
onMetadataEnqueue,
onAudioCodecUpdate
}
player.play()
player.pause()
player.stop()
Hi I'm trying to attach a visualizer to your player but Im unsure of the source name.
Thank you!
On these versions of iOS, the webaudio and html5 method mainly works. If you set an endpoint with the received audio/aac data, then webaudio will be selected in player.js:94, and it does not have aac support in the mapping, because of this, the HTML5 player is eventually selected, because of this an error occurs.
Also on version 10 of iOS, icecastMetadataPlayer.js:55 is checked against window.EventTarget, but somehow it doesn’t work like that. Because of this, errors occur and the player crashes. I solved it by overriding window.EventTarget with a polyfill.
ReferenceError: Worker is not defined
from node_modules/icecast-metadata-player/build/icecast-metadata-player-1.10.6.min.js
(21:7898)
icecast-metadata-player
uses the Media Source Extensions API to play the stream audio in the browser. This works great in most browsers, except iOS / Webkit, which is the only modern browser that doesn't support MSE. iOS should still play the stream, but there will not be any metadata updates. An alternative will need to be found in order to support real-time metadata updates in iOS.
This is a continuation of #40.
Possible Alternatives:
icecast-metadata-js
, and send the stream data as the response to an HTML5 Audio element.
src
of an HTML5 Audio element.icecast-metadata-js
mse-audio-wrapper
to support the current MSE solution and it could be adapted to expose the frame by frame durations and byte offsets.There should be some logic to retry the request if the connection is dropped or some other recoverable network error occurs. The below options are an initial idea to configure retries.
retryTimeout
:
retryInterval
:
Add support for IcecastMetadataStats (icestats
etc.) in IcecastMetadataPlayer.
Hi, there is a really good station but it wont play. I just wanted to know if I could play it, and if not why.
https://streams.radio.co/se1a320b47/listen
Thank you!
Hi,
is there any way to attach an existing audio html element to your component? So you have a metadataChange event? Without using IcecastMetadataStats...
Hi,
I am unable to play the following stream:
http://stream.syntheticfm.com:8040/stream
It does work with your average html5 player though...
Thank you!
onError
should be called when the connection is dropped, or there was a 4xx or 5xx response code.onStreamStart
- called when the stream request start loading dataonStreamEnd
- called when the stream request ends i.e. stops loading data (success, or error)When starting a stream on mobile devices, it never gets buffered state, and the stream does not start to play.
It was working properly in version 1.8.0, but not in 1.9.0 and later. I saw there were some changes around the codec. Do you have any ideas about what causes this issue?
If I change the playback method to html5, it looks like it's started but there is no sound.
This issue comes up especially on iPhones and some TVs inbuilt browsers, on Android it's working properly.
Thank you for your work!
Check out this station url:
Also, on Android theres an app called netkeeper that is able to get any url for any radio station playing in any browser/app!
Thank you!
Hello Ethan,
first of all thank you for your hard work. I really like it and it works perfectly on Android. Unfortunately I can't get it to work on iOS. I am using Ionic and Angular. The stream is loading (network tab of developer console), but I can't hear anything. The event listener gives success on play, no error. The stream keeps on loading.
If I use a standard html5 audio without your script the stream plays fine. I really look forward to use your icecast-metadata-player.
Do you have any idea?
Hi, Im trying to get feedback after calling onMetadataFailed! Does it work for the metadata only? And not the entire Stream?
Thank you!
In https://github.com/eshaz/icecast-metadata-js/tree/master/src/icecast-metadata-stats#usage, there is
To start querying once every n seconds for metadata and statistics, call start().
statsListener.start();
To stop querying, call stop()
statsListener.start();
Why does the stop() example have .start()?
So far I have been unable to figure out how to use the metadata scripts in-browser, specifically IcecastMetadataReader
.
I've tried to include IcecastMetadataReader.js
in the html document, but it is a Node.js-specific file and results in console errors when included.
I also tried to "browserify" the script using the Node.js browserify tool. While including the resulting .js file no longer results in a console error on page load, I cannot use the IcecastMetadataReader
class in any other scripts and it errors out as being undefined.
Help would be greatly appreciated in getting started here. I'm likely missing something simple but I can't figure out what it is.
Hi,
thank you for this module, it's great! I'm building for myself a little web player with multiple channels, and I wondered if there is a way to stop the player, change its source (the URL it plays), and then start playing the other source?
I was looking for a function or a property but no avail so far.
What's your advice on this? Thanks in advance.
Hi. I hope you're ok. I've been testing my streaming with the "Bare Minimum HTML Demo" but the only response I get is this blocking responses from my server. Maybe I'm missing something in the icecast.xml ??
I have also looked at the logs, specifically /var/log/icecast/error.log
After taking a look at it I find that there's an error in the parsing (EROR connection/_handle_connection):
[2021-10-01 16:51:59] DBUG stats/modify_node_event update global clients (0)
[2021-10-01 16:51:59] DBUG stats/modify_node_event update global clients (1)
[2021-10-01 16:51:59] DBUG stats/modify_node_event update global connections (59)
[2021-10-01 16:51:59] DBUG stats/modify_node_event update global clients (0)
[2021-10-01 16:51:59] DBUG stats/modify_node_event update global clients (1)
[2021-10-01 16:51:59] DBUG stats/modify_node_event update global connections (60)
[2021-10-01 16:51:59] DBUG stats/modify_node_event update global clients (0)
[2021-10-01 16:51:59] EROR connection/_handle_connection HTTP request parsing failed
[2021-10-01 16:51:59] EROR connection/_handle_connection HTTP request parsing failed
[2021-10-01 16:51:59] EROR connection/_handle_connection HTTP request parsing failed
Is it something in the icecast.xml, I don't know if I'm missing some permissions, have you come across this problem before?
Thanks for your piece of software. I would like to use it like a charm. If you know something let me know please.
icecast-metadata-js
should also support extracting metadata from an Opus / Vorbis stream in addition to Icy Metadata. This functionality should be able to be added to IcecastMetadataReader
and acomidate for minimal user knowledge of the actual metadata source.
When a stream resumes after a retry event it may be possible to sync-up the current audio buffer with the new request data coming in from the new request. Given the new response contains audio that has already been processed in the previous response, and the audio buffer is not emptied, the two streams can be concatenated with no gaps.
mse-audio-wrapper
Probably will add the audio frame hashing in codec-parser
so it can be reused for other use cases.
It's possible that codec-parser
could be updated with a dedupliate
flag that doesn't reprocess data that's already been processed within the last n seconds.
Will need to update mse-audio-wrapper
to accept an array of CodecFrames
returned from codec-parser
as input.
In IcecastMetadataPlayer, a new option should be added that triggers retry logic after a successful disconnect of a stream. This may help listeners reconnect when a disconnect is caused by something other than a network error, such as the Icecast server / source disconnecting and restarting the stream.
I've seen that the majority of Icecast streams are meant to continue indefinitely, such as with a radio station. In these cases, any disconnect should be treated as an error. This option might make sense to default to true
because of this usage pattern.
options.alwaysReconnect
: default true
true
to reconnect after a stream is disconnected for any reason.
false
to stop playback after a stream is disconnected successfully on the server side.
Using the plugin to stream a ICECAST2 Stream, The meta Data will not load on Chrome:
Falling back to HTML5 audio with no metadata updates. See the console for details on the error.
Chrome Developer JS Console:
Access to fetch at 'http://ICESERVERIP:8000/STREAM.mp3' from origin 'http://WEBSERVERIP' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Doing some research has lead to this for normal HTML5 Audio Player to disable CORS. Is there a way to implement this into the JS?
<audio controls crossorigin="anonymous">
<source src="http://myicecastserver.dns/mount" type="audio/mpeg" />
</audio>
The browser implementation (see example in readme.md) that is able to read and display metadata relies on the MediaSource API. This is a huge limitation since MP3 is widely used as an Icecast source. Only the Chrome MediaSource API (to my knowledge) supports audio/mpeg for a MediaSource. To be able to show Icecast metadata from a MP3 source on any other browser it will need to be wrapped in a container.
In Firefox MediaSource.isTypeSupported('audio/mp4; codecs="mp3"')
returns true, so maybe the right container to use is MP4?
An ideal encoder would take in raw mp3 data, wrap it in an mp4 container, and return the result. The consumer of this should just be able to send raw MP3 bytes, and then pipe the result into a SourceBuffer as audio/mp4.
Hello, i receive this error if i try the player on my icecast server and can receive audio stream but not metadata. If I open Chrome with --disable-web-security all works without errors.
Chrome Console log
icecast-metadata-player-0.0.3.min.js:formatted:2011 GET URL net::ERR_EMPTY_RESPONSE
t @ icecast-metadata-pla…n.js:formatted:2011
Ae @ icecast-metadata-pla…n.js:formatted:2016
play @ icecast-metadata-pla…n.js:formatted:1928
onclick @ VM103 :125
icecast-metadata-player-0.0.3.min.js:formatted:1954 icecast-metadata-js
Network request failed, possibly due to a CORS issue. Trying again without ICY Metadata.
icecast-metadata-player-0.0.3.min.js:formatted:227 icecast-metadata-js
Passed in Icy-MetaInt is invalid. Attempting to detect ICY Metadata.
See https://github.com/eshaz/icecast-metadata-js for information on how to properly request ICY Metadata.
icecast-metadata-player-0.0.3.min.js:formatted:244 icecast-metadata-js
ICY Metadata not detected after searching 107800 bytes for 2.095 seconds.
Assuming stream does not contain ICY metadata. Audio errors will occur if there is ICY metadata.
Testing Icecast server with CURL seems to respond with correct headers:
curl -H "Icy-MetaData: 1" -v "URL"
* Trying IP...
* TCP_NODELAY set
* Connected to host (ip) port #### (#0)
> GET /stream HTTP/1.1
> Host: host:port
> User-Agent: curl/7.64.1
> Accept: */*
> Icy-MetaData: 1
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: Icecast 2.4.4
< Connection: Close
< Date: Sat, 23 Jan 2021 10:37:46 GMT
< Content-Type: audio/mpeg
< Cache-Control: no-cache, no-store
< Expires: Mon, 26 Jul 1997 05:00:00 GMT
< Pragma: no-cache
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, OPTIONS
< Access-Control-Allow-Headers: Content-Type, Icy-Metadata
< Access-Control-Expose-Headers: Icy-MetaInt, Icy-Br, Icy-Description, Icy-Genre, Icy-Name, Ice-Audio-Info, Icy-Url, Icy-Sr, Icy-Vbr, Icy-Pub
< icy-br:160
< ice-audio-info: ice-bitrate=160;ice-channels=2;ice-samplerate=44100
< icy-name:no name
< icy-pub:0
< icy-metaint:16000
What am I doing wrong?
MediaSource has a very small buffer size on devices like Chromecast or Firestick. Some FLAC streams reach this limit due to a large burst on connect value and throw a QuotaExceededError that is currently not handled and stops playback.
Potential solutions:
The queueing will need to happen after the frame sync logic, and any audio in this separate queue should be considered "buffered" for playback.
See: https://developers.google.com/web/updates/2017/10/quotaexceedederror
I've been able to use the player, get audio, and parse incoming metadata events. However, onStream and onError do not seem to produce any result. I'm looking for a way to detect whenever the stream goes down for instance, and I thought onError could do just that (and I suppose onStream could notify when the stream is back up). However if the player is playing and I cut the stream, onError is not triggered. Furthermore onStream never seems to be triggered by any event.
const player = new IcecastMetadataPlayer(endpoint, {
onStream: (input) => { console.log(input); },
onError: (input) => { console.log(input); },
onMetadata: (input) => { onMetadata(input); },
metadataTypes: ['ogg']
});
Hey @eshaz! This isn't really a problem relating to the library but how do you set up the Access-Control-Allow-Headers on the icecast server?
Thanks!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.