Refactor the overflow menu to use components design.

Closes #1673.

Change-Id: I030745def928796a6abc813a91fb163cb2d76175
This commit is contained in:
Sandra Lokshina
2019-01-29 09:23:00 -08:00
parent d2069dcd76
commit 4d41b7b90c
13 changed files with 1454 additions and 1177 deletions
+6
View File
@@ -1,21 +1,27 @@
# UI library.
+../../third_party/language-mapping-list/language-mapping-list.js
+../../ui/audio_language_selection.js
+../../ui/externs/ui.js
+../../ui/cast_button.js
+../../ui/controls.js
+../../ui/constants.js
+../../ui/enums.js
+../../ui/element.js
+../../ui/fast_forward_button.js
+../../ui/fullscreen_button.js
+../../ui/language_utils.js
+../../ui/localization.js
+../../ui/locales.js
+../../ui/mute_button.js
+../../ui/overflow_menu.js
+../../ui/pip_button.js
+../../ui/presentation_time.js
+../../ui/resolution_selection.js
+../../ui/rewind_button.js
+../../ui/spacer.js
+../../ui/text_displayer.js
+../../ui/text_selection.js
+../../ui/ui.js
+../../ui/ui_utils.js
+../../ui/volume_bar.js
+1
View File
@@ -111,6 +111,7 @@ module.exports = function(config) {
{pattern: 'ui/**/*.css', included: false},
{pattern: 'ui/**/*.less', included: false},
{pattern: 'third_party/closure/goog/**/*.js', included: false},
{pattern: 'third_party/language-mapping-list/**/*.js', included: false},
{pattern: 'test/test/assets/*', included: false},
{pattern: 'dist/shaka-player.ui.js', included: false},
{pattern: 'node_modules/**/*.js', included: false},
+9 -3
View File
@@ -61,16 +61,22 @@ goog.require('shaka.text.SimpleTextDisplayer');
goog.require('shaka.text.TextEngine');
goog.require('shaka.text.TtmlTextParser');
goog.require('shaka.text.VttTextParser');
goog.require('shaka.ui.Overlay');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.Controls');
goog.require('shaka.ui.OverflowMenu');
goog.require('shaka.ui.AudioLanguageSelection');
goog.require('shaka.ui.CastButton');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.FastForwardButton');
goog.require('shaka.ui.FullscreenButton');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.MuteButton');
goog.require('shaka.ui.OverflowMenu');
goog.require('shaka.ui.Overlay');
goog.require('shaka.ui.PipButton');
goog.require('shaka.ui.PresentationTimeTracker');
goog.require('shaka.ui.ResolutionSelection');
goog.require('shaka.ui.RewindButton');
goog.require('shaka.ui.Spacer');
goog.require('shaka.ui.TextSelection');
goog.require('shaka.ui.VolumeBar');
goog.require('shaka.util.Error');
goog.require('shaka.util.Iterables');
+209
View File
@@ -0,0 +1,209 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.AudioLanguageSelection');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Enums');
goog.require('shaka.ui.LanguageUtils');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.OverflowMenu');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.AudioLanguageSelection = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
this.addLanguagesButton_();
this.addAudioLangMenu_();
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateLocalizedStrings_();
// TODO: is there a more efficient way of updating just the strings
// we need instead of running the whole language update?
this.updateAudioLanguages_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateLocalizedStrings_();
// TODO: is there a more efficient way of updating just the strings
// we need instead of running the whole language update?
this.updateAudioLanguages_();
});
this.eventManager.listen(this.player, 'trackschanged', () => {
this.updateAudioLanguages_();
});
this.eventManager.listen(this.player, 'variantchanged', () => {
this.updateAudioLanguages_();
});
this.eventManager.listen(this.languagesButton_, 'click', () => {
this.onLanguagesClick_();
});
// Set up all the strings in the user's preferred language.
this.updateLocalizedStrings_();
}
/**
* @private
*/
addAudioLangMenu_() {
/** @private {!HTMLElement} */
this.audioLangMenu_ = shaka.ui.Utils.createHTMLElement('div');
this.audioLangMenu_.classList.add('shaka-audio-languages');
this.audioLangMenu_.classList.add('shaka-no-propagation');
this.audioLangMenu_.classList.add('shaka-show-controls-on-mouse-over');
this.audioLangMenu_.classList.add('shaka-settings-menu');
/** @private {!HTMLElement} */
this.backFromLanguageButton_ = shaka.ui.Utils.createHTMLElement('button');
this.backFromLanguageButton_.classList.add('shaka-back-to-overflow-button');
this.audioLangMenu_.appendChild(this.backFromLanguageButton_);
const backIcon = shaka.ui.Utils.createHTMLElement('i');
backIcon.classList.add('material-icons');
backIcon.textContent = shaka.ui.Enums.MaterialDesignIcons.BACK;
this.backFromLanguageButton_.appendChild(backIcon);
/** @private {!HTMLElement} */
this.backFromLanguageSpan_ = shaka.ui.Utils.createHTMLElement('span');
this.backFromLanguageButton_.appendChild(this.backFromLanguageSpan_);
const controlsContainer = this.controls.getControlsContainer();
controlsContainer.appendChild(this.audioLangMenu_);
}
/**
* @private
*/
addLanguagesButton_() {
/** @private {!HTMLElement} */
this.languagesButton_ = shaka.ui.Utils.createHTMLElement('button');
this.languagesButton_.classList.add('shaka-language-button');
const icon = shaka.ui.Utils.createHTMLElement('i');
icon.classList.add('material-icons');
icon.textContent = shaka.ui.Enums.MaterialDesignIcons.LANGUAGE;
this.languagesButton_.appendChild(icon);
const label = shaka.ui.Utils.createHTMLElement('label');
label.classList.add('shaka-overflow-button-label');
/** @private {!HTMLElement} */
this.languageNameSpan_ = shaka.ui.Utils.createHTMLElement('span');
this.languageNameSpan_.classList.add('languageSpan');
label.appendChild(this.languageNameSpan_);
/** @private {!HTMLElement} */
this.currentAudioLanguage_ = shaka.ui.Utils.createHTMLElement('span');
this.currentAudioLanguage_.classList.add('shaka-current-selection-span');
const language = this.player.getConfiguration().preferredAudioLanguage;
this.currentAudioLanguage_.textContent =
shaka.ui.LanguageUtils.getLanguageName(language, this.localization);
label.appendChild(this.currentAudioLanguage_);
this.languagesButton_.appendChild(label);
this.parent.appendChild(this.languagesButton_);
}
/** @private */
onLanguagesClick_() {
this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuopen'));
shaka.ui.Utils.setDisplay(this.audioLangMenu_, true);
// Focus on the currently selected language button.
shaka.ui.Utils.focusOnTheChosenItem(this.audioLangMenu_);
}
/** @private */
updateAudioLanguages_() {
const tracks = this.player.getVariantTracks();
const languagesAndRoles = this.player.getAudioLanguagesAndRoles();
const languages = languagesAndRoles.map((langAndRole) => {
return langAndRole.language;
});
shaka.ui.LanguageUtils.updateLanguages(tracks, this.audioLangMenu_,
languages,
this.onAudioLanguageSelected_.bind(this), /* updateChosen */ true,
this.currentAudioLanguage_,
this.localization);
shaka.ui.Utils.focusOnTheChosenItem(this.audioLangMenu_);
}
/**
* @param {string} language
* @private
*/
onAudioLanguageSelected_(language) {
this.player.selectAudioLanguage(language);
}
/**
* @private
*/
updateLocalizedStrings_() {
const LocIds = shaka.ui.Locales.Ids;
this.backFromLanguageButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_BACK));
this.languagesButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_LANGUAGE));
this.languageNameSpan_.textContent =
this.localization.resolve(LocIds.LABEL_LANGUAGE);
this.backFromLanguageSpan_.textContent =
this.localization.resolve(LocIds.LABEL_LANGUAGE);
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.AudioLanguageSelection.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.AudioLanguageSelection(rootElement, controls);
}
};
shaka.ui.OverflowMenu.registerElement(
'language', new shaka.ui.AudioLanguageSelection.Factory());
+184
View File
@@ -0,0 +1,184 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.CastButton');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.OverflowMenu');
goog.require('shaka.ui.Utils');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.CastButton = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
/** @private {!shaka.cast.CastProxy} */
this.castProxy_ = this.controls.getCastProxy();
/** @private {!HTMLElement} */
this.castButton_ = shaka.ui.Utils.createHTMLElement('button');
this.castButton_.classList.add('shaka-cast-button');
this.castButton_.setAttribute('aria-pressed', 'false');
/** @private {!HTMLElement} */
this.castIcon_ = shaka.ui.Utils.createHTMLElement('i');
this.castIcon_.classList.add('material-icons');
this.castIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.CAST;
this.castButton_.appendChild(this.castIcon_);
const label = shaka.ui.Utils.createHTMLElement('label');
label.classList.add('shaka-overflow-button-label');
this.castNameSpan_ = shaka.ui.Utils.createHTMLElement('span');
label.appendChild(this.castNameSpan_);
this.castCurrentSelectionSpan_ =
shaka.ui.Utils.createHTMLElement('span');
this.castCurrentSelectionSpan_.classList.add(
'shaka-current-selection-span');
label.appendChild(this.castCurrentSelectionSpan_);
this.castButton_.appendChild(label);
this.parent.appendChild(this.castButton_);
// Setup strings in the correct language
this.updateLocalizedStrings_();
// Setup button display and state according to the current cast status
this.onCastStatusChange_();
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateLocalizedStrings_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateLocalizedStrings_();
});
this.eventManager.listen(this.castButton_, 'click', () => {
this.onCastClick_();
});
this.eventManager.listen(this.controls, 'caststatuschanged', () => {
this.onCastStatusChange_();
});
}
/** @private */
async onCastClick_() {
if (this.castProxy_.isCasting()) {
this.castProxy_.suggestDisconnect();
} else {
try {
this.castButton_.disabled = true;
await this.castProxy_.cast();
this.castButton_.disabled = false;
} catch (error) {
this.castButton_.disabled = false;
if (error.code != shaka.util.Error.Code.CAST_CANCELED_BY_USER) {
this.controls.dispatchEvent(new shaka.util.FakeEvent('error', {
errorDetails: error,
}));
}
}
}
}
/**
* @private
*/
onCastStatusChange_() {
const canCast = this.castProxy_.canCast() && this.controls.isCastAllowed();
const isCasting = this.castProxy_.isCasting();
const materialDesignIcons = shaka.ui.Enums.MaterialDesignIcons;
shaka.ui.Utils.setDisplay(this.castButton_, canCast);
this.castIcon_.textContent = isCasting ?
materialDesignIcons.EXIT_CAST :
materialDesignIcons.CAST;
// Aria-pressed set to true when casting, set to false otherwise.
if (canCast) {
if (isCasting) {
this.castButton_.setAttribute('aria-pressed', 'true');
} else {
this.castButton_.setAttribute('aria-pressed', 'false');
}
}
this.setCurrentCastSelection_();
}
/**
* @private
*/
setCurrentCastSelection_() {
if (this.castProxy_.isCasting()) {
this.castCurrentSelectionSpan_.textContent =
this.castProxy_.receiverName();
} else {
this.castCurrentSelectionSpan_.textContent =
this.localization.resolve(shaka.ui.Locales.Ids.LABEL_NOT_CASTING);
}
}
/**
* @private
*/
updateLocalizedStrings_() {
const LocIds = shaka.ui.Locales.Ids;
this.castButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_CAST));
this.castNameSpan_.textContent =
this.localization.resolve(LocIds.LABEL_CAST);
// If we're not casting, string "not casting" will be displayed,
// which needs localization.
this.setCurrentCastSelection_();
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.CastButton.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.CastButton(rootElement, controls);
}
};
shaka.ui.OverflowMenu.registerElement(
'cast', new shaka.ui.CastButton.Factory());
+10 -60
View File
@@ -17,6 +17,7 @@
goog.provide('shaka.ui.Controls');
goog.provide('shaka.ui.ControlsPanel');
goog.require('goog.asserts');
goog.require('shaka.ui.Constants');
@@ -109,7 +110,7 @@ shaka.ui.Controls = function(player, videoContainer, video, config) {
this.hideSettingsMenusTimer_ = new shaka.util.Timer(() => {
/** type {function(!HTMLElement)} */
const hide = (control) => {
shaka.ui.Controls.setDisplay(control, /* visible= */ false);
shaka.ui.Utils.setDisplay(control, /* visible= */ false);
};
for (const menu of this.settingsMenus_) {
@@ -170,7 +171,7 @@ goog.inherits(shaka.ui.Controls, shaka.util.FakeEventTarget);
/** @private {!Map.<string, !shaka.extern.IUIElement.Factory>} */
shaka.ui.Controls.elementNamesToFactories_ = new Map();
shaka.ui.ControlsPanel.elementNamesToFactories_ = new Map();
/**
@@ -216,7 +217,7 @@ shaka.ui.Controls.prototype.destroy = function() {
* @export
*/
shaka.ui.Controls.registerElement = function(name, factory) {
shaka.ui.Controls.elementNamesToFactories_.set(name, factory);
shaka.ui.ControlsPanel.elementNamesToFactories_.set(name, factory);
};
@@ -255,7 +256,7 @@ shaka.ui.Controls.prototype.loadComplete = function() {
shaka.ui.Controls.prototype.setEnabledShakaControls = function(enabled) {
this.enabled_ = enabled;
if (enabled) {
shaka.ui.Controls.setDisplay(
shaka.ui.Utils.setDisplay(
this.controlsButtonPanel_.parentElement, true);
// If we're hiding native controls, make sure the video element itself is
@@ -263,7 +264,7 @@ shaka.ui.Controls.prototype.setEnabledShakaControls = function(enabled) {
this.video_.tabIndex = -1;
this.video_.controls = false;
} else {
shaka.ui.Controls.setDisplay(
shaka.ui.Utils.setDisplay(
this.controlsButtonPanel_.parentElement, false);
}
@@ -406,29 +407,6 @@ shaka.ui.Controls.prototype.setLastTouchEventTime = function(time) {
};
/**
* Depending on the value of display, sets/removes css class of element to
* either display it or hide.
*
* @param {Element} element
* @param {boolean} display
* @export
*/
shaka.ui.Controls.setDisplay = function(element, display) {
if (!element) return;
if (display) {
element.classList.add('shaka-displayed');
// Removing a non-existent class doesn't throw, so, even if
// the element is not hidden, this should be fine. Same for displayed
// below.
element.classList.remove('shaka-hidden');
} else {
element.classList.add('shaka-hidden');
element.classList.remove('shaka-displayed');
}
};
/**
* Display controls even if css says overwise.
* Normally, controls opacity is controled by CSS, but there are
@@ -607,14 +585,8 @@ shaka.ui.Controls.prototype.addControlsButtonPanel_ = function() {
// Create the elements specified by controlPanelElements
for (let i = 0; i < this.config_.controlPanelElements.length; i++) {
const name = this.config_.controlPanelElements[i];
if (shaka.ui.Controls.elementNamesToFactories_.get(name)) {
if (shaka.ui.Controls.controlPanelElements_.indexOf(name) == -1) {
// Not a control panel element, skip
shaka.log.warning('Element is not part of control panel ' +
'elements and will be skipped', name);
continue;
}
const factory = shaka.ui.Controls.elementNamesToFactories_.get(name);
if (shaka.ui.ControlsPanel.elementNamesToFactories_.get(name)) {
const factory = shaka.ui.ControlsPanel.elementNamesToFactories_.get(name);
this.elements_.push(factory.create(this.controlsButtonPanel_, this));
}
}
@@ -748,15 +720,6 @@ shaka.ui.Controls.prototype.addSeekBar_ = function() {
};
/**
* Checks if the cast proxy can cast.
* @return {boolean}
*/
shaka.ui.Controls.prototype.canCast = function() {
return this.castProxy_.canCast();
};
/**
* Hiding the cursor when the mouse stops moving seems to be the only decent UX
* in fullscreen mode. Since we can't use pure CSS for that, we use events both
@@ -1135,12 +1098,12 @@ shaka.ui.Controls.prototype.updateTimeAndSeekRange_ = function() {
const seekRange = this.player_.seekRange();
const seekWindow = seekRange.end - seekRange.start;
if (seekWindow < Constants.MIN_SEEK_WINDOW_TO_SHOW_SEEKBAR) {
shaka.ui.Controls.setDisplay(this.seekBarContainer_, false);
shaka.ui.Utils.setDisplay(this.seekBarContainer_, false);
for (let menu of this.settingsMenus_) {
menu.classList.add('shaka-low-position');
}
} else {
shaka.ui.Controls.setDisplay(this.seekBarContainer_, true);
shaka.ui.Utils.setDisplay(this.seekBarContainer_, true);
for (let menu of this.settingsMenus_) {
menu.classList.remove('shaka-low-position');
}
@@ -1321,16 +1284,3 @@ shaka.ui.Controls.createLocalization_ = function() {
return localization;
};
/** @private {!Array.<string>} */
shaka.ui.Controls.controlPanelElements_ = [
'time_and_duration',
'mute',
'volume',
'fullscreen',
'overflow_menu',
'rewind',
'fast_forward',
'spacer',
];
+140
View File
@@ -0,0 +1,140 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.LanguageUtils');
goog.require('mozilla.LanguageMapping');
shaka.ui.LanguageUtils = class {
/**
* @param {!Array.<shaka.extern.Track>} tracks
* @param {!HTMLElement} langMenu
* @param {!Array.<string>} languages
* @param {function(string)} onLanguageSelected
* @param {boolean} updateChosen
* @param {!HTMLElement} currentSelectionElement
* @param {shaka.ui.Localization} localization
*/
// TODO: Do the benefits of having this common code in a method still
// outweigh the complexity of the parameter list?
static updateLanguages(tracks, langMenu, languages, onLanguageSelected,
updateChosen, currentSelectionElement, localization) {
// Using array.filter(f)[0] as an alternative to array.find(f) which is
// not supported in IE11.
const activeTracks = tracks.filter(function(track) {
return track.active == true;
});
const selectedTrack = activeTracks[0];
// Remove old languages
// 1. Save the back to menu button
const backButton = shaka.ui.Utils.getFirstDescendantWithClassName(
langMenu, 'shaka-back-to-overflow-button');
// 2. Remove everything
while (langMenu.firstChild) {
langMenu.removeChild(langMenu.firstChild);
}
// 3. Add the backTo Menu button back
langMenu.appendChild(backButton);
// 4. Add new buttons
languages.forEach((language) => {
let button = shaka.ui.Utils.createHTMLElement('button');
button.addEventListener('click', () => { onLanguageSelected(language); });
let span = shaka.ui.Utils.createHTMLElement('span');
span.textContent =
shaka.ui.LanguageUtils.getLanguageName(language, localization);
button.appendChild(span);
if (updateChosen && (language == selectedTrack.language)) {
button.appendChild(shaka.ui.Utils.checkmarkIcon());
span.classList.add('shaka-chosen-item');
button.setAttribute('aria-selected', 'true');
currentSelectionElement.textContent = span.textContent;
}
langMenu.appendChild(button);
});
}
/**
* Returns the language's name for itself in its own script (autoglottonym),
* if we have it.
*
* If the locale, including region, can be mapped to a name, we return a very
* specific name including the region. For example, "de-AT" would map to
* "Deutsch (Österreich)" or Austrian German.
*
* If only the language part of the locale is in our map, we append the locale
* itself for specificity. For example, "ar-EG" (Egyptian Arabic) would map
* to "ﺎﻠﻋﺮﺒﻳﺓ (ar-EG)". In this way, multiple versions of Arabic whose
* regions are not in our map would not all look the same in the language
* list, but could be distinguished by their locale.
*
* Finally, if language part of the locale is not in our map, we label it
* "unknown", as translated to the UI locale, and we append the locale itself
* for specificity. For example, "sjn" would map to "Unknown (sjn)". In this
* way, multiple unrecognized languages would not all look the same in the
* language list, but could be distinguished by their locale.
*
* @param {string} locale
* @param {shaka.ui.Localization} localization
* @return {string} The language's name for itself in its own script, or as
* close as we can get with the information we have.
*/
static getLanguageName(locale, localization) {
if (!locale && !localization) {
return '';
}
// Shorthand for resolving a localization ID.
const resolve = (id) => localization.resolve(id);
// Handle some special cases first. These are reserved language tags that
// are used to indicate something that isn't one specific language.
switch (locale) {
case 'mul':
return resolve(shaka.ui.Locales.Ids.LABEL_MULTIPLE_LANGUAGES);
case 'zxx':
return resolve(shaka.ui.Locales.Ids.LABEL_NOT_APPLICABLE);
}
// Extract the base language from the locale as a fallback step.
const language = shaka.util.LanguageUtils.getBase(locale);
// First try to resolve the full language name.
// If that fails, try the base.
// Finally, report "unknown".
// When there is a loss of specificity (either to a base language or to
// "unknown"), we should append the original language code.
// Otherwise, there may be multiple identical-looking items in the list.
if (locale in mozilla.LanguageMapping) {
return mozilla.LanguageMapping[locale].nativeName;
} else if (language in mozilla.LanguageMapping) {
return mozilla.LanguageMapping[language].nativeName +
' (' + locale + ')';
} else {
return resolve(shaka.ui.Locales.Ids.LABEL_UNKNOWN_LANGUAGE) +
' (' + locale + ')';
}
}
};
+34 -1114
View File
File diff suppressed because it is too large Load Diff
+217
View File
@@ -0,0 +1,217 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.PipButton');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.OverflowMenu');
goog.require('shaka.ui.Utils');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.PipButton = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
/** @private {!HTMLMediaElement} */
this.localVideo_ = this.controls.getLocalVideo();
const LocIds = shaka.ui.Locales.Ids;
/** @private {!HTMLElement} */
this.pipButton_ = shaka.ui.Utils.createHTMLElement('button');
this.pipButton_.classList.add('shaka-pip-button');
/** @private {!HTMLElement} */
this.pipIcon_ = shaka.ui.Utils.createHTMLElement('i');
this.pipIcon_.classList.add('material-icons');
this.pipIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.PIP;
this.pipButton_.appendChild(this.pipIcon_);
const label = shaka.ui.Utils.createHTMLElement('label');
label.classList.add('shaka-overflow-button-label');
this.pipNameSpan_ = shaka.ui.Utils.createHTMLElement('span');
this.pipNameSpan_.textContent =
this.localization.resolve(LocIds.LABEL_PICTURE_IN_PICTURE);
label.appendChild(this.pipNameSpan_);
/** @private {!HTMLElement} */
this.currentPipState_ = shaka.ui.Utils.createHTMLElement('span');
this.currentPipState_.classList.add('shaka-current-selection-span');
label.appendChild(this.currentPipState_);
this.pipButton_.appendChild(label);
this.updateLocalizedStrings_();
this.parent.appendChild(this.pipButton_);
// Don't display the button if PiP is not supported or not allowed
// TODO: Can this ever change? Is it worth creating the button if the below
// condition is true?
if (!this.isPipAllowed_()) {
shaka.ui.Utils.setDisplay(this.pipButton_, false);
}
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateLocalizedStrings_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateLocalizedStrings_();
});
this.eventManager.listen(this.pipButton_, 'click', () => {
this.onPipClick_();
});
this.eventManager.listen(
this.localVideo_, 'enterpictureinpicture', () => {
this.onEnterPictureInPicture_();
});
this.eventManager.listen(
this.localVideo_, 'leavepictureinpicture', () => {
this.onLeavePictureInPicture_();
});
this.eventManager.listen(this.controls, 'caststatuschange', (e) => {
this.onCastStatusChange_(e);
});
}
/**
* @return {boolean}
* @private
*/
isPipAllowed_() {
return document.pictureInPictureEnabled &&
!this.video.disablePictureInPicture;
}
/**
* @return {!Promise}
* @private
*/
async onPipClick_() {
try {
if (!document.pictureInPictureElement) {
await this.video.requestPictureInPicture();
} else {
await document.exitPictureInPicture();
}
} catch (error) {
this.controls.dispatchEvent(new shaka.util.FakeEvent('error', {
errorDetails: error,
}));
}
}
/** @private */
onEnterPictureInPicture_() {
const LocIds = shaka.ui.Locales.Ids;
this.pipIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.EXIT_PIP;
this.pipButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_EXIT_PICTURE_IN_PICTURE));
this.currentPipState_.textContent =
this.localization.resolve(LocIds.LABEL_PICTURE_IN_PICTURE_ON);
}
/** @private */
onLeavePictureInPicture_() {
const LocIds = shaka.ui.Locales.Ids;
this.pipIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.PIP;
this.pipButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_ENTER_PICTURE_IN_PICTURE));
this.currentPipState_.textContent =
this.localization.resolve(LocIds.LABEL_PICTURE_IN_PICTURE_OFF);
}
/**
* @private
*/
updateLocalizedStrings_() {
const LocIds = shaka.ui.Locales.Ids;
this.pipNameSpan_.textContent =
this.localization.resolve(LocIds.LABEL_PICTURE_IN_PICTURE);
const ariaLabel = document.pictureInPictureElement ?
LocIds.ARIA_LABEL_EXIT_PICTURE_IN_PICTURE :
LocIds.ARIA_LABEL_ENTER_PICTURE_IN_PICTURE;
this.pipButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(ariaLabel));
const currentPipState = document.pictureInPictureElement ?
LocIds.LABEL_PICTURE_IN_PICTURE_ON :
LocIds.LABEL_PICTURE_IN_PICTURE_OFF;
this.currentPipState_.textContent =
this.localization.resolve(currentPipState);
}
/**
* @param {Event} e
* @private
*/
onCastStatusChange_(e) {
const isCasting = e['newStatus'];
if (isCasting) {
// Picture-in-picture is not applicable if we're casting
if (this.isPipAllowed_()) {
shaka.ui.Utils.setDisplay(this.pipButton_, false);
}
} else {
if (this.isPipAllowed_()) {
shaka.ui.Utils.setDisplay(this.pipButton_, true);
}
}
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.PipButton.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.PipButton(rootElement, controls);
}
};
shaka.ui.OverflowMenu.registerElement(
'picture_in_picture', new shaka.ui.PipButton.Factory());
+299
View File
@@ -0,0 +1,299 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.ResolutionSelection');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Enums');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.OverflowMenu');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.ResolutionSelection = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
this.addResolutionButton_();
this.addResolutionMenu_();
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateLocalizedStrings_();
// If abr is enabled, the 'Auto' string needs localization.
// TODO: is there a more efficient way of updating just the strings
// we need instead of running the whole resolution update?
this.updateResolutionSelection_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateLocalizedStrings_();
// If abr is enabled, the 'Auto' string needs localization.
// TODO: is there a more efficient way of updating just the strings
// we need instead of running the whole resolution update?
this.updateResolutionSelection_();
});
this.eventManager.listen(this.resolutionButton_, 'click', () => {
this.onResolutionClick_();
});
this.eventManager.listen(this.player, 'variantchanged', () => {
this.updateResolutionSelection_();
});
this.eventManager.listen(this.player, 'trackschanged', () => {
this.updateResolutionSelection_();
});
// Set up all the strings in the user's preferred language.
this.updateLocalizedStrings_();
}
/**
* @private
*/
addResolutionButton_() {
/** @private {!HTMLElement}*/
this.resolutionButton_ = shaka.ui.Utils.createHTMLElement('button');
this.resolutionButton_.classList.add('shaka-resolution-button');
const icon = shaka.ui.Utils.createHTMLElement('i');
icon.classList.add('material-icons');
icon.textContent = shaka.ui.Enums.MaterialDesignIcons.RESOLUTION;
this.resolutionButton_.appendChild(icon);
const label = shaka.ui.Utils.createHTMLElement('label');
label.classList.add('shaka-overflow-button-label');
/** @private {!HTMLElement}*/
this.resolutionNameSpan_ = shaka.ui.Utils.createHTMLElement('span');
label.appendChild(this.resolutionNameSpan_);
/** @private {!HTMLElement}*/
this.currentResolution_ = shaka.ui.Utils.createHTMLElement('span');
this.currentResolution_.classList.add('shaka-current-selection-span');
label.appendChild(this.currentResolution_);
this.resolutionButton_.appendChild(label);
this.parent.appendChild(this.resolutionButton_);
}
/**
* @private
*/
addResolutionMenu_() {
/** @private {!HTMLElement}*/
this.resolutionMenu_ = shaka.ui.Utils.createHTMLElement('div');
this.resolutionMenu_.classList.add('shaka-resolutions');
this.resolutionMenu_.classList.add('shaka-no-propagation');
this.resolutionMenu_.classList.add('shaka-show-controls-on-mouse-over');
this.resolutionMenu_.classList.add('shaka-settings-menu');
/** @private {!HTMLElement}*/
this.backFromResolutionButton_ =
shaka.ui.Utils.createHTMLElement('button');
this.backFromResolutionButton_.classList.add(
'shaka-back-to-overflow-button');
this.resolutionMenu_.appendChild(this.backFromResolutionButton_);
const backIcon = shaka.ui.Utils.createHTMLElement('i');
backIcon.classList.add('material-icons');
backIcon.textContent = shaka.ui.Enums.MaterialDesignIcons.BACK;
this.backFromResolutionButton_.appendChild(backIcon);
/** @private {!HTMLElement}*/
this.backFromResolutionSpan_ = shaka.ui.Utils.createHTMLElement('span');
this.backFromResolutionButton_.appendChild(this.backFromResolutionSpan_);
// Add the abr option
const auto = shaka.ui.Utils.createHTMLElement('button');
auto.setAttribute('aria-selected', 'true');
this.resolutionMenu_.appendChild(auto);
auto.appendChild(shaka.ui.Utils.checkmarkIcon());
/** @private {!HTMLElement}*/
this.abrOnSpan_ = shaka.ui.Utils.createHTMLElement('span');
this.abrOnSpan_.classList.add('shaka-auto-span');
auto.appendChild(this.abrOnSpan_);
const controlsContainer = this.controls.getControlsContainer();
controlsContainer.appendChild(this.resolutionMenu_);
}
/** @private */
updateResolutionSelection_() {
let tracks = this.player.getVariantTracks();
// Hide resolution menu and button for audio-only content.
if (tracks.length && !tracks[0].height) {
shaka.ui.Utils.setDisplay(this.resolutionMenu_, false);
shaka.ui.Utils.setDisplay(this.resolutionButton_, false);
return;
}
tracks.sort(function(t1, t2) {
return t1.height - t2.height;
});
tracks.reverse();
// 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.
const selectedTrack = tracks.find((track) => track.active);
if (selectedTrack) {
const language = selectedTrack.language;
// Filter by current audio language.
tracks = tracks.filter(function(track) {
return track.language == language;
});
}
// Remove old shaka-resolutions
// 1. Save the back to menu button
const backButton = shaka.ui.Utils.getFirstDescendantWithClassName(
this.resolutionMenu_, 'shaka-back-to-overflow-button');
// 2. Remove everything
while (this.resolutionMenu_.firstChild) {
this.resolutionMenu_.removeChild(this.resolutionMenu_.firstChild);
}
// 3. Add the backTo Menu button back
this.resolutionMenu_.appendChild(backButton);
const abrEnabled = this.player.getConfiguration().abr.enabled;
// Add new ones
tracks.forEach((track) => {
let button = shaka.ui.Utils.createHTMLElement('button');
button.classList.add('explicit-resolution');
button.addEventListener('click',
this.onTrackSelected_.bind(this, track));
let span = shaka.ui.Utils.createHTMLElement('span');
span.textContent = track.height + 'p';
button.appendChild(span);
if (!abrEnabled && track == selectedTrack) {
// If abr is disabled, mark the selected track's resolution.
button.setAttribute('aria-selected', 'true');
button.appendChild(shaka.ui.Utils.checkmarkIcon());
span.classList.add('shaka-chosen-item');
this.currentResolution_.textContent = span.textContent;
}
this.resolutionMenu_.appendChild(button);
});
// Add the Auto button
let autoButton = shaka.ui.Utils.createHTMLElement('button');
autoButton.addEventListener('click', function() {
let config = {abr: {enabled: true}};
this.player.configure(config);
this.updateResolutionSelection_();
}.bind(this));
let autoSpan = shaka.ui.Utils.createHTMLElement('span');
autoSpan.textContent =
this.localization.resolve(shaka.ui.Locales.Ids.LABEL_AUTO_QUALITY);
autoButton.appendChild(autoSpan);
// If abr is enabled reflect it by marking 'Auto' as selected.
if (abrEnabled) {
autoButton.setAttribute('aria-selected', 'true');
autoButton.appendChild(shaka.ui.Utils.checkmarkIcon());
autoSpan.classList.add('shaka-chosen-item');
this.currentResolution_.textContent =
this.localization.resolve(shaka.ui.Locales.Ids.LABEL_AUTO_QUALITY);
}
this.resolutionMenu_.appendChild(autoButton);
shaka.ui.Utils.focusOnTheChosenItem(this.resolutionMenu_);
}
/** @private */
onResolutionClick_() {
this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuopen'));
shaka.ui.Utils.setDisplay(this.resolutionMenu_, true);
shaka.ui.Utils.focusOnTheChosenItem(this.resolutionMenu_);
}
/**
* @param {!shaka.extern.Track} track
* @private
*/
onTrackSelected_(track) {
// Disable abr manager before changing tracks.
let config = {abr: {enabled: false}};
this.player.configure(config);
this.player.selectVariantTrack(track, /* clearBuffer */ true);
}
/**
* @private
*/
updateLocalizedStrings_() {
const LocIds = shaka.ui.Locales.Ids;
this.resolutionButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_RESOLUTION));
this.backFromResolutionButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_RESOLUTION));
this.backFromResolutionSpan_.textContent =
this.localization.resolve(LocIds.LABEL_RESOLUTION);
this.resolutionNameSpan_.textContent =
this.localization.resolve(LocIds.LABEL_RESOLUTION);
this.abrOnSpan_.textContent =
this.localization.resolve(LocIds.LABEL_AUTO_QUALITY);
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.ResolutionSelection.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.ResolutionSelection(rootElement, controls);
}
};
shaka.ui.OverflowMenu.registerElement(
'quality', new shaka.ui.ResolutionSelection.Factory());
+290
View File
@@ -0,0 +1,290 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.TextSelection');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Enums');
goog.require('shaka.ui.LanguageUtils');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.OverflowMenu');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.TextSelection = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
this.addCaptionButton_();
this.addTextLangMenu_();
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateLocalizedStrings_();
// If captions/subtitles are off, this string needs localization.
// TODO: is there a more efficient way of updating just the strings
// we need instead of running the whole language update?
this.updateTextLanguages_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateLocalizedStrings_();
// If captions/subtitles are off, this string needs localization.
// TODO: is there a more efficient way of updating just the strings
// we need instead of running the whole language update?
this.updateTextLanguages_();
});
this.eventManager.listen(this.player, 'texttrackvisibility', () => {
this.onCaptionStateChange_();
});
this.eventManager.listen(this.captionButton_, 'click', () => {
this.onCaptionClick_();
});
this.eventManager.listen(this.player, 'textchanged', () => {
this.updateTextLanguages_();
});
this.eventManager.listen(this.player, 'trackschanged', () => {
this.onTracksChange_();
});
// Initialize caption state with a fake event.
this.onCaptionStateChange_();
// Set up all the strings in the user's preferred language.
this.updateLocalizedStrings_();
}
/**
* @private
*/
addCaptionButton_() {
/** @private {!HTMLElement} */
this.captionButton_ = shaka.ui.Utils.createHTMLElement('button');
this.captionButton_.classList.add('shaka-caption-button');
/** @private {!HTMLElement} */
this.captionIcon_ = shaka.ui.Utils.createHTMLElement('i');
this.captionIcon_.classList.add('material-icons');
this.captionIcon_.textContent =
shaka.ui.Enums.MaterialDesignIcons.CLOSED_CAPTIONS;
if (this.player && this.player.isTextTrackVisible()) {
this.captionButton_.setAttribute('aria-pressed', 'true');
} else {
this.captionButton_.setAttribute('aria-pressed', 'false');
}
this.captionButton_.appendChild(this.captionIcon_);
const label = shaka.ui.Utils.createHTMLElement('label');
label.classList.add('shaka-overflow-button-label');
/** @private {!HTMLElement} */
this.captionsNameSpan_ = shaka.ui.Utils.createHTMLElement('span');
label.appendChild(this.captionsNameSpan_);
/** @private {!HTMLElement} */
this.currentCaptions_ = shaka.ui.Utils.createHTMLElement('span');
this.currentCaptions_.classList.add('shaka-current-selection-span');
label.appendChild(this.currentCaptions_);
this.captionButton_.appendChild(label);
this.parent.appendChild(this.captionButton_);
}
/**
* @private
*/
addTextLangMenu_() {
/** @private {!HTMLElement} */
this.textLangMenu_ = shaka.ui.Utils.createHTMLElement('div');
this.textLangMenu_.classList.add('shaka-text-languages');
this.textLangMenu_.classList.add('shaka-no-propagation');
this.textLangMenu_.classList.add('shaka-show-controls-on-mouse-over');
this.textLangMenu_.classList.add('shaka-settings-menu');
/** @private {!HTMLElement} */
this.backFromCaptionsButton_ = shaka.ui.Utils.createHTMLElement('button');
this.backFromCaptionsButton_.classList.add('shaka-back-to-overflow-button');
this.textLangMenu_.appendChild(this.backFromCaptionsButton_);
const backIcon = shaka.ui.Utils.createHTMLElement('i');
backIcon.classList.add('material-icons');
backIcon.textContent = shaka.ui.Enums.MaterialDesignIcons.BACK;
this.backFromCaptionsButton_.appendChild(backIcon);
/** @private {!HTMLElement} */
this.backFromCaptionsSpan_ = shaka.ui.Utils.createHTMLElement('span');
this.backFromCaptionsButton_.appendChild(this.backFromCaptionsSpan_);
// Add the off option
const off = shaka.ui.Utils.createHTMLElement('button');
off.setAttribute('aria-selected', 'true');
this.textLangMenu_.appendChild(off);
off.appendChild(shaka.ui.Utils.checkmarkIcon());
/** @private {!HTMLElement} */
this.captionsOffSpan_ = shaka.ui.Utils.createHTMLElement('span');
this.captionsOffSpan_.classList.add('shaka-auto-span');
off.appendChild(this.captionsOffSpan_);
const controlsContainer = this.controls.getControlsContainer();
controlsContainer.appendChild(this.textLangMenu_);
}
/** @private */
onCaptionClick_() {
this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuopen'));
shaka.ui.Utils.setDisplay(this.textLangMenu_, true);
// Focus on the currently selected language button.
shaka.ui.Utils.focusOnTheChosenItem(this.textLangMenu_);
}
/** @private */
onCaptionStateChange_() {
if (this.captionIcon_) {
if (this.player.isTextTrackVisible()) {
this.captionIcon_.classList.add('shaka-captions-on');
this.captionIcon_.classList.remove('shaka-captions-off');
} else {
this.captionIcon_.classList.add('shaka-captions-off');
this.captionIcon_.classList.remove('shaka-captions-on');
}
}
}
/** @private */
updateTextLanguages_() {
const tracks = this.player.getTextTracks();
const languagesAndRoles = this.player.getTextLanguagesAndRoles();
const languages = languagesAndRoles.map((langAndRole) => {
return langAndRole.language;
});
shaka.ui.LanguageUtils.updateLanguages(tracks, this.textLangMenu_,
languages,
this.onTextLanguageSelected_.bind(this),
// Don't mark current text language as chosen unless captions are enabled
this.player.isTextTrackVisible(),
this.currentCaptions_,
this.localization);
// Add the Off button
let offButton = shaka.ui.Utils.createHTMLElement('button');
offButton.addEventListener('click', () => {
this.player.setTextTrackVisibility(false);
this.updateTextLanguages_();
});
offButton.appendChild(this.captionsOffSpan_);
this.textLangMenu_.appendChild(offButton);
if (!this.player.isTextTrackVisible()) {
offButton.setAttribute('aria-selected', 'true');
offButton.appendChild(shaka.ui.Utils.checkmarkIcon());
this.captionsOffSpan_.classList.add('shaka-chosen-item');
this.currentCaptions_.textContent =
this.localization.resolve(shaka.ui.Locales.Ids.LABEL_CAPTIONS_OFF);
}
shaka.ui.Utils.focusOnTheChosenItem(this.textLangMenu_);
}
/**
* @param {string} language
* @return {!Promise}
* @private
*/
async onTextLanguageSelected_(language) {
await this.player.setTextTrackVisibility(true);
this.player.selectTextLanguage(language);
}
/**
* @private
*/
updateLocalizedStrings_() {
const LocIds = shaka.ui.Locales.Ids;
this.captionButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_CAPTIONS));
this.backFromCaptionsButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(LocIds.ARIA_LABEL_BACK));
this.captionsNameSpan_.textContent =
this.localization.resolve(LocIds.LABEL_CAPTIONS);
this.backFromCaptionsSpan_.textContent =
this.localization.resolve(LocIds.LABEL_CAPTIONS);
this.captionsOffSpan_.textContent =
this.localization.resolve(LocIds.LABEL_CAPTIONS_OFF);
}
/** @private */
onTracksChange_() {
// TS content might have captions embedded in video stream, we can't know
// until we start transmuxing. So, always show the caption button if we're
// playing TS content.
if (shaka.ui.Utils.isTsContent(this.player)) {
shaka.ui.Utils.setDisplay(this.captionButton_, true);
} else {
const hasText = this.player.getTextTracks().length;
shaka.ui.Utils.setDisplay(this.captionButton_, hasText > 0);
}
this.updateTextLanguages_();
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.TextSelection.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.TextSelection(rootElement, controls);
}
};
shaka.ui.OverflowMenu.registerElement(
'captions', new shaka.ui.TextSelection.Factory());
+1
View File
@@ -107,6 +107,7 @@ shaka.ui.Overlay.prototype.defaultConfig_ = function() {
'quality',
'language',
'picture_in_picture',
'cast',
],
addSeekBar: true,
castReceiverAppId: '',
+54
View File
@@ -81,3 +81,57 @@ shaka.ui.Utils.createHTMLElement = function(tagName) {
/** @type {!HTMLElement} */ (document.createElement(tagName));
return element;
};
/**
* Finds a descendant of |menu| that has a 'shaka-chosen-item' class
* and focuses on its' parent.
*
* @param {HTMLElement} menu
*/
shaka.ui.Utils.focusOnTheChosenItem = function(menu) {
if (!menu) return;
const chosenItem = shaka.ui.Utils.getDescendantIfExists(
menu, 'shaka-chosen-item');
if (chosenItem) {
chosenItem.parentElement.focus();
}
};
/**
* @return {!Element}
*/
shaka.ui.Utils.checkmarkIcon = function() {
let icon = shaka.ui.Utils.createHTMLElement('i');
icon.classList.add('material-icons');
icon.classList.add('shaka-chosen-item');
icon.textContent = shaka.ui.Enums.MaterialDesignIcons.CHECKMARK;
// Screen reader should ignore icon text.
icon.setAttribute('aria-hidden', 'true');
return icon;
};
/**
* Depending on the value of display, sets/removes the css class of element to
* either display it or hide it.
*
* @param {Element} element
* @param {boolean} display
* @export
*/
shaka.ui.Utils.setDisplay = function(element, display) {
if (!element) return;
if (display) {
element.classList.add('shaka-displayed');
// Removing a non-existent class doesn't throw, so, even if
// the element is not hidden, this should be fine. Same for displayed
// below.
element.classList.remove('shaka-hidden');
} else {
element.classList.add('shaka-hidden');
element.classList.remove('shaka-displayed');
}
};