@music-metadata/icy
Version:
Parses ICY metadata from a web stream
214 lines (151 loc) • 5.36 kB
Markdown
[](https://github.com/Borewit/music-metadata-icy/actions/workflows/nodejs-ci.yml)
[](https://npmjs.org/package/@music-metadata/icy)
[](https://npmcharts.com/compare/@music-metadata%2Ficy?start=365)
# @music-metadata/icy
Decode [ICY metadata](https://en.wikipedia.org/wiki/SHOUTcast#Metadata) (used by Icecast and Shoutcast) from audio streams, commonly used in internet radio.
This module extracts ICY metadata (e.g., `StreamTitle`) from HTTP responses while passing through clean audio chunks for playback or further processing.
> ✅ **Lightweight** • **Fast** • **Web & Node-compatible** • Built on [`strtok3`](https://github.com/Borewit/strtok3)
## 🚀 Installation
```bash
npm install @music-metadata/icy
```
Or with Yarn:
```bash
yarn add @music-metadata/icy
```
## Demo
* [ICY Radio Stream Player](https://github.com/Borewit/icy-radio-stream-player)
## 📦 Usage
```ts
import { parseIcyResponse } from '@music-metadata/icy';
const response = await fetch('https://example.com/radio-stream', {
headers: {
'Icy-MetaData': '1'
}
});
const audioStream = parseIcyResponse(response, ({ metadata }) => {
const title = metadata.StreamTitle;
if (title) {
console.log('Now Playing:', title);
}
});
// You can now pipe `audioStream` to a decoder or audio player.
```
## 🧠 API
### `parseIcyResponse(response, handler): ReadableStream<Uint8Array>`
Process a fetch-compatible HTTP response and extract ICY metadata on the fly.
#### Parameters
* `response: Response`
A standard Fetch API [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object with streaming body.
* `handler: (update: MetadataUpdate) => void`
A callback triggered when new ICY metadata is available.
#### Returns
* `ReadableStream<Uint8Array>`
A web-compatible readable stream containing **only the audio payload**, excluding metadata.
#### Example
```ts
{
metadata: {
StreamTitle: 'Cool Song',
StreamUrl: 'https://example.com',
...
},
stats: {
totalBytesRead: 20480,
audioBytesRead: 19200,
icyBytesRead: 1280
}
}
```
### `decodeIcyStreamChunks(stream, metaInt, handler): ReadableStream<Uint8Array>`
Lower-level function to extract ICY metadata from a `ReadableStream` where the metadata interval is already known.
#### Parameters
* `stream: ReadableStream<Uint8Array>` or Node's `ReadableStream`
* `metaInt: number` – The icy metadata interval in bytes.
* `handler: (update: MetadataUpdate) => void` – Metadata callback, same as above.
#### Returns
* `ReadableStream<Uint8Array>` – Cleaned stream without metadata blocks.
Use this method if you already know the `icy-metaint` (e.g., from headers or external configuration).
## 🧺 ICY Metadata Parsing
ICY metadata is parsed from raw string format:
```ts
"StreamTitle='song';StreamUrl='url';"
```
Parsed result:
```ts
{
StreamTitle: 'song',
StreamUrl: 'url'
}
```
Internally handled by:
```ts
function parseRawIcyMetadata(raw: string): Map<string, string>
```
## 📜 Types
### `type IcyMetadata`
```ts
type IcyMetadata = {
StreamTitle?: string;
StreamUrl?: string;
icyName?: string;
icyGenre?: string;
icyUrl?: string;
bitrate?: string;
contentType?: string;
[key: string]: string | undefined;
}
```
### `type MetadataUpdate`
```ts
type MetadataUpdate = {
metadata: IcyMetadata;
stats: {
totalBytesRead: number;
audioBytesRead: number;
icyBytesRead: number;
};
};
```
## 🧱 Internals
If `Icy-Metaint` is not provided by the server, the module attempts to **auto-detect** the metadata interval by scanning the stream for known ICY patterns such as `"StreamTitle="`.
## 🧭 How It Works
The following diagram shows how `@music-metadata/icy` fits into a web-based ICY audio streaming pipeline, parsing interleaved metadata while passing clean audio through to playback:
```mermaid
graph TD
%% Node Styles
style A fill:#bbf,stroke:#333,stroke-width:2px
style B fill:#ddf,stroke:#333,stroke-width:2px
style C fill:#afa,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5
style D fill:#ffe4b3,stroke:#333,stroke-width:2px
style E fill:#fcc,stroke:#333,stroke-width:2px,stroke-dasharray: 3 3
style F fill:#fcf,stroke:#333,stroke-width:2px
style G fill:#cff,stroke:#333,stroke-width:2px,stroke-dasharray: 2 4
%% Nodes
A["🎧 ICY Web Stream<br/>(Icecast via Fetch)"]
B["🔀 Fetch with<br/>ICY-MetaData Header"]
C["🧩 @music-metadata/icy<br/>(ICY Parser)"]
D["🔁 Decoded Audio Stream"]
E["🎵 HTML5 Audio<br/><audio> Element"]
F["🛰️ ICY Metadata Events"]
G["🖥️ Metadata Display<br/>in React UI"]
%% Flow
A --> B
B -->|ICY Interleaved Audio| C
C -->|Audio Stream| D
D --> E
C -->|Metadata Events| F
F -->|Track Info etc.| G
```
## 📄 License
MIT — see [LICENSE.txt](LICENSE.txt) for full text.