mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-13 20:12:54 -07:00
feat(stats): add v1 immersion stats dashboard (#19)
This commit is contained in:
@@ -1,19 +1,37 @@
|
||||
import type { MergedToken, SubtitleData } from '../../types';
|
||||
|
||||
export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
appState: {
|
||||
initialArgs?: { jellyfinPlay?: unknown } | null;
|
||||
overlayRuntimeInitialized: boolean;
|
||||
mpvClient: { connected?: boolean } | null;
|
||||
mpvClient:
|
||||
| {
|
||||
connected?: boolean;
|
||||
currentSecondarySubText?: string;
|
||||
currentTimePos?: number;
|
||||
requestProperty?: (name: string) => Promise<unknown>;
|
||||
}
|
||||
| null;
|
||||
immersionTracker: {
|
||||
recordSubtitleLine?: (text: string, start: number, end: number) => void;
|
||||
recordSubtitleLine?: (
|
||||
text: string,
|
||||
start: number,
|
||||
end: number,
|
||||
tokens?: MergedToken[] | null,
|
||||
secondaryText?: string | null,
|
||||
) => void;
|
||||
handleMediaTitleUpdate?: (title: string) => void;
|
||||
recordPlaybackPosition?: (time: number) => void;
|
||||
recordMediaDuration?: (durationSec: number) => void;
|
||||
recordPauseState?: (paused: boolean) => void;
|
||||
} | null;
|
||||
subtitleTimingTracker: {
|
||||
recordSubtitle?: (text: string, start: number, end: number) => void;
|
||||
} | null;
|
||||
currentMediaPath?: string | null;
|
||||
currentSubText: string;
|
||||
currentSubAssText: string;
|
||||
currentSubtitleData?: SubtitleData | null;
|
||||
playbackPaused: boolean | null;
|
||||
previousSecondarySubVisibility: boolean | null;
|
||||
};
|
||||
@@ -25,7 +43,11 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
maybeRunAnilistPostWatchUpdate: () => Promise<void>;
|
||||
logSubtitleTimingError: (message: string, error: unknown) => void;
|
||||
broadcastToOverlayWindows: (channel: string, payload: unknown) => void;
|
||||
getImmediateSubtitlePayload?: (text: string) => SubtitleData | null;
|
||||
emitImmediateSubtitle?: (payload: SubtitleData) => void;
|
||||
onSubtitleChange: (text: string) => void;
|
||||
onSubtitleTrackChange?: (sid: number | null) => void;
|
||||
onSubtitleTrackListChange?: (trackList: unknown[] | null) => void;
|
||||
updateCurrentMediaPath: (path: string) => void;
|
||||
restoreMpvSubVisibility: () => void;
|
||||
getCurrentAnilistMediaKey: () => string | null;
|
||||
@@ -38,10 +60,21 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
updateCurrentMediaTitle: (title: string) => void;
|
||||
resetAnilistMediaGuessState: () => void;
|
||||
reportJellyfinRemoteProgress: (forceImmediate: boolean) => void;
|
||||
onTimePosUpdate?: (time: number) => void;
|
||||
updateSubtitleRenderMetrics: (patch: Record<string, unknown>) => void;
|
||||
refreshDiscordPresence: () => void;
|
||||
ensureImmersionTrackerInitialized: () => void;
|
||||
tokenizeSubtitleForImmersion?: (text: string) => Promise<SubtitleData | null>;
|
||||
}) {
|
||||
const writePlaybackPositionFromMpv = (timeSec: unknown): void => {
|
||||
const normalizedTimeSec = Number(timeSec);
|
||||
if (!Number.isFinite(normalizedTimeSec)) {
|
||||
return;
|
||||
}
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordPlaybackPosition?.(normalizedTimeSec);
|
||||
};
|
||||
|
||||
return () => ({
|
||||
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
||||
syncOverlayMpvSubtitleSuppression: () => deps.syncOverlayMpvSubtitleSuppression(),
|
||||
@@ -53,7 +86,31 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
quitApp: () => deps.quitApp(),
|
||||
recordImmersionSubtitleLine: (text: string, start: number, end: number) => {
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordSubtitleLine?.(text, start, end);
|
||||
const tracker = deps.appState.immersionTracker;
|
||||
if (!tracker?.recordSubtitleLine) {
|
||||
return;
|
||||
}
|
||||
const secondaryText = deps.appState.mpvClient?.currentSecondarySubText || null;
|
||||
const cachedTokens =
|
||||
deps.appState.currentSubtitleData?.text === text
|
||||
? deps.appState.currentSubtitleData.tokens
|
||||
: null;
|
||||
if (cachedTokens) {
|
||||
tracker.recordSubtitleLine(text, start, end, cachedTokens, secondaryText);
|
||||
return;
|
||||
}
|
||||
if (!deps.tokenizeSubtitleForImmersion) {
|
||||
tracker.recordSubtitleLine(text, start, end, null, secondaryText);
|
||||
return;
|
||||
}
|
||||
void deps
|
||||
.tokenizeSubtitleForImmersion(text)
|
||||
.then((payload) => {
|
||||
tracker.recordSubtitleLine?.(text, start, end, payload?.tokens ?? null, secondaryText);
|
||||
})
|
||||
.catch(() => {
|
||||
tracker.recordSubtitleLine?.(text, start, end, null, secondaryText);
|
||||
});
|
||||
},
|
||||
hasSubtitleTimingTracker: () => Boolean(deps.appState.subtitleTimingTracker),
|
||||
recordSubtitleTiming: (text: string, start: number, end: number) =>
|
||||
@@ -64,9 +121,21 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
setCurrentSubText: (text: string) => {
|
||||
deps.appState.currentSubText = text;
|
||||
},
|
||||
broadcastSubtitle: (payload: { text: string; tokens: null }) =>
|
||||
getImmediateSubtitlePayload: deps.getImmediateSubtitlePayload
|
||||
? (text: string) => deps.getImmediateSubtitlePayload!(text)
|
||||
: undefined,
|
||||
emitImmediateSubtitle: deps.emitImmediateSubtitle
|
||||
? (payload: SubtitleData) => deps.emitImmediateSubtitle!(payload)
|
||||
: undefined,
|
||||
broadcastSubtitle: (payload: SubtitleData) =>
|
||||
deps.broadcastToOverlayWindows('subtitle:set', payload),
|
||||
onSubtitleChange: (text: string) => deps.onSubtitleChange(text),
|
||||
onSubtitleTrackChange: deps.onSubtitleTrackChange
|
||||
? (sid: number | null) => deps.onSubtitleTrackChange!(sid)
|
||||
: undefined,
|
||||
onSubtitleTrackListChange: deps.onSubtitleTrackListChange
|
||||
? (trackList: unknown[] | null) => deps.onSubtitleTrackListChange!(trackList)
|
||||
: undefined,
|
||||
refreshDiscordPresence: () => deps.refreshDiscordPresence(),
|
||||
setCurrentSubAssText: (text: string) => {
|
||||
deps.appState.currentSubAssText = text;
|
||||
@@ -95,13 +164,39 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordPlaybackPosition?.(time);
|
||||
},
|
||||
recordMediaDuration: (durationSec: number) => {
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordMediaDuration?.(durationSec);
|
||||
},
|
||||
reportJellyfinRemoteProgress: (forceImmediate: boolean) =>
|
||||
deps.reportJellyfinRemoteProgress(forceImmediate),
|
||||
onTimePosUpdate: deps.onTimePosUpdate
|
||||
? (time: number) => deps.onTimePosUpdate!(time)
|
||||
: undefined,
|
||||
recordPauseState: (paused: boolean) => {
|
||||
deps.appState.playbackPaused = paused;
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordPauseState?.(paused);
|
||||
},
|
||||
flushPlaybackPositionOnMediaPathClear: (mediaPath: string) => {
|
||||
const mpvClient = deps.appState.mpvClient;
|
||||
const currentKnownTime = Number(mpvClient?.currentTimePos);
|
||||
writePlaybackPositionFromMpv(currentKnownTime);
|
||||
if (!mpvClient?.requestProperty) {
|
||||
return;
|
||||
}
|
||||
void mpvClient.requestProperty('time-pos').then((timePos) => {
|
||||
const currentPath = (deps.appState.currentMediaPath ?? '').trim();
|
||||
if (currentPath.length > 0 && currentPath !== mediaPath) {
|
||||
return;
|
||||
}
|
||||
const resolvedTime = Number(timePos);
|
||||
if (Number.isFinite(currentKnownTime) && Number.isFinite(resolvedTime) && currentKnownTime === resolvedTime) {
|
||||
return;
|
||||
}
|
||||
writePlaybackPositionFromMpv(resolvedTime);
|
||||
});
|
||||
},
|
||||
updateSubtitleRenderMetrics: (patch: Record<string, unknown>) =>
|
||||
deps.updateSubtitleRenderMetrics(patch),
|
||||
setPreviousSecondarySubVisibility: (visible: boolean) => {
|
||||
|
||||
Reference in New Issue
Block a user