mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 20:26:16 -08:00
test(dash): [PKG-DASH-05] checkpoint real SDK media and seek diagnostics
This commit is contained in:
1 parent
6840544e0e
commit
c4b5f5f454
47 files changed
+2216
-11
No files matched your search
@@ -3,5 +3,8 @@
|
||||
test/browser/media/hls/*.m3u8 text eol=lf
|
||||
test/browser/fixtures/hls-grouped.m3u8 text eol=lf
|
||||
test/browser/media/hls/*.mpegts binary
|
||||
# Preserve generated XML whitespace and its frozen playback-input hashes.
|
||||
test/browser/media/dash/*.mpd text eol=lf whitespace=-blank-at-eol
|
||||
test/browser/media/dash/*.m4s binary
|
||||
test/browser/media/*.m4a binary
|
||||
refactor/baselines/core-vendor/*.txt text eol=lf
|
||||
@@ -114,10 +114,17 @@ tarballs offline outside the workspace, repeats a frozen install and checks all
|
||||
public type modes without resolving any workspace source. It also verifies that
|
||||
implementation files/config do not leak into the archive. This is type acceptance;
|
||||
the full installed runtime and SDK release matrix still belongs to PKG-DASH-06/05.
|
||||
Browser tests exercise real old/new core DOM, menu clicks, native MP4 and cleanup,
|
||||
`dash-control.spec.js` tests exercise real old/new core DOM, menu clicks, native MP4 and cleanup,
|
||||
with controlled SDK methods. They do not load dash.js or test MPD/ABR decoding.
|
||||
PKG-DASH-05 must verify pinned dash.js 4.5.2 and 5.2.1 with real adaptive media and
|
||||
review SDK-driven refresh/source events and the existing demo lifecycle. Physical
|
||||
devices and installed package acceptance remain separate release gates.
|
||||
`dash-sdk.spec.js` now separately loads hash-pinned npm dash.js 4.5.2/5.2.1 and local
|
||||
multi-quality/multi-audio DASH segments. It covers decoding, explicit update after
|
||||
external selection, source topology replacement, retained callbacks and SDK ownership.
|
||||
It also includes a native SDK control without an ArtPlayer instance. Early SDK 4.5.2
|
||||
paused-seek stalls remain open; later passes do not resolve them. Unsupported MSE is
|
||||
recorded as a playback capability gap, not acceptance. This plugin currently refreshes
|
||||
on ready/restart/explicit update, not automatically on external SDK events.
|
||||
See [refactor validation](../../refactor/dash-validation.md) for baseline failures
|
||||
and [task plan](../../refactor/plan.md) for remaining type and release work.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-DASH-05",
|
||||
"sdks": [
|
||||
{
|
||||
"metadataUrl": "https://registry.npmjs.org/dashjs/4.5.2",
|
||||
"codeMember": "package/dist/dash.all.min.js",
|
||||
"release": {
|
||||
"name": "dashjs",
|
||||
"version": "4.5.2",
|
||||
"tarball": "https://registry.npmjs.org/dashjs/-/dashjs-4.5.2.tgz",
|
||||
"integrity": "sha512-WXPk0lPDSaHjiSVoVRh2jQPiMmB1alKUH8hV2CVmaI0vPUeT1wIY7madVE38SthfOmwS9IJViv1RrxrxdGjElg==",
|
||||
"sha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
|
||||
"files": {
|
||||
"package/package.json": "62b2295c4a92ec996ff066f7e5739893c46fce44d80c940c61ced9d5710226d1",
|
||||
"package/LICENSE.md": "f7cc4ba24850cc564766c75923d07c3d22fa27f304285f4d25ecffeaec964e28",
|
||||
"package/index.d.ts": "13e6bd0a3c77e2db9378e355915929ed3dda5d8de9c48f446c9d3e9fb7154dbd",
|
||||
"package/dist/dash.all.min.js": "17849c05872997095a63d74bde3fed6f79c14c6a48cdf904833d880b93a476da"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadataUrl": "https://registry.npmjs.org/dashjs/5.2.1",
|
||||
"codeMember": "package/dist/modern/umd/dash.all.min.js",
|
||||
"release": {
|
||||
"name": "dashjs",
|
||||
"version": "5.2.1",
|
||||
"tarball": "https://registry.npmjs.org/dashjs/-/dashjs-5.2.1.tgz",
|
||||
"integrity": "sha512-axcJmLeJCTJMLUfQRErUJB1KOiU5iHF+DMLBc3FKeox1HPYtGUQlC6Xm5Mm6vhBUR3CHHLIPOqw0JDIqMtP+Dw==",
|
||||
"sha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
|
||||
"files": {
|
||||
"package/package.json": "f89fa389855d2a21b380fa69c84735da25bd93e3b9d7c39bd0d634493425fd8c",
|
||||
"package/LICENSE.md": "f7cc4ba24850cc564766c75923d07c3d22fa27f304285f4d25ecffeaec964e28",
|
||||
"package/index.d.ts": "03b420509a480f893bf8253232cfd28428dc0be5f254fefe99a91e65847c010e",
|
||||
"package/dist/modern/umd/dash.all.min.js": "3a22b1f4576ab294f41e56f156cec7972a5f83edda379416778293ff22d2eb49"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"note": "Pinned SDK archives are test-only. Selected member hashes and whole-archive integrity; no production dependency or blanket SDK-version support claim."
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# PKG-DASH-05:真实 DASH SDK 与本地媒体验证检查点
|
||||
|
||||
起点 `6840544e`。本批新增真实 SDK/媒体测试与来源证据,没有修改生产实现、公开声明、
|
||||
依赖或版本。05 保持 doing,不把浏览器能力缺口或尚未解释的播放停滞计为完成。
|
||||
|
||||
## 输入与执行
|
||||
|
||||
- 固定实际 npm dash.js 4.5.2 与 5.2.1,归档 SHA512 integrity/SHA256,保存运行脚本、
|
||||
package.json、原始声明和 BSD-3-Clause 许可证成员指纹。SDK 仅供测试,未复制进生产包。
|
||||
- 新增 `scripts/generate-dash-fixture.mjs`:FFmpeg 合成 12 秒、90p/180p、英语 440 Hz/
|
||||
法语 880 Hz AAC 的本地 DASH。三种 MPD 分别为双画质双音轨、纯视频、单画质纯视频。
|
||||
31 个资源逐字节校验,生成器拒绝覆盖已有目录;MPD 强制 LF,m4s 作为二进制。
|
||||
MPD 保留生成时移除音轨/画质节点留下的空白节点,Git 对此类夹具关闭行尾空白提示,
|
||||
不手改已运行的 XML 字节或重新写指纹;此例外不适用于源码与其他文件。
|
||||
- `test/browser/dash-sdk.spec.js` 加载真实 SDK、发布插件或候选插件、新旧核心,在真实
|
||||
Chromium/Firefox/WebKit 执行。测试控制栏选择、Auto、实际解码视频尺寸与时钟、
|
||||
外部 SDK 选择后的同步 update、换源菜单拓扑、旧回调与调用方 SDK 销毁所有权。
|
||||
- 原生对照不创建 ArtPlayer/插件实例,直接用同一 SDK、video、媒体、缓冲配置执行
|
||||
画质/音轨与暂停 seek。页面仍加载核心脚本,另断言 Artplayer.instances 为零。
|
||||
- 每例附 SDK/插件/媒体来源、输入指纹、能力、轨道/画质、媒体缓冲区、事件时间、片段请求
|
||||
和浏览器 trace。报告与整个 results 目录在下一次套件前归档到独立缓存。
|
||||
|
||||
## 失败原因与证据边界
|
||||
|
||||
首轮 24 项:10 通过、8 失败、6 跳过。6 个能力探针的失败来自测试主动 destroy 未 initialize
|
||||
的 SDK:实际 4.5.2 源码 destroy → reset → attachSource(null),后者要求已初始化。
|
||||
探针只读取版本/能力,不创建媒体资源;已移除错误的未初始化销毁,实际播放用例仍验证销毁。
|
||||
|
||||
其余两项分别出现在 Chromium 候选核心/发布插件与 Firefox 发布核心/候选插件的 SDK 4.5.2
|
||||
组合:暂停后 seek 到 6 秒无法推进。trace 证明 AbortError 在测试超时后的销毁才出现,
|
||||
不能把它当成停滞起因。补充诊断后 24 项为 17 通过、1 失败、6 跳过;Chromium 停滞复现:
|
||||
视频缓冲终点 5.999999 秒,time=6、paused=false、seeking=true、readyState=1,未请求
|
||||
下一段视频,SDK 错误列表为空。SDK 音轨 rendered 事件已先于 seeking 到达,不能简单
|
||||
归因为“没有等音轨切完”。DASH-SEEK-01 保持 open。
|
||||
|
||||
原生初始对照、增加缓冲终点接近 6 秒的前提后各 6 项为 4 通过、2 跳过,均没有复现停滞。
|
||||
这些通过不能证明 ArtPlayer 是根因,也不能关闭此前新旧核心组合的失败。后续应继续比较
|
||||
SourceBuffer 与 video.buffered 的范围、SDK 调度和暂停/seek 顺序;不得仅增大超时、偏移
|
||||
seek 目标或吞掉未处理异常来制造通过。
|
||||
|
||||
外部 update 与换源首轮 24 项为 8 通过、8 失败、8 跳过。8 个换源失败源于测试保留了
|
||||
registry 的可变 option 对象;Component.update 会 Object.assign 修改它,调用到的是新
|
||||
回调。改为在换源前保存实际函数和旧 selector item;定向重跑 12 项为 8 通过、4 跳过。
|
||||
验证真实 SDK 连续切到无音轨、单画质、原拓扑,旧回调不改变新 SDK,四次 SDK 生命周期
|
||||
由调用方恰好销毁四次。没有据此修改生产组件更新语义。
|
||||
|
||||
正式 main 整套 54 项为 36 通过、2 失败、16 跳过;两个失败是 Chromium 新旧核心/发布插件
|
||||
的同类 SDK 4.5.2 seek 停滞。候选组合本轮通过不能关闭首轮 Firefox 候选插件失败。
|
||||
完整 `yarn ci:check` 717 项通过(661 单元、14 工程、42 基线),261 个生产 TS 严格检查通过;
|
||||
它不包含该浏览器套件,不能把本地工程检查通过表述为全部浏览器通过。生成器整理参数后
|
||||
在新目录重建,31 个资源与冻结字节全部相同;只证明同一 FFmpeg/主机下本次重建。
|
||||
已查看候选核心/main/SDK 5.2.1 的 Chromium 播放截图,6 秒画面和 Auto/fr 控件显示正常。
|
||||
|
||||
最终 main 执行和 CI 摘要见 [机器证据](../baselines/dash-sdk-checkpoint.json)。所有早期报告、
|
||||
trace 和结果目录均保留;本地缓存未上传,换机器应重新执行。后续通过不覆盖开放失败。
|
||||
|
||||
## 后续范围与回退
|
||||
|
||||
当前 217 项:74 done、4 doing、139 todo。DASH-05 仍需完成停滞排查、实际 SDK 类型消费者、
|
||||
SDK 驱动状态刷新/错误和播放组合、legacy 正式产物与有效 MSE/设备环境验证。
|
||||
原有同步 update 已通过实际 SDK 对照;当前插件不会自动订阅 SDK 状态变化,自动刷新是否
|
||||
增强及其订阅/重入/销毁规则需单独验证,不能从显式 update 的通过推导自动同步已实现。
|
||||
06 继续实际 8082 示例生命周期与完整隔离分发,版本升级和全项目发布复盘按总计划执行。
|
||||
|
||||
本批为独立本地 checkpoint,不推送、不发布。回退只移除这批测试、合成媒体、生成器和记录,
|
||||
保留 01~04 已完成的源码、兼容声明与验证。新增基线测试纳入已有 test:baseline/ci:check。
|
||||
@@ -19,6 +19,13 @@
|
||||
TS 4.3.5/5.9.3、五种模块模式、八条非法用法及实际 npm 旧声明的消费者对照。
|
||||
- `yarn test:dash-types-package`:Yarn 打包核心/DASH、工作区外离线安装与冻结锁重装,
|
||||
检查五种模式、CommonJS callable/命名空间、每模式八条非法调用,并拒绝解析回工作区。
|
||||
- `yarn test:browser test/browser/dash-sdk.spec.js --trace on`:真实 npm dash.js 4.5.2/5.2.1,
|
||||
本地多画质/多音轨 MPD,新旧核心/插件、外部同步 update、连续换源和 SDK 原生对照。
|
||||
`ARTPLAYER_DASH_ARTIFACT` 可选择正式 main/legacy;未指定则测试源码构建。
|
||||
无 MSE 的引擎只通过能力探针,播放用例明确跳过,不计为该引擎 DASH 验收。
|
||||
- `node --test refactor/scripts/dash-sdk.test.mjs`:SDK 归档/成员/许可证来源、媒体指纹与拓扑。
|
||||
如需重新合成诊断媒体,用 `node scripts/generate-dash-fixture.mjs refactor/.cache/new-dash-media`,
|
||||
输出目录必须不存在;可通过 `ARTPLAYER_FFMPEG` 指定 FFmpeg,不覆盖已冻结输入。
|
||||
|
||||
Node helper 分别加载当前源码、固定 Git 提交的旧工作区 UMD、实际 npm main/legacy/module。
|
||||
Git 内容以 LF 指纹核对,发布归档与成员以原始字节指纹核对。`ARTPLAYER_TEST_DASH` 可指定
|
||||
@@ -29,10 +36,12 @@ Git 内容以 LF 指纹核对,发布归档与成员以原始字节指纹核对
|
||||
原始轨道身份、调用顺序、错误身份、ABR 配置保留、换 SDK、多实例和关闭后的引用。
|
||||
真实旧实现的缺陷观察保持冻结;候选生命周期新增正确行为断言,不把历史缺陷当作兼容要求。
|
||||
|
||||
浏览器使用真实发布/候选 ArtPlayer DOM、真实发布/候选插件和本地原生 MP4。SDK 方法可控,
|
||||
`dash-control.spec.js` 使用真实发布/候选 ArtPlayer DOM、真实发布/候选插件和本地原生 MP4。SDK 方法可控,
|
||||
断言真实点击触发的参数及控制栏/设置同步,再通过真实播放按钮验证视频时钟推进、无媒体错误。
|
||||
**没有加载 dash.js,没有验证 MPD、ABR 自动切换或自适应流解码。** 这些仍是 05 必须完成的
|
||||
固定 dash.js 4.5.2 / 5.2.1 集成验证。`ARTPLAYER_DASH_ARTIFACT` 可替换插件脚本;02 未设置,
|
||||
固定 dash.js 4.5.2 / 5.2.1 集成验证。独立 `dash-sdk.spec.js` 已开始真实验证,状态以
|
||||
[05 检查点](changes/2026-09-12-PKG-DASH-05-sdk-checkpoint.md) 为准;不覆盖早期停滞失败。
|
||||
`ARTPLAYER_DASH_ARTIFACT` 可替换插件脚本;02 未设置,
|
||||
03 分别使用正式 main 与 legacy 文件。核心仍按服务端 manifest 加载发布/候选源码构建。
|
||||
|
||||
## 02 首次失败与证据
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 140 / doing 3 / blocked 0 / done 74 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 139 / doing 4 / blocked 0 / done 74 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
| PKG-DASH-02 | artplayer-plugin-dash-control<br>建立特有行为与错误测试 | PKG-DASH-01, ENG-03, ENG-05 | 保留已有 5 项稳定 ID 回归,补发布版 SDK 4.x/当前 5.x、音轨/空列表/过滤/换源与关闭引用 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-DASH-03 | artplayer-plugin-dash-control<br>整理内部职责与资源 | PKG-DASH-02, CORE-11, CORE-14 | 能力适配保留 SDK 4.x 与 5.x,稳定 ID 映射、ABR 状态、UI 清理和生命周期职责分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-DASH-04 | artplayer-plugin-dash-control<br>迁移自有源码和公开类型 | PKG-DASH-03, ENG-04, ENG-06, CORE-07 | dash.js adapter、selector 和回调类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-DASH-05 | artplayer-plugin-dash-control<br>验证新旧核心和组合 | PKG-DASH-04, CORE-22 | 固定 dash.js 4.5.2/5.2.1 与本地 DASH 实际清晰度/音轨、高亮、Auto 和换源组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-DASH-05 | artplayer-plugin-dash-control<br>验证新旧核心和组合 | PKG-DASH-04, CORE-22 | 固定 dash.js 4.5.2/5.2.1 与本地 DASH 实际清晰度/音轨、高亮、Auto 和换源组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
|
||||
| PKG-DASH-06 | artplayer-plugin-dash-control<br>验证分发并同步文档 | PKG-DASH-05, ENG-07 | dash.control.js、支持的 dash.js 版本与产物 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-multiple-subtitles
|
||||
@@ -483,3 +483,4 @@
|
||||
- PKG-DASH-02: [记录](changes/2026-09-12-PKG-DASH-02-tests.md) [记录](dash-validation.md) [记录](baselines/dash-validation.json)
|
||||
- PKG-DASH-03: [记录](changes/2026-09-12-PKG-DASH-03-runtime.md) [记录](baselines/dash-runtime.json) [记录](dash-validation.md)
|
||||
- PKG-DASH-04: [记录](changes/2026-09-12-PKG-DASH-04-types.md) [记录](baselines/dash-types-validation.json) [记录](dash-validation.md)
|
||||
- PKG-DASH-05: [记录](changes/2026-09-12-PKG-DASH-05-sdk-checkpoint.md) [记录](baselines/dash-sdk.json) [记录](baselines/dash-sdk-checkpoint.json) [记录](dash-validation.md)
|
||||
@@ -1,5 +1,24 @@
|
||||
# 进度与证据
|
||||
|
||||
## 当前实施:DASH-05 真实 SDK 与 MPD 检查点
|
||||
|
||||
已固定 npm dash.js 4.5.2/5.2.1 归档/成员/许可证指纹,新增本地双画质双音轨 DASH、
|
||||
纯视频和单画质拓扑,真实 Chromium/Firefox/WebKit 测试及不创建 ArtPlayer 的 SDK 对照。
|
||||
正式 main 共 54 项:36 通过、2 失败、16 因 Windows WebKit 无 MSE 跳过,不算播放验收。
|
||||
外部 SDK 选择后的同步 update、连续换源/菜单清理、旧回调及调用方 SDK 销毁所有权通过。
|
||||
|
||||
两个 main 失败均为 SDK 4.5.2/发布插件的暂停 seek:新旧核心都可在 5.999999 秒缓冲
|
||||
终点 seek 到 6 秒后停滞。DASH-SEEK-01 保持 open;AbortError 是超时后销毁的结果,
|
||||
不能当作起因。原生对照通过不足以定位或关闭它,首次 Firefox/候选插件失败也仍保留。
|
||||
已修正能力探针错误销毁未初始化 SDK、以及换源测试误保留可变 option 对象两个测试问题。
|
||||
完整本地工程 CI 717 项通过、261 个生产 TS 严格检查通过;该 CI 不含上述失败的 SDK 浏览器套件。
|
||||
生成器在同一 FFmpeg/主机的新目录重建 31 个资源,全部字节一致,已查看实际播放截图。
|
||||
|
||||
当前 217 项:74 done、4 doing、139 todo。DASH-05 尚未完成;实际 SDK 类型、自动状态
|
||||
刷新/错误、legacy SDK 矩阵、设备和完整分发继续推进。此次没有修改生产源码、依赖或版本。
|
||||
详见 [本批记录](changes/2026-09-12-PKG-DASH-05-sdk-checkpoint.md)、
|
||||
[执行证据](baselines/dash-sdk-checkpoint.json)。以下各节为对应阶段当时的历史状态。
|
||||
|
||||
## 最新完成:DASH-04 严格 TS、公开类型和编辑器迁移
|
||||
|
||||
DASH 五个自有模块全部 TS 化,源码/公开工厂可互相赋值;默认字段和自定义 SDK 泛型准确,
|
||||
|
||||
@@ -140,3 +140,4 @@
|
||||
| DASH-STATE-01 | open / 已复现 | DASH 当前项高亮使用真值 ID、音轨身份和标签去重,边界待核实 | PKG-DASH-02, PKG-DASH-03, PKG-DASH-05 |
|
||||
| DASH-TYPE-01 | resolved / 源码/产物事实 | DASH 类型未表达默认工厂参数、SDK 回调对象和现代模块入口 | PKG-DASH-04 |
|
||||
| DASH-DEMO-01 | open / 源码/产物事实 | DASH 示例仅新 SDK 且重复安装会累积销毁回调 | PKG-DASH-06 |
|
||||
| DASH-SEEK-01 | open / 已复现 | 真实 dash.js 4.5.2 暂停 seek 到缓冲片段边界后可能持续 waiting | PKG-DASH-05, REVIEW-02 |
|
||||
+21
-2
@@ -2850,7 +2850,8 @@
|
||||
"test/dash-contract.test.js",
|
||||
"refactor/dash-validation.md",
|
||||
"test/dash-lifecycle.test.js",
|
||||
"refactor/baselines/dash-runtime.json"
|
||||
"refactor/baselines/dash-runtime.json",
|
||||
"refactor/baselines/dash-sdk-checkpoint.json"
|
||||
],
|
||||
"compatibleResolution": "按方法能力适配两代已存在用法,保留 v5 稳定 ID,不能要求旧消费者改用新 SDK。",
|
||||
"closureCriteria": "归档旧用法、v4/v5 受控及实际媒体矩阵通过,清晰度键不混用。"
|
||||
@@ -2903,7 +2904,8 @@
|
||||
"test/dash-contract.test.js",
|
||||
"refactor/dash-validation.md",
|
||||
"test/dash-lifecycle.test.js",
|
||||
"refactor/baselines/dash-runtime.json"
|
||||
"refactor/baselines/dash-runtime.json",
|
||||
"refactor/baselines/dash-sdk-checkpoint.json"
|
||||
],
|
||||
"compatibleResolution": "验证零 ID、重复标签和克隆音轨当前项,在保持原展示语义时按实际可选项定位高亮。",
|
||||
"closureCriteria": "有受控和真实 SDK 证据说明选中的 representation/track 与 UI 一致。"
|
||||
@@ -2950,6 +2952,23 @@
|
||||
],
|
||||
"compatibleResolution": "验证真实 SDK 销毁和示例重载路径,对齐 v4/v5 文档与示例,不让旧监听误操作新实例。",
|
||||
"closureCriteria": "真实 8082 demo、旧/新 SDK 路径与 tarball 产物一致,监听和 SDK 所有权可验证。"
|
||||
},
|
||||
{
|
||||
"id": "DASH-SEEK-01",
|
||||
"title": "真实 dash.js 4.5.2 暂停 seek 到缓冲片段边界后可能持续 waiting",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"PKG-DASH-05",
|
||||
"REVIEW-02"
|
||||
],
|
||||
"evidence": [
|
||||
"test/browser/dash-sdk.spec.js",
|
||||
"refactor/baselines/dash-sdk-checkpoint.json",
|
||||
"refactor/changes/2026-09-12-PKG-DASH-05-sdk-checkpoint.md"
|
||||
],
|
||||
"compatibleResolution": "先比较实际 SDK、SourceBuffer/video 缓冲与暂停 seek 调度;不偏移公开 seek 目标,不吞异常或增加超时伪造通过。",
|
||||
"closureCriteria": "原失败的新旧核心/插件组合有确定根因与对应修复/可验证环境边界;原生对照及原顺序、原目标回归成立,后续偶然通过不能关闭。"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Verify frozen real SDK and media inputs.
|
||||
import test from 'node:test'
|
||||
import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
test('DASH real SDK releases retain exact versions, executable members, declarations and license provenance', async () => {
|
||||
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/dash-sdk.json')))
|
||||
assert.equal(baseline.task, 'PKG-DASH-05')
|
||||
assert.deepEqual(baseline.sdks.map(sdk => sdk.release.version), ['4.5.2', '5.2.1'])
|
||||
for (const sdk of baseline.sdks) {
|
||||
const archive = await ensureArchive(sdk.release)
|
||||
assert.equal(sdk.release.name, 'dashjs')
|
||||
assert.equal(sdk.metadataUrl, `https://registry.npmjs.org/dashjs/${sdk.release.version}`)
|
||||
assert(sdk.release.files[sdk.codeMember])
|
||||
assert(sdk.release.files['package/index.d.ts'])
|
||||
assert(sdk.release.files['package/LICENSE.md'])
|
||||
for (const [member, expected] of Object.entries(sdk.release.files))
|
||||
assert.equal(hash(readMember(archive, member)), expected, member)
|
||||
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
|
||||
assert.equal(manifest.version, sdk.release.version)
|
||||
assert.equal(manifest.name, 'dashjs')
|
||||
assert.equal(manifest.license, 'BSD-3-Clause')
|
||||
}
|
||||
})
|
||||
|
||||
test('DASH media bytes and topology remain reproducible inputs rather than remote stream assumptions', () => {
|
||||
const directory = path.join(refactorDir, '../test/browser/media/dash')
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'manifest.json')))
|
||||
assert.equal(manifest.kind, 'generated-dash-media')
|
||||
assert.equal(manifest.duration, 12)
|
||||
assert.deepEqual(manifest.videoHeights, [90, 180])
|
||||
assert.deepEqual(manifest.audio, [{ lang: 'en', frequency: 440 }, { lang: 'fr', frequency: 880 }])
|
||||
assert.deepEqual(fs.readdirSync(directory).filter(name => name !== 'manifest.json').sort(), Object.keys(manifest.files).sort())
|
||||
for (const [name, expected] of Object.entries(manifest.files)) {
|
||||
assert(/^[\w.-]+$/.test(name))
|
||||
const bytes = fs.readFileSync(path.join(directory, name))
|
||||
assert.equal(hash(bytes), expected.sha256, name)
|
||||
assert.equal(bytes.length, expected.bytes, name)
|
||||
}
|
||||
const read = name => fs.readFileSync(path.join(directory, name), 'utf8')
|
||||
assert.equal([...read('master.mpd').matchAll(/contentType="audio"/g)].length, 2)
|
||||
assert.equal([...read('master.mpd').matchAll(/<Representation\b/g)].length, 4)
|
||||
assert.equal([...read('video-only.mpd').matchAll(/<Representation\b/g)].length, 2)
|
||||
assert(!read('video-only.mpd').includes('contentType="audio"'))
|
||||
assert.equal([...read('single.mpd').matchAll(/<Representation\b/g)].length, 1)
|
||||
assert(!read('single.mpd').includes('height="180"'))
|
||||
assert(manifest.commands[0].includes('init-$RepresentationID$.m4s'))
|
||||
assert(manifest.commands[0].includes('chunk-$RepresentationID$-$Number%05d$.m4s'))
|
||||
})
|
||||
+7
-2
@@ -2058,11 +2058,16 @@
|
||||
"PKG-DASH-04",
|
||||
"CORE-22"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "固定 dash.js 4.5.2/5.2.1 与本地 DASH 实际清晰度/音轨、高亮、Auto 和换源组合",
|
||||
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-DASH-05-sdk-checkpoint.md",
|
||||
"baselines/dash-sdk.json",
|
||||
"baselines/dash-sdk-checkpoint.json",
|
||||
"dash-validation.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-DASH-06",
|
||||
|
||||
@@ -1047,10 +1047,12 @@
|
||||
],
|
||||
"sourceFiles": [
|
||||
"packages/artplayer-plugin-dash-control/src/index.ts",
|
||||
"docs/assets/example/dash.control.js"
|
||||
"docs/assets/example/dash.control.js",
|
||||
"refactor/baselines/dash-sdk.json",
|
||||
"test/browser/dash-sdk.spec.js"
|
||||
],
|
||||
"contract": "Caller owns art.dash. Candidate adapts the released 4.x quality method surface and workspace 5.x stable representation ID surface. Controlled contracts and DOM validation pass; actual pinned 4.5.2/5.2.1 SDK media remains PKG-DASH-05.",
|
||||
"validationStatus": "Source integration observed; complete real SDK/service/device matrix not yet verified.",
|
||||
"validationStatus": "Pinned real npm 4.5.2/5.2.1, generated MPD decoding, explicit update and source replacement now exercised. DASH-SEEK-01 remains open; Windows WebKit has no MSE. SDK declarations, automatic state refresh, full formats/devices/distribution remain incomplete.",
|
||||
"demoPathsSource": "refactor/baselines/demo-inventory.json"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { hash } from '../refactor/scripts/releases.mjs'
|
||||
|
||||
const root = fileURLToPath(new URL('../', import.meta.url))
|
||||
const output = path.resolve(root, process.argv[2] || 'test/browser/media/dash')
|
||||
assert(!fs.existsSync(output), 'Use a new directory; never overwrite frozen media')
|
||||
const ffmpeg = process.env.ARTPLAYER_FFMPEG || 'ffmpeg'
|
||||
const version = execFileSync(ffmpeg, ['-version'], { encoding: 'utf8' }).split(/\r?\n/)[0]
|
||||
fs.mkdirSync(output, { recursive: true })
|
||||
const args = [
|
||||
['-hide_banner', '-loglevel', 'error', '-n'],
|
||||
['-f', 'lavfi', '-i', 'testsrc2=size=160x90:rate=24'],
|
||||
['-f', 'lavfi', '-i', 'testsrc2=size=320x180:rate=24'],
|
||||
['-f', 'lavfi', '-i', 'sine=frequency=440:sample_rate=48000'],
|
||||
['-f', 'lavfi', '-i', 'sine=frequency=880:sample_rate=48000'],
|
||||
['-t', '12', '-map', '0:v', '-map', '1:v', '-map', '2:a', '-map', '3:a'],
|
||||
['-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-profile:v', 'baseline', '-level:v', '3.0'],
|
||||
['-b:v:0', '150k', '-b:v:1', '350k', '-g', '48', '-keyint_min', '48', '-sc_threshold', '0'],
|
||||
['-c:a', 'aac', '-b:a', '48k', '-ac', '2', '-metadata:s:a:0', 'language=en', '-metadata:s:a:1', 'language=fr'],
|
||||
['-f', 'dash', '-seg_duration', '2', '-use_template', '1', '-use_timeline', '1'],
|
||||
['-adaptation_sets', 'id=0,streams=0,1 id=1,streams=2 id=2,streams=3'],
|
||||
['-init_seg_name', 'init-$RepresentationID$.m4s', '-media_seg_name', 'chunk-$RepresentationID$-$Number%05d$.m4s', 'master.mpd'],
|
||||
].flat()
|
||||
execFileSync(ffmpeg, args, { cwd: output, stdio: 'pipe', timeout: 120000 })
|
||||
const master = fs.readFileSync(path.join(output, 'master.mpd'), 'utf8')
|
||||
const audioSets = /<AdaptationSet\b[^>]+contentType="audio"[\s\S]*?<\/AdaptationSet>/g
|
||||
assert.equal([...master.matchAll(audioSets)].length, 2)
|
||||
const videoOnly = master.replace(audioSets, '')
|
||||
assert(!videoOnly.includes('contentType="audio"'))
|
||||
fs.writeFileSync(path.join(output, 'video-only.mpd'), videoOnly)
|
||||
const single = videoOnly.replace(/<Representation\b[^>]+id="1"[\s\S]*?<\/Representation>/, '')
|
||||
assert.equal([...single.matchAll(/<Representation\b/g)].length, 1)
|
||||
fs.writeFileSync(path.join(output, 'single.mpd'), single)
|
||||
const files = Object.fromEntries(fs.readdirSync(output).sort().map(name => [name, { sha256: hash(fs.readFileSync(path.join(output, name))), bytes: fs.statSync(path.join(output, name)).size }]))
|
||||
fs.writeFileSync(path.join(output, 'manifest.json'), `${JSON.stringify({ kind: 'generated-dash-media', version, duration: 12, videoHeights: [90, 180], audio: [{ lang: 'en', frequency: 440 }, { lang: 'fr', frequency: 880 }], commands: [args], files }, null, 2)}\n`)
|
||||
console.log(`Generated ${Object.keys(files).length} DASH files in ${output}`)
|
||||
@@ -0,0 +1,307 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import process from 'node:process'
|
||||
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
|
||||
import { compilePackage } from '../helpers/load.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const sdks = new Map()
|
||||
const media = new Map()
|
||||
let candidate
|
||||
let published
|
||||
let mediaManifest
|
||||
let pluginRelease
|
||||
|
||||
test.beforeAll(async () => {
|
||||
const baseline = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/dash-sdk.json', import.meta.url)))
|
||||
for (const sdk of baseline.sdks) {
|
||||
const archive = await ensureArchive(sdk.release)
|
||||
const bytes = readMember(archive, sdk.codeMember)
|
||||
assert.equal(hash(bytes), sdk.release.files[sdk.codeMember])
|
||||
sdks.set(sdk.release.version, { ...sdk, code: bytes.toString() })
|
||||
}
|
||||
pluginRelease = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/dash-control-release.json', import.meta.url))).release
|
||||
const bytes = readMember(await ensureArchive(pluginRelease), 'package/dist/artplayer-plugin-dash-control.js')
|
||||
assert.equal(hash(bytes), pluginRelease.files['package/dist/artplayer-plugin-dash-control.js'])
|
||||
published = bytes.toString()
|
||||
candidate = process.env.ARTPLAYER_DASH_ARTIFACT ? fs.readFileSync(process.env.ARTPLAYER_DASH_ARTIFACT, 'utf8') : await compilePackage('artplayer-plugin-dash-control', 'umd')
|
||||
mediaManifest = JSON.parse(fs.readFileSync(new URL('./media/dash/manifest.json', import.meta.url)))
|
||||
for (const [name, expected] of Object.entries(mediaManifest.files)) {
|
||||
assert(/^[\w.-]+$/.test(name))
|
||||
const bytes = fs.readFileSync(new URL(`./media/dash/${name}`, import.meta.url))
|
||||
assert.equal(hash(bytes), expected.sha256)
|
||||
assert.equal(bytes.length, expected.bytes)
|
||||
media.set(name, bytes)
|
||||
}
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
const state = await page.evaluate(() => {
|
||||
const art = window.art
|
||||
const dash = art?.dash || window.nativeDash
|
||||
let tracks
|
||||
let levels
|
||||
try {
|
||||
tracks = dash?.getTracksFor('audio').map(track => ({ id: track.id, index: track.index, lang: track.lang }))
|
||||
levels = (dash?.getRepresentationsByType?.('video') || dash?.getBitrateInfoListFor?.('video'))?.map(level => ({ id: level.id, qualityIndex: level.qualityIndex, height: level.height }))
|
||||
}
|
||||
catch (error) {
|
||||
levels = { error: error.message }
|
||||
}
|
||||
const video = art?.video || window.nativeVideo
|
||||
const buffered = video ? Array.from({ length: video.buffered.length }, (_, index) => [video.buffered.start(index), video.buffered.end(index)]) : []
|
||||
return { errors: window.sdkErrors, events: window.sdkEvents, mediaEvents: window.mediaEvents, nativePlay: window.nativePlay, tracks, levels, buffered, paused: video?.paused, seeking: video?.seeking, time: video?.currentTime, ready: art?.isReady, width: video?.videoWidth, height: video?.videoHeight, error: video?.error?.code, readyState: video?.readyState, destroyed: window.sdkDestroyed }
|
||||
}).catch(error => ({ error: error.message }))
|
||||
await testInfo.attach('dash-sdk-state', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
if (!page.isClosed()) {
|
||||
await page.evaluate(() => {
|
||||
if (window.art && !window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
window.nativeDash?.destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
async function loadSDK(page, version, core, testInfo) {
|
||||
const sdk = sdks.get(version)
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.addScriptTag({ content: sdk.code })
|
||||
const capability = await page.evaluate(() => ({ sdk: window.dashjs.supportsMediaSource(), mse: typeof window.MediaSource, managed: typeof window.ManagedMediaSource, avc: Boolean(window.MediaSource?.isTypeSupported('video/mp4; codecs="avc1.42c01e"')), aac: Boolean(window.MediaSource?.isTypeSupported('audio/mp4; codecs="mp4a.40.2"')) }))
|
||||
await testInfo.attach('dash-sdk-inputs', { contentType: 'application/json', body: JSON.stringify({ sdk: sdk.release, codeMember: sdk.codeMember, pluginRelease, candidateSHA256: hash(candidate), candidate: process.env.ARTPLAYER_DASH_ARTIFACT || 'workspace source build', media: mediaManifest, capability }) })
|
||||
return capability
|
||||
}
|
||||
|
||||
async function openDash(page, version, core, plugin, testInfo) {
|
||||
const capability = await loadSDK(page, version, core, testInfo)
|
||||
test.skip(!capability.sdk, 'Actual SDK reports no MediaSource support; this engine has no DASH playback acceptance')
|
||||
await routeMedia(page)
|
||||
await page.addScriptTag({ content: plugin === 'published' ? published : candidate })
|
||||
await page.evaluate(() => {
|
||||
window.sdkErrors = []
|
||||
window.sdkEvents = []
|
||||
window.mediaEvents = []
|
||||
window.sdkDestroyed = 0
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '/dash-fixture/master.mpd',
|
||||
muted: true,
|
||||
setting: true,
|
||||
plugins: [window.artplayerPluginDashControl({ quality: { control: true, setting: true }, audio: { control: true, setting: true } })],
|
||||
customType: { mpd(video, url, art) {
|
||||
if (art.dash) {
|
||||
art.dash.destroy()
|
||||
window.sdkDestroyed++
|
||||
}
|
||||
const dash = window.dashjs.MediaPlayer().create()
|
||||
art.dash = dash
|
||||
dash.updateSettings({ debug: { logLevel: 0 }, streaming: { abr: { initialBitrate: { video: 150 } }, buffer: { fastSwitchEnabled: true, bufferTimeDefault: 2, bufferTimeAtTopQuality: 2 } } })
|
||||
const events = window.dashjs.MediaPlayer.events
|
||||
dash.on(events.ERROR, event => window.sdkErrors.push({ code: event.error?.code, message: event.error?.message, event: event.type }))
|
||||
for (const name of ['STREAM_INITIALIZED', 'QUALITY_CHANGE_REQUESTED', 'QUALITY_CHANGE_RENDERED', 'TRACK_CHANGE_RENDERED', 'STREAM_TEARDOWN_COMPLETE', 'BUFFER_LEVEL_STATE_CHANGED', 'FRAGMENT_LOADING_COMPLETED', 'PLAYBACK_SEEKING', 'PLAYBACK_SEEKED']) {
|
||||
if (events[name])
|
||||
dash.on(events[name], event => window.sdkEvents.push({ name, at: performance.now(), mediaType: event.mediaType, oldQuality: event.oldQuality, newQuality: event.newQuality, state: event.state, url: event.request?.url, time: video.currentTime }))
|
||||
}
|
||||
dash.initialize(video, url, false)
|
||||
} },
|
||||
})
|
||||
const art = window.art
|
||||
for (const name of ['play', 'playing', 'pause', 'seeking', 'seeked', 'waiting', 'emptied', 'loadedmetadata', 'loadeddata', 'canplay', 'error']) {
|
||||
art.video.addEventListener(name, () => window.mediaEvents.push({ name, at: performance.now(), time: art.video.currentTime, paused: art.video.paused, readyState: art.video.readyState, buffered: Array.from({ length: art.video.buffered.length }, (_, index) => [art.video.buffered.start(index), art.video.buffered.end(index)]) }))
|
||||
}
|
||||
art.on('destroy', () => {
|
||||
art.dash.destroy()
|
||||
window.sdkDestroyed++
|
||||
})
|
||||
document.querySelector('#play').onclick = () => art.play()
|
||||
document.querySelector('#pause').onclick = () => art.pause()
|
||||
})
|
||||
}
|
||||
|
||||
async function routeMedia(page) {
|
||||
await page.route('**/dash-fixture/**', async (route) => {
|
||||
const name = new URL(route.request().url()).pathname.split('/').at(-1)
|
||||
const bytes = media.get(name)
|
||||
await route.fulfill({ status: bytes ? 200 : 503, body: bytes || 'Intentional DASH fixture failure', contentType: name.endsWith('.mpd') ? 'application/dash+xml' : 'video/mp4' })
|
||||
})
|
||||
}
|
||||
|
||||
for (const version of ['4.5.2', '5.2.1']) {
|
||||
test(`dash.js ${version}: actual SDK reports MSE capabilities and version`, async ({ page }, testInfo) => {
|
||||
const capability = await loadSDK(page, version, 'candidate', testInfo)
|
||||
// This capability probe creates no media resources; destroy requires initialize.
|
||||
const actual = await page.evaluate(() => window.dashjs.MediaPlayer().create().getVersion())
|
||||
expect(actual).toBe(version)
|
||||
if (capability.mse === 'undefined' && capability.managed === 'undefined')
|
||||
expect(capability.sdk).toBe(false)
|
||||
else
|
||||
expect(capability.sdk).toBe(true)
|
||||
})
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const [plugin, version] of [['published', '4.5.2'], ['candidate', '4.5.2'], ['candidate', '5.2.1']]) {
|
||||
test(`${core} core / ${plugin} DASH / ${version}: real MPD decoding and UI selection`, async ({ page }, testInfo) => {
|
||||
await openDash(page, version, core, plugin, testInfo)
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
|
||||
expect(await page.evaluate(() => window.art.dash.getTracksFor('audio').map(track => track.lang))).toEqual(['en', 'fr'])
|
||||
expect(await page.evaluate(() => window.art.video.getVideoPlaybackQuality().totalVideoFrames)).toBeGreaterThan(0)
|
||||
const control = page.locator('.art-control-dash-quality')
|
||||
await control.hover()
|
||||
await control.locator('.art-selector-item').filter({ hasText: /^180p$/ }).click()
|
||||
await expect.poll(() => page.evaluate(() => ({ auto: window.art.dash.getSettings().streaming.abr.autoSwitchBitrate.video, height: window.art.video.videoHeight }))).toEqual({ auto: false, height: 180 })
|
||||
await control.hover()
|
||||
await control.locator('.art-selector-item').filter({ hasText: /^Auto$/ }).click()
|
||||
expect(await page.evaluate(() => window.art.dash.getSettings().streaming.abr.autoSwitchBitrate.video)).toBe(true)
|
||||
const audio = page.locator('.art-control-dash-audio')
|
||||
await audio.hover()
|
||||
await audio.locator('.art-selector-item').filter({ hasText: /^fr$/ }).click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.dash.getCurrentTrackFor('audio').lang)).toBe('fr')
|
||||
await expect(audio.locator('.art-selector-value')).toHaveText('fr')
|
||||
expect(await page.evaluate(() => window.art.setting.find('dash-audio').tooltip)).toBe('fr')
|
||||
await page.locator('#pause').click()
|
||||
expect(await page.evaluate(() => window.art.video.paused)).toBe(true)
|
||||
await page.evaluate(() => {
|
||||
window.art.seek = 6
|
||||
})
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(6.2)
|
||||
expect(await page.evaluate(() => window.sdkErrors)).toEqual([])
|
||||
await testInfo.attach('dash-sdk-playing', { contentType: 'image/png', body: await page.screenshot() })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const version of ['4.5.2', '5.2.1']) {
|
||||
test(`dash.js ${version}: native SDK track switch and paused seek without ArtPlayer`, async ({ page }, testInfo) => {
|
||||
const capability = await loadSDK(page, version, 'candidate', testInfo)
|
||||
test.skip(!capability.sdk, 'Actual SDK reports no MediaSource support; no native DASH playback acceptance')
|
||||
await routeMedia(page)
|
||||
await page.evaluate(() => {
|
||||
const video = document.createElement('video')
|
||||
window.nativeVideo = video
|
||||
video.muted = true
|
||||
document.querySelector('.player').append(video)
|
||||
const dash = window.dashjs.MediaPlayer().create()
|
||||
window.nativeDash = dash
|
||||
window.sdkEvents = []
|
||||
window.sdkErrors = []
|
||||
window.mediaEvents = []
|
||||
dash.updateSettings({ debug: { logLevel: 0 }, streaming: { abr: { initialBitrate: { video: 150 } }, buffer: { fastSwitchEnabled: true, bufferTimeDefault: 2, bufferTimeAtTopQuality: 2 } } })
|
||||
const events = window.dashjs.MediaPlayer.events
|
||||
dash.on(events.ERROR, event => window.sdkErrors.push({ code: event.error?.code, message: event.error?.message }))
|
||||
for (const name of ['FRAGMENT_LOADING_COMPLETED', 'TRACK_CHANGE_RENDERED', 'QUALITY_CHANGE_RENDERED', 'BUFFER_LEVEL_STATE_CHANGED', 'PLAYBACK_SEEKING', 'PLAYBACK_SEEKED']) {
|
||||
dash.on(events[name], event => window.sdkEvents.push({ name, at: performance.now(), mediaType: event.mediaType, state: event.state, url: event.request?.url, time: video.currentTime }))
|
||||
}
|
||||
for (const name of ['play', 'playing', 'pause', 'seeking', 'seeked', 'waiting']) {
|
||||
video.addEventListener(name, () => window.mediaEvents.push({ name, at: performance.now(), time: video.currentTime, paused: video.paused, readyState: video.readyState, buffered: Array.from({ length: video.buffered.length }, (_, index) => [video.buffered.start(index), video.buffered.end(index)]) }))
|
||||
}
|
||||
dash.initialize(video, '/dash-fixture/master.mpd', false)
|
||||
document.querySelector('#play').onclick = () => {
|
||||
window.nativePlay = 'pending'
|
||||
video.play().then(() => {
|
||||
window.nativePlay = 'fulfilled'
|
||||
}, (error) => {
|
||||
window.nativePlay = `${error.name}: ${error.message}`
|
||||
})
|
||||
}
|
||||
document.querySelector('#pause').onclick = () => video.pause()
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVideo.readyState)).toBeGreaterThanOrEqual(2)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(0.3)
|
||||
await page.evaluate(() => {
|
||||
const dash = window.nativeDash
|
||||
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: false } } } })
|
||||
if (dash.setQualityFor)
|
||||
dash.setQualityFor('video', 1, true)
|
||||
else
|
||||
dash.setRepresentationForTypeById('video', '1', true)
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVideo.videoHeight)).toBe(180)
|
||||
// Match the failed integrated run: video data ends at the next seek target.
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVideo.buffered.end(window.nativeVideo.buffered.length - 1))).toBeCloseTo(6, 4)
|
||||
await page.evaluate(() => {
|
||||
const dash = window.nativeDash
|
||||
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } })
|
||||
dash.setCurrentTrack(dash.getTracksFor('audio').find(track => track.lang === 'fr'))
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.nativeDash.getCurrentTrackFor('audio').lang)).toBe('fr')
|
||||
await page.locator('#pause').click()
|
||||
await page.evaluate(() => {
|
||||
window.nativeVideo.currentTime = 6
|
||||
})
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)
|
||||
expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')
|
||||
expect(await page.evaluate(() => window.sdkErrors)).toEqual([])
|
||||
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
|
||||
})
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const version of ['4.5.2', '5.2.1']) {
|
||||
test(`${core} core / DASH ${version}: explicit update reflects external SDK selection`, async ({ page }, testInfo) => {
|
||||
await openDash(page, version, core, 'candidate', testInfo)
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
|
||||
await page.evaluate(() => {
|
||||
const dash = window.art.dash
|
||||
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: false } } } })
|
||||
if (dash.setQualityFor)
|
||||
dash.setQualityFor('video', 1)
|
||||
else
|
||||
dash.setRepresentationForTypeById('video', '1')
|
||||
dash.setCurrentTrack(dash.getTracksFor('audio').find(track => track.lang === 'fr'))
|
||||
})
|
||||
await expect.poll(() => page.evaluate(() => window.art.video.videoHeight)).toBe(180)
|
||||
await page.evaluate(() => window.art.plugins.artplayerPluginDashControl.update())
|
||||
await expect(page.locator('.art-control-dash-quality .art-selector-value')).toHaveText('180p')
|
||||
await expect(page.locator('.art-control-dash-audio .art-selector-value')).toHaveText('fr')
|
||||
expect(await page.evaluate(() => ({ quality: window.art.setting.find('dash-quality').tooltip, audio: window.art.setting.find('dash-audio').tooltip }))).toEqual({ quality: '180p', audio: 'fr' })
|
||||
await page.evaluate(() => {
|
||||
window.art.dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } })
|
||||
window.art.plugins.artplayerPluginDashControl.update()
|
||||
})
|
||||
await expect(page.locator('.art-control-dash-quality .art-selector-value')).toHaveText('Auto')
|
||||
expect(await page.evaluate(() => window.sdkErrors)).toEqual([])
|
||||
})
|
||||
|
||||
test(`${core} core / DASH ${version}: actual source topology replacement and owned cleanup`, async ({ page }, testInfo) => {
|
||||
await openDash(page, version, core, 'candidate', testInfo)
|
||||
await expect(page.locator('.art-control-dash-audio .art-selector-value')).toHaveText('en')
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
|
||||
await page.evaluate(() => {
|
||||
const quality = window.art.controls.cache.get('dash-quality').option
|
||||
const audio = window.art.setting.find('dash-audio')
|
||||
// Registry update mutates the option object; retain the actual old callback.
|
||||
window.retainedQuality = { onSelect: quality.onSelect, item: quality.selector[0] }
|
||||
window.retainedAudio = { onSelect: audio.onSelect, item: audio.selector[1] }
|
||||
})
|
||||
await page.evaluate(() => window.art.switchUrl('/dash-fixture/video-only.mpd'))
|
||||
await expect(page.locator('.art-control-dash-audio')).toHaveCount(0)
|
||||
expect(await page.evaluate(() => Boolean(window.art.setting.find('dash-audio')))).toBe(false)
|
||||
expect(await page.evaluate(() => window.art.dash.getTracksFor('audio'))).toEqual([])
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
|
||||
await page.evaluate(() => {
|
||||
window.retainedQuality.onSelect(window.retainedQuality.item)
|
||||
window.retainedAudio.onSelect(window.retainedAudio.item)
|
||||
})
|
||||
expect(await page.evaluate(() => window.art.dash.getSettings().streaming.abr.autoSwitchBitrate.video)).toBe(true)
|
||||
await page.evaluate(() => window.art.switchUrl('/dash-fixture/single.mpd'))
|
||||
await expect(page.locator('.art-control-dash-quality .art-selector-item')).toHaveText(['90p', 'Auto'])
|
||||
await expect.poll(() => page.evaluate(() => window.art.video.videoHeight)).toBe(90)
|
||||
await page.evaluate(() => window.art.switchUrl('/dash-fixture/master.mpd'))
|
||||
await expect(page.locator('.art-control-dash-audio .art-selector-value')).toHaveText('en')
|
||||
await expect(page.locator('.art-control-dash-quality .art-selector-item')).toHaveText(['180p', '90p', 'Auto'])
|
||||
expect(await page.evaluate(() => window.sdkDestroyed)).toBe(3)
|
||||
expect(await page.evaluate(() => window.sdkErrors)).toEqual([])
|
||||
await page.evaluate(() => window.art.destroy(false))
|
||||
expect(await page.evaluate(() => window.sdkDestroyed)).toBe(4)
|
||||
await expect(page.locator('.art-control-dash-quality, .art-control-dash-audio')).toHaveCount(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,222 @@
|
||||
{
|
||||
"kind": "generated-dash-media",
|
||||
"version": "ffmpeg version 2025-11-12-git-6cdd2cbe32-full_build-www.gyan.dev Copyright (c) 2000-2025 the FFmpeg developers",
|
||||
"duration": 12,
|
||||
"videoHeights": [
|
||||
90,
|
||||
180
|
||||
],
|
||||
"audio": [
|
||||
{
|
||||
"lang": "en",
|
||||
"frequency": 440
|
||||
},
|
||||
{
|
||||
"lang": "fr",
|
||||
"frequency": 880
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
[
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"error",
|
||||
"-n",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"testsrc2=size=160x90:rate=24",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"testsrc2=size=320x180:rate=24",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"sine=frequency=440:sample_rate=48000",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"sine=frequency=880:sample_rate=48000",
|
||||
"-t",
|
||||
"12",
|
||||
"-map",
|
||||
"0:v",
|
||||
"-map",
|
||||
"1:v",
|
||||
"-map",
|
||||
"2:a",
|
||||
"-map",
|
||||
"3:a",
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-profile:v",
|
||||
"baseline",
|
||||
"-level:v",
|
||||
"3.0",
|
||||
"-b:v:0",
|
||||
"150k",
|
||||
"-b:v:1",
|
||||
"350k",
|
||||
"-g",
|
||||
"48",
|
||||
"-keyint_min",
|
||||
"48",
|
||||
"-sc_threshold",
|
||||
"0",
|
||||
"-c:a",
|
||||
"aac",
|
||||
"-b:a",
|
||||
"48k",
|
||||
"-ac",
|
||||
"2",
|
||||
"-metadata:s:a:0",
|
||||
"language=en",
|
||||
"-metadata:s:a:1",
|
||||
"language=fr",
|
||||
"-f",
|
||||
"dash",
|
||||
"-seg_duration",
|
||||
"2",
|
||||
"-use_template",
|
||||
"1",
|
||||
"-use_timeline",
|
||||
"1",
|
||||
"-adaptation_sets",
|
||||
"id=0,streams=0,1 id=1,streams=2 id=2,streams=3",
|
||||
"-init_seg_name",
|
||||
"init-$RepresentationID$.m4s",
|
||||
"-media_seg_name",
|
||||
"chunk-$RepresentationID$-$Number%05d$.m4s",
|
||||
"master.mpd"
|
||||
]
|
||||
],
|
||||
"files": {
|
||||
"chunk-0-00001.m4s": {
|
||||
"sha256": "0bc9f5c9bfc50526dfdc0241ac5d2da2332097d738450a41887bceb8d0b1f305",
|
||||
"bytes": 32029
|
||||
},
|
||||
"chunk-0-00002.m4s": {
|
||||
"sha256": "a3baefa3f68381a844419beabdee422dc4298c102a7b45a9da16d89b74f461fc",
|
||||
"bytes": 41742
|
||||
},
|
||||
"chunk-0-00003.m4s": {
|
||||
"sha256": "f2540e0a1871967a910e25567aa19ce9aaef002bba84f8cdda8ca9bbc8abd40e",
|
||||
"bytes": 36346
|
||||
},
|
||||
"chunk-0-00004.m4s": {
|
||||
"sha256": "21905f15e90b22cd868d55924d44723b9a02a7b1132384a60b526e49646e9e07",
|
||||
"bytes": 41226
|
||||
},
|
||||
"chunk-0-00005.m4s": {
|
||||
"sha256": "9d0cb0486da40f3a4e6819a5964d4db945305d328cce6ce2c21a60a57c413f42",
|
||||
"bytes": 35119
|
||||
},
|
||||
"chunk-0-00006.m4s": {
|
||||
"sha256": "f52fd4ecf9d0276261b183c6e44096ad2f28267a5f2a0a15ac65cec8705b605f",
|
||||
"bytes": 38357
|
||||
},
|
||||
"chunk-1-00001.m4s": {
|
||||
"sha256": "389d6f02741679ed7ef43f529bbec746b5e1b62a97519ca80be16f114e76845c",
|
||||
"bytes": 72180
|
||||
},
|
||||
"chunk-1-00002.m4s": {
|
||||
"sha256": "5cc737843b84a064acb65d985b2ea7ff151c26937f5bf48cacdf0f39c9e60122",
|
||||
"bytes": 94545
|
||||
},
|
||||
"chunk-1-00003.m4s": {
|
||||
"sha256": "2a1b56d88858f3d9b6070ab8645d878b12f8baa29c567b038d190f89c2f863e6",
|
||||
"bytes": 84303
|
||||
},
|
||||
"chunk-1-00004.m4s": {
|
||||
"sha256": "be7198ae2b2d04e27748fcbe8669734a7ead3a51c25fb7f1c9d981740bf5f92d",
|
||||
"bytes": 95672
|
||||
},
|
||||
"chunk-1-00005.m4s": {
|
||||
"sha256": "502e3dd1ce6ca85c442ba849e605a1b0cb1ca23472af6fe6e0dacaea8a38d5dc",
|
||||
"bytes": 82244
|
||||
},
|
||||
"chunk-1-00006.m4s": {
|
||||
"sha256": "34d6e27576dc3c834d0aff9e5925581a899c354c4fdd34fc639cc63f9807c62f",
|
||||
"bytes": 90139
|
||||
},
|
||||
"chunk-2-00001.m4s": {
|
||||
"sha256": "23eaaffa433ed1808f57b95e14b85fba15b2d7e8eea6c51fd02ce409dbccf7de",
|
||||
"bytes": 12437
|
||||
},
|
||||
"chunk-2-00002.m4s": {
|
||||
"sha256": "f40e15cb8d377dcd4eeac508493694e4a131fd4a0d970ff2636b1b40fc728f0a",
|
||||
"bytes": 12641
|
||||
},
|
||||
"chunk-2-00003.m4s": {
|
||||
"sha256": "e1a1f49359d23f460f9b0a32df5f5da7ba6d234fe59cc4a9c0a5f7e201fed643",
|
||||
"bytes": 12663
|
||||
},
|
||||
"chunk-2-00004.m4s": {
|
||||
"sha256": "a5b0625a33a62cf2b73414c92b7c9f6ac945650d3ff6dadaff26705d10920bf6",
|
||||
"bytes": 12657
|
||||
},
|
||||
"chunk-2-00005.m4s": {
|
||||
"sha256": "ed446e2eb99474765e9b0eb40728bd5d79eba79e74b5fa688a0dbddfb3f4b82a",
|
||||
"bytes": 12682
|
||||
},
|
||||
"chunk-2-00006.m4s": {
|
||||
"sha256": "6b969d834a87836f84eac8769fba2962c8303aacbb832f4710e642a371c6e38e",
|
||||
"bytes": 13008
|
||||
},
|
||||
"chunk-3-00001.m4s": {
|
||||
"sha256": "9171ffc0421c4dc0f874eb8801c2df0b4bf312981a8841455b13081cca1b0feb",
|
||||
"bytes": 12453
|
||||
},
|
||||
"chunk-3-00002.m4s": {
|
||||
"sha256": "da7a827ad08e9b48647f2bc6c90d5db1b53ef15965575d38b7e9facbddd37fc1",
|
||||
"bytes": 12644
|
||||
},
|
||||
"chunk-3-00003.m4s": {
|
||||
"sha256": "3d5f9fd99a21ce3d59f11416936d79252a12bc95560988f1abe4ccf802733ebc",
|
||||
"bytes": 12643
|
||||
},
|
||||
"chunk-3-00004.m4s": {
|
||||
"sha256": "eeca9b9a3f91c8491ad663cb5d686b168c530dc771d46c35ff5919b3a8b4ee37",
|
||||
"bytes": 12678
|
||||
},
|
||||
"chunk-3-00005.m4s": {
|
||||
"sha256": "747526362131944d696b09059ba0a19c106e363a30fa07cf7ec86081c4e277e8",
|
||||
"bytes": 12582
|
||||
},
|
||||
"chunk-3-00006.m4s": {
|
||||
"sha256": "5234fd26fd201536c839a1740d3bd57a5fe8c86e3e180f7f1e4c70cc41f30072",
|
||||
"bytes": 13077
|
||||
},
|
||||
"init-0.m4s": {
|
||||
"sha256": "d0a16341c48647e27452db83de88f88209dbec7e539e159e5057469d28980881",
|
||||
"bytes": 827
|
||||
},
|
||||
"init-1.m4s": {
|
||||
"sha256": "3e814e958e68e98f947883a2b7511a36fb06db127ea074ccd569af0200839948",
|
||||
"bytes": 828
|
||||
},
|
||||
"init-2.m4s": {
|
||||
"sha256": "866460ae9e8465b1473c3dbbc63f38416815d75d906790109575cf1de8332423",
|
||||
"bytes": 764
|
||||
},
|
||||
"init-3.m4s": {
|
||||
"sha256": "866460ae9e8465b1473c3dbbc63f38416815d75d906790109575cf1de8332423",
|
||||
"bytes": 764
|
||||
},
|
||||
"master.mpd": {
|
||||
"sha256": "6a4cb0a956861dee9aff1c592525c41fb4cf660be86b4c3892a91a46aeec53ef",
|
||||
"bytes": 2986
|
||||
},
|
||||
"single.mpd": {
|
||||
"sha256": "bdbd8a027281cefec1d4021d7e5a2731875188eb331612bc35eef75f265a700b",
|
||||
"bytes": 1227
|
||||
},
|
||||
"video-only.mpd": {
|
||||
"sha256": "b7f8d37c023810ecc38a2564d4644eda4b5a0b0dd2d10dfd39f70a4b49be0566",
|
||||
"bytes": 1622
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
||||
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
||||
type="static"
|
||||
mediaPresentationDuration="PT12.0S"
|
||||
maxSegmentDuration="PT2.0S"
|
||||
minBufferTime="PT4.0S">
|
||||
<ProgramInformation>
|
||||
</ProgramInformation>
|
||||
<ServiceDescription id="0">
|
||||
</ServiceDescription>
|
||||
<Period id="0" start="PT0.0S">
|
||||
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="24/1" maxWidth="320" maxHeight="180" par="16:9">
|
||||
<Representation id="0" mimeType="video/mp4" codecs="avc1.42c01e" bandwidth="150000" width="160" height="90" sar="1:1">
|
||||
<SegmentTemplate timescale="12288" initialization="init-$RepresentationID$.m4s" media="chunk-$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="24576" r="5" />
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
<Representation id="1" mimeType="video/mp4" codecs="avc1.42c01e" bandwidth="350000" width="320" height="180" sar="1:1">
|
||||
<SegmentTemplate timescale="12288" initialization="init-$RepresentationID$.m4s" media="chunk-$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="24576" r="5" />
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="1" contentType="audio" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" lang="en">
|
||||
<Representation id="2" mimeType="audio/mp4" codecs="mp4a.40.2" bandwidth="48000" audioSamplingRate="48000">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
|
||||
<SegmentTemplate timescale="48000" initialization="init-$RepresentationID$.m4s" media="chunk-$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="95232" />
|
||||
<S d="96256" r="3" />
|
||||
<S d="95744" />
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="2" contentType="audio" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" lang="fr">
|
||||
<Representation id="3" mimeType="audio/mp4" codecs="mp4a.40.2" bandwidth="48000" audioSamplingRate="48000">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
|
||||
<SegmentTemplate timescale="48000" initialization="init-$RepresentationID$.m4s" media="chunk-$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="95232" />
|
||||
<S d="96256" r="3" />
|
||||
<S d="95744" />
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
||||
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
||||
type="static"
|
||||
mediaPresentationDuration="PT12.0S"
|
||||
maxSegmentDuration="PT2.0S"
|
||||
minBufferTime="PT4.0S">
|
||||
<ProgramInformation>
|
||||
</ProgramInformation>
|
||||
<ServiceDescription id="0">
|
||||
</ServiceDescription>
|
||||
<Period id="0" start="PT0.0S">
|
||||
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="24/1" maxWidth="320" maxHeight="180" par="16:9">
|
||||
<Representation id="0" mimeType="video/mp4" codecs="avc1.42c01e" bandwidth="150000" width="160" height="90" sar="1:1">
|
||||
<SegmentTemplate timescale="12288" initialization="init-$RepresentationID$.m4s" media="chunk-$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="24576" r="5" />
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
|
||||
</AdaptationSet>
|
||||
|
||||
|
||||
</Period>
|
||||
</MPD>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
||||
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
||||
type="static"
|
||||
mediaPresentationDuration="PT12.0S"
|
||||
maxSegmentDuration="PT2.0S"
|
||||
minBufferTime="PT4.0S">
|
||||
<ProgramInformation>
|
||||
</ProgramInformation>
|
||||
<ServiceDescription id="0">
|
||||
</ServiceDescription>
|
||||
<Period id="0" start="PT0.0S">
|
||||
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="24/1" maxWidth="320" maxHeight="180" par="16:9">
|
||||
<Representation id="0" mimeType="video/mp4" codecs="avc1.42c01e" bandwidth="150000" width="160" height="90" sar="1:1">
|
||||
<SegmentTemplate timescale="12288" initialization="init-$RepresentationID$.m4s" media="chunk-$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="24576" r="5" />
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
<Representation id="1" mimeType="video/mp4" codecs="avc1.42c01e" bandwidth="350000" width="320" height="180" sar="1:1">
|
||||
<SegmentTemplate timescale="12288" initialization="init-$RepresentationID$.m4s" media="chunk-$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="24576" r="5" />
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
|
||||
|
||||
</Period>
|
||||
</MPD>
|
||||
Reference in new issue
Block a user