test(multiple-subtitles): [PKG-MULTI-SUB-09] isolate old-core source seeking from caption loss

This commit is contained in:
Harvey Zhao committed 2026-09-14 13:02:49 +08:00
1 parent efed847cdf
commit 31f9024bff
11 files changed
+8419 -18

No files matched your search

@@ -151,9 +151,12 @@ active cue; the matrix explicitly records lost simultaneous languages for1.1.0+
and candidate, while1.0.0's index-merged cue displays both. This remains an open
compatibility issue, not an approved limitation. CORE-SUBTITLE-OFFSET-01 corrects
paused offsets in the candidate core; published Firefox hosts retain their exact
historical defect observations. WebKit5.3.0/5.4.0 source-switch failures remain
under task05 investigation. See its change and validation JSON for original failures
and the independent core offset change for the corrected candidate evidence.
historical defect observations. Task09 traced apparent WebKit5.3.0/5.4.0 caption
loss to native seeks ending near zero: the cue itself remained intact. A no-plugin
control reproduces the same old-host problem. Candidate core already waits for
source seeking to complete. The ordering probe retains immediate old calls, while
caption combinations wait for native seeking to settle before an independent seek.
See task09's change/validation record and the core offset change for precise scope.
The matrix is runnable with `yarn test:browser test/browser/multiple-subtitles-combinations.spec.js`;
do not replace these failures with skips or count boundary observations as acceptance.
See `../../refactor/baselines/multiple-subtitles-contract.md` for exact historical differences.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,81 @@
# PKG-MULTI-SUB-09: distinguish source seeking from caption loss
Baseline: efed847cdfcbe5110e88b31527c46e622bcc83ee. This task closes the
misattributed switching finding with independent native evidence and corrects
integration sequencing. No production code, public type, dependency, version,
lockfile or built artifact changes. PKG-MULTI-SUB-05 remains doing.
## Root cause evidence
The original WebKit failures retained a valid Translation cue at1..3 seconds,
but the actual media clock was approximately0.002 seconds, with no active cues.
The subtitle was correctly absent at that time; the intended seek to1.5 had not
landed. Those reports predate currentTime instrumentation.
`multiple-subtitles-switch.spec.js` compares no plugin, current plugin, and actual
npm plugin1.2.0 on cores5.3.0/5.4.0/candidate. Each case plays real local media,
enters/exits native fullscreen, then performs three source changes. It captures
native currentTime writes and media events, Promise settlement state, first seek
completion and a second seek only if the first missed after native seeking ended.
The property probe forwards to the original native getter/setter; it does not
intercept or replace source operations. Outcomes and full traces are stored in
[multiple-subtitles-switch-validation.json](../baselines/multiple-subtitles-switch-validation.json).
The first WebKit run confirms5 misses in18 old-core source changes, including
both no-plugin old-core controls. All9 candidate-core changes land correctly.
Old core promises sometimes resolve while video.seeking is still true, allowing
the caller's new seek to overlap native restoration. After the restoration has
ended, a fresh seek reaches the cue interval and both old/new plugins display it.
Candidate source/switch.ts already waits for native seeking to finish and uses
the position restoration logic covered by BASE-LIFE-44/CORE-19. No plugin-level
seek retry, clock rewrite, blanket source reload or global core patch is needed.
The actual-main three-engine trace run has no missed first seeks in81 changes,
but old WebKit hosts still settle during native seeking in6 of18 changes. This
variation is retained; it is an intermittent old-host race, not proof that the
initial misses were fixed by rerunning. All27 candidate-core changes in that run
have seeking=false at settlement and honor the first caller seek.
## Test sequencing and validation
The caption combination test now waits for video.seeking=false after switchUrl
before testing the separate user seek/caption behavior. The immediate old-call
path is retained in the independent ordering test above, including its misses.
This wait is a predicate on real native state, not a fixed delay or a silent
retry of the acceptance assertion. It does not claim old switchUrl promises have
been changed or all historical playback contracts are correct.
One later full run exposed an unrelated fixture sequence: it started exiting
fullscreen when the document property changed but before the public enter event
arrived, resulting in old-core notifications[false,false]. The normal enter/exit
scenario now waits for the entering event before requesting exit; the original
strict final[true,false] assertion remains. The failed trace is retained. No
production fullscreen correction is claimed by this test-sequencing adjustment.
- Initial source WebKit ordering probe:9 cases pass their observation/candidate
assertions, including the5 old first-seek misses noted above.
- Actual core and plugin main artifacts, three engines:84 cases,83 passing
assertions,1 fullscreen-sequencing failure. All27 ordering cases pass, and the
run also includes explicitly labelled historical overlap/offset defects.
- After the entering-event predicate correction, the affected timed-combination
matrix is rerun unchanged otherwise:9/9 pass across three cores and engines.
Do not describe this as a fresh84-case all-green run.
- The two changed browser scripts pass lint;50 CI regression tests pass. Final
plan/risk and risk-validator checks cover the completed task state.
MULTI-SUB-SWITCH-01 is resolved as a classified old-core seeking issue already
handled in the candidate core, with the original calls and failures preserved.
This does not resolve MULTI-SUB-MERGE-01 or complete device/distribution acceptance.
Use the actual loaded core/plugin hashes when comparing future runs; the fixture
supports both source builds and the existing explicit artifact map.
## Maintenance and rollback
Keep source readiness, actual seek landing and caption rendering distinct when
debugging media failures. Run the ordering probe with/without the plugin before
adding plugin retries for an underlying player clock problem. Keep candidate
first-seek assertions strict; old observations must never count as correct calls.
Both suites run under yarn test:browser. A normal core/plugin build was not needed
because production bytes are unchanged; the checked artifacts came from the prior
completed implementation commits. Revert this independent task09 commit to remove
the new probe and integration predicates. Local commit only, no push/publication.
+7 -5
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 248 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 249 项,范围 22 个包及工作区/示例。
状态:todo 55 / doing 17 / blocked 0 / done 176 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 55 / doing 17 / blocked 0 / done 177 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -14,7 +14,7 @@
| 包 | 基线版本 | 任务 |
| --- | --- | --- |
| artplayer | 5.4.1 | CORE-01, CORE-02, CORE-03, CORE-04, CORE-05, CORE-06, CORE-07, CORE-08, CORE-09, CORE-10, CORE-11, CORE-12, CORE-13, CORE-14, CORE-15, CORE-16, CORE-17, CORE-18, CORE-19, CORE-20, CORE-21, CORE-23, CORE-22, CORE-24, SITE-BUILD-01, CORE-25, ENG-12, CORE-SUBTITLE-OFFSET-01 |
| artplayer | 5.4.1 | CORE-01, CORE-02, CORE-03, CORE-04, CORE-05, CORE-06, CORE-07, CORE-08, CORE-09, CORE-10, CORE-11, CORE-12, CORE-13, CORE-14, CORE-15, CORE-16, CORE-17, CORE-18, CORE-19, CORE-20, CORE-21, CORE-23, CORE-22, CORE-24, SITE-BUILD-01, CORE-25, ENG-12, CORE-SUBTITLE-OFFSET-01, PKG-MULTI-SUB-09 |
| artplayer-plugin-ads | 2.1.0 | PKG-ADS-01, PKG-ADS-02, PKG-ADS-03, PKG-ADS-04, PKG-ADS-05, PKG-ADS-06 |
| artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06, PKG-FACTORY-01 |
| artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06, PKG-ASR-07, PKG-ASR-08, PKG-ASR-09 |
@@ -28,7 +28,7 @@
| artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 |
| artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 |
| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06, PKG-JASSUB-07, PKG-JASSUB-08, PKG-JASSUB-09 |
| artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-07, PKG-MULTI-SUB-08, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06 |
| artplayer-plugin-multiple-subtitles | 1.2.0 | PKG-MULTI-SUB-01, PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-04, PKG-MULTI-SUB-07, PKG-MULTI-SUB-08, PKG-MULTI-SUB-05, PKG-MULTI-SUB-06, PKG-MULTI-SUB-09 |
| artplayer-plugin-vast | 1.2.0 | PKG-VAST-01, PKG-VAST-02, PKG-VAST-03, PKG-VAST-04, PKG-VAST-05, PKG-VAST-06 |
| artplayer-plugin-vtt-thumbnail | 1.1.0 | PKG-VTT-THUMB-01, PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 |
| artplayer-proxy-canvas | 1.1.0 | PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-01, PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-04, PKG-CANVAS-05, PKG-CANVAS-06, PKG-FACTORY-01 |
@@ -229,7 +229,8 @@
| PKG-MULTI-SUB-07 | artplayer-plugin-multiple-subtitles<br>修复字幕内嵌时间戳序列化 | PKG-MULTI-SUB-03 | 保留数字时间戳节点、修正无效 NaN 输出、明确旧/新字幕输出差异 | 旧九份实现可复现,候选有效内嵌时间戳及文字/CSS/原生字幕通过,公开工厂与选择接口不变 | H | done |
| PKG-MULTI-SUB-08 | artplayer-plugin-multiple-subtitles<br>修复字幕实体解码与嵌套标签包装 | PKG-MULTI-SUB-04, PKG-MULTI-SUB-07 | 使用解析器既有实体表入口修复多余分号;保护字面文本并以兄弟节点包装标签,保留CSS钩子和时间戳 | 冻结旧实现复现、实体/双重编码/标签/选择重置回归、新旧核心真实字幕显示通过;vendor原文与来源校验不变,类型/分发兼容 | M | done |
| PKG-MULTI-SUB-05 | artplayer-plugin-multiple-subtitles<br>验证新旧核心和组合 | PKG-MULTI-SUB-04, CORE-22, PKG-MULTI-SUB-07, PKG-MULTI-SUB-08 | 与核心字幕/偏移/全屏组合,旧名称调用不变 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
| PKG-MULTI-SUB-06 | artplayer-plugin-multiple-subtitles<br>验证分发并同步文档 | PKG-MULTI-SUB-05, ENG-07, CORE-SUBTITLE-OFFSET-01 | multiple.subtitles.js、parser 来源许可及打包边界 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
| PKG-MULTI-SUB-06 | artplayer-plugin-multiple-subtitles<br>验证分发并同步文档 | PKG-MULTI-SUB-05, ENG-07, CORE-SUBTITLE-OFFSET-01, PKG-MULTI-SUB-09 | multiple.subtitles.js、parser 来源许可及打包边界 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
| PKG-MULTI-SUB-09 | artplayer-plugin-multiple-subtitles, artplayer<br>定位旧核心切源寻址与字幕显示边界 | PKG-MULTI-SUB-08, CORE-19 | 无插件及旧新插件对照、真实事件轨迹、原切源失败归因和组合测试顺序 | 保留旧核心立即寻址观察,候选首次寻址与正常字幕组合通过;不把原生时钟失败误归为字幕丢失 | M | done |
## 5 包迁移:artplayer-plugin-jassub
@@ -647,3 +648,4 @@
- PKG-AUTO-THUMB-10: [记录](changes/2026-09-14-PKG-AUTO-THUMB-10-encoding.md) [记录](baselines/auto-thumbnail-encoding-validation.json)
- ENG-LINT-01: [记录](changes/2026-09-14-ENG-LINT-01-manifest-order.md) [记录](baselines/manifest-lint-validation.json)
- CORE-SUBTITLE-OFFSET-01: [记录](changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md) [记录](baselines/subtitle-offset-validation.json)
- PKG-MULTI-SUB-09: [记录](changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md) [记录](baselines/multiple-subtitles-switch-validation.json)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据
## PKG-MULTI-SUB-09 切源显示问题归因完成
失败时字幕cue仍在1至3秒,媒体却停在约0.002秒;新增无插件/候选插件/旧1.2.0
对照并记录原生currentTime写入、事件和Promise状态。首轮WebKit27次切源中旧
核心18次有5次立即seek未到达,两个无插件旧核心组也复现;候选9次全部到达。
正式main三引擎81次切源无失位,但旧WebKit仍有6次在seeking中结算;候选27次
均在原生定位结束后结算并兑现首个seek。确认是已有旧核心寻址问题,候选CORE-19
路径已处理,无需在多字幕插件追加重试或改时钟。原失败及立即调用路径完整留证。
84项正式产物矩阵83断言通过,1项测试未等待全屏进入通知即退出而失败;保留
严格true/false顺序断言,补事件谓词后受影响9项三引擎全通过,不声称重跑84项。
脚本lint与CI50项通过。见[归因记录](changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md)
和[证据](baselines/multiple-subtitles-switch-validation.json)。关闭误归因的SWITCH风险,
无生产、声明、依赖、版本或构建产物修改;249项为177 done、17 doing、55 todo。
任务05仍未完成,下一步处理旧5.1核心的双语渲染适配,再补剩余组合与分发验收。
## CORE-SUBTITLE-OFFSET-01 暂停字幕偏移修复完成
原生video/track探针确认Firefox155暂停时cue时间更新后活动标记陈旧;候选核心
+1 -1
View File
@@ -266,4 +266,4 @@
| AUTO-THUMB-ENCODE-TIMEOUT-01 | resolved / 源码/产物事实 | Pending JPEG encoding has no bounded lifetime after frame readiness finishes | PKG-AUTO-THUMB-10 |
| MULTI-SUB-MARKUP-01 | resolved / 已复现 | Semantic cue nodes receive HTML wrappers inside their annotation fields | PKG-MULTI-SUB-08 |
| MULTI-SUB-OFFSET-01 | resolved / 已复现 | Paused Firefox subtitle offset leaves out-of-time native cues active and captions visible | PKG-MULTI-SUB-05, CORE-SUBTITLE-OFFSET-01 |
| MULTI-SUB-SWITCH-01 | open / 待取证 | Intermittent WebKit core5.3.0 caption loss after selected-track source switch | PKG-MULTI-SUB-05 |
| MULTI-SUB-SWITCH-01 | resolved / 已复现 | Intermittent WebKit core5.3.0 caption loss after selected-track source switch | PKG-MULTI-SUB-05, PKG-MULTI-SUB-09 |
+16 -7
View File
@@ -6028,21 +6028,30 @@
{
"id": "MULTI-SUB-SWITCH-01",
"title": "Intermittent WebKit core5.3.0 caption loss after selected-track source switch",
"confirmation": "unverified",
"workspaceState": "One Windows WebKit26.6 run lost Translation after switchUrl and seek1.5; diagnostic rerun passed without a production change. Root cause and reproducibility are unresolved; do not mark accepted or fixed. Expanded153 offset/lifecycle run again lost the selected caption after source switch on published5.3.0 and5.4.0 in WebKit, while candidate core passed. Root cause remains unclassified; the offset fix does not close this issue.",
"compatibleResolution": "Inspect native track loading and source-switch ordering with deterministic state evidence; distinguish historical host failure from fixture ordering and candidate regression.",
"confirmation": "reproduced",
"workspaceState": "Original failing snapshots kept the1..3 cue intact but video time was near0.002. Independent no-plugin and published1.2/current-plugin WebKit traces reproduce5 old-core first-seek misses, with promises sometimes settled during native seeking. Candidate already waits and honors every first seek. Actual main three-engine probes and corrected normal caption sequencing pass; old race observations remain explicit.",
"compatibleResolution": "Classify the old-host source/native-seek race separately from caption rendering; preserve immediate-call probes, require candidate first seeks, and wait for real native source completion before independent caption-seek assertions.",
"closureCriteria": "Root cause classified with targeted evidence, any required fix validated, and changed candidate/old-core combination rerun without hiding the original failure.",
"status": "open",
"status": "resolved",
"owners": [
"PKG-MULTI-SUB-05"
"PKG-MULTI-SUB-05",
"PKG-MULTI-SUB-09"
],
"evidence": [
"refactor/changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md",
"refactor/baselines/multiple-subtitles-combinations-validation.json",
"test/browser/multiple-subtitles-combinations.spec.js",
"refactor/changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md",
"refactor/baselines/subtitle-offset-validation.json"
]
"refactor/baselines/subtitle-offset-validation.json",
"refactor/changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md",
"refactor/baselines/multiple-subtitles-switch-validation.json",
"test/browser/multiple-subtitles-switch.spec.js"
],
"resolutionEvidence": [
"refactor/changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md",
"refactor/baselines/multiple-subtitles-switch-validation.json"
],
"resolutionRationale": "No-plugin controls reproduce the same miss and a settled seek restores correct old/new captions without changing plugin data. Candidate source code already provides the necessary readiness guard (BASE-LIFE-44/CORE-19 context). This resolves the suspected plugin-switch defect; it does not approve every historical source call or complete package task05."
}
]
}
+23 -1
View File
@@ -2430,7 +2430,8 @@
"dependsOn": [
"PKG-MULTI-SUB-05",
"ENG-07",
"CORE-SUBTITLE-OFFSET-01"
"CORE-SUBTITLE-OFFSET-01",
"PKG-MULTI-SUB-09"
],
"status": "todo",
"risk": "H",
@@ -5157,6 +5158,27 @@
"changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md",
"baselines/subtitle-offset-validation.json"
]
},
{
"id": "PKG-MULTI-SUB-09",
"phase": "5 包迁移:artplayer-plugin-multiple-subtitles",
"title": "定位旧核心切源寻址与字幕显示边界",
"scope": [
"artplayer-plugin-multiple-subtitles",
"artplayer"
],
"dependsOn": [
"PKG-MULTI-SUB-08",
"CORE-19"
],
"status": "done",
"risk": "M",
"deliverable": "无插件及旧新插件对照、真实事件轨迹、原切源失败归因和组合测试顺序",
"acceptance": "保留旧核心立即寻址观察,候选首次寻址与正常字幕组合通过;不把原生时钟失败误归为字幕丢失",
"evidence": [
"changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md",
"baselines/multiple-subtitles-switch-validation.json"
]
}
]
}
+10 -1
View File
@@ -221,7 +221,8 @@ Explicit single-active-cue probes record historical and candidate overlap defect
their passing assertions do not mean simultaneous captions are compatible.
Candidate paused Firefox offsets are corrected by CORE-SUBTITLE-OFFSET-01. Old
Firefox hosts retain explicit defect observations; their behavior is not silently
counted as correct. WebKit source-switch failures remain under investigation.
counted as correct. Task09 classified the apparent WebKit source-switch caption
loss as an old-host seek landing near zero, reproducible without any plugin.
See `refactor/changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md` before changing
expectations. Reports include native active cues and rendered text. The new frozen
5.1.2 core route verifies archive integrity and member hashes before serving.
@@ -235,3 +236,11 @@ without creating an Artplayer instance to compare native timing edits, cue
reinsertion and mode toggling. It records actual time within the required interval,
not a fabricated millisecond-exact seek landing. These diagnostic observations
do not claim every native engine strategy is correct.
`multiple-subtitles-switch.spec.js` records source Promise settlement, native
currentTime writes/events and actual seek landing with no plugin, candidate and
published1.2.0. Old first-seek misses are retained as observations; every candidate
first seek must land. The caption combination fixture separately waits for native
source seeking to finish and for fullscreen-enter notification before testing
subsequent commands. It retains exact caption and enter/exit order assertions;
do not add delays or plugin retries to cover old core readiness behavior.
@@ -84,6 +84,7 @@ for (const core of cores.filter(core => !['published-5.1.2', 'published-5.1.7'].
})
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => (document.fullscreenElement || document.webkitFullscreenElement) === window.art.template.$player)).toBe(true)
await expect.poll(() => page.evaluate(() => window.fullscreenEvents)).toEqual([true])
await expect(page.locator('.art-subtitle-a')).toBeVisible()
expect(await page.evaluate(() => (document.fullscreenElement || document.webkitFullscreenElement).contains(window.art.template.$subtitle))).toBe(true)
await page.evaluate(() => {
@@ -94,6 +95,9 @@ for (const core of cores.filter(core => !['published-5.1.2', 'published-5.1.7'].
await expect(page.locator('.art-subtitle-a')).toHaveCount(0)
await expect(page.locator('.art-subtitle-b')).toHaveText('Translation')
await page.evaluate(() => window.art.switchUrl('/test/pattern.mp4?subtitles-combination'))
// Old core promises can settle during their native restoration seek.
// The immediate-seek path is retained separately in multiple-subtitles-switch.spec.js.
await expect.poll(() => page.evaluate(() => window.art.video.seeking)).toBe(false)
await seek(page, 1.5)
await expect(page.locator('.art-subtitle-b')).toHaveText('Translation')
await page.evaluate(() => window.art.plugins.multipleSubtitles.reset())
@@ -0,0 +1,84 @@
import { hash } from '../../refactor/scripts/releases.mjs'
import { multipleSubtitlesCandidate, multipleSubtitlesHistorical } from '../helpers/multiple-subtitles.js'
import { expect, test } from './fixtures.js'
const candidate = await multipleSubtitlesCandidate()
const published = (await multipleSubtitlesHistorical()).find(item => item.name === 'published-1.2.0-main')
const variants = [null, candidate, published]
for (const core of ['published-5.3.0', 'published', 'candidate']) {
for (const implementation of variants) {
test(`Source seek ordering / ${core} / ${implementation?.name || 'no-plugin'}`, async ({ page, browser }, testInfo) => {
await page.route('**/test/switch-caption.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:01.000 --> 00:03.000\nCaption\n' }))
await page.goto(`/test/player.html?core=${core}`)
if (implementation)
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${implementation.code}; window.multipleFactory = module.exports.default || module.exports; })();` })
await page.evaluate(() => {
window.createPlayer('/test/pattern.mp4')
const video = window.art.video
window.switchTrace = []
window.switchState = () => ({ time: video.currentTime, seeking: video.seeking, duration: video.duration, readyState: video.readyState, paused: video.paused, source: video.currentSrc, active: Array.from(window.art.template.$track.track.activeCues || [], cue => cue.text), displayed: window.art.template.$subtitle.textContent })
const record = (event, value) => window.switchTrace.push({ event, value, ...window.switchState() })
const descriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'currentTime')
Object.defineProperty(video, 'currentTime', {
configurable: true,
get() { return descriptor.get.call(this) },
set(value) {
record('write-currentTime', value)
descriptor.set.call(this, value)
},
})
for (const event of ['loadedmetadata', 'loadeddata', 'canplay', 'seeking', 'seeked', 'emptied', 'pause'])
video.addEventListener(event, () => record(event))
})
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
if (implementation) {
await page.evaluate(async () => {
await window.art.plugins.add(window.multipleFactory({ subtitles: [{ name: 'caption', url: '/test/switch-caption.vtt' }] }))
})
await expect.poll(() => page.evaluate(() => window.art.template.$track.track.cues?.length)).toBe(1)
}
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
await page.locator('#pause').click()
await page.evaluate(() => {
document.querySelector('#play').onclick = () => {
window.art.fullscreen = true
}
})
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => Boolean(document.fullscreenElement || document.webkitFullscreenElement))).toBe(true)
await page.evaluate(() => {
window.art.fullscreen = false
})
await expect.poll(() => page.evaluate(() => Boolean(document.fullscreenElement || document.webkitFullscreenElement))).toBe(false)
const outcomes = []
for (let round = 0; round < 3; round++) {
await page.evaluate(round => window.art.switchUrl(`/test/pattern.mp4?seek-order=${round}`), round)
const settled = await page.evaluate(() => window.switchState())
await page.evaluate(() => new Promise((resolve) => {
window.art.once('video:seeked', resolve)
window.art.seek = 1.5
}))
await expect.poll(() => page.evaluate(() => window.art.video.seeking)).toBe(false)
const firstSeek = await page.evaluate(() => window.switchState())
const landed = Math.abs(firstSeek.time - 1.5) < 0.05
if (!landed) {
await page.evaluate(() => new Promise((resolve) => {
window.art.once('video:seeked', resolve)
window.art.seek = 1.5
}))
await expect.poll(() => page.evaluate(() => window.art.video.seeking)).toBe(false)
}
await expect.poll(() => page.evaluate(() => Math.abs(window.art.currentTime - 1.5))).toBeLessThan(0.05)
if (implementation)
await expect(page.locator('.art-subtitle-caption')).toHaveText('Caption')
outcomes.push({ round, settled, firstSeek, landed, afterSettledSeek: await page.evaluate(() => window.switchState()) })
}
await testInfo.attach('source-seek-order', { contentType: 'application/json', body: JSON.stringify({ core, plugin: implementation?.name || 'none', pluginSha256: implementation ? hash(implementation.code) : null, browser: browser.version(), outcomes, trace: await page.evaluate(() => window.switchTrace), scope: 'Three real source changes with immediate versus settled seek; old misses are recorded, candidate must honor every first seek' }) })
if (core === 'candidate')
expect(outcomes.every(item => item.landed)).toBe(true)
await page.evaluate(() => window.art.destroy())
})
}
}