rx-player
Version:
Canal+ HTML5 Video Player
275 lines (274 loc) • 12.2 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 __());
};
})();
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
var config_1 = require("../../../../config");
var event_emitter_1 = require("../../../../utils/event_emitter");
var reference_1 = require("../../../../utils/reference");
var task_canceller_1 = require("../../../../utils/task_canceller");
var refresh_scheduled_events_list_1 = require("./refresh_scheduled_events_list");
/**
* Get events from manifest and emit each time an event has to be emitted
*/
var StreamEventsEmitter = /** @class */ (function (_super) {
__extends(StreamEventsEmitter, _super);
/**
* @param {Object} playbackObserver
*/
function StreamEventsEmitter(playbackObserver) {
var _this = _super.call(this) || this;
_this._playbackObserver = playbackObserver;
_this._canceller = null;
_this._scheduledEventsRef = new reference_1.default([]);
_this._eventsBeingPlayed = new WeakMap();
_this._isPaused = true;
_this._previousObservation = null;
return _this;
}
/**
* Initialize the emitter for the given content manifest.
*
* The emitter starts in a paused state so the caller can resume it only once
* the corresponding media instance is actually ready.
* @param {Object} manifest
*/
StreamEventsEmitter.prototype.start = function (manifest) {
var _this = this;
if (this._canceller !== null) {
return;
}
this._canceller = new task_canceller_1.default("StreamEventsEmitter");
var cancelSignal = this._canceller.signal;
this._isPaused = true;
this._previousObservation = null;
this._eventsBeingPlayed = new WeakMap();
var isPollingEvents = false;
var cancelCurrentPolling = new task_canceller_1.default("StreamEventsEmitter Polling");
cancelCurrentPolling.linkToSignal(cancelSignal);
this._scheduledEventsRef.setValue((0, refresh_scheduled_events_list_1.default)([], manifest));
this._scheduledEventsRef.onUpdate(function (_a) {
var scheduledEventsLength = _a.length;
if (scheduledEventsLength === 0) {
if (isPollingEvents) {
cancelCurrentPolling.cancel("no stream event");
cancelCurrentPolling = new task_canceller_1.default("StreamEventsEmitter Polling");
cancelCurrentPolling.linkToSignal(cancelSignal);
isPollingEvents = false;
}
return;
}
else if (isPollingEvents) {
return;
}
isPollingEvents = true;
var checkStreamEvents = function () {
if (_this._isPaused) {
return;
}
var newObservation = _this._constructObservation();
if (_this._previousObservation === null) {
_this._previousObservation = newObservation;
return;
}
_this._emitStreamEvents(_this._scheduledEventsRef.getValue(), _this._previousObservation, newObservation, cancelCurrentPolling.signal);
_this._previousObservation = newObservation;
};
var STREAM_EVENT_EMITTER_POLL_INTERVAL = config_1.default.getCurrent().STREAM_EVENT_EMITTER_POLL_INTERVAL;
var intervalId = setInterval(checkStreamEvents, STREAM_EVENT_EMITTER_POLL_INTERVAL);
_this._playbackObserver.listen(checkStreamEvents, {
includeLastObservation: false,
clearSignal: cancelCurrentPolling.signal,
});
cancelCurrentPolling.signal.register(function () {
clearInterval(intervalId);
});
}, { emitCurrentValue: true, clearSignal: cancelSignal });
};
/**
* Suspend event evaluation until `resume` has been called.
*
* To use when playback metrics should be temporarily ignored, for example
* while reloading a content.
*/
StreamEventsEmitter.prototype.pause = function () {
this._isPaused = true;
this._previousObservation = null;
};
/**
* Resume event evaluation from the current playback state.
*/
StreamEventsEmitter.prototype.resume = function () {
this._isPaused = false;
// Take a snapshot right now to avoid comparing a post-reload position
// with a stale pre-reload observation.
this._previousObservation = this._constructObservation();
};
/** Refresh the tracked stream-event list after a manifest update. */
StreamEventsEmitter.prototype.onManifestUpdate = function (man) {
var prev = this._scheduledEventsRef.getValue();
this._scheduledEventsRef.setValue((0, refresh_scheduled_events_list_1.default)(prev, man));
};
/**
* @param {string | undefined} reason - Human-inspectable reason behind the
* stop. Used for debugging matters, especially for debug log
* inspection.
*/
StreamEventsEmitter.prototype.stop = function (reason) {
if (this._canceller !== null) {
this._canceller.cancel(reason !== null && reason !== void 0 ? reason : "StreamEventsEmitter stop");
this._canceller = null;
this._previousObservation = null;
this._eventsBeingPlayed = new WeakMap();
}
};
/**
* Construct observation object relied on by the `StreamEventsEmitted` by
* generating it from this instance's `PlaybackObserver`.
*/
StreamEventsEmitter.prototype._constructObservation = function () {
var _a;
var lastObservation = this._playbackObserver.getReference().getValue();
var currentTime = (_a = this._playbackObserver.getCurrentTime()) !== null && _a !== void 0 ? _a : this._playbackObserver.getReference().getValue().position.getPolled();
var isSeeking = lastObservation.seeking !== 0 /* SeekingState.None */;
return { currentTime: currentTime, isSeeking: isSeeking };
};
/**
* Examine playback situation from playback observations to emit stream events and
* prepare `onExit` callbacks if needed.
* @param {Array.<Object>} scheduledEvents
* @param {Object} oldObservation
* @param {Object} newObservation
* @param {Object} stopSignal
*/
StreamEventsEmitter.prototype._emitStreamEvents = function (scheduledEvents, oldObservation, newObservation, stopSignal) {
var e_1, _a, e_2, _b;
var previousTime = oldObservation.currentTime;
var isSeeking = newObservation.isSeeking, currentTime = newObservation.currentTime;
var eventsToSend = [];
var eventsToExit = [];
for (var i = 0; i < scheduledEvents.length; i++) {
var event_1 = scheduledEvents[i];
var start = event_1.start;
var end = isFiniteStreamEvent(event_1) ? event_1.end : undefined;
var isBeingPlayed = this._eventsBeingPlayed.has(event_1);
if (isBeingPlayed) {
if (start > currentTime || (end !== undefined && currentTime >= end)) {
if (isFiniteStreamEvent(event_1)) {
eventsToExit.push(event_1.publicEvent);
}
this._eventsBeingPlayed.delete(event_1);
}
}
else if (start <= currentTime && end !== undefined && currentTime < end) {
eventsToSend.push({ type: "stream-event", value: event_1.publicEvent });
this._eventsBeingPlayed.set(event_1, true);
}
else if (previousTime < start && currentTime >= (end !== null && end !== void 0 ? end : start)) {
if (isSeeking) {
eventsToSend.push({
type: "stream-event-skip",
value: event_1.publicEvent,
});
}
else {
eventsToSend.push({ type: "stream-event", value: event_1.publicEvent });
if (isFiniteStreamEvent(event_1)) {
eventsToExit.push(event_1.publicEvent);
}
}
}
}
if (eventsToSend.length > 0) {
try {
for (var eventsToSend_1 = __values(eventsToSend), eventsToSend_1_1 = eventsToSend_1.next(); !eventsToSend_1_1.done; eventsToSend_1_1 = eventsToSend_1.next()) {
var event_2 = eventsToSend_1_1.value;
if (event_2.type === "stream-event") {
this.trigger("event", event_2.value);
}
else {
this.trigger("eventSkip", event_2.value);
}
if (stopSignal.isCancelled()) {
return;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (eventsToSend_1_1 && !eventsToSend_1_1.done && (_a = eventsToSend_1.return)) _a.call(eventsToSend_1);
}
finally { if (e_1) throw e_1.error; }
}
}
if (eventsToExit.length > 0) {
try {
for (var eventsToExit_1 = __values(eventsToExit), eventsToExit_1_1 = eventsToExit_1.next(); !eventsToExit_1_1.done; eventsToExit_1_1 = eventsToExit_1.next()) {
var event_3 = eventsToExit_1_1.value;
if (typeof event_3.onExit === "function") {
event_3.onExit();
}
if (stopSignal.isCancelled()) {
return;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (eventsToExit_1_1 && !eventsToExit_1_1.done && (_b = eventsToExit_1.return)) _b.call(eventsToExit_1);
}
finally { if (e_2) throw e_2.error; }
}
}
};
return StreamEventsEmitter;
}(event_emitter_1.default));
exports.default = StreamEventsEmitter;
/**
* Tells if a stream event has a duration
* @param {Object} evt
* @returns {Boolean}
*/
function isFiniteStreamEvent(evt) {
return evt.end !== undefined;
}