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)
|
│ ├── 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
|
||||||
|
|||||||
@@ -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
6
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
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/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";
|
||||||
|
|||||||
@@ -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'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
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('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');
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user