mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-09 15:13:32 -07:00
9d77907877
- 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
129 lines
3.7 KiB
TypeScript
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();
|
|
}
|