diff --git a/src/components/video-editor/timeline/Row.tsx b/src/components/video-editor/timeline/Row.tsx index f3d11bcf..17a59e83 100644 --- a/src/components/video-editor/timeline/Row.tsx +++ b/src/components/video-editor/timeline/Row.tsx @@ -8,6 +8,11 @@ interface RowProps extends RowDefinition { background?: React.ReactNode; } +/** + * A single horizontal lane in the timeline. Wraps the dnd-timeline `useRow` + * hook and adds an optional `background` layer (e.g. `BackgroundWaveform`), + * an empty-state hint label, and a minimum height. + */ export default function Row({ id, children, hint, isEmpty, background }: RowProps) { const { setNodeRef, rowWrapperStyle, rowStyle } = useRow({ id }); diff --git a/src/hooks/useAudioPeaks.ts b/src/hooks/useAudioPeaks.ts index 38875370..d640e961 100644 --- a/src/hooks/useAudioPeaks.ts +++ b/src/hooks/useAudioPeaks.ts @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { loadFileAsArrayBuffer } from "@/lib/exporter/streamingDecoder"; let _audioCtx: AudioContext | null = null; +/** Returns the shared AudioContext, creating it lazily on first call. */ function getAudioCtx(): AudioContext { if (!_audioCtx) _audioCtx = new AudioContext(); return _audioCtx; diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index f7687233..fbe1453f 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -180,6 +180,7 @@ export class StreamingVideoDecoder { private cancelled = false; private metadata: DecodedVideoInfo | null = null; + /** Loads the video file and returns it as both a Blob and a File for WebDemuxer. */ private async loadSourceFile(videoUrl: string): Promise<{ file: File; blob: Blob }> { const buffer = await this.withTimeout( loadFileAsArrayBuffer(videoUrl), @@ -616,6 +617,10 @@ export class StreamingVideoDecoder { } } + /** + * Converts trim regions into the segments that should be kept. + * Returns a single full-duration segment when no trim regions are present. + */ private computeSegments( totalDuration: number, trimRegions?: TrimRegion[], @@ -644,6 +649,11 @@ export class StreamingVideoDecoder { return segments; } + /** + * Calculates the effective output duration (in seconds) and total frame count + * for a given combination of trim and speed regions at the target frame rate. + * Requires `loadMetadata()` to have been called first. + */ getExportMetrics( targetFrameRate: number, trimRegions?: TrimRegion[], @@ -664,6 +674,10 @@ export class StreamingVideoDecoder { }; } + /** + * Splits keep-segments by overlapping speed regions, annotating each + * sub-segment with its playback speed multiplier (defaults to 1×). + */ private splitBySpeed( segments: Array<{ startSec: number; endSec: number }>, speedRegions?: SpeedRegion[], @@ -696,14 +710,17 @@ export class StreamingVideoDecoder { return result.filter((s) => s.endSec - s.startSec > 0.0001); } + /** Returns the underlying WebDemuxer instance, or null if not yet loaded. */ getDemuxer(): WebDemuxer | null { return this.demuxer; } + /** Signals the decoder to stop processing at the next cancellation checkpoint. */ cancel(): void { this.cancelled = true; } + /** Cancels decoding and releases the VideoDecoder and WebDemuxer resources. */ destroy(): void { this.cancelled = true; @@ -726,6 +743,7 @@ export class StreamingVideoDecoder { } } + /** Wraps a promise with a hard timeout, rejecting with `message` if it exceeds `timeoutMs`. */ private withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { return new Promise((resolve, reject) => { const timer = window.setTimeout(() => reject(new Error(message)), timeoutMs);