UNPKG

shaka-player

Version:
203 lines (173 loc) 6.06 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.polyfill.PiPWebkit'); goog.require('shaka.log'); goog.require('shaka.polyfill'); /** * @summary A polyfill to provide PiP support in Safari. * Note that Safari only supports PiP on video elements, not audio. * @export */ shaka.polyfill.PiPWebkit = class { /** * Install the polyfill if needed. * @export */ static install() { if (!window.HTMLVideoElement) { // Avoid errors on very old browsers. return; } // eslint-disable-next-line no-restricted-syntax const proto = HTMLVideoElement.prototype; if (proto.requestPictureInPicture && document.exitPictureInPicture) { // No polyfill needed. return; } if (!proto.webkitSupportsPresentationMode) { // No Webkit PiP API available. return; } const PiPWebkit = shaka.polyfill.PiPWebkit; shaka.log.debug('PiPWebkit.install'); // Polyfill document.pictureInPictureEnabled. // It's definitely enabled now. :-) document.pictureInPictureEnabled = true; // Polyfill document.pictureInPictureElement. // This is initially empty. We don't need getter or setter because we don't // need any special handling when this is set. We assume in good faith that // applications won't try to set this directly. document.pictureInPictureElement = null; // Polyfill HTMLVideoElement.requestPictureInPicture. proto.requestPictureInPicture = PiPWebkit.requestPictureInPicture_; // Polyfill HTMLVideoElement.disablePictureInPicture. Object.defineProperty(proto, 'disablePictureInPicture', { get: PiPWebkit.getDisablePictureInPicture_, set: PiPWebkit.setDisablePictureInPicture_, // You should be able to discover this property. enumerable: true, // And maybe we're not so smart. Let someone else change it if they want. configurable: true, }); // Polyfill document.exitPictureInPicture. document.exitPictureInPicture = PiPWebkit.exitPictureInPicture_; // Use the "capturing" event phase to get the webkit presentation mode event // from the document. This way, we get the event on its way from document // to the target element without having to intercept events in every // possible video element. document.addEventListener( 'webkitpresentationmodechanged', PiPWebkit.proxyEvent_, /* useCapture= */ true); } /** * @param {!Event} event * @private */ static proxyEvent_(event) { const PiPWebkit = shaka.polyfill.PiPWebkit; const element = /** @type {!HTMLVideoElement} */(event.target); if (element.webkitPresentationMode == PiPWebkit.PIP_MODE_) { // Keep track of the PiP element. This element just entered PiP mode. document.pictureInPictureElement = element; // Dispatch a standard event to match. const event2 = new Event('enterpictureinpicture'); element.dispatchEvent(event2); } else { // Keep track of the PiP element. This element just left PiP mode. // If something else hasn't already take its place, clear it. if (document.pictureInPictureElement == element) { document.pictureInPictureElement = null; } // Dispatch a standard event to match. const event2 = new Event('leavepictureinpicture'); element.dispatchEvent(event2); } } /** * @this {HTMLVideoElement} * @return {!Promise} * @private */ static requestPictureInPicture_() { const PiPWebkit = shaka.polyfill.PiPWebkit; // NOTE: "this" here is the video element. // Check if PiP is enabled for this element. if (!this.webkitSupportsPresentationMode(PiPWebkit.PIP_MODE_)) { const error = new Error('PiP not allowed by video element'); return Promise.reject(error); } else { // Enter PiP mode. this.webkitSetPresentationMode(PiPWebkit.PIP_MODE_); document.pictureInPictureElement = this; return Promise.resolve(); } } /** * @this {Document} * @return {!Promise} * @private */ static exitPictureInPicture_() { const PiPWebkit = shaka.polyfill.PiPWebkit; const pipElement = /** @type {HTMLVideoElement} */(document.pictureInPictureElement); if (pipElement) { // Exit PiP mode. pipElement.webkitSetPresentationMode(PiPWebkit.INLINE_MODE_); document.pictureInPictureElement = null; return Promise.resolve(); } else { const error = new Error('No picture in picture element found'); return Promise.reject(error); } } /** * @this {HTMLVideoElement} * @return {boolean} * @private */ static getDisablePictureInPicture_() { // This respects the HTML attribute, which may have been set in HTML or // through the JS setter. if (this.hasAttribute('disablePictureInPicture')) { return true; } // Use Apple's non-standard API to know if PiP is allowed on this // device for this content. If not, say that PiP is disabled, even // if not specified by the user through the setter or HTML attribute. const PiPWebkit = shaka.polyfill.PiPWebkit; return !this.webkitSupportsPresentationMode(PiPWebkit.PIP_MODE_); } /** * @this {HTMLVideoElement} * @param {boolean} value * @private */ static setDisablePictureInPicture_(value) { // This mimics how the JS setter works in browsers that implement the spec. if (value) { this.setAttribute('disablePictureInPicture', ''); } else { this.removeAttribute('disablePictureInPicture'); } } }; /** * The presentation mode string used to indicate PiP mode in Safari. * * @const {string} * @private */ shaka.polyfill.PiPWebkit.PIP_MODE_ = 'picture-in-picture'; /** * The presentation mode string used to indicate inline mode in Safari. * * @const {string} * @private */ shaka.polyfill.PiPWebkit.INLINE_MODE_ = 'inline'; shaka.polyfill.register(shaka.polyfill.PiPWebkit.install);