mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 03:16:46 -07:00
fix: flush playback position before media path clear
This commit is contained in:
@@ -87,6 +87,7 @@ test('media path change handler reports stop for empty path and probes media key
|
||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||
ensureAnilistMediaGuess: (mediaKey) => calls.push(`guess:${mediaKey}`),
|
||||
syncImmersionMediaState: () => calls.push('sync'),
|
||||
flushPlaybackPositionOnMediaPathClear: () => calls.push('flush-playback'),
|
||||
scheduleCharacterDictionarySync: () => calls.push('dict-sync'),
|
||||
signalAutoplayReadyIfWarm: (path) => calls.push(`autoplay:${path}`),
|
||||
refreshDiscordPresence: () => calls.push('presence'),
|
||||
@@ -94,6 +95,7 @@ test('media path change handler reports stop for empty path and probes media key
|
||||
|
||||
handler({ path: '' });
|
||||
assert.deepEqual(calls, [
|
||||
'flush-playback',
|
||||
'path:',
|
||||
'stopped',
|
||||
'restore-mpv-sub',
|
||||
@@ -116,6 +118,7 @@ test('media path change handler signals autoplay-ready fast path for warm non-em
|
||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||
ensureAnilistMediaGuess: (mediaKey) => calls.push(`guess:${mediaKey}`),
|
||||
syncImmersionMediaState: () => calls.push('sync'),
|
||||
flushPlaybackPositionOnMediaPathClear: () => calls.push('flush-playback'),
|
||||
scheduleCharacterDictionarySync: () => calls.push('dict-sync'),
|
||||
signalAutoplayReadyIfWarm: (path) => calls.push(`autoplay:${path}`),
|
||||
refreshDiscordPresence: () => calls.push('presence'),
|
||||
@@ -133,6 +136,35 @@ test('media path change handler signals autoplay-ready fast path for warm non-em
|
||||
]);
|
||||
});
|
||||
|
||||
test('media path change handler ignores playback flush for non-empty path', () => {
|
||||
const calls: string[] = [];
|
||||
const handler = createHandleMpvMediaPathChangeHandler({
|
||||
updateCurrentMediaPath: (path) => calls.push(`path:${path}`),
|
||||
reportJellyfinRemoteStopped: () => calls.push('stopped'),
|
||||
restoreMpvSubVisibility: () => calls.push('restore-mpv-sub'),
|
||||
getCurrentAnilistMediaKey: () => null,
|
||||
resetAnilistMediaTracking: (mediaKey) => calls.push(`reset:${String(mediaKey)}`),
|
||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||
ensureAnilistMediaGuess: (mediaKey) => calls.push(`guess:${mediaKey}`),
|
||||
syncImmersionMediaState: () => calls.push('sync'),
|
||||
flushPlaybackPositionOnMediaPathClear: () => calls.push('flush-playback'),
|
||||
scheduleCharacterDictionarySync: () => calls.push('dict-sync'),
|
||||
signalAutoplayReadyIfWarm: (path) => calls.push(`autoplay:${path}`),
|
||||
refreshDiscordPresence: () => calls.push('presence'),
|
||||
});
|
||||
|
||||
handler({ path: '/tmp/video.mkv' });
|
||||
assert.ok(!calls.includes('flush-playback'));
|
||||
assert.deepEqual(calls, [
|
||||
'path:/tmp/video.mkv',
|
||||
'reset:null',
|
||||
'sync',
|
||||
'dict-sync',
|
||||
'autoplay:/tmp/video.mkv',
|
||||
'presence',
|
||||
]);
|
||||
});
|
||||
|
||||
test('media title change handler clears guess state without re-scheduling character dictionary sync', () => {
|
||||
const calls: string[] = [];
|
||||
const deps: Parameters<typeof createHandleMpvMediaTitleChangeHandler>[0] & {
|
||||
|
||||
@@ -53,10 +53,14 @@ export function createHandleMpvMediaPathChangeHandler(deps: {
|
||||
syncImmersionMediaState: () => void;
|
||||
scheduleCharacterDictionarySync?: () => void;
|
||||
signalAutoplayReadyIfWarm?: (path: string) => void;
|
||||
flushPlaybackPositionOnMediaPathClear?: (mediaPath: string) => void;
|
||||
refreshDiscordPresence: () => void;
|
||||
}) {
|
||||
return ({ path }: { path: string | null }): void => {
|
||||
const normalizedPath = typeof path === 'string' ? path : '';
|
||||
if (!normalizedPath) {
|
||||
deps.flushPlaybackPositionOnMediaPathClear?.(normalizedPath);
|
||||
}
|
||||
deps.updateCurrentMediaPath(normalizedPath);
|
||||
if (!normalizedPath) {
|
||||
deps.reportJellyfinRemoteStopped();
|
||||
|
||||
@@ -44,6 +44,7 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
||||
maybeProbeAnilistDuration: (mediaKey) => calls.push(`probe:${mediaKey}`),
|
||||
ensureAnilistMediaGuess: (mediaKey) => calls.push(`guess:${mediaKey}`),
|
||||
syncImmersionMediaState: () => calls.push('sync-immersion'),
|
||||
flushPlaybackPositionOnMediaPathClear: () => calls.push('flush-playback'),
|
||||
|
||||
updateCurrentMediaTitle: (title) => calls.push(`media-title:${title}`),
|
||||
resetAnilistMediaGuessState: () => calls.push('reset-guess-state'),
|
||||
@@ -86,4 +87,6 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
||||
assert.ok(calls.includes('progress:normal'));
|
||||
assert.ok(calls.includes('progress:force'));
|
||||
assert.ok(calls.includes('presence-refresh'));
|
||||
assert.ok(calls.includes('sync-immersion'));
|
||||
assert.ok(calls.includes('flush-playback'));
|
||||
});
|
||||
|
||||
@@ -56,6 +56,7 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
||||
ensureAnilistMediaGuess: (mediaKey: string) => void;
|
||||
syncImmersionMediaState: () => void;
|
||||
signalAutoplayReadyIfWarm?: (path: string) => void;
|
||||
flushPlaybackPositionOnMediaPathClear?: (mediaPath: string) => void;
|
||||
|
||||
updateCurrentMediaTitle: (title: string) => void;
|
||||
resetAnilistMediaGuessState: () => void;
|
||||
@@ -114,6 +115,8 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
||||
maybeProbeAnilistDuration: (mediaKey) => deps.maybeProbeAnilistDuration(mediaKey),
|
||||
ensureAnilistMediaGuess: (mediaKey) => deps.ensureAnilistMediaGuess(mediaKey),
|
||||
syncImmersionMediaState: () => deps.syncImmersionMediaState(),
|
||||
flushPlaybackPositionOnMediaPathClear: (mediaPath) =>
|
||||
deps.flushPlaybackPositionOnMediaPathClear?.(mediaPath),
|
||||
signalAutoplayReadyIfWarm: (path) => deps.signalAutoplayReadyIfWarm?.(path),
|
||||
scheduleCharacterDictionarySync: () => deps.scheduleCharacterDictionarySync?.(),
|
||||
refreshDiscordPresence: () => deps.refreshDiscordPresence(),
|
||||
|
||||
@@ -7,7 +7,11 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
const appState = {
|
||||
initialArgs: { jellyfinPlay: true },
|
||||
overlayRuntimeInitialized: true,
|
||||
mpvClient: { connected: true },
|
||||
mpvClient: {
|
||||
connected: true,
|
||||
currentTimePos: 12.25,
|
||||
requestProperty: async () => 18.75,
|
||||
},
|
||||
immersionTracker: {
|
||||
recordSubtitleLine: (text: string) => calls.push(`immersion-sub:${text}`),
|
||||
handleMediaTitleUpdate: (title: string) => calls.push(`immersion-title:${title}`),
|
||||
@@ -92,6 +96,8 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
deps.recordPauseState(true);
|
||||
deps.updateSubtitleRenderMetrics({});
|
||||
deps.setPreviousSecondarySubVisibility(true);
|
||||
deps.flushPlaybackPositionOnMediaPathClear?.('');
|
||||
await Promise.resolve();
|
||||
|
||||
assert.equal(appState.currentSubText, 'sub');
|
||||
assert.equal(appState.currentSubAssText, 'ass');
|
||||
@@ -106,4 +112,6 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
||||
assert.ok(calls.includes('metrics'));
|
||||
assert.ok(calls.includes('presence-refresh'));
|
||||
assert.ok(calls.includes('restore-mpv-sub'));
|
||||
assert.ok(calls.includes('immersion-time:12.25'));
|
||||
assert.ok(calls.includes('immersion-time:18.75'));
|
||||
});
|
||||
|
||||
@@ -4,7 +4,14 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
appState: {
|
||||
initialArgs?: { jellyfinPlay?: unknown } | null;
|
||||
overlayRuntimeInitialized: boolean;
|
||||
mpvClient: { connected?: boolean; currentSecondarySubText?: string } | null;
|
||||
mpvClient:
|
||||
| {
|
||||
connected?: boolean;
|
||||
currentSecondarySubText?: string;
|
||||
currentTimePos?: number;
|
||||
requestProperty?: (name: string) => Promise<unknown>;
|
||||
}
|
||||
| null;
|
||||
immersionTracker: {
|
||||
recordSubtitleLine?: (
|
||||
text: string,
|
||||
@@ -21,6 +28,7 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
subtitleTimingTracker: {
|
||||
recordSubtitle?: (text: string, start: number, end: number) => void;
|
||||
} | null;
|
||||
currentMediaPath?: string | null;
|
||||
currentSubText: string;
|
||||
currentSubAssText: string;
|
||||
currentSubtitleData?: SubtitleData | null;
|
||||
@@ -58,6 +66,15 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
ensureImmersionTrackerInitialized: () => void;
|
||||
tokenizeSubtitleForImmersion?: (text: string) => Promise<SubtitleData | null>;
|
||||
}) {
|
||||
const writePlaybackPositionFromMpv = (timeSec: unknown): void => {
|
||||
const normalizedTimeSec = Number(timeSec);
|
||||
if (!Number.isFinite(normalizedTimeSec)) {
|
||||
return;
|
||||
}
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordPlaybackPosition?.(normalizedTimeSec);
|
||||
};
|
||||
|
||||
return () => ({
|
||||
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
||||
syncOverlayMpvSubtitleSuppression: () => deps.syncOverlayMpvSubtitleSuppression(),
|
||||
@@ -161,6 +178,25 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordPauseState?.(paused);
|
||||
},
|
||||
flushPlaybackPositionOnMediaPathClear: (mediaPath: string) => {
|
||||
const mpvClient = deps.appState.mpvClient;
|
||||
const currentKnownTime = Number(mpvClient?.currentTimePos);
|
||||
writePlaybackPositionFromMpv(currentKnownTime);
|
||||
if (!mpvClient?.requestProperty) {
|
||||
return;
|
||||
}
|
||||
void mpvClient.requestProperty('time-pos').then((timePos) => {
|
||||
const currentPath = (deps.appState.currentMediaPath ?? '').trim();
|
||||
if (currentPath.length > 0 && currentPath !== mediaPath) {
|
||||
return;
|
||||
}
|
||||
const resolvedTime = Number(timePos);
|
||||
if (Number.isFinite(currentKnownTime) && Number.isFinite(resolvedTime) && currentKnownTime === resolvedTime) {
|
||||
return;
|
||||
}
|
||||
writePlaybackPositionFromMpv(resolvedTime);
|
||||
});
|
||||
},
|
||||
updateSubtitleRenderMetrics: (patch: Record<string, unknown>) =>
|
||||
deps.updateSubtitleRenderMetrics(patch),
|
||||
setPreviousSecondarySubVisibility: (visible: boolean) => {
|
||||
|
||||
Reference in New Issue
Block a user