Skip to content

Commit

Permalink
feat: add built-in Picture-in-Picture button (#6002)
Browse files Browse the repository at this point in the history
Adds a new PictureInPictureToggle component in the controls bar of the player. It depends on videojs-font 3.2.0 (videojs/font#41) for icons.

Final spec piece from #5824.
  • Loading branch information
beaufortfrancois authored and gkatsev committed Jun 18, 2019
1 parent 204ff46 commit 116d84a
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/guides/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ Player
│ ├── SubtitlesButton (hidden, unless there are relevant tracks)
│ ├── CaptionsButton (hidden, unless there are relevant tracks)
│ ├── AudioTrackButton (hidden, unless there are relevant tracks)
│ └── PictureInPictureToggle
│ └── FullscreenToggle
├── ErrorDisplay (hidden, until there is an error)
├── TextTrackSettings
Expand Down
1 change: 1 addition & 0 deletions docs/legacy-docs/guides/components.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ <h1 id="components">Components</h1>
ChaptersButton (Hidden by default)
SubtitlesButton (Hidden by default)
CaptionsButton (Hidden by default)
PictureInPictureToggle
FullscreenToggle
ErrorDisplay
TextTrackSettings
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"keycode": "^2.2.0",
"safe-json-parse": "4.0.0",
"tsml": "1.0.1",
"videojs-font": "3.1.1",
"videojs-font": "3.2.0",
"videojs-vtt.js": "^0.14.1",
"xhr": "2.4.0"
},
Expand Down
12 changes: 12 additions & 0 deletions src/css/components/_picture-in-picture.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.video-js .vjs-picture-in-picture-control {
cursor: pointer;
@include flex(none);

& .vjs-icon-placeholder {
@extend .vjs-icon-picture-in-picture-enter;
}
}
// Switch to the exit icon when the player is in Picture-in-Picture
.video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder {
@extend .vjs-icon-picture-in-picture-exit;
}
1 change: 1 addition & 0 deletions src/css/video-js.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
@import "components/time";
@import "components/play-pause";
@import "components/text-track";
@import "components/picture-in-picture";
@import "components/fullscreen";
@import "components/playback-rate";
@import "components/error";
Expand Down
2 changes: 2 additions & 0 deletions src/js/control-bar/control-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import './time-controls/remaining-time-display.js';
import './live-display.js';
import './seek-to-live.js';
import './progress-control/progress-control.js';
import './picture-in-picture-toggle.js';
import './fullscreen-toggle.js';
import './volume-panel.js';
import './text-track-controls/chapters-button.js';
Expand Down Expand Up @@ -67,6 +68,7 @@ ControlBar.prototype.options_ = {
'descriptionsButton',
'subsCapsButton',
'audioTrackButton',
'pictureInPictureToggle',
'fullscreenToggle'
]
};
Expand Down
93 changes: 93 additions & 0 deletions src/js/control-bar/picture-in-picture-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* @file picture-in-picture-toggle.js
*/
import Button from '../button.js';
import Component from '../component.js';
import document from 'global/document';

/**
* Toggle Picture-in-Picture mode
*
* @extends Button
*/
class PictureInPictureToggle extends Button {

/**
* Creates an instance of this class.
*
* @param {Player} player
* The `Player` that this class should be attached to.
*
* @param {Object} [options]
* The key/value store of player options.
*/
constructor(player, options) {
super(player, options);
this.on(player, 'pictureinpicturechange', this.handlePictureInPictureChange);

// 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();
}
}

/**
* Builds the default DOM `className`.
*
* @return {string}
* The DOM `className` for this object.
*/
buildCSSClass() {
return `vjs-picture-in-picture-control ${super.buildCSSClass()}`;
}

/**
* Handles pictureinpicturechange on the player and change control text accordingly.
*
* @param {EventTarget~Event} [event]
* The {@link Player#pictureinpicturechange} event that caused this function to be
* called.
*
* @listens Player#pictureinpicturechange
*/
handlePictureInPictureChange(event) {
if (this.player_.isInPictureInPicture()) {
this.controlText('Exit Picture-in-Picture');
} else {
this.controlText('Picture-in-Picture');
}
}

/**
* This gets called when an `PictureInPictureToggle` is "clicked". See
* {@link ClickableComponent} for more detailed information on what a click can be.
*
* @param {EventTarget~Event} [event]
* The `keydown`, `tap`, or `click` event that caused this function to be
* called.
*
* @listens tap
* @listens click
*/
handleClick(event) {
if (!this.player_.isInPictureInPicture()) {
this.player_.requestPictureInPicture();
} else {
this.player_.exitPictureInPicture();
}
}

}

/**
* The text that should display over the `PictureInPictureToggle`s controls. Added for localization.
*
* @type {string}
* @private
*/
PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture';

Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle);
export default PictureInPictureToggle;
1 change: 1 addition & 0 deletions test/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ QUnit.test('should export useful components to the public', function(assert) {
assert.ok(videojs.getComponent('ControlBar'), 'ControlBar should be public');
assert.ok(videojs.getComponent('Button'), 'Button should be public');
assert.ok(videojs.getComponent('PlayToggle'), 'PlayToggle should be public');
assert.ok(videojs.getComponent('PictureInPictureToggle'), 'PictureInPictureToggle should be public');
assert.ok(videojs.getComponent('FullscreenToggle'), 'FullscreenToggle should be public');
assert.ok(videojs.getComponent('BigPlayButton'), 'BigPlayButton should be public');
assert.ok(videojs.getComponent('LoadingSpinner'), 'LoadingSpinner should be public');
Expand Down
17 changes: 17 additions & 0 deletions test/unit/controls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import VolumeBar from '../../src/js/control-bar/volume-control/volume-bar.js';
import PlayToggle from '../../src/js/control-bar/play-toggle.js';
import PlaybackRateMenuButton from '../../src/js/control-bar/playback-rate-menu/playback-rate-menu-button.js';
import Slider from '../../src/js/slider/slider.js';
import PictureInPictureToggle from '../../src/js/control-bar/picture-in-picture-toggle.js';
import FullscreenToggle from '../../src/js/control-bar/fullscreen-toggle.js';
import ControlBar from '../../src/js/control-bar/control-bar.js';
import TestHelpers from './test-helpers.js';
Expand Down Expand Up @@ -152,6 +153,22 @@ QUnit.test('should hide playback rate control if it\'s not supported', function(
playbackRate.dispose();
});

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

player.isInPictureInPicture(true);
player.trigger('pictureinpicturechange');
assert.equal(pictureInPictureToggle.controlText(), 'Exit Picture-in-Picture', 'Control Text is correct while switching to Picture-in-Picture mode');

player.isInPictureInPicture(false);
player.trigger('pictureinpicturechange');
assert.equal(pictureInPictureToggle.controlText(), 'Picture-in-Picture', 'Control Text is correct while switching back to normal mode');

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

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

0 comments on commit 116d84a

Please sign in to comment.