Skip to content

Commit

Permalink
fix(picture-in-picture-control): hide the component in non-compatible…
Browse files Browse the repository at this point in the history
… browsers (#7899)
  • Loading branch information
amtins authored May 31, 2023
1 parent 20df248 commit d524e57
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 30 deletions.
10 changes: 1 addition & 9 deletions src/js/control-bar/control-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* @file control-bar.js
*/
import Component from '../component.js';
import document from 'global/document';

// Required children
import './play-toggle.js';
Expand Down Expand Up @@ -73,17 +72,10 @@ ControlBar.prototype.options_ = {
'descriptionsButton',
'subsCapsButton',
'audioTrackButton',
'pictureInPictureToggle',
'fullscreenToggle'
]
};

if ('exitPictureInPicture' in document) {
ControlBar.prototype.options_.children.splice(
ControlBar.prototype.options_.children.length - 1,
0,
'pictureInPictureToggle'
);
}

Component.registerComponent('ControlBar', ControlBar);
export default ControlBar;
54 changes: 38 additions & 16 deletions src/js/control-bar/picture-in-picture-toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,10 @@ class PictureInPictureToggle extends Button {
*/
constructor(player, options) {
super(player, options);

this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], (e) => this.handlePictureInPictureChange(e));
this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], (e) => this.handlePictureInPictureEnabledChange(e));

this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => {
// This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
const isSourceAudio = player.currentType().substring(0, 5) === 'audio';

if (isSourceAudio || player.audioPosterMode() || player.audioOnlyMode()) {
if (player.isInPictureInPicture()) {
player.exitPictureInPicture();
}
this.hide();
} else {
this.show();
}

});
this.on(player, ['loadedmetadata', 'audioonlymodechange', 'audiopostermodechange'], () => this.handlePictureInPictureAudioModeChange());

// TODO: Deactivate button on player emptied event.
this.disable();
Expand All @@ -56,7 +43,30 @@ class PictureInPictureToggle extends Button {
* The DOM `className` for this object.
*/
buildCSSClass() {
return `vjs-picture-in-picture-control ${super.buildCSSClass()}`;
return `vjs-picture-in-picture-control vjs-hidden ${super.buildCSSClass()}`;
}

/**
* Displays or hides the button depending on the audio mode detection.
* Exits picture-in-picture if it is enabled when switching to audio mode.
*/
handlePictureInPictureAudioModeChange() {
// This audio detection will not detect HLS or DASH audio-only streams because there was no reliable way to detect them at the time
const isSourceAudio = this.player_.currentType().substring(0, 5) === 'audio';
const isAudioMode =
isSourceAudio || this.player_.audioPosterMode() || this.player_.audioOnlyMode();

if (!isAudioMode) {
this.show();

return;
}

if (this.player_.isInPictureInPicture()) {
this.player_.exitPictureInPicture();
}

this.hide();
}

/**
Expand Down Expand Up @@ -117,6 +127,18 @@ class PictureInPictureToggle extends Button {
}
}

/**
* Show the `Component`s element if it is hidden by removing the
* 'vjs-hidden' class name from it only in browsers that support the Picture-in-Picture API.
*/
show() {
// Does not allow to display the pictureInPictureToggle in browsers that do not support the Picture-in-Picture API, e.g. Firefox.
if (typeof document.exitPictureInPicture !== 'function') {
return;
}

super.show();
}
}

/**
Expand Down
30 changes: 28 additions & 2 deletions test/unit/controls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,11 @@ QUnit.test('Picture-in-Picture control is hidden when the source is audio', func
player.src({src: 'example.mp4', type: 'video/mp4'});
player.trigger('loadedmetadata');

assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden initially');
if (document.exitPictureInPicture) {
assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden initially');
} else {
assert.ok(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden if PiP is not supported');
}

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

assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
if (document.exitPictureInPicture) {
assert.notOk(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
} else {
assert.ok(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden if PiP is not supported');
}

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

QUnit.test('Picture-in-Picture control should only be displayed if the browser supports it', function(assert) {
const player = TestHelpers.makePlayer();
const pictureInPictureToggle = new PictureInPictureToggle(player);

player.trigger('loadedmetadata');

if (document.exitPictureInPicture) {
// Browser that does support PiP
assert.false(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is not hidden');
} else {
// Browser that does not support PiP
assert.true(pictureInPictureToggle.hasClass('vjs-hidden'), 'pictureInPictureToggle button is hidden');
}

player.dispose();
pictureInPictureToggle.dispose();
});

QUnit.test('Fullscreen control text should be correct when fullscreenchange is triggered', function(assert) {
const player = TestHelpers.makePlayer({controlBar: false});
const fullscreentoggle = new FullscreenToggle(player);
Expand Down
15 changes: 12 additions & 3 deletions test/unit/player.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3150,15 +3150,20 @@ QUnit.test('audioOnlyMode(true/false) hides/shows video-specific control bar com
controlBar.getChild('ChaptersButton').update();

player.trigger('ready');
player.trigger('loadedmetadata');
player.hasStarted(true);

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

if (el) {
// Sanity check that component is showing
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is initially visible`);
if (!document.exitPictureInPicture && child === 'PictureInPictureToggle') {
assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is not visible if PiP is not supported`);
} else {
// Sanity check that component is showing
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is initially visible`);
}
}
});

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

if (el) {
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is shown`);
if (!document.exitPictureInPicture && child === 'PictureInPictureToggle') {
assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is not visible if PiP is not supported`);
} else {
assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is shown`);
}
}
});
})
Expand Down

0 comments on commit d524e57

Please sign in to comment.