feat: make startup warmups configurable with low-power mode

This commit is contained in:
2026-02-27 21:06:12 -08:00
parent 3a1d746a2e
commit 1e645f961b
16 changed files with 338 additions and 20 deletions

View File

@@ -26,6 +26,7 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
const calls: string[] = [];
let started = false;
let metrics = BASE_METRICS;
let mecabTokenizer: { id: string } | null = null;
class FakeMpvClient {
connected = false;
@@ -140,9 +141,15 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
return { text };
},
createMecabTokenizerAndCheckMainDeps: {
getMecabTokenizer: () => ({ id: 'mecab' }),
setMecabTokenizer: () => {},
createMecabTokenizer: () => ({ id: 'mecab' }),
getMecabTokenizer: () => mecabTokenizer,
setMecabTokenizer: (next) => {
mecabTokenizer = next as { id: string };
calls.push('set-mecab');
},
createMecabTokenizer: () => {
calls.push('create-mecab');
return { id: 'mecab' };
},
checkAvailability: async () => {
calls.push('check-mecab');
},
@@ -176,6 +183,10 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
ensureYomitanExtensionLoaded: async () => {
calls.push('warmup-yomitan');
},
shouldWarmupMecab: () => true,
shouldWarmupYomitanExtension: () => true,
shouldWarmupSubtitleDictionaries: () => true,
shouldWarmupJellyfinRemoteSession: () => true,
shouldAutoConnectJellyfinRemote: () => false,
startJellyfinRemoteSession: async () => {
calls.push('warmup-jellyfin');
@@ -212,9 +223,12 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
assert.ok(calls.includes('broadcast-metrics'));
assert.ok(calls.includes('create-tokenizer-runtime-deps'));
assert.ok(calls.includes('tokenize:subtitle text'));
assert.ok(calls.includes('create-mecab'));
assert.ok(calls.includes('set-mecab'));
assert.ok(calls.includes('check-mecab'));
assert.ok(calls.includes('prewarm-jlpt'));
assert.ok(calls.includes('prewarm-frequency'));
assert.ok(calls.includes('set-started:true'));
assert.ok(calls.includes('warmup-yomitan'));
assert.ok(calls.indexOf('create-mecab') < calls.indexOf('set-started:true'));
});

View File

@@ -133,6 +133,10 @@ export function composeMpvRuntimeHandlers<
options.tokenizer.prewarmSubtitleDictionariesMainDeps,
);
const tokenizeSubtitle = async (text: string): Promise<TTokenizedSubtitle> => {
await options.warmups.startBackgroundWarmupsMainDeps.ensureYomitanExtensionLoaded();
if (!options.tokenizer.createMecabTokenizerAndCheckMainDeps.getMecabTokenizer()) {
await createMecabTokenizerAndCheck().catch(() => {});
}
await prewarmSubtitleDictionaries();
return options.tokenizer.tokenizeSubtitle(
text,

View File

@@ -34,6 +34,10 @@ test('startup warmups main deps builders map callbacks', async () => {
prewarmSubtitleDictionaries: async () => {
calls.push('dict');
},
shouldWarmupMecab: () => false,
shouldWarmupYomitanExtension: () => true,
shouldWarmupSubtitleDictionaries: () => false,
shouldWarmupJellyfinRemoteSession: () => true,
shouldAutoConnectJellyfinRemote: () => true,
startJellyfinRemoteSession: async () => {
calls.push('jellyfin');
@@ -48,6 +52,10 @@ test('startup warmups main deps builders map callbacks', async () => {
await start.createMecabTokenizerAndCheck();
await start.ensureYomitanExtensionLoaded();
await start.prewarmSubtitleDictionaries();
assert.equal(start.shouldWarmupMecab(), false);
assert.equal(start.shouldWarmupYomitanExtension(), true);
assert.equal(start.shouldWarmupSubtitleDictionaries(), false);
assert.equal(start.shouldWarmupJellyfinRemoteSession(), true);
assert.equal(start.shouldAutoConnectJellyfinRemote(), true);
await start.startJellyfinRemoteSession();

View File

@@ -25,6 +25,10 @@ export function createBuildStartBackgroundWarmupsMainDepsHandler(deps: StartBack
createMecabTokenizerAndCheck: () => deps.createMecabTokenizerAndCheck(),
ensureYomitanExtensionLoaded: () => deps.ensureYomitanExtensionLoaded(),
prewarmSubtitleDictionaries: () => deps.prewarmSubtitleDictionaries(),
shouldWarmupMecab: () => deps.shouldWarmupMecab(),
shouldWarmupYomitanExtension: () => deps.shouldWarmupYomitanExtension(),
shouldWarmupSubtitleDictionaries: () => deps.shouldWarmupSubtitleDictionaries(),
shouldWarmupJellyfinRemoteSession: () => deps.shouldWarmupJellyfinRemoteSession(),
shouldAutoConnectJellyfinRemote: () => deps.shouldAutoConnectJellyfinRemote(),
startJellyfinRemoteSession: () => deps.startJellyfinRemoteSession(),
});

View File

@@ -41,6 +41,10 @@ test('startBackgroundWarmups no-ops when already started', () => {
createMecabTokenizerAndCheck: async () => {},
ensureYomitanExtensionLoaded: async () => {},
prewarmSubtitleDictionaries: async () => {},
shouldWarmupMecab: () => true,
shouldWarmupYomitanExtension: () => true,
shouldWarmupSubtitleDictionaries: () => true,
shouldWarmupJellyfinRemoteSession: () => true,
shouldAutoConnectJellyfinRemote: () => false,
startJellyfinRemoteSession: async () => {},
});
@@ -49,7 +53,7 @@ test('startBackgroundWarmups no-ops when already started', () => {
assert.equal(launches, 0);
});
test('startBackgroundWarmups does not schedule jellyfin warmup when jellyfin.enabled is false', () => {
test('startBackgroundWarmups respects per-integration warmup toggles', () => {
const labels: string[] = [];
let started = false;
const startWarmups = createStartBackgroundWarmupsHandler({
@@ -64,9 +68,13 @@ test('startBackgroundWarmups does not schedule jellyfin warmup when jellyfin.ena
createMecabTokenizerAndCheck: async () => {},
ensureYomitanExtensionLoaded: async () => {},
prewarmSubtitleDictionaries: async () => {},
shouldWarmupMecab: () => false,
shouldWarmupYomitanExtension: () => true,
shouldWarmupSubtitleDictionaries: () => false,
shouldWarmupJellyfinRemoteSession: () => false,
shouldAutoConnectJellyfinRemote: () =>
shouldAutoConnectJellyfinRemote({
enabled: false,
enabled: true,
remoteControlEnabled: true,
remoteControlAutoConnect: true,
}),
@@ -75,7 +83,7 @@ test('startBackgroundWarmups does not schedule jellyfin warmup when jellyfin.ena
startWarmups();
assert.equal(started, true);
assert.deepEqual(labels, ['mecab', 'yomitan-extension', 'subtitle-dictionaries']);
assert.deepEqual(labels, ['yomitan-extension']);
});
test('startBackgroundWarmups schedules jellyfin warmup when all jellyfin flags are enabled', () => {
@@ -93,6 +101,10 @@ test('startBackgroundWarmups schedules jellyfin warmup when all jellyfin flags a
createMecabTokenizerAndCheck: async () => {},
ensureYomitanExtensionLoaded: async () => {},
prewarmSubtitleDictionaries: async () => {},
shouldWarmupMecab: () => true,
shouldWarmupYomitanExtension: () => true,
shouldWarmupSubtitleDictionaries: () => true,
shouldWarmupJellyfinRemoteSession: () => true,
shouldAutoConnectJellyfinRemote: () =>
shouldAutoConnectJellyfinRemote({
enabled: true,
@@ -111,3 +123,36 @@ test('startBackgroundWarmups schedules jellyfin warmup when all jellyfin flags a
'jellyfin-remote-session',
]);
});
test('startBackgroundWarmups skips jellyfin warmup when warmup is deferred', () => {
const labels: string[] = [];
let started = false;
const startWarmups = createStartBackgroundWarmupsHandler({
getStarted: () => started,
setStarted: (value) => {
started = value;
},
isTexthookerOnlyMode: () => false,
launchTask: (label) => {
labels.push(label);
},
createMecabTokenizerAndCheck: async () => {},
ensureYomitanExtensionLoaded: async () => {},
prewarmSubtitleDictionaries: async () => {},
shouldWarmupMecab: () => false,
shouldWarmupYomitanExtension: () => true,
shouldWarmupSubtitleDictionaries: () => false,
shouldWarmupJellyfinRemoteSession: () => false,
shouldAutoConnectJellyfinRemote: () =>
shouldAutoConnectJellyfinRemote({
enabled: true,
remoteControlEnabled: true,
remoteControlAutoConnect: true,
}),
startJellyfinRemoteSession: async () => {},
});
startWarmups();
assert.equal(started, true);
assert.deepEqual(labels, ['yomitan-extension']);
});

View File

@@ -24,6 +24,10 @@ export function createStartBackgroundWarmupsHandler(deps: {
createMecabTokenizerAndCheck: () => Promise<void>;
ensureYomitanExtensionLoaded: () => Promise<void>;
prewarmSubtitleDictionaries: () => Promise<void>;
shouldWarmupMecab: () => boolean;
shouldWarmupYomitanExtension: () => boolean;
shouldWarmupSubtitleDictionaries: () => boolean;
shouldWarmupJellyfinRemoteSession: () => boolean;
shouldAutoConnectJellyfinRemote: () => boolean;
startJellyfinRemoteSession: () => Promise<void>;
}) {
@@ -32,16 +36,22 @@ export function createStartBackgroundWarmupsHandler(deps: {
if (deps.isTexthookerOnlyMode()) return;
deps.setStarted(true);
deps.launchTask('mecab', async () => {
await deps.createMecabTokenizerAndCheck();
});
deps.launchTask('yomitan-extension', async () => {
await deps.ensureYomitanExtensionLoaded();
});
deps.launchTask('subtitle-dictionaries', async () => {
await deps.prewarmSubtitleDictionaries();
});
if (deps.shouldAutoConnectJellyfinRemote()) {
if (deps.shouldWarmupMecab()) {
deps.launchTask('mecab', async () => {
await deps.createMecabTokenizerAndCheck();
});
}
if (deps.shouldWarmupYomitanExtension()) {
deps.launchTask('yomitan-extension', async () => {
await deps.ensureYomitanExtensionLoaded();
});
}
if (deps.shouldWarmupSubtitleDictionaries()) {
deps.launchTask('subtitle-dictionaries', async () => {
await deps.prewarmSubtitleDictionaries();
});
}
if (deps.shouldWarmupJellyfinRemoteSession() && deps.shouldAutoConnectJellyfinRemote()) {
deps.launchTask('jellyfin-remote-session', async () => {
await deps.startJellyfinRemoteSession();
});