Skip to content

Commit 64b775c

Browse files
committed
Fixed requestVideoFrame Prollyfill
1 parent d1c1c88 commit 64b775c

File tree

2 files changed

+217
-45
lines changed

2 files changed

+217
-45
lines changed

include/util.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ sabre.Complaint.prototype.reset = function () {};
1717
*/
1818
sabre.Complaint.resetAll = function () {};
1919

20+
/**
21+
* Sets groups of values in an Arrayish object with a stride and offset.
22+
* @param {!Int8Array|!Uint8Array|!Uint8ClampedArray|!Int16Array|!Uint16Array|!Uint32Array|!BigInt64Array|!BigUint64Array|!Float32Array|!Float64Array|!Array<?>|!string} dest The target sequence.
23+
* @param {!Int8Array|!Uint8Array|!Uint8ClampedArray|!Int16Array|!Uint16Array|!Uint32Array|!BigInt64Array|!BigUint64Array|!Float32Array|!Float64Array|!Array<?>|!string} src The source sequence.
24+
* @param {number} stride The number of elements to skip between each group.
25+
* @param {number} gsize The number of elements in each group.
26+
* @param {number} offset The starting index in the target sequence.
27+
*/
28+
sabre.setArrayishWithStride = function (dest, src, stride, gsize, offset) {};
29+
2030
/**
2131
* Performs a transition between two numbers given current time, start, end, and acceleration.
2232
* @param {number} curtime current time relative to event start.

src/util.js

Lines changed: 207 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)