fix: restore integrated texthooker startup

This commit is contained in:
2026-03-30 00:25:30 -07:00
parent 55b350c3a2
commit 8e5c21b443
20 changed files with 197 additions and 71 deletions

View File

@@ -176,7 +176,7 @@ test('runAppReadyRuntime skips heavy startup when shouldSkipHeavyStartup returns
assert.ok(calls.indexOf('handleFirstRunSetup') < calls.indexOf('handleInitialArgs'));
});
test('runAppReadyRuntime uses minimal startup for texthooker-only mode', async () => {
test('runAppReadyRuntime keeps websocket startup in texthooker-only mode but skips overlay window', async () => {
const { deps, calls } = makeDeps({
texthookerOnlyMode: true,
reloadConfig: () => calls.push('reloadConfig'),
@@ -185,7 +185,16 @@ test('runAppReadyRuntime uses minimal startup for texthooker-only mode', async (
await runAppReadyRuntime(deps);
assert.deepEqual(calls, ['ensureDefaultConfigBootstrap', 'reloadConfig', 'handleInitialArgs']);
assert.ok(calls.includes('reloadConfig'));
assert.ok(calls.includes('createMpvClient'));
assert.ok(calls.includes('startAnnotationWebsocket:6678'));
assert.ok(calls.includes('startTexthooker:5174:ws://127.0.0.1:6678'));
assert.ok(calls.includes('createSubtitleTimingTracker'));
assert.ok(calls.includes('handleFirstRunSetup'));
assert.ok(calls.includes('handleInitialArgs'));
assert.ok(calls.includes('log:Texthooker-only mode enabled; skipping overlay window.'));
assert.equal(calls.includes('initializeOverlayRuntime'), false);
assert.equal(calls.includes('setVisibleOverlayVisible:true'), false);
});
test('runAppReadyRuntime skips Jellyfin remote startup when dependency is not wired', async () => {

View File

@@ -62,6 +62,7 @@ function createDeps(overrides: Partial<CliCommandServiceDeps> = {}) {
let mpvSocketPath = '/tmp/subminer.sock';
let texthookerPort = 5174;
const osd: string[] = [];
let texthookerWebsocketUrl: string | undefined;
const deps: CliCommandServiceDeps = {
getMpvSocketPath: () => mpvSocketPath,
@@ -82,9 +83,10 @@ function createDeps(overrides: Partial<CliCommandServiceDeps> = {}) {
calls.push(`setTexthookerPort:${port}`);
},
getTexthookerPort: () => texthookerPort,
getTexthookerWebsocketUrl: () => texthookerWebsocketUrl,
shouldOpenTexthookerBrowser: () => true,
ensureTexthookerRunning: (port) => {
calls.push(`ensureTexthookerRunning:${port}`);
ensureTexthookerRunning: (port, websocketUrl) => {
calls.push(`ensureTexthookerRunning:${port}:${websocketUrl ?? ''}`);
},
openTexthookerInBrowser: (url) => {
calls.push(`openTexthookerInBrowser:${url}`);
@@ -354,10 +356,20 @@ test('handleCliCommand runs texthooker flow with browser open', () => {
handleCliCommand(args, 'initial', deps);
assert.ok(calls.includes('ensureTexthookerRunning:5174'));
assert.ok(calls.includes('ensureTexthookerRunning:5174:'));
assert.ok(calls.includes('openTexthookerInBrowser:http://127.0.0.1:5174'));
});
test('handleCliCommand forwards resolved websocket url to texthooker startup', () => {
const { deps, calls } = createDeps({
getTexthookerWebsocketUrl: () => 'ws://127.0.0.1:6678',
});
handleCliCommand(makeArgs({ texthooker: true }), 'initial', deps);
assert.ok(calls.includes('ensureTexthookerRunning:5174:ws://127.0.0.1:6678'));
});
test('handleCliCommand reports async mine errors to OSD', async () => {
const { deps, calls, osd } = createDeps({
mineSentenceCard: async () => {

View File

@@ -10,8 +10,9 @@ export interface CliCommandServiceDeps {
isTexthookerRunning: () => boolean;
setTexthookerPort: (port: number) => void;
getTexthookerPort: () => number;
getTexthookerWebsocketUrl: () => string | undefined;
shouldOpenTexthookerBrowser: () => boolean;
ensureTexthookerRunning: (port: number) => void;
ensureTexthookerRunning: (port: number, websocketUrl?: string) => void;
openTexthookerInBrowser: (url: string) => void;
stopApp: () => void;
isOverlayRuntimeInitialized: () => boolean;
@@ -84,7 +85,7 @@ interface MpvClientLike {
interface TexthookerServiceLike {
isRunning: () => boolean;
start: (port: number) => void;
start: (port: number, websocketUrl?: string) => void;
}
interface MpvCliRuntime {
@@ -98,6 +99,7 @@ interface TexthookerCliRuntime {
service: TexthookerServiceLike;
getPort: () => number;
setPort: (port: number) => void;
getWebsocketUrl: () => string | undefined;
shouldOpenBrowser: () => boolean;
openInBrowser: (url: string) => void;
}
@@ -194,10 +196,11 @@ export function createCliCommandDepsRuntime(
isTexthookerRunning: () => options.texthooker.service.isRunning(),
setTexthookerPort: options.texthooker.setPort,
getTexthookerPort: options.texthooker.getPort,
getTexthookerWebsocketUrl: options.texthooker.getWebsocketUrl,
shouldOpenTexthookerBrowser: options.texthooker.shouldOpenBrowser,
ensureTexthookerRunning: (port) => {
ensureTexthookerRunning: (port, websocketUrl) => {
if (!options.texthooker.service.isRunning()) {
options.texthooker.service.start(port);
options.texthooker.service.start(port, websocketUrl);
}
},
openTexthookerInBrowser: options.texthooker.openInBrowser,
@@ -473,7 +476,7 @@ export function handleCliCommand(
);
} else if (args.texthooker) {
const texthookerPort = deps.getTexthookerPort();
deps.ensureTexthookerRunning(texthookerPort);
deps.ensureTexthookerRunning(texthookerPort, deps.getTexthookerWebsocketUrl());
if (deps.shouldOpenTexthookerBrowser()) {
deps.openTexthookerInBrowser(`http://127.0.0.1:${texthookerPort}`);
}

View File

@@ -98,6 +98,13 @@ interface AppReadyConfigLike {
};
}
type TexthookerWebsocketConfigLike = Pick<AppReadyConfigLike, 'annotationWebsocket' | 'websocket'>;
type TexthookerWebsocketDefaults = {
defaultWebsocketPort: number;
defaultAnnotationWebsocketPort: number;
};
export interface AppReadyRuntimeDeps {
ensureDefaultConfigBootstrap: () => void;
loadSubtitlePosition: () => void;
@@ -169,6 +176,29 @@ function getStartupCriticalConfigErrors(config: AppReadyConfigLike): string[] {
return errors;
}
export function resolveTexthookerWebsocketUrl(
config: TexthookerWebsocketConfigLike,
defaults: TexthookerWebsocketDefaults,
hasMpvWebsocketPlugin: boolean,
): string | undefined {
const wsConfig = config.websocket || {};
const wsEnabled = wsConfig.enabled ?? 'auto';
const wsPort = wsConfig.port || defaults.defaultWebsocketPort;
const annotationWsConfig = config.annotationWebsocket || {};
const annotationWsEnabled = annotationWsConfig.enabled !== false;
const annotationWsPort = annotationWsConfig.port || defaults.defaultAnnotationWebsocketPort;
if (annotationWsEnabled) {
return `ws://127.0.0.1:${annotationWsPort}`;
}
if (wsEnabled === true || (wsEnabled === 'auto' && !hasMpvWebsocketPlugin)) {
return `ws://127.0.0.1:${wsPort}`;
}
return undefined;
}
export function shouldAutoInitializeOverlayRuntimeFromConfig(config: RuntimeConfigLike): boolean {
return config.auto_start_overlay === true;
}
@@ -201,12 +231,6 @@ export async function runAppReadyRuntime(deps: AppReadyRuntimeDeps): Promise<voi
return;
}
if (deps.texthookerOnlyMode) {
deps.reloadConfig();
deps.handleInitialArgs();
return;
}
if (deps.shouldUseMinimalStartup?.()) {
deps.reloadConfig();
deps.handleInitialArgs();
@@ -262,7 +286,14 @@ export async function runAppReadyRuntime(deps: AppReadyRuntimeDeps): Promise<voi
const annotationWsEnabled = annotationWsConfig.enabled !== false;
const annotationWsPort = annotationWsConfig.port || deps.defaultAnnotationWebsocketPort;
const texthookerPort = deps.defaultTexthookerPort;
let texthookerWebsocketUrl: string | undefined;
const texthookerWebsocketUrl = resolveTexthookerWebsocketUrl(
config,
{
defaultWebsocketPort: deps.defaultWebsocketPort,
defaultAnnotationWebsocketPort: deps.defaultAnnotationWebsocketPort,
},
deps.hasMpvWebsocketPlugin(),
);
if (wsEnabled === true || (wsEnabled === 'auto' && !deps.hasMpvWebsocketPlugin())) {
deps.startSubtitleWebsocket(wsPort);
@@ -272,9 +303,6 @@ export async function runAppReadyRuntime(deps: AppReadyRuntimeDeps): Promise<voi
if (annotationWsEnabled) {
deps.startAnnotationWebsocket(annotationWsPort);
texthookerWebsocketUrl = `ws://127.0.0.1:${annotationWsPort}`;
} else if (wsEnabled === true || (wsEnabled === 'auto' && !deps.hasMpvWebsocketPlugin())) {
texthookerWebsocketUrl = `ws://127.0.0.1:${wsPort}`;
}
if (config.texthooker?.launchAtStartup !== false) {