Skip to content

Commit

Permalink
feat(ui): Add quality selection for audio-only content (#3649)
Browse files Browse the repository at this point in the history
Replaces resolution menu with audio quality menu when content is audio-only.

Fixes: #2071
  • Loading branch information
nbcl committed Apr 21, 2022
1 parent d1699de commit adc3502
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 49 deletions.
16 changes: 0 additions & 16 deletions test/ui/ui_integration.js
Expand Up @@ -454,22 +454,6 @@ describe('UI', () => {
expect(isChosen).not.toBe(null);
});

it('restores the resolutions menu after audio-only playback', async () => {
/** @type {HTMLElement} */
const resolutionButton = shaka.util.Dom.getElementByClassName(
'shaka-resolution-button', videoContainer);

// Load an audio-only clip. The menu should be hidden.
await player.load('test:sintel_audio_only_compiled');
expect(player.isAudioOnly()).toBe(true);
expect(resolutionButton.classList.contains('shaka-hidden')).toBe(true);

// Load an audio-video clip. The menu should be visible again.
await player.load('test:sintel_multi_lingual_multi_res_compiled');
expect(player.isAudioOnly()).toBe(false);
expect(resolutionButton.classList.contains('shaka-hidden')).toBe(false);
});

/**
* @return {Element}
*/
Expand Down
27 changes: 27 additions & 0 deletions test/ui/ui_unit.js
Expand Up @@ -642,6 +642,33 @@ describe('UI', () => {
expect(getResolutions()).toEqual(['540p']);
});

it('displays audio quality based on current stream', async () => {
const manifest =
shaka.test.ManifestGenerator.generate((manifest) => {
manifest.addVariant(0, (variant) => {
variant.addAudio(0);
variant.bandwidth = 100000;
});
manifest.addVariant(1, (variant) => {
variant.addAudio(1);
variant.bandwidth = 200000;
});
});

shaka.media.ManifestParser.registerParserByMime(
fakeMimeType, () => new shaka.test.FakeManifestParser(manifest));

await player.load(
/* uri= */ 'fake', /* startTime= */ 0, fakeMimeType);

const qualityButtons = videoContainer.querySelectorAll(
'button.explicit-resolution > span');
const qualityOptions =
Array.from(qualityButtons).map((btn) => btn.innerText);

expect(qualityOptions).toEqual(['200 kbits/s', '100 kbits/s']);
});

/**
* Use internals to update the resolution menu. Our fake manifest can
* cause problems with startup where the Player will get stuck using
Expand Down
1 change: 1 addition & 0 deletions ui/locales/en.json
Expand Up @@ -27,6 +27,7 @@
"PICTURE_IN_PICTURE": "Picture-in-Picture",
"PLAY": "Play",
"PLAYBACK_RATE": "Playback speed",
"QUALITY": "Quality",
"REPLAY": "Replay",
"RESOLUTION": "Resolution",
"REWIND": "Rewind",
Expand Down
5 changes: 5 additions & 0 deletions ui/locales/source.json
Expand Up @@ -113,6 +113,11 @@
"message": "Playback speed",
"description": "Label for a button used to navigate to a submenu to choose the playback speed in the video player."
},
"QUALITY": {
"description": "Label for a button used to open a submenu to choose audio quality in the video player.",
"meaning": "Audio quality",
"message": "Quality"
},
"REPLAY": {
"description": "Label for a button used to replay the video in a video player.",
"message": "Replay"
Expand Down
64 changes: 31 additions & 33 deletions ui/resolution_selection.js
Expand Up @@ -61,9 +61,6 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
});

this.updateResolutionSelection_();

// Set up all the strings in the user's preferred language.
this.updateLocalizedStrings_();
}


Expand All @@ -72,27 +69,6 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
/** @type {!Array.<shaka.extern.Track>} */
let tracks = this.player.getVariantTracks();

// Hide resolution menu and button for audio-only content and src= content
// without resolution information.
// TODO: for audio-only content, this should be a bitrate selection menu
// instead.
if (tracks.length && !tracks[0].height) {
shaka.ui.Utils.setDisplay(this.menu, false);
shaka.ui.Utils.setDisplay(this.button, false);
return;
}
// Otherwise, restore it.
shaka.ui.Utils.setDisplay(this.button, true);

tracks.sort((t1, t2) => {
// We have already screened for audio-only content, but the compiler
// doesn't know that.
goog.asserts.assert(t1.height != null, 'Null height');
goog.asserts.assert(t2.height != null, 'Null height');

return t2.height - t1.height;
});

// If there is a selected variant track, then we filter out any tracks in
// a different language. Then we use those remaining tracks to display the
// available resolutions.
Expand All @@ -104,14 +80,31 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
track.channelsCount == selectedTrack.channelsCount);
}

// Remove duplicate entries with the same height. This can happen if
// we have multiple resolutions of audio. Pick an arbitrary one.
// Remove duplicate entries with the same resolution or quality depending
// on content type. Pick an arbitrary one.
tracks = tracks.filter((track, idx) => {
// Keep the first one with the same height.
const otherIdx = tracks.findIndex((t) => t.height == track.height);
// Keep the first one with the same height or bandwidth.
const otherIdx = this.player.isAudioOnly() ?
tracks.findIndex((t) => t.bandwidth == track.bandwidth) :
tracks.findIndex((t) => t.height == track.height);
return otherIdx == idx;
});

// Sort the tracks by height or bandwith depending on content type.
if (this.player.isAudioOnly()) {
tracks.sort((t1, t2) => {
goog.asserts.assert(t1.bandwidth != null, 'Null bandwidth');
goog.asserts.assert(t2.bandwidth != null, 'Null bandwidth');
return t2.bandwidth - t1.bandwidth;
});
} else {
tracks.sort((t1, t2) => {
goog.asserts.assert(t1.height != null, 'Null height');
goog.asserts.assert(t2.height != null, 'Null height');
return t2.height - t1.height;
});
}

// Remove old shaka-resolutions
// 1. Save the back to menu button
const backButton = shaka.ui.Utils.getFirstDescendantWithClassName(
Expand All @@ -133,7 +126,8 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
() => this.onTrackSelected_(track));

const span = shaka.util.Dom.createHTMLElement('span');
span.textContent = track.height + 'p';
span.textContent = this.player.isAudioOnly() ?
Math.round(track.bandwidth / 1000) + ' kbits/s' : track.height + 'p';
button.appendChild(span);

if (!abrEnabled && track == selectedTrack) {
Expand Down Expand Up @@ -179,6 +173,8 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
shaka.ui.Utils.focusOnTheChosenItem(this.menu);
this.controls.dispatchEvent(
new shaka.util.FakeEvent('resolutionselectionupdated'));

this.updateLocalizedStrings_();
}


Expand All @@ -200,13 +196,15 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
*/
updateLocalizedStrings_() {
const LocIds = shaka.ui.Locales.Ids;
const locId = this.player.isAudioOnly() ?
LocIds.QUALITY : LocIds.RESOLUTION;

this.button.ariaLabel = this.localization.resolve(LocIds.RESOLUTION);
this.backButton.ariaLabel = this.localization.resolve(LocIds.RESOLUTION);
this.button.ariaLabel = this.localization.resolve(locId);
this.backButton.ariaLabel = this.localization.resolve(locId);
this.backSpan.textContent =
this.localization.resolve(LocIds.RESOLUTION);
this.localization.resolve(locId);
this.nameSpan.textContent =
this.localization.resolve(LocIds.RESOLUTION);
this.localization.resolve(locId);
this.abrOnSpan_.textContent =
this.localization.resolve(LocIds.AUTO_QUALITY);

Expand Down

0 comments on commit adc3502

Please sign in to comment.