mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-24 17:35:10 +03:00
37a5e4abd2
This solution involves polyfilling the disablePictureInPicture attribute on the video element. If the polyfill is installed, it will show document.pictureInPictureEnabled==true, but video.disablePictureInPicture will be a getter that does a runtime check on that specific element using webkitSupportsPresentationMode. disablePictureInPicture (JS property & HTML attribute) is part of the PiP spec, so this should be sufficient to disable PiP correctly on iOS in your application, whether or not you are using our UI. Closes #2199 Change-Id: Iac9fc7e6cf6d4b0f7576f55af7507348ab2dc02c
200 lines
5.9 KiB
JavaScript
200 lines
5.9 KiB
JavaScript
/** @license
|
|
* 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.
|
|
*/
|
|
shaka.polyfill.PiPWebkit = class {
|
|
/**
|
|
* Install the polyfill if needed.
|
|
*/
|
|
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);
|