@@ -95,13 +95,20 @@ global.Math.trunc =
9595} ) ( global ) ;
9696
9797( function ( global ) {
98+ const frameIdCount = 1024 ;
99+
98100 let shownTimeAlert = false ;
99- let repaintTimes = [ 0 , 0 , 0 ] ;
100101 let animationFrameId = null ;
101- let callbacksCount = 0 ;
102- let callbacks = [ ] ;
102+ let currentFrameId = 0 ;
103+
104+ const repaintTimes = [ 0 , 0 , 0 ] ;
105+
106+ const callbackRanges = [ ] ;
107+ const callbacks = [ ] ;
108+
109+ const videoList = [ ] ;
110+ const lastVideoMetrics = { } ;
103111
104- let lastVideoMetrics = { } ;
105112
106113 function getMetricForVideo ( video ) {
107114 let metric = video . mozPresentedFrames ?? null ;
@@ -112,10 +119,16 @@ global.Math.trunc =
112119 return metric ;
113120 } else {
114121 let quality = video . getVideoPlaybackQuality ( ) ;
115- return quality . totalVideoFrames - quality . droppedVideoFrames ;
122+ if ( quality ) return quality . totalVideoFrames - quality . droppedVideoFrames ;
123+ else return video . currentTime ;
116124 }
117125 }
118126
127+ /**
128+ * Checks if the video has updated.
129+ * @modifies callbackRanges
130+ * @param {number } currentTime high res time.
131+ */
119132 function checkForNewVideoFrame ( currentTime ) {
120133 if ( currentTime >= Number . MAX_SAFE_INTEGER && ! shownTimeAlert ) {
121134 shownTimeAlert = true ;
@@ -132,46 +145,77 @@ global.Math.trunc =
132145 }
133146 }
134147
135- let avgRepaintTime =
148+ const avgRepaintTime =
136149 ( repaintTimes [ 0 ] + repaintTimes [ 1 ] + repaintTimes [ 2 ] ) / 3 ;
137- let videosChanged = { } ;
138- for ( let i = 0 ; i < callbacks . length ; i ++ ) {
139- if ( callbacks [ i ] === null ) continue ;
140- let isNewFrame = true ;
141- let video = callbacks [ i ] . video ;
142- let callback = callbacks [ i ] . callback ;
143- let metric = getMetricForVideo ( video ) ;
144- if ( ! videosChanged [ video ] ) {
145- let currentMetric = metric ?? NaN ;
146- let lastMetric = lastVideoMetrics [ video ] ?? NaN ;
147- lastVideoMetrics [ video ] = currentMetric ;
148- isNewFrame = currentMetric !== lastMetric ;
150+ const videosChanged = { } ;
151+
152+ let rangeIndex = - 1 ;
153+ let newFrameId = false ;
154+
155+ while ( ++ rangeIndex < callbackRanges . length ) {
156+ if ( callbackRanges [ rangeIndex ] . frameId . value !== currentFrameId ) continue ;
157+ const currentCallbackRange = callbackRanges [ rangeIndex ] ;
158+ let shouldRemoveCurrentRange = false ;
159+ currentCallbackRange . frameId . noEdit = true ;
160+ const currentCallbacksEnd = currentCallbackRange . callbacksStart + currentCallbackRange . callbacksCount ;
161+ for ( let i = currentCallbackRange . callbacksStart ; i < currentCallbacksEnd ; i ++ ) {
162+ if ( callbacks [ i ] === null ) {
163+ if ( currentCallbackRange . callbacksStart === i ) {
164+ currentCallbackRange . callbacksStart ++ ;
165+ }
166+ continue ;
167+ }
168+ const videoid = callbacks [ i ] . videoid ;
169+ const video = videoList [ callbacks [ i ] . videoid ] ;
170+ const callback = callbacks [ i ] . callback ;
171+ const metric = getMetricForVideo ( video ) ;
172+ let isNewFrame = videosChanged [ videoid ] ?? true ;
173+ if ( ! videosChanged [ videoid ] ) {
174+ const currentMetric = metric ?? NaN ;
175+ const lastMetric = lastVideoMetrics [ videoid ] ?? NaN ;
176+ lastVideoMetrics [ videoid ] = currentMetric ;
177+ isNewFrame = currentMetric !== lastMetric ;
178+ }
179+ videosChanged [ videoid ] = isNewFrame ;
180+ if ( isNewFrame ) {
181+ newFrameId = true ;
182+ if ( -- currentCallbackRange . callbacksCount === 0 ) {
183+ shouldRemoveCurrentRange = true ;
184+ } else if ( currentCallbackRange . callbacksStart === i )
185+ currentCallbackRange . callbacksStart ++ ;
186+ else currentCallbackRange . callbacksCount ++ ;
187+ callbacks [ i ] = null ;
188+ let presentTime = currentTime + avgRepaintTime ;
189+ callback . call ( video , currentTime , {
190+ "presentationTime" : currentTime ,
191+ "expectedDisplayTime" : presentTime ,
192+ "width" : video . videoWidth ?? video . width ,
193+ "height" : video . videoHeight ?? video . height ,
194+ "mediaTime" :
195+ video . currentTime +
196+ ( performance . now ( ) - presentTime ) / 1000 ,
197+ "presentedFrames" : metric ?? - 1 ,
198+ "processingDuration" : video . mozFrameDelay ?? 0
199+ } ) ;
200+ }
149201 }
150- videosChanged [ video ] = isNewFrame ;
151- if ( isNewFrame ) {
152- if ( -- callbacksCount === 0 ) {
153- callbacks = [ ] ;
154- } else callbacks [ i ] = null ;
155- let presentTime = currentTime + avgRepaintTime ;
156- callback . call ( video , currentTime , {
157- "presentationTime" : currentTime ,
158- "expectedDisplayTime" : presentTime ,
159- "width" : video . videoWidth ?? video . width ,
160- "height" : video . videoHeight ?? video . height ,
161- "mediaTime" :
162- video . currentTime +
163- ( performance . now ( ) - presentTime ) / 1000 ,
164- "presentedFrames" : metric ?? - 1 ,
165- "processingDuration" : video . mozFrameDelay ?? 0
166- } ) ;
202+ if ( shouldRemoveCurrentRange ) {
203+ callbackRanges . shift ( ) ;
204+ rangeIndex -- ;
167205 }
168206 }
169207
170- if ( callbacksCount !== 0 )
208+ if ( newFrameId && ++ currentFrameId >= frameIdCount ) {
209+ currentFrameId = 0 ;
210+ }
211+
212+ if ( callbackRanges . length > 0 ) {
171213 animationFrameId = global . requestAnimationFrame (
172214 checkForNewVideoFrame
173215 ) ;
174- else animationFrameId = null ;
216+ } else {
217+ animationFrameId = null ;
218+ }
175219 repaintTimes [ 0 ] = repaintTimes [ 1 ] ;
176220 repaintTimes [ 1 ] = repaintTimes [ 2 ] ;
177221 repaintTimes [ 2 ] = performance . now ( ) - currentTime ;
@@ -190,8 +234,86 @@ global.Math.trunc =
190234 global . HTMLVideoElement . prototype [ "oRequestVideoFrameCallback" ] ??
191235 function ( cb ) {
192236 let vid = this ;
193- callbacksCount ++ ;
194- let id = callbacks . push ( { video : vid , callback : cb } ) - 1 ;
237+ const lastCallbackRangeIndex = callbackRanges . length - 1 ;
238+ const earliestAvailableUnusedIndex = ( function ( ) {
239+ let earliest = 0 ;
240+ const processingNextFrame = callbackRanges . length > 0 ;
241+ let i = 0 ;
242+ if ( processingNextFrame ) {
243+ do {
244+ const rangeEnd = callbackRanges [ i ] . callbacksStart + callbackRanges [ i ] . callbacksCount ;
245+ if ( callbackRanges [ i ] . callbacksStart <= earliest && rangeEnd - 1 >= earliest ) {
246+ earliest = rangeEnd ;
247+ }
248+ } while ( callbackRanges [ i ] . frameId . noEdit && ++ i <= lastCallbackRangeIndex ) ;
249+ }
250+ if ( i <= lastCallbackRangeIndex ) {
251+ return callbacks . indexOf ( null , earliest ) ;
252+ } else return callbacks . length ;
253+ } ) ( ) ;
254+ const earliestAvailableNullCallbackIndexFromEnd = ( function ( ) {
255+ let earliestIndex = callbacks . length ;
256+ let i ;
257+ for ( i = lastCallbackRangeIndex ; i >= 0 ; i -- ) {
258+ const currentRange = callbackRanges [ i ] ;
259+ if ( currentRange . frameId . noEdit || currentRange . frameId . value !== callbackRanges [ 0 ] . frameId . value ) break ;
260+ if ( currentRange . callbacksStart < earliestIndex ) {
261+ earliestIndex = currentRange . callbacksStart ;
262+ break ;
263+ }
264+ }
265+ if ( i >= 0 ) {
266+ return callbacks . indexOf ( null , earliestIndex ) ;
267+ } else return callbacks . length ;
268+ } ) ( ) ;
269+ let id = ( ( earliestAvailableUnusedIndex >= 0 && earliestAvailableUnusedIndex < earliestAvailableNullCallbackIndexFromEnd ) ? earliestAvailableUnusedIndex : earliestAvailableNullCallbackIndexFromEnd ) ;
270+ let videoId = videoList . indexOf ( vid ) ;
271+ if ( videoId === - 1 ) {
272+ videoId = videoList . push ( vid ) - 1 ;
273+ }
274+ callbacks [ id ] = { video : vid , callback : cb , videoid : videoId } ;
275+ let callbackRange = lastCallbackRangeIndex >= 0 ? callbackRanges [ lastCallbackRangeIndex ] : null ;
276+ const localLastFrameId = callbackRange ? callbackRange . frameId : { value : - 1 , noEdit : true } ;
277+ {
278+ let newRange = ! callbackRange ;
279+ if ( callbackRange && ! callbackRange . frameId . noEdit ) {
280+ if ( id < callbackRange . callbacksStart ) {
281+ let clearBetween = true ;
282+ for ( let i = callbackRange . callbacksStart - 1 ; i > id && i > 0 ; i -- ) {
283+ if ( callbacks [ i ] !== null ) {
284+ clearBetween = false ;
285+ break ;
286+ }
287+ }
288+ if ( clearBetween ) {
289+ callbackRange . callbacksCount += callbackRange . callbacksStart - id ;
290+ callbackRange . callbacksStart = id ;
291+
292+ } else newRange = true ;
293+ } else if ( id >= callbackRange . callbacksStart + callbackRange . callbacksCount ) {
294+ let clearBetween = true ;
295+ for ( let i = callbackRange . callbacksStart + callbackRange . callbacksCount ; i < id && i < callbacks . length ; i ++ ) {
296+ if ( callbacks [ i ] !== null ) {
297+ clearBetween = false ;
298+ break ;
299+ }
300+ }
301+ if ( clearBetween ) {
302+ callbackRange . callbacksCount = id - callbackRange . callbacksStart + 1 ;
303+ } else newRange = true ;
304+ }
305+ // we don't do anything if it's already in the range, as it's already scheduled.
306+ } else newRange = true ;
307+ if ( newRange ) {
308+ const potentialNewFrameId = ( ! localLastFrameId . noEdit ? localLastFrameId : { value : ( localLastFrameId . value < frameIdCount - 1 ? localLastFrameId . value + 1 : 0 ) , noEdit : false } ) ;
309+ callbackRange = {
310+ frameId : potentialNewFrameId ,
311+ callbacksStart : id ,
312+ callbacksCount : 1 ,
313+ }
314+ callbackRanges . push ( callbackRange ) ;
315+ }
316+ }
195317 if ( animationFrameId === null ) {
196318 animationFrameId = global . requestAnimationFrame (
197319 checkForNewVideoFrame
@@ -215,19 +337,59 @@ global.Math.trunc =
215337 global . HTMLVideoElement . prototype [ "oCancelVideoFrameCallback" ] ??
216338 function ( id ) {
217339 if ( callbacks [ id ] && callbacks [ id ] . video === this ) {
218- if ( -- callbacksCount === 0 ) {
219- callbacks = [ ] ;
220- if ( animationFrameId !== null ) {
221- global . cancelAnimationFrame ( animationFrameId ) ;
222- animationFrameId = null ;
340+ const parentCallbackRangeIndex = ( function ( ) {
341+ for ( let i = 0 ; i < callbackRanges . length ; i ++ ) {
342+ const callbacksStart = callbackRanges [ i ] . callbacksStart ;
343+ const callbacksEnd = callbacksStart + callbackRanges [ i ] . callbacksCount ;
344+ if ( callbacksStart <= id && id < callbacksEnd ) {
345+ return i ;
346+ }
223347 }
224- } else callbacks [ id ] = null ;
348+ return NaN ;
349+ } ) ( ) ;
350+ const parentCallbackRange = callbackRanges [ parentCallbackRangeIndex ] ;
351+ // We can edit in this case even if the noEdit flag is set, because we're removing a callback.
352+ if ( parentCallbackRange . callbacksStart === id ) {
353+ parentCallbackRange . callbacksStart ++ ;
354+ parentCallbackRange . callbacksCount -- ;
355+ } else if ( parentCallbackRange . callbacksStart + parentCallbackRange . callbacksCount === id - 1 ) {
356+ parentCallbackRange . callbacksCount -- ;
357+ }
358+ // We can remove the range if it's empty and not a noEdit flagged frame.
359+ if ( parentCallbackRange . callbacksCount <= 0 && ! parentCallbackRange . frameId . noEdit ) {
360+ callbackRanges . splice ( parentCallbackRangeIndex , 1 ) ;
361+ }
362+ callbacks [ id ] = null ;
225363 }
226364 }
227365 ) ;
228366 } ) ( global ) ;
229367} ) ( global ) ;
230368
369+ /**
370+ * Sets groups of values in an Arrayish object with a stride and offset.
371+ * @param {!Int8Array|!Uint8Array|!Uint8ClampedArray|!Int16Array|!Uint16Array|!Uint32Array|!BigInt64Array|!BigUint64Array|!Float32Array|!Float64Array|!Array<?>|!string } dest The target sequence.
372+ * @param {!Int8Array|!Uint8Array|!Uint8ClampedArray|!Int16Array|!Uint16Array|!Uint32Array|!BigInt64Array|!BigUint64Array|!Float32Array|!Float64Array|!Array<?>|!string } src The source sequence.
373+ * @param {number } stride The number of elements to skip between each group.
374+ * @param {number } gsize The number of elements in each group.
375+ * @param {number } offset The starting index in the target sequence.
376+ */
377+ sabre [ "setArrayishWithStride" ] = function setArrayishWithStride (
378+ dest ,
379+ src ,
380+ stride ,
381+ gsize ,
382+ offset
383+ ) {
384+ for ( let i = 0 ; i < ( src . length / gsize ) | 0 ; i ++ ) {
385+ for ( let j = 0 ; j < gsize ; j ++ ) {
386+ const groupIndex = i * gsize ;
387+ const strideIndex = i * stride ;
388+ dest [ offset + strideIndex + j ] = src [ groupIndex + j ] ;
389+ }
390+ }
391+ } ;
392+
231393/**
232394 * Performs a transition between two numbers given current time, start, end, and acceleration.
233395 * @param {number } curtime current time relative to event start.
0 commit comments