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: adds disablePictureInPicture method to the player API. #6378

Merged
merged 8 commits into from
Apr 22, 2020
2 changes: 2 additions & 0 deletions docs/guides/angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export class VjsPlayerComponent implements OnInit, OnDestroy {
}
}
```

Don't forget to include the Video.js CSS, located at `video.js/dist/video-js.css`.

```css
/* vjs-player.component.css */
@import '~video.js/dist/video-js.css';
Expand Down
2 changes: 2 additions & 0 deletions docs/translations-needed.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
## Status of translations

<!-- START langtable -->

| Language file | Missing translations |
| ----------------------- | ----------------------------------------------------------------------------------- |
| ar.json (missing 3) | progress bar timing: currentTime={1} duration={2} |
Expand Down Expand Up @@ -941,4 +942,5 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Picture-in-Picture |
| zh-TW.json (missing 2) | Exit Picture-in-Picture |
| | Picture-in-Picture |

<!-- END langtable -->
72 changes: 72 additions & 0 deletions sandbox/pip-disabled.html.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Sandbox</title>
<link href="../dist/video-js.css" rel="stylesheet" type="text/css">
<script src="../dist/video.js"></script>
</head>
<body>
<div style="background-color:#eee; border: 1px solid #777; padding: 10px; margin-bottom: 20px; font-size: .8em; line-height: 1.5em; font-family: Verdana, sans-serif;">
<p>You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example (so don't edit or add those files). To get started run `npm start` and open the index.html</p>
<pre>npm start</pre>
<pre>open http://localhost:9999/sandbox/index.html</pre>
</div>

<video-js
id="vid1"
controls
disablepictureinpicture
preload="none"
width="640"
height="264"
poster="http://vjs.zencdn.net/v/oceans.png">
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm">
<source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video-js>
<p>
PiP disabled via <b>disablepictureinpicture</b> attribute.
</p>
<video-js
id="vid2"
controls
preload="auto"
width="640"
height="264"
poster="http://vjs.zencdn.net/v/oceans.png">
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm">
<source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video-js>
<p>
PiP disabled via <b>disablePictureInPicture</b> player option.
</p>
<button id="PiPToggleBtn">call disablePictureInPicture(false) for the player 2</button>
<script>
var pipDisabled = true;
var vid = document.getElementById('vid1');
var player = videojs(vid);

var vid2 = document.getElementById('vid2');
var player2 = videojs(vid2, {
controlBar: {
pictureInPictureToggle: true
},
disablePictureInPicture: true
});

var pipToggle = document.getElementById('PiPToggleBtn');
pipToggle.addEventListener('click', function() {
pipToggle.innerText = 'call disablePictureInPicture(' + pipDisabled + ') for the player 2';
pipDisabled = !pipDisabled;
player2.disablePictureInPicture(pipDisabled);
});
</script>

</body>
</html>
20 changes: 15 additions & 5 deletions src/js/control-bar/picture-in-picture-toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,10 @@ class PictureInPictureToggle extends Button {
constructor(player, options) {
super(player, options);
this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], this.handlePictureInPictureChange);
this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], this.handlePictureInPictureEnabledChange);

// TODO: Activate button on player loadedmetadata event.
// TODO: Deactivate button on player emptied event.
// TODO: Deactivate button if disablepictureinpicture attribute is present.
if (!document.pictureInPictureEnabled) {
this.disable();
}
this.disable();
}

/**
Expand All @@ -46,6 +43,18 @@ class PictureInPictureToggle extends Button {
return `vjs-picture-in-picture-control ${super.buildCSSClass()}`;
}

/**
* Enables or disables button based on document.pictureInPictureEnabled property value
* or on value returned by player.disablePictureInPicture() method.
*/
handlePictureInPictureEnabledChange() {
if (!document.pictureInPictureEnabled || this.player_.disablePictureInPicture()) {
this.disable();
} else {
this.enable();
}
}

/**
* Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly.
*
Expand All @@ -62,6 +71,7 @@ class PictureInPictureToggle extends Button {
} else {
this.controlText('Picture-in-Picture');
}
this.handlePictureInPictureEnabledChange();
}

/**
Expand Down
17 changes: 17 additions & 0 deletions src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,7 @@ class Player extends Component {
'playsinline': this.options_.playsinline,
'preload': this.options_.preload,
'loop': this.options_.loop,
'disablePictureInPicture': this.options_.disablePictureInPicture,
'muted': this.options_.muted,
'poster': this.poster(),
'language': this.language(),
Expand Down Expand Up @@ -2981,6 +2982,22 @@ class Player extends Component {
this.trigger('exitFullWindow');
}

/**
* Disable Picture-in-Picture mode.
*
* @param {boolean} value
* - true will disable Picture-in-Picture mode
* - false will enable Picture-in-Picture mode
*/
disablePictureInPicture(value) {
if (value === undefined) {
return this.techGet_('disablePictureInPicture');
}
this.techCall_('setDisablePictureInPicture', value);
this.options_.disablePictureInPicture = value;
this.trigger('disablepictureinpicturechanged');
}

/**
* Check if the player is in Picture-in-Picture mode or tell the player that it
* is or is not in Picture-in-Picture mode.
Expand Down
36 changes: 33 additions & 3 deletions src/js/tech/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ class Html5 extends Tech {
Dom.setAttribute(el, 'preload', this.options_.preload);
}

if (this.options_.disablePictureInPicture !== undefined) {
el.disablePictureInPicture = this.options_.disablePictureInPicture;
}

// Update specific tag settings, in case they were overridden
// `autoplay` has to be *last* so that `muted` and `playsinline` are present
// when iOS/Safari or other browsers attempt to autoplay.
Expand Down Expand Up @@ -1518,8 +1522,8 @@ Html5.resetMediaElement = function(el) {
// Wrap native properties with a getter
// The list is as followed
// paused, currentTime, buffered, volume, poster, preload, error, seeking
// seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
// readyState, videoWidth, videoHeight, crossOrigin
// seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
// played, networkState, readyState, videoWidth, videoHeight, crossOrigin
[
/**
* Get the value of `paused` from the media element. `paused` indicates whether the media element
Expand Down Expand Up @@ -1691,6 +1695,19 @@ Html5.resetMediaElement = function(el) {
*/
'defaultPlaybackRate',

/**
* Get the value of 'disablePictureInPicture' from the video element.
*
* @method Html5#disablePictureInPicture
* @return {boolean} value
* - The value of `disablePictureInPicture` from the video element.
* - True indicates that the video can't be played in Picture-In-Picture mode
* - False indicates that the video can be played in Picture-In-Picture mode
*
* @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
*/
'disablePictureInPicture',

/**
* Get the value of `played` from the media element. `played` returns a `TimeRange`
* object representing points in the media timeline that have been played.
Expand Down Expand Up @@ -1788,7 +1805,8 @@ Html5.resetMediaElement = function(el) {
// Wrap native properties with a setter in this format:
// set + toTitleCase(name)
// The list is as follows:
// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate, setCrossOrigin
// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
// setDisablePictureInPicture, setCrossOrigin
[
/**
* Set the value of `volume` on the media element. `volume` indicates the current
Expand Down Expand Up @@ -1880,6 +1898,18 @@ Html5.resetMediaElement = function(el) {
*/
'defaultPlaybackRate',

/**
* Prevents the browser from suggesting a Picture-in-Picture context menu
* or to request Picture-in-Picture automatically in some cases.
*
* @method Html5#setDisablePictureInPicture
* @param {boolean} value
* The true value will disable Picture-in-Picture mode.
*
* @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
*/
'disablePictureInPicture',

/**
* Set the value of `crossOrigin` from the media element. `crossOrigin` indicates
* to the browser that should sent the cookies along with the requests for the
Expand Down
16 changes: 16 additions & 0 deletions src/js/tech/tech.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,22 @@ class Tech extends Component {
}
}

/**
* A method to check for the presence of the 'disablePictureInPicture' <video> property.
*
* @abstract
*/
disablePictureInPicture() {
return false;
}

/**
* A method to set or unset the 'disablePictureInPicture' <video> property.
*
* @abstract
*/
setDisablePictureInPicture() {}

/**
* A method to set a poster from a `Tech`.
*
Expand Down
1 change: 1 addition & 0 deletions test/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ QUnit.test('should be able to access expected player API methods', function(asse
assert.ok(player.textTracks, 'textTracks exists');
assert.ok(player.requestFullscreen, 'requestFullscreen exists');
assert.ok(player.exitFullscreen, 'exitFullscreen exists');
assert.ok(player.disablePictureInPicture, 'disablePictureInPicture exists');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, though, I wonder if these tests even run anymore... 🤔

assert.ok(player.requestPictureInPicture, 'requestPictureInPicture exists');
assert.ok(player.exitPictureInPicture, 'exitPictureInPicture exists');
assert.ok(player.playbackRate, 'playbackRate exists');
Expand Down
46 changes: 46 additions & 0 deletions test/unit/controls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,52 @@ QUnit.test('Picture-in-Picture control text should be correct when enterpicturei
pictureInPictureToggle.dispose();
});

QUnit.test('Picture-in-Picture control enabled property value should be correct when enterpictureinpicture and leavepictureinpicture are triggered', function(assert) {
const player = TestHelpers.makePlayer();
const pictureInPictureToggle = new PictureInPictureToggle(player);

assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after creation');

if ('pictureInPictureEnabled' in document) {
player.isInPictureInPicture(true);
player.trigger('enterpictureinpicture');
assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an enterpictureinpicture event');

player.isInPictureInPicture(false);
player.trigger('leavepictureinpicture');
assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an leavepictureinpicture event');
} else {
player.isInPictureInPicture(true);
player.trigger('enterpictureinpicture');
assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an enterpictureinpicture event');

player.isInPictureInPicture(false);
player.trigger('leavepictureinpicture');
assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an leavepictureinpicture event');
}

player.dispose();
pictureInPictureToggle.dispose();
});

QUnit.test('Picture-in-Picture control enabled property value should be correct when loadedmetadata is triggered', function(assert) {
const player = TestHelpers.makePlayer();
const pictureInPictureToggle = new PictureInPictureToggle(player);

assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after creation');

if ('pictureInPictureEnabled' in document) {
player.trigger('loadedmetadata');
assert.equal(pictureInPictureToggle.enabled_, true, 'pictureInPictureToggle button should be enabled after triggering an loadedmetadata event');
} else {
player.trigger('loadedmetadata');
assert.equal(pictureInPictureToggle.enabled_, false, 'pictureInPictureToggle button should be disabled after triggering an loadedmetadata event');
}

player.dispose();
pictureInPictureToggle.dispose();
});

QUnit.test('Fullscreen control text should be correct when fullscreenchange is triggered', function(assert) {
const player = TestHelpers.makePlayer({controlBar: false});
const fullscreentoggle = new FullscreenToggle(player);
Expand Down