mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
c7368024ae
I went ahead and implemented the full structured preference system that was discussed in https://github.com/shaka-project/shaka-player/issues/1591. Instead of just expanding languages to arrays, I replaced all 14 individual preference fields with 3 structured arrays: ```tsx preferredAudio (language, role, label, channelCount, codec, spatialAudio) preferredText (language, role, format, forced) preferredVideo (label, role, codec, hdrLevel, layout) ``` Each array entry works as an AND filter - so you can say things like "I want Korean with 5.1 surround, but if not available, English is fine too": ```tsx player.configure('preferredAudio', [ {language: 'ko', channelCount: 6}, {language: 'ko'}, {language: 'en'}, ]); ``` <img width="1728" height="965" alt="image" src="https://github.com/user-attachments/assets/7b088150-139b-475e-bdba-5bc77dd4e524" /> **Config** - Replaced the 14 individual fields with 3 arrays of typed preference objects (AudioPreference, TextPreference, VideoPreference). The old fields still work at runtime with a deprecation warning, so existing apps won't break immediately. **Demo** - The demo config UI now shows inline expandable preference lists instead of flat text inputs. You can add/remove entries and configure each field per entry. URL hash serialization was updated to use JSON format, with legacy param fallbacks preserved.
242 lines
6.4 KiB
JavaScript
242 lines
6.4 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
|
|
goog.provide('shaka.ui.TextSelection');
|
|
|
|
goog.require('shaka.ui.Controls');
|
|
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');
|
|
goog.require('shaka.ui.SettingsMenu');
|
|
goog.require('shaka.ui.Utils');
|
|
goog.require('shaka.util.Dom');
|
|
goog.require('shaka.util.FakeEvent');
|
|
goog.requireType('shaka.ui.Controls');
|
|
|
|
|
|
/**
|
|
* @extends {shaka.ui.SettingsMenu}
|
|
* @final
|
|
* @export
|
|
*/
|
|
shaka.ui.TextSelection = class extends shaka.ui.SettingsMenu {
|
|
/**
|
|
* @param {!HTMLElement} parent
|
|
* @param {!shaka.ui.Controls} controls
|
|
*/
|
|
constructor(parent, controls) {
|
|
super(parent,
|
|
controls, shaka.ui.Enums.MaterialDesignSVGIcons['CLOSED_CAPTIONS']);
|
|
|
|
this.button.classList.add('shaka-caption-button');
|
|
this.button.classList.add('shaka-tooltip-status');
|
|
this.menu.classList.add('shaka-text-languages');
|
|
|
|
let hasTrack = false;
|
|
if (this.player) {
|
|
const tracks = this.player.getTextTracks() || [];
|
|
hasTrack = tracks.some((track) => track.active);
|
|
}
|
|
|
|
if (hasTrack) {
|
|
this.button.ariaPressed = 'true';
|
|
} else {
|
|
this.button.ariaPressed = 'false';
|
|
}
|
|
|
|
this.addOffOption_();
|
|
|
|
this.eventManager.listenMulti(
|
|
this.localization,
|
|
[
|
|
shaka.ui.Localization.LOCALE_UPDATED,
|
|
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.listenMulti(
|
|
this.player,
|
|
[
|
|
'loading',
|
|
'loaded',
|
|
'unloading',
|
|
'textchanged',
|
|
], () => {
|
|
this.onCaptionStateChange_();
|
|
this.updateTextLanguages_();
|
|
});
|
|
|
|
this.eventManager.listen(this.player, 'trackschanged', () => {
|
|
this.updateTextLanguages_();
|
|
});
|
|
|
|
if (this.isSubMenu) {
|
|
this.eventManager.listenMulti(
|
|
this.controls,
|
|
[
|
|
'submenuopen',
|
|
'submenuclose',
|
|
], () => {
|
|
this.updateTextLanguages_();
|
|
});
|
|
}
|
|
|
|
// Initialize caption state with a fake event.
|
|
this.onCaptionStateChange_();
|
|
|
|
// Set up all the strings in the user's preferred language.
|
|
this.updateLocalizedStrings_();
|
|
|
|
this.updateTextLanguages_();
|
|
}
|
|
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addOffOption_() {
|
|
const off = shaka.util.Dom.createButton();
|
|
off.ariaSelected = 'true';
|
|
this.menu.appendChild(off);
|
|
|
|
off.appendChild(shaka.ui.Utils.checkmarkIcon());
|
|
|
|
/** @private {!HTMLElement} */
|
|
this.captionsOffSpan_ = shaka.util.Dom.createHTMLElement('span');
|
|
|
|
off.appendChild(this.captionsOffSpan_);
|
|
}
|
|
|
|
|
|
/** @private */
|
|
onCaptionStateChange_() {
|
|
const tracks = this.player.getTextTracks() || [];
|
|
const hasTrack = tracks.some((track) => track.active);
|
|
|
|
if (hasTrack) {
|
|
this.icon.use(shaka.ui.Enums.MaterialDesignSVGIcons['CLOSED_CAPTIONS']);
|
|
this.button.ariaPressed = 'true';
|
|
} else {
|
|
this.icon.use(
|
|
shaka.ui.Enums.MaterialDesignSVGIcons['CLOSED_CAPTIONS_OFF']);
|
|
this.button.ariaPressed = 'false';
|
|
}
|
|
|
|
this.controls.dispatchEvent(
|
|
new shaka.util.FakeEvent('captionselectionupdated'));
|
|
}
|
|
|
|
/** @private */
|
|
updateTextLanguages_() {
|
|
const tracks = this.player.getTextTracks() || [];
|
|
const hasTrack = tracks.some((track) => track.active);
|
|
|
|
shaka.ui.LanguageUtils.updateTextTracks(tracks, this.menu,
|
|
(track) => this.onTextTrackSelected_(track),
|
|
|
|
// Don't mark current text language as chosen unless captions are
|
|
// enabled
|
|
hasTrack,
|
|
this.currentSelection,
|
|
this.localization,
|
|
this.controls.getConfig());
|
|
|
|
// Add the Off button
|
|
const offButton = shaka.util.Dom.createButton();
|
|
offButton.classList.add('shaka-turn-captions-off-button');
|
|
this.eventManager.listen(offButton, 'click', () => {
|
|
this.player.selectTextTrack();
|
|
});
|
|
|
|
offButton.appendChild(this.captionsOffSpan_);
|
|
|
|
this.menu.appendChild(offButton);
|
|
|
|
if (!hasTrack) {
|
|
offButton.ariaSelected = 'true';
|
|
offButton.appendChild(shaka.ui.Utils.checkmarkIcon());
|
|
this.captionsOffSpan_.classList.add('shaka-chosen-item');
|
|
this.currentSelection.textContent =
|
|
this.localization.resolve(shaka.ui.Locales.Ids.OFF);
|
|
} else {
|
|
this.captionsOffSpan_.classList.remove('shaka-chosen-item');
|
|
}
|
|
|
|
this.button.setAttribute('shaka-status', this.currentSelection.textContent);
|
|
|
|
shaka.ui.Utils.focusOnTheChosenItem(this.menu);
|
|
|
|
this.controls.dispatchEvent(
|
|
new shaka.util.FakeEvent('captionselectionupdated'));
|
|
|
|
shaka.ui.Utils.setDisplay(
|
|
this.button, tracks.length > 0 && !this.isSubMenuOpened);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {!shaka.extern.TextTrack} track
|
|
* @private
|
|
*/
|
|
onTextTrackSelected_(track) {
|
|
this.player.selectTextTrack(track);
|
|
|
|
// Set text preference for when reloading the stream (e.g. casting), keep
|
|
// this selection.
|
|
this.player.configure({
|
|
preferredText: [{
|
|
language: track.language,
|
|
role: '',
|
|
format: '',
|
|
forced: track.forced || false,
|
|
}],
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
updateLocalizedStrings_() {
|
|
const LocIds = shaka.ui.Locales.Ids;
|
|
|
|
this.button.ariaLabel = this.localization.resolve(LocIds.CAPTIONS);
|
|
this.backButton.ariaLabel = this.localization.resolve(LocIds.BACK);
|
|
this.nameSpan.textContent =
|
|
this.localization.resolve(LocIds.CAPTIONS);
|
|
this.backSpan.textContent =
|
|
this.localization.resolve(LocIds.CAPTIONS);
|
|
this.captionsOffSpan_.textContent =
|
|
this.localization.resolve(LocIds.OFF);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @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());
|
|
|
|
shaka.ui.Controls.registerElement(
|
|
'captions', new shaka.ui.TextSelection.Factory());
|