mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-24 05:16:19 -07:00
fix(anki): honor overlay pause that races timing review setup
- Track overlay pauses per review request so a pause arriving while setup reads mpv's pause state still cancels the playback restore - Ignore cancelPlaybackResume when no review request is in flight - Add a test covering the pause-read race and reset between requests
This commit is contained in:
@@ -686,6 +686,64 @@ for (const paused of [true, false]) {
|
||||
});
|
||||
}
|
||||
|
||||
test('media timing review honors an overlay pause that arrives while setup reads the pause state', async () => {
|
||||
const commands: Array<Array<string | number>> = [];
|
||||
const pauseReads: Array<(paused: boolean) => void> = [];
|
||||
let runtime: ReturnType<typeof createMediaTimingReviewRuntime>;
|
||||
runtime = createMediaTimingReviewRuntime({
|
||||
getMpvClient: () => ({
|
||||
connected: true,
|
||||
currentVideoPath: '/video/show.mkv',
|
||||
requestProperty: (name) =>
|
||||
name === 'pause'
|
||||
? new Promise((resolve) => pauseReads.push(resolve))
|
||||
: Promise.resolve(name === 'duration' ? 100 : null),
|
||||
send: ({ command }) => commands.push(command),
|
||||
}),
|
||||
getCurrentMediaPath: () => '/video/show.mkv',
|
||||
getMpvExecutablePath: () => 'mpv',
|
||||
generateWaveform: async () => [],
|
||||
createPreviewSession: () => ({
|
||||
start: async () => undefined,
|
||||
play: async () => undefined,
|
||||
stop: async () => undefined,
|
||||
onPlaybackEnded: () => undefined,
|
||||
dispose: () => undefined,
|
||||
}),
|
||||
openModal: async (payload) => {
|
||||
queueMicrotask(() => {
|
||||
runtime.resolveReview({ reviewId: payload.reviewId, decision: { action: 'use-original' } });
|
||||
});
|
||||
return true;
|
||||
},
|
||||
showStatus: () => undefined,
|
||||
});
|
||||
const request = {
|
||||
kind: 'sentence' as const,
|
||||
text: '字幕',
|
||||
startTime: 10,
|
||||
endTime: 12,
|
||||
audioPadding: 0,
|
||||
maxMediaDuration: 30,
|
||||
};
|
||||
|
||||
const first = runtime.requestReview(request);
|
||||
runtime.cancelPlaybackResume();
|
||||
// mpv answered the read before the overlay's pause reached it.
|
||||
pauseReads[0]!(false);
|
||||
await first;
|
||||
assert.deepEqual(commands, [['set_property', 'pause', 'yes']]);
|
||||
|
||||
commands.length = 0;
|
||||
const second = runtime.requestReview(request);
|
||||
pauseReads[1]!(false);
|
||||
await second;
|
||||
assert.deepEqual(commands, [
|
||||
['set_property', 'pause', 'yes'],
|
||||
['set_property', 'pause', 'no'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('media timing review watchdog falls back when the renderer stops responding', async () => {
|
||||
const { pendingDecision } = await startActiveMediaTimingReview({ decisionTimeoutMs: 0 });
|
||||
|
||||
|
||||
@@ -284,6 +284,8 @@ export function createMediaTimingReviewRuntime(deps: MediaTimingReviewRuntimeDep
|
||||
let currentRequest: ReviewRequestLifecycle | null = null;
|
||||
let pendingPauseRestore: ReviewMpvClient | null = null;
|
||||
let resumeDeferred = false;
|
||||
/** Set when the overlay pauses during the current request, even before its setup reads the pause state. */
|
||||
let restoreCancelled = false;
|
||||
|
||||
function restorePendingPlayback(): void {
|
||||
const mpvClient = pendingPauseRestore;
|
||||
@@ -318,7 +320,9 @@ export function createMediaTimingReviewRuntime(deps: MediaTimingReviewRuntimeDep
|
||||
* not resume it: drops both a held overlay resume and the review's own restore.
|
||||
*/
|
||||
function cancelPlaybackResume(): void {
|
||||
if (!currentRequest) return;
|
||||
resumeDeferred = false;
|
||||
restoreCancelled = true;
|
||||
if (active) active.restorePlayback = false;
|
||||
}
|
||||
|
||||
@@ -441,7 +445,8 @@ export function createMediaTimingReviewRuntime(deps: MediaTimingReviewRuntimeDep
|
||||
const [pauseRaw, durationRaw, audioTrackRaw, volumeRaw, resolvedSource, videoSource] =
|
||||
setup.values;
|
||||
const pauseState = booleanProperty(pauseRaw);
|
||||
pendingPauseRestore = pauseState === false ? mpvClient : null;
|
||||
// The pause read can predate an overlay pause that arrived during setup.
|
||||
pendingPauseRestore = pauseState === false && !restoreCancelled ? mpvClient : null;
|
||||
mpvClient.send({ command: ['set_property', 'pause', 'yes'] });
|
||||
if (lifecycle.isCancelled()) {
|
||||
restorePendingPlayback();
|
||||
@@ -559,6 +564,7 @@ export function createMediaTimingReviewRuntime(deps: MediaTimingReviewRuntimeDep
|
||||
}
|
||||
const lifecycle = createReviewRequestLifecycle();
|
||||
currentRequest = lifecycle;
|
||||
restoreCancelled = false;
|
||||
try {
|
||||
return await runReview(request, lifecycle);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user