123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- /**
- * ranges
- *
- * Utilities for working with TimeRanges.
- *
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- var _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'); } }; })();
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _videoJs = require('video.js');
- var _videoJs2 = _interopRequireDefault(_videoJs);
- // Fudge factor to account for TimeRanges rounding
- var TIME_FUDGE_FACTOR = 1 / 30;
- // Comparisons between time values such as current time and the end of the buffered range
- // can be misleading because of precision differences or when the current media has poorly
- // aligned audio and video, which can cause values to be slightly off from what you would
- // expect. This value is what we consider to be safe to use in such comparisons to account
- // for these scenarios.
- var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
- /**
- * Clamps a value to within a range
- * @param {Number} num - the value to clamp
- * @param {Number} start - the start of the range to clamp within, inclusive
- * @param {Number} end - the end of the range to clamp within, inclusive
- * @return {Number}
- */
- var clamp = function clamp(num, _ref) {
- var _ref2 = _slicedToArray(_ref, 2);
- var start = _ref2[0];
- var end = _ref2[1];
- return Math.min(Math.max(start, num), end);
- };
- var filterRanges = function filterRanges(timeRanges, predicate) {
- var results = [];
- var i = undefined;
- if (timeRanges && timeRanges.length) {
- // Search for ranges that match the predicate
- for (i = 0; i < timeRanges.length; i++) {
- if (predicate(timeRanges.start(i), timeRanges.end(i))) {
- results.push([timeRanges.start(i), timeRanges.end(i)]);
- }
- }
- }
- return _videoJs2['default'].createTimeRanges(results);
- };
- /**
- * Attempts to find the buffered TimeRange that contains the specified
- * time.
- * @param {TimeRanges} buffered - the TimeRanges object to query
- * @param {number} time - the time to filter on.
- * @returns {TimeRanges} a new TimeRanges object
- */
- var findRange = function findRange(buffered, time) {
- return filterRanges(buffered, function (start, end) {
- return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
- });
- };
- /**
- * Returns the TimeRanges that begin later than the specified time.
- * @param {TimeRanges} timeRanges - the TimeRanges object to query
- * @param {number} time - the time to filter on.
- * @returns {TimeRanges} a new TimeRanges object.
- */
- var findNextRange = function findNextRange(timeRanges, time) {
- return filterRanges(timeRanges, function (start) {
- return start - TIME_FUDGE_FACTOR >= time;
- });
- };
- /**
- * Returns gaps within a list of TimeRanges
- * @param {TimeRanges} buffered - the TimeRanges object
- * @return {TimeRanges} a TimeRanges object of gaps
- */
- var findGaps = function findGaps(buffered) {
- if (buffered.length < 2) {
- return _videoJs2['default'].createTimeRanges();
- }
- var ranges = [];
- for (var i = 1; i < buffered.length; i++) {
- var start = buffered.end(i - 1);
- var end = buffered.start(i);
- ranges.push([start, end]);
- }
- return _videoJs2['default'].createTimeRanges(ranges);
- };
- /**
- * Search for a likely end time for the segment that was just appened
- * based on the state of the `buffered` property before and after the
- * append. If we fin only one such uncommon end-point return it.
- * @param {TimeRanges} original - the buffered time ranges before the update
- * @param {TimeRanges} update - the buffered time ranges after the update
- * @returns {Number|null} the end time added between `original` and `update`,
- * or null if one cannot be unambiguously determined.
- */
- var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) {
- var i = undefined;
- var start = undefined;
- var end = undefined;
- var result = [];
- var edges = [];
- // In order to qualify as a possible candidate, the end point must:
- // 1) Not have already existed in the `original` ranges
- // 2) Not result from the shrinking of a range that already existed
- // in the `original` ranges
- // 3) Not be contained inside of a range that existed in `original`
- var overlapsCurrentEnd = function overlapsCurrentEnd(span) {
- return span[0] <= end && span[1] >= end;
- };
- if (original) {
- // Save all the edges in the `original` TimeRanges object
- for (i = 0; i < original.length; i++) {
- start = original.start(i);
- end = original.end(i);
- edges.push([start, end]);
- }
- }
- if (update) {
- // Save any end-points in `update` that are not in the `original`
- // TimeRanges object
- for (i = 0; i < update.length; i++) {
- start = update.start(i);
- end = update.end(i);
- if (edges.some(overlapsCurrentEnd)) {
- continue;
- }
- // at this point it must be a unique non-shrinking end edge
- result.push(end);
- }
- }
- // we err on the side of caution and return null if didn't find
- // exactly *one* differing end edge in the search above
- if (result.length !== 1) {
- return null;
- }
- return result[0];
- };
- /**
- * Calculate the intersection of two TimeRanges
- * @param {TimeRanges} bufferA
- * @param {TimeRanges} bufferB
- * @returns {TimeRanges} The interesection of `bufferA` with `bufferB`
- */
- var bufferIntersection = function bufferIntersection(bufferA, bufferB) {
- var start = null;
- var end = null;
- var arity = 0;
- var extents = [];
- var ranges = [];
- if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
- return _videoJs2['default'].createTimeRange();
- }
- // Handle the case where we have both buffers and create an
- // intersection of the two
- var count = bufferA.length;
- // A) Gather up all start and end times
- while (count--) {
- extents.push({ time: bufferA.start(count), type: 'start' });
- extents.push({ time: bufferA.end(count), type: 'end' });
- }
- count = bufferB.length;
- while (count--) {
- extents.push({ time: bufferB.start(count), type: 'start' });
- extents.push({ time: bufferB.end(count), type: 'end' });
- }
- // B) Sort them by time
- extents.sort(function (a, b) {
- return a.time - b.time;
- });
- // C) Go along one by one incrementing arity for start and decrementing
- // arity for ends
- for (count = 0; count < extents.length; count++) {
- if (extents[count].type === 'start') {
- arity++;
- // D) If arity is ever incremented to 2 we are entering an
- // overlapping range
- if (arity === 2) {
- start = extents[count].time;
- }
- } else if (extents[count].type === 'end') {
- arity--;
- // E) If arity is ever decremented to 1 we leaving an
- // overlapping range
- if (arity === 1) {
- end = extents[count].time;
- }
- }
- // F) Record overlapping ranges
- if (start !== null && end !== null) {
- ranges.push([start, end]);
- start = null;
- end = null;
- }
- }
- return _videoJs2['default'].createTimeRanges(ranges);
- };
- /**
- * Calculates the percentage of `segmentRange` that overlaps the
- * `buffered` time ranges.
- * @param {TimeRanges} segmentRange - the time range that the segment
- * covers adjusted according to currentTime
- * @param {TimeRanges} referenceRange - the original time range that the
- * segment covers
- * @param {Number} currentTime - time in seconds where the current playback
- * is at
- * @param {TimeRanges} buffered - the currently buffered time ranges
- * @returns {Number} percent of the segment currently buffered
- */
- var calculateBufferedPercent = function calculateBufferedPercent(adjustedRange, referenceRange, currentTime, buffered) {
- var referenceDuration = referenceRange.end(0) - referenceRange.start(0);
- var adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0);
- var bufferMissingFromAdjusted = referenceDuration - adjustedDuration;
- var adjustedIntersection = bufferIntersection(adjustedRange, buffered);
- var referenceIntersection = bufferIntersection(referenceRange, buffered);
- var adjustedOverlap = 0;
- var referenceOverlap = 0;
- var count = adjustedIntersection.length;
- while (count--) {
- adjustedOverlap += adjustedIntersection.end(count) - adjustedIntersection.start(count);
- // If the current overlap segment starts at currentTime, then increase the
- // overlap duration so that it actually starts at the beginning of referenceRange
- // by including the difference between the two Range's durations
- // This is a work around for the way Flash has no buffer before currentTime
- if (adjustedIntersection.start(count) === currentTime) {
- adjustedOverlap += bufferMissingFromAdjusted;
- }
- }
- count = referenceIntersection.length;
- while (count--) {
- referenceOverlap += referenceIntersection.end(count) - referenceIntersection.start(count);
- }
- // Use whichever value is larger for the percentage-buffered since that value
- // is likely more accurate because the only way
- return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100;
- };
- /**
- * Return the amount of a range specified by the startOfSegment and segmentDuration
- * overlaps the current buffered content.
- *
- * @param {Number} startOfSegment - the time where the segment begins
- * @param {Number} segmentDuration - the duration of the segment in seconds
- * @param {Number} currentTime - time in seconds where the current playback
- * is at
- * @param {TimeRanges} buffered - the state of the buffer
- * @returns {Number} percentage of the segment's time range that is
- * already in `buffered`
- */
- var getSegmentBufferedPercent = function getSegmentBufferedPercent(startOfSegment, segmentDuration, currentTime, buffered) {
- var endOfSegment = startOfSegment + segmentDuration;
- // The entire time range of the segment
- var originalSegmentRange = _videoJs2['default'].createTimeRanges([[startOfSegment, endOfSegment]]);
- // The adjusted segment time range that is setup such that it starts
- // no earlier than currentTime
- // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts
- // for that and the function will still return 100% if a only half of a
- // segment is actually in the buffer as long as the currentTime is also
- // half-way through the segment
- var adjustedSegmentRange = _videoJs2['default'].createTimeRanges([[clamp(startOfSegment, [currentTime, endOfSegment]), endOfSegment]]);
- // This condition happens when the currentTime is beyond the segment's
- // end time
- if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) {
- return 0;
- }
- var percent = calculateBufferedPercent(adjustedSegmentRange, originalSegmentRange, currentTime, buffered);
- // If the segment is reported as having a zero duration, return 0%
- // since it is likely that we will need to fetch the segment
- if (isNaN(percent) || percent === Infinity || percent === -Infinity) {
- return 0;
- }
- return percent;
- };
- /**
- * Gets a human readable string for a TimeRange
- *
- * @param {TimeRange} range
- * @returns {String} a human readable string
- */
- var printableRange = function printableRange(range) {
- var strArr = [];
- if (!range || !range.length) {
- return '';
- }
- for (var i = 0; i < range.length; i++) {
- strArr.push(range.start(i) + ' => ' + range.end(i));
- }
- return strArr.join(', ');
- };
- /**
- * Calculates the amount of time left in seconds until the player hits the end of the
- * buffer and causes a rebuffer
- *
- * @param {TimeRange} buffered
- * The state of the buffer
- * @param {Numnber} currentTime
- * The current time of the player
- * @param {Number} playbackRate
- * The current playback rate of the player. Defaults to 1.
- * @return {Number}
- * Time until the player has to start rebuffering in seconds.
- * @function timeUntilRebuffer
- */
- var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
- var playbackRate = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2];
- var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
- return (bufferedEnd - currentTime) / playbackRate;
- };
- exports['default'] = {
- findRange: findRange,
- findNextRange: findNextRange,
- findGaps: findGaps,
- findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd,
- getSegmentBufferedPercent: getSegmentBufferedPercent,
- TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR,
- SAFE_TIME_DELTA: SAFE_TIME_DELTA,
- printableRange: printableRange,
- timeUntilRebuffer: timeUntilRebuffer
- };
- module.exports = exports['default'];
|