@@ -2,7 +2,7 @@ import { WebDemuxer } from "web-demuxer";
22import type { SpeedRegion , TrimRegion } from "@/components/video-editor/types" ;
33
44const SOURCE_LOAD_TIMEOUT_MS = 60_000 ;
5-
5+ const EPSILON_SEC = 0.001 ;
66/**
77 * Build a full WebCodecs-compatible AV1 codec string from the AV1CodecConfigurationRecord.
88 * web-demuxer may return a bare "av01" when the WASM-side parser fails to read
@@ -246,10 +246,11 @@ export class StreamingVideoDecoder {
246246 speedRegions ,
247247 ) ;
248248 const segmentOutputFrameCounts = segments . map ( ( segment ) =>
249- Math . ceil ( ( ( segment . endSec - segment . startSec ) / segment . speed ) * targetFrameRate ) ,
249+ Math . ceil (
250+ ( ( segment . endSec - segment . startSec - EPSILON_SEC ) / segment . speed ) * targetFrameRate ,
251+ ) ,
250252 ) ;
251253 const frameDurationUs = 1_000_000 / targetFrameRate ;
252- const epsilonSec = 0.001 ;
253254
254255 // Async frame queue — decoder pushes, consumer pulls
255256 const pendingFrames : VideoFrame [ ] = [ ] ;
@@ -360,7 +361,7 @@ export class StreamingVideoDecoder {
360361
361362 const sourceTimeSec =
362363 segment . startSec + ( segmentFrameIndex / targetFrameRate ) * segment . speed ;
363- if ( sourceTimeSec >= segment . endSec - epsilonSec ) return false ;
364+ if ( sourceTimeSec >= segment . endSec - EPSILON_SEC ) return false ;
364365
365366 const clone = new VideoFrame ( heldFrame , { timestamp : heldFrame . timestamp } ) ;
366367 await onFrame ( clone , exportFrameIndex * frameDurationUs , sourceTimeSec * 1000 ) ;
@@ -379,7 +380,7 @@ export class StreamingVideoDecoder {
379380 // Finalize completed segments before handling this frame.
380381 while (
381382 segmentIdx < segments . length &&
382- frameTimeSec >= segments [ segmentIdx ] . endSec - epsilonSec
383+ frameTimeSec >= segments [ segmentIdx ] . endSec - EPSILON_SEC
383384 ) {
384385 const segment = segments [ segmentIdx ] ;
385386 while ( ! this . cancelled && ( await emitHeldFrameForTarget ( segment ) ) ) {
@@ -391,7 +392,7 @@ export class StreamingVideoDecoder {
391392 if (
392393 heldFrame &&
393394 segmentIdx < segments . length &&
394- heldFrameSec < segments [ segmentIdx ] . startSec - epsilonSec
395+ heldFrameSec < segments [ segmentIdx ] . startSec - EPSILON_SEC
395396 ) {
396397 heldFrame . close ( ) ;
397398 heldFrame = null ;
@@ -406,7 +407,7 @@ export class StreamingVideoDecoder {
406407 const currentSegment = segments [ segmentIdx ] ;
407408
408409 // Before current segment (trimmed region or pre-roll).
409- if ( frameTimeSec < currentSegment . startSec - epsilonSec ) {
410+ if ( frameTimeSec < currentSegment . startSec - EPSILON_SEC ) {
410411 frame . close ( ) ;
411412 continue ;
412413 }
@@ -427,7 +428,7 @@ export class StreamingVideoDecoder {
427428
428429 const sourceTimeSec =
429430 currentSegment . startSec + ( segmentFrameIndex / targetFrameRate ) * currentSegment . speed ;
430- if ( sourceTimeSec >= currentSegment . endSec - epsilonSec ) {
431+ if ( sourceTimeSec >= currentSegment . endSec - EPSILON_SEC ) {
431432 break ;
432433 }
433434 if ( sourceTimeSec > handoffBoundarySec ) {
@@ -449,7 +450,7 @@ export class StreamingVideoDecoder {
449450 if ( heldFrame && segmentIdx < segments . length ) {
450451 while ( ! this . cancelled && segmentIdx < segments . length ) {
451452 const segment = segments [ segmentIdx ] ;
452- if ( heldFrameSec < segment . startSec - epsilonSec ) {
453+ if ( heldFrameSec < segment . startSec - EPSILON_SEC ) {
453454 break ;
454455 }
455456
@@ -461,7 +462,7 @@ export class StreamingVideoDecoder {
461462 segmentFrameIndex = 0 ;
462463 if (
463464 segmentIdx < segments . length &&
464- heldFrameSec < segments [ segmentIdx ] . startSec - epsilonSec
465+ heldFrameSec < segments [ segmentIdx ] . startSec - EPSILON_SEC
465466 ) {
466467 break ;
467468 }
@@ -536,11 +537,24 @@ export class StreamingVideoDecoder {
536537 return segments ;
537538 }
538539
539- getEffectiveDuration ( trimRegions ?: TrimRegion [ ] , speedRegions ?: SpeedRegion [ ] ) : number {
540+ getExportMetrics (
541+ targetFrameRate : number ,
542+ trimRegions ?: TrimRegion [ ] ,
543+ speedRegions ?: SpeedRegion [ ] ,
544+ ) : { effectiveDuration : number ; totalFrames : number } {
540545 if ( ! this . metadata ) throw new Error ( "Must call loadMetadata() first" ) ;
541546 const trimSegments = this . computeSegments ( this . metadata . duration , trimRegions ) ;
542- const speedSegments = this . splitBySpeed ( trimSegments , speedRegions ) ;
543- return speedSegments . reduce ( ( sum , seg ) => sum + ( seg . endSec - seg . startSec ) / seg . speed , 0 ) ;
547+ const segments = this . splitBySpeed ( trimSegments , speedRegions ) ;
548+ return {
549+ effectiveDuration : segments . reduce (
550+ ( sum , seg ) => sum + ( seg . endSec - seg . startSec ) / seg . speed ,
551+ 0 ,
552+ ) ,
553+ totalFrames : segments . reduce ( ( sum , seg ) => {
554+ const segDur = seg . endSec - seg . startSec - EPSILON_SEC ;
555+ return sum + Math . max ( 0 , Math . ceil ( ( segDur / seg . speed ) * targetFrameRate ) ) ;
556+ } , 0 ) ,
557+ } ;
544558 }
545559
546560 private splitBySpeed (
0 commit comments