UNPKG

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