From 22a4c0b3b3bee9cb1e3b3d531389511b2048914a Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 10 Feb 2022 17:55:00 -0500 Subject: [PATCH 01/16] audioOnlyMode wip --- src/css/components/_captions.scss | 4 + src/css/components/_chapters.scss | 4 + src/css/components/_control-bar.scss | 10 +- src/css/components/_descriptions.scss | 4 + src/css/components/_fullscreen.scss | 5 + src/css/components/_layout.scss | 4 + src/css/components/_picture-in-picture.scss | 5 + src/css/components/_subs-caps.scss | 4 + src/js/player.js | 101 +++++++++++++++++++- test/unit/player.test.js | 38 ++++++++ 10 files changed, 174 insertions(+), 5 deletions(-) diff --git a/src/css/components/_captions.scss b/src/css/components/_captions.scss index 1fe22af244..8cffef9f68 100644 --- a/src/css/components/_captions.scss +++ b/src/css/components/_captions.scss @@ -1,3 +1,7 @@ .video-js .vjs-captions-button .vjs-icon-placeholder { @extend .vjs-icon-captions; } + +.video-js.vjs-audio-only-mode .vjs-captions-button { + display: none; +} diff --git a/src/css/components/_chapters.scss b/src/css/components/_chapters.scss index 7eab3e8036..5765cfe4a7 100644 --- a/src/css/components/_chapters.scss +++ b/src/css/components/_chapters.scss @@ -2,6 +2,10 @@ @extend .vjs-icon-chapters; } +.video-js.vjs-audio-only-mode .vjs-chapters-button { + display: none; +} + .vjs-chapters-button .vjs-menu ul { width: 24em; } diff --git a/src/css/components/_control-bar.scss b/src/css/components/_control-bar.scss index e10de8df1d..8c7d44d63d 100644 --- a/src/css/components/_control-bar.scss +++ b/src/css/components/_control-bar.scss @@ -10,8 +10,9 @@ @include background-color-with-alpha($primary-background-color, $primary-background-transparency); } -// Video has started playing -.vjs-has-started .vjs-control-bar { +// Video has started playing or we are in audioOnlyMode +.vjs-has-started .vjs-control-bar, +.vjs-audio-only-mode .vjs-control-bar { @include display-flex; visibility: visible; opacity: 1; @@ -41,8 +42,9 @@ display: none !important; } -// Don't hide the control bar if it's audio -.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { +// Don't hide the control bar if it's audio or in audioOnlyMode +.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar, +.vjs-audio-only-mode.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { opacity: 1; visibility: visible; } diff --git a/src/css/components/_descriptions.scss b/src/css/components/_descriptions.scss index 854a1cbb9c..9cf8d95d6f 100644 --- a/src/css/components/_descriptions.scss +++ b/src/css/components/_descriptions.scss @@ -1,3 +1,7 @@ .video-js .vjs-descriptions-button .vjs-icon-placeholder { @extend .vjs-icon-audio-description; } + +.video-js.vjs-audio-only-mode .vjs-descriptions-button { + display: none; +} diff --git a/src/css/components/_fullscreen.scss b/src/css/components/_fullscreen.scss index c68343833a..a9deba41f1 100644 --- a/src/css/components/_fullscreen.scss +++ b/src/css/components/_fullscreen.scss @@ -6,6 +6,11 @@ @extend .vjs-icon-fullscreen-enter; } } + +.video-js.vjs-audio-only-mode .vjs-fullscreen-control { + display: none; +} + // Switch to the exit icon when the player is in fullscreen .video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder { @extend .vjs-icon-fullscreen-exit; diff --git a/src/css/components/_layout.scss b/src/css/components/_layout.scss index d6b5a18366..3b2c44e9fb 100644 --- a/src/css/components/_layout.scss +++ b/src/css/components/_layout.scss @@ -111,6 +111,10 @@ height: 100%; } +.video-js.vjs-audio-only-mode .vjs-tech { + display: none; +} + // Fullscreen Styles body.vjs-full-window { padding: 0; diff --git a/src/css/components/_picture-in-picture.scss b/src/css/components/_picture-in-picture.scss index e3426571ef..cfba09b928 100644 --- a/src/css/components/_picture-in-picture.scss +++ b/src/css/components/_picture-in-picture.scss @@ -6,6 +6,11 @@ @extend .vjs-icon-picture-in-picture-enter; } } + +.video-js.vjs-audio-only-mode .vjs-picture-in-picture-control { + display: none; +} + // 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; diff --git a/src/css/components/_subs-caps.scss b/src/css/components/_subs-caps.scss index d3b1ab759d..89bc353892 100644 --- a/src/css/components/_subs-caps.scss +++ b/src/css/components/_subs-caps.scss @@ -25,3 +25,7 @@ font-size: 1.5em; line-height: inherit; } + +.video-js.vjs-audio-only-mode .vjs-subs-caps-button { + display: none; +} diff --git a/src/js/player.js b/src/js/player.js index 69655f3b89..91a08aa1f1 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -54,6 +54,7 @@ import './live-tracker.js'; // Import Html5 tech, at least for disposing the original video tag. import './tech/html5.js'; +import videojs from './video.js'; // The following tech events are simply re-triggered // on the player when they happen @@ -395,6 +396,12 @@ class Player extends Component { // Init debugEnabled_ this.debugEnabled_ = false; + // Init state audioOnlyMode_ + this.audioOnlyMode_ = false; + + // Init state hiddenPlayerChildren_ + this.hiddenPlayerChildren_ = []; + // if the global option object was accidentally blown away by // someone, bail early with an informative error if (!this.options_ || @@ -574,6 +581,7 @@ class Player extends Component { this.breakpoints(this.options_.breakpoints); this.responsive(this.options_.responsive); + this.audioOnlyMode(this.options_.audioOnlyMode); } /** @@ -4290,6 +4298,96 @@ class Player extends Component { return !!this.isAudio_; } + /** + * Get the current audioOnlyMode state or set audioOnlyMode to true or false. + * + * Setting this to `true` will hide all player components except the control bar, + * as well as control bar components needed only for video. + * + * @param {boolean} [value] + * The value to set audioOnlyMode to. + * + * @return {boolean} + * True if audioOnlyMode is on, false otherwise. + */ + audioOnlyMode(value) { + if (value === undefined) { + return this.audioOnlyMode_; + } + + if (value === this.audioOnlyMode_) { + return; + } + + if (typeof value !== 'boolean') { + videojs.log.warn('audioOnlyMode can only be enabled or disabled using a boolean value'); + return; + } + + if (value === true) { + // Update styling immediately so we can get the control bar's height + this.addClass('vjs-audio-only-mode'); + + const playerChildren = this.children(); + const controlBar = this.getChild('ControlBar'); + const controlBarHeight = controlBar && controlBar.height(); + + // Hide all player components except the control bar + playerChildren.forEach(child => { + if (child.name_ === 'ControlBar') { + return; + } + + if (child.el_ && child.hide && !child.hasClass('vjs-hidden')) { + child.hide(); + + this.hiddenPlayerChildren_.push(child); + } + }); + + // Set the player height the same as the control bar + this.height(controlBarHeight); + + // Show control bar + this.userActive(true); + + // Fullscreen is not supported in audioOnlyMode, so exit if we need to + if (this.isFullscreen()) { + this.exitFullscreen(); + } + + this.audioOnlyMode_ = true; + + this.trigger('audioonlymodechange'); + + videojs.log(this.hiddenPlayerChildren_); + } else { + this.removeClass('vjs-audio-only-mode'); + + // Show player components that were previously hidden + if (this.hiddenPlayerChildren_.length > 0) { + this.hiddenPlayerChildren_.forEach(child => { + if (child.name_ === 'ControlBar') { + return; + } + + if (child.el_ && child.show) { + child.show(); + } + }); + } + + this.height(this.options_.height); + + // Show control bar + this.userActive(true); + + this.audioOnlyMode_ = false; + + this.trigger('audioonlymodechange'); + } + } + /** * A helper method for adding a {@link TextTrack} to our * {@link TextTrackList}. @@ -5096,7 +5194,8 @@ Player.prototype.options_ = { }, breakpoints: {}, - responsive: false + responsive: false, + audioOnlyMode: false }; [ diff --git a/test/unit/player.test.js b/test/unit/player.test.js index fd5a120da4..4c92441a74 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2770,3 +2770,41 @@ QUnit.test('playbackRates only accepts arrays of numbers', function(assert) { player.dispose(); }); + +// QUnit.test('audioOnlyMode(true) hides player components except control bar', function(assert) { +// const player = TestHelpers.makePlayer({}); +// const components = ['PosterImage', 'TextTrackDisplay']; + +// components.forEach(component => { +// const el = player.getChild(component).el(); + +// assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'block'); +// }); + +// player.audioOnlyMode(true); + +// components.forEach(component => { +// const el = player.getChild(component).el(); + +// assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none'); +// }); +// }); + +// QUnit.test('audioOnlyMode(false) shows player components previously hidden', function(assert) { +// // TestHelpers.getComputedStyle(el, 'width'); + +// }); + +// QUnit.test('audioOnlyMode(true) hides video-specific control bar components', function(assert) { +// // TestHelpers.getComputedStyle(el, 'width'); + +// }); + +// QUnit.test('audioOnlyMode(false) shows video-specific control bar components previously hidden', function(assert) { +// // TestHelpers.getComputedStyle(el, 'width'); + +// }); + +// QUnit.test('audioOnlyMode() gets the current audioOnlyMode_ state', function(assert) { + +// }); From c4cd4dd124a338406d2e4983702e74b8dae5323c Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 10 Feb 2022 18:15:59 -0500 Subject: [PATCH 02/16] fix incorrect logs --- src/js/player.js | 8 +++----- test/unit/player.test.js | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/js/player.js b/src/js/player.js index 91a08aa1f1..67a23c2ed7 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -54,7 +54,6 @@ import './live-tracker.js'; // Import Html5 tech, at least for disposing the original video tag. import './tech/html5.js'; -import videojs from './video.js'; // The following tech events are simply re-triggered // on the player when they happen @@ -4320,7 +4319,7 @@ class Player extends Component { } if (typeof value !== 'boolean') { - videojs.log.warn('audioOnlyMode can only be enabled or disabled using a boolean value'); + log.warn('audioOnlyMode can only be enabled or disabled using a boolean value'); return; } @@ -4332,7 +4331,8 @@ class Player extends Component { const controlBar = this.getChild('ControlBar'); const controlBarHeight = controlBar && controlBar.height(); - // Hide all player components except the control bar + // Hide all player components except the control bar. Control bar components + // needed only for video are hidden with CSS playerChildren.forEach(child => { if (child.name_ === 'ControlBar') { return; @@ -4359,8 +4359,6 @@ class Player extends Component { this.audioOnlyMode_ = true; this.trigger('audioonlymodechange'); - - videojs.log(this.hiddenPlayerChildren_); } else { this.removeClass('vjs-audio-only-mode'); diff --git a/test/unit/player.test.js b/test/unit/player.test.js index 4c92441a74..0a8c0eaa69 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2771,7 +2771,7 @@ QUnit.test('playbackRates only accepts arrays of numbers', function(assert) { player.dispose(); }); -// QUnit.test('audioOnlyMode(true) hides player components except control bar', function(assert) { +// QUnit.only('audioOnlyMode(true) hides player components except control bar', function(assert) { // const player = TestHelpers.makePlayer({}); // const components = ['PosterImage', 'TextTrackDisplay']; From 72b714d365a0ce830b4c2de6c56a6f5c21008a5f Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Wed, 16 Feb 2022 11:34:15 -0500 Subject: [PATCH 03/16] add tests --- src/css/components/_chapters.scss | 4 - test/unit/player.test.js | 177 ++++++++++++++++++++++++++---- 2 files changed, 153 insertions(+), 28 deletions(-) diff --git a/src/css/components/_chapters.scss b/src/css/components/_chapters.scss index 5765cfe4a7..7eab3e8036 100644 --- a/src/css/components/_chapters.scss +++ b/src/css/components/_chapters.scss @@ -2,10 +2,6 @@ @extend .vjs-icon-chapters; } -.video-js.vjs-audio-only-mode .vjs-chapters-button { - display: none; -} - .vjs-chapters-button .vjs-menu ul { width: 24em; } diff --git a/test/unit/player.test.js b/test/unit/player.test.js index 0a8c0eaa69..900778c041 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2771,40 +2771,169 @@ QUnit.test('playbackRates only accepts arrays of numbers', function(assert) { player.dispose(); }); -// QUnit.only('audioOnlyMode(true) hides player components except control bar', function(assert) { -// const player = TestHelpers.makePlayer({}); -// const components = ['PosterImage', 'TextTrackDisplay']; +QUnit.test('audioOnlyMode can be set by option', function(assert) { + const player = TestHelpers.makePlayer({ audioOnlyMode: true }); -// components.forEach(component => { -// const el = player.getChild(component).el(); + assert.equal(player.audioOnlyMode(), true, 'defaults to false'); + assert.equal(player.hasClass('vjs-audio-only-mode'), true, 'defaults to false'); +}); -// assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'block'); -// }); +QUnit.test('audioOnlyMode() gets the correct audioOnlyMode state', function(assert) { + const player = TestHelpers.makePlayer({}); -// player.audioOnlyMode(true); + assert.equal(player.audioOnlyMode(), false, 'defaults to false'); -// components.forEach(component => { -// const el = player.getChild(component).el(); + player.audioOnlyMode(true); + assert.equal(player.audioOnlyMode(), true, 'returns updated state after enabled'); -// assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none'); -// }); -// }); + player.audioOnlyMode(false); + assert.equal(player.audioOnlyMode(), false, 'returns updated state after disabled'); +}); -// QUnit.test('audioOnlyMode(false) shows player components previously hidden', function(assert) { -// // TestHelpers.getComputedStyle(el, 'width'); +QUnit.test('audioOnlyMode(true/false) adds or removes vjs-audio-only-mode class to player', function(assert) { + const player = TestHelpers.makePlayer({}); -// }); + assert.equal(player.hasClass('vjs-audio-only-mode'), false, 'class not initially present'); + + player.audioOnlyMode(true); + assert.equal(player.hasClass('vjs-audio-only-mode'), true, 'class was added'); + + player.audioOnlyMode(false); + assert.equal(player.hasClass('vjs-audio-only-mode'), false, 'class was removed'); +}); -// QUnit.test('audioOnlyMode(true) hides video-specific control bar components', function(assert) { -// // TestHelpers.getComputedStyle(el, 'width'); +QUnit.test('audioOnlyMode(true) makes player height equal to control bar height', function(assert) { + const player = TestHelpers.makePlayer({controls: true, height: 600}); + + player.hasStarted(true); + + const controlBarHeight = player.getChild('ControlBar').height(); + const playerHeight = player.height(); + + assert.notEqual(playerHeight, controlBarHeight, 'heights are not the same'); + + player.audioOnlyMode(true); + assert.equal(player.height(), controlBarHeight, 'player height set to height of control bar in audioOnlyMode'); + +}); -// }); +QUnit.test('audioOnlyMode(true/false) hides/shows player components except control bar', function(assert) { + const player = TestHelpers.makePlayer({controls: true}); -// QUnit.test('audioOnlyMode(false) shows video-specific control bar components previously hidden', function(assert) { -// // TestHelpers.getComputedStyle(el, 'width'); + player.hasStarted(true); -// }); + assert.equal(TestHelpers.getComputedStyle(player.getChild('TextTrackDisplay').el_, 'display'), 'block', 'TextTrackDisplay is initially visible'); + assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'block', 'Tech is initially visible'); + assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is initially visible'); -// QUnit.test('audioOnlyMode() gets the current audioOnlyMode_ state', function(assert) { + player.audioOnlyMode(true); -// }); + assert.equal(TestHelpers.getComputedStyle(player.getChild('TextTrackDisplay').el_, 'display'), 'none', 'TextTrackDisplay is hidden'); + assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'none', 'Tech is hidden'); + assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is still visible'); + + // Sanity check that all non-ControlBar player children are hidden + player.children().forEach(child => { + const el = child.el_; + + if (el) { + if (child.name_ !== 'ControlBar') { + assert.equal(TestHelpers.getComputedStyle(child.el_, 'display') === 'none', true, 'non-controlBar component is hidden'); + } + } + }); + + player.audioOnlyMode(false); + + assert.equal(TestHelpers.getComputedStyle(player.getChild('TextTrackDisplay').el_, 'display'), 'block', 'TextTrackDisplay is visible again'); + assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'block', 'Tech is visible again'); + assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is still visible'); +}); + +QUnit.test('audioOnlyMode(true/false) hides/shows video-specific control bar components', function(assert) { + const tracks = ['captions', 'subtitles', 'descriptions', 'chapters'].map(kind => { + return { + kind, + label: 'English' + }; + }); + const player = TestHelpers.makePlayer({controls: true, tracks, playbackRates: [1, 2]}); + + this.clock.tick(1000); + + const controlBar = player.getChild('ControlBar'); + const childrenShownInAudioOnlyMode = [ + 'PlayToggle', + 'VolumePanel', + 'ProgressControl', + 'PlaybackRateMenuButton', + 'ChaptersButton', + 'RemainingTimeDisplay' + ]; + const childrenHiddenInAudioOnlyMode = [ + 'CaptionsButton', + 'DescriptionsButton', + 'FullscreenToggle', + 'PictureInPictureToggle', + 'SubsCapsButton' + ]; + + const allChildren = childrenShownInAudioOnlyMode.concat(childrenHiddenInAudioOnlyMode); + + const chapters = player.textTracks()[3]; + + chapters.addCue({ + startTime: 0, + endTime: 2, + text: 'Chapter 1' + }); + chapters.addCue({ + startTime: 2, + endTime: 4, + text: 'Chapter 2' + }); + + // ChaptersButton only shows once cues added and update() called + controlBar.getChild('ChaptersButton').update(); + + 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`); + } + }); + + player.audioOnlyMode(true); + + childrenHiddenInAudioOnlyMode.forEach(child => { + const el = controlBar.getChild(child) && controlBar.getChild(child).el_; + + if (el) { + assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is hidden`); + } + }); + + childrenShownInAudioOnlyMode.forEach(child => { + const el = controlBar.getChild(child) && controlBar.getChild(child).el_; + + if (el) { + assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is still shown`); + } + }); + + player.audioOnlyMode(false); + + // Check that all are showing again + allChildren.concat(childrenHiddenInAudioOnlyMode).forEach(child => { + const el = controlBar.getChild(child) && controlBar.getChild(child).el_; + + if (el) { + assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is shown`); + } + }); +}); From e0b89bdbec777f0ee366aa121d48bcceadbd478c Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Wed, 16 Feb 2022 14:00:02 -0500 Subject: [PATCH 04/16] minor code changes and add another test --- src/js/player.js | 13 +++---------- test/unit/player.test.js | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/js/player.js b/src/js/player.js index 67a23c2ed7..fbd9f6956e 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -4338,7 +4338,7 @@ class Player extends Component { return; } - if (child.el_ && child.hide && !child.hasClass('vjs-hidden')) { + if (child.el_ && !child.hasClass('vjs-hidden')) { child.hide(); this.hiddenPlayerChildren_.push(child); @@ -4364,17 +4364,10 @@ class Player extends Component { // Show player components that were previously hidden if (this.hiddenPlayerChildren_.length > 0) { - this.hiddenPlayerChildren_.forEach(child => { - if (child.name_ === 'ControlBar') { - return; - } - - if (child.el_ && child.show) { - child.show(); - } - }); + this.hiddenPlayerChildren_.forEach(child => child.show()); } + // Reset player height this.height(this.options_.height); // Show control bar diff --git a/test/unit/player.test.js b/test/unit/player.test.js index 900778c041..97a289ab28 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2802,6 +2802,25 @@ QUnit.test('audioOnlyMode(true/false) adds or removes vjs-audio-only-mode class assert.equal(player.hasClass('vjs-audio-only-mode'), false, 'class was removed'); }); +QUnit.test('setting audioOnlyMode() triggers audioonlymodechange event', function(assert) { + const player = TestHelpers.makePlayer({}); + let audioOnlyModeState = false; + let audioOnlyModeChangeEvents = 0; + + player.on('audioonlymodechange', () => { + audioOnlyModeChangeEvents++; + audioOnlyModeState = player.audioOnlyMode(); + }); + + player.audioOnlyMode(true); + assert.equal(audioOnlyModeState, true, 'state is correct'); + assert.equal(audioOnlyModeChangeEvents, 1, 'event fired once'); + + player.audioOnlyMode(false); + assert.equal(audioOnlyModeState, false, 'state is correct'); + assert.equal(audioOnlyModeChangeEvents, 2, 'event fired again'); +}); + QUnit.test('audioOnlyMode(true) makes player height equal to control bar height', function(assert) { const player = TestHelpers.makePlayer({controls: true, height: 600}); From 226574d0a9b7b44be9021f3785e4efd8fa23a053 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Wed, 16 Feb 2022 14:22:34 -0500 Subject: [PATCH 05/16] update docs --- docs/guides/options.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/guides/options.md b/docs/guides/options.md index 3ebf4eead7..5f990894a8 100644 --- a/docs/guides/options.md +++ b/docs/guides/options.md @@ -21,6 +21,7 @@ * [width](#width) * [Video.js-specific Options](#videojs-specific-options) * [aspectRatio](#aspectratio) + * [audioOnlyMode](#audioonlymode) * [autoSetup](#autosetup) * [breakpoints](#breakpoints) * [children](#children) @@ -181,6 +182,12 @@ Puts the player in [fluid](#fluid) mode and the value is used when calculating t Alternatively, the classes `vjs-16-9`, `vjs-9-16`, `vjs-4-3` or `vjs-1-1` can be added to the player. +### `audioOnlyMode` + +> Type: `boolean` +> Default: `false` +> If set to true, it hides all player components except the control bar, as well as any specific controls that are needed only for video. This option can be set to `true` or `false` by calling `audioPosterMode([true|false])` at runtime. + ### `autoSetup` > Type: `boolean` From ec78eb74828822fda277ce9560b504c7105d68d8 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Wed, 16 Feb 2022 14:25:50 -0500 Subject: [PATCH 06/16] fix formatting --- docs/guides/options.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guides/options.md b/docs/guides/options.md index 5f990894a8..921e169a4d 100644 --- a/docs/guides/options.md +++ b/docs/guides/options.md @@ -186,7 +186,8 @@ Alternatively, the classes `vjs-16-9`, `vjs-9-16`, `vjs-4-3` or `vjs-1-1` can be > Type: `boolean` > Default: `false` -> If set to true, it hides all player components except the control bar, as well as any specific controls that are needed only for video. This option can be set to `true` or `false` by calling `audioPosterMode([true|false])` at runtime. + +If set to true, it hides all player components except the control bar, as well as any specific controls that are needed only for video. This option can be set to `true` or `false` by calling `audioPosterMode([true|false])` at runtime. ### `autoSetup` From bfba831437f1a4d14266f64b45d270a07f98604b Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 17 Feb 2022 13:50:05 -0500 Subject: [PATCH 07/16] fix typo --- docs/guides/options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/options.md b/docs/guides/options.md index 921e169a4d..f96f37bf84 100644 --- a/docs/guides/options.md +++ b/docs/guides/options.md @@ -187,7 +187,7 @@ Alternatively, the classes `vjs-16-9`, `vjs-9-16`, `vjs-4-3` or `vjs-1-1` can be > Type: `boolean` > Default: `false` -If set to true, it hides all player components except the control bar, as well as any specific controls that are needed only for video. This option can be set to `true` or `false` by calling `audioPosterMode([true|false])` at runtime. +If set to true, it hides all player components except the control bar, as well as any specific controls that are needed only for video. This option can be set to `true` or `false` by calling `audioOnlyMode([true|false])` at runtime. ### `autoSetup` From c9dab32f75497fefcbceb043d08030182cda944d Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 24 Feb 2022 11:23:20 -0500 Subject: [PATCH 08/16] Consolidate conditions Co-authored-by: Pat O'Neill --- src/js/player.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/js/player.js b/src/js/player.js index fbd9f6956e..4d0da9d9a9 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -4310,19 +4310,10 @@ class Player extends Component { * True if audioOnlyMode is on, false otherwise. */ audioOnlyMode(value) { - if (value === undefined) { + if (typeof value !== 'boolean' || value === this.audioOnlyMode_) { return this.audioOnlyMode_; } - if (value === this.audioOnlyMode_) { - return; - } - - if (typeof value !== 'boolean') { - log.warn('audioOnlyMode can only be enabled or disabled using a boolean value'); - return; - } - if (value === true) { // Update styling immediately so we can get the control bar's height this.addClass('vjs-audio-only-mode'); From 1f345c6418e655d4755dfbf20039c9e78beeabad Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 24 Feb 2022 11:24:48 -0500 Subject: [PATCH 09/16] Compare objects instead of name string Co-authored-by: Pat O'Neill --- src/js/player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/player.js b/src/js/player.js index 4d0da9d9a9..fb5cae5955 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -4325,7 +4325,7 @@ class Player extends Component { // Hide all player components except the control bar. Control bar components // needed only for video are hidden with CSS playerChildren.forEach(child => { - if (child.name_ === 'ControlBar') { + if (child === controlBar) { return; } From 67e01cbb3a5e7e8e8478d91744d86d9c3b4cf0ab Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 24 Feb 2022 13:01:18 -0500 Subject: [PATCH 10/16] code review changes --- src/js/player.js | 33 +++++++++++++-------------------- test/unit/player.test.js | 5 ++++- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/js/player.js b/src/js/player.js index fb5cae5955..cb1978e831 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -398,8 +398,11 @@ class Player extends Component { // Init state audioOnlyMode_ this.audioOnlyMode_ = false; - // Init state hiddenPlayerChildren_ - this.hiddenPlayerChildren_ = []; + // Init state audioOnlyCache_ + this.audioOnlyCache_ = { + playerHeight: null, + hiddenChildren: [] + }; // if the global option object was accidentally blown away by // someone, bail early with an informative error @@ -4332,42 +4335,32 @@ class Player extends Component { if (child.el_ && !child.hasClass('vjs-hidden')) { child.hide(); - this.hiddenPlayerChildren_.push(child); + this.audioOnlyCache_.hiddenChildren.push(child); } }); + this.audioOnlyCache_.playerHeight = this.height(); + // Set the player height the same as the control bar this.height(controlBarHeight); - // Show control bar - this.userActive(true); - // Fullscreen is not supported in audioOnlyMode, so exit if we need to if (this.isFullscreen()) { this.exitFullscreen(); } - - this.audioOnlyMode_ = true; - - this.trigger('audioonlymodechange'); } else { this.removeClass('vjs-audio-only-mode'); // Show player components that were previously hidden - if (this.hiddenPlayerChildren_.length > 0) { - this.hiddenPlayerChildren_.forEach(child => child.show()); - } + this.audioOnlyCache_.hiddenChildren.forEach(child => child.show()); // Reset player height - this.height(this.options_.height); - - // Show control bar - this.userActive(true); + this.height(this.audioOnlyCache_.playerHeight); + } - this.audioOnlyMode_ = false; + this.audioOnlyMode_ = value; - this.trigger('audioonlymodechange'); - } + this.trigger('audioonlymodechange'); } /** diff --git a/test/unit/player.test.js b/test/unit/player.test.js index 97a289ab28..74473fc1f7 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2821,7 +2821,7 @@ QUnit.test('setting audioOnlyMode() triggers audioonlymodechange event', functio assert.equal(audioOnlyModeChangeEvents, 2, 'event fired again'); }); -QUnit.test('audioOnlyMode(true) makes player height equal to control bar height', function(assert) { +QUnit.test('audioOnlyMode(true/false) changes player height', function(assert) { const player = TestHelpers.makePlayer({controls: true, height: 600}); player.hasStarted(true); @@ -2830,10 +2830,13 @@ QUnit.test('audioOnlyMode(true) makes player height equal to control bar height' const playerHeight = player.height(); assert.notEqual(playerHeight, controlBarHeight, 'heights are not the same'); + assert.equal(player.height(), playerHeight, 'player initial height is correct'); player.audioOnlyMode(true); assert.equal(player.height(), controlBarHeight, 'player height set to height of control bar in audioOnlyMode'); + player.audioOnlyMode(false); + assert.equal(player.height(), playerHeight, 'player reset to original height when disabling audioOnlyMode'); }); QUnit.test('audioOnlyMode(true/false) hides/shows player components except control bar', function(assert) { From 7365f2a68f942a9ef3887b8190f6f167fb729c03 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 3 Mar 2022 11:22:42 -0500 Subject: [PATCH 11/16] remove unnecessary equivalence check Co-authored-by: Gary Katsevman --- src/js/player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/player.js b/src/js/player.js index cb1978e831..ff00eb91d4 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -4317,7 +4317,7 @@ class Player extends Component { return this.audioOnlyMode_; } - if (value === true) { + if (value) { // Update styling immediately so we can get the control bar's height this.addClass('vjs-audio-only-mode'); From 5cba8e465ae3776aa13dded87d841eeeb2fcedab Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 3 Mar 2022 11:27:32 -0500 Subject: [PATCH 12/16] replace height() with currentHeight() Co-authored-by: Gary Katsevman --- src/js/player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/player.js b/src/js/player.js index ff00eb91d4..882b63dff5 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -4323,7 +4323,7 @@ class Player extends Component { const playerChildren = this.children(); const controlBar = this.getChild('ControlBar'); - const controlBarHeight = controlBar && controlBar.height(); + const controlBarHeight = controlBar && controlBar.currentHeight(); // Hide all player components except the control bar. Control bar components // needed only for video are hidden with CSS From 22efbc58311057bbc22ba57db72ceb6a9edc9b82 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Mon, 7 Mar 2022 12:25:47 -0500 Subject: [PATCH 13/16] rewrite for async pip and fs handling --- src/js/player.js | 104 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/src/js/player.js b/src/js/player.js index 882b63dff5..648d9f57fd 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -4300,6 +4300,51 @@ class Player extends Component { return !!this.isAudio_; } + updateAudioOnlyModeState_(value) { + this.audioOnlyMode_ = value; + this.trigger('audioonlymodechange'); + } + + enableAudioOnlyUI_() { + // Update styling immediately to show the control bar so we can get its height + this.addClass('vjs-audio-only-mode'); + + const playerChildren = this.children(); + const controlBar = this.getChild('ControlBar'); + const controlBarHeight = controlBar && controlBar.currentHeight(); + + // Hide all player components except the control bar. Control bar components + // needed only for video are hidden with CSS + playerChildren.forEach(child => { + if (child === controlBar) { + return; + } + + if (child.el_ && !child.hasClass('vjs-hidden')) { + child.hide(); + + this.audioOnlyCache_.hiddenChildren.push(child); + } + }); + + this.audioOnlyCache_.playerHeight = this.currentHeight(); + + // Set the player height the same as the control bar + this.height(controlBarHeight); + this.updateAudioOnlyModeState_(true); + } + + disableAudioOnlyUI_() { + this.removeClass('vjs-audio-only-mode'); + + // Show player components that were previously hidden + this.audioOnlyCache_.hiddenChildren.forEach(child => child.show()); + + // Reset player height + this.height(this.audioOnlyCache_.playerHeight); + this.updateAudioOnlyModeState_(false); + } + /** * Get the current audioOnlyMode state or set audioOnlyMode to true or false. * @@ -4309,58 +4354,51 @@ class Player extends Component { * @param {boolean} [value] * The value to set audioOnlyMode to. * - * @return {boolean} - * True if audioOnlyMode is on, false otherwise. + * @return {Promise|boolean} + * A Promise is returned when setting the state, and a boolean when getting + * the present state */ audioOnlyMode(value) { if (typeof value !== 'boolean' || value === this.audioOnlyMode_) { return this.audioOnlyMode_; } - if (value) { - // Update styling immediately so we can get the control bar's height - this.addClass('vjs-audio-only-mode'); + const PromiseClass = this.options_.Promise || window.Promise; - const playerChildren = this.children(); - const controlBar = this.getChild('ControlBar'); - const controlBarHeight = controlBar && controlBar.currentHeight(); + if (PromiseClass) { + // Enable Audio Only Mode + if (value) { + const exitPromises = []; - // Hide all player components except the control bar. Control bar components - // needed only for video are hidden with CSS - playerChildren.forEach(child => { - if (child === controlBar) { - return; + // Fullscreen and PiP are not supported in audioOnlyMode, so exit if we need to. + if (this.isInPictureInPicture()) { + exitPromises.push(this.exitPictureInPicture()); } - if (child.el_ && !child.hasClass('vjs-hidden')) { - child.hide(); - - this.audioOnlyCache_.hiddenChildren.push(child); + if (this.isFullscreen()) { + exitPromises.push(this.exitFullscreen()); } - }); - this.audioOnlyCache_.playerHeight = this.height(); + return PromiseClass.all(exitPromises).then(() => this.enableAudioOnlyUI_()); + } - // Set the player height the same as the control bar - this.height(controlBarHeight); + // Disable Audio Only Mode + return PromiseClass.resolve().then(() => this.disableAudioOnlyUI_()); + } + + if (value) { + if (this.isInPictureInPicture()) { + this.exitPictureInPicture(); + } - // Fullscreen is not supported in audioOnlyMode, so exit if we need to if (this.isFullscreen()) { this.exitFullscreen(); } - } else { - this.removeClass('vjs-audio-only-mode'); - // Show player components that were previously hidden - this.audioOnlyCache_.hiddenChildren.forEach(child => child.show()); - - // Reset player height - this.height(this.audioOnlyCache_.playerHeight); + this.enableAudioOnlyUI_(); + } else { + this.disableAudioOnlyUI_(); } - - this.audioOnlyMode_ = value; - - this.trigger('audioonlymodechange'); } /** From 89ac9c651def6557c4fbed0993477f46843a6ae7 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 8 Mar 2022 11:48:34 -0500 Subject: [PATCH 14/16] asyncify tests --- test/unit/player.test.js | 198 ++++++++++++++++++++++++--------------- 1 file changed, 125 insertions(+), 73 deletions(-) diff --git a/test/unit/player.test.js b/test/unit/player.test.js index 74473fc1f7..17c508ac6f 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2101,6 +2101,7 @@ QUnit.test('When VIDEOJS_NO_DYNAMIC_STYLE is set, apply sizing directly to the t // clear the HEAD before running this test const styles = document.querySelectorAll('style'); let i = styles.length; + const originalVjsNoDynamicStyling = window.VIDEOJS_NO_DYNAMIC_STYLE; while (i--) { const style = styles[i]; @@ -2128,6 +2129,7 @@ QUnit.test('When VIDEOJS_NO_DYNAMIC_STYLE is set, apply sizing directly to the t assert.equal(player.tech_.el().width, 600, 'the width is equal to 600'); assert.equal(player.tech_.el().height, 300, 'the height is equal 300'); player.dispose(); + window.VIDEOJS_NO_DYNAMIC_STYLE = originalVjsNoDynamicStyling; }); QUnit.test('should allow to register custom player when any player has not been created', function(assert) { @@ -2772,10 +2774,52 @@ QUnit.test('playbackRates only accepts arrays of numbers', function(assert) { }); QUnit.test('audioOnlyMode can be set by option', function(assert) { - const player = TestHelpers.makePlayer({ audioOnlyMode: true }); + assert.expect(4); + + const player = TestHelpers.makePlayer({audioOnlyMode: true}); + + player.one('audioonlymodechange', () => { + assert.equal(player.audioOnlyMode(), true, 'asynchronously set via option'); + assert.equal(player.hasClass('vjs-audio-only-mode'), true, 'class added asynchronously'); + }); - assert.equal(player.audioOnlyMode(), true, 'defaults to false'); - assert.equal(player.hasClass('vjs-audio-only-mode'), true, 'defaults to false'); + assert.equal(player.audioOnlyMode(), false, 'defaults to false'); + assert.equal(player.hasClass('vjs-audio-only-mode'), false, 'initially does not have class'); +}); + +QUnit.test('audioOnlyMode(true) returns Promise when promises are supported', function(assert) { + const player = TestHelpers.makePlayer({}); + const returnValTrue = player.audioOnlyMode(true); + + if (window.Promise) { + assert.ok(returnValTrue instanceof window.Promise, 'audioOnlyMode(true) returns Promise when supported'); + } else { + assert.equal(returnValTrue, undefined, 'audioOnlyMode(true) returns undefined when promises unsupported'); + } + + return returnValTrue; +}); + +QUnit.test('audioOnlyMode(false) returns Promise when promises are supported', function(assert) { + const player = TestHelpers.makePlayer({audioOnlyMode: true}); + + player.one('audioonlymodechange', () => { + const returnValFalse = player.audioOnlyMode(false); + + if (window.Promise) { + assert.ok(returnValFalse instanceof window.Promise, 'audioOnlyMode(false) returns Promise when supported'); + } else { + assert.equal(returnValFalse, undefined, 'audioOnlyMode(false) returns undefined when promises unsupported'); + } + + return returnValFalse; + }); +}); + +QUnit.test('audioOnlyMode() getter returns Boolean', function(assert) { + const player = TestHelpers.makePlayer({}); + + assert.ok(typeof player.audioOnlyMode() === 'boolean', 'getter correctly returns boolean'); }); QUnit.test('audioOnlyMode() gets the correct audioOnlyMode state', function(assert) { @@ -2783,11 +2827,11 @@ QUnit.test('audioOnlyMode() gets the correct audioOnlyMode state', function(asse assert.equal(player.audioOnlyMode(), false, 'defaults to false'); - player.audioOnlyMode(true); - assert.equal(player.audioOnlyMode(), true, 'returns updated state after enabled'); - - player.audioOnlyMode(false); - assert.equal(player.audioOnlyMode(), false, 'returns updated state after disabled'); + return player.audioOnlyMode(true) + .then(() => assert.equal(player.audioOnlyMode(), true, 'returns updated state after enabled')) + .then(() => player.audioOnlyMode(false)) + .then(() => assert.equal(player.audioOnlyMode(), false, 'returns updated state after disabled')) + .catch(() => assert.ok(false, 'test error')); }); QUnit.test('audioOnlyMode(true/false) adds or removes vjs-audio-only-mode class to player', function(assert) { @@ -2795,11 +2839,11 @@ QUnit.test('audioOnlyMode(true/false) adds or removes vjs-audio-only-mode class assert.equal(player.hasClass('vjs-audio-only-mode'), false, 'class not initially present'); - player.audioOnlyMode(true); - assert.equal(player.hasClass('vjs-audio-only-mode'), true, 'class was added'); - - player.audioOnlyMode(false); - assert.equal(player.hasClass('vjs-audio-only-mode'), false, 'class was removed'); + return player.audioOnlyMode(true) + .then(() => assert.equal(player.hasClass('vjs-audio-only-mode'), true, 'class was added')) + .then(() => player.audioOnlyMode(false)) + .then(() => assert.equal(player.hasClass('vjs-audio-only-mode'), false, 'class was removed')) + .catch(() => assert.ok(false, 'test error')); }); QUnit.test('setting audioOnlyMode() triggers audioonlymodechange event', function(assert) { @@ -2812,13 +2856,17 @@ QUnit.test('setting audioOnlyMode() triggers audioonlymodechange event', functio audioOnlyModeState = player.audioOnlyMode(); }); - player.audioOnlyMode(true); - assert.equal(audioOnlyModeState, true, 'state is correct'); - assert.equal(audioOnlyModeChangeEvents, 1, 'event fired once'); - - player.audioOnlyMode(false); - assert.equal(audioOnlyModeState, false, 'state is correct'); - assert.equal(audioOnlyModeChangeEvents, 2, 'event fired again'); + return player.audioOnlyMode(true) + .then(() => { + assert.equal(audioOnlyModeState, true, 'state is correct'); + assert.equal(audioOnlyModeChangeEvents, 1, 'event fired once'); + }) + .then(() => player.audioOnlyMode(false)) + .then(() => { + assert.equal(audioOnlyModeState, false, 'state is correct'); + assert.equal(audioOnlyModeChangeEvents, 2, 'event fired again'); + }) + .catch(() => assert.ok(false, 'test error')); }); QUnit.test('audioOnlyMode(true/false) changes player height', function(assert) { @@ -2826,17 +2874,17 @@ QUnit.test('audioOnlyMode(true/false) changes player height', function(assert) { player.hasStarted(true); - const controlBarHeight = player.getChild('ControlBar').height(); - const playerHeight = player.height(); + const controlBarHeight = player.getChild('ControlBar').currentHeight(); + const playerHeight = player.currentHeight(); assert.notEqual(playerHeight, controlBarHeight, 'heights are not the same'); - assert.equal(player.height(), playerHeight, 'player initial height is correct'); + assert.equal(player.currentHeight(), playerHeight, 'player initial height is correct'); - player.audioOnlyMode(true); - assert.equal(player.height(), controlBarHeight, 'player height set to height of control bar in audioOnlyMode'); - - player.audioOnlyMode(false); - assert.equal(player.height(), playerHeight, 'player reset to original height when disabling audioOnlyMode'); + return player.audioOnlyMode(true) + .then(() => assert.equal(player.currentHeight(), controlBarHeight, 'player height set to height of control bar in audioOnlyMode')) + .then(() => player.audioOnlyMode(false)) + .then(() => assert.equal(player.currentHeight(), playerHeight, 'player reset to original height when disabling audioOnlyMode')) + .catch(() => assert.ok(false, 'test error')); }); QUnit.test('audioOnlyMode(true/false) hides/shows player components except control bar', function(assert) { @@ -2848,28 +2896,30 @@ QUnit.test('audioOnlyMode(true/false) hides/shows player components except contr assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'block', 'Tech is initially visible'); assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is initially visible'); - player.audioOnlyMode(true); - - assert.equal(TestHelpers.getComputedStyle(player.getChild('TextTrackDisplay').el_, 'display'), 'none', 'TextTrackDisplay is hidden'); - assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'none', 'Tech is hidden'); - assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is still visible'); - - // Sanity check that all non-ControlBar player children are hidden - player.children().forEach(child => { - const el = child.el_; + return player.audioOnlyMode(true) + .then(() => { + assert.equal(TestHelpers.getComputedStyle(player.getChild('TextTrackDisplay').el_, 'display'), 'none', 'TextTrackDisplay is hidden'); + assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'none', 'Tech is hidden'); + assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is still visible'); - if (el) { - if (child.name_ !== 'ControlBar') { - assert.equal(TestHelpers.getComputedStyle(child.el_, 'display') === 'none', true, 'non-controlBar component is hidden'); - } - } - }); + // Sanity check that all non-ControlBar player children are hidden + player.children().forEach(child => { + const el = child.el_; - player.audioOnlyMode(false); - - assert.equal(TestHelpers.getComputedStyle(player.getChild('TextTrackDisplay').el_, 'display'), 'block', 'TextTrackDisplay is visible again'); - assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'block', 'Tech is visible again'); - assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is still visible'); + if (el) { + if (child.name_ !== 'ControlBar') { + assert.equal(TestHelpers.getComputedStyle(child.el_, 'display') === 'none', true, 'non-controlBar component is hidden'); + } + } + }); + }) + .then(() => player.audioOnlyMode(false)) + .then(() => { + assert.equal(TestHelpers.getComputedStyle(player.getChild('TextTrackDisplay').el_, 'display'), 'block', 'TextTrackDisplay is visible again'); + assert.equal(TestHelpers.getComputedStyle(player.tech(true).el_, 'display'), 'block', 'Tech is visible again'); + assert.equal(TestHelpers.getComputedStyle(player.getChild('ControlBar').el_, 'display'), 'flex', 'ControlBar is still visible'); + }) + .catch(() => assert.ok(false, 'test error')); }); QUnit.test('audioOnlyMode(true/false) hides/shows video-specific control bar components', function(assert) { @@ -2930,32 +2980,34 @@ QUnit.test('audioOnlyMode(true/false) hides/shows video-specific control bar com } }); - player.audioOnlyMode(true); - - childrenHiddenInAudioOnlyMode.forEach(child => { - const el = controlBar.getChild(child) && controlBar.getChild(child).el_; - - if (el) { - assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is hidden`); - } - }); - - childrenShownInAudioOnlyMode.forEach(child => { - const el = controlBar.getChild(child) && controlBar.getChild(child).el_; + return player.audioOnlyMode(true) + .then(() => { + childrenHiddenInAudioOnlyMode.forEach(child => { + const el = controlBar.getChild(child) && controlBar.getChild(child).el_; - if (el) { - assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is still shown`); - } - }); - - player.audioOnlyMode(false); + if (el) { + assert.equal(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is hidden`); + } + }); - // Check that all are showing again - allChildren.concat(childrenHiddenInAudioOnlyMode).forEach(child => { - const el = controlBar.getChild(child) && controlBar.getChild(child).el_; + childrenShownInAudioOnlyMode.forEach(child => { + const el = controlBar.getChild(child) && controlBar.getChild(child).el_; - if (el) { - assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is shown`); - } - }); + if (el) { + assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is still shown`); + } + }); + }) + .then(() => player.audioOnlyMode(false)) + .then(() => { + // Check that all are showing again + allChildren.concat(childrenHiddenInAudioOnlyMode).forEach(child => { + const el = controlBar.getChild(child) && controlBar.getChild(child).el_; + + if (el) { + assert.notEqual(TestHelpers.getComputedStyle(el, 'display'), 'none', `${child} is shown`); + } + }); + }) + .catch(() => assert.ok(false, 'test error')); }); From 3b70220ad37ee2a3db760b6d48de35f207ff8958 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 8 Mar 2022 12:16:12 -0500 Subject: [PATCH 15/16] update doc --- docs/guides/options.md | 2 +- test/unit/player.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/options.md b/docs/guides/options.md index cedbe4326f..d1634ef64c 100644 --- a/docs/guides/options.md +++ b/docs/guides/options.md @@ -188,7 +188,7 @@ Alternatively, the classes `vjs-16-9`, `vjs-9-16`, `vjs-4-3` or `vjs-1-1` can be > Type: `boolean` > Default: `false` -If set to true, it hides all player components except the control bar, as well as any specific controls that are needed only for video. This option can be set to `true` or `false` by calling `audioOnlyMode([true|false])` at runtime. +If set to true, it asynchronously hides all player components except the control bar, as well as any specific controls that are needed only for video. This option can be set to `true` or `false` by calling `audioOnlyMode([true|false])` at runtime. When used as a setter, it returns a Promise. When used as a getter, it returns a Boolean. ### `audioPosterMode` diff --git a/test/unit/player.test.js b/test/unit/player.test.js index 6b7d50aed2..f302c7c4ab 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2132,9 +2132,9 @@ QUnit.test('Make sure that player\'s style el respects VIDEOJS_NO_DYNAMIC_STYLE QUnit.test('When VIDEOJS_NO_DYNAMIC_STYLE is set, apply sizing directly to the tech el', function(assert) { // clear the HEAD before running this test + const originalVjsNoDynamicStyling = window.VIDEOJS_NO_DYNAMIC_STYLE; const styles = document.querySelectorAll('style'); let i = styles.length; - const originalVjsNoDynamicStyling = window.VIDEOJS_NO_DYNAMIC_STYLE; while (i--) { const style = styles[i]; From b9f39f7ceef66021a0f7f2d58fdc7c892763040f Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 8 Mar 2022 17:19:55 -0500 Subject: [PATCH 16/16] add test --- test/unit/player.test.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/test/unit/player.test.js b/test/unit/player.test.js index f302c7c4ab..e368c550a3 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -2826,8 +2826,6 @@ QUnit.test('audioOnlyMode(true) returns Promise when promises are supported', fu if (window.Promise) { assert.ok(returnValTrue instanceof window.Promise, 'audioOnlyMode(true) returns Promise when supported'); - } else { - assert.equal(returnValTrue, undefined, 'audioOnlyMode(true) returns undefined when promises unsupported'); } return returnValTrue; @@ -2841,8 +2839,6 @@ QUnit.test('audioOnlyMode(false) returns Promise when promises are supported', f if (window.Promise) { assert.ok(returnValFalse instanceof window.Promise, 'audioOnlyMode(false) returns Promise when supported'); - } else { - assert.equal(returnValFalse, undefined, 'audioOnlyMode(false) returns undefined when promises unsupported'); } return returnValFalse; @@ -2855,6 +2851,25 @@ QUnit.test('audioOnlyMode() getter returns Boolean', function(assert) { assert.ok(typeof player.audioOnlyMode() === 'boolean', 'getter correctly returns boolean'); }); +QUnit.test('audioOnlyMode(true/false) is synchronous and returns undefined when promises are unsupported', function(assert) { + const originalPromise = window.Promise; + const player = TestHelpers.makePlayer({}); + + window.Promise = undefined; + + const returnValTrue = player.audioOnlyMode(true); + + assert.equal(returnValTrue, undefined, 'return value is undefined'); + assert.ok(player.audioOnlyMode(), 'state synchronously set to true'); + + const returnValFalse = player.audioOnlyMode(false); + + assert.equal(returnValFalse, undefined, 'return value is undefined'); + assert.notOk(player.audioOnlyMode(), 'state synchronously set to false'); + + window.Promise = originalPromise; +}); + QUnit.test('audioOnlyMode() gets the correct audioOnlyMode state', function(assert) { const player = TestHelpers.makePlayer({});