Files
SubMiner/src/main/runtime/current-subtitle-snapshot.ts
T
sudacode 9d77907877 feat(overlay): add loading OSD spinner and queue notifications until ren
- Show mpv OSD spinner from start-file until subminer-overlay-loading-ready; force-shown for visible-overlay startup regardless of osd_messages setting
- Gate non-macOS overlay visibility on content-ready so first subtitle line is immediately hoverable and clickable
- Queue startup notifications in main process until overlay window finishes loading; upsert progress cards by id to avoid cold-start floods
- Defer background warmups until after overlay runtime init so queued notifications can deliver promptly
- Preserve character dictionary checking/building/importing/ready phases as distinct history entries; route building and importing to system notifications when notificationType is both
2026-06-08 02:22:54 -07:00

129 lines
3.7 KiB
TypeScript

import type { SubtitleData } from '../../types';
type CurrentSubtitleMpvClient = {
connected?: boolean;
requestProperty: (name: string) => Promise<unknown>;
};
export async function resolveCurrentSubtitleForRenderer(deps: {
currentSubText: string;
currentSubtitleData: SubtitleData | null;
withCurrentSubtitleTiming: (payload: SubtitleData) => SubtitleData;
tokenizeSubtitle?: (text: string) => Promise<SubtitleData | null>;
tokenizeUncached?: boolean;
onResolvedSubtitle?: (payload: SubtitleData) => void;
}): Promise<SubtitleData> {
const resolve = (payload: SubtitleData): SubtitleData => {
const timedPayload = deps.withCurrentSubtitleTiming(payload);
deps.onResolvedSubtitle?.(timedPayload);
return timedPayload;
};
if (deps.currentSubtitleData?.text === deps.currentSubText) {
return resolve(deps.currentSubtitleData);
}
if (!deps.currentSubText.trim()) {
return resolve({
text: deps.currentSubText,
tokens: null,
});
}
if (deps.tokenizeUncached !== false) {
const tokenized = await deps.tokenizeSubtitle?.(deps.currentSubText);
if (tokenized) {
return resolve(tokenized);
}
}
return resolve({
text: deps.currentSubText,
tokens: null,
});
}
export async function primeVisibleOverlaySubtitleFromMpv(deps: {
getMpvClient: () => CurrentSubtitleMpvClient | null;
setCurrentSubText: (text: string) => void;
getCurrentSubtitleData: () => SubtitleData | null;
consumeCachedSubtitle: (text: string) => SubtitleData | null;
onSubtitleChange: (text: string) => void;
refreshCurrentSubtitle: (text: string) => void;
emitSubtitle: (payload: SubtitleData) => void;
deferUncachedRefresh?: boolean;
setCurrentSecondarySubText?: (text: string) => void;
emitSecondarySubtitle?: (text: string) => void;
logDebug?: (message: string) => void;
}): Promise<void> {
const client = deps.getMpvClient();
if (!client?.connected) {
return;
}
let subTextRaw: unknown;
try {
subTextRaw = await client.requestProperty('sub-text');
} catch (error) {
deps.logDebug?.(
`[visible-overlay-subtitle-prime] failed to read sub-text: ${
error instanceof Error ? error.message : String(error)
}`,
);
return;
}
const text = typeof subTextRaw === 'string' ? subTextRaw : '';
deps.setCurrentSubText(text);
const primeSecondarySubtitle = async (): Promise<void> => {
if (!deps.setCurrentSecondarySubText && !deps.emitSecondarySubtitle) {
return;
}
try {
const secondarySubTextRaw = await client.requestProperty('secondary-sub-text');
const secondaryText = typeof secondarySubTextRaw === 'string' ? secondarySubTextRaw : '';
deps.setCurrentSecondarySubText?.(secondaryText);
deps.emitSecondarySubtitle?.(secondaryText);
} catch (error) {
deps.logDebug?.(
`[visible-overlay-subtitle-prime] failed to read secondary-sub-text: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
};
if (!text.trim()) {
deps.onSubtitleChange(text);
deps.emitSubtitle({ text, tokens: null });
await primeSecondarySubtitle();
return;
}
const currentPayload = deps.getCurrentSubtitleData();
if (currentPayload?.text === text) {
deps.emitSubtitle(currentPayload);
deps.refreshCurrentSubtitle(text);
await primeSecondarySubtitle();
return;
}
const cachedPayload = deps.consumeCachedSubtitle(text);
if (cachedPayload) {
deps.onSubtitleChange(text);
deps.emitSubtitle(cachedPayload);
await primeSecondarySubtitle();
return;
}
if (deps.deferUncachedRefresh === true) {
await primeSecondarySubtitle();
return;
}
deps.refreshCurrentSubtitle(text);
await primeSecondarySubtitle();
}