mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
fix: reuse background tokenization warmups
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
id: TASK-131
|
||||||
|
title: Avoid duplicate tokenization warmup after background startup
|
||||||
|
status: Done
|
||||||
|
assignee:
|
||||||
|
- codex
|
||||||
|
created_date: '2026-03-08 10:12'
|
||||||
|
updated_date: '2026-03-08 12:00'
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
dependencies: []
|
||||||
|
references:
|
||||||
|
- >-
|
||||||
|
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/composers/mpv-runtime-composer.ts
|
||||||
|
- >-
|
||||||
|
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/startup-warmups.ts
|
||||||
|
- >-
|
||||||
|
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/composers/mpv-runtime-composer.test.ts
|
||||||
|
priority: medium
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
When SubMiner is already running in the background and mpv is launched from the launcher or mpv plugin, the live app should reuse startup tokenization warmup state instead of re-entering the Yomitan/tokenization/annotation warmup path on first overlay use.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [x] #1 Background startup tokenization warmup is recorded in the runtime state used by later mpv/tokenization flows.
|
||||||
|
- [x] #2 Launching mpv from the launcher or plugin against an already-running background app does not re-run duplicate Yomitan/tokenization annotation warmup work in the live process.
|
||||||
|
- [x] #3 Regression tests cover the warmed-background path and protect against re-entering duplicate warmup work.
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
<!-- SECTION:PLAN:BEGIN -->
|
||||||
|
1. Add a regression test covering the case where background startup warmups already completed and a later tokenize call must not re-enter Yomitan/MeCab/dictionary warmups.
|
||||||
|
2. Update mpv tokenization warmup composition so startup background warmups and on-demand tokenization share the same completion state.
|
||||||
|
3. Run the focused composer/runtime tests and update acceptance criteria/notes with results.
|
||||||
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
Root-cause hypothesis: startup background warmups and on-demand tokenization warmups use separate state, so later mpv launch can re-enter warmup bookkeeping even though background startup already warmed dependencies.
|
||||||
|
|
||||||
|
Implemented shared warmup state between startup background warmups and on-demand tokenization warmups by forwarding scheduled Yomitan/tokenization promises into the mpv runtime composer. Added regression coverage for the warmed-background path. Verified with `bun run test:fast` plus focused composer/startup warmup tests.
|
||||||
|
|
||||||
|
Follow-up root cause from live retest: second mpv open could still pause on the startup gate because the runtime only treated full background tokenization warmup completion as reusable readiness. In practice, first-file tokenization could already be ready while slower dictionary prewarm work was still finishing, so reopening a video waited on duplicate warmup completion even though annotations were already usable.
|
||||||
|
|
||||||
|
Adjusted `src/main/runtime/composers/mpv-runtime-composer.ts` so autoplay reuse keys off a separate playback-ready latch. The latch flips true either when background warmups fully cover tokenization or when `onTokenizationReady` fires for a real subtitle line. `src/main.ts` already uses `isTokenizationWarmupReady()` to fast-signal `subminer-autoplay-ready` on a fresh media-path change, so reopened videos can now resume immediately once tokenization has succeeded once in the persistent app.
|
||||||
|
|
||||||
|
Validation update: `bun test src/core/services/cli-command.test.ts src/main/runtime/mpv-main-event-actions.test.ts src/main/runtime/composers/mpv-runtime-composer.test.ts launcher/mpv.test.ts launcher/smoke.e2e.test.ts` passed, `lua scripts/test-plugin-start-gate.lua` passed, and `bun run typecheck` passed.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
Background startup tokenization warmups now feed the same in-memory warmup state used by later mpv tokenization. When the app is already running and warmed in the background, launcher/plugin-driven mpv startup reuses that state instead of re-entering Yomitan/tokenization annotation warmups. Added a regression test for the warmed-background path and verified with `bun run test:fast`.
|
||||||
|
|
||||||
|
A later follow-up fixed the remaining second-open delay: autoplay reuse no longer waits for the entire background dictionary warmup pipeline to finish. After the persistent app has produced one tokenization-ready event, later mpv reconnects reuse that readiness immediately, so reopening the same or another video does not pause again on duplicate warmup bookkeeping.
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
@@ -212,6 +212,7 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
|||||||
assert.equal(typeof composed.createMecabTokenizerAndCheck, 'function');
|
assert.equal(typeof composed.createMecabTokenizerAndCheck, 'function');
|
||||||
assert.equal(typeof composed.prewarmSubtitleDictionaries, 'function');
|
assert.equal(typeof composed.prewarmSubtitleDictionaries, 'function');
|
||||||
assert.equal(typeof composed.startTokenizationWarmups, 'function');
|
assert.equal(typeof composed.startTokenizationWarmups, 'function');
|
||||||
|
assert.equal(typeof composed.isTokenizationWarmupReady, 'function');
|
||||||
assert.equal(typeof composed.launchBackgroundWarmupTask, 'function');
|
assert.equal(typeof composed.launchBackgroundWarmupTask, 'function');
|
||||||
assert.equal(typeof composed.startBackgroundWarmups, 'function');
|
assert.equal(typeof composed.startBackgroundWarmups, 'function');
|
||||||
|
|
||||||
@@ -219,7 +220,9 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
|||||||
assert.equal(client.connected, true);
|
assert.equal(client.connected, true);
|
||||||
|
|
||||||
composed.updateMpvSubtitleRenderMetrics({ subPos: 90 });
|
composed.updateMpvSubtitleRenderMetrics({ subPos: 90 });
|
||||||
|
assert.equal(composed.isTokenizationWarmupReady(), false);
|
||||||
await composed.startTokenizationWarmups();
|
await composed.startTokenizationWarmups();
|
||||||
|
assert.equal(composed.isTokenizationWarmupReady(), true);
|
||||||
const tokenized = await composed.tokenizeSubtitle('subtitle text');
|
const tokenized = await composed.tokenizeSubtitle('subtitle text');
|
||||||
await composed.createMecabTokenizerAndCheck();
|
await composed.createMecabTokenizerAndCheck();
|
||||||
await composed.prewarmSubtitleDictionaries();
|
await composed.prewarmSubtitleDictionaries();
|
||||||
@@ -789,9 +792,11 @@ test('composeMpvRuntimeHandlers shows annotation loading OSD after tokenization-
|
|||||||
const warmupPromise = composed.startTokenizationWarmups();
|
const warmupPromise = composed.startTokenizationWarmups();
|
||||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||||
assert.deepEqual(osdMessages, []);
|
assert.deepEqual(osdMessages, []);
|
||||||
|
assert.equal(composed.isTokenizationWarmupReady(), false);
|
||||||
|
|
||||||
await composed.tokenizeSubtitle('first line');
|
await composed.tokenizeSubtitle('first line');
|
||||||
assert.deepEqual(osdMessages, ['Loading subtitle annotations |']);
|
assert.deepEqual(osdMessages, ['Loading subtitle annotations |']);
|
||||||
|
assert.equal(composed.isTokenizationWarmupReady(), true);
|
||||||
|
|
||||||
jlptDeferred.resolve();
|
jlptDeferred.resolve();
|
||||||
frequencyDeferred.resolve();
|
frequencyDeferred.resolve();
|
||||||
@@ -800,3 +805,154 @@ test('composeMpvRuntimeHandlers shows annotation loading OSD after tokenization-
|
|||||||
|
|
||||||
assert.deepEqual(osdMessages, ['Loading subtitle annotations |', 'Subtitle annotations loaded']);
|
assert.deepEqual(osdMessages, ['Loading subtitle annotations |', 'Subtitle annotations loaded']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('composeMpvRuntimeHandlers reuses completed background tokenization warmups for later tokenize calls', async () => {
|
||||||
|
let started = false;
|
||||||
|
let yomitanWarmupCalls = 0;
|
||||||
|
let mecabWarmupCalls = 0;
|
||||||
|
let jlptWarmupCalls = 0;
|
||||||
|
let frequencyWarmupCalls = 0;
|
||||||
|
let mecabTokenizer: { tokenize: () => Promise<never[]> } | null = null;
|
||||||
|
|
||||||
|
const composed = composeMpvRuntimeHandlers<
|
||||||
|
{ connect: () => void; on: () => void },
|
||||||
|
{ isKnownWord: () => boolean },
|
||||||
|
{ text: string }
|
||||||
|
>({
|
||||||
|
bindMpvMainEventHandlersMainDeps: {
|
||||||
|
appState: {
|
||||||
|
initialArgs: null,
|
||||||
|
overlayRuntimeInitialized: true,
|
||||||
|
mpvClient: null,
|
||||||
|
immersionTracker: null,
|
||||||
|
subtitleTimingTracker: null,
|
||||||
|
currentSubText: '',
|
||||||
|
currentSubAssText: '',
|
||||||
|
playbackPaused: null,
|
||||||
|
previousSecondarySubVisibility: null,
|
||||||
|
},
|
||||||
|
getQuitOnDisconnectArmed: () => false,
|
||||||
|
scheduleQuitCheck: () => {},
|
||||||
|
quitApp: () => {},
|
||||||
|
reportJellyfinRemoteStopped: () => {},
|
||||||
|
syncOverlayMpvSubtitleSuppression: () => {},
|
||||||
|
maybeRunAnilistPostWatchUpdate: async () => {},
|
||||||
|
logSubtitleTimingError: () => {},
|
||||||
|
broadcastToOverlayWindows: () => {},
|
||||||
|
onSubtitleChange: () => {},
|
||||||
|
refreshDiscordPresence: () => {},
|
||||||
|
ensureImmersionTrackerInitialized: () => {},
|
||||||
|
updateCurrentMediaPath: () => {},
|
||||||
|
restoreMpvSubVisibility: () => {},
|
||||||
|
getCurrentAnilistMediaKey: () => null,
|
||||||
|
resetAnilistMediaTracking: () => {},
|
||||||
|
maybeProbeAnilistDuration: () => {},
|
||||||
|
ensureAnilistMediaGuess: () => {},
|
||||||
|
syncImmersionMediaState: () => {},
|
||||||
|
updateCurrentMediaTitle: () => {},
|
||||||
|
resetAnilistMediaGuessState: () => {},
|
||||||
|
reportJellyfinRemoteProgress: () => {},
|
||||||
|
updateSubtitleRenderMetrics: () => {},
|
||||||
|
},
|
||||||
|
mpvClientRuntimeServiceFactoryMainDeps: {
|
||||||
|
createClient: class {
|
||||||
|
connect(): void {}
|
||||||
|
on(): void {}
|
||||||
|
},
|
||||||
|
getSocketPath: () => '/tmp/mpv.sock',
|
||||||
|
getResolvedConfig: () => ({ auto_start_overlay: false }),
|
||||||
|
isAutoStartOverlayEnabled: () => false,
|
||||||
|
setOverlayVisible: () => {},
|
||||||
|
isVisibleOverlayVisible: () => false,
|
||||||
|
getReconnectTimer: () => null,
|
||||||
|
setReconnectTimer: () => {},
|
||||||
|
},
|
||||||
|
updateMpvSubtitleRenderMetricsMainDeps: {
|
||||||
|
getCurrentMetrics: () => BASE_METRICS,
|
||||||
|
setCurrentMetrics: () => {},
|
||||||
|
applyPatch: (current, patch) => ({ next: { ...current, ...patch }, changed: true }),
|
||||||
|
broadcastMetrics: () => {},
|
||||||
|
},
|
||||||
|
tokenizer: {
|
||||||
|
buildTokenizerDepsMainDeps: {
|
||||||
|
getYomitanExt: () => null,
|
||||||
|
getYomitanParserWindow: () => null,
|
||||||
|
setYomitanParserWindow: () => {},
|
||||||
|
getYomitanParserReadyPromise: () => null,
|
||||||
|
setYomitanParserReadyPromise: () => {},
|
||||||
|
getYomitanParserInitPromise: () => null,
|
||||||
|
setYomitanParserInitPromise: () => {},
|
||||||
|
isKnownWord: () => false,
|
||||||
|
recordLookup: () => {},
|
||||||
|
getKnownWordMatchMode: () => 'headword',
|
||||||
|
getNPlusOneEnabled: () => true,
|
||||||
|
getMinSentenceWordsForNPlusOne: () => 3,
|
||||||
|
getJlptLevel: () => null,
|
||||||
|
getJlptEnabled: () => true,
|
||||||
|
getFrequencyDictionaryEnabled: () => true,
|
||||||
|
getFrequencyDictionaryMatchMode: () => 'headword',
|
||||||
|
getFrequencyRank: () => null,
|
||||||
|
getYomitanGroupDebugEnabled: () => false,
|
||||||
|
getMecabTokenizer: () => mecabTokenizer,
|
||||||
|
},
|
||||||
|
createTokenizerRuntimeDeps: () => ({ isKnownWord: () => false }),
|
||||||
|
tokenizeSubtitle: async (text) => ({ text }),
|
||||||
|
createMecabTokenizerAndCheckMainDeps: {
|
||||||
|
getMecabTokenizer: () => mecabTokenizer,
|
||||||
|
setMecabTokenizer: (next) => {
|
||||||
|
mecabTokenizer = next as { tokenize: () => Promise<never[]> };
|
||||||
|
},
|
||||||
|
createMecabTokenizer: () => ({ tokenize: async () => [] }),
|
||||||
|
checkAvailability: async () => {
|
||||||
|
mecabWarmupCalls += 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prewarmSubtitleDictionariesMainDeps: {
|
||||||
|
ensureJlptDictionaryLookup: async () => {
|
||||||
|
jlptWarmupCalls += 1;
|
||||||
|
},
|
||||||
|
ensureFrequencyDictionaryLookup: async () => {
|
||||||
|
frequencyWarmupCalls += 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warmups: {
|
||||||
|
launchBackgroundWarmupTaskMainDeps: {
|
||||||
|
now: () => 0,
|
||||||
|
logDebug: () => {},
|
||||||
|
logWarn: () => {},
|
||||||
|
},
|
||||||
|
startBackgroundWarmupsMainDeps: {
|
||||||
|
getStarted: () => started,
|
||||||
|
setStarted: (next) => {
|
||||||
|
started = next;
|
||||||
|
},
|
||||||
|
isTexthookerOnlyMode: () => false,
|
||||||
|
ensureYomitanExtensionLoaded: async () => {
|
||||||
|
yomitanWarmupCalls += 1;
|
||||||
|
},
|
||||||
|
shouldWarmupMecab: () => true,
|
||||||
|
shouldWarmupYomitanExtension: () => true,
|
||||||
|
shouldWarmupSubtitleDictionaries: () => true,
|
||||||
|
shouldWarmupJellyfinRemoteSession: () => false,
|
||||||
|
shouldAutoConnectJellyfinRemote: () => false,
|
||||||
|
startJellyfinRemoteSession: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
composed.startBackgroundWarmups();
|
||||||
|
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||||
|
|
||||||
|
assert.equal(yomitanWarmupCalls, 1);
|
||||||
|
assert.equal(mecabWarmupCalls, 1);
|
||||||
|
assert.equal(jlptWarmupCalls, 1);
|
||||||
|
assert.equal(frequencyWarmupCalls, 1);
|
||||||
|
|
||||||
|
await composed.tokenizeSubtitle('first line after background warmup');
|
||||||
|
|
||||||
|
assert.equal(yomitanWarmupCalls, 1);
|
||||||
|
assert.equal(mecabWarmupCalls, 1);
|
||||||
|
assert.equal(jlptWarmupCalls, 1);
|
||||||
|
assert.equal(frequencyWarmupCalls, 1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export type MpvRuntimeComposerResult<
|
|||||||
createMecabTokenizerAndCheck: () => Promise<void>;
|
createMecabTokenizerAndCheck: () => Promise<void>;
|
||||||
prewarmSubtitleDictionaries: () => Promise<void>;
|
prewarmSubtitleDictionaries: () => Promise<void>;
|
||||||
startTokenizationWarmups: () => Promise<void>;
|
startTokenizationWarmups: () => Promise<void>;
|
||||||
|
isTokenizationWarmupReady: () => boolean;
|
||||||
launchBackgroundWarmupTask: ReturnType<typeof createLaunchBackgroundWarmupTaskFromStartup>;
|
launchBackgroundWarmupTask: ReturnType<typeof createLaunchBackgroundWarmupTaskFromStartup>;
|
||||||
startBackgroundWarmups: ReturnType<typeof createStartBackgroundWarmupsFromStartup>;
|
startBackgroundWarmups: ReturnType<typeof createStartBackgroundWarmupsFromStartup>;
|
||||||
}>;
|
}>;
|
||||||
@@ -151,6 +152,36 @@ export function composeMpvRuntimeHandlers<
|
|||||||
let tokenizationPrerequisiteWarmupInFlight: Promise<void> | null = null;
|
let tokenizationPrerequisiteWarmupInFlight: Promise<void> | null = null;
|
||||||
let tokenizationPrerequisiteWarmupCompleted = false;
|
let tokenizationPrerequisiteWarmupCompleted = false;
|
||||||
let tokenizationWarmupCompleted = false;
|
let tokenizationWarmupCompleted = false;
|
||||||
|
let tokenizationPlaybackReady = false;
|
||||||
|
const markTokenizationPrerequisiteWarmupCompleted = (): void => {
|
||||||
|
tokenizationPrerequisiteWarmupCompleted = true;
|
||||||
|
};
|
||||||
|
const markTokenizationPlaybackReady = (): void => {
|
||||||
|
tokenizationPlaybackReady = true;
|
||||||
|
};
|
||||||
|
const markTokenizationWarmupCompleted = (): void => {
|
||||||
|
tokenizationPrerequisiteWarmupCompleted = true;
|
||||||
|
tokenizationWarmupCompleted = true;
|
||||||
|
tokenizationPlaybackReady = true;
|
||||||
|
};
|
||||||
|
const backgroundWarmupCoversOnDemandTokenization = (): boolean => {
|
||||||
|
if (!options.warmups.startBackgroundWarmupsMainDeps.shouldWarmupYomitanExtension()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
shouldInitializeMecabForAnnotations() &&
|
||||||
|
!options.warmups.startBackgroundWarmupsMainDeps.shouldWarmupMecab()
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
shouldWarmupAnnotationDictionaries() &&
|
||||||
|
!options.warmups.startBackgroundWarmupsMainDeps.shouldWarmupSubtitleDictionaries()
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
const ensureTokenizationPrerequisites = (): Promise<void> => {
|
const ensureTokenizationPrerequisites = (): Promise<void> => {
|
||||||
if (tokenizationPrerequisiteWarmupCompleted) {
|
if (tokenizationPrerequisiteWarmupCompleted) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -159,7 +190,7 @@ export function composeMpvRuntimeHandlers<
|
|||||||
tokenizationPrerequisiteWarmupInFlight = options.warmups.startBackgroundWarmupsMainDeps
|
tokenizationPrerequisiteWarmupInFlight = options.warmups.startBackgroundWarmupsMainDeps
|
||||||
.ensureYomitanExtensionLoaded()
|
.ensureYomitanExtensionLoaded()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
tokenizationPrerequisiteWarmupCompleted = true;
|
markTokenizationPrerequisiteWarmupCompleted();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
tokenizationPrerequisiteWarmupInFlight = null;
|
tokenizationPrerequisiteWarmupInFlight = null;
|
||||||
@@ -184,7 +215,7 @@ export function composeMpvRuntimeHandlers<
|
|||||||
warmupTasks.push(prewarmSubtitleDictionaries().catch(() => {}));
|
warmupTasks.push(prewarmSubtitleDictionaries().catch(() => {}));
|
||||||
}
|
}
|
||||||
await Promise.all(warmupTasks);
|
await Promise.all(warmupTasks);
|
||||||
tokenizationWarmupCompleted = true;
|
markTokenizationWarmupCompleted();
|
||||||
})().finally(() => {
|
})().finally(() => {
|
||||||
tokenizationWarmupInFlight = null;
|
tokenizationWarmupInFlight = null;
|
||||||
});
|
});
|
||||||
@@ -198,6 +229,7 @@ export function composeMpvRuntimeHandlers<
|
|||||||
if (shouldWarmupAnnotationDictionaries()) {
|
if (shouldWarmupAnnotationDictionaries()) {
|
||||||
const onTokenizationReady = tokenizerMainDeps.onTokenizationReady;
|
const onTokenizationReady = tokenizerMainDeps.onTokenizationReady;
|
||||||
tokenizerMainDeps.onTokenizationReady = (tokenizedText: string): void => {
|
tokenizerMainDeps.onTokenizationReady = (tokenizedText: string): void => {
|
||||||
|
markTokenizationPlaybackReady();
|
||||||
onTokenizationReady?.(tokenizedText);
|
onTokenizationReady?.(tokenizedText);
|
||||||
if (!tokenizationWarmupCompleted) {
|
if (!tokenizationWarmupCompleted) {
|
||||||
void prewarmSubtitleDictionaries({ showLoadingOsd: true }).catch(() => {});
|
void prewarmSubtitleDictionaries({ showLoadingOsd: true }).catch(() => {});
|
||||||
@@ -221,6 +253,36 @@ export function composeMpvRuntimeHandlers<
|
|||||||
launchTask: (label, task) => launchBackgroundWarmupTask(label, task),
|
launchTask: (label, task) => launchBackgroundWarmupTask(label, task),
|
||||||
createMecabTokenizerAndCheck: () => createMecabTokenizerAndCheck(),
|
createMecabTokenizerAndCheck: () => createMecabTokenizerAndCheck(),
|
||||||
prewarmSubtitleDictionaries: () => prewarmSubtitleDictionaries(),
|
prewarmSubtitleDictionaries: () => prewarmSubtitleDictionaries(),
|
||||||
|
onYomitanExtensionWarmupScheduled: (promise) => {
|
||||||
|
if (tokenizationPrerequisiteWarmupCompleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const finalizedPromise = promise
|
||||||
|
.then(() => {
|
||||||
|
markTokenizationPrerequisiteWarmupCompleted();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (tokenizationPrerequisiteWarmupInFlight === finalizedPromise) {
|
||||||
|
tokenizationPrerequisiteWarmupInFlight = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tokenizationPrerequisiteWarmupInFlight = finalizedPromise;
|
||||||
|
},
|
||||||
|
onTokenizationWarmupScheduled: (promise) => {
|
||||||
|
if (tokenizationWarmupCompleted || !backgroundWarmupCoversOnDemandTokenization()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const finalizedPromise = promise
|
||||||
|
.then(() => {
|
||||||
|
markTokenizationWarmupCompleted();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (tokenizationWarmupInFlight === finalizedPromise) {
|
||||||
|
tokenizationWarmupInFlight = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tokenizationWarmupInFlight = finalizedPromise;
|
||||||
|
},
|
||||||
})(),
|
})(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -232,6 +294,7 @@ export function composeMpvRuntimeHandlers<
|
|||||||
createMecabTokenizerAndCheck: () => createMecabTokenizerAndCheck(),
|
createMecabTokenizerAndCheck: () => createMecabTokenizerAndCheck(),
|
||||||
prewarmSubtitleDictionaries: () => prewarmSubtitleDictionaries(),
|
prewarmSubtitleDictionaries: () => prewarmSubtitleDictionaries(),
|
||||||
startTokenizationWarmups,
|
startTokenizationWarmups,
|
||||||
|
isTokenizationWarmupReady: () => tokenizationPlaybackReady,
|
||||||
launchBackgroundWarmupTask: (label, task) => launchBackgroundWarmupTask(label, task),
|
launchBackgroundWarmupTask: (label, task) => launchBackgroundWarmupTask(label, task),
|
||||||
startBackgroundWarmups: () => startBackgroundWarmups(),
|
startBackgroundWarmups: () => startBackgroundWarmups(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,18 @@ export function createBuildStartBackgroundWarmupsMainDepsHandler(
|
|||||||
shouldWarmupJellyfinRemoteSession: () => deps.shouldWarmupJellyfinRemoteSession(),
|
shouldWarmupJellyfinRemoteSession: () => deps.shouldWarmupJellyfinRemoteSession(),
|
||||||
shouldAutoConnectJellyfinRemote: () => deps.shouldAutoConnectJellyfinRemote(),
|
shouldAutoConnectJellyfinRemote: () => deps.shouldAutoConnectJellyfinRemote(),
|
||||||
startJellyfinRemoteSession: () => deps.startJellyfinRemoteSession(),
|
startJellyfinRemoteSession: () => deps.startJellyfinRemoteSession(),
|
||||||
|
...(deps.onYomitanExtensionWarmupScheduled
|
||||||
|
? {
|
||||||
|
onYomitanExtensionWarmupScheduled: (promise: Promise<void>) =>
|
||||||
|
deps.onYomitanExtensionWarmupScheduled!(promise),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(deps.onTokenizationWarmupScheduled
|
||||||
|
? {
|
||||||
|
onTokenizationWarmupScheduled: (promise: Promise<void>) =>
|
||||||
|
deps.onTokenizationWarmupScheduled!(promise),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
logDebug: deps.logDebug,
|
logDebug: deps.logDebug,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export function createStartBackgroundWarmupsHandler(deps: {
|
|||||||
shouldWarmupJellyfinRemoteSession: () => boolean;
|
shouldWarmupJellyfinRemoteSession: () => boolean;
|
||||||
shouldAutoConnectJellyfinRemote: () => boolean;
|
shouldAutoConnectJellyfinRemote: () => boolean;
|
||||||
startJellyfinRemoteSession: () => Promise<void>;
|
startJellyfinRemoteSession: () => Promise<void>;
|
||||||
|
onYomitanExtensionWarmupScheduled?: (promise: Promise<void>) => void;
|
||||||
|
onTokenizationWarmupScheduled?: (promise: Promise<void>) => void;
|
||||||
logDebug?: (message: string) => void;
|
logDebug?: (message: string) => void;
|
||||||
}) {
|
}) {
|
||||||
return (): void => {
|
return (): void => {
|
||||||
@@ -46,9 +48,7 @@ export function createStartBackgroundWarmupsHandler(deps: {
|
|||||||
const shouldWarmupTokenization =
|
const shouldWarmupTokenization =
|
||||||
warmupMecab || warmupYomitanExtension || warmupSubtitleDictionaries;
|
warmupMecab || warmupYomitanExtension || warmupSubtitleDictionaries;
|
||||||
if (shouldWarmupTokenization) {
|
if (shouldWarmupTokenization) {
|
||||||
deps.launchTask('subtitle-tokenization', async () => {
|
const yomitanWarmupPromise = warmupYomitanExtension
|
||||||
await Promise.all([
|
|
||||||
warmupYomitanExtension
|
|
||||||
? (async () => {
|
? (async () => {
|
||||||
deps.logDebug?.('[startup-warmup] stage start: yomitan-extension');
|
deps.logDebug?.('[startup-warmup] stage start: yomitan-extension');
|
||||||
await deps.ensureYomitanExtensionLoaded();
|
await deps.ensureYomitanExtensionLoaded();
|
||||||
@@ -56,7 +56,13 @@ export function createStartBackgroundWarmupsHandler(deps: {
|
|||||||
})()
|
})()
|
||||||
: Promise.resolve().then(() => {
|
: Promise.resolve().then(() => {
|
||||||
deps.logDebug?.('[startup-warmup] stage skipped: yomitan-extension');
|
deps.logDebug?.('[startup-warmup] stage skipped: yomitan-extension');
|
||||||
}),
|
});
|
||||||
|
if (warmupYomitanExtension) {
|
||||||
|
deps.onYomitanExtensionWarmupScheduled?.(yomitanWarmupPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenizationWarmupPromise = Promise.all([
|
||||||
|
yomitanWarmupPromise,
|
||||||
warmupMecab
|
warmupMecab
|
||||||
? (async () => {
|
? (async () => {
|
||||||
deps.logDebug?.('[startup-warmup] stage start: mecab');
|
deps.logDebug?.('[startup-warmup] stage start: mecab');
|
||||||
@@ -75,8 +81,9 @@ export function createStartBackgroundWarmupsHandler(deps: {
|
|||||||
: Promise.resolve().then(() => {
|
: Promise.resolve().then(() => {
|
||||||
deps.logDebug?.('[startup-warmup] stage skipped: subtitle-dictionaries');
|
deps.logDebug?.('[startup-warmup] stage skipped: subtitle-dictionaries');
|
||||||
}),
|
}),
|
||||||
]);
|
]).then(() => {});
|
||||||
});
|
deps.onTokenizationWarmupScheduled?.(tokenizationWarmupPromise);
|
||||||
|
deps.launchTask('subtitle-tokenization', () => tokenizationWarmupPromise);
|
||||||
}
|
}
|
||||||
if (warmupJellyfinRemoteSession && autoConnectJellyfinRemote) {
|
if (warmupJellyfinRemoteSession && autoConnectJellyfinRemote) {
|
||||||
deps.launchTask('jellyfin-remote-session', async () => {
|
deps.launchTask('jellyfin-remote-session', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user