refactor(audio): [PKG-AUDIO-04] migrate strict source and compatible type entries

This commit is contained in:
Harvey Zhao committed 2026-09-12 02:58:44 +08:00
1 parent 9c07ea830c
commit 13c898c149
31 files changed
+18923 -90

No files matched your search

+12
View File
@@ -70,3 +70,15 @@ Windows WebKit 也给出错误 4;AAC 矩阵通过不能覆盖该 WAV 或真实
浏览器测试均共用端口 8084 和输出目录;不要同时执行多个 suite。开始下一次前复制报告
和结果目录(包含 trace),并记录对应测试输入。Node CI 可独立并行执行。
## PKG-AUDIO-04 类型与结束事件用例补强
源文件已迁移为严格 TS。默认/legacy 保留旧 update 的必填 URL 推断;/runtime 与编辑器
RuntimeFactory 提供准确的部分更新契约,实际运行函数不变。五组编译消费和 Monaco
三引擎验证见 [类型交付](changes/2026-09-12-PKG-AUDIO-04-types.md) 及
[冻结证据](baselines/audio-types-validation.json),不要用早期 JS 状态覆盖当前状态。
legacy 首次 WebKit 的“候选核心 + 已发布 audio”结束观察失败,原始证据保留。
两种结束用例现统一先确认视频/音频 seek 完成且就绪,再确认音频实际恢复推进后才
观测 video.ended,并记录宿主事件;避免原测试因音频早已暂停而误判。相关 WebKit
12 次及最终 main/legacy 各 42 项通过。未证明首次暂停的确切来源,未改生产代码。
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,66 @@
# PKG-AUDIO-04:严格 TS 与兼容公开类型
状态:done;起点 9c07ea83;完成后独立本地提交,不推送或发布。
迁移 index/track 两个职责模块,不改变上一任务已验证的运行时逻辑。
公开 Option/Result/UpdateOption,保留必传工厂参数、同一 HTMLAudioElement 和同步 update。
默认入口保留旧 Result 类型;新增 /runtime 类型入口描述部分 update,检验 Parameters
提取、旧结构赋值和无注解回调推断。两入口复用相同 JS/mjs 文件。
保持 root/legacy 运行时路径,补 CJS/ESM 类型桥及由公开声明生成的编辑器全局类型。
严格源码、TS 4.3/5.9 消费模式、负例、Monaco 语义/运行、实际产物浏览器回归已通过。
冻结证据见 [audio-types-validation.json](../baselines/audio-types-validation.json)。
## 兼容方案依据
实际编译用例证明直接 Partial 或“Partial 在前、Option 在后”重载会使旧上下文实现
`update(options) { audio.src = options.url }` 推断 url 为 string | undefined。
虽然显式注解和 Parameters 通过,它仍是公开类型回归。泛型替代还产生 implicit any
或放过未知字段,因此未采用;保留了这些失败日志,不通过删用例宣称兼容。
最终沿用核心的默认兼容类型 + /runtime 精确类型方式,运行入口完全相同,源码可严格
赋值为两种工厂契约,无 any 或方法双变协变豁免。编辑器通过 RuntimeFactory 显式选择
精确语义,原全局类型仍保留旧推断。
## 实施与验收
- index.ts 管理宿主事件及订阅;track.ts 管理音频元素与状态。事件使用明确的字面量
联合,异常使用 unknown。独立 tsconfig 启用严格检查且不接受 JS 源码。
- 保留原 root/legacy 运行文件与 main/module/types/legacy 字段;新增 CJS/ESM 声明桥、
TS 4.3 的 typesVersions 和复用原 JS 的 /runtime 入口。未新增依赖。
- `yarn build:ts` 生成编辑器声明,独立 TS 5.9.3/4.3.5 检查通过。五组消费方式各
12 个错误用法被拒绝;旧显式/上下文实现、Parameters 提取、README 真实 TS 示例、
部分 update 和同步 void 通过。三个专用类型测试通过。
- 实际 require/import 证明 root 与 /runtime 是同一个函数;legacy 可调用。
Monaco worker 三引擎验证 core/HLS/audio,共 9 项通过,包含语义正反例与脚本运行。
- 完整 `yarn ci:check` 586 项通过(540 单元、11 工程、35 基线),256 个生产 TS 文件
纳入严格检查。随后增加的 README 编译断言通过专用测试,最终浏览器辅助函数另经
定向 lint 和两个完整产物矩阵验证。既有核心声明有一条未使用禁用指令警告。
- `yarn build artplayer-plugin-audio-track` 和三格式 Node 89 项通过;正式 main/legacy
三引擎最终各 42 项通过,无重试或跳过。每组包含 9 项旧缺陷观察、3 项 WAV 能力诊断,
不将它们全部计为候选无缺陷验收。三种 JS 与 Audio-03 SHA256 完全相同,docs/compiled
与包产物逐字节一致,类型迁移没有引入运行时代码。
- 实际 Yarn pack 曾检出 tsconfig.json 泄漏,已加入该包 .npmignore。修正后 12 个文件
通过既有 checkFiles/packedFiles 校验:6 份声明全量包含且与源码相同、所有 exports
有目标、历史分发文件保留、无 src/tsconfig。完整隔离安装消费仍由 Audio-06 验收。
## 浏览器失败的处理
首次 legacy 为 41 通过、1 失败:WebKit 上“候选核心 + 已发布 audio”的音频在视频
结束断言前已暂停。这一组合未加载迁移后的 audio;首次 trace 未记录宿主事件,不能
断言已证明具体原因。原始报告与 trace 保留在 .cache/audio04-browser-legacy-first*。
原辅助流程将暂停后 seek 与重新播放挨在一起,缺少 seek 完成和音频实际恢复前提,
既可能误报旧版,也可能让候选因早已暂停而假通过。现在两种用例统一等待双方 seek
完成、readyState >= 3,并确认音频已恢复且时间推进,再观测视频结束;新增宿主事件
记录,未放宽超时、增加重试或修改生产代码。WebKit 相关 4 项各重复 3 次通过,随后
main/legacy 两套完整矩阵通过。增强后的用例通过不等于原生暂停的确切原因已定位。
## 兼容范围、后续与回退
API-09/11 采用 ADR-023:默认旧类型刻意保持 url 必填推断,准确的部分更新类型通过
/runtime 或编辑器 RuntimeFactory 显式选择;AUDIO-TYPE-01 按此兼容范围接受。
README demo 名称改为现存 audio.track,尚无本次 8082 页面验收,AUDIO-DEMO-01 保持开放。
负偏移/时长边界、真实缓冲与连续切源继续 Audio-05,隔离安装/实际 demo 继续 Audio-06。
未修改版本;各包下一 major 由 REL-09 落地,物理设备及发布复盘门槛不变。
回退本任务独立提交可恢复 Audio-03 的 JS 与旧声明;正式 JS 产物未变,不需回退
音频生命周期修复。共享生成器对 core/HLS 的回归已验证。
+1
View File
@@ -27,6 +27,7 @@
| ADR-021 | 采用方向 | 用户指定 Yarn,固定 Classic 1.22.22 和唯一 yarn.lock | 与原 v1 锁格式一致;保留 Node 24.21.0 和既有依赖版本,ENG-PM-01 验证;替代 ENG-01 的 npm 默认工具选择 |
| ADR-022 | 采用方向 | Chrome 不可用时使用 Codex 内置浏览器 | 用户明确授权;记录实际环境和能力范围,必要设备测试仍独立验收 |
| ADR-023 | 已验证于 Audio Track | 参数类型纠正会破坏旧推断时,默认入口保留兼容类型,/runtime 复用相同运行文件提供精确类型 | PKG-AUDIO-04 的 Parameters 与无注解 update 实现证明 Partial/重载仍可能破坏旧用户;沿用核心的类型分层方向,具体包需独立编译与入口身份验证,不增加第二套实现或用 any 绕过 |
## 开始实现前需补齐的决定
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 216 项,范围 22 个包及工作区/示例。
状态:todo 147 / doing 1 / blocked 0 / done 68 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 146 / doing 1 / blocked 0 / done 69 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -158,7 +158,7 @@
| PKG-AUDIO-01 | artplayer-plugin-audio-track<br>核对包契约与历史用法 | BASE-05 | url/offset/sync、audio 实例暴露、update 与音量语义 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
| PKG-AUDIO-02 | artplayer-plugin-audio-track<br>建立特有行为与错误测试 | PKG-AUDIO-01, ENG-03, ENG-05 | 偏移、倍率、seek、waiting/playing、加载错误和 autoplay 拒绝 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-AUDIO-03 | artplayer-plugin-audio-track<br>整理内部职责与资源 | PKG-AUDIO-02, CORE-10 | 外部 audio 的同步策略、监听与源更新生命周期 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-AUDIO-04 | artplayer-plugin-audio-track<br>迁移自有源码和公开类型 | PKG-AUDIO-03, ENG-04, ENG-06, CORE-07 | Option/Result/audio、update 输入和事件类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-AUDIO-04 | artplayer-plugin-audio-track<br>迁移自有源码和公开类型 | PKG-AUDIO-03, ENG-04, ENG-06, CORE-07 | Option/Result/audio、update 输入和事件类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-AUDIO-05 | artplayer-plugin-audio-track<br>验证新旧核心和组合 | PKG-AUDIO-04, CORE-22 | 主视频切源和缓冲恢复,多实例独立音频、旧核心 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-AUDIO-06 | artplayer-plugin-audio-track<br>验证分发并同步文档 | PKG-AUDIO-05, ENG-07 | audio.track.js 示例与运行返回值一致的声明 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
@@ -469,6 +469,7 @@
- PKG-AUDIO-01: [记录](changes/2026-09-12-PKG-AUDIO-01-contract.md) [记录](baselines/audio-track-contract.md) [记录](baselines/audio-track-release.json)
- PKG-AUDIO-02: [记录](changes/2026-09-12-PKG-AUDIO-02-tests.md) [记录](audio-validation.md) [记录](baselines/audio-validation.json)
- PKG-AUDIO-03: [记录](changes/2026-09-12-PKG-AUDIO-03-lifecycle.md) [记录](baselines/audio-lifecycle-validation.json)
- PKG-AUDIO-04: [记录](changes/2026-09-12-PKG-AUDIO-04-types.md) [记录](baselines/audio-types-validation.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)
+17
View File
@@ -1,5 +1,22 @@
# 进度与证据
## 最新完成:PKG-AUDIO-04 严格 TS 与兼容类型入口
Audio 两个职责模块已迁移 TS,累计 256 个生产 TS。默认/legacy 保留原 update 类型
推断;新增 /runtime 精确入口支持部分配置,复用同一 JS/mjs。直接拓宽类型会破坏旧
上下文实现的证据及决定已写入 ADR-023,编辑器生成和 README 同步。
完整 CI 586 项通过,五组 TS 消费各 12 个负例、专用类型 3 项、Monaco 三引擎 9 项
通过;三格式 Node 89 项、最终 main/legacy 各 42 项通过,三种 JS 与 Audio-03 字节
相同。结束用例首轮 WebKit 失败保留并补强 seek/恢复前提,未靠重试关闭问题。
真实 Yarn pack 修正 tsconfig 泄漏后保留全部 6 份声明及历史文件;隔离安装仍待 Audio-06。
当前 216 项、69 done、1 doing、146 todo。Audio-03 提交 9c07ea83 已核实,Audio-04
独立提交核实后进入 05 的边界/缓冲/切源/多实例。AUDIO-TYPE-01 按兼容范围接受,
AUDIO-SYNC-01/DEMO-01、HLS 两项 Firefox 风险和全局发布门槛仍开放。
详见 [交付记录](changes/2026-09-12-PKG-AUDIO-04-types.md) 和
[冻结证据](baselines/audio-types-validation.json)。无 push/tag/publish/merge。
## 最新完成:PKG-AUDIO-03 源码拆分与生命周期修复
Audio Track 已拆为入口事件管理与独立媒体状态两个 JS 模块;修复原生暂停/结束后
+1 -1
View File
@@ -128,5 +128,5 @@
| HLS-PLAYBACK-01 | open / 已复现 | Firefox 旧核心与 Hls 1.7.2 切组后视频档位未完成切换 | PKG-HLS-SDK-01, PKG-HLS-05, REVIEW-02 |
| AUDIO-LIFE-01 | resolved / 已复现 | 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-TYPE-01 | accepted-with-scope / 源码/产物事实 | Audio Track update 部分配置与公开/编辑器声明不一致 | PKG-AUDIO-04 |
| AUDIO-DEMO-01 | open / 源码/产物事实 | Audio Track README 示例链接名称不匹配且缺维护说明 | PKG-AUDIO-06 |
+9 -3
View File
@@ -2695,7 +2695,7 @@
"id": "AUDIO-TYPE-01",
"title": "Audio Track update 部分配置与公开/编辑器声明不一致",
"confirmation": "source-observed",
"status": "open",
"status": "accepted-with-scope",
"owners": [
"PKG-AUDIO-04"
],
@@ -2703,8 +2703,14 @@
"refactor/baselines/audio-track-contract.md",
"refactor/baselines/audio-track-release.json"
],
"compatibleResolution": "拓宽已有 JS 合法部分 update,保留 url/offset/sync 字段精度和 Parameters/ReturnType 提取,生成语义正确编辑器声明。",
"closureCriteria": "当前与旧编译器的正反例、CJS/ESM/legacy 入口及实际编辑器通过。"
"compatibleResolution": "ADR-023:默认/legacy 保留旧必填 url 推断与上下文实现;新增 /runtime 复用同一 JS,精确支持部分 update,编辑器通过 RuntimeFactory 显式选择。",
"closureCriteria": "当前与旧编译器的正反例、CJS/ESM/legacy 入口及实际编辑器通过。",
"resolutionRationale": "直接拓宽或重载会使旧上下文实现的 url 成为 string | undefined,已用实际编译复现。五组 TS 正反例、README、CJS/ESM 同一函数、真实 Monaco 三引擎已通过。接受默认旧声明有意保持窄签名的范围,不声称所有入口默认支持部分 update;隔离安装和完整分发仍由 Audio-06 验证。",
"resolutionEvidence": [
"refactor/changes/2026-09-12-PKG-AUDIO-04-types.md",
"refactor/baselines/audio-types-validation.json",
"refactor/decisions.md"
]
},
{
"id": "AUDIO-DEMO-01",
+76
View File
@@ -0,0 +1,76 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import { createRequire } from 'node:module'
// eslint-disable-next-line test/no-import-node-test -- Package consumers run in the baseline runner.
import test from 'node:test'
import { ESLint } from 'eslint'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
import { checkConsumer } from '../../scripts/typecheck.mjs'
test('Audio Track preserves root/legacy, historical extracted inputs and partial update across compilers', () => {
const source = fs.readFileSync('test/types/audio-track.ts', 'utf8')
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
assert.equal(checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 12, `${compiler.version} ${mode} must reject invalid inputs`)
}
const cjs = `import audio = require('artplayer-plugin-audio-track');
import legacy = require('artplayer-plugin-audio-track/legacy');
import runtime = require('artplayer-plugin-audio-track/runtime');
import Artplayer = require('artplayer');
const option: audio.Option = { url: 'audio.aac' };
const update: audio.UpdateOption = { offset: 1 };
const art = new Artplayer({ container: '#player', url: 'video.mp4' });
const result: audio.Result = audio(option)(art);
result.update(option); legacy(option)(art).update(option);
const precise: runtime.RuntimeResult = runtime(option)(art); precise.update(update);`
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', cjs), [])
const readme = fs.readFileSync('packages/artplayer-plugin-audio-track/README.md', 'utf8').match(/```ts\n([\s\S]*?)```/)?.[1]
assert(readme, 'Keep the documented runtime type example runnable')
assert.deepEqual(checkConsumer(ts, 'bundler-esm', readme), [])
assert.deepEqual(checkConsumer(compat, 'node10-commonjs', readme), [])
})
test('Audio Track editor declaration is reproducible and validates partial/legacy calls without leaked types', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-audio-track/types/artplayer-plugin-audio-track.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginAudioTrack')
const file = 'docs/assets/ts/artplayer-plugin-audio-track.d.ts'
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
assert.equal(formatted.errorCount, 0)
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated, 'Run yarn build:ts')
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
const consumer = `
const option: artplayerPluginAudioTrack.Option = { url: 'audio.aac' };
const part: artplayerPluginAudioTrack.UpdateOption = { offset: 1 };
const factory = artplayerPluginAudioTrack as artplayerPluginAudioTrack.RuntimeFactory;
const result = factory(option)(new Artplayer({ container: '#player', url: 'video.mp4' }));
result.update(part);
const historical: Parameters<artplayerPluginAudioTrack.Result['update']>[0] = { url: 'old.aac' };
const oldURL: string = historical.url;
// @ts-expect-error Update fields stay typed.
result.update({ offset: 'bad' });
// @ts-expect-error Factory options are required.
artplayerPluginAudioTrack();
// @ts-expect-error Private definitions do not become globals.
const hidden: artplayerPluginAudioTrackDefinitions.Option = { url: 'audio.aac' };
void [oldURL, hidden];`
for (const compiler of [ts, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [], compiler.version)
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
assert(checkPluginEditorDeclaration(`${generated}\nexport interface Broken { value: MissingType }`, core, '', compiler).some(item => item.code === 2309))
}
})
test('Audio Track runtime type entry reuses the exact root runtime for CJS and ESM', async () => {
const manifest = JSON.parse(fs.readFileSync('packages/artplayer-plugin-audio-track/package.json'))
assert.equal(manifest.exports['./runtime'].require.default, manifest.exports['.'].require.default)
assert.equal(manifest.exports['./runtime'].import.default, manifest.exports['.'].import.default)
const require = createRequire(import.meta.url)
assert.equal(require('artplayer-plugin-audio-track/runtime'), require('artplayer-plugin-audio-track'))
const [root, runtime] = await Promise.all([import('artplayer-plugin-audio-track'), import('artplayer-plugin-audio-track/runtime')])
assert.equal(runtime.default, root.default)
assert.equal(typeof root.default, 'function')
const legacy = require('artplayer-plugin-audio-track/legacy')
assert.equal(typeof legacy, 'function')
})
+5 -2
View File
@@ -1533,11 +1533,14 @@
"ENG-06",
"CORE-07"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "Option/Result/audio、update 输入和事件类型",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": []
"evidence": [
"changes/2026-09-12-PKG-AUDIO-04-types.md",
"baselines/audio-types-validation.json"
]
},
{
"id": "PKG-AUDIO-05",
+6
View File
@@ -80,4 +80,10 @@ allowJs=true、checkJs=false 允许渐进迁移时引用旧 JS;只将迁移的
## 后续接入
Audio Track 的自有源码由 packages/artplayer-plugin-audio-track/tsconfig.json 严格检查。
默认/legacy 类型保留历史 update 的完整 Option;新增 /runtime 复用同一运行文件,
提供 UpdateOption/RuntimeResult。test/types/audio-track.ts 同时验证旧 Parameters、
有/无注解结构实现及精确部分调用;audio-track-source.ts 检查实现满足两种声明。
详见包内 ARCHITECTURE.md;不能将部分参数直接覆盖默认签名而破坏旧推断。
新迁移包复制分包配置并按实际运行环境调整,加入特有正反例;编译器入口自动发现包配置。TS 构建入口与声明生成由 ENG-06/07 和各包迁移处理;不能只增加一个空 config 就宣布包迁移完成。所有生产变更仍按每任务独立提交和包内架构文档规则交付。