mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-28 04:49:49 -07:00
fix(overlay): defer autoplay release for untokenized startup priming
- Autoplay-ready gate now ignores untokenized subtitle payloads while tokenization warmup is pending, so playback doesn't resume before the first tokenized cue - Wire isTokenizationReady dep through main.ts and update gate/wiring tests accordingly - Drop stale release/release-notes.md, add changelog entry under changes/
This commit is contained in:
@@ -542,3 +542,79 @@ test('autoplay ready gate passes the pending subtitle signal to the readiness pr
|
||||
[['script-message', 'subminer-autoplay-ready']],
|
||||
);
|
||||
});
|
||||
|
||||
function createTokenizationGateHarness(deps: { isTokenizationReady: () => boolean }) {
|
||||
const commands: Array<Array<string | boolean>> = [];
|
||||
const gate = createAutoplayReadyGate({
|
||||
isAppOwnedFlowInFlight: () => false,
|
||||
getCurrentMediaPath: () => '/media/video.mkv',
|
||||
getCurrentVideoPath: () => null,
|
||||
getPlaybackPaused: () => true,
|
||||
getMpvClient: () =>
|
||||
({
|
||||
connected: true,
|
||||
requestProperty: async () => true,
|
||||
send: ({ command }: { command: Array<string | boolean> }) => {
|
||||
commands.push(command);
|
||||
},
|
||||
}) as never,
|
||||
signalPluginAutoplayReady: () => {
|
||||
commands.push(['script-message', 'subminer-autoplay-ready']);
|
||||
},
|
||||
isTokenizationReady: deps.isTokenizationReady,
|
||||
schedule: (callback) => {
|
||||
queueMicrotask(callback);
|
||||
return 1 as never;
|
||||
},
|
||||
logDebug: () => {},
|
||||
});
|
||||
return { gate, commands };
|
||||
}
|
||||
|
||||
test('autoplay ready gate ignores untokenized signals while tokenization warmup is pending', async () => {
|
||||
const { gate, commands } = createTokenizationGateHarness({
|
||||
isTokenizationReady: () => false,
|
||||
});
|
||||
|
||||
gate.maybeSignalPluginAutoplayReady({ text: '字幕', tokens: null }, { forceWhilePaused: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.deepEqual(commands, []);
|
||||
});
|
||||
|
||||
test('autoplay ready gate releases tokenized signals while tokenization warmup is pending', async () => {
|
||||
const { gate, commands } = createTokenizationGateHarness({
|
||||
isTokenizationReady: () => false,
|
||||
});
|
||||
|
||||
gate.maybeSignalPluginAutoplayReady(
|
||||
{ text: '字幕', tokens: [{ surface: '字幕' }] as never },
|
||||
{ forceWhilePaused: true },
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.deepEqual(
|
||||
commands.filter((command) => command[0] === 'script-message'),
|
||||
[['script-message', 'subminer-autoplay-ready']],
|
||||
);
|
||||
});
|
||||
|
||||
test('autoplay ready gate releases untokenized signals once tokenization warmup is ready', async () => {
|
||||
let tokenizationReady = false;
|
||||
const { gate, commands } = createTokenizationGateHarness({
|
||||
isTokenizationReady: () => tokenizationReady,
|
||||
});
|
||||
|
||||
gate.maybeSignalPluginAutoplayReady({ text: '字幕', tokens: null }, { forceWhilePaused: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.deepEqual(commands, []);
|
||||
|
||||
tokenizationReady = true;
|
||||
gate.maybeSignalPluginAutoplayReady({ text: '字幕', tokens: null }, { forceWhilePaused: true });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.deepEqual(
|
||||
commands.filter((command) => command[0] === 'script-message'),
|
||||
[['script-message', 'subminer-autoplay-ready']],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ export type AutoplayReadyGateDeps = {
|
||||
getPlaybackPaused: () => boolean | null;
|
||||
getMpvClient: () => MpvClientLike | null;
|
||||
signalPluginAutoplayReady: () => void;
|
||||
isTokenizationReady?: () => boolean;
|
||||
requestOverlayPointerRecovery?: () => void;
|
||||
onAutoplayReadyReleased?: (signal: AutoplayReadySignal) => void;
|
||||
isSignalTargetReady?: (signal: AutoplayReadySignal) => boolean;
|
||||
@@ -217,6 +218,15 @@ export function createAutoplayReadyGate(deps: AutoplayReadyGateDeps) {
|
||||
if (!payload.text.trim()) {
|
||||
return;
|
||||
}
|
||||
// Untokenized payloads (startup subtitle priming, tokenizer fallbacks) must
|
||||
// not release the startup pause gate while tokenization warmup is pending —
|
||||
// the tokenized delivery or the post-warmup release signals readiness later.
|
||||
if (payload.tokens === null && deps.isTokenizationReady && !deps.isTokenizationReady()) {
|
||||
deps.logDebug(
|
||||
'[autoplay-ready] ignored untokenized signal while tokenization warmup is pending',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
maybeReleaseAutoplayReadySignal({
|
||||
mediaPath: getSignalMediaPath(),
|
||||
|
||||
Reference in New Issue
Block a user