mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-23 05:16:23 -07:00
fix(anki): keep playback paused after overlay pause during timing review
- Treat overlay pause requests during an open media timing review as final: drop held resumes and the review's own playback restore - Add cancelPlaybackResume to the media timing review runtime - Test both paused and playing starting states
This commit is contained in:
+12
-8
@@ -2051,20 +2051,24 @@ function isExplicitMpvSeekCommand(command: readonly (string | number)[]): boolea
|
|||||||
return command[0] === 'seek' || command[0] === 'sub-seek';
|
return command[0] === 'seek' || command[0] === 'sub-seek';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMpvResumeCommand(command: readonly (string | number)[]): boolean {
|
function getMpvPauseCommandValue(command: readonly (string | number)[]): 'yes' | 'no' | null {
|
||||||
return (
|
if ((command[0] !== 'set_property' && command[0] !== 'set') || command[1] !== 'pause') {
|
||||||
(command[0] === 'set_property' || command[0] === 'set') &&
|
return null;
|
||||||
command[1] === 'pause' &&
|
}
|
||||||
command[2] === 'no'
|
return command[2] === 'yes' || command[2] === 'no' ? command[2] : null;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendRendererMpvCommand(rawCommand: (string | number)[]): void {
|
function sendRendererMpvCommand(rawCommand: (string | number)[]): void {
|
||||||
// Overlay auto-pause releases (popup closed, hover left) must not resume playback
|
// Overlay auto-pause releases (popup closed, hover left) must not resume playback
|
||||||
// behind an open timing review; the review applies them when it closes.
|
// behind an open timing review; the review applies them when it closes. A pause
|
||||||
if (isMpvResumeCommand(rawCommand) && mediaTimingReviewRuntime.deferPlaybackResume()) {
|
// request during the review keeps playback paused after it closes.
|
||||||
|
const pauseValue = getMpvPauseCommandValue(rawCommand);
|
||||||
|
if (pauseValue === 'no' && mediaTimingReviewRuntime.deferPlaybackResume()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (pauseValue === 'yes') {
|
||||||
|
mediaTimingReviewRuntime.cancelPlaybackResume();
|
||||||
|
}
|
||||||
const command =
|
const command =
|
||||||
resolveSanitizedSubtitleSeekCommand(
|
resolveSanitizedSubtitleSeekCommand(
|
||||||
rawCommand,
|
rawCommand,
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ async function startActiveMediaTimingReview(
|
|||||||
decisionTimeoutMs?: number;
|
decisionTimeoutMs?: number;
|
||||||
generateWaveform?: () => Promise<number[]>;
|
generateWaveform?: () => Promise<number[]>;
|
||||||
play?: () => Promise<void>;
|
play?: () => Promise<void>;
|
||||||
|
paused?: boolean;
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const previewCalls: Array<[number, number]> = [];
|
const previewCalls: Array<[number, number]> = [];
|
||||||
@@ -107,7 +108,8 @@ async function startActiveMediaTimingReview(
|
|||||||
getMpvClient: () => ({
|
getMpvClient: () => ({
|
||||||
connected: true,
|
connected: true,
|
||||||
currentVideoPath: '/video/show.mkv',
|
currentVideoPath: '/video/show.mkv',
|
||||||
requestProperty: async (name) => (name === 'duration' ? 100 : name === 'pause' ? true : null),
|
requestProperty: async (name) =>
|
||||||
|
name === 'duration' ? 100 : name === 'pause' ? (options.paused ?? true) : null,
|
||||||
send: ({ command }) => commands.push(command),
|
send: ({ command }) => commands.push(command),
|
||||||
}),
|
}),
|
||||||
getCurrentMediaPath: () => '/video/show.mkv',
|
getCurrentMediaPath: () => '/video/show.mkv',
|
||||||
@@ -668,6 +670,22 @@ test('media timing review holds overlay resume requests until the review closes'
|
|||||||
assert.equal(runtime.deferPlaybackResume(), false);
|
assert.equal(runtime.deferPlaybackResume(), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const paused of [true, false]) {
|
||||||
|
test(`media timing review stays paused after an overlay pause request (paused before: ${paused})`, async () => {
|
||||||
|
const { runtime, payload, pendingDecision, commands } = await startActiveMediaTimingReview({
|
||||||
|
paused,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(runtime.deferPlaybackResume(), true);
|
||||||
|
runtime.cancelPlaybackResume();
|
||||||
|
|
||||||
|
runtime.resolveReview({ reviewId: payload.reviewId, decision: { action: 'use-original' } });
|
||||||
|
await pendingDecision;
|
||||||
|
|
||||||
|
assert.deepEqual(commands, [['set_property', 'pause', 'yes']]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
test('media timing review watchdog falls back when the renderer stops responding', async () => {
|
test('media timing review watchdog falls back when the renderer stops responding', async () => {
|
||||||
const { pendingDecision } = await startActiveMediaTimingReview({ decisionTimeoutMs: 0 });
|
const { pendingDecision } = await startActiveMediaTimingReview({ decisionTimeoutMs: 0 });
|
||||||
|
|
||||||
|
|||||||
@@ -313,6 +313,15 @@ export function createMediaTimingReviewRuntime(deps: MediaTimingReviewRuntimeDep
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlay asked to pause while a review holds playback, so closing the review must
|
||||||
|
* not resume it: drops both a held overlay resume and the review's own restore.
|
||||||
|
*/
|
||||||
|
function cancelPlaybackResume(): void {
|
||||||
|
resumeDeferred = false;
|
||||||
|
if (active) active.restorePlayback = false;
|
||||||
|
}
|
||||||
|
|
||||||
function ensureWindow(
|
function ensureWindow(
|
||||||
review: ActiveReview,
|
review: ActiveReview,
|
||||||
range: RemoteMediaWindowRange,
|
range: RemoteMediaWindowRange,
|
||||||
@@ -782,6 +791,7 @@ export function createMediaTimingReviewRuntime(deps: MediaTimingReviewRuntimeDep
|
|||||||
stopPreview,
|
stopPreview,
|
||||||
resolveReview,
|
resolveReview,
|
||||||
deferPlaybackResume,
|
deferPlaybackResume,
|
||||||
|
cancelPlaybackResume,
|
||||||
dispose,
|
dispose,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user