- 音视频
- 前端
【免费下载链接】dash.js
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
dash.js 是 DASH Industry Forum 提供的 MPEG-DASH 参考播放器实现,其功能正确性由一套运行在浏览器环境中的功能测试(functional tests)保障。本文以仓库内 test/functional/index.md 为核心,系统讲解这套基于 Karma Testrunner 的functional-karma测试套件的目录结构、配置机制、测试用例组织方式、完整运行步骤,并深入剖析 Windows 环境下 Edge 浏览器运行 Widevine DRM 测试时"必须使用持久化浏览器 profile"这一关键工程问题。读完本文,你将能够理解 dash.js 功能测试的完整脉络,并掌握在本地与云端多浏览器环境中配置、执行与结果分析的具体方法。
一、功能测试套件概览:测试什么、用什么测
功能测试(functional tests)与单元测试(unit tests)定位不同:单元测试聚焦于某个类或函数的内部逻辑,而功能测试面向真实浏览器环境,验证用户可感知的播放器行为,例如 play(播放)、pause(暂停)、seek(跳转)、ended(结束)、缓冲管理、轨切换、DRM 授权等端到端流程。
functional-karma测试套件使用Karma Testrunner作为测试执行引擎,配合 Mocha + Chai 编写断言、webpack 完成测试代码打包,并可在真实浏览器(Chrome、Firefox、Safari、Edge)或云端浏览器服务(BrowserStack、LambdaTest)中运行。测试的输入是真实的 MPD 流媒体清单(既有互联网上公开的测试向量,也有仓库内置的静态 MPD),输出是 JUnit XML、HTML 报告与覆盖率数据。
整套体系由 test/functional/config/karma.functional.conf.cjs 这一 Karma 配置文件驱动,其核心机制将在后文展开。
二、目录结构:七个角色的职责划分
原文档定义了功能测试套件的目录职责,结合当前仓库的实际布局,各目录的真实作用如下:
| 目录 | 原文档定义 | 仓库中的实际内容 |
|---|---|---|
adapter | 封装测试所需附加逻辑的适配器类,如DashJsAdapter.js作为 dash.js 功能的包装层 | test/functional/adapter/DashJsAdapter.js 封装了 MediaPlayer 的初始化、attachSource、seek、play/pause、事件监听与缓冲查询等 API;另有 GoogleAdManagerAdapter.js 用于广告场景 |
config | 定义待执行测试用例集合的测试配置文件,目标配置文件在Utils.js中导入 | test/functional/config/test-configurations/ 存放浏览器/环境配置(local.json、local-windows.json、browserstack.json、lambdatest.json等),streams/ 存放测试向量流清单(smoke.json、drm.json、text.json、all.json等) |
content | 作为测试输入的内置静态 MPD,由 Karma 自动以带 CORS 头的方式伺服 | test/functional/content/ 下有gap/、multi-period/、preload/三类 MPD,通过/base/test/functional/content/路径访问 |
helper | 定义常量值并为特定用例过滤相关测试向量 | 对应 test/functional/src/Constants.js(测试用例清单与超时阈值常量)与 test/functional/src/Utils.js(getTestvectorsForTestcase过滤逻辑) |
results | 测试结果汇总输出目录 | 报告由配置写入test/functional/results/(JUnit XML、HTML 报告、覆盖率) |
test | 测试用例的实现 | test/functional/test/ 按功能类别分子目录:advanced/、audio/、buffer/、drm/、feature-support/、live/、playback/、playback-advanced/、text/、video/等,另含common/common.js公共断言辅助 |
view | 定制化的测试执行视图,包含 video 元素 | test/functional/view/index.html 提供<video id="video-element">、TTML 渲染 div 与广告 UI div,作为 Karma 的 customContextFile 被加载 |
需要特别说明的是,helper目录在原文档中的描述为"定义常量值和过滤测试向量",当前仓库将其实现为src/下的Constants.js与Utils.js;results目录在运行测试后由报告插件自动生成。从源码结构看,这套命名与实现存在一定演进差异,阅读时以实际文件为准。
2.1 测试执行上下文:view/index.html
test/functional/view/index.html 是整个测试的"舞台":它创建一个 640×480 的 muted<video>元素、一个用于 TTML 字幕渲染的#ttml-rendering-div和一个广告 UI 容器。每个测试用例都会通过DashJsAdapter拿到这个视频元素并执行真实播放,这也是功能测试区别于单元测试的关键——它不 mock video 元素,而是驱动真实的媒体管道。
三、配置机制:两层配置如何驱动一次测试运行
原文档指出:"主配置定义在karma.functional.conf.js,调整 testvectors 或 testcases 可修改config/content.js,或者换用src/Utils.js中引入的不同配置文件。" 当前仓库的实现方式如下:
- Karma 主配置:test/functional/config/karma.functional.conf.cjs;
- 测试向量(testvectors)清单:按场景拆分为多个 JSON 文件,存放在 test/functional/config/test-configurations/streams/;
- 浏览器与环境配置:存放在 test/functional/config/test-configurations/。
3.1 karma.functional.conf.cjs 的工作方式
配置文件并非写死一套参数,而是通过命令行参数--configfile与--streamsfile动态加载 JSON,实现"环境配置 × 流配置"的组合:
const argv = yargs(hideBin(process.argv)).parse() const configFileName = argv.configfile const streamsFileName = argv.streamsfile // 加载环境配置(浏览器、端口、自定义 launcher、报告器) let testConfiguration = JSON.parse(fs.readFileSync(`test/functional/config/test-configurations/${configFileName}.json`, 'utf-8')); // 加载流配置(测试文件包含/排除列表、测试向量) const streamsConfiguration = JSON.parse(fs.readFileSync(`test/functional/config/test-configurations/streams/${streamsFileName}.json`, 'utf-8'));核心行为包括:
- 测试文件筛选:根据 streams 配置中的
testfiles.included/testfiles.excluded决定加载哪些用例文件(_getIncludedTestfiles/_getExcludedTestfiles)。included含"all"时加载全部test/functional/test/**/*.js,否则按条目拼出对应文件路径。 - 静态内容伺服:将 test/functional/content/ 下的
*.mpd以served: true, included: false方式交给 Karma 静态服务器,并针对test/functional/content/.*\.mpd$追加Access-Control-Allow-Origin: *等 CORS 头,保证浏览器能跨源读取这些 MPD 及其引用的分片。 - 打包与覆盖率:测试用例通过
webpack预处理,dist/dash.all.debug.js与dist/dash.mss.min.js走coverage预处理以收集覆盖率。 - 运行参数:
singleRun: true(CI 模式,跑完即退出)、browserNoActivityTimeout/browserDisconnectTimeout均为 120000ms、默认并发度concurrency为 2(可由环境配置覆盖)、Mocha 超时 100000ms、默认不使用 iframe(useIframe: false)。 - 云端适配:当环境配置
type为lambdatest时,通过_adjustConfigurationForLambdatest注入 LambdaTest 的用户名、访问密钥与 hub 地址;BrowserStack 的凭据则取自环境变量BROWSERSTACK_USER/BROWSERSTACK_ACCESS_KEY。 - 报告输出:JUnit XML 写入
test/functional/results/test/karma/junit/,HTML 报告写入test/functional/results/test/karma/htmlreporter/,覆盖率写入test/functional/results/coverage/karma/。
3.2 环境配置文件(test-configurations/*.json)
以本地通用配置 local.json 为例,其结构展示了自定义浏览器 launcher 的标准写法:
{ "browsers": ["firefox_custom", "chrome_custom", "safari_custom"], "hostname": "localhost", "port": 4200, "protocol": "http", "concurrency": 3, "customLaunchers": { "chrome_custom": { "base": "Chrome", "flags": [ "--disable-web-security", "--autoplay-policy=no-user-gesture-required", "--disable-popup-blocking", "--disable-search-engine-choice-screen" ] }, "firefox_custom": { "base": "Firefox", "prefs": {} }, "safari_custom": { "base": "SafariNative", "flags": ["--disable-popup-blocking"] } }, "reporters": ["junit", "mocha", "html", "coverage"] }关键点:
--autoplay-policy=no-user-gesture-required允许无用户手势自动播放,这是功能测试能够自动起播的前提;--disable-web-security放宽同源限制,配合 CORS 头保证跨域 MPD 可加载;- 报告器组合
junit(机器可读 XML)、mocha(终端进度)、html(可视化报告)、coverage(覆盖率)。
其他环境配置的差异点:local-ubuntu.json为 Firefox 设置了media.eme.enabled: true前缀并指定固定profile;local-singlebrowser.json只启动 Chrome 且concurrency: 1;browserstack.json通过base: "BrowserStack"定义云端 launcher(如bs_chrome_win_11、bs_safari_mac),凭据来自环境变量;tizen.json/webos.json则通过 launchers/ 下的自定义 Karma launcher 支持三星 Tizen 与 LG webOS 电视平台。
3.3 测试向量配置文件(streams/*.json)
每个 streams 文件由testfiles(决定跑哪些用例文件)与testvectors(决定喂给用例哪些流)两部分组成。以 smoke.json 为例,其 testvectors 覆盖:Smooth Streaming(MSS)VOD、Akamai 多周期 MPD、Axinom 多 DRM(Widevine + PlayReady)单密钥/多密钥向量、Shaka Angel-One Widevine、LiveSim2 的 EZDRM cbcs 直播、ClearKey 向量、SCTE-35 emsg 事件、Annex I(LL-DASH chunk 编码)、MPD Patching、动态转静态(dynamic-to-static)等。
单个测试向量的典型字段:
{ "name": "Single-period, 1080p, H.264, 5 video, 3 audio, 3 text tracks, CMAF, cbcs encryption, single key, Widevine+PlayReady", "type": "vod", "url": "https://media.axprod.net/TestVectors/Dash/protected_dash_1080p_h264_singlekey/manifest.mpd", "includedTestfiles": ["playback/*", "audio/*", "video/*", "text/*"], "excludedPlatforms": [{ "browser": "safari" }], "drm": { "com.widevine.alpha": { "serverURL": "https://drm-widevine-licensing.axprod.net/AcquireLicense" }, "com.microsoft.playready": { "serverURL": "https://drm-playready-licensing.axprod.net/AcquireLicense" } } }各字段含义:
| 字段 | 作用 |
|---|---|
name | 测试向量的可读名称,出现在用例 describe 标题中 |
type | 内容类型:vod或live,供用例区分动态/静态流行为(见 Constants.js 的CONTENT_TYPES) |
url | MPD 地址;以/base/test/functional/content/开头的为仓库内置静态 MPD |
includedTestfiles/excludedTestfiles | 该向量参与哪些用例(支持playback/*通配类别),与testfiles.included的全局筛选配合 |
excludedPlatforms | 在哪些浏览器上跳过该向量(如 Safari 不支持部分 DRM 场景) |
drm | 按 Key System 配置的授权信息:serverURL(license 服务器)、httpRequestHeaders(如 Axinom 的X-AxDRM-MessageJWT)、httpTimeout、priority等 |
platformOverrides | 按浏览器覆盖drm或settings,实现同一向量在不同平台的差异化配置 |
settings | 直接注入 dash.js 的updateSettings配置(如streaming.retryAttempts) |
testdata | 传给用例的附加参数(如 emsg 事件期望次数、周期切换等待时间、MPD patching 标志等) |
3.4 测试向量过滤的源码实现
includedTestfiles/excludedPlatforms等筛选逻辑由 test/functional/src/Utils.js 的getTestvectorsForTestcase(testcase)执行,每个用例文件都会在顶层用Utils.getTestvectorsForTestcase(TESTCASE).forEach(...)为自己的用例名拉取匹配的向量,规则包括:
- 向量声明了
excludedPlatforms且当前浏览器命中(通过CoreUtils.parseUserAgent解析 UA 判断)则跳过; - 向量同时没有 include/exclude 约束时,对所有用例生效;
- 向量
includedTestfiles精确包含用例名,或包含该用例所属类别通配(如playback/*)时生效; - 向量
includedTestfiles含"all"且用例不在其excludedTestfiles中时生效。
此外,_applyPlatformOverrides会在命中platformOverrides.browser时对向量的drm与settings做深合并克隆,从而在不污染共享配置的前提下实现 Edge 等平台的特化(例如 smoke.json 中为 Edge 强制com.microsoft.playready.recommendation系统优先级与audioRobustness: "2000")。
四、测试用例的组织方式:从常量到断言
4.1 用例清单与参数常量
test/functional/src/Constants.js 以TESTCASES.CATEGORIES定义十个用例类别(advanced/、audio/、buffer/、drm/、feature-support/、live/、playback/、playback-advanced/、text/、video/),并为每个具体用例生成如playback/play、buffer/buffer-cleanup、feature-support/cmcd-v2这样的完整键名。文件还集中定义了:
TEST_TIMEOUT_THRESHOLDS:各类断言等待阈值,例如IS_PLAYING: 5000、IS_PROGRESSING: 15000、BUFFER_CLEANUP: 45000、EVENT_WAITING_TIME: 10000;TEST_INPUTS:各类用例的行为参数,例如随机 seek 次数NUMBER_OF_RANDOM_SEEKS: 3、允许的 seek 误差MAXIMUM_ALLOWED_SEEK_DIFFERENCE: 0.5、直播延迟目标LIVE_DELAY.VALUE: 10、初始缓冲目标INITIAL_BUFFER_TARGET.VALUE: 10;DRM_SYSTEMS(Widevine / PlayReady 的 Key System 字符串)、SEGMENT_TYPES(InitializationSegment/MediaSegment)、ROLES(forced-subtitle 角色)等。
4.2 用例文件的结构范式
以 test/functional/test/playback/play.js 为模板,可看出每个用例文件遵循统一范式:引入Constants与Utils→ 用Utils.getTestvectorsForTestcase遍历匹配的向量 → 对每个向量生成一个describe(标题含用例名 + 向量名 + MPD URL)→before中通过 common.js 的initializeDashJsAdapter初始化适配器并 attachSource → 逐个it断言。play用例依次断言:进入播放状态(checkIsPlaying)、播放进度推进(checkIsProgressing)、无关键错误(checkNoCriticalErrors)。
test/functional/test/playback/seek.js 则展示了更复杂的行为验证:seek 到 0、seek 到负值(应钳制到 0)、seek 到超大值(应先注册PLAYBACK_SEEKED事件监听再 seek,断言"seek 必须真正完成而非永久挂起",随后按动态流/静态流分别验证落点:直播应贴近 live edge,VOD 应停在duration - seekDurationBackoff)、以及 3 次随机位置 seek,每次 seek 后都要重新验证播放与推进状态。
4.3 公共断言辅助与适配器
test/functional/test/common/common.js 提供checkIsPlaying、checkIsProgressing、checkIsNotProgressing、checkNoCriticalErrors、checkEventHasBeenTriggered、checkForEndedEvent、reachedTargetForwardBuffer、checkLiveDelay、checkTimeWithinThresholdForDvrWindow等可复用断言。其中checkNoCriticalErrors会将收集到的 error 事件与 error 级别日志序列化为诊断 JSON(对 URL 中的敏感查询串做脱敏),在断言失败时输出,便于定位问题。
DashJsAdapter.js 是测试与播放器的桥梁,它直接import {MediaPlayer, Debug} from '../../../dist/modern/esm/dash.all.min.js',因此功能测试依赖npm run build产出的 dist 构建产物。适配器内部:
init()中设置debug.logLevel: 3且dispatchEvent: true,使播放器把日志通过事件分发出来,供测试收集;- 注册
FRAGMENT_LOADING_STARTED、LOG、ERROR三个内部事件,分别记录已开始下载的分片 URL、各级别日志与错误事件; - 提供
isInPlayingState(监听PLAYBACK_PLAYING事件,超时则判定失败)、isProgressing(通过PLAYBACK_TIME_UPDATED事件判断在超时窗口内播放时间是否推进超过最小量)、waitForEvent、reachedTargetDelay(每 100ms 轮询getCurrentLiveLatency是否进入容差)等异步等待原语; - 包装
attachSource、seek、seekToPresentationTime、setProtectionData、getTracksFor、setCurrentTrack、getDvrWindow、getCurrentLiveLatency等播放器 API。
五、测试执行:从安装到结果分析
原文档给出的执行步骤为:npm install→npm run build→npm run test-functional-mocha→ 在test/functional-karma/results查看结果。对照当前仓库的实际脚本(见 package.json),需要注意以下几点以仓库现状为准:
- 安装依赖:
npm install(Node 版本需满足engines.node >= 20)。 - 构建 dist:
npm run build,产出功能测试所依赖的dist/modern/esm/dash.all.min.js与dist/dash.mss.min.js。 - 执行功能测试:当前仓库的脚本名为
test-functional,而非文档中的test-functional-mocha:
npm run test-functional # 等价于: karma start test/functional/config/karma.functional.conf.cjs --configfile=local --streamsfile=smoke即默认使用local环境配置与smoke流配置。若需换用其他组合,直接以命令行参数覆盖:
karma start test/functional/config/karma.functional.conf.cjs --configfile=local-ubuntu --streamsfile=drm karma start test/functional/config/karma.functional.conf.cjs --configfile=browserstack --streamsfile=smoke原文档提到"未来应允许直接通过命令行参数指定测试文件",目前该能力已部分落地:--configfile与--streamsfile正是通过命令行选配,而streams/*.json的testfiles.included/excluded已可精确控制测试文件集合。
- 查看结果:结果目录为 test/functional/results/,而非文档中的
test/functional-karma/results。运行后生成:test/functional/results/test/karma/junit/<browser>.xml(JUnit 格式,供 CI 解析);test/functional/results/test/karma/htmlreporter/<timestamp>.html(HTML 可视化报告);test/functional/results/coverage/karma/(覆盖率报告)。
另外仓库还提供了结果仪表盘:npm run test-functional-dashboard会启动 test/functional/results-dashboard/server.mjs 用于聚合展示历史结果。
5.1 一个用例的执行链路示例
以playback/play用例在本地跑 smoke 配置为例,其执行链路为:Karma 启动 Chrome → 加载view/index.html上下文 → webpack 打包用例文件 → 用例顶层调用Utils.getTestvectorsForTestcase('playback/play')筛选出 smoke.json 中includedTestfiles命中playback/play的向量 → 对每个向量创建DashJsAdapter、init(true)自动播放、setDrmData注入授权信息、attachSource(mpd)→checkIsPlaying等待PLAYBACK_PLAYING→checkIsProgressing观察PLAYBACK_TIME_UPDATED推进 →checkNoCriticalErrors校验日志与错误事件为空。任一步超时或断言失败,报告会携带脱敏后的请求/响应诊断信息输出。
六、Windows 特例:Edge 跑 Widevine DRM 测试必须使用持久化 profile
这是原文档着墨最多、也最具工程价值的专题。默认情况下 Karma 为每个浏览器启动一个临时(throwaway)profile,这在 Windows 的 Microsoft Edge 上会直接破坏 Widevine DRM 测试,原因链如下:
- Edge 的 Widevine CDM 是按 profile 下载的组件(存放在
<user data dir>\WidevineCdm\<version>\); - 全新临时 profile 会回退到 Edge 安装包自带的过期 CDM,而该 CDM 的客户端证书已被 Google 吊销;
- 强制吊销校验的 license 服务器会拒绝一切授权请求——例如
cwip-shaka-proxy.appspot.com返回 500ACCESS_DENIED,Axinom 返回 400 并附x-axdrm-errormessage: ... DRM client models with revoked certificates are not allowed to receive licenses。
此外 Edge 通过Media Foundation渲染受保护内容,并通过显示驱动(OPM/HDCP)执行 license 的输出保护策略。因此Edge 上的 DRM 测试必须在解锁的控制台会话且显示器开启的状态下运行:若工作站被锁定、显示器关闭或处于 RDP 会话,任何 license 要求输出保护的向量都会以MEDIA_ERR_DECODE ... MediaFoundationRenderer error: kOnPlaybackError (0xC0262500 / "The driver does not support OPM")失败。在 smoke 套件中,受影响的包括 Axinom 的v7-MultiDRM-*系列与 LiveSim2 EZDRM 向量;不受影响的是 Angel-One 和 Axinom cbcs 单密钥向量——它们的 license 允许未受保护输出。这是环境条件而非播放器或测试缺陷。
6.1 解决方案:edgeDataDir 持久化 profile
为此,local-windows.json 中的edge_customlauncher 通过edgeDataDir指定了一个持久化 profile 目录:
"edge_custom": { "base": "Edge", "edgeDataDir": "C:\\Users\\local_root\\.dashjs-edge-test-profile", "flags": [ "--disable-web-security", "--autoplay-policy=no-user-gesture-required", "--disable-popup-blocking", "--disable-search-engine-choice-screen" ] }新机器的一次性初始化步骤:
- 把当前版本的组件 CDM 拷贝进该目录:复制
%LOCALAPPDATA%\Microsoft\Edge\User Data\WidevineCdm\<version>到<edgeDataDir>\WidevineCdm\<version>; - 或者先用
--user-data-dir=<edgeDataDir>手动启动一次 Edge,并在edge://components页面更新 "Widevine Content Decryption Module"; - 之后由于 profile 持久存在,Edge 会自动持续更新 CDM,无需再次人工干预。
Chrome 与 Firefox 不受此问题影响:其当前 CDM 随浏览器安装分发,或在需要时按需拉取进临时 profile。
七、常见问题与排障要点
- 功能测试报错找不到 dist 产物:
DashJsAdapter直接 importdist/modern/esm/dash.all.min.js,务必先执行npm run build。 - DRM 向量在 Edge 上大面积失败:优先排查是否为持久化 profile 未初始化(见第六节),其次确认是否处于锁定/RDP 会话。
- 同一向量在不同浏览器行为不一致:检查
platformOverrides与excludedPlatforms是否按浏览器做了正确分流;从源码结构看,这是测试框架为跨浏览器差异提供的标准手段。 - 只想跑某个用例:修改 streams 配置的
testfiles.included为具体条目(如["playback/play"]),或在向量级用includedTestfiles收窄;Karma 也支持通过--grep(mocha 的 grep 能力,参考npm run test-refplayer的用法)按用例名过滤。 - 本地网络受限跑不了外网向量:可将向量
url换成/base/test/functional/content/下的内置 MPD(如gap/、multi-period/、preload/目录中的文件),这些 MPD 由 Karma 静态伺服并带 CORS 头。
八、小结
dash.js 的功能测试套件是一套以"真实浏览器 + 真实媒体流 + 播放器公开 API"为核心的三层体系:配置层(karma.functional.conf.cjs+ 环境配置 + 流配置)决定"在哪跑、跑什么";用例层(Constants.js定义清单与阈值、Utils.js负责向量筛选、各test/目录下的用例文件与common.js负责断言)决定"怎么验证";适配层(DashJsAdapter桥接 dist 构建产物与播放器 API、view/index.html提供执行上下文)决定"如何驱动"。同时,Windows + Edge + Widevine 的组合也提醒我们:功能测试的正确性不仅取决于代码,还与浏览器 profile 生命周期、显示会话状态等环境因素强相关。理解这套机制后,无论是为 dash.js 贡献新用例、排查回归,还是在 CI 中落地跨浏览器功能验证,都有了完整的入手路径。
- 音视频
- 前端
【免费下载链接】dash.js
A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.
相关推荐
dash.js 功能测试(Functional Tests)实战指南:基于 Karma 的端到端播放器验证体系
dash.js 功能测试(Functional Tests)实战指南:基于 Karma 的端到端播放器验证体系 dash.js 功能测试(Functional
音视频前端NetworkX 性能基准测试指南:基于 ASV 的基准套件配置与运行实践
NetworkX 性能基准测试指南:基于 ASV 的基准套件配置与运行实践 导读 本指南围绕 NetworkX 仓库中的 benchmarks/README.m
图计算数据分析科学计算electron-vue 单元测试实战:基于 Karma + Mocha + Chai 的测试体系与配置深度解析
electron vue 单元测试实战:基于 Karma + Mocha + Chai 的测试体系与配置深度解析 本篇技术指南围绕 electron vue 脚
前端桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考