rx-player
Version:
Canal+ HTML5 Video Player
454 lines (453 loc) • 22 kB
JavaScript
"use strict";
/**
* Copyright 2015 CANAL+ Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var config_1 = require("../../../../../config");
var log_1 = require("../../../../../log");
var assert_1 = require("../../../../../utils/assert");
var is_null_or_undefined_1 = require("../../../../../utils/is_null_or_undefined");
var get_init_segment_1 = require("./get_init_segment");
var tokens_1 = require("./tokens");
var utils_1 = require("./utils");
/**
* IRepresentationIndex implementation for DASH' SegmentTemplate without a
* SegmentTimeline.
* @class TemplateRepresentationIndex
*/
var TemplateRepresentationIndex = /** @class */ (function () {
/**
* @param {Object} index
* @param {Object} context
*/
function TemplateRepresentationIndex(index, context) {
var _a, _b, _c;
var availabilityTimeOffset = context.availabilityTimeOffset, manifestBoundsCalculator = context.manifestBoundsCalculator, isDynamic = context.isDynamic, periodEnd = context.periodEnd, periodStart = context.periodStart, representationId = context.representationId, representationBitrate = context.representationBitrate, isEMSGWhitelisted = context.isEMSGWhitelisted;
var timescale = (_a = index.timescale) !== null && _a !== void 0 ? _a : 1;
this._availabilityTimeOffset = availabilityTimeOffset;
this._manifestBoundsCalculator = manifestBoundsCalculator;
var presentationTimeOffset = (_b = index.presentationTimeOffset) !== null && _b !== void 0 ? _b : 0;
var scaledStart = periodStart * timescale;
var indexTimeOffset = presentationTimeOffset - scaledStart;
if (index.duration === undefined) {
throw new Error("Invalid SegmentTemplate: no duration");
}
var initializationUrl = ((_c = index.initialization) === null || _c === void 0 ? void 0 : _c.media) === undefined
? null
: (0, tokens_1.constructRepresentationUrl)(index.initialization.media, representationId, representationBitrate);
var segmentUrlTemplate = index.media === undefined
? null
: (0, tokens_1.constructRepresentationUrl)(index.media, representationId, representationBitrate);
this._index = {
duration: index.duration,
timescale: timescale,
indexRange: index.indexRange,
indexTimeOffset: indexTimeOffset,
initialization: (0, is_null_or_undefined_1.default)(index.initialization)
? undefined
: { url: initializationUrl, range: index.initialization.range },
url: segmentUrlTemplate,
presentationTimeOffset: presentationTimeOffset,
startNumber: index.startNumber,
endNumber: index.endNumber,
};
this._isDynamic = isDynamic;
this._periodStart = periodStart;
this._scaledRelativePeriodEnd =
periodEnd === undefined ? undefined : (periodEnd - periodStart) * timescale;
this._isEMSGWhitelisted = isEMSGWhitelisted;
}
/**
* Construct init Segment.
* @returns {Object}
*/
TemplateRepresentationIndex.prototype.getInitSegment = function () {
return (0, get_init_segment_1.default)(this._index, this._isEMSGWhitelisted);
};
/**
* @param {Number} fromTime
* @param {Number} dur
* @returns {Array.<Object>}
*/
TemplateRepresentationIndex.prototype.getSegments = function (fromTime, dur) {
var index = this._index;
var duration = index.duration, startNumber = index.startNumber, endNumber = index.endNumber, timescale = index.timescale, url = index.url;
var scaledStart = this._periodStart * timescale;
var scaledEnd = this._scaledRelativePeriodEnd;
// Convert the asked position to the right timescales, and consider them
// relatively to the Period's start.
var upFromPeriodStart = fromTime * timescale - scaledStart;
var toFromPeriodStart = (fromTime + dur) * timescale - scaledStart;
var firstSegmentStart = this._getFirstSegmentStart();
var lastSegmentStart = this._getLastSegmentStart();
if ((0, is_null_or_undefined_1.default)(firstSegmentStart) || (0, is_null_or_undefined_1.default)(lastSegmentStart)) {
return [];
}
var startPosition = Math.max(firstSegmentStart, upFromPeriodStart);
var lastWantedStartPosition = Math.min(lastSegmentStart, toFromPeriodStart);
if (lastWantedStartPosition + duration <= startPosition) {
return [];
}
var segments = [];
// number corresponding to the Period's start
var numberOffset = startNumber !== null && startNumber !== void 0 ? startNumber : 1;
// calcul initial time from Period start, where the first segment would have
// the `0` number
var numberIndexedToZero = Math.floor(startPosition / duration);
for (var timeFromPeriodStart = numberIndexedToZero * duration; timeFromPeriodStart <= lastWantedStartPosition; timeFromPeriodStart += duration) {
// To obtain the real number, adds the real number from the Period's start
var realNumber = numberIndexedToZero + numberOffset;
if (endNumber !== undefined && realNumber > endNumber) {
return segments;
}
var realDuration = !(0, is_null_or_undefined_1.default)(scaledEnd) && timeFromPeriodStart + duration > scaledEnd
? scaledEnd - timeFromPeriodStart
: duration;
var realTime = timeFromPeriodStart + scaledStart;
var manifestTime = timeFromPeriodStart + this._index.presentationTimeOffset;
var detokenizedURL = url === null ? null : (0, tokens_1.createDashUrlDetokenizer)(manifestTime, realNumber)(url);
var args = {
id: String(realNumber),
number: realNumber,
time: realTime / timescale,
end: (realTime + realDuration) / timescale,
duration: realDuration / timescale,
timescale: 1,
isInit: false,
scaledDuration: realDuration / timescale,
url: detokenizedURL,
timestampOffset: -(index.indexTimeOffset / timescale),
complete: true,
privateInfos: {
isEMSGWhitelisted: this._isEMSGWhitelisted,
},
};
segments.push(args);
numberIndexedToZero++;
}
return segments;
};
/**
* Returns first possible position in the index, in seconds.
* @returns {number|null|undefined}
*/
TemplateRepresentationIndex.prototype.getFirstAvailablePosition = function () {
var firstSegmentStart = this._getFirstSegmentStart();
if ((0, is_null_or_undefined_1.default)(firstSegmentStart)) {
return firstSegmentStart; // return undefined or null
}
return firstSegmentStart / this._index.timescale + this._periodStart;
};
/**
* Returns last possible position in the index, in seconds.
* @returns {number|null}
*/
TemplateRepresentationIndex.prototype.getLastAvailablePosition = function () {
var lastSegmentStart = this._getLastSegmentStart();
if ((0, is_null_or_undefined_1.default)(lastSegmentStart)) {
// In that case (null or undefined), getLastAvailablePosition should reflect
// the result of getLastSegmentStart, as the meaning is the same for
// the two functions. So, we return the result of the latter.
return lastSegmentStart;
}
var scaledRelativeIndexEnd = this._estimateRelativeScaledEnd();
var lastSegmentEnd = Math.min(lastSegmentStart + this._index.duration, scaledRelativeIndexEnd !== null && scaledRelativeIndexEnd !== void 0 ? scaledRelativeIndexEnd : Infinity);
return lastSegmentEnd / this._index.timescale + this._periodStart;
};
/**
* Returns the absolute end in seconds this RepresentationIndex can reach once
* all segments are available.
* @returns {number|null|undefined}
*/
TemplateRepresentationIndex.prototype.getEnd = function () {
if (!this._isDynamic) {
return this.getLastAvailablePosition();
}
var scaledRelativeIndexEnd = this._estimateRelativeScaledEnd();
if (scaledRelativeIndexEnd === undefined) {
return undefined;
}
var timescale = this._index.timescale;
var absoluteScaledIndexEnd = scaledRelativeIndexEnd + this._periodStart * timescale;
return absoluteScaledIndexEnd / timescale;
};
/**
* Returns:
* - `true` if in the given time interval, at least one new segment is
* expected to be available in the future.
* - `false` either if all segments in that time interval are already
* available for download or if none will ever be available for it.
* - `undefined` when it is not possible to tell.
*
* Always `false` in a `BaseRepresentationIndex` because all segments should
* be directly available.
* @returns {boolean}
*/
TemplateRepresentationIndex.prototype.awaitSegmentBetween = function (start, end) {
(0, assert_1.default)(start <= end);
if (!this._isDynamic) {
return false;
}
var timescale = this._index.timescale;
var segmentTimeRounding = (0, utils_1.getSegmentTimeRoundingError)(timescale);
var scaledPeriodStart = this._periodStart * timescale;
var scaledRelativeStart = start * timescale - scaledPeriodStart;
var scaledRelativeEnd = end * timescale - scaledPeriodStart;
var lastSegmentStart = this._getLastSegmentStart();
if ((0, is_null_or_undefined_1.default)(lastSegmentStart)) {
var relativeScaledIndexEnd_1 = this._estimateRelativeScaledEnd();
if (relativeScaledIndexEnd_1 === undefined) {
return scaledRelativeEnd + segmentTimeRounding >= 0;
}
return (scaledRelativeEnd + segmentTimeRounding >= 0 &&
scaledRelativeStart < relativeScaledIndexEnd_1 - segmentTimeRounding);
}
var lastSegmentEnd = lastSegmentStart + this._index.duration;
var relativeScaledIndexEnd = this._estimateRelativeScaledEnd();
if (relativeScaledIndexEnd === undefined) {
return scaledRelativeEnd > lastSegmentEnd - segmentTimeRounding;
}
return (scaledRelativeEnd > lastSegmentEnd - segmentTimeRounding &&
scaledRelativeStart < relativeScaledIndexEnd - segmentTimeRounding);
};
/**
* Returns true if, based on the arguments, the index should be refreshed.
* We never have to refresh a SegmentTemplate-based manifest.
* @returns {Boolean}
*/
TemplateRepresentationIndex.prototype.shouldRefresh = function () {
return false;
};
/**
* We cannot check for discontinuity in SegmentTemplate-based indexes.
* @returns {null}
*/
TemplateRepresentationIndex.prototype.checkDiscontinuity = function () {
return null;
};
/**
* Returns `true` if the given segment should still be available as of now
* (not removed since and still request-able).
* Returns `false` if that's not the case.
* Returns `undefined` if we do not know whether that's the case or not.
* @param {Object} segment
* @returns {boolean|undefined}
*/
TemplateRepresentationIndex.prototype.isSegmentStillAvailable = function (segment) {
if (segment.isInit) {
return true;
}
var segmentsForTime = this.getSegments(segment.time, 0.1);
if (segmentsForTime.length === 0) {
return false;
}
return (segmentsForTime[0].time === segment.time &&
segmentsForTime[0].end === segment.end &&
segmentsForTime[0].number === segment.number);
};
/**
* SegmentTemplate without a SegmentTimeline should not be updated.
* @returns {Boolean}
*/
TemplateRepresentationIndex.prototype.canBeOutOfSyncError = function () {
return false;
};
/**
* Returns `false` if the last segments in this index have already been
* generated so that we can freely go to the next period.
* Returns `true` if the index is still waiting on future segments to be
* generated.
* @returns {Boolean}
*/
TemplateRepresentationIndex.prototype.isStillAwaitingFutureSegments = function () {
if (!this._isDynamic) {
return false;
}
var scaledRelativeIndexEnd = this._estimateRelativeScaledEnd();
if (scaledRelativeIndexEnd === undefined) {
return true;
}
var timescale = this._index.timescale;
var lastSegmentStart = this._getLastSegmentStart();
// As last segment start is null if live time is before
// current period, consider the index not to be finished.
if ((0, is_null_or_undefined_1.default)(lastSegmentStart)) {
return true;
}
var lastSegmentEnd = lastSegmentStart + this._index.duration;
var segmentTimeRounding = (0, utils_1.getSegmentTimeRoundingError)(timescale);
return lastSegmentEnd + segmentTimeRounding < scaledRelativeIndexEnd;
};
/**
* @returns {Boolean}
*/
TemplateRepresentationIndex.prototype.isInitialized = function () {
return true;
};
TemplateRepresentationIndex.prototype.initialize = function () {
log_1.default.error("A `TemplateRepresentationIndex` does not need to be initialized");
};
TemplateRepresentationIndex.prototype.addPredictedSegments = function () {
log_1.default.warn("Cannot add predicted segments to a `TemplateRepresentationIndex`");
};
/**
* Returns the `duration` of each segment in the context of its Manifest (i.e.
* as the Manifest anounces them, actual segment duration may be different due
* to approximations), in seconds.
* @returns {number}
*/
TemplateRepresentationIndex.prototype.getTargetSegmentDuration = function () {
return {
duration: this._index.duration / this._index.timescale,
isPrecize: true,
};
};
/**
* @param {Object} newIndex
*/
TemplateRepresentationIndex.prototype._replace = function (newIndex) {
this._index = newIndex._index;
this._isDynamic = newIndex._isDynamic;
this._periodStart = newIndex._periodStart;
this._scaledRelativePeriodEnd = newIndex._scaledRelativePeriodEnd;
this._manifestBoundsCalculator = newIndex._manifestBoundsCalculator;
};
/**
* @param {Object} newIndex
*/
TemplateRepresentationIndex.prototype._update = function (newIndex) {
// As segments are not declared individually, as long as this Representation
// is present, we have every information we need
this._replace(newIndex);
};
/**
* Returns the timescaled start of the first segment that should be available,
* relatively to the start of the Period.
* @returns {number | null | undefined}
*/
TemplateRepresentationIndex.prototype._getFirstSegmentStart = function () {
var _a;
if (!this._isDynamic) {
return 0; // it is the start of the Period
}
// 1 - check that this index is already available
if (this._scaledRelativePeriodEnd === 0 ||
this._scaledRelativePeriodEnd === undefined) {
// /!\ The scaled max position augments continuously and might not
// reflect exactly the real server-side value. As segments are
// generated discretely.
var maximumSegmentTime = this._manifestBoundsCalculator.getEstimatedMaximumPosition((_a = this._availabilityTimeOffset) !== null && _a !== void 0 ? _a : 0);
if (maximumSegmentTime !== undefined && maximumSegmentTime < this._periodStart) {
// Maximum position is before this period.
// No segment is yet available here
return null;
}
}
var _b = this._index, duration = _b.duration, timescale = _b.timescale;
var firstPosition = this._manifestBoundsCalculator.getEstimatedMinimumSegmentTime(duration / timescale);
if (firstPosition === undefined) {
return undefined;
}
var segmentTime = firstPosition > this._periodStart
? (firstPosition - this._periodStart) * timescale
: 0;
var numberIndexedToZero = Math.floor(segmentTime / duration);
return numberIndexedToZero * duration;
};
/**
* Returns the timescaled start of the last segment that should be available,
* relatively to the start of the Period.
* Returns null if live time is before current period.
* @returns {number|null|undefined}
*/
TemplateRepresentationIndex.prototype._getLastSegmentStart = function () {
var _a, _b;
var _c = this._index, duration = _c.duration, timescale = _c.timescale, endNumber = _c.endNumber, _d = _c.startNumber, startNumber = _d === void 0 ? 1 : _d;
if (this._isDynamic) {
var liveEdge = this._manifestBoundsCalculator.getEstimatedLiveEdge();
if (liveEdge !== undefined &&
this._scaledRelativePeriodEnd !== undefined &&
this._scaledRelativePeriodEnd <
liveEdge - this._periodStart * this._index.timescale) {
var numberOfSegments = Math.ceil(this._scaledRelativePeriodEnd / duration);
if (endNumber !== undefined && endNumber - startNumber + 1 < numberOfSegments) {
numberOfSegments = endNumber - startNumber + 1;
}
return (numberOfSegments - 1) * duration;
}
var lastPosition = this._manifestBoundsCalculator.getEstimatedMaximumPosition((_a = this._availabilityTimeOffset) !== null && _a !== void 0 ? _a : 0);
if (lastPosition === undefined) {
return undefined;
}
// /!\ The scaled last position augments continuously and might not
// reflect exactly the real server-side value. As segments are
// generated discretely.
var scaledLastPosition = (lastPosition - this._periodStart) * timescale;
// Maximum position is before this period.
// No segment is yet available here
if (scaledLastPosition < 0) {
return null;
}
var numberOfSegmentsAvailable = Math.floor(scaledLastPosition / duration);
if (endNumber !== undefined &&
endNumber - startNumber + 1 < numberOfSegmentsAvailable) {
numberOfSegmentsAvailable = endNumber - startNumber + 1;
}
return numberOfSegmentsAvailable <= 0
? null
: (numberOfSegmentsAvailable - 1) * duration;
}
else {
var maximumTime = (_b = this._scaledRelativePeriodEnd) !== null && _b !== void 0 ? _b : 0;
var numberOfSegments = Math.ceil(maximumTime / duration);
if (endNumber !== undefined && endNumber - startNumber + 1 < numberOfSegments) {
numberOfSegments = endNumber - startNumber + 1;
}
var regularLastSegmentStart = (numberOfSegments - 1) * duration;
// In some SegmentTemplate, we could think that there is one more
// segment that there actually is due to a very little difference between
// the period's duration and a multiple of a segment's duration.
// Check that we're within a good margin
var minimumDuration = config_1.default.getCurrent().MINIMUM_SEGMENT_SIZE * timescale;
if (endNumber !== undefined ||
maximumTime - regularLastSegmentStart > minimumDuration ||
numberOfSegments < 2) {
return regularLastSegmentStart;
}
return (numberOfSegments - 2) * duration;
}
};
/**
* Returns an estimate of the last available position in this
* `RepresentationIndex` based on attributes such as the Period's end and
* the `endNumber` attribute.
* If the estimate cannot be made (e.g. this Period's segments are still being
* generated and its end is yet unknown), returns `undefined`.
* @returns {number|undefined}
*/
TemplateRepresentationIndex.prototype._estimateRelativeScaledEnd = function () {
var _a, _b;
if (this._index.endNumber !== undefined) {
var numberOfSegments = this._index.endNumber - ((_a = this._index.startNumber) !== null && _a !== void 0 ? _a : 1) + 1;
return Math.max(Math.min(numberOfSegments * this._index.duration, (_b = this._scaledRelativePeriodEnd) !== null && _b !== void 0 ? _b : Infinity), 0);
}
if (this._scaledRelativePeriodEnd === undefined) {
return undefined;
}
return Math.max(this._scaledRelativePeriodEnd, 0);
};
return TemplateRepresentationIndex;
}());
exports.default = TemplateRepresentationIndex;