rx-player
Version:
Canal+ HTML5 Video Player
229 lines (228 loc) • 10.4 kB
JavaScript
;
/**
* 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.
*/
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = getBufferStatus;
var is_null_or_undefined_1 = require("../../../../utils/is_null_or_undefined");
var segment_sinks_1 = require("../../../segment_sinks");
var check_for_discontinuity_1 = require("./check_for_discontinuity");
var get_needed_segments_1 = require("./get_needed_segments");
var get_segment_priority_1 = require("./get_segment_priority");
/**
* Checks on the current buffered data for the given type and Period
* and returns what should be done to fill the buffer according to the buffer
* goal, the Representation chosen, etc.
* Also emits discontinuities if found, which are parts of the buffer that won't
* be filled by any segment, even in the future.
*
* @param {Object} content
* @param {number} initialWantedTime
* @param {Object} playbackObserver
* @param {number|undefined} fastSwitchThreshold
* @param {number} bufferGoal
* @param {number} maxBufferSize
* @param {Object} segmentSink
* @returns {Object}
*/
function getBufferStatus(content, initialWantedTime, playbackObserver, fastSwitchThreshold, bufferGoal, maxBufferSize, segmentSink) {
var _a, _b, _c;
var representation = content.representation;
var isPaused = (_b = (_a = playbackObserver.getIsPaused()) !== null && _a !== void 0 ? _a : playbackObserver.getReference().getValue().paused.pending) !== null && _b !== void 0 ? _b : playbackObserver.getReference().getValue().paused.last;
var playbackRate = (_c = playbackObserver.getPlaybackRate()) !== null && _c !== void 0 ? _c : playbackObserver.getReference().getValue().speed;
var askedStart = initialWantedTime;
if (isPaused === undefined ||
playbackRate === undefined ||
isPaused ||
playbackRate <= 0) {
askedStart -= 0.1;
}
var neededRange = getRangeOfNeededSegments(content, askedStart, bufferGoal);
var shouldRefreshManifest = representation.index.shouldRefresh(neededRange.start, neededRange.end);
/**
* Every segment awaiting an "SignalSegmentComplete" operation, which
* indicates that a completely-loaded segment is still being pushed to the
* SegmentSink.
*/
var segmentsBeingPushed = segmentSink
.getPendingOperations()
.filter(function (operation) {
return operation.type === segment_sinks_1.SegmentSinkOperation.SignalSegmentComplete;
})
.map(function (operation) { return operation.value; });
/** Data on every segments buffered around `neededRange`. */
var bufferedSegments = segmentSink.getLastKnownInventory();
var currentPlaybackTime = playbackObserver.getCurrentTime();
if (currentPlaybackTime === undefined) {
// We're in a WebWorker, just consider the last known position
currentPlaybackTime = playbackObserver.getReference().getValue().position.getWanted();
}
/** Callback allowing to retrieve a segment's history in the buffer. */
var getBufferedHistory = segmentSink.getSegmentHistory.bind(segmentSink);
/** List of segments we will need to download. */
var _d = (0, get_needed_segments_1.default)({
content: content,
bufferedSegments: bufferedSegments,
currentPlaybackTime: currentPlaybackTime,
fastSwitchThreshold: fastSwitchThreshold,
getBufferedHistory: getBufferedHistory,
neededRange: neededRange,
segmentsBeingPushed: segmentsBeingPushed,
maxBufferSize: maxBufferSize,
}), segmentsToLoad = _d.segmentsToLoad, segmentsOnHold = _d.segmentsOnHold, isBufferFull = _d.isBufferFull;
var prioritizedNeededSegments = segmentsToLoad.map(function (segment) { return ({
priority: (0, get_segment_priority_1.default)(segment.time, askedStart),
segment: segment,
}); });
/**
* `true` if the current `RepresentationStream` has loaded all the
* needed segments for this Representation until the end of the Period.
*/
var hasFinishedLoading = representation.index.isInitialized() &&
!representation.index.isStillAwaitingFutureSegments() &&
neededRange.hasReachedPeriodEnd &&
prioritizedNeededSegments.length === 0 &&
segmentsOnHold.length === 0;
/**
* Start time in seconds of the next available not-yet pushed segment.
* `null` if no segment is wanted for the current wanted range.
*/
var nextSegmentStart = null;
if (segmentsBeingPushed.length > 0) {
nextSegmentStart = Math.min.apply(Math, __spreadArray([], __read(segmentsBeingPushed.map(function (info) { return info.segment.time; })), false));
}
if (segmentsOnHold.length > 0) {
nextSegmentStart =
nextSegmentStart !== null
? Math.min(nextSegmentStart, segmentsOnHold[0].time)
: segmentsOnHold[0].time;
}
if (prioritizedNeededSegments.length > 0) {
nextSegmentStart =
nextSegmentStart !== null
? Math.min(nextSegmentStart, prioritizedNeededSegments[0].segment.time)
: prioritizedNeededSegments[0].segment.time;
}
var imminentDiscontinuity = (0, check_for_discontinuity_1.default)(content, neededRange, nextSegmentStart, hasFinishedLoading, bufferedSegments);
return {
imminentDiscontinuity: imminentDiscontinuity,
hasFinishedLoading: hasFinishedLoading,
neededSegments: prioritizedNeededSegments,
isBufferFull: isBufferFull,
shouldRefreshManifest: shouldRefreshManifest,
};
}
/**
* Returns both the time range of segments that should be loaded (from a
* starting position to an ending position) and whether the end of the Period is
* reached by that range.
* @param {Object} content
* @param {number} initialWantedTime
* @param {number} bufferGoal
* @returns {Object}
*/
function getRangeOfNeededSegments(content, initialWantedTime, bufferGoal) {
var _a;
var wantedStartPosition;
var manifest = content.manifest, period = content.period, representation = content.representation;
var lastIndexPosition = representation.index.getLastAvailablePosition();
var representationIndex = representation.index;
// There is an exception for when the current initially wanted time is already
// after the last position with segments AND when we're playing the absolute
// last Period in the Manifest.
// In that case, we want to actually request at least the last segment to
// avoid ending the last Period - and by extension the content - with a
// segment which isn't the last one.
if (!(0, is_null_or_undefined_1.default)(lastIndexPosition) &&
segment_sinks_1.default.isNative(content.adaptation.type) &&
initialWantedTime >= lastIndexPosition &&
representationIndex.isInitialized() &&
!representationIndex.isStillAwaitingFutureSegments() &&
isPeriodTheCurrentAndLastOne(manifest, period, initialWantedTime)) {
wantedStartPosition = lastIndexPosition - 1;
}
else {
wantedStartPosition = initialWantedTime - 0.1;
}
var wantedEndPosition = wantedStartPosition + bufferGoal;
var hasReachedPeriodEnd;
if (!representation.index.isInitialized() ||
representation.index.isStillAwaitingFutureSegments() ||
period.end === undefined) {
hasReachedPeriodEnd = false;
}
else if (lastIndexPosition === undefined) {
// We do not know the end of this index.
hasReachedPeriodEnd = wantedEndPosition >= period.end;
}
else if (lastIndexPosition === null) {
// There is no available segment in the index currently.
hasReachedPeriodEnd = true;
}
else {
// We have a declared end. Check that our range went until the last
// position available in the index. If that's the case and we're left
// with no segments after filtering them, it means we already have
// downloaded the last segments and have nothing left to do: full.
hasReachedPeriodEnd = wantedEndPosition >= lastIndexPosition;
}
return {
start: Math.max(wantedStartPosition, period.start),
end: Math.min(wantedEndPosition, (_a = period.end) !== null && _a !== void 0 ? _a : Infinity),
hasReachedPeriodEnd: hasReachedPeriodEnd,
};
}
/**
* Returns `true` if the given Period is both:
* - the one being played (the current position is known from `time`)
* - the absolute last one in the Manifest (that is, there will never be a
* Period after it).
* @param {Object} manifest
* @param {Object} period
* @param {number} time
* @returns {boolean}
*/
function isPeriodTheCurrentAndLastOne(manifest, period, time) {
var _a;
var nextPeriod = manifest.getPeriodAfter(period);
return (period.containsTime(time, nextPeriod) &&
manifest.isLastPeriodKnown &&
period.id === ((_a = manifest.periods[manifest.periods.length - 1]) === null || _a === void 0 ? void 0 : _a.id));
}