11import { postRuntimeMessage } from "./bridge" ;
22import { swallow } from "./diagnostics" ;
3- import { evictMediaSyncState , HOLD_CAP_MS , isActiveAtPlayhead } from "./media" ;
3+ import {
4+ evictMediaSyncState ,
5+ HOLD_CAP_MS ,
6+ isActiveAtPlayhead ,
7+ sourceTimeAtPlayhead ,
8+ } from "./media" ;
49import { findInjectedRenderFrame } from "./renderFrameSibling" ;
510import type { RuntimeJson } from "./types" ;
611import { isVideoElement } from "./domRealm" ;
@@ -17,6 +22,7 @@ import {
1722 PREVIEW_PROXY_BOX_PARAM ,
1823 PREVIEW_PROXY_LIGHT_PARAM ,
1924 PREVIEW_PROXY_RETRY_PARAM ,
25+ PREVIEW_PROXY_STILL_PARAM ,
2026 quantizePreviewProxyBox ,
2127 scalePreviewProxyBox ,
2228 type PreviewProxyBox ,
@@ -193,14 +199,14 @@ function requestBox(el: HTMLMediaElement, copy: BoxedCopy, box: PreviewProxyBox
193199 pendingUpgrades . set ( el , href ) ;
194200 const wanted = ( ) =>
195201 pendingUpgrades . get ( el ) === href && el . isConnected && el . getAttribute ( "src" ) === copy . href ;
196- noteCopy ( el , "making" ) ;
202+ noteCopy ( el , "making" , null , copy . light ) ;
197203 void waitForServedProxy (
198204 href ,
199205 wanted ,
200- ( fraction ) => wanted ( ) && noteCopy ( el , "making" , fraction ) ,
206+ ( fraction ) => wanted ( ) && noteCopy ( el , "making" , fraction , copy . light ) ,
201207 ( ) => isActiveAtPlayhead ( el ) ,
202208 ) . then ( ( served ) => {
203- if ( wanted ( ) ) noteCopy ( el , served ? "ready" : "failed" ) ;
209+ if ( wanted ( ) ) noteCopy ( el , served ? "ready" : "failed" , null , copy . light ) ;
204210 if ( served && wanted ( ) ) loadProxy ( el , href , copy . originalSrc , copy . variant , box , copy . light ) ;
205211 if ( pendingUpgrades . get ( el ) === href ) pendingUpgrades . delete ( el ) ;
206212 } ) ;
@@ -390,6 +396,27 @@ function lookupCodecMapEntry<T>(pathname: string, map: Record<string, T>): T | n
390396 ) ;
391397}
392398
399+ const stills = new WeakSet < HTMLMediaElement > ( ) ;
400+
401+ /** While a file the browser cannot play is being copied, its own frame at the playhead stands in as the poster. */
402+ function showStill (
403+ el : HTMLMediaElement ,
404+ originalSrc : string ,
405+ variant : string ,
406+ box : PreviewProxyBox | null ,
407+ ) : void {
408+ const at = sourceTimeAtPlayhead ( el ) ;
409+ if ( at === undefined || stills . has ( el ) || el . hasAttribute ( "poster" ) ) return ;
410+ const url = new URL ( appendProxyParam ( originalSrc , variant , box ) , document . baseURI ) ;
411+ url . searchParams . set ( PREVIEW_PROXY_STILL_PARAM , at . toFixed ( 1 ) ) ;
412+ stills . add ( el ) ;
413+ el . setAttribute ( "poster" , url . href ) ;
414+ }
415+
416+ function dropStill ( el : HTMLMediaElement ) : void {
417+ if ( stills . delete ( el ) ) el . removeAttribute ( "poster" ) ;
418+ }
419+
393420function appendProxyParam (
394421 src : string ,
395422 variant : string ,
@@ -471,26 +498,30 @@ async function makeCopy(el: HTMLMediaElement, job: CopyJob, scale: number | null
471498 lightFacts !== undefined ,
472499 retryNext . delete ( el ) ,
473500 ) ;
474- noteCopy ( el , "making" ) ;
501+ const light = lightFacts !== undefined ;
502+ noteCopy ( el , "making" , null , light ) ;
475503 const served = await waitForServedProxy (
476504 proxiedSrc ,
477505 live ,
478- ( fraction ) => noteCopy ( el , "making" , fraction ) ,
506+ ( fraction ) => noteCopy ( el , "making" , fraction , light ) ,
479507 ( ) => isActiveAtPlayhead ( el ) ,
508+ false ,
509+ light ? undefined : ( ) => showStill ( el , originalSrc , variant , box ) ,
480510 ) ;
481511 if ( ! live ( ) ) {
512+ dropStill ( el ) ;
482513 if ( proxyRequested . get ( el ) === originalAttr ) proxyRequested . delete ( el ) ;
483514 if ( lightFacts && el . isConnected && el . getAttribute ( "src" ) === originalAttr ) {
484515 applyPreviewQuality ( el ) ;
485516 }
486517 return ;
487518 }
488519 if ( ! served ) {
489- noteCopy ( el , "failed" ) ;
520+ noteCopy ( el , "failed" , null , light ) ;
490521 emitUnavailableDiagnostic ( el , "proxy_playback_failed" , originalSrc ) ;
491522 return ;
492523 }
493- noteCopy ( el , "ready" ) ;
524+ noteCopy ( el , "ready" , null , light ) ;
494525 swappedElements . set ( el , originalAttr ) ;
495526 if ( lightFacts ) lightElements . add ( el ) ;
496527 loadProxy ( el , proxiedSrc , originalSrc , variant , box , lightFacts !== undefined ) ;
@@ -499,7 +530,7 @@ async function makeCopy(el: HTMLMediaElement, job: CopyJob, scale: number | null
499530 return new Promise ( ( landed ) => {
500531 el . addEventListener ( "loadeddata" , landed , { once : true } ) ;
501532 el . addEventListener ( "error" , landed , { once : true } ) ;
502- } ) ;
533+ } ) . then ( ( ) => dropStill ( el ) ) ;
503534}
504535
505536/**
0 commit comments