1
0
mirror of https://github.com/videojs/video.js.git synced 2025-11-06 09:19:15 +02:00

feat: add built-in Picture-in-Picture button (#6002)

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.
This commit is contained in:
François Beaufort
2019-06-18 22:42:02 +02:00
committed by Gary Katsevman
parent 204ff4619a
commit 116d84af75
10 changed files with 132 additions and 4 deletions

View File

@@ -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

View File

@@ -53,6 +53,7 @@ myPlayer.addChild('BigPlayButton');
ChaptersButton (Hidden by default)
SubtitlesButton (Hidden by default)
CaptionsButton (Hidden by default)
PictureInPictureToggle
FullscreenToggle
ErrorDisplay
TextTrackSettings

6
package-lock.json generated
View File

@@ -14556,9 +14556,9 @@
}
},
"videojs-font": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.1.1.tgz",
"integrity": "sha512-oozseAn5cVko/EobvXmk7MSlH14ujOmuf0rpXUEwifQLobIbQISZWME0RG3ALt//vnvvkVekuL7H2WkhCO7ynw=="
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",
"integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA=="
},
"videojs-generate-karma-config": {
"version": "5.2.0",

View File

@@ -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"
},

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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';
@@ -67,6 +68,7 @@ ControlBar.prototype.options_ = {
'descriptionsButton',
'subsCapsButton',
'audioTrackButton',
'pictureInPictureToggle',
'fullscreenToggle'
]
};

View File

@@ -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;

View File

@@ -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');

View File

@@ -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';
@@ -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);