【问题标题】:Streaming Icecast Audio & Metadata with Javascript and the Web Audio API使用 Javascript 和 Web 音频 API 流式传输 Icecast 音频和元数据
【发布时间】:2021-04-05 18:43:04
【问题描述】:

我一直在尝试找出实施我已有一段时间的想法的最佳方法。

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

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

有一个非常有趣的 Node.JS 解决方案,它是根据这个确切的目标开发的 - 无线电扫描仪应用程序中的实时元数据:icecast-metadata-js。这表明确实可以同时处理来自单个 icecast 流的音频和元数据。现场演示尤其令人印象深刻:https://eshaz.github.io/icecast-metadata-js/

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

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

我想知道是否有人有任何关于通过 Web Audio API 播放 mp3 流的好读物和/或示例。在大多数 JS 方面,我仍然是一个相对新手,但我了解了 API 的基本概念以及它如何处理音频数据。我正在努力实现的正确方法是:a) 实时处理来自 mp3 流的数据,b) 检测流中嵌入的元数据块并相应地处理这些块。

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

提前感谢您的建议和帮助!

【问题讨论】:

    标签: javascript metadata web-audio-api icecast


    【解决方案1】:

    很高兴您找到我的图书馆icecast-metadata-js!这个库实际上可以在客户端和 NodeJS 中使用。完全在客户端运行的现场演示的所有源代码都在存储库中: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';\0\0\0\0\0\0"

    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 标头,它告诉 Icecast 提供交错的元数据。响应标头将包含 ICY 元数据间隔 Icy-MetaInt,应捕获(如果可能)并用于确定元数据间隔。

    在演示中,我使用客户端 fetch API 来发出 GET 请求,并将响应数据提供到 IcecastReadableStream 的实例中,该实例将流和元数据分开,并通过回调使每个可用.我正在使用 Media Source API 播放流数据,并获取时间数据以正确同步元数据更新。

    这是读取 ICY 元数据所需的最低限度 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 配置从标头中读取它:

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

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

    【讨论】:

    • 感谢您的回复!我一定会再看你的实现。我被 Node.JS 的提及吓到了,但我正在查看您的代码并开始了解它是如何工作的。我特别喜欢你处理音频的方法,完全不需要 HTML5 音频元素。虽然不是绝对必要的,但在纯 JS 中为基本流做一个“最低限度”的演示会非常方便。与此同时,我会尽力为自己弄清楚。
    • @Patrick “最低限度”的概念是个好主意,而且我一直在考虑这样做。我为此放置了一个 github issue 来创建一个只需要端点的模块,它可以完成其他所有工作。该模块应该是一个可以包含在<script> 标签中的单个文件,并且会有一个简单的 HTML 示例。
    • 看起来棒极了!您的无线电扫描套件给我留下了深刻的印象,我的最终目标是类似的,但要简单得多。具有讽刺意味的是,作为流媒体设置的一部分,我还在扫描 SARA 网络。会很期待,同时我会慢慢弄清楚如何让你的图书馆工作!
    • 快速更正:在对我的 VLC 进行wireshark 以查看获取元数据间隔的正确请求标头后,就是这个:Icy-MetaData: 1
    猜你喜欢
    • 2017-05-26
    • 2012-11-06
    • 2015-11-09
    • 2011-12-21
    • 2014-11-15
    • 1970-01-01
    • 2020-09-14
    • 2013-01-11
    • 1970-01-01
    相关资源
    最近更新 更多