mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
test(multiple-subtitles): checkpoint native seek diagnostics [PKG-MULTI-SUB-05]
This commit is contained in:
1 parent
318de37e8a
commit
a8d19ce5ef
10 files changed
+7266
-8
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,63 @@
|
||||
# PKG-MULTI-SUB-05 checkpoint: source-seek diagnostics (not complete)
|
||||
|
||||
Task11 is committed as318de37e8 and its commit audit passed. This checkpoint
|
||||
continues the reopened old-core source-seek issue without changing production
|
||||
code, declarations, dependencies or built artifacts.
|
||||
|
||||
## Added evidence
|
||||
|
||||
The optional diagnostics capture native events, public seek/fullscreen events,
|
||||
monotonic times, native position/duration/seeking, active cues and switch Promise
|
||||
settlement. Full mode forwards the original native currentTime/playbackRate
|
||||
accessors and records writes/stacks; events mode leaves those properties intact.
|
||||
Both modes are opt-in. Diagnostic side observers handle rejection while the
|
||||
original returned Promise still rejects for the test consumer.
|
||||
|
||||
Initial WebKit serial3 and repeated9 cases pass their caption assertions. The
|
||||
repeated traces show all six old-core switches resolve while seeking=true;
|
||||
restoration seeked is delivered30..50ms later. The three candidate switches wait
|
||||
for restoration. No playback-rate assignment was observed in these source steps.
|
||||
The successful later user writes occurred after the prior native seeked event.
|
||||
|
||||
The actual-main events-only matrix passes105 assertions, including explicitly
|
||||
labelled historical overlap/offset defect observations. It again shows early
|
||||
old-core settlement, including WebKit native seeked43/46ms later. This is not
|
||||
proof the previously missed seeks were repaired: observation may affect timing,
|
||||
and no failing native write trace was captured in those runs.
|
||||
|
||||
The subsequent actual-main matrix with diagnostics disabled has62 passes and one
|
||||
failure on old5.3.0/WebKit. At failure, media time is0.0015139, the1..3-second cue
|
||||
is intact, active cues are empty, and the player is still alive. The candidate
|
||||
and other old-core cases pass in that run. This reproduces the same near-zero
|
||||
position category previously seen on old5.4.0; it does not implicate ASS conversion
|
||||
or prove the precise internal native overwrite sequence.
|
||||
|
||||
## Separate restoration-event control
|
||||
|
||||
`source-restoration.js` installs a native metadata listener before switchUrl.
|
||||
After the core's metadata handler, it waits for native seeked if seeking is active,
|
||||
and also waits for the original switch Promise. It cleans both listeners on
|
||||
completion or failure. There are no fixed delays, seek retries or source rewrites.
|
||||
It is enabled only with ARTPLAYER_SOURCE_RESTORE_EVENT=1; the default failing
|
||||
property-based path and existing immediate-call ordering probe remain available.
|
||||
|
||||
With diagnostics disabled, the event control passes27 cases: three repetitions
|
||||
of three core inputs across three desktop engines using actual main artifacts.
|
||||
This supports independently verifying captions after native restoration. It is
|
||||
not proof of a production fix, or sufficient evidence to close the original
|
||||
immediate-seek failure. Do not make the normal path green by relabelling this
|
||||
control as the original public API behavior.
|
||||
|
||||
Reports, input/artifact hashes, selected modes, timings and remaining failures are
|
||||
in [the checkpoint evidence](../baselines/multiple-subtitles-seek-diagnostics.json).
|
||||
The browser README documents flags. Explicit test/helper lint, CI50 and plan/risk
|
||||
checks pass. No source or package build was needed for these test-only additions.
|
||||
|
||||
## Remaining work
|
||||
|
||||
MULTI-SUB-SWITCH-01 stays open and task05 stays doing. The next diagnostic step is
|
||||
to capture the failed default path's restoration completion versus subsequent
|
||||
native seek without assuming that property polling equals event completion.
|
||||
Keep candidate public switch guarantees separate from immutable old-host defects;
|
||||
do not patch the shared media clock from the subtitle plugin. Broader historical
|
||||
profile/device acceptance and task06 distribution gates remain incomplete.
|
||||
+1
-1
@@ -558,7 +558,7 @@
|
||||
- PKG-MULTI-SUB-04: [记录](baselines/multiple-subtitles-runtime-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-runtime-types.md) [记录](baselines/multiple-subtitles-public-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-public-types.md) [记录](type-compatibility-policy.md) [记录](baselines/multiple-subtitles-approved-types.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-04-approved-types.md)
|
||||
- PKG-MULTI-SUB-07: [记录](baselines/multiple-subtitles-timestamps.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-07-timestamps.md)
|
||||
- PKG-MULTI-SUB-08: [记录](changes/2026-09-14-PKG-MULTI-SUB-08-entities.md) [记录](baselines/multiple-subtitles-entities-validation.json)
|
||||
- PKG-MULTI-SUB-05: [记录](changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md) [记录](baselines/multiple-subtitles-combinations-validation.json) [记录](baselines/multiple-subtitles-cores.json)
|
||||
- PKG-MULTI-SUB-05: [记录](changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md) [记录](baselines/multiple-subtitles-combinations-validation.json) [记录](baselines/multiple-subtitles-cores.json) [记录](changes/2026-09-14-PKG-MULTI-SUB-05-seek-diagnostics.md) [记录](baselines/multiple-subtitles-seek-diagnostics.json)
|
||||
- PKG-JASSUB-01: [记录](baselines/jassub-release.json) [记录](baselines/jassub-vendor.json) [记录](baselines/jassub-font-metadata.json) [记录](baselines/jassub-contract.md) [记录](baselines/jassub-contract-validation.json) [记录](changes/2026-09-13-PKG-JASSUB-01-baseline.md) [记录](baselines/jassub-provenance.json) [记录](baselines/jassub-provenance-validation.json) [记录](changes/2026-09-14-PKG-JASSUB-01-provenance.md)
|
||||
- PKG-JASSUB-02: [记录](changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md) [记录](baselines/jassub-failures-validation.json)
|
||||
- PKG-JASSUB-03: [记录](changes/2026-09-14-PKG-JASSUB-03-registration.md) [记录](baselines/jassub-registration-validation.json)
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MULTI-SUB-05 切源时序诊断检查点(仍未完成)
|
||||
|
||||
新增可选原生属性/事件跟踪和独立恢复事件等待对照。WebKit串行3项、重复9项及
|
||||
三引擎被动事件矩阵105项通过断言,但旧核心仍提前结算,WebKit恢复seeked晚30至50ms;
|
||||
候选核心会等待恢复。关闭诊断后63项中62通过,旧5.3.0/WebKit再次停在0.0015139秒,
|
||||
字幕cue仍完整;没有获得失败瞬间的原生写入轨迹,不能据带诊断的通过结果关闭风险。
|
||||
显式等待metadata后的原生恢复seeked对照27项通过,仅说明完成恢复后的字幕路径有效,
|
||||
不修改默认测试路径或旧公共API。即时调用探针、此前失败和MULTI-SUB-SWITCH-01保持open。
|
||||
见[检查点](changes/2026-09-14-PKG-MULTI-SUB-05-seek-diagnostics.md)和
|
||||
[证据](baselines/multiple-subtitles-seek-diagnostics.json)。本轮仅测试和文档修改,产物未变,
|
||||
脚本lint/CI50/计划风险检查通过;仍为179 done、17 doing、55 todo。task05/06未完成。
|
||||
保留原生失败时序定位入口;下一轮转回Auto Thumbnail剩余源码整理,再继续包级组合门槛。
|
||||
|
||||
## PKG-MULTI-SUB-11 旧发布核心 ASS 转换适配完成
|
||||
|
||||
实际5.1.2 tarball证明只有main转换器丢失模板换行,同版legacy与源码正确;新增
|
||||
|
||||
+7
-3
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"task": "BASE-07",
|
||||
"sourceCommit": "e01f39ff",
|
||||
"updatedAt": "2026-09-14T05:41:02.580Z",
|
||||
"updatedAt": "2026-09-14T06:00:35.506Z",
|
||||
"note": "Open means closure evidence is outstanding; source-observed and unverified are not reproduced runtime bugs. A source issue already mitigated in the workspace still requires candidate regression evidence. No accepted-with-scope item is implied by baseline task completion.",
|
||||
"items": [
|
||||
{
|
||||
@@ -6034,7 +6034,7 @@
|
||||
"id": "MULTI-SUB-SWITCH-01",
|
||||
"title": "Intermittent WebKit core5.3.0 caption loss after selected-track source switch",
|
||||
"confirmation": "reproduced",
|
||||
"workspaceState": "Reopened during task10: both source and actual-main matrices fail old5.4.0/WebKit caption after switch despite waiting seeking=false. Source failure native time0.001494, intact1..3 cue, active=[]; task09 attribution remains supported but the later-seek workaround is insufficient. Candidate core and old5.1 display regressions pass. Task05 must diagnose remaining old-host ordering, without hiding these failures.",
|
||||
"workspaceState": "Still open after task11: property traces3+9 and passive105 cases pass while confirming early old-core settlement; default untraced63 has another near-zero seek miss on5.3.0/WebKit (time0.0015139, intact1..3 cue, no active cue). No failing native-write trace yet. Opt-in metadata/restoration-seeked control passes27 cases but is not a production/API fix; default and immediate-call probes remain unchanged. Candidate source guarantees pass. Task05 still owns remaining diagnosis and acceptance.",
|
||||
"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",
|
||||
@@ -6052,7 +6052,11 @@
|
||||
"refactor/baselines/multiple-subtitles-switch-validation.json",
|
||||
"test/browser/multiple-subtitles-switch.spec.js",
|
||||
"refactor/changes/2026-09-14-PKG-MULTI-SUB-10-legacy-captions.md",
|
||||
"refactor/baselines/multiple-subtitles-legacy-validation.json"
|
||||
"refactor/baselines/multiple-subtitles-legacy-validation.json",
|
||||
"refactor/changes/2026-09-14-PKG-MULTI-SUB-05-seek-diagnostics.md",
|
||||
"refactor/baselines/multiple-subtitles-seek-diagnostics.json",
|
||||
"test/browser/source-seek-diagnostics.js",
|
||||
"test/browser/source-restoration.js"
|
||||
],
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-14-PKG-MULTI-SUB-09-switch-order.md",
|
||||
|
||||
+3
-1
@@ -2417,7 +2417,9 @@
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md",
|
||||
"baselines/multiple-subtitles-combinations-validation.json",
|
||||
"baselines/multiple-subtitles-cores.json"
|
||||
"baselines/multiple-subtitles-cores.json",
|
||||
"changes/2026-09-14-PKG-MULTI-SUB-05-seek-diagnostics.md",
|
||||
"baselines/multiple-subtitles-seek-diagnostics.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -283,3 +283,21 @@ multiline text/HTML, overlapping translation, selection/reset and empty interval
|
||||
on5.1.2/5.1.7/5.3.0/5.4.0/candidate. The attachment records the unchanged original
|
||||
host converter output alongside actual native cues and browser/artifact hashes.
|
||||
It covers desktop video/track playback, not full ASS layout or physical devices.
|
||||
|
||||
The multiple-subtitles combination suite has opt-in source-seek diagnostics:
|
||||
|
||||
- `ARTPLAYER_SOURCE_SEEK_TRACE=events` records native and public events, clocks,
|
||||
duration, active cues and switch settlement without replacing media properties.
|
||||
- `ARTPLAYER_SOURCE_SEEK_TRACE=1` additionally forwards native `currentTime` and
|
||||
`playbackRate` accessors while recording writes/stacks. The page owns these
|
||||
diagnostic listeners and descriptors; do not use this helper in production.
|
||||
- `ARTPLAYER_SOURCE_RESTORE_EVENT=1` selects a separate control that installs a
|
||||
metadata listener before switching and, when restoration is seeking, waits for
|
||||
its native seeked event as well as the original switch Promise. This tests caption
|
||||
behavior after restoration; it does not repair the old public switch contract.
|
||||
|
||||
With no flags, the original Promise plus seeking-property path remains unchanged.
|
||||
The immediate-call ordering probe also remains intact. Record the chosen flags
|
||||
alongside artifacts: diagnostic overhead may change an intermittent race, and
|
||||
passing the event control must not replace the original failure evidence. The
|
||||
combination attachment includes the selected modes and optional native trace.
|
||||
@@ -1,6 +1,9 @@
|
||||
import process from 'node:process'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { multipleSubtitlesCandidate, multipleSubtitlesHistorical } from '../helpers/multiple-subtitles.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
import { switchAfterNativeRestoration } from './source-restoration.js'
|
||||
import { installSeekDiagnostics } from './source-seek-diagnostics.js'
|
||||
|
||||
const candidate = await multipleSubtitlesCandidate()
|
||||
const historical = (await multipleSubtitlesHistorical()).filter(item => /^published-.*-main$/.test(item.name))
|
||||
@@ -15,7 +18,7 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
return null
|
||||
const track = art.template.$track?.track
|
||||
const read = cue => ({ start: cue.startTime, end: cue.endTime, text: cue.text })
|
||||
return { destroyed: art.isDestroy, paused: art.video.paused, time: art.currentTime, offset: art.subtitleOffset, cues: Array.from(track?.cues || [], read), active: Array.from(track?.activeCues || [], read), displayed: art.template.$subtitle?.textContent }
|
||||
return { destroyed: art.isDestroy, paused: art.video.paused, time: art.currentTime, seeking: art.video.seeking, duration: art.video.duration, readyState: art.video.readyState, offset: art.subtitleOffset, cues: Array.from(track?.cues || [], read), active: Array.from(track?.activeCues || [], read), displayed: art.template.$subtitle?.textContent, trace: window.sourceSeekTrace }
|
||||
})
|
||||
await testInfo.attach('multiple-combination-final-state', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
})
|
||||
@@ -27,7 +30,9 @@ async function prepare(page, core, implementation, browser, testInfo) {
|
||||
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'))
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await testInfo.attach('multiple-combination-inputs', { contentType: 'application/json', body: JSON.stringify({ core, plugin: implementation.name, pluginSha256: hash(implementation.code), browser: browser.version(), scope: 'Desktop engines; 5.3.0 is adjacent stable, not the unavailable 5.3.1 release; no minimum-version or physical-device claim' }) })
|
||||
if (['1', 'events'].includes(process.env.ARTPLAYER_SOURCE_SEEK_TRACE))
|
||||
await installSeekDiagnostics(page, process.env.ARTPLAYER_SOURCE_SEEK_TRACE === '1')
|
||||
await testInfo.attach('multiple-combination-inputs', { contentType: 'application/json', body: JSON.stringify({ core, plugin: implementation.name, pluginSha256: hash(implementation.code), browser: browser.version(), traceMode: process.env.ARTPLAYER_SOURCE_SEEK_TRACE || 'off', sourceRestoration: process.env.ARTPLAYER_SOURCE_RESTORE_EVENT === '1' ? 'native-event-control' : 'promise-and-seeking-property', scope: 'Desktop engines; 5.3.0 is adjacent stable, not the unavailable 5.3.1 release; no minimum-version or physical-device claim' }) })
|
||||
}
|
||||
|
||||
async function seek(page, time) {
|
||||
@@ -94,7 +99,22 @@ for (const core of cores.filter(core => !['published-5.1.2', 'published-5.1.7'].
|
||||
await page.evaluate(() => window.art.plugins.multipleSubtitles.tracks(['b']))
|
||||
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'))
|
||||
if (process.env.ARTPLAYER_SOURCE_RESTORE_EVENT === '1') {
|
||||
await switchAfterNativeRestoration(page, '/test/pattern.mp4?subtitles-combination')
|
||||
}
|
||||
else {
|
||||
await page.evaluate(() => {
|
||||
window.recordSourceSeek?.('before-switch')
|
||||
const pending = window.art.switchUrl('/test/pattern.mp4?subtitles-combination')
|
||||
if (window.recordSourceSeek) {
|
||||
void pending.then(
|
||||
() => window.recordSourceSeek('switch-resolved'),
|
||||
error => window.recordSourceSeek('switch-rejected', String(error)),
|
||||
)
|
||||
}
|
||||
return pending
|
||||
})
|
||||
}
|
||||
// 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)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
export async function switchAfterNativeRestoration(page, url) {
|
||||
await page.evaluate((url) => {
|
||||
const art = window.art
|
||||
if (url === art.url)
|
||||
return art.switchUrl(url)
|
||||
const video = art.video
|
||||
let resolveRestored
|
||||
const restored = new Promise((resolve) => {
|
||||
resolveRestored = resolve
|
||||
})
|
||||
const onSeeked = () => resolveRestored()
|
||||
const onMetadata = () => {
|
||||
if (video.seeking)
|
||||
video.addEventListener('seeked', onSeeked, { once: true })
|
||||
else
|
||||
resolveRestored()
|
||||
}
|
||||
const cleanup = () => {
|
||||
video.removeEventListener('loadedmetadata', onMetadata)
|
||||
video.removeEventListener('seeked', onSeeked)
|
||||
}
|
||||
video.addEventListener('loadedmetadata', onMetadata, { once: true })
|
||||
try {
|
||||
return Promise.all([art.switchUrl(url), restored]).then(() => undefined).finally(cleanup)
|
||||
}
|
||||
catch (error) {
|
||||
cleanup()
|
||||
throw error
|
||||
}
|
||||
}, url)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
export async function installSeekDiagnostics(page, properties = true) {
|
||||
await page.evaluate((properties) => {
|
||||
const art = window.art
|
||||
const video = art.video
|
||||
window.sourceSeekTrace = []
|
||||
const state = () => ({ clock: performance.now(), time: video.currentTime, duration: video.duration, seeking: video.seeking, paused: video.paused, rate: video.playbackRate, readyState: video.readyState, source: video.currentSrc, active: Array.from(art.template.$track.track.activeCues || [], cue => cue.text) })
|
||||
const record = (event, detail) => window.sourceSeekTrace.push({ event, detail, ...state() })
|
||||
for (const property of properties ? ['currentTime', 'playbackRate'] : []) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, property)
|
||||
Object.defineProperty(video, property, {
|
||||
configurable: true,
|
||||
get() { return descriptor.get.call(this) },
|
||||
set(value) {
|
||||
record(`before:${property}`, { value, stack: new Error('Native media property write').stack })
|
||||
descriptor.set.call(this, value)
|
||||
record(`after:${property}`, { value })
|
||||
},
|
||||
})
|
||||
}
|
||||
for (const event of ['loadstart', 'loadedmetadata', 'loadeddata', 'canplay', 'seeking', 'seeked', 'emptied', 'pause', 'ratechange', 'timeupdate'])
|
||||
video.addEventListener(event, () => record(`native:${event}`))
|
||||
for (const event of ['restart', 'seek', 'fullscreen'])
|
||||
art.on(event, (...values) => record(`art:${event}`, values))
|
||||
window.recordSourceSeek = record
|
||||
record('installed')
|
||||
}, properties)
|
||||
}
|
||||
Reference in new issue
Block a user