From c0b929d4b2e7b4dba5be11c5ac8d2dbcd0012abc Mon Sep 17 00:00:00 2001 From: Davide Mendolia Date: Sun, 24 May 2026 11:53:44 +0200 Subject: [PATCH] refactor: simplify function signatures and reduce line breaks for readability --- src/lib/exporter/streamingDecoder.ts | 169 +++++++-------------------- 1 file changed, 39 insertions(+), 130 deletions(-) diff --git a/src/lib/exporter/streamingDecoder.ts b/src/lib/exporter/streamingDecoder.ts index 1b686e50..0e429cb6 100644 --- a/src/lib/exporter/streamingDecoder.ts +++ b/src/lib/exporter/streamingDecoder.ts @@ -19,11 +19,7 @@ function buildAV1CodecString(description?: BufferSource): string { const bytes = description instanceof ArrayBuffer ? new Uint8Array(description) - : new Uint8Array( - description.buffer, - description.byteOffset, - description.byteLength, - ); + : new Uint8Array(description.buffer, description.byteOffset, description.byteLength); // AV1CodecConfigurationRecord layout (4+ bytes): // Byte 0: marker (1) | version (7) @@ -91,24 +87,16 @@ const SCAN_UNBOUNDED_FALLBACK_SEC = 24 * 60 * 60; * @param containerDuration Duration from the container-level metadata * @param scannedDuration Duration derived from actual packet timestamps (ground truth) */ -export function validateDuration( - containerDuration: number, - scannedDuration: number, -): number { +export function validateDuration(containerDuration: number, scannedDuration: number): number { if (scannedDuration <= 0) { // Zero scanned duration means corrupted/empty file — fall back to container // (downstream shouldFailDecodeEndedEarly will catch truly empty files) - return Number.isFinite(containerDuration) - ? Math.max(containerDuration, 0) - : 0; + return Number.isFinite(containerDuration) ? Math.max(containerDuration, 0) : 0; } if (!Number.isFinite(containerDuration) || containerDuration <= 0) { return scannedDuration; } - if ( - Math.abs(containerDuration - scannedDuration) > - DURATION_DIVERGENCE_THRESHOLD_SEC - ) { + if (Math.abs(containerDuration - scannedDuration) > DURATION_DIVERGENCE_THRESHOLD_SEC) { return scannedDuration; } return containerDuration; @@ -133,27 +121,16 @@ export function shouldFailDecodeEndedEarly({ return false; } - if ( - typeof streamDurationSec !== "number" || - !Number.isFinite(streamDurationSec) - ) { + if (typeof streamDurationSec !== "number" || !Number.isFinite(streamDurationSec)) { return true; } const metadataTailSec = requiredEndSec - streamDurationSec; const decodedNearStreamEnd = - Math.abs(lastDecodedFrameSec - streamDurationSec) <= - STREAM_DURATION_MATCH_TOLERANCE_SEC; + Math.abs(lastDecodedFrameSec - streamDurationSec) <= STREAM_DURATION_MATCH_TOLERANCE_SEC; - const maxTailSec = Math.max( - METADATA_TAIL_TOLERANCE_SEC, - requiredEndSec * 0.01, - ); - if ( - decodedNearStreamEnd && - metadataTailSec > 0 && - metadataTailSec <= maxTailSec - ) { + const maxTailSec = Math.max(METADATA_TAIL_TOLERANCE_SEC, requiredEndSec * 0.01); + if (decodedNearStreamEnd && metadataTailSec > 0 && metadataTailSec <= maxTailSec) { return false; } @@ -202,9 +179,7 @@ export class StreamingVideoDecoder { private metadata: DecodedVideoInfo | null = null; /** Routes to the appropriate loader based on whether the source is local or remote. */ - private async loadSourceFile( - videoUrl: string, - ): Promise<{ file: File; blob: Blob }> { + private async loadSourceFile(videoUrl: string): Promise<{ file: File; blob: Blob }> { const isRemoteUrl = /^(https?:|blob:|data:)/i.test(videoUrl); if (!isRemoteUrl && window.electronAPI) { return this.withTimeout( @@ -221,14 +196,10 @@ export class StreamingVideoDecoder { } /** Loads a local video file via the Electron IPC bridge. */ - static async loadLocalSourceFile( - videoUrl: string, - ): Promise<{ file: File; blob: Blob }> { + static async loadLocalSourceFile(videoUrl: string): Promise<{ file: File; blob: Blob }> { const result = await window.electronAPI.readBinaryFile(videoUrl); if (!result.success || !result.data) { - throw new Error( - result.message || result.error || "Failed to read source video", - ); + throw new Error(result.message || result.error || "Failed to read source video"); } const filename = (result.path || videoUrl).split(/[\\/]/).pop() || "video"; @@ -242,14 +213,10 @@ export class StreamingVideoDecoder { } /** Loads a remote or blob video URL via fetch. */ - static async loadRemoteSourceFile( - videoUrl: string, - ): Promise<{ file: File; blob: Blob }> { + static async loadRemoteSourceFile(videoUrl: string): Promise<{ file: File; blob: Blob }> { const response = await fetch(videoUrl); if (!response.ok) { - throw new Error( - `Failed to fetch source video: ${response.status} ${response.statusText}`, - ); + throw new Error(`Failed to fetch source video: ${response.status} ${response.statusText}`); } const blob = await response.blob(); const filename = videoUrl.split("/").pop() || "video"; @@ -263,8 +230,7 @@ export class StreamingVideoDecoder { const { file } = await this.loadSourceFile(videoUrl); // Relative URL so it resolves correctly in both dev (http) and packaged (file://) builds - const wasmUrl = new URL("./wasm/web-demuxer.wasm", window.location.href) - .href; + const wasmUrl = new URL("./wasm/web-demuxer.wasm", window.location.href).href; this.demuxer = new WebDemuxer({ wasmFilePath: wasmUrl }); await this.withTimeout( this.demuxer.load(file), @@ -277,9 +243,7 @@ export class StreamingVideoDecoder { SOURCE_LOAD_TIMEOUT_MS, "Timed out while reading video metadata.", ); - const videoStream = mediaInfo.streams.find( - (s) => s.codec_type_string === "video", - ); + const videoStream = mediaInfo.streams.find((s) => s.codec_type_string === "video"); let frameRate = 60; if (videoStream?.avg_frame_rate) { @@ -291,9 +255,7 @@ export class StreamingVideoDecoder { } } - const audioStream = mediaInfo.streams.find( - (s) => s.codec_type_string === "audio", - ); + const audioStream = mediaInfo.streams.find((s) => s.codec_type_string === "audio"); // Scan video packets to find the true content boundary. // MediaRecorder (especially on Linux) writes unreliable container durations. @@ -301,23 +263,14 @@ export class StreamingVideoDecoder { // Pass explicit range because some containers are truncated without one. // Sanitize because mediaInfo.duration can be NaN/Infinity (Chromium Linux bug), // which would propagate into demuxer.read() as an invalid endpoint. - const containerDurationSec = Number.isFinite(mediaInfo.duration) - ? mediaInfo.duration - : 0; + const containerDurationSec = Number.isFinite(mediaInfo.duration) ? mediaInfo.duration : 0; const streamDurationSec = - typeof videoStream?.duration === "number" && - Number.isFinite(videoStream.duration) + typeof videoStream?.duration === "number" && Number.isFinite(videoStream.duration) ? videoStream.duration : 0; - const hintedDurationSec = Math.max( - containerDurationSec, - streamDurationSec, - 0, - ); + const hintedDurationSec = Math.max(containerDurationSec, streamDurationSec, 0); const scanEndSec = - hintedDurationSec > 0 - ? hintedDurationSec + 0.5 - : SCAN_UNBOUNDED_FALLBACK_SEC; + hintedDurationSec > 0 ? hintedDurationSec + 0.5 : SCAN_UNBOUNDED_FALLBACK_SEC; let maxPacketEndUs = 0; const scanReader = this.demuxer.read("video", 0, scanEndSec).getReader(); try { @@ -335,18 +288,14 @@ export class StreamingVideoDecoder { } } const scannedDuration = maxPacketEndUs / 1_000_000; - const validatedDuration = validateDuration( - mediaInfo.duration, - scannedDuration, - ); + const validatedDuration = validateDuration(mediaInfo.duration, scannedDuration); this.metadata = { width: videoStream?.width || 1920, height: videoStream?.height || 1080, duration: validatedDuration, streamDuration: - typeof videoStream?.duration === "number" && - Number.isFinite(videoStream.duration) + typeof videoStream?.duration === "number" && Number.isFinite(videoStream.duration) ? videoStream.duration : undefined, frameRate, @@ -379,14 +328,8 @@ export class StreamingVideoDecoder { const decoderConfig = await this.demuxer.getDecoderConfig("video"); - console.log( - "[StreamingVideoDecoder] decoderConfig.codec:", - decoderConfig.codec, - ); - console.log( - "[StreamingVideoDecoder] decoderConfig.description:", - decoderConfig.description, - ); + console.log("[StreamingVideoDecoder] decoderConfig.codec:", decoderConfig.codec); + console.log("[StreamingVideoDecoder] decoderConfig.description:", decoderConfig.description); // web-demuxer may return bare four-character code strings ("av01", "vp08", // "vp09", "avc1") that WebCodecs rejects. Normalize them to the short or @@ -425,8 +368,7 @@ export class StreamingVideoDecoder { const segmentOutputFrameCounts = segments.map((segment) => Math.ceil( - ((segment.endSec - segment.startSec - EPSILON_SEC) / segment.speed) * - targetFrameRate, + ((segment.endSec - segment.startSec - EPSILON_SEC) / segment.speed) * targetFrameRate, ), ); const frameDurationUs = 1_000_000 / targetFrameRate; @@ -468,9 +410,7 @@ export class StreamingVideoDecoder { : decoderConfig; try { - const support = await VideoDecoder.isConfigSupported( - preferredDecoderConfig, - ); + const support = await VideoDecoder.isConfigSupported(preferredDecoderConfig); console.log( `[StreamingVideoDecoder] isConfigSupported for "${preferredDecoderConfig.codec}":`, support.supported, @@ -496,8 +436,7 @@ export class StreamingVideoDecoder { const getNextFrame = (): Promise => { if (decodeError) throw decodeError; - if (pendingFrames.length > 0) - return Promise.resolve(pendingFrames.shift()!); + if (pendingFrames.length > 0) return Promise.resolve(pendingFrames.shift()!); if (decodeDone) return Promise.resolve(null); return new Promise((resolve) => { frameResolve = resolve; @@ -561,18 +500,13 @@ export class StreamingVideoDecoder { if (segmentFrameIndex >= segmentFrameCount) return false; const sourceTimeSec = - segment.startSec + - (segmentFrameIndex / targetFrameRate) * segment.speed; + segment.startSec + (segmentFrameIndex / targetFrameRate) * segment.speed; if (sourceTimeSec >= segment.endSec - EPSILON_SEC) return false; const clone = new VideoFrame(heldFrame, { timestamp: heldFrame.timestamp, }); - await onFrame( - clone, - exportFrameIndex * frameDurationUs, - sourceTimeSec * 1000, - ); + await onFrame(clone, exportFrameIndex * frameDurationUs, sourceTimeSec * 1000); segmentFrameIndex++; exportFrameIndex++; return true; @@ -635,8 +569,7 @@ export class StreamingVideoDecoder { } const sourceTimeSec = - currentSegment.startSec + - (segmentFrameIndex / targetFrameRate) * currentSegment.speed; + currentSegment.startSec + (segmentFrameIndex / targetFrameRate) * currentSegment.speed; if (sourceTimeSec >= currentSegment.endSec - EPSILON_SEC) { break; } @@ -647,11 +580,7 @@ export class StreamingVideoDecoder { const clone = new VideoFrame(heldFrame, { timestamp: heldFrame.timestamp, }); - await onFrame( - clone, - exportFrameIndex * frameDurationUs, - sourceTimeSec * 1000, - ); + await onFrame(clone, exportFrameIndex * frameDurationUs, sourceTimeSec * 1000); segmentFrameIndex++; exportFrameIndex++; } @@ -716,9 +645,7 @@ export class StreamingVideoDecoder { }) ) { const decodedAtLabel = - lastDecodedFrameSec === null - ? "no decoded frame" - : `${lastDecodedFrameSec.toFixed(3)}s`; + lastDecodedFrameSec === null ? "no decoded frame" : `${lastDecodedFrameSec.toFixed(3)}s`; const message = `Decode ended early at ${decodedAtLabel} (needed ${requiredEndSec.toFixed(3)}s) – export may be slightly shorter than expected.`; console.warn(`[StreamingVideoDecoder] ${message}`); onWarning?.(message); @@ -768,10 +695,7 @@ export class StreamingVideoDecoder { speedRegions?: SpeedRegion[], ): { effectiveDuration: number; totalFrames: number } { if (!this.metadata) throw new Error("Must call loadMetadata() first"); - const trimSegments = this.computeSegments( - this.metadata.duration, - trimRegions, - ); + const trimSegments = this.computeSegments(this.metadata.duration, trimRegions); const segments = this.splitBySpeed(trimSegments, speedRegions); return { effectiveDuration: segments.reduce( @@ -780,9 +704,7 @@ export class StreamingVideoDecoder { ), totalFrames: segments.reduce((sum, seg) => { const segDur = seg.endSec - seg.startSec - EPSILON_SEC; - return ( - sum + Math.max(0, Math.ceil((segDur / seg.speed) * targetFrameRate)) - ); + return sum + Math.max(0, Math.ceil((segDur / seg.speed) * targetFrameRate)); }, 0), }; } @@ -798,15 +720,10 @@ export class StreamingVideoDecoder { if (!speedRegions || speedRegions.length === 0) return segments.map((s) => ({ ...s, speed: 1 })); - const result: Array<{ startSec: number; endSec: number; speed: number }> = - []; + const result: Array<{ startSec: number; endSec: number; speed: number }> = []; for (const segment of segments) { const overlapping = speedRegions - .filter( - (sr) => - sr.startMs / 1000 < segment.endSec && - sr.endMs / 1000 > segment.startSec, - ) + .filter((sr) => sr.startMs / 1000 < segment.endSec && sr.endMs / 1000 > segment.startSec) .sort((a, b) => a.startMs - b.startMs); if (overlapping.length === 0) { @@ -818,8 +735,7 @@ export class StreamingVideoDecoder { for (const sr of overlapping) { const srStart = Math.max(sr.startMs / 1000, segment.startSec); const srEnd = Math.min(sr.endMs / 1000, segment.endSec); - if (cursor < srStart) - result.push({ startSec: cursor, endSec: srStart, speed: 1 }); + if (cursor < srStart) result.push({ startSec: cursor, endSec: srStart, speed: 1 }); result.push({ startSec: srStart, endSec: srEnd, speed: sr.speed }); cursor = srEnd; } @@ -863,16 +779,9 @@ 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 { + private withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { return new Promise((resolve, reject) => { - const timer = window.setTimeout( - () => reject(new Error(message)), - timeoutMs, - ); + const timer = window.setTimeout(() => reject(new Error(message)), timeoutMs); promise.then( (value) => { window.clearTimeout(timer);