import type { TranscodeTaskFn } from './video-transcode-queue' export const VIDEO_TRANSCODE_RECOVERY_INTERVAL_MS = 60_000 interface RecoverySchedulerOptions { scan?: () => Promise intervalMs?: number onError?: (error: unknown) => void } export interface VideoTranscodeRecoveryScheduler { initial: Promise runNow: () => Promise stop: () => void } /** * 建立不会重叠的恢复调度器。一次扫描失败只记录错误,下一次周期仍会重新尝试。 */ export function createVideoTranscodeRecoveryScheduler( options: RecoverySchedulerOptions = {}, ): VideoTranscodeRecoveryScheduler { const scan = options.scan || (async () => { const [{ transcodeQueue, VIDEO_MATERIAL_TYPES }, { executeTranscodeTask }] = await Promise.all([ import('./video-transcode-queue'), import('./video-transcode-task'), ]) return transcodeQueue.runRecoveryScan( executeTranscodeTask as TranscodeTaskFn, { requeuePending: true, videoTypes: VIDEO_MATERIAL_TYPES }, ) }) const onError = options.onError || ((error) => { console.error('[VideoTranscodeRecovery] Recovery scan failed:', error) }) let inFlight: Promise | null = null const runNow = (): Promise => { if (inFlight) { return inFlight } inFlight = scan() .catch((error) => { onError(error) throw error }) .finally(() => { inFlight = null }) return inFlight } const initial = runNow() const timer = setInterval(() => { void runNow().catch(() => { // onError 已记录;保留定时器,让下一轮继续重试。 }) }, options.intervalMs || VIDEO_TRANSCODE_RECOVERY_INTERVAL_MS) ;(timer as NodeJS.Timeout).unref?.() return { initial, runNow, stop: () => clearInterval(timer), } }