UNPKG

rx-player

Version:
438 lines (437 loc) 17.4 kB
"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 }); exports.onTextTrackRemoved = exports.onTextTrackAdded = exports.onSourceBufferUpdate = exports.onTimeUpdate = exports.onSourceOpen = exports.onSourceEnded = exports.onSourceClose = exports.onSeeking = exports.onSeeked = exports.onRemoveSourceBuffers = exports.onLoadedMetadata = exports.onKeyStatusesChange = exports.onKeyMessage = exports.onKeyError = exports.onKeyAdded = exports.onEnded = void 0; exports.addEventListener = addEventListener; exports.createCompatibleEventListener = createCompatibleEventListener; exports.getPictureOnPictureStateRef = getPictureOnPictureStateRef; exports.getVideoVisibilityRef = getVideoVisibilityRef; exports.getElementResolutionRef = getElementResolutionRef; exports.getScreenResolutionRef = getScreenResolutionRef; var config_1 = require("../config"); var log_1 = require("../log"); var global_scope_1 = require("../utils/global_scope"); var is_non_empty_string_1 = require("../utils/is_non_empty_string"); var is_null_or_undefined_1 = require("../utils/is_null_or_undefined"); var noop_1 = require("../utils/noop"); var reference_1 = require("../utils/reference"); var BROWSER_PREFIXES = ["", "webkit", "moz", "ms"]; /** * Find the first supported event from the list given. * @param {HTMLElement} element * @param {string} eventNameSuffix * @returns {Boolean} */ function isEventSupported(element, eventNameSuffix) { var clone = document.createElement(element.tagName); var eventName = "on" + eventNameSuffix; if (eventName in clone) { return true; } else { clone.setAttribute(eventName, "return;"); return (typeof clone[eventName] === "function"); } } /** * Find the first supported event from the list given. * @param {HTMLElement} element * @param {Array.<string>} eventNames * @returns {string|undefined} */ function findSupportedEvent(element, eventNames) { return eventNames.filter(function (name) { return isEventSupported(element, name); })[0]; } /** * @param {Array.<string>} eventNames * @param {Array.<string>|undefined} prefixes * @returns {Array.<string>} */ function eventPrefixed(eventNames, prefixes) { return eventNames.reduce(function (parent, name) { return parent.concat((prefixes === undefined ? BROWSER_PREFIXES : prefixes).map(function (p) { return p + name; })); }, []); } function createCompatibleEventListener(eventNames, prefixes) { var mem; var prefixedEvents = eventPrefixed(eventNames, prefixes); return function (element, listener, cancelSignal) { if (cancelSignal.isCancelled()) { return; } // if the element is a HTMLElement we can detect // the supported event, and memoize it in `mem` if (typeof HTMLElement !== "undefined" && element instanceof HTMLElement) { if (typeof mem === "undefined") { mem = findSupportedEvent(element, prefixedEvents); } if ((0, is_non_empty_string_1.default)(mem)) { element.addEventListener(mem, listener); cancelSignal.register(function () { if (mem !== undefined) { element.removeEventListener(mem, listener); } }); } else { log_1.default.warn("utils", "element ".concat(element.tagName) + " does not support any of these events: " + prefixedEvents.join(", ")); return; } } prefixedEvents.forEach(function (eventName) { var hasSetOnFn = false; if (typeof element.addEventListener === "function") { element.addEventListener(eventName, listener); } else { hasSetOnFn = true; element[("on" + eventName)] = listener; } cancelSignal.register(function () { if (typeof element.removeEventListener === "function") { element.removeEventListener(eventName, listener); } if (hasSetOnFn) { delete element[("on" + eventName)]; } }); }); }; } /** * Returns a reference: * - set to `true` when the document is visible * - set to `false` when the document is hidden * @param {Object} stopListening - `CancellationSignal` allowing to free the * ressources allocated to update this value. * @returns {Object} */ function getDocumentVisibilityRef(stopListening) { var prefix; var doc = document; if (!(0, is_null_or_undefined_1.default)(doc.hidden)) { prefix = ""; } else if (!(0, is_null_or_undefined_1.default)(doc.mozHidden)) { prefix = "moz"; } else if (!(0, is_null_or_undefined_1.default)(doc.msHidden)) { prefix = "ms"; } else if (!(0, is_null_or_undefined_1.default)(doc.webkitHidden)) { prefix = "webkit"; } var hidden = (0, is_non_empty_string_1.default)(prefix) ? (prefix + "Hidden") : "hidden"; var visibilityChangeEvent = (0, is_non_empty_string_1.default)(prefix) ? prefix + "visibilitychange" : "visibilitychange"; var isHidden = document[hidden]; var ref = new reference_1.default(!isHidden, stopListening); addEventListener(document, visibilityChangeEvent, function () { var isVisible = !document[hidden]; ref.setValueIfChanged(isVisible); }, stopListening); return ref; } /** * Emit when video enters and leaves Picture-In-Picture mode. * @param {HTMLMediaElement} mediaElement * @param {Object} stopListening * @returns {Object} */ function getPictureOnPictureStateRef(mediaElement, stopListening) { if (mediaElement.webkitSupportsPresentationMode === true && typeof mediaElement.webkitSetPresentationMode === "function") { var isWebKitPIPEnabled = mediaElement.webkitPresentationMode === "picture-in-picture"; var ref_1 = new reference_1.default({ isEnabled: isWebKitPIPEnabled, pipWindow: null, }, stopListening); addEventListener(mediaElement, "webkitpresentationmodechanged", function () { var isEnabled = mediaElement.webkitPresentationMode === "picture-in-picture"; ref_1.setValue({ isEnabled: isEnabled, pipWindow: null }); }, stopListening); return ref_1; } var isPIPEnabled = document.pictureInPictureElement === mediaElement; var ref = new reference_1.default({ isEnabled: isPIPEnabled, pipWindow: null }, stopListening); addEventListener(mediaElement, "enterpictureinpicture", function (evt) { var _a; ref.setValue({ isEnabled: true, pipWindow: (_a = evt.pictureInPictureWindow) !== null && _a !== void 0 ? _a : null, }); }, stopListening); addEventListener(mediaElement, "leavepictureinpicture", function () { ref.setValue({ isEnabled: false, pipWindow: null }); }, stopListening); return ref; } /** * Returns a reference: * - Set to `true` when video is considered as visible (the page is visible * and/or the Picture-In-Picture is activated). * - Set to `false` otherwise. * @param {Object} pipStatus * @param {Object} stopListening - `CancellationSignal` allowing to free the * resources reserved to listen to video visibility change. * @returns {Object} */ function getVideoVisibilityRef(pipStatus, stopListening) { var isDocVisibleRef = getDocumentVisibilityRef(stopListening); var currentTimeout; var ref = new reference_1.default(true, stopListening); stopListening.register(function () { clearTimeout(currentTimeout); currentTimeout = undefined; }); isDocVisibleRef.onUpdate(checkCurrentVisibility, { clearSignal: stopListening, }); pipStatus.onUpdate(checkCurrentVisibility, { clearSignal: stopListening }); checkCurrentVisibility(); return ref; function checkCurrentVisibility() { clearTimeout(currentTimeout); currentTimeout = undefined; if (pipStatus.getValue().isEnabled || isDocVisibleRef.getValue()) { ref.setValueIfChanged(true); } else { var INACTIVITY_DELAY = config_1.default.getCurrent().INACTIVITY_DELAY; currentTimeout = setTimeout(function () { ref.setValueIfChanged(false); }, INACTIVITY_DELAY); } } } /** * Get video width and height from the screen dimensions. * @param {Object} stopListening * @returns {Object} */ function getScreenResolutionRef(stopListening) { var _a, _b; var pixelRatio = (0, is_null_or_undefined_1.default)(global_scope_1.default.devicePixelRatio) || global_scope_1.default.devicePixelRatio === 0 ? 1 : global_scope_1.default.devicePixelRatio; var ref = new reference_1.default({ width: (_a = global_scope_1.default.screen) === null || _a === void 0 ? void 0 : _a.width, height: (_b = global_scope_1.default.screen) === null || _b === void 0 ? void 0 : _b.height, pixelRatio: pixelRatio, }, stopListening); var interval = setInterval(checkScreenResolution, 20000); stopListening.register(function stopUpdating() { clearInterval(interval); }); return ref; function checkScreenResolution() { var oldVal = ref.getValue(); if (oldVal.width !== screen.width || oldVal.height !== screen.height || oldVal.pixelRatio !== pixelRatio) { ref.setValue({ width: screen.width, height: screen.height, pixelRatio: pixelRatio }); } } } /** * Get video width and height from HTML media element, or video estimated * dimensions when Picture-in-Picture is activated. * @param {HTMLMediaElement} mediaElement * @param {Object} pipStatusRef * @param {Object} stopListening * @returns {Object} */ function getElementResolutionRef(mediaElement, pipStatusRef, stopListening) { var pixelRatio = (0, is_null_or_undefined_1.default)(global_scope_1.default.devicePixelRatio) || global_scope_1.default.devicePixelRatio === 0 ? 1 : global_scope_1.default.devicePixelRatio; var ref = new reference_1.default({ width: mediaElement.clientWidth, height: mediaElement.clientHeight, pixelRatio: pixelRatio, }, stopListening); var clearPreviousEventListener = noop_1.default; pipStatusRef.onUpdate(checkElementResolution, { clearSignal: stopListening }); addEventListener(global_scope_1.default, "resize", checkElementResolution, stopListening); addEventListener(mediaElement, "enterpictureinpicture", checkElementResolution, stopListening); addEventListener(mediaElement, "leavepictureinpicture", checkElementResolution, stopListening); var interval = setInterval(checkElementResolution, 20000); checkElementResolution(); stopListening.register(function stopUpdating() { clearPreviousEventListener(); clearInterval(interval); }); return ref; function checkElementResolution() { clearPreviousEventListener(); var pipStatus = pipStatusRef.getValue(); var pipWindow = pipStatus.pipWindow; if (!pipStatus.isEnabled) { var oldVal = ref.getValue(); if (oldVal.width !== mediaElement.clientWidth || oldVal.height !== mediaElement.clientHeight || oldVal.pixelRatio !== pixelRatio) { ref.setValue({ width: mediaElement.clientWidth, height: mediaElement.clientHeight, pixelRatio: pixelRatio, }); } } else if (!(0, is_null_or_undefined_1.default)(pipWindow)) { var onPipResize_1 = function () { updateToPipWindowResolution(); }; pipWindow.addEventListener("resize", onPipResize_1); clearPreviousEventListener = function () { pipWindow.removeEventListener("resize", onPipResize_1); clearPreviousEventListener = noop_1.default; }; updateToPipWindowResolution(); } else { var oldVal = ref.getValue(); if (oldVal.width !== undefined || oldVal.height !== undefined || oldVal.pixelRatio !== pixelRatio) { ref.setValue({ width: undefined, height: undefined, pixelRatio: pixelRatio }); } } function updateToPipWindowResolution() { var oldVal = ref.getValue(); if (oldVal.width !== (pipWindow === null || pipWindow === void 0 ? void 0 : pipWindow.width) || oldVal.height !== (pipWindow === null || pipWindow === void 0 ? void 0 : pipWindow.height) || oldVal.pixelRatio !== pixelRatio) { ref.setValue({ width: pipWindow === null || pipWindow === void 0 ? void 0 : pipWindow.width, height: pipWindow === null || pipWindow === void 0 ? void 0 : pipWindow.height, pixelRatio: pixelRatio, }); } } } } /** * @param {HTMLMediaElement} mediaElement */ var onLoadedMetadata = createCompatibleEventListener(["loadedmetadata"]); exports.onLoadedMetadata = onLoadedMetadata; /** * @param {HTMLMediaElement} mediaElement */ var onTimeUpdate = createCompatibleEventListener(["timeupdate"]); exports.onTimeUpdate = onTimeUpdate; /** * @param {TextTrackList} mediaElement */ var onTextTrackAdded = createCompatibleEventListener(["addtrack"]); exports.onTextTrackAdded = onTextTrackAdded; /** * @param {TextTrackList} textTrackList */ var onTextTrackRemoved = createCompatibleEventListener(["removetrack"]); exports.onTextTrackRemoved = onTextTrackRemoved; /** * @param {MediaSource} mediaSource * @param {Function} listener * @param {Object} cancelSignal */ var onSourceOpen = createCompatibleEventListener(["sourceopen", "webkitsourceopen"]); exports.onSourceOpen = onSourceOpen; /** * @param {MediaSource} mediaSource * @param {Function} listener * @param {Object} cancelSignal */ var onSourceClose = createCompatibleEventListener(["sourceclose", "webkitsourceclose"]); exports.onSourceClose = onSourceClose; /** * @param {MediaSource} mediaSource * @param {Function} listener * @param {Object} cancelSignal */ var onSourceEnded = createCompatibleEventListener(["sourceended", "webkitsourceended"]); exports.onSourceEnded = onSourceEnded; /** * @param {MediaSource} mediaSource * @param {Function} listener * @param {Object} cancelSignal */ var onSourceBufferUpdate = createCompatibleEventListener(["update"]); exports.onSourceBufferUpdate = onSourceBufferUpdate; /** * @param {SourceBufferList} sourceBuffers * @param {Function} listener * @param {Object} cancelSignal */ var onRemoveSourceBuffers = createCompatibleEventListener(["removesourcebuffer"]); exports.onRemoveSourceBuffers = onRemoveSourceBuffers; /** * @param {MediaKeySession} mediaKeySession */ var onKeyMessage = createCompatibleEventListener(["keymessage", "message"]); exports.onKeyMessage = onKeyMessage; /** * @param {MediaKeySession} mediaKeySession */ var onKeyAdded = createCompatibleEventListener(["keyadded", "ready"]); exports.onKeyAdded = onKeyAdded; /** * @param {MediaKeySession} mediaKeySession */ var onKeyError = createCompatibleEventListener(["keyerror", "error"]); exports.onKeyError = onKeyError; /** * @param {MediaKeySession} mediaKeySession */ var onKeyStatusesChange = createCompatibleEventListener(["keystatuseschange"]); exports.onKeyStatusesChange = onKeyStatusesChange; /** * @param {HTMLMediaElement} mediaElement */ var onSeeking = createCompatibleEventListener(["seeking"]); exports.onSeeking = onSeeking; /** * @param {HTMLMediaElement} mediaElement */ var onSeeked = createCompatibleEventListener(["seeked"]); exports.onSeeked = onSeeked; /** * @param {HTMLMediaElement} mediaElement */ var onEnded = createCompatibleEventListener(["ended"]); exports.onEnded = onEnded; /** * Utilitary function allowing to add an event listener and remove it * automatically once the given `CancellationSignal` emits. * @param {EventTarget} elt - The element on which should be attached the event * listener. * @param {string} evt - The event you wish to listen to * @param {Function} listener - The listener function * @param {Object} stopListening - Removes the event listener once this signal * emits */ function addEventListener(elt, evt, listener, stopListening) { elt.addEventListener(evt, listener); stopListening.register(function () { elt.removeEventListener(evt, listener); }); }