Skip to content

Commit

Permalink
feat: adds disablePictureInPicture method to the player API. (#6378)
Browse files Browse the repository at this point in the history
  • Loading branch information
gjanblaszczyk authored Apr 22, 2020
1 parent 8c66c58 commit dbd5203
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 8 deletions.
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 @@ -2982,6 +2983,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 @@ -1526,8 +1530,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 @@ -1699,6 +1703,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 @@ -1796,7 +1813,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 @@ -1888,6 +1906,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');
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

0 comments on commit dbd5203

Please sign in to comment.