( inputPath: string, outputPath: string, signal?: AbortSignal, config?: Partial<Pick<EngineConfig, "ffmpegProcessTimeout">>, fps?: Fps, )
| 802 | } |
| 803 | |
| 804 | export async function applyFaststart( |
| 805 | inputPath: string, |
| 806 | outputPath: string, |
| 807 | signal?: AbortSignal, |
| 808 | config?: Partial<Pick<EngineConfig, "ffmpegProcessTimeout">>, |
| 809 | fps?: Fps, |
| 810 | ): Promise<MuxResult> { |
| 811 | // faststart is MP4-only (moves moov atom to file start for streaming). |
| 812 | // WebM and MOV don't need it — skip the re-mux. |
| 813 | if (outputPath.endsWith(".webm") || outputPath.endsWith(".mov")) { |
| 814 | if (inputPath !== outputPath) copyFileSync(inputPath, outputPath); |
| 815 | return { success: true, outputPath, durationMs: 0 }; |
| 816 | } |
| 817 | const args = ["-i", inputPath, "-c", "copy", "-movflags", "+faststart"]; |
| 818 | if (fps !== undefined) { |
| 819 | // Set the exact output framerate so the final remux doesn't PTS-average |
| 820 | // a fractional rational like `360000/12001` instead of `30/1` into the |
| 821 | // output container metadata. `-c copy` is retained; no re-encode. |
| 822 | args.push("-r", fpsToFfmpegArg(fps)); |
| 823 | } |
| 824 | args.push("-y", outputPath); |
| 825 | |
| 826 | const processTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout; |
| 827 | const result = await runFfmpeg(args, { signal, timeout: processTimeout }); |
| 828 | |
| 829 | if (signal?.aborted) { |
| 830 | return { |
| 831 | success: false, |
| 832 | outputPath, |
| 833 | durationMs: result.durationMs, |
| 834 | error: "FFmpeg faststart cancelled", |
| 835 | }; |
| 836 | } |
| 837 | return { |
| 838 | success: result.success, |
| 839 | outputPath, |
| 840 | durationMs: result.durationMs, |
| 841 | error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : undefined, |
| 842 | }; |
| 843 | } |
no test coverage detected