@@ -345,6 +345,67 @@ function mean(values) {
345345 return values . reduce ( ( a , b ) => a + b , 0 ) / values . length ;
346346}
347347
348+ function getCurrentlyPlayingSegment ( tech ) {
349+ const playlist = tech . vhs . playlists . media ( ) ;
350+ if ( ! playlist || ! playlist . segments || playlist . segments . length === 0 ) {
351+ return null ;
352+ }
353+
354+ const snapshotTime = tech . currentTime ( ) ;
355+
356+ // Find the first segment whose end is past the playhead.
357+ for ( let i = 0 ; i < playlist . segments . length ; i += 1 ) {
358+ // Note: segment.end may be undefined when not yet buffered.
359+ if ( snapshotTime < playlist . segments [ i ] . end ) {
360+ return playlist . segments [ i ] ;
361+ }
362+ }
363+
364+ return playlist . segments [ 0 ] ;
365+ }
366+
367+ // ---- Rebuffer memory persistence ----
368+ // A page reload should not reset learned caution: events are stored in
369+ // sessionStorage and restored (age-pruned) at construction. All storage
370+ // access is failure-tolerant — missing or broken sessionStorage (Node,
371+ // sandboxed iframes, private modes) silently degrades to in-memory only.
372+
373+ function loadPersistedRebufferEvents ( ) {
374+ try {
375+ if ( typeof window === "undefined" || ! window . sessionStorage ) {
376+ return [ ] ;
377+ }
378+ const raw = window . sessionStorage . getItem ( REBUFFER_STORAGE_KEY ) ;
379+ if ( ! raw ) {
380+ return [ ] ;
381+ }
382+ const events = JSON . parse ( raw ) ;
383+ if ( ! Array . isArray ( events ) ) {
384+ return [ ] ;
385+ }
386+ const cutoff = Date . now ( ) - BUFFERING_AMNESTY_DURATION ;
387+ return events
388+ . filter ( ( e ) => e && typeof e . atMs === "number" && e . atMs > cutoff )
389+ . map ( ( e ) => ( {
390+ latencyMs : Number . isFinite ( e . latencyMs ) ? e . latencyMs : null ,
391+ atMs : e . atMs ,
392+ } ) ) ;
393+ } catch {
394+ return [ ] ;
395+ }
396+ }
397+
398+ function persistRebufferEvents ( events ) {
399+ try {
400+ if ( typeof window === "undefined" || ! window . sessionStorage ) {
401+ return ;
402+ }
403+ window . sessionStorage . setItem ( REBUFFER_STORAGE_KEY , JSON . stringify ( events ) ) ;
404+ } catch {
405+ // Storage full or forbidden; in-memory behavior is the fallback.
406+ }
407+ }
408+
348409class LatencyCompensator {
349410 constructor ( player , options = { } ) {
350411 this . player = player ;
@@ -425,22 +486,21 @@ class LatencyCompensator {
425486 // recent sightings is the least-delayed bound and under-reports latency —
426487 // the conservative direction.
427488 observeClockSkew ( playlist ) {
428- for ( let i = playlist . segments . length - 1 ; i >= 0 ; i -- ) {
489+ for ( let i = playlist . segments . length - 1 ; i >= 0 ; i -= 1 ) {
429490 const segment = playlist . segments [ i ] ;
430- if ( ! segment . dateTimeObject ) {
431- continue ;
432- }
433- const key = segment . dateTimeObject . getTime ( ) ;
434- if ( key !== this . lastSkewSightingKey ) {
435- this . lastSkewSightingKey = key ;
436- this . skewSightings . push (
437- key + ( segment . duration || 0 ) * 1000 - Date . now ( )
438- ) ;
439- if ( this . skewSightings . length > SKEW_SIGHTING_WINDOW ) {
440- this . skewSightings . shift ( ) ;
491+ if ( segment . dateTimeObject ) {
492+ const key = segment . dateTimeObject . getTime ( ) ;
493+ if ( key !== this . lastSkewSightingKey ) {
494+ this . lastSkewSightingKey = key ;
495+ this . skewSightings . push (
496+ key + ( segment . duration || 0 ) * 1000 - Date . now ( )
497+ ) ;
498+ if ( this . skewSightings . length > SKEW_SIGHTING_WINDOW ) {
499+ this . skewSightings . shift ( ) ;
500+ }
441501 }
502+ return ;
442503 }
443- return ;
444504 }
445505 }
446506
@@ -1077,7 +1137,7 @@ class LatencyCompensator {
10771137 representations . forEach ( ( rep ) => {
10781138 rep . enabled ( pinned ? rep === lowest : true ) ;
10791139 } ) ;
1080- } catch ( err ) {
1140+ } catch {
10811141 // Renditions API unavailable; quality pinning is best-effort.
10821142 }
10831143 }
@@ -1123,65 +1183,4 @@ class LatencyCompensator {
11231183 }
11241184}
11251185
1126- function getCurrentlyPlayingSegment ( tech ) {
1127- const playlist = tech . vhs . playlists . media ( ) ;
1128- if ( ! playlist || ! playlist . segments || playlist . segments . length === 0 ) {
1129- return null ;
1130- }
1131-
1132- const snapshotTime = tech . currentTime ( ) ;
1133-
1134- // Find the first segment whose end is past the playhead.
1135- for ( const segment of playlist . segments ) {
1136- // Note: segment.end may be undefined when not yet buffered.
1137- if ( snapshotTime < segment . end ) {
1138- return segment ;
1139- }
1140- }
1141-
1142- return playlist . segments [ 0 ] ;
1143- }
1144-
1145- // ---- Rebuffer memory persistence ----
1146- // A page reload should not reset learned caution: events are stored in
1147- // sessionStorage and restored (age-pruned) at construction. All storage
1148- // access is failure-tolerant — missing or broken sessionStorage (Node,
1149- // sandboxed iframes, private modes) silently degrades to in-memory only.
1150-
1151- function loadPersistedRebufferEvents ( ) {
1152- try {
1153- if ( typeof window === "undefined" || ! window . sessionStorage ) {
1154- return [ ] ;
1155- }
1156- const raw = window . sessionStorage . getItem ( REBUFFER_STORAGE_KEY ) ;
1157- if ( ! raw ) {
1158- return [ ] ;
1159- }
1160- const events = JSON . parse ( raw ) ;
1161- if ( ! Array . isArray ( events ) ) {
1162- return [ ] ;
1163- }
1164- const cutoff = Date . now ( ) - BUFFERING_AMNESTY_DURATION ;
1165- return events
1166- . filter ( ( e ) => e && typeof e . atMs === "number" && e . atMs > cutoff )
1167- . map ( ( e ) => ( {
1168- latencyMs : Number . isFinite ( e . latencyMs ) ? e . latencyMs : null ,
1169- atMs : e . atMs ,
1170- } ) ) ;
1171- } catch ( err ) {
1172- return [ ] ;
1173- }
1174- }
1175-
1176- function persistRebufferEvents ( events ) {
1177- try {
1178- if ( typeof window === "undefined" || ! window . sessionStorage ) {
1179- return ;
1180- }
1181- window . sessionStorage . setItem ( REBUFFER_STORAGE_KEY , JSON . stringify ( events ) ) ;
1182- } catch ( err ) {
1183- // Storage full or forbidden; in-memory behavior is the fallback.
1184- }
1185- }
1186-
11871186export default LatencyCompensator ;
0 commit comments