UNPKG

13.1 kBJavaScriptView Raw
1/**
2 * ranges
3 *
4 * Utilities for working with TimeRanges.
5 *
6 */
7
8'use strict';
9
10Object.defineProperty(exports, '__esModule', {
11 value: true
12});
13
14var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
15
16function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
17
18var _videoJs = require('video.js');
19
20var _videoJs2 = _interopRequireDefault(_videoJs);
21
22// Fudge factor to account for TimeRanges rounding
23var TIME_FUDGE_FACTOR = 1 / 30;
24// Comparisons between time values such as current time and the end of the buffered range
25// can be misleading because of precision differences or when the current media has poorly
26// aligned audio and video, which can cause values to be slightly off from what you would
27// expect. This value is what we consider to be safe to use in such comparisons to account
28// for these scenarios.
29var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
30
31/**
32 * Clamps a value to within a range
33 * @param {Number} num - the value to clamp
34 * @param {Number} start - the start of the range to clamp within, inclusive
35 * @param {Number} end - the end of the range to clamp within, inclusive
36 * @return {Number}
37 */
38var clamp = function clamp(num, _ref) {
39 var _ref2 = _slicedToArray(_ref, 2);
40
41 var start = _ref2[0];
42 var end = _ref2[1];
43
44 return Math.min(Math.max(start, num), end);
45};
46var filterRanges = function filterRanges(timeRanges, predicate) {
47 var results = [];
48 var i = undefined;
49
50 if (timeRanges && timeRanges.length) {
51 // Search for ranges that match the predicate
52 for (i = 0; i < timeRanges.length; i++) {
53 if (predicate(timeRanges.start(i), timeRanges.end(i))) {
54 results.push([timeRanges.start(i), timeRanges.end(i)]);
55 }
56 }
57 }
58
59 return _videoJs2['default'].createTimeRanges(results);
60};
61
62/**
63 * Attempts to find the buffered TimeRange that contains the specified
64 * time.
65 * @param {TimeRanges} buffered - the TimeRanges object to query
66 * @param {number} time - the time to filter on.
67 * @returns {TimeRanges} a new TimeRanges object
68 */
69var findRange = function findRange(buffered, time) {
70 return filterRanges(buffered, function (start, end) {
71 return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
72 });
73};
74
75/**
76 * Returns the TimeRanges that begin later than the specified time.
77 * @param {TimeRanges} timeRanges - the TimeRanges object to query
78 * @param {number} time - the time to filter on.
79 * @returns {TimeRanges} a new TimeRanges object.
80 */
81var findNextRange = function findNextRange(timeRanges, time) {
82 return filterRanges(timeRanges, function (start) {
83 return start - TIME_FUDGE_FACTOR >= time;
84 });
85};
86
87/**
88 * Returns gaps within a list of TimeRanges
89 * @param {TimeRanges} buffered - the TimeRanges object
90 * @return {TimeRanges} a TimeRanges object of gaps
91 */
92var findGaps = function findGaps(buffered) {
93 if (buffered.length < 2) {
94 return _videoJs2['default'].createTimeRanges();
95 }
96
97 var ranges = [];
98
99 for (var i = 1; i < buffered.length; i++) {
100 var start = buffered.end(i - 1);
101 var end = buffered.start(i);
102
103 ranges.push([start, end]);
104 }
105
106 return _videoJs2['default'].createTimeRanges(ranges);
107};
108
109/**
110 * Search for a likely end time for the segment that was just appened
111 * based on the state of the `buffered` property before and after the
112 * append. If we fin only one such uncommon end-point return it.
113 * @param {TimeRanges} original - the buffered time ranges before the update
114 * @param {TimeRanges} update - the buffered time ranges after the update
115 * @returns {Number|null} the end time added between `original` and `update`,
116 * or null if one cannot be unambiguously determined.
117 */
118var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) {
119 var i = undefined;
120 var start = undefined;
121 var end = undefined;
122 var result = [];
123 var edges = [];
124
125 // In order to qualify as a possible candidate, the end point must:
126 // 1) Not have already existed in the `original` ranges
127 // 2) Not result from the shrinking of a range that already existed
128 // in the `original` ranges
129 // 3) Not be contained inside of a range that existed in `original`
130 var overlapsCurrentEnd = function overlapsCurrentEnd(span) {
131 return span[0] <= end && span[1] >= end;
132 };
133
134 if (original) {
135 // Save all the edges in the `original` TimeRanges object
136 for (i = 0; i < original.length; i++) {
137 start = original.start(i);
138 end = original.end(i);
139
140 edges.push([start, end]);
141 }
142 }
143
144 if (update) {
145 // Save any end-points in `update` that are not in the `original`
146 // TimeRanges object
147 for (i = 0; i < update.length; i++) {
148 start = update.start(i);
149 end = update.end(i);
150
151 if (edges.some(overlapsCurrentEnd)) {
152 continue;
153 }
154
155 // at this point it must be a unique non-shrinking end edge
156 result.push(end);
157 }
158 }
159
160 // we err on the side of caution and return null if didn't find
161 // exactly *one* differing end edge in the search above
162 if (result.length !== 1) {
163 return null;
164 }
165
166 return result[0];
167};
168
169/**
170 * Calculate the intersection of two TimeRanges
171 * @param {TimeRanges} bufferA
172 * @param {TimeRanges} bufferB
173 * @returns {TimeRanges} The interesection of `bufferA` with `bufferB`
174 */
175var bufferIntersection = function bufferIntersection(bufferA, bufferB) {
176 var start = null;
177 var end = null;
178 var arity = 0;
179 var extents = [];
180 var ranges = [];
181
182 if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
183 return _videoJs2['default'].createTimeRange();
184 }
185
186 // Handle the case where we have both buffers and create an
187 // intersection of the two
188 var count = bufferA.length;
189
190 // A) Gather up all start and end times
191 while (count--) {
192 extents.push({ time: bufferA.start(count), type: 'start' });
193 extents.push({ time: bufferA.end(count), type: 'end' });
194 }
195 count = bufferB.length;
196 while (count--) {
197 extents.push({ time: bufferB.start(count), type: 'start' });
198 extents.push({ time: bufferB.end(count), type: 'end' });
199 }
200 // B) Sort them by time
201 extents.sort(function (a, b) {
202 return a.time - b.time;
203 });
204
205 // C) Go along one by one incrementing arity for start and decrementing
206 // arity for ends
207 for (count = 0; count < extents.length; count++) {
208 if (extents[count].type === 'start') {
209 arity++;
210
211 // D) If arity is ever incremented to 2 we are entering an
212 // overlapping range
213 if (arity === 2) {
214 start = extents[count].time;
215 }
216 } else if (extents[count].type === 'end') {
217 arity--;
218
219 // E) If arity is ever decremented to 1 we leaving an
220 // overlapping range
221 if (arity === 1) {
222 end = extents[count].time;
223 }
224 }
225
226 // F) Record overlapping ranges
227 if (start !== null && end !== null) {
228 ranges.push([start, end]);
229 start = null;
230 end = null;
231 }
232 }
233
234 return _videoJs2['default'].createTimeRanges(ranges);
235};
236
237/**
238 * Calculates the percentage of `segmentRange` that overlaps the
239 * `buffered` time ranges.
240 * @param {TimeRanges} segmentRange - the time range that the segment
241 * covers adjusted according to currentTime
242 * @param {TimeRanges} referenceRange - the original time range that the
243 * segment covers
244 * @param {Number} currentTime - time in seconds where the current playback
245 * is at
246 * @param {TimeRanges} buffered - the currently buffered time ranges
247 * @returns {Number} percent of the segment currently buffered
248 */
249var calculateBufferedPercent = function calculateBufferedPercent(adjustedRange, referenceRange, currentTime, buffered) {
250 var referenceDuration = referenceRange.end(0) - referenceRange.start(0);
251 var adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0);
252 var bufferMissingFromAdjusted = referenceDuration - adjustedDuration;
253 var adjustedIntersection = bufferIntersection(adjustedRange, buffered);
254 var referenceIntersection = bufferIntersection(referenceRange, buffered);
255 var adjustedOverlap = 0;
256 var referenceOverlap = 0;
257
258 var count = adjustedIntersection.length;
259
260 while (count--) {
261 adjustedOverlap += adjustedIntersection.end(count) - adjustedIntersection.start(count);
262
263 // If the current overlap segment starts at currentTime, then increase the
264 // overlap duration so that it actually starts at the beginning of referenceRange
265 // by including the difference between the two Range's durations
266 // This is a work around for the way Flash has no buffer before currentTime
267 if (adjustedIntersection.start(count) === currentTime) {
268 adjustedOverlap += bufferMissingFromAdjusted;
269 }
270 }
271
272 count = referenceIntersection.length;
273
274 while (count--) {
275 referenceOverlap += referenceIntersection.end(count) - referenceIntersection.start(count);
276 }
277
278 // Use whichever value is larger for the percentage-buffered since that value
279 // is likely more accurate because the only way
280 return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100;
281};
282
283/**
284 * Return the amount of a range specified by the startOfSegment and segmentDuration
285 * overlaps the current buffered content.
286 *
287 * @param {Number} startOfSegment - the time where the segment begins
288 * @param {Number} segmentDuration - the duration of the segment in seconds
289 * @param {Number} currentTime - time in seconds where the current playback
290 * is at
291 * @param {TimeRanges} buffered - the state of the buffer
292 * @returns {Number} percentage of the segment's time range that is
293 * already in `buffered`
294 */
295var getSegmentBufferedPercent = function getSegmentBufferedPercent(startOfSegment, segmentDuration, currentTime, buffered) {
296 var endOfSegment = startOfSegment + segmentDuration;
297
298 // The entire time range of the segment
299 var originalSegmentRange = _videoJs2['default'].createTimeRanges([[startOfSegment, endOfSegment]]);
300
301 // The adjusted segment time range that is setup such that it starts
302 // no earlier than currentTime
303 // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts
304 // for that and the function will still return 100% if a only half of a
305 // segment is actually in the buffer as long as the currentTime is also
306 // half-way through the segment
307 var adjustedSegmentRange = _videoJs2['default'].createTimeRanges([[clamp(startOfSegment, [currentTime, endOfSegment]), endOfSegment]]);
308
309 // This condition happens when the currentTime is beyond the segment's
310 // end time
311 if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) {
312 return 0;
313 }
314
315 var percent = calculateBufferedPercent(adjustedSegmentRange, originalSegmentRange, currentTime, buffered);
316
317 // If the segment is reported as having a zero duration, return 0%
318 // since it is likely that we will need to fetch the segment
319 if (isNaN(percent) || percent === Infinity || percent === -Infinity) {
320 return 0;
321 }
322
323 return percent;
324};
325
326/**
327 * Gets a human readable string for a TimeRange
328 *
329 * @param {TimeRange} range
330 * @returns {String} a human readable string
331 */
332var printableRange = function printableRange(range) {
333 var strArr = [];
334
335 if (!range || !range.length) {
336 return '';
337 }
338
339 for (var i = 0; i < range.length; i++) {
340 strArr.push(range.start(i) + ' => ' + range.end(i));
341 }
342
343 return strArr.join(', ');
344};
345
346/**
347 * Calculates the amount of time left in seconds until the player hits the end of the
348 * buffer and causes a rebuffer
349 *
350 * @param {TimeRange} buffered
351 * The state of the buffer
352 * @param {Numnber} currentTime
353 * The current time of the player
354 * @param {Number} playbackRate
355 * The current playback rate of the player. Defaults to 1.
356 * @return {Number}
357 * Time until the player has to start rebuffering in seconds.
358 * @function timeUntilRebuffer
359 */
360var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
361 var playbackRate = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2];
362
363 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
364
365 return (bufferedEnd - currentTime) / playbackRate;
366};
367
368exports['default'] = {
369 findRange: findRange,
370 findNextRange: findNextRange,
371 findGaps: findGaps,
372 findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd,
373 getSegmentBufferedPercent: getSegmentBufferedPercent,
374 TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR,
375 SAFE_TIME_DELTA: SAFE_TIME_DELTA,
376 printableRange: printableRange,
377 timeUntilRebuffer: timeUntilRebuffer
378};
379module.exports = exports['default'];
\No newline at end of file