@@ -1015,6 +1015,11 @@ type TimelineWindowVideo = Pick<VideoElement, "start" | "end" | "mediaStart"> &
10151015 Partial < Pick < VideoElement , "playbackRate" > > &
10161016 Partial < Pick < VideoElement , "loop" > > ;
10171017
1018+ function canHoldFinalFramePastEof ( video : TimelineWindowVideo ) : boolean {
1019+ const timelineDuration = video . end - video . start ;
1020+ return ! video . loop && Number . isFinite ( timelineDuration ) && timelineDuration > 0 ;
1021+ }
1022+
10181023// Logical duration assigned to a one-frame held-tail representation. This is
10191024// deliberately below any supported output frame interval: coverage expects
10201025// one frame, while FFmpeg seeks to the separately probed real frame timestamp.
@@ -1118,6 +1123,18 @@ export function resolveTimelineExtractionWindow(
11181123 } ,
11191124 visibleDuration ,
11201125 ) ;
1126+ } else if ( canHoldFinalFramePastEof ( video ) ) {
1127+ const logicalDuration = Math . min ( sourceDuration , FINAL_FRAME_LOGICAL_DURATION_SECONDS ) ;
1128+ return withTimelineDuration (
1129+ {
1130+ compositionStart : video . start ,
1131+ mediaStart : sourceDuration - logicalDuration ,
1132+ durationSeconds : logicalDuration ,
1133+ preserveTimelineEnd : true ,
1134+ ensureFinalFrame : true ,
1135+ } ,
1136+ visibleDuration ,
1137+ ) ;
11211138 }
11221139 }
11231140 return withTimelineDuration (
@@ -1155,7 +1172,10 @@ export async function resolveFinalFrameExtractionWindow(
11551172 if ( window . mediaStart < finalFrameTimestamp - 1e-9 ) return window ;
11561173
11571174 const sourceRemaining = playableDuration - video . mediaStart ;
1158- const logicalDuration = Math . min ( sourceRemaining , FINAL_FRAME_LOGICAL_DURATION_SECONDS ) ;
1175+ const logicalDuration = Math . min (
1176+ Math . max ( sourceRemaining , window . durationSeconds ) ,
1177+ FINAL_FRAME_LOGICAL_DURATION_SECONDS ,
1178+ ) ;
11591179 return {
11601180 compositionStart : Math . max ( 0 , video . start ) ,
11611181 mediaStart : playableDuration - logicalDuration ,
@@ -1184,7 +1204,9 @@ export function resolveVideoExtractionWindow(
11841204 `Playable video stream duration is ${ playableDuration } s` ,
11851205 ) ;
11861206 }
1187- if ( video . mediaStart >= playableDuration ) {
1207+ const requestedTimelineDuration = video . end - video . start ;
1208+ const heldPastEof = video . mediaStart >= playableDuration && canHoldFinalFramePastEof ( video ) ;
1209+ if ( video . mediaStart >= playableDuration && ! heldPastEof ) {
11881210 throw new VideoSourceExtractionError (
11891211 "media_start_out_of_range" ,
11901212 false ,
@@ -1193,13 +1215,17 @@ export function resolveVideoExtractionWindow(
11931215 ) ;
11941216 }
11951217 const playbackRate = normalizePlaybackRate ( video . playbackRate ?? 1 ) ;
1196- const requestedTimelineDuration = video . end - video . start ;
11971218 const resolvedDuration =
11981219 Number . isFinite ( requestedTimelineDuration ) && requestedTimelineDuration > 0
11991220 ? requestedTimelineDuration
12001221 : resolveSegmentDuration ( requestedTimelineDuration , video . mediaStart , playableDuration ) /
12011222 playbackRate ;
1202- return resolveTimelineExtractionWindow ( video , resolvedDuration , timelineEnd , playableDuration ) ;
1223+ return resolveTimelineExtractionWindow (
1224+ video ,
1225+ resolvedDuration ,
1226+ timelineEnd ?? ( heldPastEof ? video . end : undefined ) ,
1227+ playableDuration ,
1228+ ) ;
12031229}
12041230
12051231export function resolveVideoExtractionDuration (
@@ -1723,11 +1749,10 @@ export async function extractAllVideoFrames(
17231749 const metadata = videoMetadata [ i ] ;
17241750 if ( ! entry || ! metadata ) continue ;
17251751
1726- // Guard against mediaStart past EOF — FFmpeg's `-ss` silently produces
1727- // a 0-byte file when seeking beyond the source duration, and the
1728- // downstream extractor then points at a broken input.
1752+ // Guard past-EOF windows that cannot use the non-looping held-tail plan.
1753+ // FFmpeg's `-ss` otherwise silently produces a 0-byte intermediate.
17291754 const playableDuration = resolvePlayableVideoDuration ( metadata ) ;
1730- if ( entry . video . mediaStart >= playableDuration ) {
1755+ if ( entry . video . mediaStart >= playableDuration && ! canHoldFinalFramePastEof ( entry . video ) ) {
17311756 errors . push ( {
17321757 videoId : entry . video . id ,
17331758 kind : "media_start_out_of_range" ,
0 commit comments