mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-28 08:45:41 +00:00
fix(export): only refresh watchdog on progress
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
advanceFinalizationProgress,
|
||||
type FinalizationProgressWatchdog,
|
||||
getExportFinalizationIdleTimeoutMs,
|
||||
getExportFinalizationTimeoutMs,
|
||||
INITIAL_FINALIZATION_PROGRESS_STATE,
|
||||
withFinalizationTimeout,
|
||||
} from "./finalizationTimeout";
|
||||
|
||||
@@ -160,4 +162,31 @@ describe("finalizationTimeout", () => {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("only marks finalization as progressed when normalized progress increases", () => {
|
||||
const initial = advanceFinalizationProgress({
|
||||
renderProgress: 99,
|
||||
audioProgress: 0.5,
|
||||
state: INITIAL_FINALIZATION_PROGRESS_STATE,
|
||||
});
|
||||
expect(initial.progressed).toBe(true);
|
||||
expect(initial.lastRenderProgress).toBe(99);
|
||||
expect(initial.lastAudioProgress).toBe(0.5);
|
||||
|
||||
const repeated = advanceFinalizationProgress({
|
||||
renderProgress: 99,
|
||||
audioProgress: 0.5,
|
||||
state: initial,
|
||||
});
|
||||
expect(repeated.progressed).toBe(false);
|
||||
|
||||
const advanced = advanceFinalizationProgress({
|
||||
renderProgress: 100,
|
||||
audioProgress: 0.5,
|
||||
state: repeated,
|
||||
});
|
||||
expect(advanced.progressed).toBe(true);
|
||||
expect(advanced.lastRenderProgress).toBe(100);
|
||||
expect(advanced.lastAudioProgress).toBe(0.5);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,10 @@ export type FinalizationTimeoutWorkload = "default" | "audio";
|
||||
export type FinalizationProgressWatchdog = {
|
||||
refreshProgress: () => void;
|
||||
};
|
||||
export type FinalizationProgressState = {
|
||||
lastRenderProgress: number;
|
||||
lastAudioProgress: number;
|
||||
};
|
||||
|
||||
const BASE_FINALIZATION_TIMEOUT_MS = 10 * 60_000;
|
||||
const AUDIO_TIMEOUT_HEADROOM_PER_OUTPUT_SECOND_MS = 500;
|
||||
@@ -10,6 +14,11 @@ const MIN_PROGRESS_IDLE_TIMEOUT_MS = 90_000;
|
||||
const MAX_PROGRESS_IDLE_TIMEOUT_MS = 5 * 60_000;
|
||||
const PROGRESS_IDLE_TIMEOUT_FRACTION = 0.25;
|
||||
|
||||
export const INITIAL_FINALIZATION_PROGRESS_STATE: FinalizationProgressState = {
|
||||
lastRenderProgress: -1,
|
||||
lastAudioProgress: -1,
|
||||
};
|
||||
|
||||
export function getExportFinalizationTimeoutMs({
|
||||
effectiveDurationSec,
|
||||
workload = "default",
|
||||
@@ -57,6 +66,35 @@ export function getExportFinalizationIdleTimeoutMs({
|
||||
);
|
||||
}
|
||||
|
||||
export function advanceFinalizationProgress({
|
||||
renderProgress,
|
||||
audioProgress,
|
||||
state,
|
||||
}: {
|
||||
renderProgress: number;
|
||||
audioProgress?: number;
|
||||
state: FinalizationProgressState;
|
||||
}): FinalizationProgressState & { progressed: boolean } {
|
||||
const normalizedRenderProgress = Math.max(0, Math.min(renderProgress, 100));
|
||||
const normalizedAudioProgress =
|
||||
typeof audioProgress === "number" && Number.isFinite(audioProgress)
|
||||
? Math.max(0, Math.min(audioProgress, 1))
|
||||
: null;
|
||||
const nextRenderProgress = Math.max(state.lastRenderProgress, normalizedRenderProgress);
|
||||
const nextAudioProgress =
|
||||
normalizedAudioProgress === null
|
||||
? state.lastAudioProgress
|
||||
: Math.max(state.lastAudioProgress, normalizedAudioProgress);
|
||||
|
||||
return {
|
||||
progressed:
|
||||
nextRenderProgress > state.lastRenderProgress ||
|
||||
nextAudioProgress > state.lastAudioProgress,
|
||||
lastRenderProgress: nextRenderProgress,
|
||||
lastAudioProgress: nextAudioProgress,
|
||||
};
|
||||
}
|
||||
|
||||
export async function withFinalizationTimeout<T>({
|
||||
promise,
|
||||
stage,
|
||||
|
||||
@@ -23,8 +23,10 @@ import {
|
||||
getWebCodecsKeyFrameInterval,
|
||||
} from "./exportTuning";
|
||||
import {
|
||||
advanceFinalizationProgress,
|
||||
type FinalizationProgressWatchdog,
|
||||
type FinalizationTimeoutWorkload,
|
||||
INITIAL_FINALIZATION_PROGRESS_STATE,
|
||||
withFinalizationTimeout,
|
||||
} from "./finalizationTimeout";
|
||||
import { FrameRenderer as ModernFrameRenderer } from "./modernFrameRenderer";
|
||||
@@ -155,6 +157,8 @@ export class ModernVideoExporter {
|
||||
private finalizationTimeMs = 0;
|
||||
private processedFrameCount = 0;
|
||||
private activeFinalizationProgressWatchdog: FinalizationProgressWatchdog | null = null;
|
||||
private lastFinalizationRenderProgress = INITIAL_FINALIZATION_PROGRESS_STATE.lastRenderProgress;
|
||||
private lastFinalizationAudioProgress = INITIAL_FINALIZATION_PROGRESS_STATE.lastAudioProgress;
|
||||
private lastProgressSampleTimeMs = 0;
|
||||
private lastProgressSampleFrame = 0;
|
||||
|
||||
@@ -1156,7 +1160,19 @@ export class ModernVideoExporter {
|
||||
renderProgress: number,
|
||||
audioProgress?: number,
|
||||
) {
|
||||
this.activeFinalizationProgressWatchdog?.refreshProgress();
|
||||
const nextProgress = advanceFinalizationProgress({
|
||||
renderProgress,
|
||||
audioProgress,
|
||||
state: {
|
||||
lastRenderProgress: this.lastFinalizationRenderProgress,
|
||||
lastAudioProgress: this.lastFinalizationAudioProgress,
|
||||
},
|
||||
});
|
||||
if (nextProgress.progressed) {
|
||||
this.activeFinalizationProgressWatchdog?.refreshProgress();
|
||||
}
|
||||
this.lastFinalizationRenderProgress = nextProgress.lastRenderProgress;
|
||||
this.lastFinalizationAudioProgress = nextProgress.lastAudioProgress;
|
||||
this.reportProgress(totalFrames, totalFrames, "finalizing", renderProgress, audioProgress);
|
||||
}
|
||||
|
||||
@@ -1600,6 +1616,9 @@ export class ModernVideoExporter {
|
||||
this.finalizationTimeMs = 0;
|
||||
this.processedFrameCount = 0;
|
||||
this.activeFinalizationProgressWatchdog = null;
|
||||
this.lastFinalizationRenderProgress =
|
||||
INITIAL_FINALIZATION_PROGRESS_STATE.lastRenderProgress;
|
||||
this.lastFinalizationAudioProgress = INITIAL_FINALIZATION_PROGRESS_STATE.lastAudioProgress;
|
||||
this.effectiveDurationSec = 0;
|
||||
this.lastProgressSampleTimeMs = 0;
|
||||
this.lastProgressSampleFrame = 0;
|
||||
|
||||
@@ -14,8 +14,10 @@ import type {
|
||||
} from "@/components/video-editor/types";
|
||||
import { AudioProcessor, isAacAudioEncodingSupported } from "./audioEncoder";
|
||||
import {
|
||||
advanceFinalizationProgress,
|
||||
type FinalizationProgressWatchdog,
|
||||
type FinalizationTimeoutWorkload,
|
||||
INITIAL_FINALIZATION_PROGRESS_STATE,
|
||||
withFinalizationTimeout,
|
||||
} from "./finalizationTimeout";
|
||||
import { FrameRenderer } from "./frameRenderer";
|
||||
@@ -113,6 +115,8 @@ export class VideoExporter {
|
||||
private maxNativeWriteInFlight = 1;
|
||||
private nativeEncoderError: Error | null = null;
|
||||
private activeFinalizationProgressWatchdog: FinalizationProgressWatchdog | null = null;
|
||||
private lastFinalizationRenderProgress = INITIAL_FINALIZATION_PROGRESS_STATE.lastRenderProgress;
|
||||
private lastFinalizationAudioProgress = INITIAL_FINALIZATION_PROGRESS_STATE.lastAudioProgress;
|
||||
|
||||
constructor(config: VideoExporterConfig) {
|
||||
this.config = config;
|
||||
@@ -882,7 +886,19 @@ export class VideoExporter {
|
||||
renderProgress: number,
|
||||
audioProgress?: number,
|
||||
) {
|
||||
this.activeFinalizationProgressWatchdog?.refreshProgress();
|
||||
const nextProgress = advanceFinalizationProgress({
|
||||
renderProgress,
|
||||
audioProgress,
|
||||
state: {
|
||||
lastRenderProgress: this.lastFinalizationRenderProgress,
|
||||
lastAudioProgress: this.lastFinalizationAudioProgress,
|
||||
},
|
||||
});
|
||||
if (nextProgress.progressed) {
|
||||
this.activeFinalizationProgressWatchdog?.refreshProgress();
|
||||
}
|
||||
this.lastFinalizationRenderProgress = nextProgress.lastRenderProgress;
|
||||
this.lastFinalizationAudioProgress = nextProgress.lastAudioProgress;
|
||||
this.reportProgress(totalFrames, totalFrames, "finalizing", renderProgress, audioProgress);
|
||||
}
|
||||
|
||||
@@ -1138,6 +1154,9 @@ export class VideoExporter {
|
||||
this.muxer = null;
|
||||
this.audioProcessor = null;
|
||||
this.activeFinalizationProgressWatchdog = null;
|
||||
this.lastFinalizationRenderProgress =
|
||||
INITIAL_FINALIZATION_PROGRESS_STATE.lastRenderProgress;
|
||||
this.lastFinalizationAudioProgress = INITIAL_FINALIZATION_PROGRESS_STATE.lastAudioProgress;
|
||||
this.encodeQueue = 0;
|
||||
this.pendingMuxing = Promise.resolve();
|
||||
this.nativePendingWrite = Promise.resolve();
|
||||
|
||||
Reference in New Issue
Block a user