rx-player
Version:
Canal+ HTML5 Video Player
267 lines (266 loc) • 12.3 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 __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var clear_element_src_1 = require("../../compat/clear_element_src");
var log_1 = require("../../log");
var assert_1 = require("../../utils/assert");
var is_null_or_undefined_1 = require("../../utils/is_null_or_undefined");
var noop_1 = require("../../utils/noop");
var task_canceller_1 = require("../../utils/task_canceller");
var types_1 = require("./types");
var get_loaded_reference_1 = require("./utils/get_loaded_reference");
var initial_seek_and_play_1 = require("./utils/initial_seek_and_play");
var initialize_content_decryption_1 = require("./utils/initialize_content_decryption");
var rebuffering_controller_1 = require("./utils/rebuffering_controller");
var throw_on_media_error_1 = require("./utils/throw_on_media_error");
/**
* `ContentIntializer` which will load contents by putting their URL in the
* `src` attribute of the given HTMLMediaElement.
*
* Because such contents are mainly loaded by the browser, those (called
* "directfile" contents in the RxPlayer) needs a simpler logic in-JS when
* compared to a content that relies on the MSE API.
*
* @class DirectFileContentInitializer
*/
var DirectFileContentInitializer = /** @class */ (function (_super) {
__extends(DirectFileContentInitializer, _super);
/**
* Creates a new `DirectFileContentInitializer` linked to the given settings.
* @param {Object} settings
*/
function DirectFileContentInitializer(settings) {
var _this = _super.call(this) || this;
_this._settings = settings;
_this._initCanceller = new task_canceller_1.default();
return _this;
}
/**
* "Prepare" content so it can later be played by calling `start`.
*/
DirectFileContentInitializer.prototype.prepare = function () {
return; // Directfile contents do not have any preparation
};
/**
* Start playback of the content linked to this `DirectFileContentInitializer`
* on the given `HTMLMediaElement` and its associated `PlaybackObserver`.
* @param {HTMLMediaElement} mediaElement - HTMLMediaElement on which the
* content will be played.
* @param {Object} playbackObserver - Object regularly emitting playback
* information.
*/
DirectFileContentInitializer.prototype.start = function (mediaElement, playbackObserver) {
var _this = this;
var cancelSignal = this._initCanceller.signal;
var _a = this._settings, keySystems = _a.keySystems, speed = _a.speed, url = _a.url;
(0, clear_element_src_1.default)(mediaElement);
var drmInitRef = (0, initialize_content_decryption_1.default)(mediaElement, keySystems, {
onError: function (err) { return _this._onFatalError(err); },
onWarning: function (err) { return _this.trigger("warning", err); },
onBlackListProtectionData: noop_1.default,
onKeyIdsCompatibilityUpdate: noop_1.default,
}, cancelSignal).statusRef;
/** Translate errors coming from the media element into RxPlayer errors. */
(0, throw_on_media_error_1.default)(mediaElement, function (error) { return _this._onFatalError(error); }, cancelSignal);
/**
* Class trying to avoid various stalling situations, emitting "stalled"
* events when it cannot, as well as "unstalled" events when it get out of one.
*/
var rebufferingController = new rebuffering_controller_1.default(playbackObserver, null, speed);
rebufferingController.addEventListener("stalled", function (evt) {
return _this.trigger("stalled", evt);
});
rebufferingController.addEventListener("unstalled", function () {
return _this.trigger("unstalled", null);
});
rebufferingController.addEventListener("warning", function (err) {
return _this.trigger("warning", err);
});
cancelSignal.register(function () {
rebufferingController.destroy();
});
rebufferingController.start();
drmInitRef.onUpdate(function (evt, stopListeningToDrmUpdates) {
if (evt.initializationState.type === "uninitialized") {
return; // nothing done yet
}
stopListeningToDrmUpdates();
// Start everything! (Just put the URL in the element's src).
log_1.default.info("Setting URL to HTMLMediaElement", url);
mediaElement.src = url;
cancelSignal.register(function () {
log_1.default.info("Init: Removing directfile src from media element", mediaElement.src);
(0, clear_element_src_1.default)(mediaElement);
});
if (evt.initializationState.type === "awaiting-media-link") {
evt.initializationState.value.isMediaLinked.setValue(true);
drmInitRef.onUpdate(function (newDrmStatus, stopListeningToDrmUpdatesAgain) {
if (newDrmStatus.initializationState.type === "initialized") {
stopListeningToDrmUpdatesAgain();
_this._seekAndPlay(mediaElement, playbackObserver);
}
}, { emitCurrentValue: true, clearSignal: cancelSignal });
}
else {
(0, assert_1.default)(evt.initializationState.type === "initialized");
_this._seekAndPlay(mediaElement, playbackObserver);
}
}, { emitCurrentValue: true, clearSignal: cancelSignal });
};
/**
* Update URL this `ContentIntializer` depends on.
* @param {Array.<string>|undefined} _urls
* @param {boolean} _refreshNow
*/
DirectFileContentInitializer.prototype.updateContentUrls = function (_urls, _refreshNow) {
throw new Error("Cannot update content URL of directfile contents");
};
/**
* Stop content and free all resources linked to this `ContentIntializer`.
*/
DirectFileContentInitializer.prototype.dispose = function () {
this._initCanceller.cancel();
};
/**
* Logic performed when a fatal error was triggered.
* @param {*} err - The fatal error in question.
*/
DirectFileContentInitializer.prototype._onFatalError = function (err) {
this._initCanceller.cancel();
this.trigger("error", err);
};
/**
* Perform the initial seek (to begin playback at an initially-calculated
* position based on settings) and auto-play if needed when loaded.
* @param {HTMLMediaElement} mediaElement
* @param {Object} playbackObserver
*/
DirectFileContentInitializer.prototype._seekAndPlay = function (mediaElement, playbackObserver) {
var _this = this;
var cancelSignal = this._initCanceller.signal;
var _a = this._settings, autoPlay = _a.autoPlay, startAt = _a.startAt;
var initialTime = function () {
log_1.default.debug("Init: Calculating initial time");
var initTime = getDirectFileInitialTime(mediaElement, startAt);
log_1.default.debug("Init: Initial time calculated:", initTime);
return initTime;
};
(0, initial_seek_and_play_1.default)({
mediaElement: mediaElement,
playbackObserver: playbackObserver,
startTime: initialTime,
mustAutoPlay: autoPlay,
onWarning: function (err) { return _this.trigger("warning", err); },
isDirectfile: true,
}, cancelSignal)
.autoPlayResult.then(function () {
return (0, get_loaded_reference_1.default)(playbackObserver, true, cancelSignal).onUpdate(function (isLoaded, stopListening) {
if (isLoaded) {
stopListening();
_this.trigger("loaded", {
getSegmentSinkMetrics: null,
getThumbnailData: function () {
return Promise.reject(new Error("Thumbnail data not available with directfile contents"));
},
});
}
}, { emitCurrentValue: true, clearSignal: cancelSignal });
})
.catch(function (err) {
if (!cancelSignal.isCancelled()) {
_this._onFatalError(err);
}
});
};
return DirectFileContentInitializer;
}(types_1.ContentInitializer));
exports.default = DirectFileContentInitializer;
/**
* calculate initial time as a position in seconds.
* @param {HTMLMediaElement} mediaElement
* @param {Object|undefined} [startAt]
* @returns {number}
*/
function getDirectFileInitialTime(mediaElement, startAt) {
if ((0, is_null_or_undefined_1.default)(startAt)) {
return 0;
}
if (!(0, is_null_or_undefined_1.default)(startAt.position)) {
return startAt.position;
}
else if (!(0, is_null_or_undefined_1.default)(startAt.wallClockTime)) {
return startAt.wallClockTime;
}
else if (!(0, is_null_or_undefined_1.default)(startAt.fromFirstPosition)) {
return startAt.fromFirstPosition;
}
var duration = mediaElement.duration;
if (typeof startAt.fromLastPosition === "number") {
if (!(0, is_null_or_undefined_1.default)(duration) && isFinite(duration)) {
return Math.max(0, duration + startAt.fromLastPosition);
}
if (mediaElement.seekable.length > 0) {
var lastSegmentEnd = mediaElement.seekable.end(mediaElement.seekable.length - 1);
if (isFinite(lastSegmentEnd)) {
return Math.max(0, lastSegmentEnd + startAt.fromLastPosition);
}
}
log_1.default.warn("Init: startAt.fromLastPosition set but no known duration, " +
"it may be too soon to seek");
return undefined;
}
else if (typeof startAt.fromLivePosition === "number") {
var livePosition = mediaElement.seekable.length > 0 ? mediaElement.seekable.end(0) : duration;
if ((0, is_null_or_undefined_1.default)(livePosition)) {
log_1.default.warn("Init: startAt.fromLivePosition set but no known live position, " +
"beginning at 0.");
return 0;
}
return Math.max(0, livePosition + startAt.fromLivePosition);
}
else if (!(0, is_null_or_undefined_1.default)(startAt.percentage)) {
if ((0, is_null_or_undefined_1.default)(duration) || !isFinite(duration)) {
log_1.default.warn("Init: startAt.percentage set but no known duration, " + "beginning at 0.");
return 0;
}
var percentage = startAt.percentage;
if (percentage >= 100) {
return duration;
}
else if (percentage <= 0) {
return 0;
}
var ratio = +percentage / 100;
return duration * ratio;
}
return 0;
}