123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- /**
- * @file playlist.js
- *
- * Playlist related utilities.
- */
- 'use strict';
- Object.defineProperty(exports, '__esModule', {
- value: true
- });
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
- var _videoJs = require('video.js');
- var _globalWindow = require('global/window');
- var _globalWindow2 = _interopRequireDefault(_globalWindow);
- /**
- * walk backward until we find a duration we can use
- * or return a failure
- *
- * @param {Playlist} playlist the playlist to walk through
- * @param {Number} endSequence the mediaSequence to stop walking on
- */
- var backwardDuration = function backwardDuration(playlist, endSequence) {
- var result = 0;
- var i = endSequence - playlist.mediaSequence;
- // if a start time is available for segment immediately following
- // the interval, use it
- var segment = playlist.segments[i];
- // Walk backward until we find the latest segment with timeline
- // information that is earlier than endSequence
- if (segment) {
- if (typeof segment.start !== 'undefined') {
- return { result: segment.start, precise: true };
- }
- if (typeof segment.end !== 'undefined') {
- return {
- result: segment.end - segment.duration,
- precise: true
- };
- }
- }
- while (i--) {
- segment = playlist.segments[i];
- if (typeof segment.end !== 'undefined') {
- return { result: result + segment.end, precise: true };
- }
- result += segment.duration;
- if (typeof segment.start !== 'undefined') {
- return { result: result + segment.start, precise: true };
- }
- }
- return { result: result, precise: false };
- };
- /**
- * walk forward until we find a duration we can use
- * or return a failure
- *
- * @param {Playlist} playlist the playlist to walk through
- * @param {Number} endSequence the mediaSequence to stop walking on
- */
- var forwardDuration = function forwardDuration(playlist, endSequence) {
- var result = 0;
- var segment = undefined;
- var i = endSequence - playlist.mediaSequence;
- // Walk forward until we find the earliest segment with timeline
- // information
- for (; i < playlist.segments.length; i++) {
- segment = playlist.segments[i];
- if (typeof segment.start !== 'undefined') {
- return {
- result: segment.start - result,
- precise: true
- };
- }
- result += segment.duration;
- if (typeof segment.end !== 'undefined') {
- return {
- result: segment.end - result,
- precise: true
- };
- }
- }
- // indicate we didn't find a useful duration estimate
- return { result: -1, precise: false };
- };
- /**
- * Calculate the media duration from the segments associated with a
- * playlist. The duration of a subinterval of the available segments
- * may be calculated by specifying an end index.
- *
- * @param {Object} playlist a media playlist object
- * @param {Number=} endSequence an exclusive upper boundary
- * for the playlist. Defaults to playlist length.
- * @param {Number} expired the amount of time that has dropped
- * off the front of the playlist in a live scenario
- * @return {Number} the duration between the first available segment
- * and end index.
- */
- var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
- var backward = undefined;
- var forward = undefined;
- if (typeof endSequence === 'undefined') {
- endSequence = playlist.mediaSequence + playlist.segments.length;
- }
- if (endSequence < playlist.mediaSequence) {
- return 0;
- }
- // do a backward walk to estimate the duration
- backward = backwardDuration(playlist, endSequence);
- if (backward.precise) {
- // if we were able to base our duration estimate on timing
- // information provided directly from the Media Source, return
- // it
- return backward.result;
- }
- // walk forward to see if a precise duration estimate can be made
- // that way
- forward = forwardDuration(playlist, endSequence);
- if (forward.precise) {
- // we found a segment that has been buffered and so it's
- // position is known precisely
- return forward.result;
- }
- // return the less-precise, playlist-based duration estimate
- return backward.result + expired;
- };
- /**
- * Calculates the duration of a playlist. If a start and end index
- * are specified, the duration will be for the subset of the media
- * timeline between those two indices. The total duration for live
- * playlists is always Infinity.
- *
- * @param {Object} playlist a media playlist object
- * @param {Number=} endSequence an exclusive upper
- * boundary for the playlist. Defaults to the playlist media
- * sequence number plus its length.
- * @param {Number=} expired the amount of time that has
- * dropped off the front of the playlist in a live scenario
- * @return {Number} the duration between the start index and end
- * index.
- */
- var duration = function duration(playlist, endSequence, expired) {
- if (!playlist) {
- return 0;
- }
- if (typeof expired !== 'number') {
- expired = 0;
- }
- // if a slice of the total duration is not requested, use
- // playlist-level duration indicators when they're present
- if (typeof endSequence === 'undefined') {
- // if present, use the duration specified in the playlist
- if (playlist.totalDuration) {
- return playlist.totalDuration;
- }
- // duration should be Infinity for live playlists
- if (!playlist.endList) {
- return _globalWindow2['default'].Infinity;
- }
- }
- // calculate the total duration based on the segment durations
- return intervalDuration(playlist, endSequence, expired);
- };
- exports.duration = duration;
- /**
- * Calculate the time between two indexes in the current playlist
- * neight the start- nor the end-index need to be within the current
- * playlist in which case, the targetDuration of the playlist is used
- * to approximate the durations of the segments
- *
- * @param {Object} playlist a media playlist object
- * @param {Number} startIndex
- * @param {Number} endIndex
- * @return {Number} the number of seconds between startIndex and endIndex
- */
- var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
- var durations = 0;
- if (startIndex > endIndex) {
- var _ref = [endIndex, startIndex];
- startIndex = _ref[0];
- endIndex = _ref[1];
- }
- if (startIndex < 0) {
- for (var i = startIndex; i < Math.min(0, endIndex); i++) {
- durations += playlist.targetDuration;
- }
- startIndex = 0;
- }
- for (var i = startIndex; i < endIndex; i++) {
- durations += playlist.segments[i].duration;
- }
- return durations;
- };
- exports.sumDurations = sumDurations;
- /**
- * Determines the media index of the segment corresponding to the safe edge of the live
- * window which is the duration of the last segment plus 2 target durations from the end
- * of the playlist.
- *
- * @param {Object} playlist
- * a media playlist object
- * @return {Number}
- * The media index of the segment at the safe live point. 0 if there is no "safe"
- * point.
- * @function safeLiveIndex
- */
- var safeLiveIndex = function safeLiveIndex(playlist) {
- if (!playlist.segments.length) {
- return 0;
- }
- var i = playlist.segments.length - 1;
- var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
- var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
- while (i--) {
- distanceFromEnd += playlist.segments[i].duration;
- if (distanceFromEnd >= safeDistance) {
- break;
- }
- }
- return Math.max(0, i);
- };
- exports.safeLiveIndex = safeLiveIndex;
- /**
- * Calculates the playlist end time
- *
- * @param {Object} playlist a media playlist object
- * @param {Number=} expired the amount of time that has
- * dropped off the front of the playlist in a live scenario
- * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
- * playlist end calculation should consider the safe live end
- * (truncate the playlist end by three segments). This is normally
- * used for calculating the end of the playlist's seekable range.
- * @returns {Number} the end time of playlist
- * @function playlistEnd
- */
- var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
- if (!playlist || !playlist.segments) {
- return null;
- }
- if (playlist.endList) {
- return duration(playlist);
- }
- if (expired === null) {
- return null;
- }
- expired = expired || 0;
- var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
- return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
- };
- exports.playlistEnd = playlistEnd;
- /**
- * Calculates the interval of time that is currently seekable in a
- * playlist. The returned time ranges are relative to the earliest
- * moment in the specified playlist that is still available. A full
- * seekable implementation for live streams would need to offset
- * these values by the duration of content that has expired from the
- * stream.
- *
- * @param {Object} playlist a media playlist object
- * dropped off the front of the playlist in a live scenario
- * @param {Number=} expired the amount of time that has
- * dropped off the front of the playlist in a live scenario
- * @return {TimeRanges} the periods of time that are valid targets
- * for seeking
- */
- var seekable = function seekable(playlist, expired) {
- var useSafeLiveEnd = true;
- var seekableStart = expired || 0;
- var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
- if (seekableEnd === null) {
- return (0, _videoJs.createTimeRange)();
- }
- return (0, _videoJs.createTimeRange)(seekableStart, seekableEnd);
- };
- exports.seekable = seekable;
- var isWholeNumber = function isWholeNumber(num) {
- return num - Math.floor(num) === 0;
- };
- var roundSignificantDigit = function roundSignificantDigit(increment, num) {
- // If we have a whole number, just add 1 to it
- if (isWholeNumber(num)) {
- return num + increment * 0.1;
- }
- var numDecimalDigits = num.toString().split('.')[1].length;
- for (var i = 1; i <= numDecimalDigits; i++) {
- var scale = Math.pow(10, i);
- var temp = num * scale;
- if (isWholeNumber(temp) || i === numDecimalDigits) {
- return (temp + increment) / scale;
- }
- }
- };
- var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
- var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
- /**
- * Determine the index and estimated starting time of the segment that
- * contains a specified playback position in a media playlist.
- *
- * @param {Object} playlist the media playlist to query
- * @param {Number} currentTime The number of seconds since the earliest
- * possible position to determine the containing segment for
- * @param {Number} startIndex
- * @param {Number} startTime
- * @return {Object}
- */
- var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
- var i = undefined;
- var segment = undefined;
- var numSegments = playlist.segments.length;
- var time = currentTime - startTime;
- if (time < 0) {
- // Walk backward from startIndex in the playlist, adding durations
- // until we find a segment that contains `time` and return it
- if (startIndex > 0) {
- for (i = startIndex - 1; i >= 0; i--) {
- segment = playlist.segments[i];
- time += floorLeastSignificantDigit(segment.duration);
- if (time > 0) {
- return {
- mediaIndex: i,
- startTime: startTime - sumDurations(playlist, startIndex, i)
- };
- }
- }
- }
- // We were unable to find a good segment within the playlist
- // so select the first segment
- return {
- mediaIndex: 0,
- startTime: currentTime
- };
- }
- // When startIndex is negative, we first walk forward to first segment
- // adding target durations. If we "run out of time" before getting to
- // the first segment, return the first segment
- if (startIndex < 0) {
- for (i = startIndex; i < 0; i++) {
- time -= playlist.targetDuration;
- if (time < 0) {
- return {
- mediaIndex: 0,
- startTime: currentTime
- };
- }
- }
- startIndex = 0;
- }
- // Walk forward from startIndex in the playlist, subtracting durations
- // until we find a segment that contains `time` and return it
- for (i = startIndex; i < numSegments; i++) {
- segment = playlist.segments[i];
- time -= ceilLeastSignificantDigit(segment.duration);
- if (time < 0) {
- return {
- mediaIndex: i,
- startTime: startTime + sumDurations(playlist, startIndex, i)
- };
- }
- }
- // We are out of possible candidates so load the last one...
- return {
- mediaIndex: numSegments - 1,
- startTime: currentTime
- };
- };
- exports.getMediaInfoForTime = getMediaInfoForTime;
- /**
- * Check whether the playlist is blacklisted or not.
- *
- * @param {Object} playlist the media playlist object
- * @return {boolean} whether the playlist is blacklisted or not
- * @function isBlacklisted
- */
- var isBlacklisted = function isBlacklisted(playlist) {
- return playlist.excludeUntil && playlist.excludeUntil > Date.now();
- };
- exports.isBlacklisted = isBlacklisted;
- /**
- * Check whether the playlist is compatible with current playback configuration or has
- * been blacklisted permanently for being incompatible.
- *
- * @param {Object} playlist the media playlist object
- * @return {boolean} whether the playlist is incompatible or not
- * @function isIncompatible
- */
- var isIncompatible = function isIncompatible(playlist) {
- return playlist.excludeUntil && playlist.excludeUntil === Infinity;
- };
- exports.isIncompatible = isIncompatible;
- /**
- * Check whether the playlist is enabled or not.
- *
- * @param {Object} playlist the media playlist object
- * @return {boolean} whether the playlist is enabled or not
- * @function isEnabled
- */
- var isEnabled = function isEnabled(playlist) {
- var blacklisted = isBlacklisted(playlist);
- return !playlist.disabled && !blacklisted;
- };
- exports.isEnabled = isEnabled;
- /**
- * Check whether the playlist has been manually disabled through the representations api.
- *
- * @param {Object} playlist the media playlist object
- * @return {boolean} whether the playlist is disabled manually or not
- * @function isDisabled
- */
- var isDisabled = function isDisabled(playlist) {
- return playlist.disabled;
- };
- exports.isDisabled = isDisabled;
- /**
- * Returns whether the current playlist is an AES encrypted HLS stream
- *
- * @return {Boolean} true if it's an AES encrypted HLS stream
- */
- var isAes = function isAes(media) {
- for (var i = 0; i < media.segments.length; i++) {
- if (media.segments[i].key) {
- return true;
- }
- }
- return false;
- };
- exports.isAes = isAes;
- /**
- * Returns whether the current playlist contains fMP4
- *
- * @return {Boolean} true if the playlist contains fMP4
- */
- var isFmp4 = function isFmp4(media) {
- for (var i = 0; i < media.segments.length; i++) {
- if (media.segments[i].map) {
- return true;
- }
- }
- return false;
- };
- exports.isFmp4 = isFmp4;
- /**
- * Checks if the playlist has a value for the specified attribute
- *
- * @param {String} attr
- * Attribute to check for
- * @param {Object} playlist
- * The media playlist object
- * @return {Boolean}
- * Whether the playlist contains a value for the attribute or not
- * @function hasAttribute
- */
- var hasAttribute = function hasAttribute(attr, playlist) {
- return playlist.attributes && playlist.attributes[attr];
- };
- exports.hasAttribute = hasAttribute;
- /**
- * Estimates the time required to complete a segment download from the specified playlist
- *
- * @param {Number} segmentDuration
- * Duration of requested segment
- * @param {Number} bandwidth
- * Current measured bandwidth of the player
- * @param {Object} playlist
- * The media playlist object
- * @param {Number=} bytesReceived
- * Number of bytes already received for the request. Defaults to 0
- * @return {Number|NaN}
- * The estimated time to request the segment. NaN if bandwidth information for
- * the given playlist is unavailable
- * @function estimateSegmentRequestTime
- */
- var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
- var bytesReceived = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3];
- if (!hasAttribute('BANDWIDTH', playlist)) {
- return NaN;
- }
- var size = segmentDuration * playlist.attributes.BANDWIDTH;
- return (size - bytesReceived * 8) / bandwidth;
- };
- exports.estimateSegmentRequestTime = estimateSegmentRequestTime;
- /*
- * Returns whether the current playlist is the lowest rendition
- *
- * @return {Boolean} true if on lowest rendition
- */
- var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
- if (master.playlists.length === 1) {
- return true;
- }
- var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
- return master.playlists.filter(function (playlist) {
- if (!isEnabled(playlist)) {
- return false;
- }
- return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
- }).length === 0;
- };
- exports.isLowestEnabledRendition = isLowestEnabledRendition;
- // exports
- exports['default'] = {
- duration: duration,
- seekable: seekable,
- safeLiveIndex: safeLiveIndex,
- getMediaInfoForTime: getMediaInfoForTime,
- isEnabled: isEnabled,
- isDisabled: isDisabled,
- isBlacklisted: isBlacklisted,
- isIncompatible: isIncompatible,
- playlistEnd: playlistEnd,
- isAes: isAes,
- isFmp4: isFmp4,
- hasAttribute: hasAttribute,
- estimateSegmentRequestTime: estimateSegmentRequestTime,
- isLowestEnabledRendition: isLowestEnabledRendition
- };
|