mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 05:26:15 -08:00
test(mediabunny): [PKG-MB-01] freeze published proxy and dependency contracts
This commit is contained in:
1 parent
1f93bb9ba1
commit
1cfd2e17f3
18 files changed
+1357
-7
No files matched your search
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"task": "PKG-MB-01",
|
||||
"status": "done",
|
||||
"scope": "Historical source/package/interface and dependency provenance, not real media or release acceptance",
|
||||
"checks": [
|
||||
{
|
||||
"command": "node --test refactor/scripts/mb-contract.test.mjs",
|
||||
"result": "pass",
|
||||
"tests": 19,
|
||||
"historicalArchiveMembers": 12,
|
||||
"frozenGitInputs": 17,
|
||||
"declaredOptionPaths": 25,
|
||||
"scope": "Historical package/interface, actual exports, type consumers and controlled media-method forwarding; not native media playback"
|
||||
},
|
||||
{
|
||||
"command": "focused eslint --no-fix",
|
||||
"result": "pass",
|
||||
"files": [
|
||||
"refactor/scripts/mb-contract.mjs",
|
||||
"refactor/scripts/mb-contract.test.mjs",
|
||||
"refactor/scripts/contracts.test.mjs",
|
||||
"test/helpers/mediabunny.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"command": "yarn ci:check",
|
||||
"result": "pass",
|
||||
"tests": {
|
||||
"unit": 1006,
|
||||
"engineering": 14,
|
||||
"baseline": 141,
|
||||
"mainTotal": 1161,
|
||||
"repeatedContractExecutions": 44
|
||||
},
|
||||
"strictProductionTypeScriptFiles": 290,
|
||||
"log": "refactor/.cache/mb01-ci-verified.log"
|
||||
},
|
||||
{
|
||||
"command": "production/demo/artifact hashes compared with frozen source",
|
||||
"result": "unchanged",
|
||||
"files": 17
|
||||
}
|
||||
],
|
||||
"limitations": [
|
||||
"Native MP4/WebM/HLS/Blob/Stream and AV synchronization remain MB-02/09.",
|
||||
"Historical bundled SDK exact version unknown; current installed SDK is recorded separately.",
|
||||
"License/source-notice packaging remains MB-10; no legal compliance claim.",
|
||||
"Canvas/Ambilight factory decision remains pending and is not decided by this task."
|
||||
],
|
||||
"parentCommit": "1f93bb9ba1f7329a348c1b0538473615e5278d7c",
|
||||
"failedAttempts": [
|
||||
{
|
||||
"log": "refactor/.cache/mb01-tests-first.log",
|
||||
"resolution": "Exact historical readonly-write behavior and TypeScript-version diagnostic codes retained; VM exception checked by name."
|
||||
},
|
||||
{
|
||||
"log": "refactor/.cache/mb01-ci.log",
|
||||
"resolution": "Generated contract-index.md was stale after adding two published versions; regenerated using contracts.mjs --write before the passing full CI."
|
||||
},
|
||||
{
|
||||
"log": "refactor/.cache/mb01-ci-final.log",
|
||||
"resolution": "Contract model assertion still expected fifteen published versions; updated to seventeen and explicitly asserted the two MediaBunny release identities before the final passing CI."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
# MediaBunny proxy 历史契约
|
||||
|
||||
来源:[发布与 Git 哈希](mb-release.json)、[实际产物表面及全部配置声明](mb-surface.json)。
|
||||
PKG-MB-01 只冻结可观察接口、来源和依赖/资源范围;真实媒体时序、音画同步及长期释放由
|
||||
PKG-MB-02/09 验证。以下标为源码观察的顺序,不等于浏览器已经通过。
|
||||
|
||||
## 发布范围与入口
|
||||
|
||||
| 来源 | 运行时主入口 | 类型 | 扩展能力 |
|
||||
| --- | --- | --- | --- |
|
||||
| npm 1.0.0 | CommonJS namespace.default;原生 ESM default | 可选 Option → 同步 initializer → HTMLCanvasElement | 不创建 art.mediabunny,没有三个 HLS 方法 |
|
||||
| npm 1.2.0 | CommonJS 直接工厂;原生 ESM default | 保持可选参数、Canvas 返回;增加 m3u8 | 创建 art.mediabunny,提供 HLS 状态与选轨 |
|
||||
| 冻结工作区 | 与 1.2.0 同代,单独冻结字节 | 当前声明单独冻结 | 不是对历史 tarball 的替代 |
|
||||
|
||||
两份实际 tarball 各6个文件:README、package.json、types 声明、main/legacy/ESM。
|
||||
没有独立 worker、WASM、source map 或许可证/通知成员;不据此推断 SDK 永远不会使用
|
||||
浏览器 worker 等能力。main/module/types/legacy、root/legacy exports 的原始值见 manifest。
|
||||
所有入口存在。1.0.0 关联 Git 核心5.3.1-beta.1,1.2.0关联5.4.1;这是提交关联,
|
||||
不是已测支持范围,也不是已验证的 npm 核心组合。
|
||||
|
||||
## 配置
|
||||
|
||||
完整25条顶层/嵌套配置路径与回调结构已记录在 mb-surface.json.options。
|
||||
|
||||
| 配置 | 默认或语义 | 边界 |
|
||||
| --- | --- | --- |
|
||||
| loadTimeout | 0;只接受有限数字作为超时 | 非正值不启动超时;成功后 timeout timer 的所有权待02/03验证 |
|
||||
| timeupdateInterval | 250 ms,使用 ?? | VideoEngine 发 timeupdate 的间隔 |
|
||||
| avSyncTolerance | 0.12 s,使用 ?? | 视频相对音频时钟容差 |
|
||||
| dropLateFrames | false,使用 ?? | 不因优化静默改变默认丢帧策略 |
|
||||
| poster | 空字符串 | 异步 Image 绘制;后续 poster setter 改 option,不代表已同步重设 VideoEngine |
|
||||
| source | 无 | 真值时优先,否则取 art.option.url;公开类型 string / Blob / ReadableStream<Uint8Array> |
|
||||
| preflightRange | false | 字符串非HLS源 HEAD;无 accept-ranges 或 none 发 RangeNotSupported error 后返回false;网络异常警告后继续 |
|
||||
| volume | 0.7,使用 ?? | 初始值不钳制;setter转数值并钳制0..1,同时取消muted |
|
||||
| muted | false,布尔转换 | setter布尔转换,发volumechange |
|
||||
| autoplay / loop | false | getter读option;setter为空实现,不能声称真正实现自动播放或循环 |
|
||||
| crossOrigin | 空字符串 | setter为空实现,不据此推断 fetch 的 CORS/凭据策略 |
|
||||
| m3u8 | 无,1.2.0新增 | 真值才安装loadedmetadata/restart监听 |
|
||||
| m3u8.quality / audio | 各含control、setting、title、auto、getName | control/setting默认不显示,title默认Quality/Audio,auto默认Auto |
|
||||
|
||||
工厂保留传入 option 对象的引用,没有克隆;同工厂初始化多个实例可共享该引用。
|
||||
quality.getName接收id/index/name/height/bitrate;audio.getName接收id/index/name/lang/language/bitrate。
|
||||
运行时状态还保留实际SDK track对象,当前公开配置回调声明未暴露track。
|
||||
|
||||
## Canvas 与 art.mediabunny
|
||||
|
||||
公开Result严格为HTMLCanvasElement;不能直接重标成HTMLVideoElement。1.2.0的
|
||||
art.mediabunny指向VideoShim实例,destroy回调删除该字段并调用shim.destroy。
|
||||
|
||||
入口枚举 shim 实例自身和直接原型的全部属性,为 Canvas 中不存在的属性添加可配置、
|
||||
可枚举的getter/setter;方法每次读取重新bind(shim),因此`canvas.play !== canvas.play`。
|
||||
已有Canvas属性/方法优先保留;枚举到的原生方法再绑定原Canvas,不将原生事件接口替换成shim。
|
||||
其结果是`canvas.setAttribute('src', url)`不等于`canvas.src = url`,Canvas监听器也不会
|
||||
自动收到shim发给ArtPlayer的媒体事件。浏览器中的完整属性集合仍要在02验证。
|
||||
|
||||
| 分组 | 成员与行为 |
|
||||
| --- | --- |
|
||||
| 来源/时间 | src读写保存输入,真值触发engine.load;currentSrc同源;currentTime写入Number(t)或0并调用异步seek;duration只读 |
|
||||
| 状态 | paused、playing、ended、seeking、readyState、networkState、error映射engine;playing由paused/ended计算 |
|
||||
| 媒体范围 | buffered、played、seekable是合成范围;无有效duration或end<=0为空;非空start/end不校验下标,buffered不是实际下载进度 |
|
||||
| 速度/音量 | playbackRate默认1,只拒绝NaN及<=0;volume setter钳制并取消静音;muted布尔转换;均转发engine并发对应事件 |
|
||||
| 播放 | play返回engine的Promise;pause/load返回undefined;currentTime/src属性赋值不返回底层Promise |
|
||||
| HLS(1.2.0) | getM3u8State返回Promise;switchM3u8Quality(id或'auto')、switchM3u8Audio(id或'auto')返回Promise |
|
||||
| 尺寸 | videoWidth、videoHeight读取engine;Canvas width/height仍是原生成员 |
|
||||
| 属性兼容 | poster可写修改option;autoplay/loop/crossOrigin只读option但setter不执行动作;controls=false、playsInline=true、preload='auto'、defaultMuted=false、defaultPlaybackRate=1,相应setter均无动作 |
|
||||
| 能力/帧 | canPlayType无条件'maybe',不是codec支持检测;requestVideoFrameCallback只是单次RAF,presentedFrames=0;cancelVideoFrameCallback调用cancelAnimationFrame |
|
||||
| 辅助方法 | setupEventForwarding、createTimeRanges、getBoundingClientRect、setAttribute、destroy;后两种DOM同名方法在Canvas侧仍原生优先 |
|
||||
| 意外可观察字段 | art、canvas、option、events、engine、_src、_volume、_muted、_playbackRate同样被转发;完整own/prototype清单见JSON。重构不得仅因以下划线开头就默认可以删除 |
|
||||
|
||||
readonly属性的Canvas转发setter存在,但底层shim无setter:实际1.0.0主产物静默忽略duration
|
||||
写入,1.2.0/冻结工作区主产物抛TypeError。这是已发布代际差异,不能简单标为统一旧行为。
|
||||
|
||||
## 输入与 HLS(当前冻结源码观察)
|
||||
|
||||
input.js集中检测`.m3u8`后缀,支持查询/片段且大小写不敏感;不嗅探无后缀URL内容。
|
||||
字符串/Blob/ReadableStream分别封装对应SDK Source,其余值原样传递,空值转null。
|
||||
Input选择HLS_FORMATS或ALL_FORMATS。先取主视频,再取其可配对主音频;无视频才取主音频。
|
||||
直播duration为Infinity;非直播优先metadata,null才computeDuration。SDK拒绝和无轨道路径待02测试。
|
||||
|
||||
HLS状态包含levels、audios、currentLevel、currentAudio、videoMode、audioMode;只有与当前视频
|
||||
可配对的音频进入状态列表。手动质量/音频通过SDK id选择,'auto'重新选主轨道;若旧搭档不能配对,
|
||||
补选可配对轨道并将搭档mode置auto。不能把mode名称当作已证明持续ABR自适应。
|
||||
|
||||
控件/设置名为mediabunny-quality、mediabunny-audio;按实际current id标记default,名称去重,
|
||||
质量按高度降序;音轨少于2隐藏。缺HLS状态或配置不启用时通过controls.remove/setting.remove清理。
|
||||
选中后await切轨,写notice,再await更新。并发更新/销毁后的晚回写留给07验证。
|
||||
|
||||
## 事件与异步边界(当前冻结源码观察)
|
||||
|
||||
| 触发 | 可观察事件和状态 |
|
||||
| --- | --- |
|
||||
| shim事件 | 自有EventTarget构造Event,写detail;按Artplayer.constructor.config.events逐项桥接video:名称;非DOM dispatchEvent |
|
||||
| load开始 | pause旧播放、dispose旧Input;networkState=2、readyState=0;两个零延迟timer分别发waiting、loadstart,不能假设始终早于异步metadata |
|
||||
| 元数据齐备 | readyState=1;loadedmetadata → durationchange → progress;无轨道/异步callback的次数待02复现 |
|
||||
| 加载完成 | readyState=4、networkState=1;loadeddata → canplay → canplaythrough → progress |
|
||||
| load失败/超时 | error={code:4,message}、networkState=3,发error;外层和performLoad内层都有捕获,不据此推断所有Input已释放 |
|
||||
| play/pause | play await audio后启动video,发play → playing;pause同步audio.pause/video.stop后发pause;ended重播先seek(0) |
|
||||
| seek | seeking=true;seeking → waiting,暂停并等待audio/video.seek;seeking=false后seeked,必要时恢复play |
|
||||
| 选轨 | 保留currentTime及是否播放,pause,seeking → waiting;重新load/seek后发metadata/duration/progress/data/canplay/canplaythrough/seeked,再恢复play |
|
||||
| 帧/音频调度 | VideoEngine还可发timeupdate、ended、pause、canplay、playing、waiting;AudioEngine也可发canplay、playing。一次性/重复事件不能只从主Engine判断 |
|
||||
| destroy | pause、disposeInput、audio.destroy、video.destroy;loadSeq未在destroy递增,未统一收回全部timer/RAF;准确竞态与释放证据待02 |
|
||||
|
||||
## 依赖与许可范围
|
||||
|
||||
1.0.0声明mediabunny ^1.27.3,1.2.0/当前声明^1.43.1。当前Yarn锁和安装版本1.56.1,
|
||||
其package.json为MPL-2.0,LICENSE为完整Mozilla Public License 2.0;冻结相应文件哈希。
|
||||
SDK清单另含两个@types依赖,不能把类型依赖自动计为播放产物中的JS。
|
||||
|
||||
实际历史ESM内含AudioBufferSink/CanvasSink实现,三个历史格式均未发现MPL/Source Code Form
|
||||
通知标记;两份归档均无LICENSE/NOTICE成员,README和包清单仅标MIT。这个事实不构成
|
||||
完整法律判断,但候选发布必须补齐实际内嵌依赖的来源、许可/通知和可获得的对应源码说明。
|
||||
历史归档没有构建依赖版本证明,关联Git无锁文件,因此内嵌SDK精确版本记为unknown;
|
||||
不能拿当前安装1.56.1冒充历史构建版本。由MB-LICENSE-01和PKG-MB-10继续关闭。
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-MB-01",
|
||||
"capturedAt": "2026-09-12T08:48:19.282Z",
|
||||
"registry": {
|
||||
"url": "https://registry.npmjs.org/artplayer-proxy-mediabunny",
|
||||
"versions": [
|
||||
"1.0.0",
|
||||
"1.2.0"
|
||||
],
|
||||
"distTags": {
|
||||
"latest": "1.2.0"
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"name": "artplayer-proxy-mediabunny",
|
||||
"version": "1.2.0",
|
||||
"tarball": "https://registry.npmjs.org/artplayer-proxy-mediabunny/-/artplayer-proxy-mediabunny-1.2.0.tgz",
|
||||
"integrity": "sha512-uSKkJFvd5Y2XxPPhwtPM5k9qq3xScbWnO/9fOfC8S+Liz8f8nT/iyA2eynU3maVcHZbJgA3PHZm0r6RRmSkefg==",
|
||||
"sha256": "acb016166059183917a6de2e03a65ad77b09997fc9fb62513d751a24467ca76c",
|
||||
"manifest": {
|
||||
"name": "artplayer-proxy-mediabunny",
|
||||
"version": "1.2.0",
|
||||
"description": "mediabunny proxy for ArtPlayer",
|
||||
"author": "Harvey Zhao <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer-proxy-mediabunny.d.ts",
|
||||
"import": "./dist/artplayer-proxy-mediabunny.mjs",
|
||||
"require": "./dist/artplayer-proxy-mediabunny.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer-proxy-mediabunny.d.ts",
|
||||
"import": "./dist/artplayer-proxy-mediabunny.legacy.js",
|
||||
"require": "./dist/artplayer-proxy-mediabunny.legacy.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer-proxy-mediabunny.js",
|
||||
"module": "./dist/artplayer-proxy-mediabunny.mjs",
|
||||
"types": "./types/artplayer-proxy-mediabunny.d.ts",
|
||||
"legacy": "./dist/artplayer-proxy-mediabunny.legacy.js",
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"mediabunny": "^1.43.1"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"package/README.md": "32377da36a37d7ddc2b85031645b3b2ee49fea7185c3cef1d0b674b52367d58f",
|
||||
"package/dist/artplayer-proxy-mediabunny.js": "7d088c0abe2f3c1e2857cb0547405ce1e5588630f28e484a64211fc675322bbd",
|
||||
"package/dist/artplayer-proxy-mediabunny.legacy.js": "327da5f1be221af6887efb4175c1b92e4e0c28838cae8107acca652a76bddccb",
|
||||
"package/dist/artplayer-proxy-mediabunny.mjs": "42f85d84456d1710a8b71640b1cb4f51203052692b84da9f401077f331dc057b",
|
||||
"package/package.json": "96b09ce19ea7914485aa9780329a0cfc43b18e38232ea1dd92d2e6dc27310055",
|
||||
"package/types/artplayer-proxy-mediabunny.d.ts": "af2083224d0760752d83eb2ac86b1dea5586455b81d47e4ba70f9f6a874a75d8"
|
||||
},
|
||||
"gitHead": "fc90d4a0df8fe4efaf2172ca7b04dd059eed8144",
|
||||
"publishedAt": "2026-05-04T05:27:32.743Z",
|
||||
"historicalCore": {
|
||||
"commit": "fc90d4a0df8fe4efaf2172ca7b04dd059eed8144",
|
||||
"file": "packages/artplayer/package.json",
|
||||
"version": "5.4.1",
|
||||
"sha256LF": "06cbc0de3f944dfc215ea1c4e7c3376e6352ee5d6f1f459828f45025c62f57f0",
|
||||
"scope": "Git association only, not a published core support range or proof of proxy capability"
|
||||
},
|
||||
"bundledDependencyVersion": null,
|
||||
"bundledDependencyVersionEvidence": "Published tarball does not provide dependency build provenance; Git tree contains no lockfile. The manifest range does not identify the embedded SDK version."
|
||||
},
|
||||
"previous": [
|
||||
{
|
||||
"name": "artplayer-proxy-mediabunny",
|
||||
"version": "1.0.0",
|
||||
"tarball": "https://registry.npmjs.org/artplayer-proxy-mediabunny/-/artplayer-proxy-mediabunny-1.0.0.tgz",
|
||||
"integrity": "sha512-i9ot2ZbGqviO+BdcJJS0BqgfBmXBOCt2EuRJJBrp0QArCbXkjzOxRutSn4kIUfTWnLcazgego4on1HPkt5ZE9w==",
|
||||
"sha256": "d62f3ad75d07abbc8ca597846e213f34abd15b3742f7e87f294800fdef2e8962",
|
||||
"manifest": {
|
||||
"name": "artplayer-proxy-mediabunny",
|
||||
"version": "1.0.0",
|
||||
"description": "mediabunny proxy for ArtPlayer",
|
||||
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer-proxy-mediabunny.d.ts",
|
||||
"import": "./dist/artplayer-proxy-mediabunny.mjs",
|
||||
"require": "./dist/artplayer-proxy-mediabunny.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer-proxy-mediabunny.d.ts",
|
||||
"import": "./dist/artplayer-proxy-mediabunny.legacy.js",
|
||||
"require": "./dist/artplayer-proxy-mediabunny.legacy.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer-proxy-mediabunny.js",
|
||||
"module": "./dist/artplayer-proxy-mediabunny.mjs",
|
||||
"types": "./types/artplayer-proxy-mediabunny.d.ts",
|
||||
"legacy": "./dist/artplayer-proxy-mediabunny.legacy.js",
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"dependencies": {
|
||||
"mediabunny": "^1.27.3"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"package/README.md": "5cab1501e0a724fa43bd54ce4aa61eb6c43b01411484c370a6a2054accdd5145",
|
||||
"package/dist/artplayer-proxy-mediabunny.js": "7331465d8c7a6575505b9d3fd45986c5d3d9cb3839b2620ad5a52c581199562b",
|
||||
"package/dist/artplayer-proxy-mediabunny.legacy.js": "370b3340c2bc14912ea1f64de9e9d34aa917e6620f027733dd23e7dc0b6d4d05",
|
||||
"package/dist/artplayer-proxy-mediabunny.mjs": "548adec34185ec1619bbc602c06cc8a8e0ed3c4752494f1f623aa0ec1cf2c2d7",
|
||||
"package/package.json": "7a05c5110ee7468406ca2bdcff676f8c8c9837629310e008a746ab29523745b3",
|
||||
"package/types/artplayer-proxy-mediabunny.d.ts": "d88bb80cd47603e35ca12bbf3311a7cd678b906d9cd977b73e89d7cb5a7d3075"
|
||||
},
|
||||
"gitHead": "4a0fbda3c4cd2a1b223f3172697ac98a14ddb7b6",
|
||||
"publishedAt": "2026-01-01T11:49:03.448Z",
|
||||
"historicalCore": {
|
||||
"commit": "4a0fbda3c4cd2a1b223f3172697ac98a14ddb7b6",
|
||||
"file": "packages/artplayer/package.json",
|
||||
"version": "5.3.1-beta.1",
|
||||
"sha256LF": "e1a0a390ec10cd8c1c12006ac6515414ae98a964300dd3e9c288377def948497",
|
||||
"scope": "Git association only, not a published core support range or proof of proxy capability"
|
||||
},
|
||||
"bundledDependencyVersion": null,
|
||||
"bundledDependencyVersionEvidence": "Published tarball does not provide dependency build provenance; Git tree contains no lockfile. The manifest range does not identify the embedded SDK version."
|
||||
}
|
||||
],
|
||||
"sourceCommit": "1f93bb9ba1f7329a348c1b0538473615e5278d7c",
|
||||
"source": {
|
||||
"packages/artplayer-proxy-mediabunny/src/AudioEngine.js": "910d6831372e4895d55dec78010f281a199bc88f99735630a5a3b621f192ac35",
|
||||
"packages/artplayer-proxy-mediabunny/src/EventTarget.js": "c1854510af5a37301b4bd3b1fbcc28f7e0f105f50cb67324c5c05a76f2d054fb",
|
||||
"packages/artplayer-proxy-mediabunny/src/index.js": "4e1be8937908ff6e6ef8a4e863e22b4468312cf31d66ac299322e475757773bb",
|
||||
"packages/artplayer-proxy-mediabunny/src/input.js": "4341c27a8d8ebc77334ac96fc8cec11fc28e369e115eb690c6aeb0a352863d81",
|
||||
"packages/artplayer-proxy-mediabunny/src/m3u8.js": "4639f09e0c743dc2b24078f48ca1e58eaaedaa3f5e6b863583cbd4bc4a7d3142",
|
||||
"packages/artplayer-proxy-mediabunny/src/MediaBunnyEngine.js": "3f847bd1e8dfe5334e55def87862af7412af0204691c275293758cff95950189",
|
||||
"packages/artplayer-proxy-mediabunny/src/VideoEngine.js": "6b73dd5867a5640b68f801b397ace36bb6289e50cca176bfc9288d71f67e6e0b",
|
||||
"packages/artplayer-proxy-mediabunny/src/VideoShim.js": "f01c954e8a98fb70138dded78b99eb2765f5cd1e8a98dafbb35ffbf8f7741dbb",
|
||||
"packages/artplayer-proxy-mediabunny/types/artplayer-proxy-mediabunny.d.ts": "af2083224d0760752d83eb2ac86b1dea5586455b81d47e4ba70f9f6a874a75d8",
|
||||
"packages/artplayer-proxy-mediabunny/package.json": "96b09ce19ea7914485aa9780329a0cfc43b18e38232ea1dd92d2e6dc27310055",
|
||||
"packages/artplayer-proxy-mediabunny/README.md": "32377da36a37d7ddc2b85031645b3b2ee49fea7185c3cef1d0b674b52367d58f",
|
||||
"packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.js": "7d088c0abe2f3c1e2857cb0547405ce1e5588630f28e484a64211fc675322bbd",
|
||||
"packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.legacy.js": "327da5f1be221af6887efb4175c1b92e4e0c28838cae8107acca652a76bddccb",
|
||||
"packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.mjs": "42f85d84456d1710a8b71640b1cb4f51203052692b84da9f401077f331dc057b",
|
||||
"docs/assets/example/mediabunny.js": "dad4cecb76b2e5ce9164a4da715793911123e8fd2bc4b93f4bcc86a7053bad7d",
|
||||
"packages/artplayer-plugin-hls-control/src/audio.svg": "dbee5ce7487c9f8ec927e5286188cf9439345bbe947b6c06c32c07193e01e0be",
|
||||
"packages/artplayer-plugin-hls-control/src/quality.svg": "4666a4c01764422f9008e442f5a889ff9691bb0386083d673943a0718f4d680c"
|
||||
},
|
||||
"dependency": {
|
||||
"name": "mediabunny",
|
||||
"version": "1.56.1",
|
||||
"declaredRange": "^1.43.1",
|
||||
"manifestLicense": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@types/dom-mediacapture-transform": "^0.1.11",
|
||||
"@types/dom-webcodecs": "0.1.13"
|
||||
},
|
||||
"files": {
|
||||
"package.json": "809d14362055f6db3c17dd6dd43d7b913fc4daf4ac4f4179f29128eec866d868",
|
||||
"LICENSE": "3f3d9e0024b1921b067d6f7f88deb4a60cbe7a78e76c64e3f1d7fc3b779b9d04",
|
||||
"src/index.ts": "ba7672de9670183cd7c9965b844cbc4e134b9daa7aded6e0cf1efa0a5d736d78",
|
||||
"dist/modules/src/index.js": "9236aa229772332a4f5ed04e453d8794a84311468012a9dd0de78e35301d39c0"
|
||||
},
|
||||
"scope": "Installed and Yarn-locked dependency provenance for the upcoming candidate build only, never claimed as the SDK version embedded in historical bundles"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
{
|
||||
"task": "PKG-MB-01",
|
||||
"scope": "Actual historical main bundles instantiated without a media source against a controlled canvas and ArtPlayer event host. Canvas native members win; full browser descriptor variations and native media events are MB-02/09, not this snapshot.",
|
||||
"records": [
|
||||
{
|
||||
"name": "published-1.2.0",
|
||||
"exported": "function",
|
||||
"artMediabunny": true,
|
||||
"canvasKeys": [
|
||||
"width",
|
||||
"height",
|
||||
"style",
|
||||
"getContext",
|
||||
"addEventListener",
|
||||
"removeEventListener",
|
||||
"getBoundingClientRect",
|
||||
"setAttribute",
|
||||
"art",
|
||||
"canvas",
|
||||
"option",
|
||||
"events",
|
||||
"engine",
|
||||
"_src",
|
||||
"_volume",
|
||||
"_muted",
|
||||
"_playbackRate",
|
||||
"setupEventForwarding",
|
||||
"src",
|
||||
"currentSrc",
|
||||
"currentTime",
|
||||
"duration",
|
||||
"buffered",
|
||||
"played",
|
||||
"seekable",
|
||||
"createTimeRanges",
|
||||
"paused",
|
||||
"playing",
|
||||
"ended",
|
||||
"seeking",
|
||||
"readyState",
|
||||
"networkState",
|
||||
"error",
|
||||
"playbackRate",
|
||||
"volume",
|
||||
"muted",
|
||||
"play",
|
||||
"pause",
|
||||
"load",
|
||||
"getM3u8State",
|
||||
"switchM3u8Quality",
|
||||
"switchM3u8Audio",
|
||||
"videoWidth",
|
||||
"videoHeight",
|
||||
"poster",
|
||||
"autoplay",
|
||||
"loop",
|
||||
"controls",
|
||||
"playsInline",
|
||||
"crossOrigin",
|
||||
"preload",
|
||||
"defaultMuted",
|
||||
"defaultPlaybackRate",
|
||||
"canPlayType",
|
||||
"requestVideoFrameCallback",
|
||||
"cancelVideoFrameCallback",
|
||||
"destroy"
|
||||
],
|
||||
"shimOwn": [
|
||||
"art",
|
||||
"canvas",
|
||||
"option",
|
||||
"events",
|
||||
"engine",
|
||||
"_src",
|
||||
"_volume",
|
||||
"_muted",
|
||||
"_playbackRate"
|
||||
],
|
||||
"shimPrototype": [
|
||||
"constructor",
|
||||
"setupEventForwarding",
|
||||
"addEventListener",
|
||||
"removeEventListener",
|
||||
"src",
|
||||
"currentSrc",
|
||||
"currentTime",
|
||||
"duration",
|
||||
"buffered",
|
||||
"played",
|
||||
"seekable",
|
||||
"createTimeRanges",
|
||||
"paused",
|
||||
"playing",
|
||||
"ended",
|
||||
"seeking",
|
||||
"readyState",
|
||||
"networkState",
|
||||
"error",
|
||||
"playbackRate",
|
||||
"volume",
|
||||
"muted",
|
||||
"play",
|
||||
"pause",
|
||||
"load",
|
||||
"getM3u8State",
|
||||
"switchM3u8Quality",
|
||||
"switchM3u8Audio",
|
||||
"videoWidth",
|
||||
"videoHeight",
|
||||
"poster",
|
||||
"autoplay",
|
||||
"loop",
|
||||
"controls",
|
||||
"playsInline",
|
||||
"crossOrigin",
|
||||
"preload",
|
||||
"defaultMuted",
|
||||
"defaultPlaybackRate",
|
||||
"canPlayType",
|
||||
"getBoundingClientRect",
|
||||
"requestVideoFrameCallback",
|
||||
"cancelVideoFrameCallback",
|
||||
"setAttribute",
|
||||
"destroy"
|
||||
],
|
||||
"defaults": {
|
||||
"volume": 0.7,
|
||||
"muted": false,
|
||||
"playbackRate": 1,
|
||||
"autoplay": false,
|
||||
"loop": false,
|
||||
"poster": "",
|
||||
"crossOrigin": "",
|
||||
"src": null,
|
||||
"paused": true,
|
||||
"readyState": 0,
|
||||
"networkState": 0,
|
||||
"preload": "auto",
|
||||
"playsInline": true,
|
||||
"controls": false,
|
||||
"defaultMuted": false,
|
||||
"defaultPlaybackRate": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "published-1.0.0",
|
||||
"exported": "object",
|
||||
"artMediabunny": false,
|
||||
"canvasKeys": [
|
||||
"width",
|
||||
"height",
|
||||
"style",
|
||||
"getContext",
|
||||
"addEventListener",
|
||||
"removeEventListener",
|
||||
"getBoundingClientRect",
|
||||
"setAttribute",
|
||||
"art",
|
||||
"canvas",
|
||||
"option",
|
||||
"events",
|
||||
"engine",
|
||||
"_src",
|
||||
"_volume",
|
||||
"_muted",
|
||||
"_playbackRate",
|
||||
"setupEventForwarding",
|
||||
"src",
|
||||
"currentSrc",
|
||||
"currentTime",
|
||||
"duration",
|
||||
"buffered",
|
||||
"played",
|
||||
"seekable",
|
||||
"createTimeRanges",
|
||||
"paused",
|
||||
"playing",
|
||||
"ended",
|
||||
"seeking",
|
||||
"readyState",
|
||||
"networkState",
|
||||
"error",
|
||||
"playbackRate",
|
||||
"volume",
|
||||
"muted",
|
||||
"play",
|
||||
"pause",
|
||||
"load",
|
||||
"videoWidth",
|
||||
"videoHeight",
|
||||
"poster",
|
||||
"autoplay",
|
||||
"loop",
|
||||
"controls",
|
||||
"playsInline",
|
||||
"crossOrigin",
|
||||
"preload",
|
||||
"defaultMuted",
|
||||
"defaultPlaybackRate",
|
||||
"canPlayType",
|
||||
"requestVideoFrameCallback",
|
||||
"cancelVideoFrameCallback",
|
||||
"destroy"
|
||||
],
|
||||
"shimOwn": null,
|
||||
"shimPrototype": null,
|
||||
"defaults": {
|
||||
"volume": 0.7,
|
||||
"muted": false,
|
||||
"playbackRate": 1,
|
||||
"autoplay": false,
|
||||
"loop": false,
|
||||
"poster": "",
|
||||
"crossOrigin": "",
|
||||
"src": null,
|
||||
"paused": true,
|
||||
"readyState": 0,
|
||||
"networkState": 0,
|
||||
"preload": "auto",
|
||||
"playsInline": true,
|
||||
"controls": false,
|
||||
"defaultMuted": false,
|
||||
"defaultPlaybackRate": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "frozen-workspace",
|
||||
"exported": "function",
|
||||
"artMediabunny": true,
|
||||
"canvasKeys": [
|
||||
"width",
|
||||
"height",
|
||||
"style",
|
||||
"getContext",
|
||||
"addEventListener",
|
||||
"removeEventListener",
|
||||
"getBoundingClientRect",
|
||||
"setAttribute",
|
||||
"art",
|
||||
"canvas",
|
||||
"option",
|
||||
"events",
|
||||
"engine",
|
||||
"_src",
|
||||
"_volume",
|
||||
"_muted",
|
||||
"_playbackRate",
|
||||
"setupEventForwarding",
|
||||
"src",
|
||||
"currentSrc",
|
||||
"currentTime",
|
||||
"duration",
|
||||
"buffered",
|
||||
"played",
|
||||
"seekable",
|
||||
"createTimeRanges",
|
||||
"paused",
|
||||
"playing",
|
||||
"ended",
|
||||
"seeking",
|
||||
"readyState",
|
||||
"networkState",
|
||||
"error",
|
||||
"playbackRate",
|
||||
"volume",
|
||||
"muted",
|
||||
"play",
|
||||
"pause",
|
||||
"load",
|
||||
"getM3u8State",
|
||||
"switchM3u8Quality",
|
||||
"switchM3u8Audio",
|
||||
"videoWidth",
|
||||
"videoHeight",
|
||||
"poster",
|
||||
"autoplay",
|
||||
"loop",
|
||||
"controls",
|
||||
"playsInline",
|
||||
"crossOrigin",
|
||||
"preload",
|
||||
"defaultMuted",
|
||||
"defaultPlaybackRate",
|
||||
"canPlayType",
|
||||
"requestVideoFrameCallback",
|
||||
"cancelVideoFrameCallback",
|
||||
"destroy"
|
||||
],
|
||||
"shimOwn": [
|
||||
"art",
|
||||
"canvas",
|
||||
"option",
|
||||
"events",
|
||||
"engine",
|
||||
"_src",
|
||||
"_volume",
|
||||
"_muted",
|
||||
"_playbackRate"
|
||||
],
|
||||
"shimPrototype": [
|
||||
"constructor",
|
||||
"setupEventForwarding",
|
||||
"addEventListener",
|
||||
"removeEventListener",
|
||||
"src",
|
||||
"currentSrc",
|
||||
"currentTime",
|
||||
"duration",
|
||||
"buffered",
|
||||
"played",
|
||||
"seekable",
|
||||
"createTimeRanges",
|
||||
"paused",
|
||||
"playing",
|
||||
"ended",
|
||||
"seeking",
|
||||
"readyState",
|
||||
"networkState",
|
||||
"error",
|
||||
"playbackRate",
|
||||
"volume",
|
||||
"muted",
|
||||
"play",
|
||||
"pause",
|
||||
"load",
|
||||
"getM3u8State",
|
||||
"switchM3u8Quality",
|
||||
"switchM3u8Audio",
|
||||
"videoWidth",
|
||||
"videoHeight",
|
||||
"poster",
|
||||
"autoplay",
|
||||
"loop",
|
||||
"controls",
|
||||
"playsInline",
|
||||
"crossOrigin",
|
||||
"preload",
|
||||
"defaultMuted",
|
||||
"defaultPlaybackRate",
|
||||
"canPlayType",
|
||||
"getBoundingClientRect",
|
||||
"requestVideoFrameCallback",
|
||||
"cancelVideoFrameCallback",
|
||||
"setAttribute",
|
||||
"destroy"
|
||||
],
|
||||
"defaults": {
|
||||
"volume": 0.7,
|
||||
"muted": false,
|
||||
"playbackRate": 1,
|
||||
"autoplay": false,
|
||||
"loop": false,
|
||||
"poster": "",
|
||||
"crossOrigin": "",
|
||||
"src": null,
|
||||
"paused": true,
|
||||
"readyState": 0,
|
||||
"networkState": 0,
|
||||
"preload": "auto",
|
||||
"playsInline": true,
|
||||
"controls": false,
|
||||
"defaultMuted": false,
|
||||
"defaultPlaybackRate": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"name": "m3u8",
|
||||
"optional": true,
|
||||
"type": "{\n quality?: {\n control?: boolean\n setting?: boolean\n title?: string\n auto?: string\n getName?: (level: {\n id: number\n index: number\n name: null | string\n height: number\n bitrate: number\n }) => string\n }\n audio?: {\n control?: boolean\n setting?: boolean\n title?: string\n auto?: string\n getName?: (track: {\n id: number\n index: number\n name: null | string\n lang: string\n language: string\n bitrate: number\n }) => string\n }\n }"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.quality",
|
||||
"optional": true,
|
||||
"type": "{\n control?: boolean\n setting?: boolean\n title?: string\n auto?: string\n getName?: (level: {\n id: number\n index: number\n name: null | string\n height: number\n bitrate: number\n }) => string\n }"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.quality.control",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.quality.setting",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.quality.title",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.quality.auto",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.quality.getName",
|
||||
"optional": true,
|
||||
"type": "(level: {\n id: number\n index: number\n name: null | string\n height: number\n bitrate: number\n }) => string"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.audio",
|
||||
"optional": true,
|
||||
"type": "{\n control?: boolean\n setting?: boolean\n title?: string\n auto?: string\n getName?: (track: {\n id: number\n index: number\n name: null | string\n lang: string\n language: string\n bitrate: number\n }) => string\n }"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.audio.control",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.audio.setting",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.audio.title",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.audio.auto",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "m3u8.audio.getName",
|
||||
"optional": true,
|
||||
"type": "(track: {\n id: number\n index: number\n name: null | string\n lang: string\n language: string\n bitrate: number\n }) => string"
|
||||
},
|
||||
{
|
||||
"name": "loadTimeout",
|
||||
"optional": true,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"name": "timeupdateInterval",
|
||||
"optional": true,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"name": "avSyncTolerance",
|
||||
"optional": true,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"name": "dropLateFrames",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "poster",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"optional": true,
|
||||
"type": "string | Blob | ReadableStream<Uint8Array>"
|
||||
},
|
||||
{
|
||||
"name": "preflightRange",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "volume",
|
||||
"optional": true,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"name": "muted",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "autoplay",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "loop",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "crossOrigin",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# PKG-MB-01 MediaBunny proxy 契约冻结
|
||||
|
||||
核实真实发布只有1.0.0、1.2.0,两包共12归档成员;冻结17个Git输入(8源码、声明/清单/README、
|
||||
三产物、demo和两份共享SVG),记录归档完整性及逐文件哈希。Git关联核心仅作来源关联,不当支持范围。
|
||||
|
||||
冻结3份实际主产物的可观察表面、25条声明配置路径,补充[完整契约](../baselines/mb-contract.md)
|
||||
和[后续验证入口](../mb-validation.md)。1.0.0没有art.mediabunny/HLS方法;1.2.0新增这些能力。
|
||||
原生Canvas成员优先、动态bind、伪TimeRanges、RAF式frame callback、配置引用和setter的
|
||||
实际限制均有正常接口测试或明确源码观察标记。真实解码、时序、音画同步和清理仍由02以后验证。
|
||||
|
||||
19项专项测试覆盖归档/SDK来源、许可文件事实、两代编译器与完整工厂替换、实际main/legacy/global/
|
||||
原生ESM、三份主产物的表面、setter、事件桥、转发和RAF。首次观察保留在mb01-tests-first.log:
|
||||
旧TS未知字段报2345而新TS报2353;VM错误需按name匹配;1.0.0只读写入静默而1.2.0抛TypeError。
|
||||
按版本建立精确对照,没有为通过测试改动历史实现。
|
||||
|
||||
历史包声明^1.27.3/^1.43.1;当前Yarn安装1.56.1有MPL-2.0清单和完整LICENSE。实际历史bundle包含
|
||||
SDK实现但无MPL通知标记/许可证成员,且不能确认历史内嵌SDK精确版本。记录MB-LICENSE-01,
|
||||
不把根MIT或当前安装版本当作历史产物许可证明;分发阶段须补实际来源/通知并验证归档。
|
||||
|
||||
本步骤未修改生产源码、声明或产物,不需要重建/浏览器验收。最终检查和完成状态以
|
||||
[验证记录](../baselines/mb-contract-validation.json)及独立完成提交为准。
|
||||
|
||||
最终完整CI通过1161项(1006单元、14工程、141基线),另44重复契约;290生产TS严格检查。
|
||||
第一次CI发现新增发布版本后的生成契约索引过期,使用contracts.mjs --write更新后全量重跑通过。
|
||||
最终17个生产/demo/产物文件哈希与冻结输入一致,未以重建掩盖历史行为。
|
||||
@@ -324,3 +324,5 @@
|
||||
- PUB-artplayer-plugin-document-pip-1.0.2: artplayer-plugin-document-pip@1.0.2 (published-baseline);[来源](../refactor/baselines/dpip-release.json) JSON pointer `/previous/0`。
|
||||
- PUB-artplayer-plugin-document-pip-1.0.1: artplayer-plugin-document-pip@1.0.1 (published-baseline);[来源](../refactor/baselines/dpip-release.json) JSON pointer `/previous/1`。
|
||||
- PUB-artplayer-plugin-document-pip-1.0.0: artplayer-plugin-document-pip@1.0.0 (published-baseline);[来源](../refactor/baselines/dpip-release.json) JSON pointer `/previous/2`。
|
||||
- PUB-artplayer-proxy-mediabunny-1.2.0: artplayer-proxy-mediabunny@1.2.0 (published-baseline);[来源](../refactor/baselines/mb-release.json) JSON pointer `/release`。
|
||||
- PUB-artplayer-proxy-mediabunny-1.0.0: artplayer-proxy-mediabunny@1.0.0 (published-baseline);[来源](../refactor/baselines/mb-release.json) JSON pointer `/previous/0`。
|
||||
@@ -479,7 +479,9 @@
|
||||
"API-12": "PKG-MB-02"
|
||||
},
|
||||
"versionIds": [
|
||||
"WS-artplayer-proxy-mediabunny"
|
||||
"WS-artplayer-proxy-mediabunny",
|
||||
"PUB-artplayer-proxy-mediabunny-1.2.0",
|
||||
"PUB-artplayer-proxy-mediabunny-1.0.0"
|
||||
],
|
||||
"supportWindow": "not-inferred-from-baseline",
|
||||
"scopeReview": "Each public member, callback, option, event and capability requires package contract review; family ownership alone is not exhaustive coverage"
|
||||
@@ -862,6 +864,24 @@
|
||||
"file": "refactor/baselines/dpip-release.json",
|
||||
"pointer": "/previous/2",
|
||||
"archiveSha256": "3b0164391171c817ab511f740205732e65db0e8f1d65d63f0d24d8eef4251573"
|
||||
},
|
||||
{
|
||||
"id": "PUB-artplayer-proxy-mediabunny-1.2.0",
|
||||
"package": "artplayer-proxy-mediabunny",
|
||||
"kind": "published-baseline",
|
||||
"version": "1.2.0",
|
||||
"file": "refactor/baselines/mb-release.json",
|
||||
"pointer": "/release",
|
||||
"archiveSha256": "acb016166059183917a6de2e03a65ad77b09997fc9fb62513d751a24467ca76c"
|
||||
},
|
||||
{
|
||||
"id": "PUB-artplayer-proxy-mediabunny-1.0.0",
|
||||
"package": "artplayer-proxy-mediabunny",
|
||||
"kind": "published-baseline",
|
||||
"version": "1.0.0",
|
||||
"file": "refactor/baselines/mb-release.json",
|
||||
"pointer": "/previous/0",
|
||||
"archiveSha256": "d62f3ad75d07abbc8ca597846e213f34abd15b3742f7e87f294800fdef2e8962"
|
||||
}
|
||||
],
|
||||
"cases": [
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# MediaBunny proxy 验证入口
|
||||
|
||||
开始改动前读取[冻结契约](baselines/mb-contract.md)、[接口快照](baselines/mb-surface.json)、
|
||||
[归档/源码哈希](baselines/mb-release.json)。当前阶段为PKG-MB-01;不得把接口基线称为真实媒体验收。
|
||||
|
||||
## 可重复命令
|
||||
|
||||
使用仓库固定Node 24.21.0、Yarn 1.22.22:
|
||||
|
||||
```sh
|
||||
node refactor/scripts/mb-contract.mjs
|
||||
node --test refactor/scripts/mb-contract.test.mjs
|
||||
yarn ci:check
|
||||
```
|
||||
|
||||
归档缺失时仅从固定npm URL下载,并验证SHA-512/SHA-256及逐文件哈希。冻结工作区从明确Git
|
||||
提交读取,后续迁移不能修改冻结来源。已安装SDK校验来自Yarn锁及原始文件,仅描述候选工具链。
|
||||
|
||||
历史测试使用实际main/legacy/ESM;受控Canvas与ArtPlayer事件宿主只测公开接口、方法绑定、
|
||||
属性写入、默认值和事件桥。空媒体源不会构造真实AudioContext或解码,SDK存在不等于SDK播放已验收。
|
||||
play/load/seek转发测试刻意截获engine方法;不得宣传为真实播放测试。
|
||||
|
||||
## 后续顺序
|
||||
|
||||
1. MB-02建立真实MP4/WebM/HLS/Blob/Stream和失败样本;三浏览器能力分别记录。
|
||||
复现pending load/destroy、超时后释放、无轨道、并发seek/选轨、metadata重复与晚帧/音频回调。
|
||||
2. MB-03输入/取消,MB-04事件/状态协调,MB-05帧调度,MB-06音频时钟,MB-07配对轨道/控件。
|
||||
按已批准计划分步迁移,不等到MB-08才一次性堆积全部源码修改。
|
||||
3. MB-08完成严格TS和公开声明兼容矩阵;保留HTMLCanvasElement精确Result、可选工厂和全工厂
|
||||
替换用例,避免重演FACTORY-TYPE-01。1.0.0 namespace.default与1.2.0直接导出分别验证。
|
||||
4. MB-09新旧核心、AV sync、长播放、原生DPiP和浏览器组合;MB-10实际打包安装、许可来源/通知和8082示例。
|
||||
|
||||
Chrome连接不可用时可使用内置浏览器;CLI自动化也按现有Playwright三引擎流程执行。
|
||||
保留report.json及整个results目录。受控输入不能替代真实解码,iframe不能替代原生Document PiP。
|
||||
VAST脚本VPN豁免不适用于本包。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 118 / doing 7 / blocked 0 / done 97 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 117 / doing 7 / blocked 0 / done 98 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -329,7 +329,7 @@
|
||||
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| PKG-MB-01 | artplayer-proxy-mediabunny<br>核对 shim 与媒体契约 | BASE-05 | 全部 option、art.mediabunny、canvas/shim 属性及事件表 | 公开暴露成员和实际发布依赖/资源范围明确;核实 third-party.json 所记录依赖 MPL-2.0 元数据与实际 bundle 的许可/通知范围,不从根 MIT 推断 | H | todo |
|
||||
| PKG-MB-01 | artplayer-proxy-mediabunny<br>核对 shim 与媒体契约 | BASE-05 | 全部 option、art.mediabunny、canvas/shim 属性及事件表 | 公开暴露成员和实际发布依赖/资源范围明确;核实 third-party.json 所记录依赖 MPL-2.0 元数据与实际 bundle 的许可/通知范围,不从根 MIT 推断 | H | done |
|
||||
| PKG-MB-02 | artplayer-proxy-mediabunny<br>建立真实媒体与事件基线 | PKG-MB-01, ENG-05, ENG-08 | MP4/WebM/HLS/Blob/Stream、seek/错误/轨道切换样本 | ready 事件顺序、音画同步、超时和释放可重跑 | H | todo |
|
||||
| PKG-MB-03 | artplayer-proxy-mediabunny<br>整理 input 与加载取消 | PKG-MB-02, CORE-09 | input.js 源检测、输入资源、Range 与取消 | HLS 检测在 input 边界,旧来源类型和超时保持 | H | todo |
|
||||
| PKG-MB-04 | artplayer-proxy-mediabunny<br>整理 VideoShim/EventTarget/Engine 协调 | PKG-MB-03, CORE-06, CORE-11 | shim 描述符、事件桥、加载状态和操作隔离 | 公开属性/同步异步返回与事件重入顺序保持 | H | todo |
|
||||
@@ -520,4 +520,5 @@
|
||||
- PKG-CANVAS-02: [记录](changes/2026-09-12-PKG-CANVAS-02-tests.md) [记录](baselines/canvas-behavior-validation.json) [记录](canvas-validation.md)
|
||||
- PKG-CANVAS-03: [记录](changes/2026-09-12-PKG-CANVAS-03-lifecycle.md) [记录](baselines/canvas-lifecycle-validation.json) [记录](canvas-validation.md)
|
||||
- PKG-CANVAS-04: [记录](changes/2026-09-12-PKG-CANVAS-04-types.md) [记录](baselines/canvas-types-validation.json) [记录](canvas-validation.md)
|
||||
- PKG-MB-01: [记录](baselines/mb-release.json) [记录](baselines/mb-surface.json) [记录](baselines/mb-contract.md) [记录](baselines/mb-contract-validation.json) [记录](mb-validation.md) [记录](changes/2026-09-12-PKG-MB-01-contract.md)
|
||||
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md)
|
||||
@@ -1,5 +1,15 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:PKG-MB-01 MediaBunny proxy 契约基线
|
||||
|
||||
冻结实际npm1.0.0/1.2.0共12成员、17个Git输入、3份产物接口表面与25条配置路径;
|
||||
19项专项通过。完整CI1161项(1006单元+14工程+141基线)通过,另44重复契约,
|
||||
290生产TS文件保持严格检查;生产/demo/产物哈希均未改变。
|
||||
见[步骤记录](changes/2026-09-12-PKG-MB-01-contract.md)、[完整契约](baselines/mb-contract.md)
|
||||
和[验证证据](baselines/mb-contract-validation.json)。历史内嵌SDK版本未知,与当前锁定1.56.1
|
||||
分开记录;MPL通知/来源缺口由MB-LICENSE-01跟进。真实媒体、取消、音画同步等仍待02以后。
|
||||
当前222项:98 done、7 doing、117 todo;Canvas/Ambilight类型取舍仍待确认。
|
||||
|
||||
## 当前检查点:PKG-FACTORY-01 类型兼容决策
|
||||
|
||||
Canvas/Ambilight 的工厂替换问题已增加72场景编译对照,以及可选重载的独立反例。
|
||||
|
||||
@@ -168,3 +168,6 @@
|
||||
| DPIP-MEDIA-01 | open / 已复现 | WebKit reports styled width640 for width320 media in the controlled Document PiP iframe matrix | PKG-DPIP-05 |
|
||||
| DPIP-RESOLVE-01 | resolved / 已复现 | Historical Document PiP exports hide declarations or infer an uncallable CommonJS namespace in modern resolution | PKG-DPIP-04, PKG-DPIP-06 |
|
||||
| FACTORY-TYPE-01 | open / 已复现 | Canvas and Ambilight default self property narrows assignability of historical factory replacements | PKG-FACTORY-01 |
|
||||
| MB-LICENSE-01 | open / 已复现 | Historical MediaBunny proxy bundles embed SDK code without MPL notice markers or separate license/source provenance | PKG-MB-10 |
|
||||
| MB-LIFE-01 | open / 源码/产物事实 | MediaBunny load, track replacement, timeout, RAF and audio callbacks lack one shared terminal owner | PKG-MB-02, PKG-MB-03, PKG-MB-04, PKG-MB-05, PKG-MB-06, PKG-MB-07, PKG-MB-09 |
|
||||
| MB-TYPE-01 | open / 已复现 | MediaBunny proxy declares only Canvas while exposing a shim and changes CommonJS export generation between releases | PKG-MB-08, PKG-MB-10 |
|
||||
@@ -3618,6 +3618,61 @@
|
||||
"refactor/baselines/factory-compatibility-proposals.json",
|
||||
"refactor/factory-compatibility-decision.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "MB-LICENSE-01",
|
||||
"title": "Historical MediaBunny proxy bundles embed SDK code without MPL notice markers or separate license/source provenance",
|
||||
"confirmation": "reproduced",
|
||||
"owners": [
|
||||
"PKG-MB-10"
|
||||
],
|
||||
"compatibleResolution": "Inventory the actual candidate bundle modules, preserve relevant license/notice text and corresponding source availability; never infer embedded historical SDK version from the current lock.",
|
||||
"closureCriteria": "Final candidate archive includes verified dependency license/notice and source provenance, with historical unknowns documented; no root-MIT substitution.",
|
||||
"status": "open",
|
||||
"evidence": [
|
||||
"refactor/baselines/mb-release.json",
|
||||
"refactor/baselines/mb-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-MB-01-contract.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "MB-LIFE-01",
|
||||
"title": "MediaBunny load, track replacement, timeout, RAF and audio callbacks lack one shared terminal owner",
|
||||
"confirmation": "source-observed",
|
||||
"owners": [
|
||||
"PKG-MB-02",
|
||||
"PKG-MB-03",
|
||||
"PKG-MB-04",
|
||||
"PKG-MB-05",
|
||||
"PKG-MB-06",
|
||||
"PKG-MB-07",
|
||||
"PKG-MB-09"
|
||||
],
|
||||
"compatibleResolution": "Reproduce pending operations and destroy/source-switch races before migrating each owner; preserve public event and promise behavior while preventing stale resource use.",
|
||||
"closureCriteria": "Historical failing interleavings and candidate cancellation/cleanup pass with real media and event evidence, including long playback and track topology.",
|
||||
"status": "open",
|
||||
"evidence": [
|
||||
"refactor/baselines/mb-release.json",
|
||||
"refactor/baselines/mb-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-MB-01-contract.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "MB-TYPE-01",
|
||||
"title": "MediaBunny proxy declares only Canvas while exposing a shim and changes CommonJS export generation between releases",
|
||||
"confirmation": "reproduced",
|
||||
"owners": [
|
||||
"PKG-MB-08",
|
||||
"PKG-MB-10"
|
||||
],
|
||||
"compatibleResolution": "Preserve optional whole-factory and exact Canvas assignment, model explicit shim capabilities separately and verify historical default namespace and callable consumers.",
|
||||
"closureCriteria": "Actual installed old/candidate compiler and runtime export matrices pass with negative controls; any incompatible declaration generation requires an explicit decision.",
|
||||
"status": "open",
|
||||
"evidence": [
|
||||
"refactor/baselines/mb-release.json",
|
||||
"refactor/baselines/mb-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-MB-01-contract.md"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,7 +44,8 @@ function fixture(t) {
|
||||
test('Actual contract model assigns all 264 family rows and separates exact published versions from workspace snapshots', () => {
|
||||
assert.equal(actual.policy.packages.length, 22)
|
||||
assert.equal(actual.families.length, 12)
|
||||
assert.equal(actual.policy.versions.filter(version => version.kind === 'published-baseline').length, 15)
|
||||
assert.equal(actual.policy.versions.filter(version => version.kind === 'published-baseline').length, 17)
|
||||
assert.deepEqual(actual.policy.versions.filter(version => version.kind === 'published-baseline' && version.package === 'artplayer-proxy-mediabunny').map(version => version.version).sort(), ['1.0.0', '1.2.0'])
|
||||
assert.equal(actual.policy.versions.filter(version => version.kind === 'workspace-baseline').length, 22)
|
||||
assert.equal(actual.policy.cases.length, 10)
|
||||
const markdown = renderContracts(actual)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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 lockfile from '@yarnpkg/lockfile'
|
||||
import { archiveFiles, ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
export async function verifyMbContract() {
|
||||
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/mb-release.json'), 'utf8'))
|
||||
const archives = new Map()
|
||||
for (const release of [baseline.release, ...baseline.previous]) {
|
||||
const archive = await ensureArchive(release)
|
||||
assert.deepEqual(archiveFiles(archive), Object.keys(release.files).sort())
|
||||
for (const [file, expected] of Object.entries(release.files))
|
||||
assert.equal(hash(readMember(archive, file)), expected, `${release.version}/${file}`)
|
||||
assert.deepEqual(JSON.parse(readMember(archive, 'package/package.json')), release.manifest)
|
||||
for (const field of ['main', 'module', 'types', 'legacy'])
|
||||
assert(Object.hasOwn(release.files, `package/${release.manifest[field].replace(/^\.\//, '')}`))
|
||||
const core = release.historicalCore
|
||||
const source = execFileSync('git', ['show', `${core.commit}:${core.file}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n')
|
||||
assert.equal(hash(source), core.sha256LF)
|
||||
assert.equal(JSON.parse(source).version, core.version)
|
||||
archives.set(release.version, archive)
|
||||
}
|
||||
const sources = new Map()
|
||||
for (const [file, expected] of Object.entries(baseline.source)) {
|
||||
const source = execFileSync('git', ['show', `${baseline.sourceCommit}:${file}`], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }).replaceAll('\r\n', '\n')
|
||||
assert.equal(hash(source), expected, `MediaBunny frozen source: ${file}`)
|
||||
sources.set(file, source)
|
||||
}
|
||||
const sdk = baseline.dependency
|
||||
for (const [file, expected] of Object.entries(sdk.files))
|
||||
assert.equal(hash(fs.readFileSync(path.join(refactorDir, '../node_modules', sdk.name, file))), expected, `Installed SDK provenance changed: ${file}`)
|
||||
const lock = lockfile.parse(fs.readFileSync(path.join(refactorDir, '../yarn.lock'), 'utf8'))
|
||||
assert.equal(lock.type, 'success')
|
||||
assert.equal(lock.object[`${sdk.name}@${sdk.declaredRange}`].version, sdk.version)
|
||||
return { baseline, archives, sources }
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const { baseline } = await verifyMbContract()
|
||||
console.log(`MediaBunny proxy verified: two releases, twelve archive members, ${Object.keys(baseline.source).length} frozen inputs; installed SDK ${baseline.dependency.version} provenance is separate from historical bundles`)
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Node frozen contract runner.
|
||||
import test from 'node:test'
|
||||
import vm from 'node:vm'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { mbEnvironment, mbHistorical } from '../../test/helpers/mediabunny.js'
|
||||
import { diagnostics } from './factory-assignability.mjs'
|
||||
import { verifyMbContract } from './mb-contract.mjs'
|
||||
import { readMember } from './releases.mjs'
|
||||
|
||||
const contract = await verifyMbContract()
|
||||
const { baseline, archives } = contract
|
||||
const surface = JSON.parse(fs.readFileSync('refactor/baselines/mb-surface.json', 'utf8'))
|
||||
|
||||
test('MediaBunny freezes two real releases, twelve members, seventeen Git inputs and separately locked SDK provenance', () => {
|
||||
assert.deepEqual([baseline.release.version, ...baseline.previous.map(item => item.version)], ['1.2.0', '1.0.0'])
|
||||
assert.equal([baseline.release, ...baseline.previous].reduce((sum, item) => sum + Object.keys(item.files).length, 0), 12)
|
||||
assert.equal(Object.keys(baseline.source).length, 17)
|
||||
assert.equal(baseline.dependency.version, '1.56.1')
|
||||
assert.equal(baseline.dependency.manifestLicense, 'MPL-2.0')
|
||||
assert.deepEqual([baseline.release, ...baseline.previous].map(item => item.bundledDependencyVersion), [null, null])
|
||||
})
|
||||
|
||||
test('Historical proxy archives embed SDK code without separate license/notice members or MPL markers', () => {
|
||||
for (const release of [baseline.release, ...baseline.previous]) {
|
||||
const files = Object.keys(release.files)
|
||||
assert(!files.some(file => /license|notice|\.wasm$|worker/i.test(file)))
|
||||
assert.equal(release.manifest.license, 'MIT')
|
||||
const archive = archives.get(release.version)
|
||||
for (const field of ['main', 'legacy', 'module']) {
|
||||
const code = readMember(archive, `package/${release.manifest[field].replace(/^\.\//, '')}`).toString()
|
||||
assert.doesNotMatch(code, /Mozilla Public License|MPL-2\.0|This Source Code Form/)
|
||||
if (field === 'module') {
|
||||
assert.match(code, /AudioBufferSink/)
|
||||
assert.match(code, /CanvasSink/)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.match(fs.readFileSync('node_modules/mediabunny/LICENSE', 'utf8'), /^Mozilla Public License Version 2\.0/)
|
||||
})
|
||||
|
||||
test('Published declarations preserve optional factory replacements and exact Canvas results in both supported compiler generations', () => {
|
||||
const source = `import Artplayer from 'artplayer'; import factory from './factory-history';
|
||||
type Option = Parameters<typeof factory>[0];
|
||||
const replacement: typeof factory = (_option?: Option) => (_art: Artplayer) => document.createElement('canvas');
|
||||
const result: ReturnType<ReturnType<typeof factory>> = document.createElement('canvas');
|
||||
factory(); factory({ volume: 0.7, source: new Blob([]), loadTimeout: 1000 }); void [replacement, result];`
|
||||
for (const release of [baseline.release, ...baseline.previous]) {
|
||||
const types = readMember(archives.get(release.version), `package/${release.manifest.types.replace(/^\.\//, '')}`).toString()
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(diagnostics(compiler, source, types), [])
|
||||
assert.deepEqual(diagnostics(compiler, `${source} factory({ volume: 'loud' });`, types).map(item => item.code), [2322])
|
||||
assert.deepEqual(diagnostics(compiler, `${source} factory({ m3u8: {} });`, types).map(item => item.code), release.version === '1.0.0' ? [compiler === compat ? 2345 : 2353] : [])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('Actual main, legacy, browser globals and native ESM retain historical export shapes', async () => {
|
||||
for (const release of [baseline.release, ...baseline.previous]) {
|
||||
for (const field of ['main', 'legacy']) {
|
||||
const code = readMember(archives.get(release.version), `package/${release.manifest[field].replace(/^\.\//, '')}`).toString()
|
||||
const env = mbEnvironment({ code })
|
||||
assert.equal(typeof env.exported, release.version === '1.0.0' ? 'object' : 'function')
|
||||
assert.equal(typeof env.factory(), 'function')
|
||||
const global = { ...env.globals }
|
||||
delete global.module
|
||||
delete global.exports
|
||||
vm.runInNewContext(code, global)
|
||||
const value = global.artplayerProxyMediabunny || global.window.artplayerProxyMediabunny
|
||||
assert.equal(typeof (value.default || value), 'function')
|
||||
}
|
||||
const code = readMember(archives.get(release.version), `package/${release.manifest.module.replace(/^\.\//, '')}`).toString()
|
||||
const module = await import(`data:text/javascript,${encodeURIComponent(code)}`)
|
||||
assert.deepEqual(Object.keys(module), ['default'])
|
||||
assert.equal(typeof module.default(), 'function')
|
||||
}
|
||||
})
|
||||
|
||||
for (const implementation of await mbHistorical()) {
|
||||
test(`MediaBunny ${implementation.name}: exact observable canvas surface and media defaults`, () => {
|
||||
const env = mbEnvironment(implementation)
|
||||
const canvas = env.factory()(env.art)
|
||||
const expected = surface.records.find(item => item.name === implementation.name)
|
||||
assert.equal(canvas, env.canvas)
|
||||
assert.deepEqual(Object.keys(canvas), expected.canvasKeys)
|
||||
assert.equal(Boolean(env.art.mediabunny), expected.artMediabunny)
|
||||
if (env.art.mediabunny) {
|
||||
assert.deepEqual(Object.getOwnPropertyNames(env.art.mediabunny), expected.shimOwn)
|
||||
assert.deepEqual(Object.getOwnPropertyNames(Object.getPrototypeOf(env.art.mediabunny)), expected.shimPrototype)
|
||||
}
|
||||
for (const [name, value] of Object.entries(expected.defaults)) assert.equal(canvas[name], value, name)
|
||||
for (const name of ['src', 'currentTime', 'play', 'duration', 'engine']) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(canvas, name)
|
||||
assert.equal(descriptor.enumerable, true)
|
||||
assert.equal(descriptor.configurable, true)
|
||||
assert.equal(typeof descriptor.get, 'function')
|
||||
assert.equal(typeof descriptor.set, 'function')
|
||||
}
|
||||
if (implementation.name === 'published-1.0.0') {
|
||||
assert.equal(Reflect.set(canvas, 'duration', 12), true, 'Older non-strict forwarding silently ignores readonly writes')
|
||||
assert(Number.isNaN(canvas.duration))
|
||||
}
|
||||
else {
|
||||
assert.throws(() => Reflect.set(canvas, 'duration', 12), { name: 'TypeError' })
|
||||
}
|
||||
assert.notEqual(canvas.play, canvas.play, 'Forwarded methods are rebound on every read')
|
||||
})
|
||||
|
||||
test(`MediaBunny ${implementation.name}: option reference, coercion and historical no-op setters`, () => {
|
||||
const env = mbEnvironment(implementation)
|
||||
const option = { volume: 2, muted: true, poster: 'old.png', autoplay: true, loop: true, crossOrigin: 'anonymous' }
|
||||
const canvas = env.factory(option)(env.art)
|
||||
assert.equal(canvas.option, option)
|
||||
assert.equal(canvas.volume, 2, 'Initial volume bypasses setter clamp')
|
||||
canvas.volume = '0.4'
|
||||
assert.equal(canvas.volume, 0.4)
|
||||
assert.equal(canvas.muted, false)
|
||||
canvas.volume = -1
|
||||
assert.equal(canvas.volume, 0)
|
||||
canvas.volume = 2
|
||||
assert.equal(canvas.volume, 1)
|
||||
canvas.muted = 'yes'
|
||||
assert.equal(canvas.muted, true)
|
||||
canvas.playbackRate = 2
|
||||
canvas.playbackRate = 0
|
||||
canvas.playbackRate = Number.NaN
|
||||
assert.equal(canvas.playbackRate, 2)
|
||||
canvas.poster = 'new.png'
|
||||
assert.equal(option.poster, 'new.png')
|
||||
canvas.autoplay = false
|
||||
canvas.loop = false
|
||||
canvas.crossOrigin = 'use-credentials'
|
||||
assert.equal(canvas.autoplay, true)
|
||||
assert.equal(canvas.loop, true)
|
||||
assert.equal(canvas.crossOrigin, 'anonymous')
|
||||
assert.deepEqual(env.emitted.map(item => item.name), ['video:volumechange', 'video:volumechange', 'video:volumechange', 'video:volumechange', 'video:ratechange'])
|
||||
})
|
||||
|
||||
test(`MediaBunny ${implementation.name}: native canvas methods win and configured shim events forward through ArtPlayer`, () => {
|
||||
const env = mbEnvironment(implementation)
|
||||
const canvas = env.factory()(env.art)
|
||||
assert.equal(canvas.getContext('2d'), env.context)
|
||||
assert.deepEqual(env.nativeCalls.at(-1), ['context', '2d', true])
|
||||
canvas.setAttribute('src', 'media.mp4')
|
||||
assert.equal(canvas.src, null, 'Canvas native setAttribute is not shim setAttribute')
|
||||
assert.deepEqual(env.nativeCalls.at(-1), ['attribute', 'src', 'media.mp4'])
|
||||
let nativeEvents = 0
|
||||
canvas.addEventListener('volumechange', () => nativeEvents++)
|
||||
for (const name of env.eventNames) {
|
||||
const detail = { name }
|
||||
canvas.events.emit(name, detail)
|
||||
const event = env.emitted.at(-1)
|
||||
assert.equal(event.name, `video:${name}`)
|
||||
assert.equal(event.args[0].type, name)
|
||||
assert.equal(event.args[0].detail, detail)
|
||||
}
|
||||
assert.equal(nativeEvents, 0)
|
||||
env.art.emit('resize')
|
||||
assert.deepEqual({ ...canvas.style }, { width: '100%', height: '100%', objectFit: 'contain' })
|
||||
})
|
||||
|
||||
test(`MediaBunny ${implementation.name}: sync and async forwarding, synthetic ranges and factory teardown`, async () => {
|
||||
const env = mbEnvironment(implementation)
|
||||
const canvas = env.factory()(env.art)
|
||||
const calls = []
|
||||
const promise = Promise.resolve()
|
||||
canvas.engine.play = () => promise
|
||||
canvas.engine.pause = () => calls.push('pause')
|
||||
canvas.engine.load = source => calls.push(['load', source])
|
||||
canvas.engine.seek = time => calls.push(['seek', time])
|
||||
canvas.engine.destroy = () => calls.push('destroy')
|
||||
assert.equal(canvas.play(), promise)
|
||||
assert.equal(canvas.pause(), undefined)
|
||||
canvas.src = 'first.mp4'
|
||||
assert.equal(canvas.load(), undefined)
|
||||
canvas.currentTime = '3.5'
|
||||
assert.equal(canvas.currentSrc, 'first.mp4')
|
||||
assert.equal(canvas.buffered.length, 0)
|
||||
Object.defineProperty(canvas.engine, 'duration', { value: 12, configurable: true })
|
||||
Object.defineProperty(canvas.engine, 'currentTime', { value: 3.5, configurable: true })
|
||||
assert.equal(canvas.buffered.end(99), 12, 'Synthetic range does not validate indices or measure downloaded bytes')
|
||||
assert.equal(canvas.played.end(0), 3.5)
|
||||
assert.equal(canvas.seekable.start(0), 0)
|
||||
env.art.emit('destroy')
|
||||
assert.equal(env.art.mediabunny, undefined)
|
||||
assert.deepEqual(calls, ['pause', ['load', 'first.mp4'], ['load', 'first.mp4'], ['seek', 3.5], 'destroy'])
|
||||
await promise
|
||||
})
|
||||
|
||||
test(`MediaBunny ${implementation.name}: frame callback is one cancellable RAF, not decoded-frame evidence`, () => {
|
||||
const env = mbEnvironment(implementation)
|
||||
const canvas = env.factory()(env.art)
|
||||
const calls = []
|
||||
const first = canvas.requestVideoFrameCallback((...args) => calls.push(args))
|
||||
assert.equal(first, 0)
|
||||
canvas.cancelVideoFrameCallback(first)
|
||||
assert.equal(env.frames.size, 0)
|
||||
const second = canvas.requestVideoFrameCallback((...args) => calls.push(args))
|
||||
env.frames.get(second)(100)
|
||||
assert.equal(calls.length, 1)
|
||||
assert.equal(calls[0][0], 100)
|
||||
assert.equal(calls[0][1].presentedFrames, 0)
|
||||
assert.equal(calls[0][1].mediaTime, 0)
|
||||
assert.equal(canvas.canPlayType('not/a-real-codec'), 'maybe')
|
||||
})
|
||||
}
|
||||
+9
-2
@@ -3391,11 +3391,18 @@
|
||||
"dependsOn": [
|
||||
"BASE-05"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "全部 option、art.mediabunny、canvas/shim 属性及事件表",
|
||||
"acceptance": "公开暴露成员和实际发布依赖/资源范围明确;核实 third-party.json 所记录依赖 MPL-2.0 元数据与实际 bundle 的许可/通知范围,不从根 MIT 推断",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"baselines/mb-release.json",
|
||||
"baselines/mb-surface.json",
|
||||
"baselines/mb-contract.md",
|
||||
"baselines/mb-contract-validation.json",
|
||||
"mb-validation.md",
|
||||
"changes/2026-09-12-PKG-MB-01-contract.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MB-02",
|
||||
|
||||
@@ -1176,7 +1176,7 @@
|
||||
"packages/artplayer-proxy-mediabunny/package.json"
|
||||
],
|
||||
"contract": "Preserve native-video-like events, HLS input and topology, codec capability failures and AV sync; actual library/codec environment still requires verification.",
|
||||
"validationStatus": "Source integration observed; complete real SDK/service/device matrix not yet verified.",
|
||||
"validationStatus": "PKG-MB-01 verifies actual 1.0.0/1.2.0 archives and installed 1.56.1 MPL-2.0 provenance, recorded in mb-release.json and mb-contract.md. Historical embedded SDK version is unknown; notices are MB-LICENSE-01/MB-10. Native codec, SDK media and AV sync remain MB-02/09.",
|
||||
"demoPathsSource": "refactor/baselines/demo-inventory.json"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import vm from 'node:vm'
|
||||
import { verifyMbContract } from '../../refactor/scripts/mb-contract.mjs'
|
||||
import { readMember } from '../../refactor/scripts/releases.mjs'
|
||||
|
||||
export async function mbHistorical() {
|
||||
const { baseline, archives, sources } = await verifyMbContract()
|
||||
const name = baseline.release.name
|
||||
return [
|
||||
...[baseline.release, ...baseline.previous].map(release => ({
|
||||
name: `published-${release.version}`,
|
||||
code: readMember(archives.get(release.version), `package/${release.manifest.main.replace(/^\.\//, '')}`).toString(),
|
||||
})),
|
||||
{ name: 'frozen-workspace', code: sources.get(`packages/${name}/dist/${name}.js`) },
|
||||
]
|
||||
}
|
||||
|
||||
export function mbEnvironment({ code }, option = {}) {
|
||||
const emitted = []
|
||||
const handlers = new Map()
|
||||
const frames = new Map()
|
||||
const canvasListeners = new Map()
|
||||
let nextFrame = 0
|
||||
const nativeCalls = []
|
||||
const context = { clearRect() {}, drawImage() {} }
|
||||
const canvas = {
|
||||
width: 300,
|
||||
height: 150,
|
||||
style: {},
|
||||
getContext(type) {
|
||||
nativeCalls.push(['context', type, this === canvas])
|
||||
return context
|
||||
},
|
||||
addEventListener(type, fn) { canvasListeners.set(type, fn) },
|
||||
removeEventListener(type) { canvasListeners.delete(type) },
|
||||
getBoundingClientRect() { return { width: 300, height: 150 } },
|
||||
setAttribute(name, value) { nativeCalls.push(['attribute', name, value]) },
|
||||
}
|
||||
const eventNames = ['volumechange', 'ratechange', 'play', 'pause', 'loadedmetadata', 'error']
|
||||
const art = {
|
||||
constructor: { utils: { createElement: () => canvas }, config: { events: eventNames } },
|
||||
option: { url: '', autoSize: false },
|
||||
template: { $player: {} },
|
||||
on(name, fn) {
|
||||
const listeners = handlers.get(name) || []
|
||||
listeners.push(fn)
|
||||
handlers.set(name, listeners)
|
||||
return this
|
||||
},
|
||||
emit(name, ...args) {
|
||||
emitted.push({ name, args })
|
||||
for (const fn of handlers.get(name) || []) fn(...args)
|
||||
return this
|
||||
},
|
||||
}
|
||||
const module = { exports: {} }
|
||||
const globals = {
|
||||
module,
|
||||
exports: module.exports,
|
||||
window: {},
|
||||
TextDecoder,
|
||||
TextEncoder,
|
||||
ReadableStream,
|
||||
Blob,
|
||||
Event,
|
||||
URL,
|
||||
console,
|
||||
performance,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
requestAnimationFrame(fn) {
|
||||
const id = nextFrame++
|
||||
frames.set(id, fn)
|
||||
return id
|
||||
},
|
||||
cancelAnimationFrame(id) { frames.delete(id) },
|
||||
}
|
||||
vm.runInNewContext(code, globals)
|
||||
const factory = module.exports.default || module.exports
|
||||
return { factory, exported: module.exports, art, canvas, context, option, frames, nativeCalls, eventNames, handlers, emitted, canvasListeners, globals }
|
||||
}
|
||||
Reference in new issue
Block a user