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

Add built-in Picture-in-Picture button #6002

Merged
merged 9 commits into from
Jun 18, 2019
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