Skip to content

Commit d524e57

Browse files
authored
fix(picture-in-picture-control): hide the component in non-compatible browsers (#7899)
1 parent 20df248 commit d524e57

File tree

4 files changed

+79
-30
lines changed

4 files changed

+79
-30
lines changed

src/js/control-bar/control-bar.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* @file control-bar.js
33
*/
44
import Component from '../component.js';
5-
import document from 'global/document';
65

76
// Required children
87
import './play-toggle.js';
@@ -73,17 +72,10 @@ ControlBar.prototype.options_ = {
7372
'descriptionsButton',
7473
'subsCapsButton',
7574
'audioTrackButton',
75+
'pictureInPictureToggle',
7676
'fullscreenToggle'
7777
]
7878
};
7979

80-
if ('exitPictureInPicture' in document) {
81-
ControlBar.prototype.options_.children.splice(
82-
ControlBar.prototype.options_.children.length - 1,
83-
0,
84-
'pictureInPictureToggle'
85-
);
86-
}
87-
8880
Component.registerComponent('ControlBar', ControlBar);
8981
export default ControlBar;

src/js/control-bar/picture-in-picture-toggle.js

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,10 @@ class PictureInPictureToggle extends Button {
2727
*/
2828
constructor(player, options) {
2929
super(player, options);
30+
3031
this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], (e) => this.handlePictureInPictureChange(e));
3132
this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], (e) => this.handlePictureInPictureEnabledChange(e));
32-
33-
this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => {
34-
// This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
35-
const isSourceAudio = player.currentType().substring(0, 5) === 'audio';
36-
37-
if (isSourceAudio || player.audioPosterMode() || player.audioOnlyMode()) {
38-
if (player.isInPictureInPicture()) {
39-
player.exitPictureInPicture();
40-
}
41-
this.hide();
42-
} else {
43-
this.show();
44-
}
45-
46-
});
33+
this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => this.handlePictureInPictureAudioModeChange());
4734

4835
// TODO: Deactivate button on player emptied event.
4936
this.disable();
@@ -56,7 +43,30 @@ class PictureInPictureToggle extends Button {
5643
* The DOM `className` for this object.
5744
*/
5845
buildCSSClass() {
59-
return `vjs-picture-in-picture-control ${super.buildCSSClass()}`;
46+
return `vjs-picture-in-picture-control vjs-hidden ${super.buildCSSClass()}`;
47+
}
48+
49+
/**
50+
* Displays or hides the button depending on the audio mode detection.
51+
* Exits picture-in-picture if it is enabled when switching to audio mode.
52+
*/
53+
handlePictureInPictureAudioModeChange() {
54+
// This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
55+
const isSourceAudio = this.player_.currentType().substring(0, 5) === 'audio';
56+
const isAudioMode =
57+
isSourceAudio || this.player_.audioPosterMode() || this.player_.audioOnlyMode();
58+
59+
if (!isAudioMode) {
60+
this.show();
61+
62+
return;
63+
}
64+
65+
if (this.player_.isInPictureInPicture()) {
66+
this.player_.exitPictureInPicture();
67+
}
68+
69+
this.hide();
6070
}
6171

6272
/**
@@ -117,6 +127,18 @@ class PictureInPictureToggle extends Button {
117127
}
118128
}
119129

130+
/**
131+
* Show the `Component`s element if it is hidden by removing the
132+
* 'vjs-hidden' class name from it only in browsers that support the Picture-in-Picture API.
133+
*/
134+
show() {
135+
// Does not allow to display the pictureInPictureToggle in browsers that do not support the Picture-in-Picture API, e.g. Firefox.
136+
if (typeof document.exitPictureInPicture !== 'function') {
137+
return;
138+
}
139+
140+
super.show();
141+
}
120142
}
121143

122144
/**

test/unit/controls.test.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,11 @@ QUnit.test('Picture-in-Picture control is hidden when the source is audio', func
293293
player.src({src: 'example.mp4', type: 'video/mp4'});
294294
player.trigger('loadedmetadata');
295295

296-
assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden initially');
296+
if (document.exitPictureInPicture) {
297+
assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden initially');
298+
} else {
299+
assert.ok(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden if PiP is not supported');
300+
}
297301

298302
player.src({src: 'example1.mp3', type: 'audio/mp3'});
299303
player.trigger('loadedmetadata');
@@ -318,7 +322,11 @@ QUnit.test('Picture-in-Picture control is displayed if docPiP is enabled', funct
318322
player.src({src: 'example.mp4', type: 'video/mp4'});
319323
player.trigger('loadedmetadata');
320324

321-
assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
325+
if (document.exitPictureInPicture) {
326+
assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
327+
} else {
328+
assert.ok(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden if PiP is not supported');
329+
}
322330

323331
player.dispose();
324332
pictureInPictureToggle.dispose();
@@ -327,6 +335,24 @@ QUnit.test('Picture-in-Picture control is displayed if docPiP is enabled', funct
327335
}
328336
});
329337

338+
QUnit.test('Picture-in-Picture control should only be displayed if the browser supports it', function(assert) {
339+
const player = TestHelpers.makePlayer();
340+
const pictureInPictureToggle = new PictureInPictureToggle(player);
341+
342+
player.trigger('loadedmetadata');
343+
344+
if (document.exitPictureInPicture) {
345+
// Browser that does support PiP
346+
assert.false(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
347+
} else {
348+
// Browser that does not support PiP
349+
assert.true(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden');
350+
}
351+
352+
player.dispose();
353+
pictureInPictureToggle.dispose();
354+
});
355+
330356
QUnit.test('Fullscreen control text should be correct when fullscreenchange is triggered', function(assert) {
331357
const player = TestHelpers.makePlayer({controlBar: false});
332358
const fullscreentoggle = new FullscreenToggle(player);

test/unit/player.test.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,15 +3150,20 @@ QUnit.test('audioOnlyMode(true/false) hides/shows video-specific control bar com
31503150
controlBar.getChild('ChaptersButton').update();
31513151

31523152
player.trigger('ready');
3153+
player.trigger('loadedmetadata');
31533154
player.hasStarted(true);
31543155

31553156
// Show all control bar children
31563157
allChildren.forEach(child => {
31573158
const el = controlBar.getChild(child) && controlBar.getChild(child).el_;
31583159

31593160
if (el) {
3160-
// Sanity check that component is showing
3161-
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is initially visible`);
3161+
if (!document.exitPictureInPicture && child === 'PictureInPictureToggle') {
3162+
assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is not visible if PiP is not supported`);
3163+
} else {
3164+
// Sanity check that component is showing
3165+
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is initially visible`);
3166+
}
31623167
}
31633168
});
31643169

@@ -3187,7 +3192,11 @@ QUnit.test('audioOnlyMode(true/false) hides/shows video-specific control bar com
31873192
const el = controlBar.getChild(child) && controlBar.getChild(child).el_;
31883193

31893194
if (el) {
3190-
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is shown`);
3195+
if (!document.exitPictureInPicture && child === 'PictureInPictureToggle') {
3196+
assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is not visible if PiP is not supported`);
3197+
} else {
3198+
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is shown`);
3199+
}
31913200
}
31923201
});
31933202
})

0 commit comments

Comments
 (0)