mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-04 12:41:30 -07:00
feat(core): add Electron runtime, services, and app composition
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import type { SubtitleData } from '../../types';
|
||||
|
||||
export interface SubtitleProcessingControllerDeps {
|
||||
tokenizeSubtitle: (text: string) => Promise<SubtitleData | null>;
|
||||
emitSubtitle: (payload: SubtitleData) => void;
|
||||
logDebug?: (message: string) => void;
|
||||
now?: () => number;
|
||||
}
|
||||
|
||||
export interface SubtitleProcessingController {
|
||||
onSubtitleChange: (text: string) => void;
|
||||
refreshCurrentSubtitle: (textOverride?: string) => void;
|
||||
}
|
||||
|
||||
export function createSubtitleProcessingController(
|
||||
deps: SubtitleProcessingControllerDeps,
|
||||
): SubtitleProcessingController {
|
||||
let latestText = '';
|
||||
let lastEmittedText = '';
|
||||
let processing = false;
|
||||
let staleDropCount = 0;
|
||||
let refreshRequested = false;
|
||||
const now = deps.now ?? (() => Date.now());
|
||||
|
||||
const processLatest = (): void => {
|
||||
if (processing) {
|
||||
return;
|
||||
}
|
||||
|
||||
processing = true;
|
||||
|
||||
void (async () => {
|
||||
while (true) {
|
||||
const text = latestText;
|
||||
const forceRefresh = refreshRequested;
|
||||
refreshRequested = false;
|
||||
const startedAtMs = now();
|
||||
|
||||
if (!text.trim()) {
|
||||
deps.emitSubtitle({ text, tokens: null });
|
||||
lastEmittedText = text;
|
||||
break;
|
||||
}
|
||||
|
||||
let output: SubtitleData = { text, tokens: null };
|
||||
try {
|
||||
const tokenized = await deps.tokenizeSubtitle(text);
|
||||
if (tokenized) {
|
||||
output = tokenized;
|
||||
}
|
||||
} catch (error) {
|
||||
deps.logDebug?.(`Subtitle tokenization failed: ${(error as Error).message}`);
|
||||
}
|
||||
|
||||
if (latestText !== text) {
|
||||
staleDropCount += 1;
|
||||
deps.logDebug?.(
|
||||
`Dropped stale subtitle tokenization result; dropped=${staleDropCount}, elapsed=${now() - startedAtMs}ms`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
deps.emitSubtitle(output);
|
||||
lastEmittedText = text;
|
||||
deps.logDebug?.(
|
||||
`Subtitle tokenization delivered; elapsed=${now() - startedAtMs}ms, staleDrops=${staleDropCount}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
})()
|
||||
.catch((error) => {
|
||||
deps.logDebug?.(`Subtitle processing loop failed: ${(error as Error).message}`);
|
||||
})
|
||||
.finally(() => {
|
||||
processing = false;
|
||||
if (refreshRequested || latestText !== lastEmittedText) {
|
||||
processLatest();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onSubtitleChange: (text: string) => {
|
||||
if (text === latestText) {
|
||||
return;
|
||||
}
|
||||
latestText = text;
|
||||
processLatest();
|
||||
},
|
||||
refreshCurrentSubtitle: (textOverride?: string) => {
|
||||
if (typeof textOverride === 'string') {
|
||||
latestText = textOverride;
|
||||
}
|
||||
if (!latestText.trim()) {
|
||||
return;
|
||||
}
|
||||
refreshRequested = true;
|
||||
processLatest();
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user