Icecast Metadata Player
Icecast Metadata Player is a simple to use Javascript class that plays an Icecast stream with real-time metadata updates.
- Plays an Icecast stream using the Media Source Extensions API, HTML5 audio, and Web Assembly decoder (Ogg Opus).
- Pushes synchronized metadata updates taken from ICY metadata and OGG metadata.
- Seamless playback during network changes (i.e. Wifi to Cell network).
- Available as an NPM Package and as a file to include in a
<script>
tag.
Checkout the demos here!
See the main page of this repo for other Icecast JS tools:
https://github.com/eshaz/icecast-metadata-js
Supported codecs:
- MP3
audio/mpeg
, audio/mp4
- AAC
audio/aac
, audio/mp4
- FLAC
application/ogg
, audio/mp4
- Opus
application/ogg
, audio/mp4
, audio/webm
- Vorbis
application/ogg
, audio/webm
- All other browser supported MediaSource and HTML5 Audio codecs
Supported Browsers:
- Android, Chrome, Firefox, Opera
audio/mpeg
, audio/aac
, application/ogg
(FLAC, Opus, Vorbis) - iOS 12.4 and higher, Safari Desktop
audio/mpeg
, audio/aac
, application/ogg
(Opus via opus-decoder
) - Check your Browser Here
Media Source Extension support is expanded by wrapping the audio in the ISOBMFF (mp4) or WEBM containers using mse-audio-wrapper
Contents
Installing
Install via NPM
-
npm i icecast-metadata-player
Example
import IcecastMetadataPlayer from "icecast-metadata-player";
const player = new IcecastMetadataPlayer(
"https://dsmrad.io/stream/isics-all",
{ onMetadata: (metadata) => {console.log(metadata)} }
);
Install as a standalone script
-
Download the latest build.
-
Include the file in a <script>
tag in your html.
-
IcecastMetadataPlayer
is made available as a global variable in your webpage to use wherever.
Example
<script src="icecast-metadata-player-1.5.0.min.js"></script>
<script>
const onMetadata = (metadata) => {
document.getElementById("metadata").innerHTML = metadata.StreamTitle;
};
const player =
new IcecastMetadataPlayer(
"https://dsmrad.io/stream/isics-all",
{ onMetadata }
);
</script>
<body>
<button onclick="player.play();"> Play </button>
<button onclick="player.stop();"> Stop </button>
<p> Now Playing: <span id="metadata"></span> </p>
</body>
Usage
-
To use IcecastMetadataPlayer
, create a new instance by passing in the stream endpoint, and the options object (optional). See the Options and Callbacks sections for more information.
const player = new IcecastMetadataPlayer("https://stream.example.com", {
onMetadata: (metadata) => {console.log(metadata)},
...options
})
IcecastMetadataPlayer supports reading ICY metadata, Ogg (Vorbis Comment) metadata, or both. Each section below describes how to instantiate IcecastMetadataPlayer
to use these different metadata types.
ICY Metadata
-
When reading ICY metadata, the client should be able to read the Icy-MetaInt
header value on the response. If the CORS policy does not allow clients to read the Icy-MetaInt
header, then IcecastMetadataPlayer
will attempt to detect the metadata interval based on the incoming request data.
const player = new IcecastMetadataPlayer("https://stream.example.com/stream.mp3", {
onMetadata: (metadata) => {console.log(metadata)},
metadataTypes: ["icy"]
...options
})
OGG Metadata
-
OGG (Vorbis Comment) metadata, if available, usually offers more detail than ICY metadata.
const player = new IcecastMetadataPlayer("https://stream.example.com/stream.opus", {
onMetadata: (metadata) => {console.log(metadata)},
metadataTypes: ["ogg"]
...options
})
ICY and OGG Metadata
-
ICY and OGG metadata can both be read from the stream. Usually a stream will only have one or the other, but this option is possible if needed.
const player = new IcecastMetadataPlayer("https://stream.example.com/stream.flac", {
onMetadata: (metadata) => {console.log(metadata)},
metadataTypes: ["icy", "ogg"]
...options
})
Playing a Stream
-
To begin playing a stream, call the .play()
method on the instance.
Note: IcecastMetadataPlayer will use either the MediaSource api or, if that is not available, HTML5 audio with a second request for metadata.
const player = new IcecastMetadataPlayer("https://stream.example.com/stream.flac", {
onMetadata: (metadata) => {console.log(metadata)},
metadataTypes: ["icy"]
...options
})
player.play();
-
Metadata will be sent as soon as it is discovered via the onMetadataEnqueue
callback and when the metadata is synchronized with the audio via the onMetadata
callback. See the Methods section below for additional callbacks.
Metadata
{
StreamTitle: "The stream's title",
StreamUrl: "The stream's url",
TITLE: "The stream's title",
ARTIST: "The stream's artist",
ALBUM: "The stream's album"
}
-
To stop playing the stream, call the stop()
method on the instance.
player.stop();
See the HTML demos for examples.
Reconnecting
IcecastMetadataPlayer enables retry / reconnect logic by default. When a fetch or network error occurs, IcecastMetadataPlayer will attempt to recover by retrying the fetch request.
This allows for seamless audio playback when switching networks, (i.e. from a cell network to a Wifi network).
See Retry Options to configure or disable reconnects.
Reconnect Lifecycle:
- The
error
/ onError
event will be fired indicating the issue that caused the retry process to start. - IcecastMetadataPlayer will retry the initial fetch request periodically using an exponential back-off strategy configurable in the
options
object.
- Each retry attempt will fire a
retry
/ onRetry
event.
- Retries will stop when either of the below conditions are met:
- The fetch request succeeds.
- The audio element is paused /
stop()
is called. - The audio element buffer is empty and the retry timeout is met.
- When the retry is successful, a
streamstart
/ onStreamStart
event will be fired and the audio will restart playing from the new request.
- When using the MediaSource API, the old and new request will be synchronized together on a frame basis for seamless playback.
- When the retry times out, a
retrytimeout
/ onRetryTimeout
event will be fired and the stream will stop.
Seamless audio playback:
The audio will continue to play until the buffer runs out while reconnecting. If the reconnect is successful before the buffer runs out, there will be no gap in playback.
To increase the amount of audio that is buffered by clients, increase the <burst-size>
setting in your Icecast server.
API
Methods
Getters
player.audioElement
- Returns the HTML5 Audio element.
player.icyMetaInt
- Returns the ICY Metadata Interval of this instance.
player.metadataQueue
- Returns the array of enqueued
metadata
in FIFO order.
[
{
metadata: { StreamTitle: "Title 1" },
timestampOffset: 2.5,
timestamp: 1
},
{
metadata: { StreamTitle: "Title 2" },
timestampOffset: 5,
timestamp: 2
}
]
player.state
- Returns the current state of the IcecastMetadataPlayer.
"loading", "playing", "stopping", "stopped", "retrying"
player.playbackMethod
- Returns the playback method in use to play the stream.
- The playback method is chosen after the codec of the incoming stream is determined.
"mediasource", "webaudio", "html5"
Instantiating
You can create any number of instances of IcecastMetadataPlayer on your webpage.
Each instance must have it's own audio element.
const player_1 = new IcecastMetadataPlayer("https://example.com/stream_1", {
...options,
...callbacks
});
const player_2 = new IcecastMetadataPlayer("https://example.com/stream_2", {
...options,
...callbacks
})
Options
endpoint
(required)
- HTTP(s) endpoint for the Icecast compatible stream.
audioElement
(optional) - Default new Audio()
- HTML5 Audio Element to use to play the Icecast stream.
enableLogging
(optional) Default false
- Set to
true
to enable warning and error logging to the console
playbackMethod
(optional) Default mediasource
- Sets the preferred playback method.
"mediasource", "webaudio", "html5"
- IcecastMetadataPlayer will attempt to use this playback method first before other methods.
- The playback method is automatically chosen depending on browser support for the codec of the Icecast stream.
Retry Options
Metadata Options
Callbacks (all optional)
Metadata
onMetadata(metadata, timestampOffset, timestamp)
Called when metadata is synchronized with the audio.
metadata
ICY or Ogg metadata in an object of key value pairs
- ICY:
{ "StreamTitle: "The Stream Title" }
- Ogg:
{ "TITLE: "The Stream Title", "ARTIST": "Artist 1; Artist 2"... }
timestampOffset
time when is scheduled to be updated.timestamp
time when metadata was discovered on the stream.
onMetadataEnqueue(metadata, timestampOffset, timestamp)
Called when metadata is discovered on the stream.
metadata
ICY or Ogg metadata in an object of key value pairs
- ICY:
{ "StreamTitle: "The Stream Title" }
- Ogg:
{ "TITLE: "The Stream Title", "ARTIST": "Artist 1; Artist 2"... }
timestampOffset
time when is scheduled to be updated.timestamp
time when metadata was discovered on the stream.
Stream lifecycle
onLoad()
Called when the fetch request is started.onStreamStart()
Called when fetch request begins to return data.onPlay()
Called when the audio element begins playing.onStream(streamData)
Called when stream data is sent to the audio element.onStreamEnd()
Called when the fetch request completes.onStop()
Called when the stream is completely stopped and all cleanup operations are complete.
Reconnects
onRetry()
Called when a retry / reconnect is attempted.onRetryTimeout()
Called when the retry / reconnect attempts have stopped because they have timed-out.
Error / Warning
onWarn(message, ...messages)
Called with message(s) when a warning condition is met.onError(message, ...messages)
Called with message(s) when a fallback or error condition is met.
Informational
onCodecUpdate({ ...codecInformation })
Called with audio codec information whenever there is a change
- Information such as
bitrate
and samplingRate
are passed in as an object to this callback
Events
Each callback is made available as an event. The parameters for each callback are passed into event.details
as an array.
player.addEventListener('metadata', (event) => {
const [metadata, timestampOffset, timestamp] = event.detail;
})
Troubleshooting
Debugging
Source Map
IcecastMetadataPlayer builds are supplied with a source map, which allows the minified code to be viewed as fully formatted code in a browser debugger.
- To enable the source map, simply copy
icecast-metadata-player-1.5.0.min.js.map
located in the build folder of this project to the location along side icecast-metadata-player-1.5.0.min.js
in your website. - The source map can be used to step through and debug the code as well as see the full variable names and file origin on stack traces if you are facing any issues.
Warning messages
Note: Warning messages are be enabled by setting options.enableLogging = true
Reconnected successfully after retry event. Found 145 frames (3.788 seconds) of overlapping audio data in new request. Synchronized old and new request.
- The stream successfully reconnected after a disconnect, and the old request was synchronized with the new request for seamless playback.
- This usually happens when switching networks, or if there was a brief interruption in your internet connection. (i.e. cell to WiFi, WiFi to ethernet, temporary loss of cell connection, etc.)
Reconnected successfully after retry event. Found no overlapping frames from previous request. Unable to sync old and new request.
- The stream successfully reconnected after a disconnect, but there wasn't enough data in the buffer to synchronize the old request with new request.
- If this happens frequently, try increasing the
burst-size
option (or equivalent) in your Icecast configuration to increase the client's buffered data.
Passed in Icy-MetaInt is invalid. Attempting to detect ICY Metadata.
- The stream has been requested with ICY metadata, but the server did not respond with the
Icy-MetaInt
header. IcecastMetadataPlayer
will attempt to detect the ICY metadata interval, and will timeout after a default of 2 seconds, or the value in milliseconds passed into the icyDetectionTimeout
option. - If your stream contains ICY metadata, and it is not detected, audio errors will occur. Increase the detection timeout to search longer for ICY metadata.
- This warning could also be displayed if the stream was requested with ICY metadata, but it does not contain ICY metadata. In this case, the ICY detection will timeout and the stream will play without ICY metadata. Please update your code to no longer request ICY metadata.
This stream is not an OGG stream. No OGG metadata will be returned.
- IcecastMetadataPlayer has
"ogg"
passed into the metadataTypes
options, but the stream response is not an ogg stream. ICY metadata and the stream will work without issues. Please remove the "ogg"
option to remove this warning.
Error messages
This stream was requested with ICY metadata. If there is a CORS preflight failure, try removing "icy" from the metadataTypes option.
- A network error occurred while requesting the stream with the
Icy-MetaData: 1
header.
- IcecastMetadataPlayer will attempt to retry the request using the retry configuration.
- It's possible that this was caused by the Icecast server's CORS policy not allowing the
Icy-Metadata
header. If you want ICY metadata, your CORS policy must allow this header to be requested. See CORS Troubleshooting for more information. - Additionally, attempting to access a HTTP from a HTTPS origin will be blocked by modern browsers
Your browser does not support this audio codec mime-type
Unsupported Codec mime-type
- No playback methods are able to play the audio codec being streamed from the Icecast stream. Check the URL that was passed in. If the URL is correct, this stream cannot be played on your browser.
The audio element encountered an error
- An error occurred while the browser was playing or decoding the audio. This may occur if your browser doesn't support a codec.