
Shaka Player MoQ 实战指南基于 MSF 解析器的 Media over QUIC 直播流接入【免费下载链接】shaka-playerJavaScript player library / DASH HLS client / MSE-EME player项目地址: https://gitcode.com/GitHub_Trending/sh/shaka-player本篇指南讲解如何在 Shaka Player 中使用内置的MSFMoQ Streaming Format清单解析器通过MoQTMedia over QUIC Transport协议拉取并播放实时直播内容。文章覆盖从环境前置条件、player.load()接入方式到目录Catalog发现机制、四类媒体打包格式、manifest.msf全量配置项、DRM 集成以及本地 relay 联调的完整链路并深入解析器源码与构建目标帮助你独立接入 MoQ 直播源。重要提示Shaka Player 对 MoQ 的支持目前仍处于experimental实验性阶段仅在实验性构建产物中可用。在底层规范下文的三个 IETF draft正式定稿、脱离草稿状态之前该能力将一直保持实验性质。相关规范draft-ietf-moq-transportdraft-ietf-moq-msfdraft-ietf-moq-cmsf前置条件WebTransport 与运行环境MoQ 流式传输完全依赖浏览器的WebTransport API因此接入前必须满足以下三点页面必须通过 HTTPS 或localhost提供服务——WebTransport 在非安全上下文中不可用浏览器必须支持 WebTransport API现代 Chrome/Edge 系浏览器已内置MoQT relay/服务端也必须通过 HTTPS 可达——本地联调时可用自签名证书配合fingerprintUri下文详述绕过浏览器校验。关于第 2 点Shaka 在解析器启动时会对 WebTransport 可用性做显式校验相关错误码定义在 lib/util/error.js其中WEBTRANSPORT_NOT_AVAILABLE4056表示浏览器不支持该 APIWEBTRANSPORT_INITIALIZATION_FAILED4057表示连接建立失败。基本用法mimeType是唯一强制项加载 MoQ 流的 API 与普通 DASH/HLS 完全一致但存在一个强制性要求调用player.load()时必须将application/msf作为mimeType参数传入。这是 Shaka 选择 MSF 清单解析器而非 DASH/HLS 解析器的唯一判定依据。const manifestUri https://relay.example.com/moq-endpoint; async function initPlayer() { shaka.polyfill.installAll(); if (!shaka.Player.isBrowserSupported()) { console.error(Browser not supported!); return; } const video document.getElementById(video); const player new shaka.Player(); await player.attach(video); player.addEventListener(error, (event) { console.error(Error code, event.detail.code, event.detail); }); try { // The mimeType application/msf is REQUIRED for MOQ streams. await player.load(manifestUri, /* startTime */ null, application/msf); console.log(MOQ stream loaded!); } catch (e) { console.error(Load failed, e); } } document.addEventListener(DOMContentLoaded, initPlayer);切勿省略application/msf。缺少该 MIME 类型时Shaka 无法识别这是一个 MoQ 源要么直接加载失败要么将其误判为其他格式进行解析。从源码角度印证MSF 解析器的注册入口位于 lib/msf/msf_parser.js即shaka.media.ManifestParser.registerParserByMimeType(application/msf, () new shaka.msf.MSFParser())。也就是说application/msf与解析器之间是一一映射的关系这正是该参数不能省略的根本原因。仓库自带的演示应用也在 demo/common/assets.js 中为多个 MoQ 演示资产统一设置了application/msf的 mimeType。背后发生了什么从 WebTransport 到媒体管道当以application/msf调用player.load()时Shaka 会依次执行以下流程对应MSFParser.start()的实现见 lib/msf/msf_parser.js建立 WebTransport 连接解析器创建MSFTransport实例并调用connect(uri, fingerprint, authorizationToken)打开到目标 URI 的 WebTransport 通道执行 MoQT 会话建立完成客户端/服务端握手与 draft 版本协商协商细节见下文version配置发现并获取 catalog若配置了manifest.msf.namespaces直接向该命名空间订阅SUBSCRIBE或一次性获取FETCHcatalog 轨道否则注册PUBLISH_NAMESPACE公告监听listenForAnnouncements_()见 lib/msf/msf_parser.js等待服务端广播命名空间后再动态发现解析 catalogcatalog 本质是一份JSON 文档结构定义见 externs/msf_catalog.js解析后枚举出全部音频、视频与文本轨道订阅轨道数据流对每个轨道发起 MoQT 数据订阅把到达的媒体段送入 Shaka 常规媒体管道MediaSource SourceBuffer播放。需要特别注意的是catalog 的获取有10 秒超时限制超过时限仍未拿到 catalog 会触发MSF_CATALOG_TIMEOUT4064错误而 catalog 中无任何可用轨道时则触发MSF_NO_CATALOG4062。两者均在 lib/util/error.js 中定义。仅支持直播Livecatalog 中isLive为false的 VOD 内容不受支持。解析器在processCatalog_()之后显式检查presentationTimeline.isLive()若不满足直接抛出MSF_VOD_CONTENT_NOT_SUPPORTED4058错误见 lib/msf/msf_parser.js。另外值得一提的实现细节MoQ 的媒体按“对象object”粒度以小体积 chunk 到达单个对象往往太小无法为 ABR 带宽估计器提供有效采样因此解析器按Group 聚合后上报一次带宽样本并采用各对象的“有效读取时长”而非墙钟时间——这保证在链路带宽富余时 ABR 能感知到额外容量而非始终只测出当前档位的码率见 lib/msf/msf_parser.js 附近注释。支持的打包格式Packagingcatalog 中的每条轨道通过packaging字段声明其媒体在 MoQT 对象中的封装方式。对于 Shaka 不支持的打包格式该轨道会被跳过catalog 中的其余轨道仍可正常播放。packaging对象中包含的内容规范cmaf一个 CMAF chunkdraft-ietf-moq-cmsfchunk-per-object一个 CMAF chunkdraft-ietf-moq-cmsfloc原始码流的一帧draft-ietf-moq-locm2ts一整段连续的 TS 传输包draft-gregoire-moq-msfts各打包格式的实现位于 lib/msf/packaging/ 目录cmaf.js、loc.js、m2ts.js并通过 打包注册表 以名称注册应用也可以通过PackagingRegistry.registerPackaging(name, factory)注册自定义打包格式。MPEG-2 传输流m2tsm2ts打包下一个 MoQT 对象是一段连续且完整的 TS 传输包。两种源包大小都受支持188 字节的标准 TS 传输包以及192 字节的 M2TS 源包其 4 字节到达时间戳会被丢弃因为该时间戳与呈现时间无关。此项判断逻辑在 lib/msf/packaging/m2ts.js声明了其他包大小如 204 字节的轨道会被跳过。使用m2ts打包有两点必须了解轨道必须声明codeccodec 决定 MediaSource 打开哪些 source buffer而这一动作发生在第一个 group 到达并可被探测之前因此无法从媒体数据中推断。若轨道是复用muxed节目需以逗号分隔列出两个 codec写法与 HLS 的CODECS属性完全一致{ name: program-1-ts, packaging: m2ts, codec: avc1.64001f,mp4a.40.2, m2tsPacketSize: 188, m2tsPcrPid: 257 }对于上面的复用节目Shaka 会从这一条轨道同时打开一个音频和一个视频 source buffer并从这个流的 segments 同时喂给两者。源码中该逻辑位于 lib/msf/packaging/m2ts.jsdescribeTrack()会把video/mp2t; codecsavc1.64001f,mp4a.40.2交给SegmentUtils.getBasicInfoFromMimeType()识别MediaSourceEngine 识别出该组合后为音视频各开一个 buffer。同时未声明 codec 或 codec 无法识别的轨道会给出警告并被跳过。延迟等于一个 GroupTS 传输包自身不携带时序一个 PES 包横跨多个传输包而对象边界完全由发布方决定切在哪里因此单个对象无法独立追加。规范能保证的只是“Group 起始于随机访问点”所以 Group 是最小可追加单元且只有当下一个 Group 开始时才算完整。相比之下chunk-per-object的每个对象到达即可立即追加因此m2ts的延迟天然更高。相关 catalog 字段还包含m2tsPacketsPerObject、m2tsProgramNumber、m2tsPmtPid、m2tsPsiInterval、m2tsRandomAccess、m2tsTimestampMode、m2tsScte35Pid等完整列表见 externs/msf_catalog.js。当轨道声明m2tsRandomAccess: false时Shaka 会警告 Group 起始点可能不是随机访问点开头可能无法解码。建议声明initDatabase64 编码的 PAT/PMT 包Shaka 会把其中内容规整化为 188 字节传输包并前置拼接到每一个 Group从而保证当节目的 PSI 信息没有在每个 Group 开头重复时流仍然可播见 lib/msf/packaging/m2ts.js 与normalizePackets_实现。注意 TS 没有传统意义上的初始化段PAT/PMT 是通过前置到 Group 数据而非单独追加来发挥作用的。如果发布方在两个 Group 之间发出 PCR 不连续信号Shaka 会重新锚定媒体时间使呈现时间持续向前推进并通知 transmuxer 开启一个新的初始化段。注意m2ts依赖 transmuxer而 transmuxer 是独立的构建目标。自定义构建时必须把transmuxer与msf一起包含构建目标定义见 build/types/msf 与 build/types/complete。这与 HLS 中传输流分段TS segments的情形相同——两者共用lib/transmuxer/下的 TS transmuxer 实现。MSF 配置详解所有 MoQ 专属配置均位于manifest.msf之下各配置项的默认值定义在 lib/util/player_configuration.jsplayer.configure({ manifest: { msf: { // Options described below } } });fingerprintUristring默认指向一个纯文本文件的 URL内容为服务端自签名 TLS 证书的 SHA-256 十六进制指纹。当连接本地 relay 或使用浏览器默认会拒绝的自签名证书服务端时需要此项。player.configure({ manifest: { msf: { fingerprintUri: https://relay.example.com/cert.hex, } } });设置后Shaka 会在打开 WebTransport 连接之前先获取该指纹并用它对证书做固定pin。CA 签名证书的服务端留空即可。源码层面的流程MSFParser.start()在connect()前检查fingerprintUri以RequestType.FINGERPRINT请求类型通过 NetworkingEngine 拉取文本去除空白后逐字节解析为十六进制字节数组再作为fingerprint参数传入msfTransport_.connect()见 lib/msf/msf_parser.js。namespacesArraystring默认[]要订阅 catalog 轨道的 MoQT命名空间。命名空间是字符串路径组件的数组共同标识 relay 上的会话。player.configure({ manifest: { msf: { // Subscribe to the catalog in namespace [live, channel1] namespaces: [live, channel1], } } });设置namespaces时Shaka 立即在该命名空间订阅或获取catalog留空[]时Shaka 改为监听服务端的PUBLISH_NAMESPACE公告自动采用公告的命名空间对应listenForAnnouncements_()见 lib/msf/msf_parser.js。若你提前知道命名空间请使用显式形式以减少启动延迟省去等待公告的往返时间。注意命名空间去重逻辑同一个 namespace 只会被处理一次lib/msf/msf_parser.js。authorizationTokenstring默认可选授权令牌在 MoQT 客户端 setup 握手期间发送给服务端。令牌按规范以别名类型USE_VALUE0x03编码。player.configure({ manifest: { msf: { authorizationToken: Bearer my-secret-token, } } });该令牌与fingerprintUri一样会随connect()一并传入MSFTransport用于服务端对客户端的鉴权。subscribeFilterTypeMsfFilterType默认LARGEST_OBJECT控制订阅轨道时应用的过滤器对应 MoQT 订阅的 filter 参数。枚举定义见 lib/config/msf_filter_type.js值说明shaka.config.MsfFilterType.LARGEST_OBJECT从最新可用的对象开始默认shaka.config.MsfFilterType.NEXT_GROUP_START从下一个可用的 Group 开始player.configure({ manifest: { msf: { subscribeFilterType: shaka.config.MsfFilterType.LARGEST_OBJECT, } } });枚举中还定义了NONE0x0、ABSOLUTE_START0x3、ABSOLUTE_RANGE0x4等其他取值。订阅请求组装时该值会被序列化进 subscribe 参数见 lib/msf/request_id_session.js其中LARGEST_OBJECT对应参数类型0x09见 lib/msf/msf_control_stream.js。在直播低延迟场景下LARGEST_OBJECT意味着从服务端当前已发布的最新对象起播可最大程度贴近“直播边缘”。useFetchCatalogboolean默认false为true时Shaka 使用FETCH一次性检索而非持续的SUBSCRIBE获取 catalog。适用于 catalog 静态、在会话生命周期内不更新的场景。player.configure({ manifest: { msf: { useFetchCatalog: true, } } });为false默认时Shaka 订阅 catalog 轨道服务端若推送 catalog 更新则会被拾取。源码中getCatalog_()据此分流到fetchCatalog_()或subscribeToCatalog_()见 lib/msf/msf_parser.js。两种路径都会跳过无负载的对象这类对象仅携带对象状态标记如 draft-16 的组结束标记不包含 catalog 数据。此外catalog 结构支持deltaUpdate、addTracks、removeTracks、cloneTracks等增量更新字段见 externs/msf_catalog.js为动态更新预留了扩展空间。versionMsfVersion默认AUTO控制与 MoQT 服务端协商的 draft 版本。枚举定义见 lib/config/msf_version.js值提供的 WebTransport 协议串说明shaka.config.MsfVersion.AUTOmoqt-18,moqt-16,moq-00提供全部受支持的 draft最新的优先默认shaka.config.MsfVersion.DRAFT_18moqt-18仅强制 draft-18shaka.config.MsfVersion.DRAFT_16moqt-16仅强制 draft-16shaka.config.MsfVersion.DRAFT_14moq-00已弃用。仅强制 draft-14将于 v6 移除player.configure({ manifest: { msf: { version: shaka.config.MsfVersion.DRAFT_18, } } });Draft-14 已弃用并将在 v6 中移除。无论是显式选择还是AUTO模式下服务端恰好选择了moq-00都会打印弃用警告。draft-14 早于 draft-15 引入的基于子协议的版本协商机制它在带内协商在CLIENT_SETUP中提供版本列表。请在 v6 之前迁移到 draft-16 或 draft-18README 的能力清单同样标注了 draft-14 已弃用。draft-16 与 draft-18 是两种不同的线上协议而非同一协议的版本修订draft-17 替换了变长整数编码、把控制面从单条双向流改为一对单向流、为每个请求分配独立双向流并重新分配了若干消息类型 ID。因此 Shaka 为每个 draft 维护独立实现封装为 dialect方言在协商阶段选定一次后启用。相关实现见 lib/msf/drafts/ 目录下的draft14/、draft16/、draft18/其类型定义见 externs/shaka/msf_dialect.js并有对应的单元测试 test/msf/dialect_registry_unit.js 与 test/msf/draft18_session_unit.js。版本通过WebTransport 子协议协商。注意Shaka 并不要求服务端回显该子协议——部分 relay 会接受客户端提供的子协议但WebTransport.protocol属性保持为空若把这种情况视为失败会破坏本可正常工作的连接。catalogPreprocessorfunction默认恒等函数可选回调在 catalog JSON 解析完成后、Shaka 处理其轨道之前被调用用于以编程方式修改或过滤 catalog 条目。player.configure({ manifest: { msf: { catalogPreprocessor: (catalog) { // Example: remove loc tracks from the catalog catalog.tracks catalog.tracks.filter( (t) t.packaging ! loc); return catalog; }, } } });该函数接收且必须返回一个msfCatalog.Catalog对象类型定义见 externs/msf_catalog.js。默认值为恒等函数(catalog) catalog。典型用途包括按业务规则剔除某些包装格式/码率档位的轨道、批量改写轨道元数据等。完整配置示例player.configure({ manifest: { msf: { fingerprintUri: , // Set for self-signed cert servers namespaces: [live, ch1], // Known namespace; leave [] to auto-discover authorizationToken: , // Bearer token if required by server useFetchCatalog: false, // true one-shot FETCH, false SUBSCRIBE version: shaka.config.MsfVersion.AUTO, // Version negotiation strategy subscribeFilterType: shaka.config.MsfFilterType.LARGEST_OBJECT, catalogPreprocessor: (catalog) catalog, // Identity (no-op) } } });结合 DRM 的 MoQ 接入MoQ 流的 DRM 配置方式与 DASH/HLS完全一致。DRM 信息通过 catalog 中的contentProtections条目携带包含 key system UUID、PSSH 与 license server URLShaka 会自动提取并填充其 DRM 子系统。catalog 中contentProtections的 JSON 结构包含refID、defaultKID、scheme与drmSystem后者含systemID、laURL、certURL、authzURL、pssh、robustness字段定义见 externs/msf_catalog.js轨道通过contentProtectionRefIDs引用这些条目externs/msf_catalog.js解析器侧的处理在 lib/msf/msf_parser.js含DrmUtils.getUuidMap()做 key system 识别、PlayReady 特判、defaultKID 提取。只有当 catalog 未包含 license server URL或需要硬件健壮性、自定义 header 等高级选项时才需要补充 DRM 配置player.configure({ drm: { servers: { com.widevine.alpha: https://license.example.com/widevine, com.microsoft.playready: https://license.example.com/playready, }, advanced: { com.widevine.alpha: { videoRobustness: [HW_SECURE_ALL], audioRobustness: [SW_SECURE_CRYPTO], } } }, manifest: { msf: { namespaces: [live, encrypted-channel], authorizationToken: my-token, } } }); await player.load(uri, null, application/msf);使用本地 Relay 联调测试当本地 MoQT relay 使用自签名 TLS 证书时借助fingerprintUri即可完成联调生成自签名证书并将其 SHA-256 指纹导出为十六进制字符串不要冒号、不要空格写入纯文本文件如cert.hex通过 HTTPS 端点向浏览器提供该文件配置 Shakaplayer.configure({ manifest: { msf: { fingerprintUri: https://localhost:4443/cert.hex, namespaces: [test], } } }); await player.load(https://localhost:4433/moq, null, application/msf);构建说明与实验性限制小结接入 MoQ 前请确认以下几点与当前仓库能力一致构建目标MSF 解析器位于独立构建目标msfbuild/types/msf使用m2ts打包还需transmuxerbuild/types/complete 已默认包含自定义构建务必同时引入。完整构建目标索引见 build/types/。实验性状态MoQ 支持目前仅在实验性构建中可用且将随相关 draft 的演进而变化生产环境接入前请锁定 Shaka 版本并关注 CHANGELOG.md 与 roadmap.md 中的更新。仅直播VODcatalogisLive: false会直接报错MSF_VOD_CONTENT_NOT_SUPPORTED。安全上下文页面与 relay 都必须运行于 HTTPS 或localhost。完成上述配置后player.load(uri, null, application/msf)即会把一条 MoQT 直播流无缝接入 Shaka 常规的流媒体管道复用其 ABR、DRM、字幕等既有能力。【免费下载链接】shaka-playerJavaScript player library / DASH HLS client / MSE-EME player项目地址: https://gitcode.com/GitHub_Trending/sh/shaka-player创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考