mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
import type { AnilistMediaGuess } from '../../core/services/anilist/anilist-updater';
|
|
|
|
export type AnilistMediaGuessRuntimeState = {
|
|
mediaKey: string | null;
|
|
mediaDurationSec: number | null;
|
|
mediaGuess: AnilistMediaGuess | null;
|
|
mediaGuessPromise: Promise<AnilistMediaGuess | null> | null;
|
|
lastDurationProbeAtMs: number;
|
|
};
|
|
|
|
type GuessAnilistMediaInfo = (
|
|
mediaPath: string | null,
|
|
mediaTitle: string | null,
|
|
) => Promise<AnilistMediaGuess | null>;
|
|
|
|
export function createMaybeProbeAnilistDurationHandler(deps: {
|
|
getState: () => AnilistMediaGuessRuntimeState;
|
|
setState: (state: AnilistMediaGuessRuntimeState) => void;
|
|
durationRetryIntervalMs: number;
|
|
now: () => number;
|
|
requestMpvDuration: () => Promise<unknown>;
|
|
logWarn: (message: string, error: unknown) => void;
|
|
}) {
|
|
return async (mediaKey: string): Promise<number | null> => {
|
|
const state = deps.getState();
|
|
if (state.mediaKey !== mediaKey) {
|
|
return null;
|
|
}
|
|
if (typeof state.mediaDurationSec === 'number' && state.mediaDurationSec > 0) {
|
|
return state.mediaDurationSec;
|
|
}
|
|
const now = deps.now();
|
|
if (now - state.lastDurationProbeAtMs < deps.durationRetryIntervalMs) {
|
|
return null;
|
|
}
|
|
|
|
deps.setState({
|
|
...state,
|
|
lastDurationProbeAtMs: now,
|
|
});
|
|
|
|
try {
|
|
const durationCandidate = await deps.requestMpvDuration();
|
|
const duration =
|
|
typeof durationCandidate === 'number' && Number.isFinite(durationCandidate)
|
|
? durationCandidate
|
|
: null;
|
|
const latestState = deps.getState();
|
|
if (duration && duration > 0 && latestState.mediaKey === mediaKey) {
|
|
deps.setState({
|
|
...latestState,
|
|
mediaDurationSec: duration,
|
|
});
|
|
return duration;
|
|
}
|
|
} catch (error) {
|
|
deps.logWarn('AniList duration probe failed:', error);
|
|
}
|
|
return null;
|
|
};
|
|
}
|
|
|
|
export function createEnsureAnilistMediaGuessHandler(deps: {
|
|
getState: () => AnilistMediaGuessRuntimeState;
|
|
setState: (state: AnilistMediaGuessRuntimeState) => void;
|
|
resolveMediaPathForJimaku: (currentMediaPath: string | null) => string | null;
|
|
getCurrentMediaPath: () => string | null;
|
|
getCurrentMediaTitle: () => string | null;
|
|
guessAnilistMediaInfo: GuessAnilistMediaInfo;
|
|
}) {
|
|
return async (mediaKey: string): Promise<AnilistMediaGuess | null> => {
|
|
const state = deps.getState();
|
|
if (state.mediaKey !== mediaKey) {
|
|
return null;
|
|
}
|
|
if (state.mediaGuess) {
|
|
return state.mediaGuess;
|
|
}
|
|
if (state.mediaGuessPromise) {
|
|
return state.mediaGuessPromise;
|
|
}
|
|
|
|
const mediaPathForGuess = deps.resolveMediaPathForJimaku(deps.getCurrentMediaPath());
|
|
const promise = deps
|
|
.guessAnilistMediaInfo(mediaPathForGuess, deps.getCurrentMediaTitle())
|
|
.then((guess) => {
|
|
const latestState = deps.getState();
|
|
if (latestState.mediaKey === mediaKey) {
|
|
deps.setState({
|
|
...latestState,
|
|
mediaGuess: guess,
|
|
});
|
|
}
|
|
return guess;
|
|
})
|
|
.finally(() => {
|
|
const latestState = deps.getState();
|
|
if (latestState.mediaKey === mediaKey) {
|
|
deps.setState({
|
|
...latestState,
|
|
mediaGuessPromise: null,
|
|
});
|
|
}
|
|
});
|
|
|
|
deps.setState({
|
|
...state,
|
|
mediaGuessPromise: promise,
|
|
});
|
|
return promise;
|
|
};
|
|
}
|