refactor: deduplicate mpv plugin config, fix CSS font fallbacks

- Extract shared getMpvPluginRuntimeConfig() helper to eliminate duplicate inline objects
- Call ensureImmersionTrackerStarted() before markActiveVideoWatched action
- Quote multi-word font names and add sans-serif generic fallback in subtitle sidebar CSS
- Add main-wiring tests asserting deduplication and tracker start ordering
This commit is contained in:
2026-05-19 02:14:16 -07:00
parent 2772c61aba
commit e4165a418c
4 changed files with 69 additions and 26 deletions
+18 -22
View File
@@ -1233,17 +1233,7 @@ const youtubePlaybackRuntime = createYoutubePlaybackRuntime({
resolveInstalledPluginBeforeLaunch: (detection, mpvPath) =>
promptForLegacyMpvPluginRemovalBeforeWindowsLaunch(mpvPath, detection),
},
{
socketPath: appState.mpvSocketPath,
binaryPath: getResolvedConfig().mpv.subminerBinaryPath,
backend: getResolvedConfig().mpv.backend,
autoStart: getResolvedConfig().mpv.autoStartSubMiner,
autoStartVisibleOverlay: getResolvedConfig().auto_start_overlay,
autoStartPauseUntilReady: getResolvedConfig().mpv.pauseUntilOverlayReady,
texthookerEnabled: getResolvedConfig().texthooker.launchAtStartup,
aniskipEnabled: getResolvedConfig().mpv.aniskipEnabled,
aniskipButtonKey: getResolvedConfig().mpv.aniskipButtonKey,
},
getMpvPluginRuntimeConfig(),
),
waitForYoutubeMpvConnected: (timeoutMs) => waitForYoutubeMpvConnected(timeoutMs),
prepareYoutubePlaybackInMpv: (request) => prepareYoutubePlaybackInMpv(request),
@@ -1254,6 +1244,21 @@ const youtubePlaybackRuntime = createYoutubePlaybackRuntime({
clearScheduled: (timer) => clearTimeout(timer),
});
function getMpvPluginRuntimeConfig() {
const config = getResolvedConfig();
return {
socketPath: appState.mpvSocketPath,
binaryPath: config.mpv.subminerBinaryPath,
backend: config.mpv.backend,
autoStart: config.mpv.autoStartSubMiner,
autoStartVisibleOverlay: config.auto_start_overlay,
autoStartPauseUntilReady: config.mpv.pauseUntilOverlayReady,
texthookerEnabled: config.texthooker.launchAtStartup,
aniskipEnabled: config.mpv.aniskipEnabled,
aniskipButtonKey: config.mpv.aniskipButtonKey,
};
}
let firstRunSetupMessage: string | null = null;
const resolveWindowsMpvShortcutRuntimePaths = () =>
resolveWindowsMpvShortcutPaths({
@@ -2661,17 +2666,7 @@ const {
getLaunchMode: () => getResolvedConfig().mpv.launchMode,
platform: process.platform,
execPath: process.execPath,
getPluginRuntimeConfig: () => ({
socketPath: appState.mpvSocketPath,
binaryPath: getResolvedConfig().mpv.subminerBinaryPath,
backend: getResolvedConfig().mpv.backend,
autoStart: getResolvedConfig().mpv.autoStartSubMiner,
autoStartVisibleOverlay: getResolvedConfig().auto_start_overlay,
autoStartPauseUntilReady: getResolvedConfig().mpv.pauseUntilOverlayReady,
texthookerEnabled: getResolvedConfig().texthooker.launchAtStartup,
aniskipEnabled: getResolvedConfig().mpv.aniskipEnabled,
aniskipButtonKey: getResolvedConfig().mpv.aniskipButtonKey,
}),
getPluginRuntimeConfig: () => getMpvPluginRuntimeConfig(),
defaultMpvLogPath: DEFAULT_MPV_LOG_PATH,
defaultMpvArgs: MPV_JELLYFIN_DEFAULT_ARGS,
removeSocketPath: (socketPath) => {
@@ -5203,6 +5198,7 @@ async function dispatchSessionAction(request: SessionActionDispatchRequest): Pro
toggleSubtitleSidebar: () => toggleSubtitleSidebar(),
markLastCardAsAudioCard: () => markLastCardAsAudioCard(),
markActiveVideoWatched: async () => {
ensureImmersionTrackerStarted();
const marked = (await appState.immersionTracker?.markActiveVideoWatched()) ?? false;
if (marked) {
try {
+32
View File
@@ -0,0 +1,32 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
function readMainSource(): string {
return fs.readFileSync(path.join(process.cwd(), 'src/main.ts'), 'utf8');
}
test('manual watched session action starts immersion tracker before marking watched', () => {
const source = readMainSource();
const actionBlock = source.match(
/markActiveVideoWatched: async \(\) => \{(?<body>[\s\S]*?)\n \},/,
)?.groups?.body;
assert.ok(actionBlock);
assert.match(actionBlock, /ensureImmersionTrackerStarted\(\);/);
assert.ok(
actionBlock.indexOf('ensureImmersionTrackerStarted();') <
actionBlock.indexOf('markActiveVideoWatched()'),
);
});
test('main process uses one shared mpv plugin runtime config helper', () => {
const source = readMainSource();
assert.match(source, /function getMpvPluginRuntimeConfig\(\)/);
assert.equal((source.match(/socketPath: appState\.mpvSocketPath/g) ?? []).length, 1);
assert.equal(
(source.match(/binaryPath: getResolvedConfig\(\)\.mpv\.subminerBinaryPath/g) ?? []).length,
0,
);
});
@@ -40,6 +40,20 @@ test('renderer stylesheet only hides visible focus chrome on top-level overlay f
);
});
test('subtitle sidebar stylesheet keeps quoted font fallbacks and generic family', () => {
const cssSource = readWorkspaceFile('src/renderer/style.css');
const sidebarContentBlock = cssSource.match(
/\.subtitle-sidebar-content\s*\{(?<body>[\s\S]*?)\n\}/,
)?.groups?.body;
assert.ok(sidebarContentBlock);
assert.match(sidebarContentBlock, /'Hiragino Sans'/);
assert.match(sidebarContentBlock, /'M PLUS 1'/);
assert.match(sidebarContentBlock, /'Source Han Sans JP'/);
assert.match(sidebarContentBlock, /'Noto Sans CJK JP'/);
assert.match(sidebarContentBlock, /sans-serif/);
});
test('top-level readme avoids stale overlay-layers wording', () => {
const readmeSource = readWorkspaceFile('README.md');
assert.doesNotMatch(readmeSource, /overlay layers/i);
+5 -4
View File
@@ -1912,10 +1912,11 @@ body.subtitle-sidebar-embedded-open .subtitle-sidebar-modal {
margin-left: auto;
font-family: var(
--subtitle-sidebar-font-family,
Hiragino Sans,
M PLUS 1,
Source Han Sans JP,
Noto Sans CJK JP
'Hiragino Sans',
'M PLUS 1',
'Source Han Sans JP',
'Noto Sans CJK JP',
sans-serif
);
font-size: var(--subtitle-sidebar-font-size, 16px);
background: var(--subtitle-sidebar-background-color, rgba(73, 77, 100, 0.9));