{"id":31225914,"url":"https://github.com/borewit/music-metadata-icy","last_synced_at":"2025-09-22T02:00:01.511Z","repository":{"id":305157740,"uuid":"1022096109","full_name":"Borewit/music-metadata-icy","owner":"Borewit","description":"Decode ICY metadata (used by Icecast and Shoutcast) from streaming audio responses.","archived":false,"fork":false,"pushed_at":"2025-08-19T09:26:04.000Z","size":35,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-21T21:00:49.758Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Borewit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"Borewit","buy_me_a_coffee":"borewit"}},"created_at":"2025-07-18T12:53:32.000Z","updated_at":"2025-08-19T09:26:08.000Z","dependencies_parsed_at":"2025-07-18T17:35:05.118Z","dependency_job_id":"2d257c14-a86f-4a9a-a6f6-a5a4d69bac30","html_url":"https://github.com/Borewit/music-metadata-icy","commit_stats":null,"previous_names":["borewit/music-metadata-icy"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Borewit/music-metadata-icy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Borewit%2Fmusic-metadata-icy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Borewit%2Fmusic-metadata-icy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Borewit%2Fmusic-metadata-icy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Borewit%2Fmusic-metadata-icy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Borewit","download_url":"https://codeload.github.com/Borewit/music-metadata-icy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Borewit%2Fmusic-metadata-icy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276333934,"owners_count":25624049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-21T02:00:07.055Z","response_time":72,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-22T02:00:00.586Z","updated_at":"2025-09-22T02:00:01.498Z","avatar_url":"https://github.com/Borewit.png","language":"TypeScript","funding_links":["https://github.com/sponsors/Borewit","https://buymeacoffee.com/borewit"],"categories":[],"sub_categories":[],"readme":"[![Node.js CI](https://github.com/Borewit/music-metadata-icy/actions/workflows/nodejs-ci.yml/badge.svg)](https://github.com/Borewit/music-metadata-icy/actions/workflows/nodejs-ci.yml)\n[![NPM version](https://img.shields.io/npm/v/@music-metadata%2Ficy.svg)](https://npmjs.org/package/@music-metadata/icy)\n[![npm downloads](http://img.shields.io/npm/dm/@music-metadata%2Ficy.svg)](https://npmcharts.com/compare/@music-metadata%2Ficy?start=365)\n\n# @music-metadata/icy\n\nDecode [ICY metadata](https://en.wikipedia.org/wiki/SHOUTcast#Metadata) (used by Icecast and Shoutcast) from audio streams, commonly used in internet radio.\n\nThis module extracts ICY metadata (e.g., `StreamTitle`) from HTTP responses while passing through clean audio chunks for playback or further processing.\n\n\u003e ✅ **Lightweight** • **Fast** • **Web \u0026 Node-compatible** • Built on [`strtok3`](https://github.com/Borewit/strtok3)\n\n---\n\n## 🚀 Installation\n\n```bash\nnpm install @music-metadata/icy\n```\n\nOr with Yarn:\n\n```bash\nyarn add @music-metadata/icy\n```\n\n---\n\n## Demo\n\n* [ICY Radio Stream Player](https://github.com/Borewit/icy-radio-stream-player)\n\n---\n\n## 📦 Usage\n\n```ts\nimport { parseIcyResponse } from '@music-metadata/icy';\n\nconst response = await fetch('https://example.com/radio-stream', {\n  headers: {\n    'Icy-MetaData': '1'\n  }\n});\n\nconst audioStream = parseIcyResponse(response, ({ metadata }) =\u003e {\n  const title = metadata.StreamTitle;\n  if (title) {\n    console.log('Now Playing:', title);\n  }\n});\n\n// You can now pipe `audioStream` to a decoder or audio player.\n```\n\n---\n\n## 🧠 API\n\n### `parseIcyResponse(response, handler): ReadableStream\u003cUint8Array\u003e`\n\nProcess a fetch-compatible HTTP response and extract ICY metadata on the fly.\n\n#### Parameters\n\n* `response: Response`\n  A standard Fetch API [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object with streaming body.\n* `handler: (update: MetadataUpdate) =\u003e void`\n  A callback triggered when new ICY metadata is available.\n\n#### Returns\n\n* `ReadableStream\u003cUint8Array\u003e`\n  A web-compatible readable stream containing **only the audio payload**, excluding metadata.\n\n#### Example\n\n```ts\n{\n  metadata: {\n    StreamTitle: 'Cool Song',\n    StreamUrl: 'https://example.com',\n    ...\n  },\n  stats: {\n    totalBytesRead: 20480,\n    audioBytesRead: 19200,\n    icyBytesRead: 1280\n  }\n}\n```\n\n\n---\n\n### `decodeIcyStreamChunks(stream, metaInt, handler): ReadableStream\u003cUint8Array\u003e`\n\nLower-level function to extract ICY metadata from a `ReadableStream` where the metadata interval is already known.\n\n#### Parameters\n\n* `stream: ReadableStream\u003cUint8Array\u003e` or Node's `ReadableStream`\n* `metaInt: number` – The icy metadata interval in bytes.\n* `handler: (update: MetadataUpdate) =\u003e void` – Metadata callback, same as above.\n\n#### Returns\n\n* `ReadableStream\u003cUint8Array\u003e` – Cleaned stream without metadata blocks.\n\nUse this method if you already know the `icy-metaint` (e.g., from headers or external configuration).\n\n---\n\n## 🧺 ICY Metadata Parsing\n\nICY metadata is parsed from raw string format:\n\n```ts\n\"StreamTitle='song';StreamUrl='url';\"\n```\n\nParsed result:\n\n```ts\n{\n  StreamTitle: 'song',\n  StreamUrl: 'url'\n}\n```\n\nInternally handled by:\n\n```ts\nfunction parseRawIcyMetadata(raw: string): Map\u003cstring, string\u003e\n```\n\n---\n\n## 📜 Types\n\n### `type IcyMetadata`\n\n```ts\ntype IcyMetadata = {\n  StreamTitle?: string;\n  StreamUrl?: string;\n  icyName?: string;\n  icyGenre?: string;\n  icyUrl?: string;\n  bitrate?: string;\n  contentType?: string;\n  [key: string]: string | undefined;\n}\n```\n\n### `type MetadataUpdate`\n\n```ts\ntype MetadataUpdate = {\n  metadata: IcyMetadata;\n  stats: {\n    totalBytesRead: number;\n    audioBytesRead: number;\n    icyBytesRead: number;\n  };\n};\n```\n\n---\n\n## 🧱 Internals\n\nIf `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=\"`.\n\n---\n\n## 🧭 How It Works\n\nThe 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:\n\n```mermaid\ngraph TD\n  %% Node Styles\n  style A fill:#bbf,stroke:#333,stroke-width:2px\n  style B fill:#ddf,stroke:#333,stroke-width:2px\n  style C fill:#afa,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5\n  style D fill:#ffe4b3,stroke:#333,stroke-width:2px\n  style E fill:#fcc,stroke:#333,stroke-width:2px,stroke-dasharray: 3 3\n  style F fill:#fcf,stroke:#333,stroke-width:2px\n  style G fill:#cff,stroke:#333,stroke-width:2px,stroke-dasharray: 2 4\n\n  %% Nodes\n  A[\"🎧 ICY Web Stream\u003cbr/\u003e(Icecast via Fetch)\"]\n  B[\"🔀 Fetch with\u003cbr/\u003eICY-MetaData Header\"]\n  C[\"🧩 @music-metadata/icy\u003cbr/\u003e(ICY Parser)\"]\n  D[\"🔁 Decoded Audio Stream\"]\n  E[\"🎵 HTML5 Audio\u003cbr/\u003e\u0026lt;audio\u0026gt; Element\"]\n  F[\"🛰️ ICY Metadata Events\"]\n  G[\"🖥️ Metadata Display\u003cbr/\u003ein React UI\"]\n\n  %% Flow\n  A --\u003e B\n  B --\u003e|ICY Interleaved Audio| C\n  C --\u003e|Audio Stream| D\n  D --\u003e E\n  C --\u003e|Metadata Events| F\n  F --\u003e|Track Info etc.| G\n```\n---\n\n## 📄 License\n\nMIT — see [LICENSE.txt](LICENSE.txt) for full text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborewit%2Fmusic-metadata-icy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fborewit%2Fmusic-metadata-icy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborewit%2Fmusic-metadata-icy/lists"}