使用 Javascript 和网络音频 API 流式传输 Icecast 音频和元数据

Streaming Icecast Audio & Metadata with Javascript and the Web Audio API

我一直在努力找出实现我已经有一段时间的想法的最佳方法。

目前,我有一个无线电扫描仪的 icecast mp3 流,“正在播放”元数据根据扫描仪登陆的频道实时更新。当使用 VLC 等专用媒体播放器时,元数据与接收到的音频完美对齐,并且它的功能完全符合我的要求——本质上是一个远程无线电扫描仪。我想通过网页实现类似的东西,从表面上看这似乎是一个简单的任务。

如果我只想流式传输音频,使用简单的 <audio> 标签就足够了。但是,HTML5 音频播放器没有 icecast 与 mp3 音频数据一起编码的嵌入式流内元数据的概念。虽然我可以从 icecast 服务器状态 json 查询当前“正在播放”的元数据,但由于客户端和服务器端缓冲,以这种方式完成时,音频和元数据之间可能会有超过 20 秒的延迟。在某些情况下,当扫描仪每秒向上更改其“正在播放”元数据时,这完全不适合我的应用程序。

有一个非常有趣的 Node.JS 解决方案是在考虑到这个确切目标的情况下开发的 - 无线电扫描仪应用程序中的实时元数据:icecast-metadata-js. This shows that it is indeed possible to handle both audio and metadata from a single icecast stream. The live demo is particularly impressive: https://eshaz.github.io/icecast-metadata-js/

但是,我正在寻找一种解决方案,它可以 运行 完全在客户端进行,而无需 Node.JS 安装,而且看起来应该相对简单。

经过今天大部分时间的搜索,似乎在本网站和其他地方提出了几个类似的问题,但没有任何连贯的、布局合理的答案或建议。从我到目前为止收集到的信息来看,我相信我的解决方案是使用 Javascript 流媒体功能(例如 fetch)从 icecast 服务器中提取原始 mp3 和元数据,播放通过 Web Audio API 处理音频,并在元数据块到达时对其进行处理。类似于下图:

我想知道是否有人可以阅读 and/or 通过网络音频 API 播放 mp3 流的示例。在大多数 JS 方面,我仍然是一个相对新手,但我了解了 API 的基本概念以及它如何处理音频数据。我正在努力解决的是实现 a) 来自 mp3 流的数据的实时处理,以及 b) 检测嵌入在流中的元数据块并相应地处理它们的正确方法。

抱歉,如果这是一个冗长的问题,但我想提供足够的背景故事来解释为什么我想以我的特定方式处理事情。

在此先感谢您的建议和帮助!

很高兴您找到了我的图书馆 icecast-metadata-js! This library can actually be used both client-side and in NodeJS. All of the source code for the live demo, which runs completely client side, is here in the repository: https://github.com/eshaz/icecast-metadata-js/tree/master/src/demo。演示中的流没有改变,只是服务器端的普通 Icecast 流。

你的图表基本上是正确的。 ICY 元数据交错在实际的 MP3“流”数据中。 ICY 元数据更新发生的元数据间隔或频率可以在 Icecast 服务器配置中配置 XML。此外,这可能取决于您的来源向 Icecast 发送元数据更新的频率/准确性。我的演示页面上的警察扫描仪中使用的软件几乎与音频同步更新。

通常,默认的元数据间隔为 16,000 字节,这意味着对于每 16,000 个流 (mp3) 字节,Icecast 将发送一个元数据更新。元数据更新总是包含一个长度字节。如果长度字节大于0,则元数据更新的长度为元数据长度字节*16。

ICY 元数据是一串由分号分隔的 key='value' 对。元数据更新中任何未使用的长度都被空填充。

"StreamTitle='The Stream Title';StreamUrl='https://example.com';[=13=][=13=][=13=][=13=][=13=][=13=]"

read [metadataInterval bytes]        -> Stream data
read [1 byte]                        -> Metadata Length
if [Metadata Length > 0]
   read [Metadata Length * 16 bytes] -> Metadata

byte length response data action
ICY Metadata Interval stream data send to your audio decoder
1 metadata length byte use to determine length of metadata string (do not send to audio decoder)
Metadata Length * 16 metadata string decode and update your "Now Playing" (do not send to audio decoder)

对您的 Icecast 服务器的初始 GET 请求将需要包含 Icy-MetaData: 1 header,它告诉 Icecast 提供隔行扫描的元数据。响应 header 将包含 ICY 元数据间隔 Icy-MetaInt,应捕获(如果可能)并用于确定元数据间隔。

在演示中,我使用 client-side fetch API 发出 GET 请求,并将响应数据提供给 IcecastReadableStream 的实例,该实例拆分出流和元数据,并通过回调使每个可用。我正在使用媒体源 API 播放流数据,并获取计时数据以正确同步元数据更新。

这是读取 ICY 元数据所需的 bare-minimum CORS 配置:

Access-Control-Allow-Origin: '*' // this can be scoped further down to your domain also
Access-Control-Allow-Methods: 'GET, OPTIONS'
Access-Control-Allow-Headers: 'Content-Type, Icy-Metadata'

icecast-metadata-js 可以根据需要检测 ICY 元数据间隔,但最好允许客户端使用此额外的 CORS 配置从 header 读取它:

Access-Control-Expose-Headers: 'Icy-MetaInt'

另外,我计划发布一个新功能(在我完成 Ogg 元数据之后),它封装了 fetch api 逻辑,这样用户需要做的就是提供一个 Icecast端点,并取回音频/元数据。