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) │ ├── SubtitlesButton (hidden, unless there are relevant tracks)
│ ├── CaptionsButton (hidden, unless there are relevant tracks) │ ├── CaptionsButton (hidden, unless there are relevant tracks)
│ ├── AudioTrackButton (hidden, unless there are relevant tracks) │ ├── AudioTrackButton (hidden, unless there are relevant tracks)
│ └── PictureInPictureToggle
│ └── FullscreenToggle │ └── FullscreenToggle
├── ErrorDisplay (hidden, until there is an error) ├── ErrorDisplay (hidden, until there is an error)
├── TextTrackSettings ├── TextTrackSettings

View File

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

6
package-lock.json generated
View File

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

View File

@@ -85,7 +85,7 @@
"keycode": "^2.2.0", "keycode": "^2.2.0",
"safe-json-parse": "4.0.0", "safe-json-parse": "4.0.0",
"tsml": "1.0.1", "tsml": "1.0.1",
"videojs-font": "3.1.1", "videojs-font": "3.2.0",
"videojs-vtt.js": "^0.14.1", "videojs-vtt.js": "^0.14.1",
"xhr": "2.4.0" "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/time";
@import "components/play-pause"; @import "components/play-pause";
@import "components/text-track"; @import "components/text-track";
@import "components/picture-in-picture";
@import "components/fullscreen"; @import "components/fullscreen";
@import "components/playback-rate"; @import "components/playback-rate";
@import "components/error"; @import "components/error";

View File

@@ -12,6 +12,7 @@ import './time-controls/remaining-time-display.js';
import './live-display.js'; import './live-display.js';
import './seek-to-live.js'; import './seek-to-live.js';
import './progress-control/progress-control.js'; import './progress-control/progress-control.js';
import './picture-in-picture-toggle.js';
import './fullscreen-toggle.js'; import './fullscreen-toggle.js';
import './volume-panel.js'; import './volume-panel.js';
import './text-track-controls/chapters-button.js'; import './text-track-controls/chapters-button.js';
@@ -67,6 +68,7 @@ ControlBar.prototype.options_ = {
'descriptionsButton', 'descriptionsButton',
'subsCapsButton', 'subsCapsButton',
'audioTrackButton', 'audioTrackButton',
'pictureInPictureToggle',
'fullscreenToggle' '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('ControlBar'), 'ControlBar should be public');
assert.ok(videojs.getComponent('Button'), 'Button 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('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('FullscreenToggle'), 'FullscreenToggle should be public');
assert.ok(videojs.getComponent('BigPlayButton'), 'BigPlayButton should be public'); assert.ok(videojs.getComponent('BigPlayButton'), 'BigPlayButton should be public');
assert.ok(videojs.getComponent('LoadingSpinner'), 'LoadingSpinner 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 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 PlaybackRateMenuButton from '../../src/js/control-bar/playback-rate-menu/playback-rate-menu-button.js';
import Slider from '../../src/js/slider/slider.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 FullscreenToggle from '../../src/js/control-bar/fullscreen-toggle.js';
import ControlBar from '../../src/js/control-bar/control-bar.js'; import ControlBar from '../../src/js/control-bar/control-bar.js';
import TestHelpers from './test-helpers.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(); 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) { QUnit.test('Fullscreen control text should be correct when fullscreenchange is triggered', function(assert) {
const player = TestHelpers.makePlayer(); const player = TestHelpers.makePlayer();
const fullscreentoggle = new FullscreenToggle(player); const fullscreentoggle = new FullscreenToggle(player);