Skip to content

Commit

Permalink
feat: Add support for Document Picture-in-Picture (#4969)
Browse files Browse the repository at this point in the history
This PR adds _experimental_ support for the Document Picture-in-Picture
API in the Shaka Player.

Closes #4964

Co-authored-by: Alvaro Velad Galvan <[email protected]>
Co-authored-by: Joey Parrish <[email protected]>
  • Loading branch information
3 people authored Feb 10, 2023
1 parent a8c2491 commit 3828fd6
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 5 deletions.
11 changes: 11 additions & 0 deletions demo/close_button.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ shakaDemo.CloseButton = class extends shaka.ui.Element {
shakaDemoMain.unload();
});

if ('documentPictureInPicture' in window) {
this.eventManager.listen(
window.documentPictureInPicture, 'enter', () => {
this.button_.style.display = 'none';
const pipWindow = window.documentPictureInPicture.window;
this.eventManager.listen(pipWindow, 'unload', () => {
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
10 changes: 7 additions & 3 deletions docs/tutorials/ui-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ 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.
* 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 +73,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 All @@ -83,7 +85,8 @@ The following buttons can be added to the overflow menu:
* quality: adds a button that controls enabling/disabling of abr and video resolution selection.
* language: adds a button that controls audio language selection.
* 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.
* loop: adds a button that controls if the currently selected video is played in a loop.
* playback_rate: adds a button that controls the playback rate selection.
* airplay: adds a button that opens a AirPlay dialog. The button is visible only if the browser
Expand Down Expand Up @@ -122,7 +125,8 @@ The following buttons can be added to the context menu:
* Statistics: adds a button that displays statistics of the video.
* loop: adds a button that controls if the currently selected video is played in a loop.
* 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.

Example:
```js
Expand Down
50 changes: 50 additions & 0 deletions externs/pictureinpicture.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,53 @@ HTMLMediaElement.prototype.webkitSupportsPresentationMode = function(mode) {};

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


/**
* @typedef {{
* initialAspectRatio: (number|undefined),
* width: (number|undefined),
* height: (number|undefined),
* copyStyleSheets: (boolean|undefined),
* }}
*/
var DocumentPictureInPictureOptions;


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


/**
* @param {DocumentPictureInPictureOptions} options
* @return {!Promise.<Window>}
*/
DocumentPictureInPicture.prototype.requestWindow = function(options) {};


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


/** @override */
DocumentPictureInPicture.prototype.addEventListener =
function(type, listener, options) {};


/** @override */
DocumentPictureInPicture.prototype.removeEventListener =
function(type, listener, options) {};


/** @override */
DocumentPictureInPicture.prototype.dispatchEvent = function(event) {};


/**
* @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) {
await this.toggleDocumentPictureInPicture_();
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;
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.
this.eventManager.listenOnce(pipWindow, 'unload', () => {
placeholder.replaceWith(/** @type {!Node} */(pipPlayer));
});
}

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

0 comments on commit 3828fd6

Please sign in to comment.