mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor(mpv): emit media/path/title events for app-level handlers
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import * as net from "net";
|
||||
import { EventEmitter } from "events";
|
||||
import {
|
||||
Config,
|
||||
MpvClient,
|
||||
@@ -44,7 +45,7 @@ interface SubtitleTimingTrackerLike {
|
||||
recordSubtitle: (text: string, start: number, end: number) => void;
|
||||
}
|
||||
|
||||
export interface MpvIpcClientDeps {
|
||||
export interface MpvIpcClientProtocolDeps {
|
||||
getResolvedConfig: () => Config;
|
||||
autoStartOverlay: boolean;
|
||||
setOverlayVisible: (visible: boolean) => void;
|
||||
@@ -52,29 +53,47 @@ export interface MpvIpcClientDeps {
|
||||
isVisibleOverlayVisible: () => boolean;
|
||||
getReconnectTimer: () => ReturnType<typeof setTimeout> | null;
|
||||
setReconnectTimer: (timer: ReturnType<typeof setTimeout> | null) => void;
|
||||
getCurrentSubText: () => string;
|
||||
setCurrentSubText: (text: string) => void;
|
||||
setCurrentSubAssText: (text: string) => void;
|
||||
getSubtitleTimingTracker: () => SubtitleTimingTrackerLike | null;
|
||||
subtitleWsBroadcast: (text: string) => void;
|
||||
getOverlayWindowsCount: () => number;
|
||||
tokenizeSubtitle: (text: string) => Promise<SubtitleData>;
|
||||
broadcastToOverlayWindows: (channel: string, ...args: unknown[]) => void;
|
||||
updateCurrentMediaPath: (mediaPath: unknown) => void;
|
||||
updateMpvSubtitleRenderMetrics: (
|
||||
}
|
||||
|
||||
export interface MpvIpcClientRuntimeDeps {
|
||||
getCurrentSubText?: () => string;
|
||||
setCurrentSubText?: (text: string) => void;
|
||||
setCurrentSubAssText?: (text: string) => void;
|
||||
getSubtitleTimingTracker?: () => SubtitleTimingTrackerLike | null;
|
||||
subtitleWsBroadcast?: (text: string) => void;
|
||||
getOverlayWindowsCount?: () => number;
|
||||
tokenizeSubtitle?: (text: string) => Promise<SubtitleData>;
|
||||
broadcastToOverlayWindows?: (channel: string, ...args: unknown[]) => void;
|
||||
updateCurrentMediaPath?: (mediaPath: unknown) => void;
|
||||
updateMpvSubtitleRenderMetrics?: (
|
||||
patch: Partial<MpvSubtitleRenderMetrics>,
|
||||
) => void;
|
||||
getMpvSubtitleRenderMetrics: () => MpvSubtitleRenderMetrics;
|
||||
getPreviousSecondarySubVisibility: () => boolean | null;
|
||||
setPreviousSecondarySubVisibility: (value: boolean | null) => void;
|
||||
showMpvOsd: (text: string) => void;
|
||||
getMpvSubtitleRenderMetrics?: () => MpvSubtitleRenderMetrics;
|
||||
getPreviousSecondarySubVisibility?: () => boolean | null;
|
||||
setPreviousSecondarySubVisibility?: (value: boolean | null) => void;
|
||||
showMpvOsd?: (text: string) => void;
|
||||
updateCurrentMediaTitle?: (mediaTitle: unknown) => void;
|
||||
}
|
||||
|
||||
export interface MpvIpcClientDeps extends MpvIpcClientProtocolDeps, MpvIpcClientRuntimeDeps {}
|
||||
|
||||
export interface MpvIpcClientEventMap {
|
||||
"subtitle-change": { text: string; isOverlayVisible: boolean };
|
||||
"subtitle-ass-change": { text: string };
|
||||
"secondary-subtitle-change": { text: string };
|
||||
"media-path-change": { path: string };
|
||||
"media-title-change": { title: string | null };
|
||||
"subtitle-metrics-change": { patch: Partial<MpvSubtitleRenderMetrics> };
|
||||
"secondary-subtitle-visibility": { visible: boolean };
|
||||
}
|
||||
|
||||
type MpvIpcClientEventName = keyof MpvIpcClientEventMap;
|
||||
|
||||
export class MpvIpcClient implements MpvClient {
|
||||
private socketPath: string;
|
||||
private deps: MpvIpcClientDeps;
|
||||
private deps: MpvIpcClientProtocolDeps & Required<MpvIpcClientRuntimeDeps>;
|
||||
public socket: net.Socket | null = null;
|
||||
private eventBus = new EventEmitter();
|
||||
private buffer = "";
|
||||
public connected = false;
|
||||
private connecting = false;
|
||||
@@ -96,7 +115,62 @@ export class MpvIpcClient implements MpvClient {
|
||||
|
||||
constructor(socketPath: string, deps: MpvIpcClientDeps) {
|
||||
this.socketPath = socketPath;
|
||||
this.deps = deps;
|
||||
this.deps = {
|
||||
getCurrentSubText: () => "",
|
||||
setCurrentSubText: () => undefined,
|
||||
setCurrentSubAssText: () => undefined,
|
||||
getSubtitleTimingTracker: () => null,
|
||||
subtitleWsBroadcast: () => undefined,
|
||||
getOverlayWindowsCount: () => 0,
|
||||
tokenizeSubtitle: async (text) => ({ text, tokens: null }),
|
||||
broadcastToOverlayWindows: () => undefined,
|
||||
updateCurrentMediaPath: () => undefined,
|
||||
updateCurrentMediaTitle: () => undefined,
|
||||
updateMpvSubtitleRenderMetrics: () => undefined,
|
||||
getMpvSubtitleRenderMetrics: () => ({
|
||||
subPos: 100,
|
||||
subFontSize: 36,
|
||||
subScale: 1,
|
||||
subMarginY: 0,
|
||||
subMarginX: 0,
|
||||
subFont: "",
|
||||
subSpacing: 0,
|
||||
subBold: false,
|
||||
subItalic: false,
|
||||
subBorderSize: 0,
|
||||
subShadowOffset: 0,
|
||||
subAssOverride: "yes",
|
||||
subScaleByWindow: true,
|
||||
subUseMargins: true,
|
||||
osdHeight: 0,
|
||||
osdDimensions: null,
|
||||
}),
|
||||
getPreviousSecondarySubVisibility: () => null,
|
||||
setPreviousSecondarySubVisibility: () => undefined,
|
||||
showMpvOsd: () => undefined,
|
||||
...deps,
|
||||
};
|
||||
}
|
||||
|
||||
on<EventName extends MpvIpcClientEventName>(
|
||||
event: EventName,
|
||||
listener: (payload: MpvIpcClientEventMap[EventName]) => void,
|
||||
): void {
|
||||
this.eventBus.on(event as string, listener);
|
||||
}
|
||||
|
||||
off<EventName extends MpvIpcClientEventName>(
|
||||
event: EventName,
|
||||
listener: (payload: MpvIpcClientEventMap[EventName]) => void,
|
||||
): void {
|
||||
this.eventBus.off(event as string, listener);
|
||||
}
|
||||
|
||||
private emit<EventName extends MpvIpcClientEventName>(
|
||||
event: EventName,
|
||||
payload: MpvIpcClientEventMap[EventName],
|
||||
): void {
|
||||
this.eventBus.emit(event as string, payload);
|
||||
}
|
||||
|
||||
setSocketPath(socketPath: string): void {
|
||||
@@ -219,6 +293,11 @@ export class MpvIpcClient implements MpvClient {
|
||||
if (msg.event === "property-change") {
|
||||
if (msg.name === "sub-text") {
|
||||
const nextSubText = (msg.data as string) || "";
|
||||
const overlayVisible = this.deps.isVisibleOverlayVisible();
|
||||
this.emit("subtitle-change", {
|
||||
text: nextSubText,
|
||||
isOverlayVisible: overlayVisible,
|
||||
});
|
||||
this.deps.setCurrentSubText(nextSubText);
|
||||
this.currentSubText = nextSubText;
|
||||
const subtitleTimingTracker = this.deps.getSubtitleTimingTracker();
|
||||
@@ -240,6 +319,9 @@ export class MpvIpcClient implements MpvClient {
|
||||
}
|
||||
} else if (msg.name === "sub-text-ass") {
|
||||
const nextSubAssText = (msg.data as string) || "";
|
||||
this.emit("subtitle-ass-change", {
|
||||
text: nextSubAssText,
|
||||
});
|
||||
this.deps.setCurrentSubAssText(nextSubAssText);
|
||||
this.deps.broadcastToOverlayWindows("subtitle-ass:set", nextSubAssText);
|
||||
} else if (msg.name === "sub-start") {
|
||||
@@ -269,6 +351,9 @@ export class MpvIpcClient implements MpvClient {
|
||||
}
|
||||
} else if (msg.name === "secondary-sub-text") {
|
||||
this.currentSecondarySubText = (msg.data as string) || "";
|
||||
this.emit("secondary-subtitle-change", {
|
||||
text: this.currentSecondarySubText,
|
||||
});
|
||||
this.deps.broadcastToOverlayWindows(
|
||||
"secondary-subtitle:set",
|
||||
this.currentSecondarySubText,
|
||||
@@ -287,13 +372,21 @@ export class MpvIpcClient implements MpvClient {
|
||||
this.send({ command: ["set_property", "pause", true] });
|
||||
}
|
||||
} else if (msg.name === "media-title") {
|
||||
this.emit("media-title-change", {
|
||||
title: typeof msg.data === "string" ? msg.data.trim() : null,
|
||||
});
|
||||
this.deps.updateCurrentMediaTitle?.(msg.data);
|
||||
} else if (msg.name === "path") {
|
||||
this.currentVideoPath = (msg.data as string) || "";
|
||||
this.emit("media-path-change", {
|
||||
path: (msg.data as string) || "",
|
||||
});
|
||||
this.deps.updateCurrentMediaPath(msg.data);
|
||||
this.autoLoadSecondarySubTrack();
|
||||
this.syncCurrentAudioStreamIndex();
|
||||
} else if (msg.name === "sub-pos") {
|
||||
const patch = { subPos: msg.data as number };
|
||||
this.emit("subtitle-metrics-change", { patch });
|
||||
this.deps.updateMpvSubtitleRenderMetrics({ subPos: msg.data as number });
|
||||
} else if (msg.name === "sub-font-size") {
|
||||
this.deps.updateMpvSubtitleRenderMetrics({
|
||||
@@ -423,6 +516,10 @@ export class MpvIpcClient implements MpvClient {
|
||||
const nextSubText = (msg.data as string) || "";
|
||||
this.deps.setCurrentSubText(nextSubText);
|
||||
this.currentSubText = nextSubText;
|
||||
this.emit("subtitle-change", {
|
||||
text: nextSubText,
|
||||
isOverlayVisible: this.deps.isVisibleOverlayVisible(),
|
||||
});
|
||||
this.deps.subtitleWsBroadcast(nextSubText);
|
||||
if (this.deps.getOverlayWindowsCount() > 0) {
|
||||
this.deps.tokenizeSubtitle(nextSubText).then((subtitleData) => {
|
||||
@@ -431,9 +528,15 @@ export class MpvIpcClient implements MpvClient {
|
||||
}
|
||||
} else if (msg.request_id === MPV_REQUEST_ID_SUBTEXT_ASS) {
|
||||
const nextSubAssText = (msg.data as string) || "";
|
||||
this.emit("subtitle-ass-change", {
|
||||
text: nextSubAssText,
|
||||
});
|
||||
this.deps.setCurrentSubAssText(nextSubAssText);
|
||||
this.deps.broadcastToOverlayWindows("subtitle-ass:set", nextSubAssText);
|
||||
} else if (msg.request_id === MPV_REQUEST_ID_PATH) {
|
||||
this.emit("media-path-change", {
|
||||
path: (msg.data as string) || "",
|
||||
});
|
||||
this.deps.updateCurrentMediaPath(msg.data);
|
||||
} else if (msg.request_id === MPV_REQUEST_ID_AID) {
|
||||
this.currentAudioTrackId =
|
||||
|
||||
466
src/main.ts
466
src/main.ts
@@ -265,39 +265,6 @@ process.on("SIGTERM", () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
let yomitanExt: Extension | null = null;
|
||||
let yomitanSettingsWindow: BrowserWindow | null = null;
|
||||
let yomitanParserWindow: BrowserWindow | null = null;
|
||||
let yomitanParserReadyPromise: Promise<void> | null = null;
|
||||
let yomitanParserInitPromise: Promise<boolean> | null = null;
|
||||
let mpvClient: MpvIpcClient | null = null;
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let currentSubText = "";
|
||||
let currentSubAssText = "";
|
||||
let windowTracker: BaseWindowTracker | null = null;
|
||||
let subtitlePosition: SubtitlePosition | null = null;
|
||||
let currentMediaPath: string | null = null;
|
||||
let currentMediaTitle: string | null = null;
|
||||
let pendingSubtitlePosition: SubtitlePosition | null = null;
|
||||
let mecabTokenizer: MecabTokenizer | null = null;
|
||||
let keybindings: Keybinding[] = [];
|
||||
let subtitleTimingTracker: SubtitleTimingTracker | null = null;
|
||||
let ankiIntegration: AnkiIntegration | null = null;
|
||||
let secondarySubMode: SecondarySubMode = "hover";
|
||||
let lastSecondarySubToggleAtMs = 0;
|
||||
let previousSecondarySubVisibility: boolean | null = null;
|
||||
let mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics = {
|
||||
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
};
|
||||
|
||||
let shortcutsRegistered = false;
|
||||
let overlayRuntimeInitialized = false;
|
||||
let fieldGroupingResolver: ((choice: KikuFieldGroupingChoice) => void) | null =
|
||||
null;
|
||||
let fieldGroupingResolverSequence = 0;
|
||||
let runtimeOptionsManager: RuntimeOptionsManager | null = null;
|
||||
let trackerNotReadyWarningShown = false;
|
||||
let overlayDebugVisualizationEnabled = false;
|
||||
const overlayManager = createOverlayManagerService();
|
||||
const overlayContentMeasurementStore = createOverlayContentMeasurementStoreService({
|
||||
now: () => Date.now(),
|
||||
@@ -310,23 +277,103 @@ type OverlayHostLayer = "visible" | "invisible";
|
||||
const restoreVisibleOverlayOnModalClose = new Set<OverlayHostedModal>();
|
||||
const overlayModalAutoShownLayer = new Map<OverlayHostedModal, OverlayHostLayer>();
|
||||
|
||||
interface AppState {
|
||||
yomitanExt: Extension | null;
|
||||
yomitanSettingsWindow: BrowserWindow | null;
|
||||
yomitanParserWindow: BrowserWindow | null;
|
||||
yomitanParserReadyPromise: Promise<void> | null;
|
||||
yomitanParserInitPromise: Promise<boolean> | null;
|
||||
mpvClient: MpvIpcClient | null;
|
||||
reconnectTimer: ReturnType<typeof setTimeout> | null;
|
||||
currentSubText: string;
|
||||
currentSubAssText: string;
|
||||
windowTracker: BaseWindowTracker | null;
|
||||
subtitlePosition: SubtitlePosition | null;
|
||||
currentMediaPath: string | null;
|
||||
currentMediaTitle: string | null;
|
||||
pendingSubtitlePosition: SubtitlePosition | null;
|
||||
mecabTokenizer: MecabTokenizer | null;
|
||||
keybindings: Keybinding[];
|
||||
subtitleTimingTracker: SubtitleTimingTracker | null;
|
||||
ankiIntegration: AnkiIntegration | null;
|
||||
secondarySubMode: SecondarySubMode;
|
||||
lastSecondarySubToggleAtMs: number;
|
||||
previousSecondarySubVisibility: boolean | null;
|
||||
mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics;
|
||||
shortcutsRegistered: boolean;
|
||||
overlayRuntimeInitialized: boolean;
|
||||
fieldGroupingResolver: ((choice: KikuFieldGroupingChoice) => void) | null;
|
||||
fieldGroupingResolverSequence: number;
|
||||
runtimeOptionsManager: RuntimeOptionsManager | null;
|
||||
trackerNotReadyWarningShown: boolean;
|
||||
overlayDebugVisualizationEnabled: boolean;
|
||||
subsyncInProgress: boolean;
|
||||
initialArgs: CliArgs | null;
|
||||
mpvSocketPath: string;
|
||||
texthookerPort: number;
|
||||
backendOverride: string | null;
|
||||
autoStartOverlay: boolean;
|
||||
texthookerOnlyMode: boolean;
|
||||
}
|
||||
|
||||
const appState: AppState = {
|
||||
yomitanExt: null,
|
||||
yomitanSettingsWindow: null,
|
||||
yomitanParserWindow: null,
|
||||
yomitanParserReadyPromise: null,
|
||||
yomitanParserInitPromise: null,
|
||||
mpvClient: null,
|
||||
reconnectTimer: null,
|
||||
currentSubText: "",
|
||||
currentSubAssText: "",
|
||||
windowTracker: null,
|
||||
subtitlePosition: null,
|
||||
currentMediaPath: null,
|
||||
currentMediaTitle: null,
|
||||
pendingSubtitlePosition: null,
|
||||
mecabTokenizer: null,
|
||||
keybindings: [],
|
||||
subtitleTimingTracker: null,
|
||||
ankiIntegration: null,
|
||||
secondarySubMode: "hover",
|
||||
lastSecondarySubToggleAtMs: 0,
|
||||
previousSecondarySubVisibility: null,
|
||||
mpvSubtitleRenderMetrics: {
|
||||
...DEFAULT_MPV_SUBTITLE_RENDER_METRICS,
|
||||
},
|
||||
shortcutsRegistered: false,
|
||||
overlayRuntimeInitialized: false,
|
||||
fieldGroupingResolver: null,
|
||||
fieldGroupingResolverSequence: 0,
|
||||
runtimeOptionsManager: null,
|
||||
trackerNotReadyWarningShown: false,
|
||||
overlayDebugVisualizationEnabled: false,
|
||||
subsyncInProgress: false,
|
||||
initialArgs: null,
|
||||
mpvSocketPath: getDefaultSocketPath(),
|
||||
texthookerPort: DEFAULT_TEXTHOOKER_PORT,
|
||||
backendOverride: null,
|
||||
autoStartOverlay: false,
|
||||
texthookerOnlyMode: false,
|
||||
};
|
||||
|
||||
function getFieldGroupingResolver(): ((choice: KikuFieldGroupingChoice) => void) | null {
|
||||
return fieldGroupingResolver;
|
||||
return appState.fieldGroupingResolver;
|
||||
}
|
||||
|
||||
function setFieldGroupingResolver(
|
||||
resolver: ((choice: KikuFieldGroupingChoice) => void) | null,
|
||||
): void {
|
||||
if (!resolver) {
|
||||
fieldGroupingResolver = null;
|
||||
appState.fieldGroupingResolver = null;
|
||||
return;
|
||||
}
|
||||
const sequence = ++fieldGroupingResolverSequence;
|
||||
const sequence = ++appState.fieldGroupingResolverSequence;
|
||||
const wrappedResolver = (choice: KikuFieldGroupingChoice): void => {
|
||||
if (sequence !== fieldGroupingResolverSequence) return;
|
||||
if (sequence !== appState.fieldGroupingResolverSequence) return;
|
||||
resolver(choice);
|
||||
};
|
||||
fieldGroupingResolver = wrappedResolver;
|
||||
appState.fieldGroupingResolver = wrappedResolver;
|
||||
}
|
||||
|
||||
const fieldGroupingOverlayRuntime = createFieldGroupingOverlayRuntimeService<OverlayHostedModal>({
|
||||
@@ -348,15 +395,15 @@ const createFieldGroupingCallback =
|
||||
|
||||
const SUBTITLE_POSITIONS_DIR = path.join(CONFIG_DIR, "subtitle-positions");
|
||||
|
||||
function getRuntimeOptionsState(): RuntimeOptionState[] { if (!runtimeOptionsManager) return []; return runtimeOptionsManager.listOptions(); }
|
||||
function getRuntimeOptionsState(): RuntimeOptionState[] { if (!appState.runtimeOptionsManager) return []; return appState.runtimeOptionsManager.listOptions(); }
|
||||
|
||||
function getOverlayWindows(): BrowserWindow[] {
|
||||
return overlayManager.getOverlayWindows();
|
||||
}
|
||||
|
||||
function restorePreviousSecondarySubVisibility(): void {
|
||||
if (!mpvClient || !mpvClient.connected) return;
|
||||
mpvClient.restorePreviousSecondarySubVisibility();
|
||||
if (!appState.mpvClient || !appState.mpvClient.connected) return;
|
||||
appState.mpvClient.restorePreviousSecondarySubVisibility();
|
||||
}
|
||||
|
||||
function broadcastToOverlayWindows(channel: string, ...args: unknown[]): void {
|
||||
@@ -445,10 +492,10 @@ function sendToActiveOverlayWindow(
|
||||
|
||||
function setOverlayDebugVisualizationEnabled(enabled: boolean): void {
|
||||
setOverlayDebugVisualizationEnabledRuntimeService(
|
||||
overlayDebugVisualizationEnabled,
|
||||
appState.overlayDebugVisualizationEnabled,
|
||||
enabled,
|
||||
(next) => {
|
||||
overlayDebugVisualizationEnabled = next;
|
||||
appState.overlayDebugVisualizationEnabled = next;
|
||||
},
|
||||
(channel, ...args) => broadcastToOverlayWindows(channel, ...args),
|
||||
);
|
||||
@@ -480,7 +527,7 @@ function shouldBindVisibleOverlayToMpvSubVisibility(): boolean {
|
||||
function isAutoUpdateEnabledRuntime(): boolean {
|
||||
return isAutoUpdateEnabledRuntimeService(
|
||||
getResolvedConfig(),
|
||||
runtimeOptionsManager,
|
||||
appState.runtimeOptionsManager,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -503,47 +550,47 @@ async function jimakuFetchJson<T>(
|
||||
}
|
||||
|
||||
function loadSubtitlePosition(): SubtitlePosition | null {
|
||||
subtitlePosition = loadSubtitlePositionService({
|
||||
currentMediaPath,
|
||||
appState.subtitlePosition = loadSubtitlePositionService({
|
||||
currentMediaPath: appState.currentMediaPath,
|
||||
fallbackPosition: getResolvedConfig().subtitlePosition,
|
||||
subtitlePositionsDir: SUBTITLE_POSITIONS_DIR,
|
||||
});
|
||||
return subtitlePosition;
|
||||
return appState.subtitlePosition;
|
||||
}
|
||||
|
||||
function saveSubtitlePosition(position: SubtitlePosition): void {
|
||||
subtitlePosition = position;
|
||||
appState.subtitlePosition = position;
|
||||
saveSubtitlePositionService({
|
||||
position,
|
||||
currentMediaPath,
|
||||
currentMediaPath: appState.currentMediaPath,
|
||||
subtitlePositionsDir: SUBTITLE_POSITIONS_DIR,
|
||||
onQueuePending: (queued) => {
|
||||
pendingSubtitlePosition = queued;
|
||||
appState.pendingSubtitlePosition = queued;
|
||||
},
|
||||
onPersisted: () => {
|
||||
pendingSubtitlePosition = null;
|
||||
appState.pendingSubtitlePosition = null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateCurrentMediaPath(mediaPath: unknown): void {
|
||||
if (typeof mediaPath !== "string" || !isRemoteMediaPath(mediaPath)) {
|
||||
currentMediaTitle = null;
|
||||
appState.currentMediaTitle = null;
|
||||
}
|
||||
updateCurrentMediaPathService({
|
||||
mediaPath,
|
||||
currentMediaPath,
|
||||
pendingSubtitlePosition,
|
||||
currentMediaPath: appState.currentMediaPath,
|
||||
pendingSubtitlePosition: appState.pendingSubtitlePosition,
|
||||
subtitlePositionsDir: SUBTITLE_POSITIONS_DIR,
|
||||
loadSubtitlePosition: () => loadSubtitlePosition(),
|
||||
setCurrentMediaPath: (nextPath) => {
|
||||
currentMediaPath = nextPath;
|
||||
appState.currentMediaPath = nextPath;
|
||||
},
|
||||
clearPendingSubtitlePosition: () => {
|
||||
pendingSubtitlePosition = null;
|
||||
appState.pendingSubtitlePosition = null;
|
||||
},
|
||||
setSubtitlePosition: (position) => {
|
||||
subtitlePosition = position;
|
||||
appState.subtitlePosition = position;
|
||||
},
|
||||
broadcastSubtitlePosition: (position) => {
|
||||
broadcastToOverlayWindows("subtitle-position:set", position);
|
||||
@@ -554,26 +601,18 @@ function updateCurrentMediaPath(mediaPath: unknown): void {
|
||||
function updateCurrentMediaTitle(mediaTitle: unknown): void {
|
||||
if (typeof mediaTitle === "string") {
|
||||
const sanitized = mediaTitle.trim();
|
||||
currentMediaTitle = sanitized.length > 0 ? sanitized : null;
|
||||
appState.currentMediaTitle = sanitized.length > 0 ? sanitized : null;
|
||||
return;
|
||||
}
|
||||
currentMediaTitle = null;
|
||||
appState.currentMediaTitle = null;
|
||||
}
|
||||
|
||||
function resolveMediaPathForJimaku(mediaPath: string | null): string | null {
|
||||
return mediaPath && isRemoteMediaPath(mediaPath) && currentMediaTitle
|
||||
? currentMediaTitle
|
||||
return mediaPath && isRemoteMediaPath(mediaPath) && appState.currentMediaTitle
|
||||
? appState.currentMediaTitle
|
||||
: mediaPath;
|
||||
}
|
||||
|
||||
let subsyncInProgress = false;
|
||||
let initialArgs: CliArgs;
|
||||
let mpvSocketPath = getDefaultSocketPath();
|
||||
let texthookerPort = DEFAULT_TEXTHOOKER_PORT;
|
||||
let backendOverride: string | null = null;
|
||||
let autoStartOverlay = false;
|
||||
let texthookerOnlyMode = false;
|
||||
|
||||
const startupState = runStartupBootstrapRuntimeService({
|
||||
argv: process.argv,
|
||||
parseArgs: (argv) => parseArgs(argv),
|
||||
@@ -624,31 +663,31 @@ const startupState = runStartupBootstrapRuntimeService({
|
||||
await runAppReadyRuntimeService({
|
||||
loadSubtitlePosition: () => loadSubtitlePosition(),
|
||||
resolveKeybindings: () => {
|
||||
keybindings = resolveKeybindings(getResolvedConfig(), DEFAULT_KEYBINDINGS);
|
||||
appState.keybindings = resolveKeybindings(getResolvedConfig(), DEFAULT_KEYBINDINGS);
|
||||
},
|
||||
createMpvClient: () => {
|
||||
mpvClient = new MpvIpcClient(
|
||||
mpvSocketPath,
|
||||
appState.mpvClient = new MpvIpcClient(
|
||||
appState.mpvSocketPath,
|
||||
{
|
||||
getResolvedConfig: () => getResolvedConfig(),
|
||||
autoStartOverlay,
|
||||
autoStartOverlay: appState.autoStartOverlay,
|
||||
setOverlayVisible: (visible) => setOverlayVisible(visible),
|
||||
shouldBindVisibleOverlayToMpvSubVisibility: () =>
|
||||
shouldBindVisibleOverlayToMpvSubVisibility(),
|
||||
isVisibleOverlayVisible: () =>
|
||||
overlayManager.getVisibleOverlayVisible(),
|
||||
getReconnectTimer: () => reconnectTimer,
|
||||
getReconnectTimer: () => appState.reconnectTimer,
|
||||
setReconnectTimer: (timer) => {
|
||||
reconnectTimer = timer;
|
||||
appState.reconnectTimer = timer;
|
||||
},
|
||||
getCurrentSubText: () => currentSubText,
|
||||
getCurrentSubText: () => appState.currentSubText,
|
||||
setCurrentSubText: (text) => {
|
||||
currentSubText = text;
|
||||
appState.currentSubText = text;
|
||||
},
|
||||
setCurrentSubAssText: (text) => {
|
||||
currentSubAssText = text;
|
||||
appState.currentSubAssText = text;
|
||||
},
|
||||
getSubtitleTimingTracker: () => subtitleTimingTracker,
|
||||
getSubtitleTimingTracker: () => appState.subtitleTimingTracker,
|
||||
subtitleWsBroadcast: (text) => {
|
||||
subtitleWsService.broadcast(text);
|
||||
},
|
||||
@@ -657,26 +696,21 @@ const startupState = runStartupBootstrapRuntimeService({
|
||||
broadcastToOverlayWindows: (channel, ...channelArgs) => {
|
||||
broadcastToOverlayWindows(channel, ...channelArgs);
|
||||
},
|
||||
updateCurrentMediaPath: (mediaPath) => {
|
||||
updateCurrentMediaPath(mediaPath);
|
||||
},
|
||||
updateCurrentMediaTitle: (mediaTitle) => {
|
||||
updateCurrentMediaTitle(mediaTitle);
|
||||
},
|
||||
updateMpvSubtitleRenderMetrics: (patch) => {
|
||||
updateMpvSubtitleRenderMetrics(patch);
|
||||
},
|
||||
getMpvSubtitleRenderMetrics: () => mpvSubtitleRenderMetrics,
|
||||
getMpvSubtitleRenderMetrics: () => appState.mpvSubtitleRenderMetrics,
|
||||
getPreviousSecondarySubVisibility: () =>
|
||||
previousSecondarySubVisibility,
|
||||
appState.previousSecondarySubVisibility,
|
||||
setPreviousSecondarySubVisibility: (value) => {
|
||||
previousSecondarySubVisibility = value;
|
||||
appState.previousSecondarySubVisibility = value;
|
||||
},
|
||||
showMpvOsd: (text) => {
|
||||
showMpvOsd(text);
|
||||
},
|
||||
},
|
||||
);
|
||||
bindMpvClientEventHandlers(appState.mpvClient);
|
||||
},
|
||||
reloadConfig: () => {
|
||||
configService.reloadConfig();
|
||||
@@ -686,12 +720,12 @@ const startupState = runStartupBootstrapRuntimeService({
|
||||
getConfigWarnings: () => configService.getWarnings(),
|
||||
logConfigWarning: (warning) => appLogger.logConfigWarning(warning),
|
||||
initRuntimeOptionsManager: () => {
|
||||
runtimeOptionsManager = new RuntimeOptionsManager(
|
||||
appState.runtimeOptionsManager = new RuntimeOptionsManager(
|
||||
() => configService.getConfig().ankiConnect,
|
||||
{
|
||||
applyAnkiPatch: (patch) => {
|
||||
if (ankiIntegration) {
|
||||
ankiIntegration.applyRuntimeConfigPatch(patch);
|
||||
if (appState.ankiIntegration) {
|
||||
appState.ankiIntegration.applyRuntimeConfigPatch(patch);
|
||||
}
|
||||
},
|
||||
onOptionsChanged: () => {
|
||||
@@ -702,28 +736,28 @@ const startupState = runStartupBootstrapRuntimeService({
|
||||
);
|
||||
},
|
||||
setSecondarySubMode: (mode) => {
|
||||
secondarySubMode = mode;
|
||||
appState.secondarySubMode = mode;
|
||||
},
|
||||
defaultSecondarySubMode: "hover",
|
||||
defaultWebsocketPort: DEFAULT_CONFIG.websocket.port,
|
||||
hasMpvWebsocketPlugin: () => hasMpvWebsocketPlugin(),
|
||||
startSubtitleWebsocket: (port) => {
|
||||
subtitleWsService.start(port, () => currentSubText);
|
||||
subtitleWsService.start(port, () => appState.currentSubText);
|
||||
},
|
||||
log: (message) => appLogger.logInfo(message),
|
||||
createMecabTokenizerAndCheck: async () => {
|
||||
const tokenizer = new MecabTokenizer();
|
||||
mecabTokenizer = tokenizer;
|
||||
appState.mecabTokenizer = tokenizer;
|
||||
await tokenizer.checkAvailability();
|
||||
},
|
||||
createSubtitleTimingTracker: () => {
|
||||
const tracker = new SubtitleTimingTracker();
|
||||
subtitleTimingTracker = tracker;
|
||||
appState.subtitleTimingTracker = tracker;
|
||||
},
|
||||
loadYomitanExtension: async () => {
|
||||
await loadYomitanExtension();
|
||||
},
|
||||
texthookerOnlyMode,
|
||||
texthookerOnlyMode: appState.texthookerOnlyMode,
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig: () =>
|
||||
shouldAutoInitializeOverlayRuntimeFromConfig(),
|
||||
initializeOverlayRuntime: () => initializeOverlayRuntime(),
|
||||
@@ -735,30 +769,30 @@ const startupState = runStartupBootstrapRuntimeService({
|
||||
globalShortcut.unregisterAll();
|
||||
subtitleWsService.stop();
|
||||
texthookerService.stop();
|
||||
if (yomitanParserWindow && !yomitanParserWindow.isDestroyed()) {
|
||||
yomitanParserWindow.destroy();
|
||||
if (appState.yomitanParserWindow && !appState.yomitanParserWindow.isDestroyed()) {
|
||||
appState.yomitanParserWindow.destroy();
|
||||
}
|
||||
yomitanParserWindow = null;
|
||||
yomitanParserReadyPromise = null;
|
||||
yomitanParserInitPromise = null;
|
||||
if (windowTracker) {
|
||||
windowTracker.stop();
|
||||
appState.yomitanParserWindow = null;
|
||||
appState.yomitanParserReadyPromise = null;
|
||||
appState.yomitanParserInitPromise = null;
|
||||
if (appState.windowTracker) {
|
||||
appState.windowTracker.stop();
|
||||
}
|
||||
if (mpvClient && mpvClient.socket) {
|
||||
mpvClient.socket.destroy();
|
||||
if (appState.mpvClient && appState.mpvClient.socket) {
|
||||
appState.mpvClient.socket.destroy();
|
||||
}
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer);
|
||||
if (appState.reconnectTimer) {
|
||||
clearTimeout(appState.reconnectTimer);
|
||||
}
|
||||
if (subtitleTimingTracker) {
|
||||
subtitleTimingTracker.destroy();
|
||||
if (appState.subtitleTimingTracker) {
|
||||
appState.subtitleTimingTracker.destroy();
|
||||
}
|
||||
if (ankiIntegration) {
|
||||
ankiIntegration.destroy();
|
||||
if (appState.ankiIntegration) {
|
||||
appState.ankiIntegration.destroy();
|
||||
}
|
||||
},
|
||||
shouldRestoreWindowsOnActivate: () =>
|
||||
overlayRuntimeInitialized && BrowserWindow.getAllWindows().length === 0,
|
||||
appState.overlayRuntimeInitialized && BrowserWindow.getAllWindows().length === 0,
|
||||
restoreWindowsOnActivate: () => {
|
||||
createMainWindow();
|
||||
createInvisibleWindow();
|
||||
@@ -769,12 +803,12 @@ const startupState = runStartupBootstrapRuntimeService({
|
||||
},
|
||||
});
|
||||
|
||||
initialArgs = startupState.initialArgs;
|
||||
mpvSocketPath = startupState.mpvSocketPath;
|
||||
texthookerPort = startupState.texthookerPort;
|
||||
backendOverride = startupState.backendOverride;
|
||||
autoStartOverlay = startupState.autoStartOverlay;
|
||||
texthookerOnlyMode = startupState.texthookerOnlyMode;
|
||||
appState.initialArgs = startupState.initialArgs;
|
||||
appState.mpvSocketPath = startupState.mpvSocketPath;
|
||||
appState.texthookerPort = startupState.texthookerPort;
|
||||
appState.backendOverride = startupState.backendOverride;
|
||||
appState.autoStartOverlay = startupState.autoStartOverlay;
|
||||
appState.texthookerOnlyMode = startupState.texthookerOnlyMode;
|
||||
|
||||
function handleCliCommand(
|
||||
args: CliArgs,
|
||||
@@ -782,18 +816,18 @@ function handleCliCommand(
|
||||
): void {
|
||||
const deps = createCliCommandDepsRuntimeService({
|
||||
mpv: {
|
||||
getSocketPath: () => mpvSocketPath,
|
||||
getSocketPath: () => appState.mpvSocketPath,
|
||||
setSocketPath: (socketPath) => {
|
||||
mpvSocketPath = socketPath;
|
||||
appState.mpvSocketPath = socketPath;
|
||||
},
|
||||
getClient: () => mpvClient,
|
||||
getClient: () => appState.mpvClient,
|
||||
showOsd: (text) => showMpvOsd(text),
|
||||
},
|
||||
texthooker: {
|
||||
service: texthookerService,
|
||||
getPort: () => texthookerPort,
|
||||
getPort: () => appState.texthookerPort,
|
||||
setPort: (port) => {
|
||||
texthookerPort = port;
|
||||
appState.texthookerPort = port;
|
||||
},
|
||||
shouldOpenBrowser: () => getResolvedConfig().texthooker?.openBrowser !== false,
|
||||
openInBrowser: (url) => {
|
||||
@@ -803,7 +837,7 @@ function handleCliCommand(
|
||||
},
|
||||
},
|
||||
overlay: {
|
||||
isInitialized: () => overlayRuntimeInitialized,
|
||||
isInitialized: () => appState.overlayRuntimeInitialized,
|
||||
initialize: () => initializeOverlayRuntime(),
|
||||
toggleVisible: () => toggleVisibleOverlay(),
|
||||
toggleInvisible: () => toggleInvisibleOverlay(),
|
||||
@@ -847,21 +881,37 @@ function handleCliCommand(
|
||||
}
|
||||
|
||||
function handleInitialArgs(): void {
|
||||
handleCliCommand(initialArgs, "initial");
|
||||
if (!appState.initialArgs) return;
|
||||
handleCliCommand(appState.initialArgs, "initial");
|
||||
}
|
||||
|
||||
function bindMpvClientEventHandlers(mpvClient: MpvIpcClient): void {
|
||||
mpvClient.on("media-path-change", ({ path }) => {
|
||||
updateCurrentMediaPath(path);
|
||||
});
|
||||
mpvClient.on("media-title-change", ({ title }) => {
|
||||
updateCurrentMediaTitle(title);
|
||||
});
|
||||
mpvClient.on("subtitle-ass-change", ({ text }) => {
|
||||
appState.currentSubAssText = text;
|
||||
});
|
||||
mpvClient.on("subtitle-change", ({ text }) => {
|
||||
appState.currentSubText = text;
|
||||
});
|
||||
}
|
||||
|
||||
function updateMpvSubtitleRenderMetrics(
|
||||
patch: Partial<MpvSubtitleRenderMetrics>,
|
||||
): void {
|
||||
const { next, changed } = applyMpvSubtitleRenderMetricsPatchService(
|
||||
mpvSubtitleRenderMetrics,
|
||||
appState.mpvSubtitleRenderMetrics,
|
||||
patch,
|
||||
);
|
||||
if (!changed) return;
|
||||
mpvSubtitleRenderMetrics = next;
|
||||
appState.mpvSubtitleRenderMetrics = next;
|
||||
broadcastToOverlayWindows(
|
||||
"mpv-subtitle-render-metrics:set",
|
||||
mpvSubtitleRenderMetrics,
|
||||
appState.mpvSubtitleRenderMetrics,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -869,20 +919,20 @@ async function tokenizeSubtitle(text: string): Promise<SubtitleData> {
|
||||
return tokenizeSubtitleService(
|
||||
text,
|
||||
createTokenizerDepsRuntimeService({
|
||||
getYomitanExt: () => yomitanExt,
|
||||
getYomitanParserWindow: () => yomitanParserWindow,
|
||||
getYomitanExt: () => appState.yomitanExt,
|
||||
getYomitanParserWindow: () => appState.yomitanParserWindow,
|
||||
setYomitanParserWindow: (window) => {
|
||||
yomitanParserWindow = window;
|
||||
appState.yomitanParserWindow = window;
|
||||
},
|
||||
getYomitanParserReadyPromise: () => yomitanParserReadyPromise,
|
||||
getYomitanParserReadyPromise: () => appState.yomitanParserReadyPromise,
|
||||
setYomitanParserReadyPromise: (promise) => {
|
||||
yomitanParserReadyPromise = promise;
|
||||
appState.yomitanParserReadyPromise = promise;
|
||||
},
|
||||
getYomitanParserInitPromise: () => yomitanParserInitPromise,
|
||||
getYomitanParserInitPromise: () => appState.yomitanParserInitPromise,
|
||||
setYomitanParserInitPromise: (promise) => {
|
||||
yomitanParserInitPromise = promise;
|
||||
appState.yomitanParserInitPromise = promise;
|
||||
},
|
||||
getMecabTokenizer: () => mecabTokenizer,
|
||||
getMecabTokenizer: () => appState.mecabTokenizer,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -912,18 +962,18 @@ function enforceOverlayLayerOrder(): void {
|
||||
async function loadYomitanExtension(): Promise<Extension | null> {
|
||||
return loadYomitanExtensionService({
|
||||
userDataPath: USER_DATA_PATH,
|
||||
getYomitanParserWindow: () => yomitanParserWindow,
|
||||
getYomitanParserWindow: () => appState.yomitanParserWindow,
|
||||
setYomitanParserWindow: (window) => {
|
||||
yomitanParserWindow = window;
|
||||
appState.yomitanParserWindow = window;
|
||||
},
|
||||
setYomitanParserReadyPromise: (promise) => {
|
||||
yomitanParserReadyPromise = promise;
|
||||
appState.yomitanParserReadyPromise = promise;
|
||||
},
|
||||
setYomitanParserInitPromise: (promise) => {
|
||||
yomitanParserInitPromise = promise;
|
||||
appState.yomitanParserInitPromise = promise;
|
||||
},
|
||||
setYomitanExtension: (extension) => {
|
||||
yomitanExt = extension;
|
||||
appState.yomitanExt = extension;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -933,7 +983,7 @@ function createOverlayWindow(kind: "visible" | "invisible"): BrowserWindow {
|
||||
kind,
|
||||
{
|
||||
isDev,
|
||||
overlayDebugVisualizationEnabled,
|
||||
overlayDebugVisualizationEnabled: appState.overlayDebugVisualizationEnabled,
|
||||
ensureOverlayWindowLevel: (window) => ensureOverlayWindowLevel(window),
|
||||
onRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(),
|
||||
setOverlayDebugVisualizationEnabled: (enabled) =>
|
||||
@@ -967,12 +1017,12 @@ function createInvisibleWindow(): BrowserWindow {
|
||||
}
|
||||
|
||||
function initializeOverlayRuntime(): void {
|
||||
if (overlayRuntimeInitialized) {
|
||||
if (appState.overlayRuntimeInitialized) {
|
||||
return;
|
||||
}
|
||||
const result = initializeOverlayRuntimeService(
|
||||
{
|
||||
backendOverride,
|
||||
backendOverride: appState.backendOverride,
|
||||
getInitialInvisibleOverlayVisibility: () =>
|
||||
getInitialInvisibleOverlayVisibility(),
|
||||
createMainWindow: () => {
|
||||
@@ -1004,30 +1054,30 @@ function initializeOverlayRuntime(): void {
|
||||
syncOverlayShortcuts();
|
||||
},
|
||||
setWindowTracker: (tracker) => {
|
||||
windowTracker = tracker;
|
||||
appState.windowTracker = tracker;
|
||||
},
|
||||
getResolvedConfig: () => getResolvedConfig(),
|
||||
getSubtitleTimingTracker: () => subtitleTimingTracker,
|
||||
getMpvClient: () => mpvClient,
|
||||
getRuntimeOptionsManager: () => runtimeOptionsManager,
|
||||
getSubtitleTimingTracker: () => appState.subtitleTimingTracker,
|
||||
getMpvClient: () => appState.mpvClient,
|
||||
getRuntimeOptionsManager: () => appState.runtimeOptionsManager,
|
||||
setAnkiIntegration: (integration) => {
|
||||
ankiIntegration = integration as AnkiIntegration | null;
|
||||
appState.ankiIntegration = integration as AnkiIntegration | null;
|
||||
},
|
||||
showDesktopNotification,
|
||||
createFieldGroupingCallback: () => createFieldGroupingCallback(),
|
||||
},
|
||||
);
|
||||
overlayManager.setInvisibleOverlayVisible(result.invisibleOverlayVisible);
|
||||
overlayRuntimeInitialized = true;
|
||||
appState.overlayRuntimeInitialized = true;
|
||||
}
|
||||
|
||||
function openYomitanSettings(): void {
|
||||
openYomitanSettingsWindow(
|
||||
{
|
||||
yomitanExt,
|
||||
getExistingWindow: () => yomitanSettingsWindow,
|
||||
yomitanExt: appState.yomitanExt,
|
||||
getExistingWindow: () => appState.yomitanSettingsWindow,
|
||||
setWindow: (window: BrowserWindow | null) => {
|
||||
yomitanSettingsWindow = window;
|
||||
appState.yomitanSettingsWindow = window;
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1090,13 +1140,13 @@ function tryHandleOverlayShortcutLocalFallback(input: Electron.Input): boolean {
|
||||
function cycleSecondarySubMode(): void {
|
||||
cycleSecondarySubModeService(
|
||||
{
|
||||
getSecondarySubMode: () => secondarySubMode,
|
||||
getSecondarySubMode: () => appState.secondarySubMode,
|
||||
setSecondarySubMode: (mode: SecondarySubMode) => {
|
||||
secondarySubMode = mode;
|
||||
appState.secondarySubMode = mode;
|
||||
},
|
||||
getLastSecondarySubToggleAtMs: () => lastSecondarySubToggleAtMs,
|
||||
getLastSecondarySubToggleAtMs: () => appState.lastSecondarySubToggleAtMs,
|
||||
setLastSecondarySubToggleAtMs: (timestampMs: number) => {
|
||||
lastSecondarySubToggleAtMs = timestampMs;
|
||||
appState.lastSecondarySubToggleAtMs = timestampMs;
|
||||
},
|
||||
broadcastSecondarySubMode: (mode: SecondarySubMode) => {
|
||||
broadcastToOverlayWindows("secondary-subtitle:mode", mode);
|
||||
@@ -1109,7 +1159,7 @@ function cycleSecondarySubMode(): void {
|
||||
function showMpvOsd(text: string): void {
|
||||
appendToMpvLog(`[OSD] ${text}`);
|
||||
showMpvOsdRuntimeService(
|
||||
mpvClient,
|
||||
appState.mpvClient,
|
||||
text,
|
||||
(line) => {
|
||||
console.log(line);
|
||||
@@ -1141,11 +1191,11 @@ const mineSentenceSession = numericShortcutRuntime.createSession();
|
||||
|
||||
function getSubsyncRuntimeDeps() {
|
||||
return {
|
||||
getMpvClient: () => mpvClient,
|
||||
getMpvClient: () => appState.mpvClient,
|
||||
getResolvedSubsyncConfig: () => getSubsyncConfig(getResolvedConfig().subsync),
|
||||
isSubsyncInProgress: () => subsyncInProgress,
|
||||
isSubsyncInProgress: () => appState.subsyncInProgress,
|
||||
setSubsyncInProgress: (inProgress: boolean) => {
|
||||
subsyncInProgress = inProgress;
|
||||
appState.subsyncInProgress = inProgress;
|
||||
},
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
openManualPicker: (payload: SubsyncManualPayload) => {
|
||||
@@ -1180,7 +1230,7 @@ function handleMultiCopyDigit(count: number): void {
|
||||
handleMultiCopyDigitService(
|
||||
count,
|
||||
{
|
||||
subtitleTimingTracker,
|
||||
subtitleTimingTracker: appState.subtitleTimingTracker,
|
||||
writeClipboardText: (text) => clipboard.writeText(text),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
},
|
||||
@@ -1190,7 +1240,7 @@ function handleMultiCopyDigit(count: number): void {
|
||||
function copyCurrentSubtitle(): void {
|
||||
copyCurrentSubtitleService(
|
||||
{
|
||||
subtitleTimingTracker,
|
||||
subtitleTimingTracker: appState.subtitleTimingTracker,
|
||||
writeClipboardText: (text) => clipboard.writeText(text),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
},
|
||||
@@ -1200,7 +1250,7 @@ function copyCurrentSubtitle(): void {
|
||||
async function updateLastCardFromClipboard(): Promise<void> {
|
||||
await updateLastCardFromClipboardService(
|
||||
{
|
||||
ankiIntegration,
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
readClipboardText: () => clipboard.readText(),
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
},
|
||||
@@ -1210,7 +1260,7 @@ async function updateLastCardFromClipboard(): Promise<void> {
|
||||
async function triggerFieldGrouping(): Promise<void> {
|
||||
await triggerFieldGroupingService(
|
||||
{
|
||||
ankiIntegration,
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
},
|
||||
);
|
||||
@@ -1219,7 +1269,7 @@ async function triggerFieldGrouping(): Promise<void> {
|
||||
async function markLastCardAsAudioCard(): Promise<void> {
|
||||
await markLastCardAsAudioCardService(
|
||||
{
|
||||
ankiIntegration,
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
},
|
||||
);
|
||||
@@ -1228,8 +1278,8 @@ async function markLastCardAsAudioCard(): Promise<void> {
|
||||
async function mineSentenceCard(): Promise<void> {
|
||||
await mineSentenceCardService(
|
||||
{
|
||||
ankiIntegration,
|
||||
mpvClient,
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
mpvClient: appState.mpvClient,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
},
|
||||
);
|
||||
@@ -1255,10 +1305,10 @@ function handleMineSentenceDigit(count: number): void {
|
||||
handleMineSentenceDigitService(
|
||||
count,
|
||||
{
|
||||
subtitleTimingTracker,
|
||||
ankiIntegration,
|
||||
subtitleTimingTracker: appState.subtitleTimingTracker,
|
||||
ankiIntegration: appState.ankiIntegration,
|
||||
getCurrentSecondarySubText: () =>
|
||||
mpvClient?.currentSecondarySubText || undefined,
|
||||
appState.mpvClient?.currentSecondarySubText || undefined,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
logError: (message, err) => {
|
||||
console.error(message, err);
|
||||
@@ -1268,7 +1318,7 @@ function handleMineSentenceDigit(count: number): void {
|
||||
}
|
||||
|
||||
function registerOverlayShortcuts(): void {
|
||||
shortcutsRegistered = registerOverlayShortcutsService(
|
||||
appState.shortcutsRegistered = registerOverlayShortcutsService(
|
||||
getConfiguredShortcuts(),
|
||||
getOverlayShortcutRuntimeHandlers().overlayHandlers,
|
||||
);
|
||||
@@ -1284,24 +1334,24 @@ function getOverlayShortcutLifecycleDeps() {
|
||||
}
|
||||
|
||||
function unregisterOverlayShortcuts(): void {
|
||||
shortcutsRegistered = unregisterOverlayShortcutsRuntimeService(
|
||||
shortcutsRegistered,
|
||||
appState.shortcutsRegistered = unregisterOverlayShortcutsRuntimeService(
|
||||
appState.shortcutsRegistered,
|
||||
getOverlayShortcutLifecycleDeps(),
|
||||
);
|
||||
}
|
||||
|
||||
function shouldOverlayShortcutsBeActive(): boolean { return overlayRuntimeInitialized; }
|
||||
function shouldOverlayShortcutsBeActive(): boolean { return appState.overlayRuntimeInitialized; }
|
||||
function syncOverlayShortcuts(): void {
|
||||
shortcutsRegistered = syncOverlayShortcutsRuntimeService(
|
||||
appState.shortcutsRegistered = syncOverlayShortcutsRuntimeService(
|
||||
shouldOverlayShortcutsBeActive(),
|
||||
shortcutsRegistered,
|
||||
appState.shortcutsRegistered,
|
||||
getOverlayShortcutLifecycleDeps(),
|
||||
);
|
||||
}
|
||||
function refreshOverlayShortcuts(): void {
|
||||
shortcutsRegistered = refreshOverlayShortcutsRuntimeService(
|
||||
appState.shortcutsRegistered = refreshOverlayShortcutsRuntimeService(
|
||||
shouldOverlayShortcutsBeActive(),
|
||||
shortcutsRegistered,
|
||||
appState.shortcutsRegistered,
|
||||
getOverlayShortcutLifecycleDeps(),
|
||||
);
|
||||
}
|
||||
@@ -1311,10 +1361,10 @@ function updateVisibleOverlayVisibility(): void {
|
||||
{
|
||||
visibleOverlayVisible: overlayManager.getVisibleOverlayVisible(),
|
||||
mainWindow: overlayManager.getMainWindow(),
|
||||
windowTracker,
|
||||
trackerNotReadyWarningShown,
|
||||
windowTracker: appState.windowTracker,
|
||||
trackerNotReadyWarningShown: appState.trackerNotReadyWarningShown,
|
||||
setTrackerNotReadyWarningShown: (shown) => {
|
||||
trackerNotReadyWarningShown = shown;
|
||||
appState.trackerNotReadyWarningShown = shown;
|
||||
},
|
||||
updateVisibleOverlayBounds: (geometry) => updateVisibleOverlayBounds(geometry),
|
||||
ensureOverlayWindowLevel: (window) => ensureOverlayWindowLevel(window),
|
||||
@@ -1330,7 +1380,7 @@ function updateInvisibleOverlayVisibility(): void {
|
||||
invisibleWindow: overlayManager.getInvisibleWindow(),
|
||||
visibleOverlayVisible: overlayManager.getVisibleOverlayVisible(),
|
||||
invisibleOverlayVisible: overlayManager.getInvisibleOverlayVisible(),
|
||||
windowTracker,
|
||||
windowTracker: appState.windowTracker,
|
||||
updateInvisibleOverlayBounds: (geometry) => updateInvisibleOverlayBounds(geometry),
|
||||
ensureOverlayWindowLevel: (window) => ensureOverlayWindowLevel(window),
|
||||
enforceOverlayLayerOrder: () => enforceOverlayLayerOrder(),
|
||||
@@ -1367,9 +1417,9 @@ function setVisibleOverlayVisible(visible: boolean): void {
|
||||
syncInvisibleOverlayMousePassthrough(),
|
||||
shouldBindVisibleOverlayToMpvSubVisibility: () =>
|
||||
shouldBindVisibleOverlayToMpvSubVisibility(),
|
||||
isMpvConnected: () => Boolean(mpvClient && mpvClient.connected),
|
||||
isMpvConnected: () => Boolean(appState.mpvClient && appState.mpvClient.connected),
|
||||
setMpvSubVisibility: (mpvSubVisible) => {
|
||||
setMpvSubVisibilityRuntimeService(mpvClient, mpvSubVisible);
|
||||
setMpvSubVisibilityRuntimeService(appState.mpvClient, mpvSubVisible);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1426,21 +1476,21 @@ function handleMpvCommandFromIpc(command: (string | number)[]): void {
|
||||
triggerSubsyncFromConfig: () => triggerSubsyncFromConfig(),
|
||||
openRuntimeOptionsPalette: () => openRuntimeOptionsPalette(),
|
||||
runtimeOptionsCycle: (id, direction) => {
|
||||
if (!runtimeOptionsManager) {
|
||||
if (!appState.runtimeOptionsManager) {
|
||||
return { ok: false, error: "Runtime options manager unavailable" };
|
||||
}
|
||||
return applyRuntimeOptionResultRuntimeService(
|
||||
runtimeOptionsManager.cycleOption(id, direction),
|
||||
appState.runtimeOptionsManager.cycleOption(id, direction),
|
||||
(text) => showMpvOsd(text),
|
||||
);
|
||||
},
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
mpvReplaySubtitle: () => replayCurrentSubtitleRuntimeService(mpvClient),
|
||||
mpvPlayNextSubtitle: () => playNextSubtitleRuntimeService(mpvClient),
|
||||
mpvReplaySubtitle: () => replayCurrentSubtitleRuntimeService(appState.mpvClient),
|
||||
mpvPlayNextSubtitle: () => playNextSubtitleRuntimeService(appState.mpvClient),
|
||||
mpvSendCommand: (rawCommand) =>
|
||||
sendMpvCommandRuntimeService(mpvClient, rawCommand),
|
||||
isMpvConnected: () => Boolean(mpvClient && mpvClient.connected),
|
||||
hasRuntimeOptionsManager: () => runtimeOptionsManager !== null,
|
||||
sendMpvCommandRuntimeService(appState.mpvClient, rawCommand),
|
||||
isMpvConnected: () => Boolean(appState.mpvClient && appState.mpvClient.connected),
|
||||
hasRuntimeOptionsManager: () => appState.runtimeOptionsManager !== null,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1454,14 +1504,14 @@ async function runSubsyncManualFromIpc(
|
||||
const runtimeOptionsIpcDeps = {
|
||||
setRuntimeOption: (id: string, value: unknown) =>
|
||||
setRuntimeOptionFromIpcRuntimeService(
|
||||
runtimeOptionsManager,
|
||||
appState.runtimeOptionsManager,
|
||||
id as RuntimeOptionId,
|
||||
value as RuntimeOptionValue,
|
||||
(text) => showMpvOsd(text),
|
||||
),
|
||||
cycleRuntimeOption: (id: string, direction: 1 | -1) =>
|
||||
cycleRuntimeOptionFromIpcRuntimeService(
|
||||
runtimeOptionsManager,
|
||||
appState.runtimeOptionsManager,
|
||||
id as RuntimeOptionId,
|
||||
direction,
|
||||
(text) => showMpvOsd(text),
|
||||
@@ -1480,21 +1530,21 @@ registerIpcHandlersService(
|
||||
openYomitanSettings: () => openYomitanSettings(),
|
||||
quitApp: () => app.quit(),
|
||||
toggleVisibleOverlay: () => toggleVisibleOverlay(),
|
||||
tokenizeCurrentSubtitle: () => tokenizeSubtitle(currentSubText),
|
||||
getCurrentSubtitleAss: () => currentSubAssText,
|
||||
getMpvSubtitleRenderMetrics: () => mpvSubtitleRenderMetrics,
|
||||
tokenizeCurrentSubtitle: () => tokenizeSubtitle(appState.currentSubText),
|
||||
getCurrentSubtitleAss: () => appState.currentSubAssText,
|
||||
getMpvSubtitleRenderMetrics: () => appState.mpvSubtitleRenderMetrics,
|
||||
getSubtitlePosition: () => loadSubtitlePosition(),
|
||||
getSubtitleStyle: () => getResolvedConfig().subtitleStyle ?? null,
|
||||
saveSubtitlePosition: (position) =>
|
||||
saveSubtitlePosition(position as SubtitlePosition),
|
||||
getMecabTokenizer: () => mecabTokenizer,
|
||||
getMecabTokenizer: () => appState.mecabTokenizer,
|
||||
handleMpvCommand: (command) => handleMpvCommandFromIpc(command),
|
||||
getKeybindings: () => keybindings,
|
||||
getSecondarySubMode: () => secondarySubMode,
|
||||
getMpvClient: () => mpvClient,
|
||||
getKeybindings: () => appState.keybindings,
|
||||
getSecondarySubMode: () => appState.secondarySubMode,
|
||||
getMpvClient: () => appState.mpvClient,
|
||||
runSubsyncManual: (request) =>
|
||||
runSubsyncManualFromIpc(request as SubsyncManualRunRequest),
|
||||
getAnkiConnectStatus: () => ankiIntegration !== null,
|
||||
getAnkiConnectStatus: () => appState.ankiIntegration !== null,
|
||||
getRuntimeOptions: () => getRuntimeOptionsState(),
|
||||
setRuntimeOption: runtimeOptionsIpcDeps.setRuntimeOption,
|
||||
cycleRuntimeOption: runtimeOptionsIpcDeps.cycleRuntimeOption,
|
||||
@@ -1510,12 +1560,12 @@ registerAnkiJimakuIpcRuntimeService(
|
||||
configService.patchRawConfig({ ankiConnect: { enabled } });
|
||||
},
|
||||
getResolvedConfig: () => getResolvedConfig(),
|
||||
getRuntimeOptionsManager: () => runtimeOptionsManager,
|
||||
getSubtitleTimingTracker: () => subtitleTimingTracker,
|
||||
getMpvClient: () => mpvClient,
|
||||
getAnkiIntegration: () => ankiIntegration,
|
||||
getRuntimeOptionsManager: () => appState.runtimeOptionsManager,
|
||||
getSubtitleTimingTracker: () => appState.subtitleTimingTracker,
|
||||
getMpvClient: () => appState.mpvClient,
|
||||
getAnkiIntegration: () => appState.ankiIntegration,
|
||||
setAnkiIntegration: (integration) => {
|
||||
ankiIntegration = integration;
|
||||
appState.ankiIntegration = integration;
|
||||
},
|
||||
showDesktopNotification,
|
||||
createFieldGroupingCallback: () => createFieldGroupingCallback(),
|
||||
@@ -1523,7 +1573,7 @@ registerAnkiJimakuIpcRuntimeService(
|
||||
getFieldGroupingResolver: () => getFieldGroupingResolver(),
|
||||
setFieldGroupingResolver: (resolver) => setFieldGroupingResolver(resolver),
|
||||
parseMediaInfo: (mediaPath) => parseMediaInfo(resolveMediaPathForJimaku(mediaPath)),
|
||||
getCurrentMediaPath: () => currentMediaPath,
|
||||
getCurrentMediaPath: () => appState.currentMediaPath,
|
||||
jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query),
|
||||
getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(),
|
||||
getJimakuLanguagePreference: () => getJimakuLanguagePreference(),
|
||||
|
||||
Reference in New Issue
Block a user