mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-20 16:57:25 +03:00
Refactor the overflow menu to use components design.
Closes #1673. Change-Id: I030745def928796a6abc813a91fb163cb2d76175
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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());
|
||||
@@ -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());
|
||||
Vendored
+10
-60
@@ -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',
|
||||
];
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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());
|
||||
@@ -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());
|
||||
@@ -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());
|
||||
@@ -107,6 +107,7 @@ shaka.ui.Overlay.prototype.defaultConfig_ = function() {
|
||||
'quality',
|
||||
'language',
|
||||
'picture_in_picture',
|
||||
'cast',
|
||||
],
|
||||
addSeekBar: true,
|
||||
castReceiverAppId: '',
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user