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:
committed by
Gary Katsevman
parent
204ff4619a
commit
116d84af75
@@ -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
|
||||
|
||||
@@ -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
6
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
12
src/css/components/_picture-in-picture.scss
Normal file
12
src/css/components/_picture-in-picture.scss
Normal 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;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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'
|
||||
]
|
||||
};
|
||||
|
||||
93
src/js/control-bar/picture-in-picture-toggle.js
Normal file
93
src/js/control-bar/picture-in-picture-toggle.js
Normal 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;
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user