Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for Document Picture-in-Picture #4969

Merged
merged 14 commits into from
Feb 10, 2023
10 changes: 10 additions & 0 deletions demo/close_button.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ shakaDemo.CloseButton = class extends shaka.ui.Element {
shakaDemoMain.unload();
});

if ('documentPictureInPicture' in window) {
window.documentPictureInPicture.addEventListener('enter', (event) => {
joeyparrish marked this conversation as resolved.
Show resolved Hide resolved
this.button_.style.display = 'none';
const pipWindow = window.documentPictureInPicture.window;
pipWindow.addEventListener('unload', (event) => {
joeyparrish marked this conversation as resolved.
Show resolved Hide resolved
this.button_.style.display = 'block';
});
});
}

// TODO: Make sure that the screenreader description of this control is
// localized!
}
Expand Down
4 changes: 4 additions & 0 deletions demo/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,10 @@ shakaDemo.Main = class {
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
}
if (window.documentPictureInPicture &&
window.documentPictureInPicture.window) {
window.documentPictureInPicture.window.close();
}
this.player_.unload();

// The currently-selected asset changed, so update asset cards.
Expand Down
3 changes: 2 additions & 1 deletion docs/tutorials/ui-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The following elements can be added to the UI bar using this configuration value
starts playing the presentation at an increased speed
* spacer: adds a chunk of empty space between the adjacent elements.
* picture_in_picture: adds a button that enables/disables picture-in-picture mode on browsers
that support it. Button is invisible on other browsers.
that support it. Button is invisible on other browsers. Note that it will use the [Document Picture-in-Picture API]() if supported.
theodab marked this conversation as resolved.
Show resolved Hide resolved
* loop: adds a button that controls if the currently selected video is played in a loop.
* airplay: adds a button that opens a AirPlay dialog. The button is visible only if the browser
supports AirPlay.
Expand All @@ -72,6 +72,7 @@ The following elements can be added to the UI bar using this configuration value
* playback_rate: adds a button that controls the playback rate selection.
* captions: adds a button that controls the current text track selection (including turning it off).
<!-- TODO: If we add more buttons that can be put in the order this way, list them here. -->
[Document Picture-in-Picture API]: https://developer.chrome.com/docs/web-platform/document-picture-in-picture/

Similarly, the 'overflowMenuButtons' configuration option can be used to control
the contents of the overflow menu.
Expand Down
31 changes: 31 additions & 0 deletions externs/pictureinpicture.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,34 @@ HTMLMediaElement.prototype.webkitSupportsPresentationMode = function(mode) {};

/** @type {string} */
HTMLMediaElement.prototype.webkitPresentationMode;


/**
* @constructor
*/
function DocumentPictureInPicture() {}


/**
* @return {!Promise}
*/
DocumentPictureInPicture.prototype.requestWindow = function(options) {};


/** @type {Window} */
DocumentPictureInPicture.prototype.window;


/**
* @param {string} type
* @param {Function} listener
*/
DocumentPictureInPicture.prototype.addEventListener =
function(type, listener) {};


/**
* @see https://wicg.github.io/document-picture-in-picture/#api
* @type {!DocumentPictureInPicture}
*/
Window.prototype.documentPictureInPicture;
55 changes: 53 additions & 2 deletions ui/pip_button.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
/** @private {HTMLMediaElement} */
this.localVideo_ = this.controls.getLocalVideo();

/** @private {HTMLElement } */
this.videoContainer_ = this.controls.getVideoContainer();

const LocIds = shaka.ui.Locales.Ids;
/** @private {!HTMLButtonElement} */
this.pipButton_ = shaka.util.Dom.createButton();
Expand Down Expand Up @@ -111,8 +114,8 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
* @private
*/
isPipAllowed_() {
return document.pictureInPictureEnabled &&
!this.video.disablePictureInPicture;
return ('documentPictureInPicture' in window) ||
(document.pictureInPictureEnabled && !this.video.disablePictureInPicture);
}


Expand All @@ -122,6 +125,10 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
*/
async onPipClick_() {
try {
if ('documentPictureInPicture' in window) {
avelad marked this conversation as resolved.
Show resolved Hide resolved
await this.toggleDocumentPictureInPicture_();
theodab marked this conversation as resolved.
Show resolved Hide resolved
return;
}
if (!document.pictureInPictureElement) {
// If you were fullscreen, leave fullscreen first.
if (document.fullscreenElement) {
Expand All @@ -137,6 +144,50 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
}
}

/**
* The Document Picture-in-Picture API makes it possible to open an
* always-on-top window that can be populated with arbitrary HTML content.
* https://developer.chrome.com/docs/web-platform/document-picture-in-picture
* @private
*/
async toggleDocumentPictureInPicture_() {
// Close Picture-in-Picture window if any.
if (window.documentPictureInPicture.window) {
window.documentPictureInPicture.window.close();
this.onLeavePictureInPicture_();
return;
}

// Open a Picture-in-Picture window.
const pipPlayer = this.videoContainer_;
const rectPipPlayer = pipPlayer.getBoundingClientRect();
const pipWindow = await window.documentPictureInPicture.requestWindow({
initialAspectRatio: rectPipPlayer.width / rectPipPlayer.height,
copyStyleSheets: true,
});

// Add placeholder for the player.
const parentPlayer = pipPlayer.parentNode || document.body;
const placeholder = this.videoContainer_.cloneNode(true);
placeholder.style.visibility = 'hidden';
placeholder.style.height = getComputedStyle(pipPlayer).height;
theodab marked this conversation as resolved.
Show resolved Hide resolved
parentPlayer.appendChild(placeholder);

// Make sure player fits in the Picture-in-Picture window.
const styles = document.createElement('style');
styles.append(`[data-shaka-player-container] {
width: 100% !important; max-height: 100%}`);
pipWindow.document.head.append(styles);

// Move player to the Picture-in-Picture window.
pipWindow.document.body.append(pipPlayer);
this.onEnterPictureInPicture_();

// Listen for the PiP closing event to move the player back.
pipWindow.addEventListener('unload', () => {
joeyparrish marked this conversation as resolved.
Show resolved Hide resolved
placeholder.replaceWith(/** @type {!Node} */(pipPlayer));
}, {once: true});
}

/** @private */
onEnterPictureInPicture_() {
Expand Down