diff --git a/refactor/baselines/README.md b/refactor/baselines/README.md index 67ad00adf..9d2c837c6 100644 --- a/refactor/baselines/README.md +++ b/refactor/baselines/README.md @@ -35,6 +35,10 @@ BASE-01 于 2026-09-10 固定核心与 chapter 的 npm 发布内容,数据在 ## 公共 API 报告 +Audio Track 的后续包基线由 PKG-AUDIO-01 独立冻结,见 +[契约和重跑](audio-track-contract.md) 及 [1.1.0 归档来源](audio-track-release.json)。 +它不改写本目录初始 BASE 快照,媒体/类型/设备通过范围由后续任务提供。 + BASE-02 已在内置浏览器取得核心/chapter 的同步 API 和描述符快照,见 [覆盖与重跑](api-coverage.md) 和 [冻结报告](public-api.json)。事件/生命周期、DOM、类型、分发和其他包仍由各自任务补齐。 ## 事件与生命周期报告 diff --git a/refactor/baselines/audio-track-contract.md b/refactor/baselines/audio-track-contract.md new file mode 100644 index 000000000..1202f7bd2 --- /dev/null +++ b/refactor/baselines/audio-track-contract.md @@ -0,0 +1,94 @@ +# Audio Track 1.1.0 兼容基线 + +PKG-AUDIO-01;源码起点 26b2983daf9edf9c798ce6a1638deeb3c36ba778。 +固定来源、逐文件 SHA-256、npm SRI、历史源码及比较结果见 +[audio-track-release.json](audio-track-release.json)。不覆盖 BASE-01 的历史快照。 + +## 来源与范围 + +npm 观察到 1.0.0、1.1.0,采集时 latest 为 1.1.0。本次实际归档范围为 **1.1.0**, +不是所有历史版本。六文件归档完整性通过,manifest、README、公开声明与起点源码 +在 LF 规范化后相同,发布 ESM 与源码工厂经 AST 提取/esbuild 规范化后相同。 +这证明比较范围内的逻辑一致,不能代替真实媒体行为测试。 + +registry gitHead daf133b22630b4a0eecfa3336bbddab0e9119d96 对应本地包版本 1.0.2, +不是该 npm 1.1.0 源码的充分证明;因此保留归档完整性和独立工厂比较。 +manifest 无 runtime/peer 依赖、无核心/浏览器/TS 最低版本承诺。 +旧核心对照使用现有冻结的 5.4.0;这只是具体检查点,不宣称最低支持版本为 5.4.0。 + +## 公开形状及调用 + +```js +const attach = artplayerPluginAudioTrack({ url: '/audio.aac', offset: 0, sync: 0.3 }) +// ArtPlayer 注册时调用 attach(art) +const result = art.plugins.artplayerPluginAudioTrack +result.update({ url: '/next.aac', offset: 0.2 }) +const audio = result.audio // 同一 HTMLAudioElement,可直接操作 +``` + +工厂配置为必传对象;工厂同步返回安装函数,安装同步返回 `{ name, audio, update }`。 +name 固定为 `artplayerPluginAudioTrack`,update 同步返回 undefined。没有额外公开 destroy、 +事件、UI、CSS、静态版本或 SDK。插件创建独立 Audio 元素,不替换主视频,也不自动静音主视频; +因此含原音轨的主视频可能与外部音频同时播放,这是现有行为,不静默改变音量策略。 +每次安装创建独立状态;配置在安装时解构,调用工厂后、安装前的对象修改会生效。 + +| 输入或行为 | 1.1.0 源码/发布实现事实 | 兼容与后续验证 | +| --- | --- | --- | +| url | 初始仅 truthy 时赋给 audio.src,preload 为 auto | URL 字符串交给浏览器解析,无 SDK | +| offset | 默认 0,目标 audio 时间是 art.currentTime + offset | 正数音频领先、负数滞后;负目标/超时长在 02 实测 | +| sync | 默认 0.3,仅绝对偏差严格大于阈值才 seek | 等于阈值不写;未校验负数、NaN、Infinity,不能凭猜测新增拒绝规则 | +| update | truthy 且不同 URL 才赋 src;playing 时调用 play;之后才更新 offset/sync | 同 URL 不重载,空 URL 不清源;JS 允许只传 offset/sync,且不立即同步 | +| audio | 公开真实元素,同一引用贯穿安装/更新/销毁 | 不换为不可访问封装,不用新元素实现每次切源 | +| 音量/倍率 | 初始 volume/muted 来自 art;倍率 video?.playbackRate 或 1 | 后续 video:volumechange/ratechange 同步,无自有主视频 mute | +| play 拒绝 | 三个调用点各自 catch 并 console.warn 原错误 | 没有返回给调用者的 play Promise、没有自定义错误事件 | + +| ArtPlayer 事件 | 外部音频行为 | +| --- | --- | +| play | 有 URL 才同步目标时间,随后 audio.play | +| pause | audio.pause | +| seek | 有 video/URL 才同步时间 | +| video:timeupdate | art.playing 时同步时间 | +| video:ratechange | 读 art.video.playbackRate | +| video:volumechange | 同步 art.volume 和 art.muted | +| video:waiting | audio.pause | +| video:playing | 有 URL 且 art.playing 才 audio.play,不主动 sync | +| destroy | pause、src = ''、load,按此顺序 | + +没有定时器或自建请求;浏览器拥有媒体加载。当前九个 emitter 回调没有插件自身解绑, +依赖核心销毁清空事件。没有 closed 状态,外部保留的 update 可在销毁后写入新 src。 +这些是源码观察,02 用受控复现及真实媒体验证;不能只凭观察称内存泄漏已复现。 + +## 类型和分发 + +公开默认导出为 `(option: Option) => (art: Artplayer) => Result`,Option.url 必需、 +offset/sync 可选;Result.audio 为 HTMLAudioElement,update(option: Option): void。 +Option/Result 未具名导出,消费者可用 Parameters/ReturnType 提取;04 保留这些旧合法调用。 +JS 的 update({ offset: 1 }) 与声明不一致,允许拓宽 update 为部分配置,但不弱化字段类型。 +docs 编辑器声明还同时包含 export default/export =,由 04 做语义验证及生成修正, +本步不声称编辑器或 TS 消费已经通过。 + +root exports 使用 types + import(mjs)/require(js),legacy import/require 都指向 legacy.js; +main/module/types/legacy 的完整值冻结在 manifest。UMD 全局名与默认导出一致。 +保留 `/legacy`、旧文件路径、同步注册和未加载 DOM 时的导入边界;实际各格式消费由 02/04/06 验证。 +当前版本保持 1.1.0,REL-09 按用户要求准备 2.0.0,未获 npm 发布授权。 + +## 文档、样本与差异归属 + +README 仅说明插件名、Demo 和 MIT;其 `example=audio-track` 与真实 +`docs/assets/example/audio.track.js` 不一致。common.js 直接拼接名称,无连字符转换。 +实际示例使用本地 sprite-fight.mp4 + sprite-fight.aac,二者存在;没有演示 update、 +偏移边界或销毁。06 应修链接并以正式 8082 demo 验证,不能拿静态文件存在作为播放通过。 + +| 风险 | 当前证据等级 | 负责任务 | +| --- | --- | --- | +| AUDIO-LIFE-01:销毁后 update 可写 src,监听/异步播放归属缺少终止守卫 | 源码事实,未作浏览器泄漏结论 | 02/03/05 | +| AUDIO-SYNC-01:偏移边界及切源/缓冲恢复缺少媒体时序证据 | 待取证,不预设所有当前行为是缺陷 | 02/03/05 | +| AUDIO-TYPE-01:update 部分配置和编辑器声明不一致 | 源码/声明事实 | 04 | +| AUDIO-DEMO-01:README 示例名不匹配、维护说明缺失 | 源码/文件事实 | 06 | + +## 重跑 + +`node refactor/scripts/audio-contract.mjs`:重验固定归档、六文件、manifest、历史 Git 内容与工厂。 +首次仅从固定 npm URL 下载至忽略缓存;不安装、不执行归档脚本、不覆盖基线。 +`node --test refactor/scripts/audio-contract.test.mjs`:包含该验证及规范化工具的有意行为变化检查。 +本步不改生产代码/声明/构建产物;所有媒体、类型和设备验收仍是后续任务。 diff --git a/refactor/baselines/audio-track-release.json b/refactor/baselines/audio-track-release.json new file mode 100644 index 000000000..f9dbc9dc9 --- /dev/null +++ b/refactor/baselines/audio-track-release.json @@ -0,0 +1,113 @@ +{ + "schemaVersion": 1, + "task": "PKG-AUDIO-01", + "capturedAt": "2026-09-11T17:44:31.880Z", + "sourceCommit": "26b2983daf9edf9c798ce6a1638deeb3c36ba778", + "registryObservation": { + "registryUrl": "https://registry.npmjs.org/artplayer-plugin-audio-track", + "distTags": { + "latest": "1.1.0" + }, + "versions": [ + "1.0.0", + "1.1.0" + ], + "publishedAt": "2026-03-12T14:21:57.424Z", + "note": "Observed metadata; not a promise of future latest version or support for every older release." + }, + "release": { + "name": "artplayer-plugin-audio-track", + "version": "1.1.0", + "metadataUrl": "https://registry.npmjs.org/artplayer-plugin-audio-track/1.1.0", + "tarball": "https://registry.npmjs.org/artplayer-plugin-audio-track/-/artplayer-plugin-audio-track-1.1.0.tgz", + "integrity": "sha512-pvvHvTSqC2+sBelwYyjAv2CVmaQgG9KpX+i96t/e8oyZRX/JuepC9iSVIp7GzB3AtliFzhVamksT+8lXvD3RCg==", + "sha256": "7e84f6a2f4bfada428a77ef04b749bf3db53e0149fa1afe7bf5749d925e801ff", + "archiveBytes": 2205, + "registryGitHead": "daf133b22630b4a0eecfa3336bbddab0e9119d96", + "files": { + "package/README.md": "9a03352433ae90cb9d3198e17571c9bb598334b84a77a3a1df2c4b000ce8347d", + "package/dist/artplayer-plugin-audio-track.js": "473f39f1d750f0722cca7e857c12c3c978ebe3c3b2352e325f1248318e508683", + "package/dist/artplayer-plugin-audio-track.legacy.js": "d8dc74bce76112d7430c98e3165605e32fde5d3c603be4b496eac58f574576cb", + "package/dist/artplayer-plugin-audio-track.mjs": "4ac599bc5a1a6ecb0cb1942d4269528bfb2cd9d047f988de61fe671a77ad14cd", + "package/package.json": "c466ae55c40e8f6bdd97bec20fcf916c8c4f44eef3e47694638be005eb445df7", + "package/types/artplayer-plugin-audio-track.d.ts": "27bc0dfedacb7e0188cb668ac8708874a86b00d489f1636037c2465d3344437e" + }, + "manifest": { + "name": "artplayer-plugin-audio-track", + "version": "1.1.0", + "description": "audio-track plugin for ArtPlayer", + "author": "Harvey Zhao ", + "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-plugin-audio-track.d.ts", + "import": "./dist/artplayer-plugin-audio-track.mjs", + "require": "./dist/artplayer-plugin-audio-track.js" + }, + "./legacy": { + "types": "./types/artplayer-plugin-audio-track.d.ts", + "import": "./dist/artplayer-plugin-audio-track.legacy.js", + "require": "./dist/artplayer-plugin-audio-track.legacy.js" + } + }, + "main": "dist/artplayer-plugin-audio-track.js", + "module": "./dist/artplayer-plugin-audio-track.mjs", + "types": "types/artplayer-plugin-audio-track.d.ts", + "legacy": "dist/artplayer-plugin-audio-track.legacy.js", + "browserslist": "last 1 Chrome version" + } + }, + "source": { + "packages/artplayer-plugin-audio-track/package.json": "c466ae55c40e8f6bdd97bec20fcf916c8c4f44eef3e47694638be005eb445df7", + "packages/artplayer-plugin-audio-track/README.md": "9a03352433ae90cb9d3198e17571c9bb598334b84a77a3a1df2c4b000ce8347d", + "packages/artplayer-plugin-audio-track/types/artplayer-plugin-audio-track.d.ts": "27bc0dfedacb7e0188cb668ac8708874a86b00d489f1636037c2465d3344437e", + "packages/artplayer-plugin-audio-track/src/index.js": "470237ebd62e8fb8537e809db03fd02871844d41c03d422a22956bd2756ac28e", + "docs/assets/example/audio.track.js": "9ed26b2ee006e1a430db68b2a1b308c4801dd3a4cc87c74e8a8f5ccf3456ac8d", + "docs/assets/ts/artplayer-plugin-audio-track.d.ts": "e0815cb05be2366957df77f0de81a46f863c4d8e0a0697fac1258c4f48da85ea" + }, + "comparisons": [ + { + "file": "package.json", + "publishedLF": "c466ae55c40e8f6bdd97bec20fcf916c8c4f44eef3e47694638be005eb445df7", + "workspaceLF": "c466ae55c40e8f6bdd97bec20fcf916c8c4f44eef3e47694638be005eb445df7", + "equal": true + }, + { + "file": "README.md", + "publishedLF": "9a03352433ae90cb9d3198e17571c9bb598334b84a77a3a1df2c4b000ce8347d", + "workspaceLF": "9a03352433ae90cb9d3198e17571c9bb598334b84a77a3a1df2c4b000ce8347d", + "equal": true + }, + { + "file": "types/artplayer-plugin-audio-track.d.ts", + "publishedLF": "27bc0dfedacb7e0188cb668ac8708874a86b00d489f1636037c2465d3344437e", + "workspaceLF": "27bc0dfedacb7e0188cb668ac8708874a86b00d489f1636037c2465d3344437e", + "equal": true + } + ], + "functionComparison": { + "sha256": "6a758f0a88567283ae44bc9d1fa0906161f6ce9d384803637d7124c2dc1b87da", + "typescript": "5.9.3", + "esbuild": "0.27.7", + "scope": [ + "artplayerPluginAudioTrack" + ], + "excludes": [ + "bundler wrappers" + ], + "note": "Historical source at sourceCommit; normalized factory comparison is not runtime acceptance." + } +} diff --git a/refactor/changes/2026-09-12-PKG-AUDIO-01-contract.md b/refactor/changes/2026-09-12-PKG-AUDIO-01-contract.md new file mode 100644 index 000000000..d9ec52794 --- /dev/null +++ b/refactor/changes/2026-09-12-PKG-AUDIO-01-contract.md @@ -0,0 +1,41 @@ +# PKG-AUDIO-01:Audio Track 发布契约 + +状态:本步契约验收完成。起点为 HLS 诊断检查点 26b2983d;后者仍未完成,不构成依赖阻塞。 + +范围:固定 audio-track 1.1.0 的 npm 归档、声明、源码和 demo 差异,记录同步、 +暴露 audio、update 和事件契约。本步不修改生产行为。后续 02 建行为测试,03 整理 +同步与资源,04 迁移严格 TS 和兼容声明,05/06 完成组合、设备与分发验收。 + +## 来源和结果 + +真实 npm 1.1.0 归档 2205 字节、六文件,SHA-512 SRI、SHA-256、成员哈希及入口存在性通过。 +manifest、README、声明与历史源码 LF 内容一致,完整工厂 AST 规范化比较一致。 +registry gitHead 对应包版本 1.0.2,故没有把该字段当成 1.1.0 源码证明。 +来源和范围见 [冻结输入](../baselines/audio-track-release.json), +详细旧行为与映射见 [契约](../baselines/audio-track-contract.md)。 + +识别出 AUDIO-LIFE-01/SYNC-01/TYPE-01/DEMO-01,分别交由 02~06;都是源码事实或待取证, +没有声称已复现真实音频泄漏或已修复。包括公开 audio、严格同步阈值、无自动主视频静音、 +update 部分配置、错误警告、源更新及销毁顺序,旧合法接口均维持原状。 +修正文档环境矩阵对本包的能力描述:它同步独立外部 Audio,不负责 HLS 轨道拓扑选择。 + +## 验证与范围 + +- `node refactor/scripts/audio-contract.mjs`:六文件归档及历史源码检查通过。 +- `node --test refactor/scripts/audio-contract.test.mjs`:两项通过;额外验证格式变化不误判, + 真实函数行为变化不能获得相同规范化指纹,缺失工厂明确失败。 +- 定向 ESLint:首次报告对象换行三处,修正格式后复验通过。 +- 计划与风险表生成/校验、Git diff 空白检查通过:216 任务、126 风险,没有豁免未解决项。 + +新增一个只读契约检查入口及其 Node 测试,复用已有 esbuild/TypeScript/tar 依赖, +无新安装依赖或 lock 变更。首次会下载已冻结归档至忽略缓存;不会安装/执行归档代码, +不会更新持久基线。`yarn test:baseline` 的既有文件模式自动纳入这两项。 +生产源码、声明、demo、构建产物完全未改,因此本步不重建、不声称浏览器或类型消费者通过。 + +## 提交、回退与接续 + +独立完成提交主题:`docs(audio): [PKG-AUDIO-01] freeze released synchronization contract`。 +仅回退本任务新增基线/检查及任务文档即可恢复先前状态;无生产行为回退需求。 +基线本身长期不可覆盖,后续修改另建证据。下一步 PKG-AUDIO-02 建立可重跑的旧版行为、 +边界与错误测试,03/04 按证据拆分、修复、迁移 TS;包内实际模块地图随实现交付。 +剩余所有包、设备、CI/CD、major 及三轮复盘继续保留。未 push/tag/publish/merge。 diff --git a/refactor/environment-matrix.md b/refactor/environment-matrix.md index e48fe51cb..f73d6f8ad 100644 --- a/refactor/environment-matrix.md +++ b/refactor/environment-matrix.md @@ -36,7 +36,7 @@ Node10 是 TS 模块解析模式名,不代表支持 Node.js 10。现代 es2020 | artplayer-plugin-ads / 2.1.0 | 广告图片/视频、本片恢复、跳过、错误/销毁;本地短广告和主片 | 发布范围及所有浏览器场景待;无需借 IMA 结果代替自有广告逻辑 | PKG-ADS-01/02/05/06 | | artplayer-plugin-ambilight / 1.1.0 | Canvas 采样、CORS、resize、后台/销毁;同源及受控跨源 MP4 | 待真实画布和资源测量 | PKG-AMBILIGHT-01/02/05/06 | | artplayer-plugin-asr / 2.1.0 | PCM 音频输出/暂停/销毁;AudioContext + 调用方服务 | 待音频环境与调用方 WebSocket 验证;示例服务版本未知,不在 PR 调线上服务 | PKG-ASR-01/02/05/06、EX-03 | -| artplayer-plugin-audio-track / 1.1.0 | 音轨切换与同步;固定双音轨/单音轨/无音轨样本 | 待轨道来源和支持窗口确认、Safari/桌面实测 | PKG-AUDIO-01/02/05/06 | +| artplayer-plugin-audio-track / 1.1.0 | 独立外部 Audio 与主视频时钟同步;本地 MP4+AAC,偏移/切源/缓冲/失败样本 | 1.1.0 发布及调用契约见 [包基线](baselines/audio-track-contract.md);旧核心 5.4.0 为具体对照点,桌面/Safari 实测待完成 | PKG-AUDIO-01/02/05/06 | | artplayer-plugin-auto-thumbnail / 1.1.0 | 帧提取/跳转、CORS、回收;本地 MP4+跨源失败 | 待 Canvas/seek/移动内存行为 | PKG-AUTO-THUMB-01/02/05/06 | | artplayer-plugin-chapter / 1.1.0 | 区间、边界/重叠、hover/update、进度条组合;MP4+固定章节 | 发布 1.1.0 已冻结;最小注册/两段 DOM 已验,边界和新旧组合待 | PKG-CHAPTER-01/02/04/05/06、PILOT-01 | | artplayer-plugin-chromecast / 1.1.0 | Cast SDK、发送/接收会话、取消/断线、本机恢复;接收端可访问媒体 | SDK 动态地址已记录;真实接收设备和 sender 版本未知、待运行 | PKG-CAST-01/02/05/06 | diff --git a/refactor/plan.md b/refactor/plan.md index 46d788281..687287541 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 216 项,范围 22 个包及工作区/示例。 -状态:todo 150 / doing 1 / blocked 0 / done 65 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 149 / doing 1 / blocked 0 / done 66 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -155,7 +155,7 @@ | ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 | | --- | --- | --- | --- | --- | --- | --- | -| PKG-AUDIO-01 | artplayer-plugin-audio-track
核对包契约与历史用法 | BASE-05 | url/offset/sync、audio 实例暴露、update 与音量语义 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | todo | +| PKG-AUDIO-01 | artplayer-plugin-audio-track
核对包契约与历史用法 | BASE-05 | url/offset/sync、audio 实例暴露、update 与音量语义 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done | | PKG-AUDIO-02 | artplayer-plugin-audio-track
建立特有行为与错误测试 | PKG-AUDIO-01, ENG-03, ENG-05 | 偏移、倍率、seek、waiting/playing、加载错误和 autoplay 拒绝 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo | | PKG-AUDIO-03 | artplayer-plugin-audio-track
整理内部职责与资源 | PKG-AUDIO-02, CORE-10 | 外部 audio 的同步策略、监听与源更新生命周期 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo | | PKG-AUDIO-04 | artplayer-plugin-audio-track
迁移自有源码和公开类型 | PKG-AUDIO-03, ENG-04, ENG-06, CORE-07 | Option/Result/audio、update 输入和事件类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | @@ -466,6 +466,7 @@ - PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json) - PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json) - PKG-CHAPTER-04: [记录](changes/2026-09-10-PKG-CHAPTER-04-public-types.md) [记录](baselines/chapter-types-validation.json) +- PKG-AUDIO-01: [记录](changes/2026-09-12-PKG-AUDIO-01-contract.md) [记录](baselines/audio-track-contract.md) [记录](baselines/audio-track-release.json) - PKG-HLS-01: [记录](baselines/hls-control-contract.md) [记录](baselines/hls-control-release.json) [记录](changes/2026-09-11-PKG-HLS-01-contract.md) - PKG-HLS-02: [记录](hls-validation.md) [记录](baselines/hls-sdk.json) [记录](baselines/hls-validation.json) [记录](changes/2026-09-11-PKG-HLS-02-tests.md) - PKG-HLS-03: [记录](changes/2026-09-12-PKG-HLS-03-modules.md) [记录](baselines/hls-modules-validation.json) [记录](hls-validation.md) diff --git a/refactor/progress.md b/refactor/progress.md index 49dc82a6e..ae0a5a700 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,17 @@ # 进度与证据 +## 最新完成:PKG-AUDIO-01 外部音轨发布契约 + +冻结 npm audio-track 1.1.0 六文件及完整性,manifest/README/声明和起点一致, +发布工厂与源码规范化比较通过。两项可重跑检查通过;音频同步、update 部分配置、 +销毁后引用和示例链接差异已分别登记,实际行为与媒体测试由 02 接续。 +源码/声明/产物未改,本步不代表音频浏览器或类型验收通过。 + +HLS 诊断检查点已提交 26b2983d,HLS-SDK-01 仍 doing,两项 Firefox 风险仍 open。 +当前 216 项、66 done、1 doing、149 todo。Audio-01 按任务独立本地提交后核实, +继续 Audio-02,避免未归因的 HLS 平台问题阻塞无依赖的其他包。 +详见 [契约](baselines/audio-track-contract.md) 和 [记录](changes/2026-09-12-PKG-AUDIO-01-contract.md)。 + ## HLS 诊断 checkpoint:已缩小范围,尚未关闭风险 独立诊断 runner 已加入直接 Hls、发布核心、候选核心对照,以及 HTTP/route、worker/观察器、 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index cc9512cd6..d8709ea94 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -126,3 +126,7 @@ | HLS-ENV-01 | open / 已复现 | Windows WebKit 缺失 MSE,HLS 播放矩阵尚缺 Safari 证据 | PKG-HLS-05, REL-09, REVIEW-02 | | HLS-CRASH-01 | open / 已复现 | Firefox Hls 1.5.17 worker 销毁可在原生 video 与候选核心复现崩溃 | PKG-HLS-SDK-01, PKG-HLS-05, REVIEW-02 | | HLS-PLAYBACK-01 | open / 已复现 | Firefox 旧核心与 Hls 1.7.2 切组后视频档位未完成切换 | PKG-HLS-SDK-01, PKG-HLS-05, REVIEW-02 | +| AUDIO-LIFE-01 | open / 源码/产物事实 | Audio Track 销毁后 update 与媒体/订阅缺少终止守卫 | PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-05 | +| AUDIO-SYNC-01 | open / 待取证 | Audio Track 偏移边界和切源/缓冲恢复尚缺真实时序证据 | PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-05 | +| AUDIO-TYPE-01 | open / 源码/产物事实 | Audio Track update 部分配置与公开/编辑器声明不一致 | PKG-AUDIO-04 | +| AUDIO-DEMO-01 | open / 源码/产物事实 | Audio Track README 示例链接名称不匹配且缺维护说明 | PKG-AUDIO-06 | diff --git a/refactor/risks.json b/refactor/risks.json index 76446a2e2..920c3257b 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -2643,6 +2643,70 @@ ], "compatibleResolution": "区分 SDK、测试媒体/时序和宿主问题,保留真实档位/解码断言及 trace;不扩大等待或只验证菜单。", "closureCriteria": "直接 Hls 与新旧核心对照定位切换停滞,验证处置和正常/连续组切换,不能以重复通过关闭。" + }, + { + "id": "AUDIO-LIFE-01", + "title": "Audio Track 销毁后 update 与媒体/订阅缺少终止守卫", + "confirmation": "source-observed", + "status": "open", + "owners": [ + "PKG-AUDIO-02", + "PKG-AUDIO-03", + "PKG-AUDIO-05" + ], + "evidence": [ + "refactor/baselines/audio-track-contract.md", + "refactor/baselines/audio-track-release.json" + ], + "compatibleResolution": "保留公开 audio 和同步 update,封闭销毁后的资源写入并管理插件订阅,验证待决播放拒绝。", + "closureCriteria": "受控复现和新旧核心真实媒体切源/销毁验证通过,不以源码观察代替泄漏证据。" + }, + { + "id": "AUDIO-SYNC-01", + "title": "Audio Track 偏移边界和切源/缓冲恢复尚缺真实时序证据", + "confirmation": "unverified", + "status": "open", + "owners": [ + "PKG-AUDIO-02", + "PKG-AUDIO-03", + "PKG-AUDIO-05" + ], + "evidence": [ + "refactor/baselines/audio-track-contract.md", + "refactor/baselines/audio-track-release.json" + ], + "compatibleResolution": "固定严格阈值、偏移正负、倍率和音量语义;实测负目标/超时长及连续切源,按证据修复。", + "closureCriteria": "正常/边界/失败/缓冲恢复有媒体与受控断言,实际支持环境缺口明确处理。" + }, + { + "id": "AUDIO-TYPE-01", + "title": "Audio Track update 部分配置与公开/编辑器声明不一致", + "confirmation": "source-observed", + "status": "open", + "owners": [ + "PKG-AUDIO-04" + ], + "evidence": [ + "refactor/baselines/audio-track-contract.md", + "refactor/baselines/audio-track-release.json" + ], + "compatibleResolution": "拓宽已有 JS 合法部分 update,保留 url/offset/sync 字段精度和 Parameters/ReturnType 提取,生成语义正确编辑器声明。", + "closureCriteria": "当前与旧编译器的正反例、CJS/ESM/legacy 入口及实际编辑器通过。" + }, + { + "id": "AUDIO-DEMO-01", + "title": "Audio Track README 示例链接名称不匹配且缺维护说明", + "confirmation": "source-observed", + "status": "open", + "owners": [ + "PKG-AUDIO-06" + ], + "evidence": [ + "refactor/baselines/audio-track-contract.md", + "refactor/baselines/audio-track-release.json" + ], + "compatibleResolution": "修正 audio.track 示例路径,补真实模块地图及 update/生命周期说明。", + "closureCriteria": "README、示例、公开声明一致,8082 正式 demo 和包分发验证通过。" } ] } diff --git a/refactor/scripts/audio-contract.mjs b/refactor/scripts/audio-contract.mjs new file mode 100644 index 000000000..c1ceb8354 --- /dev/null +++ b/refactor/scripts/audio-contract.mjs @@ -0,0 +1,62 @@ +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 { transform } from 'esbuild' +import ts from 'typescript' +import { archiveFiles, ensureArchive, hash, readMember, refactorDir } from './releases.mjs' + +const root = path.resolve(refactorDir, '..') +export const normalizeLF = value => value.toString().replace(/\r\n/g, '\n') + +export async function audioFunctionHash(code) { + const source = ts.createSourceFile('audio.js', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS) + assert.equal(source.parseDiagnostics.length, 0, 'Invalid audio source') + const functions = source.statements.filter(item => ts.isFunctionDeclaration(item) && item.name?.text === 'artplayerPluginAudioTrack') + assert.equal(functions.length, 1, 'Expected exactly one audio factory') + const normalized = await transform(functions[0].getText(source).replace(/^export\s+default\s+/, ''), { + loader: 'js', + target: 'es2020', + minifySyntax: true, + minifyWhitespace: true, + }) + return hash(normalized.code) +} + +export async function verifyAudioContract() { + const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/audio-track-release.json'), 'utf8')) + const { release } = baseline + const archive = await ensureArchive(release) + assert.deepEqual(archiveFiles(archive), Object.keys(release.files).sort()) + for (const [member, expected] of Object.entries(release.files)) + assert.equal(hash(readMember(archive, member)), expected, `Changed release member: ${member}`) + const manifest = JSON.parse(readMember(archive, 'package/package.json')) + assert.deepEqual(manifest, release.manifest) + for (const entry of [manifest.main, manifest.module, manifest.types, manifest.legacy]) + assert(Object.hasOwn(release.files, `package/${entry.replace(/^\.\//, '')}`)) + assert(/^[a-f\d]{40}$/.test(baseline.sourceCommit)) + const historical = new Map() + for (const [file, expected] of Object.entries(baseline.source)) { + const content = normalizeLF(execFileSync('git', ['show', `${baseline.sourceCommit}:${file}`], { cwd: root })) + assert.equal(hash(content), expected, `Changed historical source: ${file}`) + historical.set(file, content) + } + for (const comparison of baseline.comparisons) { + const published = hash(normalizeLF(readMember(archive, `package/${comparison.file}`))) + const source = hash(historical.get(`packages/${release.name}/${comparison.file}`)) + assert.equal(published, comparison.publishedLF) + assert.equal(source, comparison.workspaceLF) + assert.equal(published === source, comparison.equal) + } + const sourceHash = await audioFunctionHash(historical.get(`packages/${release.name}/src/index.js`)) + const publishedHash = await audioFunctionHash(readMember(archive, `package/${manifest.module.replace(/^\.\//, '')}`).toString()) + assert.equal(sourceHash, baseline.functionComparison.sha256) + assert.equal(publishedHash, sourceHash) + console.log(`Verified ${release.name}@${release.version}: archive, ${Object.keys(release.files).length} members, historical sources and normalized factory`) + return { baseline, archive } +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) + await verifyAudioContract() diff --git a/refactor/scripts/audio-contract.test.mjs b/refactor/scripts/audio-contract.test.mjs new file mode 100644 index 000000000..d423d7ac0 --- /dev/null +++ b/refactor/scripts/audio-contract.test.mjs @@ -0,0 +1,14 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Frozen baseline checks run in Node. +import test from 'node:test' +import { audioFunctionHash, verifyAudioContract } from './audio-contract.mjs' + +test('Audio Track 1.1.0 archive and historical contract remain immutable', verifyAudioContract) + +test('audio factory comparison ignores formatting but detects changed behavior', async () => { + const original = 'export default function artplayerPluginAudioTrack(option) { return option.offset + 1 }' + const formatted = 'function artplayerPluginAudioTrack(option) {\n return option.offset + 1;\n }' + assert.equal(await audioFunctionHash(original), await audioFunctionHash(formatted)) + assert.notEqual(await audioFunctionHash(original), await audioFunctionHash(original.replace('+ 1', '+ 2'))) + await assert.rejects(audioFunctionHash('function unrelated() {}'), /exactly one audio factory/) +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index 6192b50e5..c39901f37 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -1468,11 +1468,15 @@ "dependsOn": [ "BASE-05" ], - "status": "todo", + "status": "done", "risk": "H", "deliverable": "url/offset/sync、audio 实例暴露、update 与音量语义", "acceptance": "源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结", - "evidence": [] + "evidence": [ + "changes/2026-09-12-PKG-AUDIO-01-contract.md", + "baselines/audio-track-contract.md", + "baselines/audio-track-release.json" + ] }, { "id": "PKG-AUDIO-02",