UNPKG

rx-player

Version:
971 lines (970 loc) 40.2 kB
"use strict"; 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 }); exports.DummyMediaElement = void 0; var event_listeners_1 = require("../../../compat/event_listeners"); var event_emitter_1 = require("../../../utils/event_emitter"); var monotonic_timestamp_1 = require("../../../utils/monotonic_timestamp"); var noop_1 = require("../../../utils/noop"); var ranges_1 = require("../../../utils/ranges"); var task_canceller_1 = require("../../../utils/task_canceller"); var eme_1 = require("./eme"); var mse_1 = require("./mse"); var utils_1 = require("./utils"); /** * Minimum amount of buffer remaining in front of the currently-played position * in seconds from which content playback happens. If we're below that value, * the `DummyMediaElement` will start rebuffering itself. */ var MINIMUM_BUFFER_SIZE_FOR_PLAYBACK = 0.1; /** * The maximum interval in milliseonds at which we re-check the current playback * conditions (the `currentTime`, the `readyState` if we're waiting for data * etc.) * Note that this logic also happens on specific events: new media is added, * removed, methods are called... */ var TICK_INTERVAL = 40; /** * `HTMLMediaElement` implementation that should be compatible to the * `RxPlayer`. * * This class will act as if it is a regular `HTMLMediaElement` playing media * provided through the linked MSE API mocks. * Properties will try to mimick what an actual `HTMLMediaElement` would return * but the content won't actually be decoded nor deciphered. * @class DummyMediaElement */ var DummyMediaElement = /** @class */ (function (_super) { __extends(DummyMediaElement, _super); function DummyMediaElement(opts) { if (opts === void 0) { opts = {}; } var _a, _b; var _this = _super.call(this) || this; _this.buffered = new utils_1.default(); _this.childNodes = []; _this.ended = false; _this.error = null; _this.isDummy = true; _this.mediaKeys = null; _this.nodeName = (_a = opts.nodeName) !== null && _a !== void 0 ? _a : "VIDEO"; _this.paused = true; _this.preload = "auto"; _this.readyState = 0; _this.seekable = new utils_1.default(); _this.seeking = false; _this.textTracks = []; _this._allowedToPlay = opts.allowedToPlay !== false; _this._attachedMediaSource = null; _this._autoplay = false; _this._canAutoPlay = true; _this._currentContentCanceller = null; _this._duration = NaN; _this._eventScheduler = new utils_1.EventScheduler(); _this._isFreezing = null; _this._lastPosition = { position: 0, timestamp: (0, monotonic_timestamp_1.default)(), }; _this._muted = false; _this._pendingPlayPromises = []; _this._playbackRate = 1; _this._src = ""; _this._volume = 1; _this._wasLoadedDataSentForCurrentContent = false; _this._wasPlayPerformedOnCurrentContent = false; _this.onencrypted = null; _this.oncanplay = null; _this.oncanplaythrough = null; _this.onended = null; _this.onerror = null; _this.onloadeddata = null; _this.onloadedmetadata = null; _this.onpause = null; _this.onplay = null; _this.onplaying = null; _this.onratechange = null; _this.onseeked = null; _this.onseeking = null; _this.onstalled = null; _this.ontimeupdate = null; _this.onenterpictureinpicture = null; _this.onleavepictureinpicture = null; _this.onvolumechange = null; _this.onwaiting = null; var setMediaKeys = function (mediaElement, mediaKeys) { return new Promise(function (resolve) { mediaElement.mediaKeys = mediaKeys; if (mediaElement === _this && mediaKeys instanceof eme_1.DummyMediaKeys) { mediaKeys.onDummySessionKeyUpdates = function () { _this._tick(); }; } resolve(undefined); _this._tick(); }); }; _this.FORCED_MEDIA_SOURCE = mse_1.DummyMediaSource; _this.FORCED_EME_API = { requestMediaKeySystemAccess: (0, eme_1.createRequestMediaKeySystemAccess)((_b = opts.drmOptions) === null || _b === void 0 ? void 0 : _b.requestMediaKeySystemAccessConfig), onEncrypted: (0, event_listeners_1.createCompatibleEventListener)(["encrypted"]), setMediaKeys: setMediaKeys, implementation: "standard", }; return _this; } Object.defineProperty(DummyMediaElement.prototype, "duration", { /** * `HTMLMediaElement.duration` property getter. */ get: function () { // TODO liveSeekableRange etc. return this._duration; }, enumerable: false, configurable: true }); Object.defineProperty(DummyMediaElement.prototype, "volume", { /** * `HTMLMediaElement.volume` property getter. */ get: function () { return this._volume; }, /** * `HTMLMediaElement.volume` property setter. */ set: function (newVolume) { this._volume = newVolume; this._eventScheduler.schedule(this, "volumechange", null).catch(noop_1.default); }, enumerable: false, configurable: true }); Object.defineProperty(DummyMediaElement.prototype, "muted", { /** * `HTMLMediaElement.muted` property getter. */ get: function () { return this._muted; }, /** * `HTMLMediaElement.muted` property setter. */ set: function (newMuted) { this._muted = newMuted; this._eventScheduler.schedule(this, "volumechange", null).catch(noop_1.default); }, enumerable: false, configurable: true }); Object.defineProperty(DummyMediaElement.prototype, "autoplay", { /** * `HTMLMediaElement.autoplay` property getter. */ get: function () { return this._autoplay; }, /** * `HTMLMediaElement.autoplay` property setter. * * A paused content may play if paused while autoplay is set to `true` and * if the current content was never played until now. * @param {boolean} val - New `autoplay` value. */ set: function (val) { this._autoplay = val; if (this._currentContentCanceller !== null && this.readyState >= 4 && !this._wasPlayPerformedOnCurrentContent) { this._tick(); this.paused = false; this._wasPlayPerformedOnCurrentContent = true; this._eventScheduler .schedule(this, "play", this._currentContentCanceller.signal) .catch(noop_1.default); this._notifyAboutPlaying(); } }, enumerable: false, configurable: true }); Object.defineProperty(DummyMediaElement.prototype, "src", { /** * `HTMLMediaElement.src` property getter. */ get: function () { return this._src; }, /** * `HTMLMediaElement.src` property setter. * * For now, we assume that on a `DummyMediaElement` it is only useful to play * "directfile" contents which we don't support for now. * @param {string} val - The new URL */ set: function (val) { var _this = this; var _a; this._src = val; this.srcObject = null; (_a = this._currentContentCanceller) === null || _a === void 0 ? void 0 : _a.cancel(); var canceller = new task_canceller_1.default(); this._currentContentCanceller = canceller; setTimeout(function () { var _a; while (_this._pendingPlayPromises.length > 0) { var error = new Error("A new source was set"); error.name = "AbortError"; (_a = _this._pendingPlayPromises.shift()) === null || _a === void 0 ? void 0 : _a.reject(error); } var err = createMediaError("Failed to open media", 4); _this.error = err; _this._eventScheduler.schedule(_this, "error", canceller.signal).catch(noop_1.default); }); }, enumerable: false, configurable: true }); /** * An `HTMLMediaElement`'s `addTextTrack` method. * Here I did not want to implement that complexity for now as we don't really * need it at the time I'm writing this. So it just throws. */ DummyMediaElement.prototype.addTextTrack = function () { throw new Error("Not implemented yet"); }; Object.defineProperty(DummyMediaElement.prototype, "srcObject", { get: function () { return this._attachedMediaSource; }, /** * `HTMLMediaElement.srcObject` property setter, * * Right now, that the main way to attach a `DummyMediaSource` to a * `DummyMediaElement`. * @param {Object|null} val - The `DummyMediaSource` wanted or `null` to stop * playback. */ set: function (val) { // media element load algorithm var _this = this; var _a, _b; (_a = this._currentContentCanceller) === null || _a === void 0 ? void 0 : _a.cancel(); this._wasLoadedDataSentForCurrentContent = false; this._wasPlayPerformedOnCurrentContent = false; this.buffered = new utils_1.default(); this.seekable = new utils_1.default(); this._duration = NaN; this.error = null; this._canAutoPlay = true; this.seeking = false; this.readyState = 0; this.paused = true; this._isFreezing = null; this._lastPosition = { position: 0, timestamp: (0, monotonic_timestamp_1.default)(), }; this.ended = false; this.playbackRate = 1; while (this._pendingPlayPromises.length > 0) { var error = new Error("A new source was set"); error.name = "AbortError"; (_b = this._pendingPlayPromises.shift()) === null || _b === void 0 ? void 0 : _b.reject(error); } var prev = this._attachedMediaSource; prev === null || prev === void 0 ? void 0 : prev.destroy(); if (val !== null) { if (!(val instanceof mse_1.DummyMediaSource) && val !== null) { this._attachedMediaSource = null; throw new Error("A DummyMediaElement can only be linked to a DummyMediaSource"); } this._attachedMediaSource = val; this._currentContentCanceller = new task_canceller_1.default(); var intervalId_1 = setInterval(function () { _this._tick(); }, TICK_INTERVAL); this._currentContentCanceller.signal.register(function () { clearInterval(intervalId_1); }); this._attachCurrentMediaSource(); } else { this._attachedMediaSource = null; } }, enumerable: false, configurable: true }); /** * EME's `HTMLMediaElement.setMediaKeys` method. * Here we go through the `FORCED_EME_API` property instead, so that method * just throws. */ DummyMediaElement.prototype.setMediaKeys = function (_mk) { return Promise.reject("EME not implemented on dummy media element."); }; Object.defineProperty(DummyMediaElement.prototype, "currentTime", { /** * `HTMLMediaElement.currentTime` property getter. */ get: function () { return this._tick(); }, /** * `HTMLMediaElement.currentTime` property setter. */ set: function (val) { this._tick(); var prevPosition = this._lastPosition.position; var canceller = this._currentContentCanceller; if (canceller === null || this.readyState === 0) { return; } var seekingPos = val; this.seeking = true; if (this._isFreezing !== null && this._isFreezing.resolvesOnSeek) { this._isFreezing = null; } if (seekingPos > this._duration) { seekingPos = this._duration; } // From the WHATWG spec: // If the playback position is not in one of the ranges given in the // seekable attribute, then let it be the position in one of the ranges // given in the seekable attribute that is the nearest to the new playback // position. /** * Offset to add to `seekingPos` so it is contianed in a seekable range. * `null` if no seekable range is found for now. */ var currentBestOffset = null; for (var i = 0; i < this.seekable.length; i++) { if (seekingPos >= this.seekable.start(i) && seekingPos <= this.seekable.end(i)) { currentBestOffset = 0; break; } else { var distance = void 0; if (seekingPos < this.seekable.start(i)) { distance = this.seekable.start(i) - seekingPos; } else { distance = this.seekable.end(i) - seekingPos; } if (currentBestOffset === null) { currentBestOffset = distance; } else if (Math.abs(distance) < Math.abs(currentBestOffset)) { currentBestOffset = distance; } else { // From the WHATWG spec: // If two positions both satisfy that constraint (i.e. the new playback // position is exactly in the middle between two ranges in the seekable // attribute) then use the position that is closest to the current // playback position. var prevCandidatePosition = currentBestOffset + seekingPos; var newCandidatePosition = distance + seekingPos; if (Math.abs(prevPosition - newCandidatePosition) < Math.abs(prevPosition - prevCandidatePosition)) { currentBestOffset = distance; } } } } if (currentBestOffset === null) { this.seeking = false; return; } if (currentBestOffset !== 0) { seekingPos += currentBestOffset; } this._lastPosition.position = val; this._lastPosition.timestamp = (0, monotonic_timestamp_1.default)(); this.seeking = true; this._eventScheduler.schedule(this, "seeking", canceller.signal).catch(noop_1.default); this._tick(); }, enumerable: false, configurable: true }); Object.defineProperty(DummyMediaElement.prototype, "playbackRate", { /** * HTMLMediaElement.playbackRate property getter. */ get: function () { return this._playbackRate; }, /** * HTMLMediaElement.playbackRate property setter. */ set: function (val) { this._tick(); this._playbackRate = val; this._eventScheduler.schedule(this, "ratechange", null).catch(noop_1.default); }, enumerable: false, configurable: true }); /** * HTMLMediaElement.pause() method. */ DummyMediaElement.prototype.play = function () { var _this = this; var _a; if (!this._allowedToPlay) { var error = new Error("Dummy media element cannot play"); error.name = "NotAllowedError"; return Promise.reject(error); } if (((_a = this.error) === null || _a === void 0 ? void 0 : _a.code) === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) { var error = new Error("`play` call on not supported content"); error.name = "NotSupportedError"; return Promise.reject(error); } var promise = new Promise(function (res, rej) { _this._pendingPlayPromises.push({ resolve: res, reject: rej }); }); if (this.ended && this.playbackRate >= 0) { this.ended = false; this._lastPosition.position = this.seekable.length > 0 ? this.seekable.start(0) : this._lastPosition.position; this._lastPosition.timestamp = (0, monotonic_timestamp_1.default)(); } if (this._currentContentCanceller !== null && this.paused) { this._tick(); this.paused = false; this._wasPlayPerformedOnCurrentContent = true; // run the time marches on steps this._eventScheduler .schedule(this, "play", this._currentContentCanceller.signal) .catch(noop_1.default); if (this.readyState <= 2) { this._eventScheduler .schedule(this, "waiting", this._currentContentCanceller.signal) .catch(noop_1.default); } else { this._notifyAboutPlaying(); } } else if (this._currentContentCanceller !== null && this.readyState >= 3) { // take pending play promises and queue a media element task given the // media element to resolve pending play promises with the result. while (this._pendingPlayPromises.length > 0) { var playPromise = this._pendingPlayPromises.shift(); playPromise === null || playPromise === void 0 ? void 0 : playPromise.resolve(); } } this._canAutoPlay = false; return promise; }; /** * HTMLMediaElement.pause() method. */ DummyMediaElement.prototype.pause = function () { this._internalPauseSteps(); }; /** * An `Element`'s `removeAttribute` method. * Here I did not want to implement that complexity for now as we don't really * need it at the time I'm writing this beside removing the `"src"` attribute. * So I just allow to do that * @param {string} attr */ DummyMediaElement.prototype.removeAttribute = function (attr) { if (attr === "src") { this.src = ""; } else { throw new Error('Removing the attribute + "' + String(attr) + '" is not yet supported on a `DummyMediaElement`.'); } return; }; /** * A `Node`'s `hasChildNodes` method. * Here I did not want to implement that complexity for now as we don't really * need it at the time I'm writing this. So it just returns false. * @returns {boolean} */ DummyMediaElement.prototype.hasChildNodes = function () { return false; }; /** * A `Node`'s `appendChild` method. * Here I did not want to implement that complexity for now as we don't really * need it at the time I'm writing this. So it just throws. * @param {Node} _child */ DummyMediaElement.prototype.appendChild = function (_child) { throw new Error("Unimplemented"); }; /** * A `Node`'s `removeChild` method. * Here I did not want to implement that complexity for now as we don't really * need it at the time I'm writing this. So it just throws. * @param {*} x */ DummyMediaElement.prototype.removeChild = function (x) { if (x === null) { throw new TypeError("Asked to remove null child"); } var notFoundErr = new Error("DummyMediaElement has no child"); notFoundErr.name = "NotFoundError"; throw notFoundErr; }; /** * An added method to force a "freezing" occurence: playback will stall, even * if there's decodable and decipherable data in the buffer. * * Playback will stop freezing once you call the `stopFreezing` method. * @param {boolean} resolvesOnSeek - If `true` the freeze occurence will * disappear once a seek is performed. */ DummyMediaElement.prototype.startFreezing = function (resolvesOnSeek) { this._tick(); this._isFreezing = { resolvesOnSeek: resolvesOnSeek, }; }; /** * Stop "freezing" occurence started with the `startFreezing` method. */ DummyMediaElement.prototype.stopFreezing = function () { this._tick(); this._isFreezing = null; }; /** * Method to call once playback reaches the end of the content. * Will send the right events and performs the right steps at that point. */ DummyMediaElement.prototype._onPlayingEndOfContent = function () { var _a, _b; if (((_a = this._attachedMediaSource) === null || _a === void 0 ? void 0 : _a.readyState) !== "ended") { return; } if (this.ended) { return; } // TODO: loop attribute? var canceller = this._currentContentCanceller; if (this.playbackRate < 0 || canceller === null) { return; } this._eventScheduler.schedule(this, "timeupdate", canceller.signal).catch(noop_1.default); if (!this.paused) { this.paused = true; this._eventScheduler.schedule(this, "pause", canceller.signal).catch(noop_1.default); } while (this._pendingPlayPromises.length > 0) { var error = new Error("The content has ended"); error.name = "AbortError"; (_b = this._pendingPlayPromises.shift()) === null || _b === void 0 ? void 0 : _b.reject(error); } this.ended = true; this._eventScheduler.schedule(this, "ended", canceller.signal).catch(noop_1.default); }; /** * Method corresponding to the WHATWG's "is eligible for autoplay" logic. * @returns {boolean} */ DummyMediaElement.prototype._isEligibleForAutoplay = function () { return this._canAutoPlay && this.paused && this.autoplay; }; /** * Performs steps on the `_attachedMediaSource` that have to happen on it when * it is attached to the `HTMLMediaElement`. */ DummyMediaElement.prototype._attachCurrentMediaSource = function () { var _this = this; var dummyMs = this._attachedMediaSource; if (dummyMs === null) { return; } dummyMs.updateCallbacks({ hasMediaElementErrored: function () { return _this.error !== null; }, onBufferedUpdate: function () { _this._tick(); }, updateMediaElementDuration: function (duration) { // TODO check _this._duration = duration; if (_this._lastPosition.position > _this._duration) { _this._lastPosition.position = _this._duration; _this._lastPosition.timestamp = (0, monotonic_timestamp_1.default)(); } }, }); dummyMs.readyState = "open"; dummyMs.eventScheduler.schedule(dummyMs, "sourceopen", null).catch(noop_1.default); }; /** * Performs the WHATWG "notify about playing" steps. */ DummyMediaElement.prototype._notifyAboutPlaying = function () { var _a; this._eventScheduler .schedule(this, "playing", (_a = this._currentContentCanceller) === null || _a === void 0 ? void 0 : _a.signal) .catch(noop_1.default); while (this._pendingPlayPromises.length > 0) { var playPromise = this._pendingPlayPromises.shift(); playPromise === null || playPromise === void 0 ? void 0 : playPromise.resolve(); } }; /** * Performs the WHATWG "internal pause steps". */ DummyMediaElement.prototype._internalPauseSteps = function () { var _a, _b, _c; // "Set the media element's can autoplay flag to false." this._canAutoPlay = false; if (!this.paused) { this._tick(); this.paused = true; // "Change the value of paused to true." this._eventScheduler .schedule(this, "timeupdate", (_a = this._currentContentCanceller) === null || _a === void 0 ? void 0 : _a.signal) .catch(noop_1.default); this._eventScheduler .schedule(this, "pause", (_b = this._currentContentCanceller) === null || _b === void 0 ? void 0 : _b.signal) .catch(noop_1.default); while (this._pendingPlayPromises.length > 0) { var error = new Error("The content was paused"); error.name = "AbortError"; (_c = this._pendingPlayPromises.shift()) === null || _c === void 0 ? void 0 : _c.reject(error); } } }; /** * Method re-checking the current position to see if we should begin * rebuffering, ending the content, changing the `readyState` etc. * * Should be called at regular intervals and everytime one of the property * that has an effect on the playback speed (`paused`, `playbackRate` etc.) * will change just before it changes (so the `lastPosition` object is * updated accordingly). * * Returns the new calculated `currentTime` property. * * @returns {number} - The new `currentTime` property. */ DummyMediaElement.prototype._tick = function () { this._updateBufferedRanges(); var bufferInfo = this._getCurrentBufferHealth(); if (this._attachedMediaSource !== null && this._lastPosition.position >= this._attachedMediaSource.duration) { this._lastPosition.position = this._attachedMediaSource.duration; this._lastPosition.timestamp = (0, monotonic_timestamp_1.default)(); this._updateReadyState({ isMissingMetadata: bufferInfo.isMissingMetadata, isMissingKey: bufferInfo.isMissingKey, }); this._onPlayingEndOfContent(); return this._lastPosition.position; } if (bufferInfo.range === null) { this._updateReadyState({ isMissingMetadata: bufferInfo.isMissingMetadata, isMissingKey: bufferInfo.isMissingKey, }); this._lastPosition.timestamp = (0, monotonic_timestamp_1.default)(); return this._lastPosition.position; } var playbackSpeed = bufferInfo.isMissingKey || bufferInfo.isMissingMetadata || this._isFreezing !== null || this.paused || this.ended || this.readyState < 3 || this.playbackRate <= 0 ? 0 : this.playbackRate; var now = (0, monotonic_timestamp_1.default)(); var elapsedTime = now - this._lastPosition.timestamp; var newPosition = this._lastPosition.position + (elapsedTime * playbackSpeed) / 1000; if (newPosition > bufferInfo.range.end) { newPosition = bufferInfo.range.end; } this._lastPosition.position = newPosition; this._lastPosition.timestamp = now; this._updateReadyState({ isMissingMetadata: bufferInfo.isMissingMetadata, isMissingKey: bufferInfo.isMissingKey, }); if (this._attachedMediaSource !== null && this._lastPosition.position >= this._attachedMediaSource.duration) { this._onPlayingEndOfContent(); } return this._lastPosition.position; }; /** * Check if the current `readyState` property is the right one according to * the `DummyMediaElement`'s state. * If not update it and send the right events. * @param {Object} obj * @param {boolean} obj.isMissingMetadata - If `true`, at least one active * buffer doesn't have enough metadata for the `HAVE_METADATA` `readyState` * yet. * @param {boolean} obj.isMissingKey - If `true`, at least one active * buffer is missing the decryption key for the media at the current * position. */ DummyMediaElement.prototype._updateReadyState = function (_a) { var _this = this; var isMissingMetadata = _a.isMissingMetadata, isMissingKey = _a.isMissingKey; var canceller = this._currentContentCanceller; if (this.readyState === 0) { if (canceller === null || isMissingMetadata) { return; } this.readyState = 1; this.seekable.insert(0, Infinity, null); this._eventScheduler.schedule(this, "loadedmetadata", canceller.signal).catch(noop_1.default); } var currentRange = this.buffered.getRangeFor(this._lastPosition.position); if (this.readyState === 1) { if (isMissingMetadata) { this.readyState = 0; return; } if (currentRange === null || isMissingKey || ((this._attachedMediaSource === null || currentRange.end < this._attachedMediaSource.duration) && currentRange.end - this._lastPosition.position < MINIMUM_BUFFER_SIZE_FOR_PLAYBACK)) { return; } if (canceller === null) { return; } this.readyState = 3; var loadedDataProm = this._wasLoadedDataSentForCurrentContent ? Promise.resolve() : this._eventScheduler.schedule(this, "loadeddata", canceller.signal); this._wasLoadedDataSentForCurrentContent = true; loadedDataProm .then(function () { return _this._eventScheduler.schedule(_this, "canplay", canceller.signal); }) .then(function () { _this.readyState = 4; if (!_this.paused) { _this._notifyAboutPlaying(); } else if (_this._isEligibleForAutoplay()) { _this._tick(); _this.paused = false; _this._wasPlayPerformedOnCurrentContent = true; _this._eventScheduler.schedule(_this, "play", canceller.signal).catch(noop_1.default); _this._notifyAboutPlaying(); } if (_this.seeking) { _this.seeking = false; _this._eventScheduler .schedule(_this, "timeupdate", canceller.signal) .catch(noop_1.default); _this._eventScheduler.schedule(_this, "seeked", canceller.signal).catch(noop_1.default); } return _this._eventScheduler.schedule(_this, "canplaythrough", canceller.signal); }) .catch(noop_1.default); } else if (this.readyState > 1) { if (isMissingMetadata) { this.readyState = 0; return; } if (currentRange === null || isMissingKey || ((this._attachedMediaSource === null || this._attachedMediaSource.readyState !== "ended" || currentRange.end < this._attachedMediaSource.duration) && currentRange.end - this._lastPosition.position < MINIMUM_BUFFER_SIZE_FOR_PLAYBACK)) { if (canceller === null) { return; } this.readyState = 1; if (!this.paused && this.error === null) { this._eventScheduler .schedule(this, "timeupdate", canceller.signal) .then(function () { return _this._eventScheduler.schedule(_this, "waiting", canceller.signal); }) .catch(noop_1.default); } } if (this.seeking) { this.seeking = false; if (canceller !== null) { this._eventScheduler.schedule(this, "timeupdate", canceller.signal).catch(noop_1.default); this._eventScheduler.schedule(this, "seeked", canceller.signal).catch(noop_1.default); } } } }; /** * Update `buffered` HTML5 property based on what MSE sourceBuffers have * themselves buffered. */ DummyMediaElement.prototype._updateBufferedRanges = function () { var e_1, _a; var _this = this; var _b; if (this._attachedMediaSource === null) { this.buffered.remove(0, Infinity); return; } var allBuffered = (_b = this._attachedMediaSource.sourceBuffers.reduce(function (acc, sb) { var e_2, _a; var _b; if (acc === null) { return (0, ranges_1.convertToRanges)(sb.buffered); } if (((_b = _this._attachedMediaSource) === null || _b === void 0 ? void 0 : _b.readyState) === "ended") { var newRanges = (0, ranges_1.convertToRanges)(sb.buffered); try { for (var newRanges_1 = __values(newRanges), newRanges_1_1 = newRanges_1.next(); !newRanges_1_1.done; newRanges_1_1 = newRanges_1.next()) { var newRange = newRanges_1_1.value; (0, ranges_1.insertInto)(acc, newRange); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (newRanges_1_1 && !newRanges_1_1.done && (_a = newRanges_1.return)) _a.call(newRanges_1); } finally { if (e_2) throw e_2.error; } } return acc; } return (0, ranges_1.keepRangeIntersection)(acc, (0, ranges_1.convertToRanges)(sb.buffered)); }, null)) !== null && _b !== void 0 ? _b : []; this.buffered = new utils_1.default(); try { for (var allBuffered_1 = __values(allBuffered), allBuffered_1_1 = allBuffered_1.next(); !allBuffered_1_1.done; allBuffered_1_1 = allBuffered_1.next()) { var newRange = allBuffered_1_1.value; this.buffered.insert(newRange.start, newRange.end, null); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (allBuffered_1_1 && !allBuffered_1_1.done && (_a = allBuffered_1.return)) _a.call(allBuffered_1); } finally { if (e_1) throw e_1.error; } } if (this.buffered.length > 0) { var bufferEnd = this.buffered.end(this.buffered.length - 1); if (bufferEnd > this.duration) { this._duration = bufferEnd; } } }; /** * Get key information on the media buffers linked to this media element. * @returns {Object} */ DummyMediaElement.prototype._getCurrentBufferHealth = function () { var _this = this; var _a; if (this._attachedMediaSource === null) { this.buffered.remove(0, Infinity); this.readyState = 0; return { range: null, isMissingMetadata: true, isMissingKey: false, }; } var isMissingMetadata = this._attachedMediaSource.sourceBuffers.some(function (sb) { return !sb.hasMetadata; }); var isMissingKey = this._attachedMediaSource.sourceBuffers.some(function (sb) { var metadata = sb.buffered.getMetadataFor(_this._lastPosition.position); if (metadata === null || metadata.keyIds === null) { return false; } if (_this.mediaKeys === null) { return true; } var dummySessions = _this.mediaKeys.dummySessions; return metadata.keyIds.some(function (k) { return !dummySessions.some(function (s) { var e_3, _a; var keyMap = s.keyStatuses.getInnerMap(); try { for (var _b = __values(keyMap.keys()), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; if (key === k) { var val = keyMap.get(key); return (val === null || val === void 0 ? void 0 : val.status) === "usable"; } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_3) throw e_3.error; } } return false; }); }); }); return { range: (_a = this.buffered.getRangeFor(this._lastPosition.position)) !== null && _a !== void 0 ? _a : null, isMissingMetadata: isMissingMetadata, isMissingKey: isMissingKey, }; }; return DummyMediaElement; }(event_emitter_1.default)); exports.DummyMediaElement = DummyMediaElement; /** * Create Object respecting the HTMLs `MediaError` interface with the given code * and error message. * @param {string} msg * @param {number} code * @returns {Object} */ function createMediaError(msg, code) { var err = new Error(msg); err.name = "MediaError"; err.code = code; err.MEDIA_ERR_ABORTED = 1; err.MEDIA_ERR_NETWORK = 2; err.MEDIA_ERR_DECODE = 3; err.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; return err; }