feat(UI): Add live subtitle style preview on hover (#10077)

This commit is contained in:
Aditya Mishra
2026-05-12 16:54:17 +05:30
committed by GitHub
parent a66e234143
commit 415ebdc52e
62 changed files with 847 additions and 16 deletions
+1
View File
@@ -46,6 +46,7 @@
+../../ui/text_position.js +../../ui/text_position.js
+../../ui/text_selection.js +../../ui/text_selection.js
+../../ui/text_size.js +../../ui/text_size.js
+../../ui/text_style_preview.js
+../../ui/toggle_stereoscopic.js +../../ui/toggle_stereoscopic.js
+../../ui/ui.js +../../ui/ui.js
+../../ui/ui_utils.js +../../ui/ui_utils.js
+10
View File
@@ -7640,6 +7640,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
return this.videoContainer_; return this.videoContainer_;
} }
/**
* Returns the active text displayer, if one has been created. This is not
* exported; it is for internal integrations such as the UI controls.
*
* @return {?shaka.extern.TextDisplayer}
*/
getTextDisplayer() {
return this.textDisplayer_;
}
/** /**
* @param {!shaka.util.Error} error * @param {!shaka.util.Error} error
* @private * @private
+120 -13
View File
@@ -56,6 +56,15 @@ shaka.text.UITextDisplayer = class {
/** @private {?shaka.extern.TextDisplayerConfiguration} */ /** @private {?shaka.extern.TextDisplayerConfiguration} */
this.config_ = null; this.config_ = null;
/** @private {?shaka.extern.TextDisplayerConfiguration} */
this.previewConfig_ = null;
/** @private {?shaka.text.Cue} */
this.previewCue_ = null;
/** @private {boolean} */
this.showingPreviewCue_ = false;
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.textContainer_ = shaka.util.Dom.createHTMLElement('div'); this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
this.textContainer_.classList.add('shaka-text-container'); this.textContainer_.classList.add('shaka-text-container');
@@ -231,6 +240,38 @@ shaka.text.UITextDisplayer = class {
this.updateCaptions_(/* forceUpdate= */ true); this.updateCaptions_(/* forceUpdate= */ true);
} }
/**
* Temporarily previews text displayer style settings using the normal UI
* text rendering path.
*
* @param {!shaka.extern.TextDisplayerConfiguration} config
* @param {string} exampleText
* @export
*/
setTextStylePreview(config, exampleText) {
this.previewConfig_ =
/** @type {!shaka.extern.TextDisplayerConfiguration} */(
Object.assign({}, config));
this.previewCue_ = new shaka.text.Cue(
Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, exampleText);
this.updateCaptions_(/* forceUpdate= */ true);
}
/**
* Clears temporary text style preview settings.
* @export
*/
clearTextStylePreview() {
if (!this.previewConfig_ && !this.previewCue_) {
return;
}
this.previewConfig_ = null;
this.previewCue_ = null;
this.showingPreviewCue_ = false;
this.updateCaptions_(/* forceUpdate= */ true);
}
/** /**
* @override * @override
@@ -278,6 +319,9 @@ shaka.text.UITextDisplayer = class {
this.isTextVisible_ = false; this.isTextVisible_ = false;
this.cues_ = []; this.cues_ = [];
this.previewConfig_ = null;
this.previewCue_ = null;
this.showingPreviewCue_ = false;
this.captionsTimer_?.stop(); this.captionsTimer_?.stop();
this.captionsTimer_ = null; this.captionsTimer_ = null;
this.applyVisibilityTimer_?.stop(); this.applyVisibilityTimer_?.stop();
@@ -381,6 +425,14 @@ shaka.text.UITextDisplayer = class {
} }
} }
/**
* @return {?shaka.extern.TextDisplayerConfiguration}
* @private
*/
getActiveConfig_() {
return this.previewConfig_ || this.config_;
}
/** /**
* @private * @private
*/ */
@@ -450,6 +502,8 @@ shaka.text.UITextDisplayer = class {
* @private * @private
*/ */
updateCuesRecursive_(cues, container, currentTime, parents) { updateCuesRecursive_(cues, container, currentTime, parents) {
const config = this.getActiveConfig_();
// Set to true if the cues have changed in some way, which will require // Set to true if the cues have changed in some way, which will require
// DOM changes. E.g. if a cue was added or removed. // DOM changes. E.g. if a cue was added or removed.
let updateDOM = false; let updateDOM = false;
@@ -474,8 +528,8 @@ shaka.text.UITextDisplayer = class {
let cueKey = cue; let cueKey = cue;
let cueRegistry = this.currentCuesMap_.get(cue); let cueRegistry = this.currentCuesMap_.get(cue);
if (!cueRegistry && this.config_ && if (!cueRegistry && config &&
this.config_.positionArea != shaka.config.PositionArea.DEFAULT) { config.positionArea != shaka.config.PositionArea.DEFAULT) {
for (const key of this.currentCuesMap_.keys()) { for (const key of this.currentCuesMap_.keys()) {
if (shaka.text.Cue.equal(cue, key)) { if (shaka.text.Cue.equal(cue, key)) {
cueKey = key; cueKey = key;
@@ -551,8 +605,8 @@ shaka.text.UITextDisplayer = class {
}); });
for (const cue of toPlant) { for (const cue of toPlant) {
let cueRegistry = this.currentCuesMap_.get(cue); let cueRegistry = this.currentCuesMap_.get(cue);
if (!cueRegistry && if (!cueRegistry && config &&
this.config_.positionArea != shaka.config.PositionArea.DEFAULT) { config.positionArea != shaka.config.PositionArea.DEFAULT) {
for (const key of this.currentCuesMap_.keys()) { for (const key of this.currentCuesMap_.keys()) {
if (shaka.text.Cue.equal(cue, key)) { if (shaka.text.Cue.equal(cue, key)) {
cueRegistry = this.currentCuesMap_.get(key); cueRegistry = this.currentCuesMap_.get(key);
@@ -573,6 +627,30 @@ shaka.text.UITextDisplayer = class {
} }
} }
/**
* @param {!Array<!shaka.text.Cue>} cues
* @param {number} currentTime
* @return {boolean}
* @private
*/
hasCueAtTime_(cues, currentTime) {
return cues.some((cue) => {
return cue.startTime <= currentTime && cue.endTime > currentTime &&
this.hasCueContent_(cue);
});
}
/**
* @param {!shaka.text.Cue} cue
* @return {boolean}
* @private
*/
hasCueContent_(cue) {
const text = cue.payload.replace(/[\u00a0\u200B]/g, ' ').trim();
return !!text || !!cue.backgroundImage ||
cue.nestedCues.some((nestedCue) => this.hasCueContent_(nestedCue));
}
/** /**
* Display the current captions. * Display the current captions.
* @param {boolean=} forceUpdate * @param {boolean=} forceUpdate
@@ -583,9 +661,34 @@ shaka.text.UITextDisplayer = class {
return; return;
} }
const delay = this.config_?.subtitleDelay ?? 0; const config = this.getActiveConfig_();
const delay = config?.subtitleDelay ?? 0;
const currentTime = this.video_.currentTime - delay; const currentTime = this.video_.currentTime - delay;
if (!this.isTextVisible_ || forceUpdate) { const showPreviewCue = !!this.previewCue_ &&
!this.hasCueAtTime_(this.cues_, currentTime);
if (showPreviewCue != this.showingPreviewCue_) {
forceUpdate = true;
this.showingPreviewCue_ = showPreviewCue;
}
if (showPreviewCue) {
// Force a full clear if we are showing a preview. This prevents
// "stacking" bugs because the preview cue object changes
// frequently during hover.
forceUpdate = true;
}
const shouldBeVisible = this.isTextVisible_ || showPreviewCue;
if (shouldBeVisible) {
if (!this.textContainer_.parentElement) {
this.videoContainer_.appendChild(this.textContainer_);
}
} else {
if (this.textContainer_.parentElement) {
this.videoContainer_.removeChild(this.textContainer_);
}
}
if (!shouldBeVisible || forceUpdate) {
// Remove child elements from all regions. // Remove child elements from all regions.
for (const regionElement of this.regionElements_.values()) { for (const regionElement of this.regionElements_.values()) {
shaka.util.Dom.removeAllChildren(regionElement); shaka.util.Dom.removeAllChildren(regionElement);
@@ -596,7 +699,7 @@ shaka.text.UITextDisplayer = class {
this.currentCuesMap_.clear(); this.currentCuesMap_.clear();
this.regionElements_.clear(); this.regionElements_.clear();
} }
if (this.isTextVisible_) { if (shouldBeVisible) {
// Log currently attached cue elements for verification, later. // Log currently attached cue elements for verification, later.
const previousCuesMap = new Map(); const previousCuesMap = new Map();
if (goog.DEBUG) { if (goog.DEBUG) {
@@ -605,9 +708,10 @@ shaka.text.UITextDisplayer = class {
} }
} }
let cues = this.cues_; let cues = showPreviewCue && this.previewCue_ ?
if (this.config_ && [this.previewCue_] : this.cues_;
this.config_.positionArea != shaka.config.PositionArea.DEFAULT) { if (config &&
config.positionArea != shaka.config.PositionArea.DEFAULT) {
cues = cues.map((cue) => this.processCueStyle_(cue)); cues = cues.map((cue) => this.processCueStyle_(cue));
} }
@@ -634,13 +738,15 @@ shaka.text.UITextDisplayer = class {
/** @private */ /** @private */
processCueStyle_(cue) { processCueStyle_(cue) {
const config = /** @type {!shaka.extern.TextDisplayerConfiguration} */(
this.getActiveConfig_());
goog.asserts.assert( goog.asserts.assert(
this.config_.positionArea !== shaka.config.PositionArea.DEFAULT, config.positionArea !== shaka.config.PositionArea.DEFAULT,
'processCueStyle_ is intended to use on non default positioning'); 'processCueStyle_ is intended to use on non default positioning');
const modifiedCue = cue.clone(); const modifiedCue = cue.clone();
shaka.text.Utils.resetCuePositioning(modifiedCue); shaka.text.Utils.resetCuePositioning(modifiedCue);
modifiedCue.region = shaka.text.UITextDisplayer.CustomRegion_.value(); modifiedCue.region = shaka.text.UITextDisplayer.CustomRegion_.value();
switch (this.config_.positionArea) { switch (config.positionArea) {
case shaka.config.PositionArea.TOP_LEFT: case shaka.config.PositionArea.TOP_LEFT:
modifiedCue.textAlign = shaka.text.Cue.textAlign.LEFT; modifiedCue.textAlign = shaka.text.Cue.textAlign.LEFT;
modifiedCue.displayAlign = shaka.text.Cue.displayAlign.BEFORE; modifiedCue.displayAlign = shaka.text.Cue.displayAlign.BEFORE;
@@ -1011,7 +1117,8 @@ shaka.text.UITextDisplayer = class {
style.fontWeight = cue.fontWeight.toString(); style.fontWeight = cue.fontWeight.toString();
style.fontStyle = cue.fontStyle; style.fontStyle = cue.fontStyle;
style.letterSpacing = cue.letterSpacing; style.letterSpacing = cue.letterSpacing;
const fontScaleFactor = this.config_ ? this.config_.fontScaleFactor : 1; const config = this.getActiveConfig_();
const fontScaleFactor = config ? config.fontScaleFactor : 1;
if (fontScaleFactor !== 1 || cue.fontSize) { if (fontScaleFactor !== 1 || cue.fontSize) {
// Use browser default (1em) if fontSize is not set or empty // Use browser default (1em) if fontSize is not set or empty
const fontSize = cue.fontSize || '1em'; const fontSize = cue.fontSize || '1em';
+1
View File
@@ -20,6 +20,7 @@ describe('CastUtils', () => {
'getNetworkingEngine', // Handled specially 'getNetworkingEngine', // Handled specially
'getDrmEngine', // Handled specially 'getDrmEngine', // Handled specially
'getMediaElement', // Handled specially 'getMediaElement', // Handled specially
'getTextDisplayer', // Internal UI hook, not proxied.
'destroy', // Should use CastProxy.destroy instead 'destroy', // Should use CastProxy.destroy instead
'drmInfo', // Too large to proxy 'drmInfo', // Too large to proxy
'getManifest', // Too large to proxy 'getManifest', // Too large to proxy
+95 -2
View File
@@ -631,13 +631,106 @@ describe('UITextDisplayer', () => {
expect(videoContainer.childNodes.length).toBe(0); expect(videoContainer.childNodes.length).toBe(0);
}); });
it('previews text styles through the normal renderer', () => {
const cue = new shaka.text.Cue(0, 100, 'Previewed cue');
const config =
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
textDisplayer.configure(config);
textDisplayer.setTextVisibility(true);
textDisplayer.append([cue]);
updateCaptions();
const textContainer = videoContainer.querySelector('.shaka-text-container');
let cueElement = textContainer.querySelector('div');
expect(cueElement.textContent).toBe('Previewed cue');
expect(cueElement.style.fontSize).toBe('');
const previewConfig =
/** @type {!shaka.extern.TextDisplayerConfiguration} */(
Object.assign({}, config, {'fontScaleFactor': 2}));
textDisplayer.setTextStylePreview(
previewConfig, 'Subtitles example');
cueElement = textContainer.querySelector('div');
expect(cueElement.textContent).toBe('Previewed cue');
expect(cueElement.style.fontSize).toBe('2em');
textDisplayer.clearTextStylePreview();
cueElement = textContainer.querySelector('div');
expect(cueElement.textContent).toBe('Previewed cue');
expect(cueElement.style.fontSize).toBe('');
});
it('shows example text only when no cue is active during preview', () => {
const config =
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
textDisplayer.configure(config);
textDisplayer.setTextVisibility(true);
textDisplayer.setTextStylePreview(config, 'Subtitles example');
const textContainer = videoContainer.querySelector('.shaka-text-container');
expect(textContainer.textContent).toBe('Subtitles example');
textDisplayer.clearTextStylePreview();
expect(textContainer.textContent).toBe('');
textDisplayer.setTextStylePreview(config, 'Subtitles example');
expect(textContainer.textContent).toBe('Subtitles example');
textDisplayer.append([new shaka.text.Cue(0, 100, 'Real subtitle')]);
updateCaptions();
expect(textContainer.textContent).toBe('Real subtitle');
});
it('shows example text while normal text visibility is off', () => {
const config =
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
textDisplayer.configure(config);
textDisplayer.setTextStylePreview(config, 'Subtitles example');
const textContainer = videoContainer.querySelector('.shaka-text-container');
expect(textContainer.textContent).toBe('Subtitles example');
textDisplayer.clearTextStylePreview();
expect(videoContainer.querySelector('.shaka-text-container')).toBe(null);
});
it('replaces example text during repeated preview updates', () => {
const config =
shaka.util.PlayerConfiguration.createDefault().textDisplayer;
textDisplayer.configure(config);
textDisplayer.setTextVisibility(true);
textDisplayer.setTextStylePreview(config, 'First example');
const textContainer = videoContainer.querySelector('.shaka-text-container');
let cueElements = textContainer.querySelectorAll('div');
expect(cueElements.length).toBe(1);
expect(textContainer.textContent).toBe('First example');
textDisplayer.setTextStylePreview(config, 'Second example');
cueElements = textContainer.querySelectorAll('div');
expect(cueElements.length).toBe(1);
expect(textContainer.textContent).toBe('Second example');
});
it('positions cue at top-left when positionArea=TOP_LEFT', () => { it('positions cue at top-left when positionArea=TOP_LEFT', () => {
/** @type {!shaka.text.Cue} */ /** @type {!shaka.text.Cue} */
const cue = new shaka.text.Cue(0, 100, 'Top-Left'); const cue = new shaka.text.Cue(0, 100, 'Top-Left');
textDisplayer.setTextVisibility(true); textDisplayer.setTextVisibility(true);
const player = new shaka.Player(); const config =
const config = player.getConfiguration().textDisplayer; shaka.util.PlayerConfiguration.createDefault().textDisplayer;
config.positionArea = shaka.config.PositionArea.TOP_LEFT; config.positionArea = shaka.config.PositionArea.TOP_LEFT;
textDisplayer.configure(config); textDisplayer.configure(config);
+224
View File
@@ -656,6 +656,230 @@ describe('UI', () => {
}); });
}); });
describe('caption style preview', () => {
/** @type {shaka.ui.Controls} */
let controls;
/** @type {!jasmine.Spy} */
let setPreviewSpy;
/** @type {!jasmine.Spy} */
let clearPreviewSpy;
/**
* @param {!HTMLElement} menu
* @param {string} label
* @return {!HTMLElement}
*/
function getStyleOption(menu, label) {
const buttons = Array.from(
menu.querySelectorAll('button[role="menuitemradio"]'));
const button = buttons.find((button) => {
const span = button.querySelector('span');
return span && span.textContent == label;
});
expect(button).not.toBe(undefined);
return /** @type {!HTMLElement} */(button);
}
/**
* @return {!shaka.extern.TextDisplayerConfiguration}
*/
function latestPreviewConfig() {
const calls = setPreviewSpy.calls;
expect(calls.count()).toBeGreaterThan(0);
return /** @type {!shaka.extern.TextDisplayerConfiguration} */(
calls.mostRecent().args[0]);
}
/**
* @param {?shaka.Player} player
*/
function usePreviewTextDisplayer(player) {
expect(player).not.toBe(null);
const localPlayer = /** @type {!shaka.Player} */(player);
/** @type {?} */
const textDisplayer = {};
setPreviewSpy = textDisplayer.setTextStylePreview =
jasmine.createSpy('setTextStylePreview');
clearPreviewSpy = textDisplayer.clearTextStylePreview =
jasmine.createSpy('clearTextStylePreview');
spyOn(localPlayer, 'getTextDisplayer').and.returnValue(
/** @type {!shaka.extern.TextDisplayer} */(textDisplayer));
}
it('does not require player or displayer preview methods', () => {
const player = /** @type {?} */(new shaka.util.FakeEventTarget());
player.getConfiguration = () => {
return shaka.util.PlayerConfiguration.createDefault();
};
const localization = new shaka.ui.Localization('en');
shaka.ui.Locales.addTo(localization);
const preview = new shaka.ui.TextStylePreview(
/** @type {!shaka.Player} */(player), localization);
expect(() => {
preview.show();
preview.update({'fontScaleFactor': 2});
preview.reset();
preview.hide();
}).not.toThrow();
preview.release();
});
it('updates and reverts font size on hover', async () => {
const config = {
controlPanelElements: [
'captions-size',
],
customContextMenu: false,
};
const ui = await UiUtils.createUIThroughAPI(
videoContainer, video, config);
controls = ui.getControls();
player = controls.getLocalPlayer();
usePreviewTextDisplayer(player);
player.configure('textDisplayer.fontScaleFactor', 1.25);
controls.showUI();
const menu = UiUtils.getElementByClassName(
videoContainer, 'shaka-text-positions');
const button = UiUtils.getElementByClassName(
videoContainer, 'shaka-caption-size-button');
button.click();
expect(latestPreviewConfig().fontScaleFactor).toBe(1.25);
const largerOption = getStyleOption(menu, '200%');
UiUtils.simulateEvent(largerOption, 'mouseenter');
expect(latestPreviewConfig().fontScaleFactor).toBe(2);
UiUtils.simulateEvent(largerOption, 'mouseleave');
expect(latestPreviewConfig().fontScaleFactor).toBe(1.25);
const selectedOption = getStyleOption(menu, '150%');
selectedOption.click();
expect(latestPreviewConfig().fontScaleFactor).toBe(1.5);
UiUtils.simulateEvent(selectedOption, 'mouseleave');
expect(latestPreviewConfig().fontScaleFactor).toBe(1.5);
controls.hideSettingsMenus();
});
it('updates and reverts text position on focus', async () => {
const config = {
controlPanelElements: [
'captions-position',
],
customContextMenu: false,
};
const ui = await UiUtils.createUIThroughAPI(
videoContainer, video, config);
controls = ui.getControls();
player = controls.getLocalPlayer();
usePreviewTextDisplayer(player);
controls.showUI();
const menu = UiUtils.getElementByClassName(
videoContainer, 'shaka-text-positions');
const button = UiUtils.getElementByClassName(
videoContainer, 'shaka-caption-position-button');
button.click();
expect(latestPreviewConfig().positionArea)
.toBe(shaka.config.PositionArea.DEFAULT);
const topLeftOption = getStyleOption(menu, 'Top left');
topLeftOption.dispatchEvent(new Event('focus'));
expect(latestPreviewConfig().positionArea)
.toBe(shaka.config.PositionArea.TOP_LEFT);
topLeftOption.dispatchEvent(new Event('blur'));
expect(latestPreviewConfig().positionArea)
.toBe(shaka.config.PositionArea.DEFAULT);
});
it('keeps a committed text size as the preview baseline', async () => {
const config = {
controlPanelElements: [
'captions-size',
],
customContextMenu: false,
};
const ui = await UiUtils.createUIThroughAPI(
videoContainer, video, config);
controls = ui.getControls();
player = controls.getLocalPlayer();
usePreviewTextDisplayer(player);
player.configure('textDisplayer.fontScaleFactor', 1.25);
controls.showUI();
const menu = UiUtils.getElementByClassName(
videoContainer, 'shaka-text-positions');
const button = UiUtils.getElementByClassName(
videoContainer, 'shaka-caption-size-button');
button.click();
const largerOption = getStyleOption(menu, '200%');
UiUtils.simulateEvent(largerOption, 'mouseenter');
expect(latestPreviewConfig().fontScaleFactor).toBe(2);
player.configure('textDisplayer.fontScaleFactor', 1.5);
expect(latestPreviewConfig().fontScaleFactor).toBe(2);
UiUtils.simulateEvent(largerOption, 'mouseleave');
expect(latestPreviewConfig().fontScaleFactor).toBe(1.5);
});
it('hides the preview when a context menu closes', async () => {
const config = {
controlPanelElements: [],
contextMenuElements: [
'captions-size',
],
customContextMenu: true,
};
const ui = await UiUtils.createUIThroughAPI(
videoContainer, video, config);
controls = ui.getControls();
player = controls.getLocalPlayer();
usePreviewTextDisplayer(player);
controls.showUI();
const controlsContainer = controls.getControlsContainer();
UiUtils.simulateEvent(controlsContainer, 'contextmenu');
const captionsSizeButton = UiUtils.getElementByClassName(
videoContainer, 'shaka-caption-size-button');
captionsSizeButton.click();
UiUtils.simulateEvent(controlsContainer, 'click');
expect(clearPreviewSpy).toHaveBeenCalledTimes(1);
});
it('hides the preview when the UI is reconfigured', async () => {
const config = {
controlPanelElements: [
'captions-size',
],
customContextMenu: false,
};
const ui = await UiUtils.createUIThroughAPI(
videoContainer, video, config);
controls = ui.getControls();
player = controls.getLocalPlayer();
usePreviewTextDisplayer(player);
controls.showUI();
const button = UiUtils.getElementByClassName(
videoContainer, 'shaka-caption-size-button');
button.click();
ui.configure('showUIAlways', true);
expect(clearPreviewSpy).toHaveBeenCalledTimes(1);
});
});
describe('resolutions menu', () => { describe('resolutions menu', () => {
/** @type {!HTMLElement} */ /** @type {!HTMLElement} */
let resolutionsMenu; let resolutionsMenu;
+1
View File
@@ -132,6 +132,7 @@ shaka.ui.ContextMenu = class extends shaka.ui.Element {
*/ */
closeMenu() { closeMenu() {
shaka.ui.Utils.setDisplay(this.contextMenu_, false); shaka.ui.Utils.setDisplay(this.contextMenu_, false);
this.controls.hideTextStylePreview();
} }
/** /**
+42
View File
@@ -26,6 +26,7 @@ goog.require('shaka.ui.Localization');
goog.require('shaka.ui.MediaSession'); goog.require('shaka.ui.MediaSession');
goog.require('shaka.ui.SeekBar'); goog.require('shaka.ui.SeekBar');
goog.require('shaka.ui.SkipAdButton'); goog.require('shaka.ui.SkipAdButton');
goog.require('shaka.ui.TextStylePreview');
goog.require('shaka.ui.Utils'); goog.require('shaka.ui.Utils');
goog.require('shaka.ui.VRManager'); goog.require('shaka.ui.VRManager');
goog.require('shaka.util.ArrayUtils'); goog.require('shaka.util.ArrayUtils');
@@ -290,6 +291,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
for (const menu of this.menus_) { for (const menu of this.menus_) {
shaka.ui.Utils.setDisplay(menu, /* visible= */ false); shaka.ui.Utils.setDisplay(menu, /* visible= */ false);
} }
this.dispatchEvent(new shaka.util.FakeEvent('submenuclose'));
this.hideTextStylePreview();
if (this.config_.enableTooltips) { if (this.config_.enableTooltips) {
this.controlsButtonPanel_.classList.add('shaka-tooltips-on'); this.controlsButtonPanel_.classList.add('shaka-tooltips-on');
} }
@@ -331,6 +334,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
/** @private {shaka.ui.Localization} */ /** @private {shaka.ui.Localization} */
this.localization_ = shaka.ui.Controls.createLocalization_(); this.localization_ = shaka.ui.Controls.createLocalization_();
/** @private {?shaka.ui.TextStylePreview} */
this.textStylePreview_ = new shaka.ui.TextStylePreview(
this.localPlayer_, this.localization_);
/** @private {shaka.util.EventManager} */ /** @private {shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager(); this.eventManager_ = new shaka.util.EventManager();
@@ -431,6 +438,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
await document.exitPictureInPicture(); await document.exitPictureInPicture();
} }
this.textStylePreview_?.release();
this.textStylePreview_ = null;
this.eventManager_?.release(); this.eventManager_?.release();
this.eventManager_ = null; this.eventManager_ = null;
@@ -568,6 +578,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
*/ */
configure(config) { configure(config) {
this.config_ = config; this.config_ = config;
this.hideTextStylePreview();
this.castProxy_.changeReceiverId(config.castReceiverAppId, this.castProxy_.changeReceiverId(config.castReceiverAppId,
config.castAndroidReceiverCompatible); config.castAndroidReceiverCompatible);
@@ -912,6 +923,35 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
for (const menu of this.contextMenus_) { for (const menu of this.contextMenus_) {
shaka.ui.Utils.setDisplay(menu, /* visible= */ false); shaka.ui.Utils.setDisplay(menu, /* visible= */ false);
} }
this.hideTextStylePreview();
}
/**
* Shows a temporary subtitle with the current text displayer style.
*/
showTextStylePreview() {
this.textStylePreview_?.show();
}
/**
* Updates the temporary subtitle style without changing player config.
*
* @param {!shaka.ui.TextStylePreview.Configuration=} config
*/
updateTextStylePreview(config = {}) {
this.textStylePreview_?.update(config);
}
/**
* Reverts the temporary subtitle to the style captured when the menu opened.
*/
resetTextStylePreview() {
this.textStylePreview_?.reset();
}
/** Removes the temporary subtitle style preview. */
hideTextStylePreview() {
this.textStylePreview_?.hide();
} }
/** /**
@@ -1318,6 +1358,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
this.videoContainer_.getElementsByClassName('shaka-settings-menu')); this.videoContainer_.getElementsByClassName('shaka-settings-menu'));
this.menus_.push(...Array.from( this.menus_.push(...Array.from(
this.videoContainer_.getElementsByClassName('shaka-overflow-menu'))); this.videoContainer_.getElementsByClassName('shaka-overflow-menu')));
this.menus_.push(...Array.from(
this.videoContainer_.getElementsByClassName('shaka-sub-menu')));
this.contextMenus_ = Array.from( this.contextMenus_ = Array.from(
this.videoContainer_.getElementsByClassName('shaka-context-menu')); this.videoContainer_.getElementsByClassName('shaka-context-menu'));
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Skip ahead to live", "SKIP_TO_LIVE": "Skip ahead to live",
"STATISTICS": "Statistics", "STATISTICS": "Statistics",
"SUBTITLE_FORCED": "Forced", "SUBTITLE_FORCED": "Forced",
"SUBTITLES_EXAMPLE": "Subtitles example",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Toggle stereoscopic", "TOGGLE_STEREOSCOPIC": "Toggle stereoscopic",
"UNDETERMINED_LANGUAGE": "Undetermined", "UNDETERMINED_LANGUAGE": "Undetermined",
+1
View File
@@ -41,6 +41,7 @@
"SKIP_TO_LIVE": "الانتقال إلى بث مباشر", "SKIP_TO_LIVE": "الانتقال إلى بث مباشر",
"STATISTICS": "الإحصاءات", "STATISTICS": "الإحصاءات",
"SUBTITLE_FORCED": "عرض إجباري", "SUBTITLE_FORCED": "عرض إجباري",
"SUBTITLES_EXAMPLE": "مثال على الترجمة",
"SURROUND": "صوت محيطي", "SURROUND": "صوت محيطي",
"TOGGLE_STEREOSCOPIC": "إيقاف العرض المجسّم أو تفعيله", "TOGGLE_STEREOSCOPIC": "إيقاف العرض المجسّم أو تفعيله",
"UNDETERMINED_LANGUAGE": "غير محدد", "UNDETERMINED_LANGUAGE": "غير محدد",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Прапусціць і перайсці да жывога эфіра", "SKIP_TO_LIVE": "Прапусціць і перайсці да жывога эфіра",
"STATISTICS": "Статыстыка", "STATISTICS": "Статыстыка",
"SUBTITLE_FORCED": "Субцітры прымусова", "SUBTITLE_FORCED": "Субцітры прымусова",
"SUBTITLES_EXAMPLE": "Прыклад субцітраў",
"SURROUND": "Аб'ёмны гук", "SURROUND": "Аб'ёмны гук",
"TOGGLE_STEREOSCOPIC": "Уключыць або адключыць стэрэаскапічны рэжым", "TOGGLE_STEREOSCOPIC": "Уключыць або адключыць стэрэаскапічны рэжым",
"UNDETERMINED_LANGUAGE": "Не пазначана", "UNDETERMINED_LANGUAGE": "Не пазначана",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Преминаване напред към предаването на живо", "SKIP_TO_LIVE": "Преминаване напред към предаването на живо",
"STATISTICS": "Статистически данни", "STATISTICS": "Статистически данни",
"SUBTITLE_FORCED": "Принудително", "SUBTITLE_FORCED": "Принудително",
"SUBTITLES_EXAMPLE": "Примерни субтитри",
"SURROUND": "Съраунд", "SURROUND": "Съраунд",
"TOGGLE_STEREOSCOPIC": "Превключване на стереоскопичния режим", "TOGGLE_STEREOSCOPIC": "Превключване на стереоскопичния режим",
"UNDETERMINED_LANGUAGE": "Неопределено", "UNDETERMINED_LANGUAGE": "Неопределено",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Passa a l'emissió en directe", "SKIP_TO_LIVE": "Passa a l'emissió en directe",
"STATISTICS": "Estadístiques", "STATISTICS": "Estadístiques",
"SUBTITLE_FORCED": "Forçat", "SUBTITLE_FORCED": "Forçat",
"SUBTITLES_EXAMPLE": "Exemple de subtítols",
"SURROUND": "So envoltant", "SURROUND": "So envoltant",
"TOGGLE_STEREOSCOPIC": "Commuta l'opció estereoscòpica", "TOGGLE_STEREOSCOPIC": "Commuta l'opció estereoscòpica",
"UNDETERMINED_LANGUAGE": "Sense especificar", "UNDETERMINED_LANGUAGE": "Sense especificar",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Přeskočit na živé vysílání", "SKIP_TO_LIVE": "Přeskočit na živé vysílání",
"STATISTICS": "Statistiky", "STATISTICS": "Statistiky",
"SUBTITLE_FORCED": "Vynuceno", "SUBTITLE_FORCED": "Vynuceno",
"SUBTITLES_EXAMPLE": "Ukázka titulků",
"SURROUND": "Prostorový zvuk", "SURROUND": "Prostorový zvuk",
"TOGGLE_STEREOSCOPIC": "Přepnout stereoskopické zobrazení", "TOGGLE_STEREOSCOPIC": "Přepnout stereoskopické zobrazení",
"UNDETERMINED_LANGUAGE": "Neurčeno", "UNDETERMINED_LANGUAGE": "Neurčeno",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Gå videre til liveudsendelse", "SKIP_TO_LIVE": "Gå videre til liveudsendelse",
"STATISTICS": "Statistik", "STATISTICS": "Statistik",
"SUBTITLE_FORCED": "Gennemtvunget", "SUBTITLE_FORCED": "Gennemtvunget",
"SUBTITLES_EXAMPLE": "Eksempel på undertekster",
"SURROUND": "Surroundsound", "SURROUND": "Surroundsound",
"TOGGLE_STEREOSCOPIC": "Slå stereoskopisk visning til/fra", "TOGGLE_STEREOSCOPIC": "Slå stereoskopisk visning til/fra",
"UNDETERMINED_LANGUAGE": "Ikke fastslået", "UNDETERMINED_LANGUAGE": "Ikke fastslået",
+1
View File
@@ -50,6 +50,7 @@
"SKIP_TO_LIVE": "Zum Live-Videostream wechseln", "SKIP_TO_LIVE": "Zum Live-Videostream wechseln",
"STATISTICS": "Statistiken", "STATISTICS": "Statistiken",
"SUBTITLE_FORCED": "Erzwungen", "SUBTITLE_FORCED": "Erzwungen",
"SUBTITLES_EXAMPLE": "Beispiel für Untertitel",
"SUBTITLE_POSITION": "Untertitelposition", "SUBTITLE_POSITION": "Untertitelposition",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Einstellung „stereoskopisch“ ein/ausschalten", "TOGGLE_STEREOSCOPIC": "Einstellung „stereoskopisch“ ein/ausschalten",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Μετάβαση στη ζωντανή μετάδοση", "SKIP_TO_LIVE": "Μετάβαση στη ζωντανή μετάδοση",
"STATISTICS": "Στατιστικά στοιχεία", "STATISTICS": "Στατιστικά στοιχεία",
"SUBTITLE_FORCED": "Αναγκαστικό", "SUBTITLE_FORCED": "Αναγκαστικό",
"SUBTITLES_EXAMPLE": "Παράδειγμα υπότιτλων",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Εναλλαγή στερεοσκοπικής", "TOGGLE_STEREOSCOPIC": "Εναλλαγή στερεοσκοπικής",
"UNDETERMINED_LANGUAGE": "Δεν έχει καθοριστεί", "UNDETERMINED_LANGUAGE": "Δεν έχει καθοριστεί",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Skip ahead to live", "SKIP_TO_LIVE": "Skip ahead to live",
"STATISTICS": "Statistics", "STATISTICS": "Statistics",
"SUBTITLE_FORCED": "Forced", "SUBTITLE_FORCED": "Forced",
"SUBTITLES_EXAMPLE": "Subtitles example",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Toggle stereoscopic", "TOGGLE_STEREOSCOPIC": "Toggle stereoscopic",
"UNDETERMINED_LANGUAGE": "Undetermined", "UNDETERMINED_LANGUAGE": "Undetermined",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "[Šķîþ åĥéåð ţö ļîvé one two three four]", "SKIP_TO_LIVE": "[Šķîþ åĥéåð ţö ļîvé one two three four]",
"STATISTICS": "[Šţåţîšţîçš one two]", "STATISTICS": "[Šţåţîšţîçš one two]",
"SUBTITLE_FORCED": "[Föŕçéð one]", "SUBTITLE_FORCED": "[Föŕçéð one]",
"SUBTITLES_EXAMPLE": "[Šüƀţîţļéš éẋåɱƥļé one two]",
"SURROUND": "[Šûŕŕöûñð one]", "SURROUND": "[Šûŕŕöûñð one]",
"TOGGLE_STEREOSCOPIC": "[Ţöĝĝļé šţéŕéöšçöþîç one two three]", "TOGGLE_STEREOSCOPIC": "[Ţöĝĝļé šţéŕéöšçöþîç one two three]",
"UNDETERMINED_LANGUAGE": "[Ûñðéţéŕmîñéð one two]", "UNDETERMINED_LANGUAGE": "[Ûñðéţéŕmîñéð one two]",
+1
View File
@@ -54,6 +54,7 @@
"SKIP_PREVIOUS": "Previous", "SKIP_PREVIOUS": "Previous",
"STATISTICS": "Statistics", "STATISTICS": "Statistics",
"SUBTITLE_FORCED": "Forced", "SUBTITLE_FORCED": "Forced",
"SUBTITLES_EXAMPLE": "Subtitles example",
"SUBTITLE_POSITION": "Subtitle position", "SUBTITLE_POSITION": "Subtitle position",
"SUBTITLE_SIZE": "Subtitle size", "SUBTITLE_SIZE": "Subtitle size",
"SURROUND": "Surround", "SURROUND": "Surround",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Adelantar hasta la transmisión en vivo", "SKIP_TO_LIVE": "Adelantar hasta la transmisión en vivo",
"STATISTICS": "Estadísticas", "STATISTICS": "Estadísticas",
"SUBTITLE_FORCED": "Forzado", "SUBTITLE_FORCED": "Forzado",
"SUBTITLES_EXAMPLE": "Ejemplo de subtítulos",
"SURROUND": "Sonido envolvente", "SURROUND": "Sonido envolvente",
"TOGGLE_STEREOSCOPIC": "Activar o desactivar el modo estereoscópico", "TOGGLE_STEREOSCOPIC": "Activar o desactivar el modo estereoscópico",
"UNDETERMINED_LANGUAGE": "Sin especificar", "UNDETERMINED_LANGUAGE": "Sin especificar",
+1
View File
@@ -54,6 +54,7 @@
"SKIP_PREVIOUS": "Anterior", "SKIP_PREVIOUS": "Anterior",
"STATISTICS": "Estadísticas", "STATISTICS": "Estadísticas",
"SUBTITLE_FORCED": "Forzado", "SUBTITLE_FORCED": "Forzado",
"SUBTITLES_EXAMPLE": "Ejemplo de subtítulos",
"SUBTITLE_POSITION": "Posición de los subtítulos", "SUBTITLE_POSITION": "Posición de los subtítulos",
"SUBTITLE_SIZE": "Tamaño de los subtítulos", "SUBTITLE_SIZE": "Tamaño de los subtítulos",
"SURROUND": "Envolvente", "SURROUND": "Envolvente",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "برای پخش مستقیم رد شوید و به جلو بروید", "SKIP_TO_LIVE": "برای پخش مستقیم رد شوید و به جلو بروید",
"STATISTICS": "آمار", "STATISTICS": "آمار",
"SUBTITLE_FORCED": "نمایش اجباری", "SUBTITLE_FORCED": "نمایش اجباری",
"SUBTITLES_EXAMPLE": "نمونه زیرنویس",
"SURROUND": "فراگیر", "SURROUND": "فراگیر",
"TOGGLE_STEREOSCOPIC": "روشن/خاموش کردن برجسته‌نمایی", "TOGGLE_STEREOSCOPIC": "روشن/خاموش کردن برجسته‌نمایی",
"UNDETERMINED_LANGUAGE": "نامعین", "UNDETERMINED_LANGUAGE": "نامعین",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Siirry suoraan lähetykseen", "SKIP_TO_LIVE": "Siirry suoraan lähetykseen",
"STATISTICS": "Tilastot", "STATISTICS": "Tilastot",
"SUBTITLE_FORCED": "Pakotettu", "SUBTITLE_FORCED": "Pakotettu",
"SUBTITLES_EXAMPLE": "Tekstitysesimerkki",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Stereoskooppi päälle/pois", "TOGGLE_STEREOSCOPIC": "Stereoskooppi päälle/pois",
"UNDETERMINED_LANGUAGE": "Määrittämätön", "UNDETERMINED_LANGUAGE": "Määrittämätön",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Lumaktaw sa live", "SKIP_TO_LIVE": "Lumaktaw sa live",
"STATISTICS": "Mga Istatistika", "STATISTICS": "Mga Istatistika",
"SUBTITLE_FORCED": "Sapilitan", "SUBTITLE_FORCED": "Sapilitan",
"SUBTITLES_EXAMPLE": "Halimbawa ng subtitle",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "I-toggle sa stereoscopic", "TOGGLE_STEREOSCOPIC": "I-toggle sa stereoscopic",
"UNDETERMINED_LANGUAGE": "Hindi tinukoy", "UNDETERMINED_LANGUAGE": "Hindi tinukoy",
+1
View File
@@ -53,6 +53,7 @@
"SKIP_PREVIOUS": "Précédente", "SKIP_PREVIOUS": "Précédente",
"STATISTICS": "Statistiques", "STATISTICS": "Statistiques",
"SUBTITLE_FORCED": "Forcé", "SUBTITLE_FORCED": "Forcé",
"SUBTITLES_EXAMPLE": "Exemple de sous-titres",
"SUBTITLE_POSITION": "Position des sous-titres", "SUBTITLE_POSITION": "Position des sous-titres",
"SUBTITLE_SIZE": "Taille des sous-titres", "SUBTITLE_SIZE": "Taille des sous-titres",
"SURROUND": "Surround", "SURROUND": "Surround",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "सीधा लाइव प्रसारण देखने के लिए छोड़ें", "SKIP_TO_LIVE": "सीधा लाइव प्रसारण देखने के लिए छोड़ें",
"STATISTICS": "आंकड़े", "STATISTICS": "आंकड़े",
"SUBTITLE_FORCED": "फ़ोर्स्ड", "SUBTITLE_FORCED": "फ़ोर्स्ड",
"SUBTITLES_EXAMPLE": "उपशीर्षक उदाहरण",
"SURROUND": "सराउंड", "SURROUND": "सराउंड",
"TOGGLE_STEREOSCOPIC": "स्टीरियोस्कोपिक को टॉगल करें", "TOGGLE_STEREOSCOPIC": "स्टीरियोस्कोपिक को टॉगल करें",
"UNDETERMINED_LANGUAGE": "जानकारी नहीं है", "UNDETERMINED_LANGUAGE": "जानकारी नहीं है",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Preskoči na uživo", "SKIP_TO_LIVE": "Preskoči na uživo",
"STATISTICS": "Statistika", "STATISTICS": "Statistika",
"SUBTITLE_FORCED": "Prisilno", "SUBTITLE_FORCED": "Prisilno",
"SUBTITLES_EXAMPLE": "Primjer titlova",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Prebaci stereoskopski prikaz", "TOGGLE_STEREOSCOPIC": "Prebaci stereoskopski prikaz",
"UNDETERMINED_LANGUAGE": "Neodređeno", "UNDETERMINED_LANGUAGE": "Neodređeno",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Ugrás az élő közvetítésre", "SKIP_TO_LIVE": "Ugrás az élő közvetítésre",
"STATISTICS": "Statisztikák", "STATISTICS": "Statisztikák",
"SUBTITLE_FORCED": "Kényszerített", "SUBTITLE_FORCED": "Kényszerített",
"SUBTITLES_EXAMPLE": "Feliratminta",
"SURROUND": "Térhatású hangzás", "SURROUND": "Térhatású hangzás",
"TOGGLE_STEREOSCOPIC": "Sztereó térhatás be-/kikapcsolása", "TOGGLE_STEREOSCOPIC": "Sztereó térhatás be-/kikapcsolása",
"UNDETERMINED_LANGUAGE": "Meghatározatlan", "UNDETERMINED_LANGUAGE": "Meghatározatlan",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Langsung ke live streaming", "SKIP_TO_LIVE": "Langsung ke live streaming",
"STATISTICS": "Statistik", "STATISTICS": "Statistik",
"SUBTITLE_FORCED": "Dipaksa", "SUBTITLE_FORCED": "Dipaksa",
"SUBTITLES_EXAMPLE": "Contoh subtitle",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Aktifkan/nonaktifkan stereoskop", "TOGGLE_STEREOSCOPIC": "Aktifkan/nonaktifkan stereoskop",
"UNDETERMINED_LANGUAGE": "Belum ditentukan", "UNDETERMINED_LANGUAGE": "Belum ditentukan",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Passa alla trasmissione dal vivo", "SKIP_TO_LIVE": "Passa alla trasmissione dal vivo",
"STATISTICS": "Statistiche", "STATISTICS": "Statistiche",
"SUBTITLE_FORCED": "Forzata", "SUBTITLE_FORCED": "Forzata",
"SUBTITLES_EXAMPLE": "Esempio di sottotitoli",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Pulsante di attivazione/disattivazione stereoscopico", "TOGGLE_STEREOSCOPIC": "Pulsante di attivazione/disattivazione stereoscopico",
"UNDETERMINED_LANGUAGE": "Indeterminata", "UNDETERMINED_LANGUAGE": "Indeterminata",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "דילוג קדימה לשידור הישיר", "SKIP_TO_LIVE": "דילוג קדימה לשידור הישיר",
"STATISTICS": "נתונים סטטיסטיים", "STATISTICS": "נתונים סטטיסטיים",
"SUBTITLE_FORCED": "מאולץ", "SUBTITLE_FORCED": "מאולץ",
"SUBTITLES_EXAMPLE": "דוגמה לכתוביות",
"SURROUND": "סראונד", "SURROUND": "סראונד",
"TOGGLE_STEREOSCOPIC": "החלפת מצב סטריאוסקופי", "TOGGLE_STEREOSCOPIC": "החלפת מצב סטריאוסקופי",
"UNDETERMINED_LANGUAGE": "לא ידוע", "UNDETERMINED_LANGUAGE": "לא ידוע",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "ライブ配信までスキップ", "SKIP_TO_LIVE": "ライブ配信までスキップ",
"STATISTICS": "統計情報", "STATISTICS": "統計情報",
"SUBTITLE_FORCED": "強制", "SUBTITLE_FORCED": "強制",
"SUBTITLES_EXAMPLE": "字幕の例",
"SURROUND": "サラウンド", "SURROUND": "サラウンド",
"TOGGLE_STEREOSCOPIC": "立体画像を切り替える", "TOGGLE_STEREOSCOPIC": "立体画像を切り替える",
"UNDETERMINED_LANGUAGE": "不明", "UNDETERMINED_LANGUAGE": "不明",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "라이브 동영상으로 건너뛰기", "SKIP_TO_LIVE": "라이브 동영상으로 건너뛰기",
"STATISTICS": "통계", "STATISTICS": "통계",
"SUBTITLE_FORCED": "강제", "SUBTITLE_FORCED": "강제",
"SUBTITLES_EXAMPLE": "자막 예시",
"SURROUND": "서라운드", "SURROUND": "서라운드",
"TOGGLE_STEREOSCOPIC": "입체 보기 전환", "TOGGLE_STEREOSCOPIC": "입체 보기 전환",
"UNDETERMINED_LANGUAGE": "미정", "UNDETERMINED_LANGUAGE": "미정",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Praleisti ir transliuoti tiesiogiai", "SKIP_TO_LIVE": "Praleisti ir transliuoti tiesiogiai",
"STATISTICS": "Statistika", "STATISTICS": "Statistika",
"SUBTITLE_FORCED": "Priverstinis", "SUBTITLE_FORCED": "Priverstinis",
"SUBTITLES_EXAMPLE": "Subtitrų pavyzdys",
"SURROUND": "Erdvinis garsas", "SURROUND": "Erdvinis garsas",
"TOGGLE_STEREOSCOPIC": "Perjungti stereoskopinį vaizdą", "TOGGLE_STEREOSCOPIC": "Perjungti stereoskopinį vaizdą",
"UNDETERMINED_LANGUAGE": "Nenustatyta", "UNDETERMINED_LANGUAGE": "Nenustatyta",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Pāriet uz tiešraidi", "SKIP_TO_LIVE": "Pāriet uz tiešraidi",
"STATISTICS": "Statistika", "STATISTICS": "Statistika",
"SUBTITLE_FORCED": "Piespiedu kārtā", "SUBTITLE_FORCED": "Piespiedu kārtā",
"SUBTITLES_EXAMPLE": "Subtitru piemērs",
"SURROUND": "Telpiskā skaņa", "SURROUND": "Telpiskā skaņa",
"TOGGLE_STEREOSCOPIC": "Pārslēgt stereoskopisko režīmu", "TOGGLE_STEREOSCOPIC": "Pārslēgt stereoskopisko režīmu",
"UNDETERMINED_LANGUAGE": "Nenoteikts", "UNDETERMINED_LANGUAGE": "Nenoteikts",
+1
View File
@@ -50,6 +50,7 @@
"SKIP_TO_LIVE": "Doorgaan naar live", "SKIP_TO_LIVE": "Doorgaan naar live",
"STATISTICS": "Statistieken", "STATISTICS": "Statistieken",
"SUBTITLE_FORCED": "Afgedwongen", "SUBTITLE_FORCED": "Afgedwongen",
"SUBTITLES_EXAMPLE": "Voorbeeld van ondertitels",
"SUBTITLE_POSITION": "Positie van ondertitels", "SUBTITLE_POSITION": "Positie van ondertitels",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Stereoscopisch aan-/uitzetten", "TOGGLE_STEREOSCOPIC": "Stereoscopisch aan-/uitzetten",
+1
View File
@@ -41,6 +41,7 @@
"SKIP_TO_LIVE": "Hopp frem til direktesending", "SKIP_TO_LIVE": "Hopp frem til direktesending",
"STATISTICS": "Statistikk", "STATISTICS": "Statistikk",
"SUBTITLE_FORCED": "Tvungent", "SUBTITLE_FORCED": "Tvungent",
"SUBTITLES_EXAMPLE": "Eksempel på undertekster",
"SURROUND": "Surround-lyd", "SURROUND": "Surround-lyd",
"TOGGLE_STEREOSCOPIC": "Slå stereoskopisk modus av/på", "TOGGLE_STEREOSCOPIC": "Slå stereoskopisk modus av/på",
"UNDETERMINED_LANGUAGE": "Ubestemt", "UNDETERMINED_LANGUAGE": "Ubestemt",
+2 -1
View File
@@ -35,8 +35,9 @@
"SKIP_TO_LIVE": "Anar al dirècte", "SKIP_TO_LIVE": "Anar al dirècte",
"STATISTICS": "Estatisticas", "STATISTICS": "Estatisticas",
"SUBTITLE_FORCED": "Forçat", "SUBTITLE_FORCED": "Forçat",
"SUBTITLES_EXAMPLE": "Exemple de sostítols",
"UNDETERMINED_LANGUAGE": "Lenga indeterminada", "UNDETERMINED_LANGUAGE": "Lenga indeterminada",
"UNMUTE": "Desamudir", "UNMUTE": "Desamudir",
"UNRECOGNIZED_LANGUAGE": "Non reconeguda", "UNRECOGNIZED_LANGUAGE": "Non reconeguda",
"VOLUME": "Volum", "VOLUME": "Volum"
} }
+1
View File
@@ -52,6 +52,7 @@
"SKIP_TO_LIVE": "Przejdź do transmisji na żywo", "SKIP_TO_LIVE": "Przejdź do transmisji na żywo",
"STATISTICS": "Statystyki", "STATISTICS": "Statystyki",
"SUBTITLE_FORCED": "Wymuszone", "SUBTITLE_FORCED": "Wymuszone",
"SUBTITLES_EXAMPLE": "Przykład napisów",
"SUBTITLE_POSITION": "Pozycja napisów", "SUBTITLE_POSITION": "Pozycja napisów",
"SUBTITLE_SIZE": "Rozmiar napisów", "SUBTITLE_SIZE": "Rozmiar napisów",
"SURROUND": "Przestrzenny", "SURROUND": "Przestrzenny",
+1
View File
@@ -41,6 +41,7 @@
"SKIP_TO_LIVE": "Pular para transmissão ao vivo", "SKIP_TO_LIVE": "Pular para transmissão ao vivo",
"STATISTICS": "Estatísticas", "STATISTICS": "Estatísticas",
"SUBTITLE_FORCED": "Exibição forçada", "SUBTITLE_FORCED": "Exibição forçada",
"SUBTITLES_EXAMPLE": "Exemplo de legendas",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Alternar imagem estereoscópica", "TOGGLE_STEREOSCOPIC": "Alternar imagem estereoscópica",
"UNDETERMINED_LANGUAGE": "Indeterminado", "UNDETERMINED_LANGUAGE": "Indeterminado",
+1
View File
@@ -41,6 +41,7 @@
"SKIP_TO_LIVE": "Avançar para o direto", "SKIP_TO_LIVE": "Avançar para o direto",
"STATISTICS": "Estatísticas", "STATISTICS": "Estatísticas",
"SUBTITLE_FORCED": "Forçada", "SUBTITLE_FORCED": "Forçada",
"SUBTITLES_EXAMPLE": "Exemplo de legendas",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Ativar/desativar estereoscópico", "TOGGLE_STEREOSCOPIC": "Ativar/desativar estereoscópico",
"UNDETERMINED_LANGUAGE": "Indeterminado", "UNDETERMINED_LANGUAGE": "Indeterminado",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Accesează difuzarea live", "SKIP_TO_LIVE": "Accesează difuzarea live",
"STATISTICS": "Statistici", "STATISTICS": "Statistici",
"SUBTITLE_FORCED": "Forțat", "SUBTITLE_FORCED": "Forțat",
"SUBTITLES_EXAMPLE": "Exemplu de subtitrări",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Comută la stereoscopic", "TOGGLE_STEREOSCOPIC": "Comută la stereoscopic",
"UNDETERMINED_LANGUAGE": "Nestabilită", "UNDETERMINED_LANGUAGE": "Nestabilită",
+1
View File
@@ -41,6 +41,7 @@
"SKIP_TO_LIVE": "Пропустить и перейти к прямой трансляции", "SKIP_TO_LIVE": "Пропустить и перейти к прямой трансляции",
"STATISTICS": "Статистика", "STATISTICS": "Статистика",
"SUBTITLE_FORCED": "Субтитры принудительно", "SUBTITLE_FORCED": "Субтитры принудительно",
"SUBTITLES_EXAMPLE": "Пример субтитров",
"SURROUND": "Объемный звук", "SURROUND": "Объемный звук",
"TOGGLE_STEREOSCOPIC": "Включить или отключить стереоскопический режим", "TOGGLE_STEREOSCOPIC": "Включить или отключить стереоскопический режим",
"UNDETERMINED_LANGUAGE": "Не указано", "UNDETERMINED_LANGUAGE": "Не указано",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "  ", "SKIP_TO_LIVE": "  ",
"STATISTICS": " ", "STATISTICS": " ",
"SUBTITLE_FORCED": "  ", "SUBTITLE_FORCED": "  ",
"SUBTITLES_EXAMPLE": "Subtitles example",
"SURROUND": "", "SURROUND": "",
"TOGGLE_STEREOSCOPIC": "", "TOGGLE_STEREOSCOPIC": "",
"UNDETERMINED_LANGUAGE": " ", "UNDETERMINED_LANGUAGE": " ",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Preskočiť na živé vysielanie", "SKIP_TO_LIVE": "Preskočiť na živé vysielanie",
"STATISTICS": "Štatistiky", "STATISTICS": "Štatistiky",
"SUBTITLE_FORCED": "Vynútené", "SUBTITLE_FORCED": "Vynútené",
"SUBTITLES_EXAMPLE": "Príklad titulkov",
"SURROUND": "Priestorový zvuk", "SURROUND": "Priestorový zvuk",
"TOGGLE_STEREOSCOPIC": "Prepnúť na stereoskopický režim", "TOGGLE_STEREOSCOPIC": "Prepnúť na stereoskopický režim",
"UNDETERMINED_LANGUAGE": "Neurčené", "UNDETERMINED_LANGUAGE": "Neurčené",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Preskoči naprej na oddajanje v živo", "SKIP_TO_LIVE": "Preskoči naprej na oddajanje v živo",
"STATISTICS": "Statistični podatki", "STATISTICS": "Statistični podatki",
"SUBTITLE_FORCED": "Vsiljeno", "SUBTITLE_FORCED": "Vsiljeno",
"SUBTITLES_EXAMPLE": "Primer podnapisov",
"SURROUND": "Prostorski zvok", "SURROUND": "Prostorski zvok",
"TOGGLE_STEREOSCOPIC": "Preklop stereoskopskega načina", "TOGGLE_STEREOSCOPIC": "Preklop stereoskopskega načina",
"UNDETERMINED_LANGUAGE": "Nedoločen", "UNDETERMINED_LANGUAGE": "Nedoločen",
+4
View File
@@ -223,6 +223,10 @@
"description": "Label used to identify a subtitle track that is forced to be shown.", "description": "Label used to identify a subtitle track that is forced to be shown.",
"message": "Forced" "message": "Forced"
}, },
"SUBTITLES_EXAMPLE": {
"description": "Example subtitle text shown while previewing subtitle style settings when no subtitle is currently visible.",
"message": "Subtitles example"
},
"SUBTITLE_POSITION": { "SUBTITLE_POSITION": {
"description": "Label for a button used to indicate a subtitle position.", "description": "Label for a button used to indicate a subtitle position.",
"message": "Subtitle position" "message": "Subtitle position"
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Прескочи на емитовање уживо", "SKIP_TO_LIVE": "Прескочи на емитовање уживо",
"STATISTICS": "Статистика", "STATISTICS": "Статистика",
"SUBTITLE_FORCED": "Принудно", "SUBTITLE_FORCED": "Принудно",
"SUBTITLES_EXAMPLE": "Пример титлов",
"SURROUND": "Просторни звук", "SURROUND": "Просторни звук",
"TOGGLE_STEREOSCOPIC": "Укључи/искључи стерео", "TOGGLE_STEREOSCOPIC": "Укључи/искључи стерео",
"UNDETERMINED_LANGUAGE": "Неодређено", "UNDETERMINED_LANGUAGE": "Неодређено",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Fortsätt direkt till direktsändning", "SKIP_TO_LIVE": "Fortsätt direkt till direktsändning",
"STATISTICS": "Statistik", "STATISTICS": "Statistik",
"SUBTITLE_FORCED": "Framtvingad", "SUBTITLE_FORCED": "Framtvingad",
"SUBTITLES_EXAMPLE": "Exempel på undertexter",
"SURROUND": "Surroundljud", "SURROUND": "Surroundljud",
"TOGGLE_STEREOSCOPIC": "Aktivera/inaktivera stereoskopiska bilder", "TOGGLE_STEREOSCOPIC": "Aktivera/inaktivera stereoskopiska bilder",
"UNDETERMINED_LANGUAGE": "Obestämt", "UNDETERMINED_LANGUAGE": "Obestämt",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "ข้ามไปที่การถ่ายทอดสด", "SKIP_TO_LIVE": "ข้ามไปที่การถ่ายทอดสด",
"STATISTICS": "สถิติ", "STATISTICS": "สถิติ",
"SUBTITLE_FORCED": "บังคับ", "SUBTITLE_FORCED": "บังคับ",
"SUBTITLES_EXAMPLE": "ตัวอย่างคำบรรยาย",
"SURROUND": "เซอร์ราวด์", "SURROUND": "เซอร์ราวด์",
"TOGGLE_STEREOSCOPIC": "เปิด/ปิดฟีเจอร์สามมิติ", "TOGGLE_STEREOSCOPIC": "เปิด/ปิดฟีเจอร์สามมิติ",
"UNDETERMINED_LANGUAGE": "ไม่กำหนด", "UNDETERMINED_LANGUAGE": "ไม่กำหนด",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Canlı yayına atla", "SKIP_TO_LIVE": "Canlı yayına atla",
"STATISTICS": "İstatistikler", "STATISTICS": "İstatistikler",
"SUBTITLE_FORCED": "Zorunlu", "SUBTITLE_FORCED": "Zorunlu",
"SUBTITLES_EXAMPLE": "Altyazı örneği",
"SURROUND": "Surround", "SURROUND": "Surround",
"TOGGLE_STEREOSCOPIC": "Stereoskopik modu aç/kapat", "TOGGLE_STEREOSCOPIC": "Stereoskopik modu aç/kapat",
"UNDETERMINED_LANGUAGE": "Belirsiz", "UNDETERMINED_LANGUAGE": "Belirsiz",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Перейти до прямої трансляції", "SKIP_TO_LIVE": "Перейти до прямої трансляції",
"STATISTICS": "Статистика", "STATISTICS": "Статистика",
"SUBTITLE_FORCED": "Обов’язкові субтитри", "SUBTITLE_FORCED": "Обов’язкові субтитри",
"SUBTITLES_EXAMPLE": "Приклад субтитрів",
"SURROUND": "Об’ємний звук", "SURROUND": "Об’ємний звук",
"TOGGLE_STEREOSCOPIC": "Увімкнути або вимкнути стереоскопічний режим", "TOGGLE_STEREOSCOPIC": "Увімкнути або вимкнути стереоскопічний режим",
"UNDETERMINED_LANGUAGE": "Не визначено", "UNDETERMINED_LANGUAGE": "Не визначено",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "Tua tới chương trình phát trực tiếp", "SKIP_TO_LIVE": "Tua tới chương trình phát trực tiếp",
"STATISTICS": "Số liệu thống kê", "STATISTICS": "Số liệu thống kê",
"SUBTITLE_FORCED": "Buộc hiển thị", "SUBTITLE_FORCED": "Buộc hiển thị",
"SUBTITLES_EXAMPLE": "Ví dụ phụ đề",
"SURROUND": "Vòm", "SURROUND": "Vòm",
"TOGGLE_STEREOSCOPIC": "Bật/tắt chế độ hình nổi", "TOGGLE_STEREOSCOPIC": "Bật/tắt chế độ hình nổi",
"UNDETERMINED_LANGUAGE": "Chưa xác định", "UNDETERMINED_LANGUAGE": "Chưa xác định",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "跳至当前直播", "SKIP_TO_LIVE": "跳至当前直播",
"STATISTICS": "統計資料", "STATISTICS": "統計資料",
"SUBTITLE_FORCED": "強制", "SUBTITLE_FORCED": "強制",
"SUBTITLES_EXAMPLE": "字幕範例",
"SURROUND": "環迴音效", "SURROUND": "環迴音效",
"TOGGLE_STEREOSCOPIC": "切換立體視覺", "TOGGLE_STEREOSCOPIC": "切換立體視覺",
"UNDETERMINED_LANGUAGE": "不明", "UNDETERMINED_LANGUAGE": "不明",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "跳至当前直播", "SKIP_TO_LIVE": "跳至当前直播",
"STATISTICS": "統計資料", "STATISTICS": "統計資料",
"SUBTITLE_FORCED": "強制顯示", "SUBTITLE_FORCED": "強制顯示",
"SUBTITLES_EXAMPLE": "字幕範例",
"SURROUND": "環場音效", "SURROUND": "環場音效",
"TOGGLE_STEREOSCOPIC": "切換立體影像", "TOGGLE_STEREOSCOPIC": "切換立體影像",
"UNDETERMINED_LANGUAGE": "不明", "UNDETERMINED_LANGUAGE": "不明",
+1
View File
@@ -40,6 +40,7 @@
"SKIP_TO_LIVE": "跳至当前直播", "SKIP_TO_LIVE": "跳至当前直播",
"STATISTICS": "统计信息", "STATISTICS": "统计信息",
"SUBTITLE_FORCED": "已强制显示", "SUBTITLE_FORCED": "已强制显示",
"SUBTITLES_EXAMPLE": "字幕示例",
"SURROUND": "环绕声", "SURROUND": "环绕声",
"TOGGLE_STEREOSCOPIC": "切换立体声", "TOGGLE_STEREOSCOPIC": "切换立体声",
"UNDETERMINED_LANGUAGE": "未确定", "UNDETERMINED_LANGUAGE": "未确定",
+55
View File
@@ -40,6 +40,9 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
this.addMenu_(); this.addMenu_();
/** @private {boolean} */
this.isMenuOpened_ = false;
this.inOverflowMenu_(); this.inOverflowMenu_();
this.eventManager.listen(this.button, 'click', () => { this.eventManager.listen(this.button, 'click', () => {
@@ -55,6 +58,23 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
/** @private {MutationObserver} */ /** @private {MutationObserver} */
this.mutationObserver_ = null; this.mutationObserver_ = null;
/** @private {MutationObserver} */
this.menuMutationObserver_ = null;
if (window.MutationObserver) {
this.menuMutationObserver_ = new MutationObserver(() => {
if (this.menu.classList.contains('shaka-hidden')) {
this.notifyMenuClose_();
} else {
this.notifyMenuOpen_();
}
});
this.menuMutationObserver_.observe(this.menu, {
attributes: true,
attributeFilter: ['class'],
});
}
const resize = () => this.adjustCustomStyle_(); const resize = () => this.adjustCustomStyle_();
// Use ResizeObserver if available, fallback to window resize event // Use ResizeObserver if available, fallback to window resize event
@@ -77,6 +97,10 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
this.mutationObserver_.disconnect(); this.mutationObserver_.disconnect();
this.mutationObserver_ = null; this.mutationObserver_ = null;
} }
if (this.menuMutationObserver_) {
this.menuMutationObserver_.disconnect();
this.menuMutationObserver_ = null;
}
super.release(); super.release();
} }
@@ -162,6 +186,7 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
this.backIcon_.use(shaka.ui.Enums.MaterialDesignSVGIcons['BACK']); this.backIcon_.use(shaka.ui.Enums.MaterialDesignSVGIcons['BACK']);
this.eventManager.listen(this.menu, 'click', () => { this.eventManager.listen(this.menu, 'click', () => {
this.notifyMenuClose_();
this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuclose')); this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuclose'));
shaka.ui.Utils.setDisplay(this.menu, false); shaka.ui.Utils.setDisplay(this.menu, false);
shaka.ui.Utils.setDisplay(this.parent, true); shaka.ui.Utils.setDisplay(this.parent, true);
@@ -177,6 +202,9 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
if (m.type === 'attributes' && m.attributeName === 'class') { if (m.type === 'attributes' && m.attributeName === 'class') {
const newHidden = this.parent.classList.contains('shaka-hidden'); const newHidden = this.parent.classList.contains('shaka-hidden');
if (newHidden && prevHidden != newHidden) { if (newHidden && prevHidden != newHidden) {
if (!this.menu.classList.contains('shaka-hidden')) {
this.notifyMenuClose_();
}
this.controls.dispatchEvent( this.controls.dispatchEvent(
new shaka.util.FakeEvent('submenuclose')); new shaka.util.FakeEvent('submenuclose'));
shaka.ui.Utils.setDisplay(this.menu, false); shaka.ui.Utils.setDisplay(this.menu, false);
@@ -209,10 +237,12 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuopen')); this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuopen'));
} }
shaka.ui.Utils.setDisplay(this.menu, true); shaka.ui.Utils.setDisplay(this.menu, true);
this.notifyMenuOpen_();
shaka.ui.Utils.focusOnTheChosenItem(this.menu); shaka.ui.Utils.focusOnTheChosenItem(this.menu);
this.adjustCustomStyle_(); this.adjustCustomStyle_();
this.button.setAttribute('aria-expanded', 'true'); this.button.setAttribute('aria-expanded', 'true');
} else { } else {
this.notifyMenuClose_();
shaka.ui.Utils.setDisplay(this.menu, false); shaka.ui.Utils.setDisplay(this.menu, false);
this.button.setAttribute('aria-expanded', 'false'); this.button.setAttribute('aria-expanded', 'false');
this.button.focus(); this.button.focus();
@@ -220,6 +250,31 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
} }
} }
/** @private */
notifyMenuOpen_() {
if (this.isMenuOpened_) {
return;
}
this.isMenuOpened_ = true;
this.onMenuOpen();
}
/** @private */
notifyMenuClose_() {
if (!this.isMenuOpened_) {
return;
}
this.isMenuOpened_ = false;
this.onMenuClose();
}
/** @protected */
onMenuOpen() {}
/** @protected */
onMenuClose() {}
/** /**
* @private * @private
+16
View File
@@ -135,12 +135,28 @@ shaka.ui.TextPosition = class extends shaka.ui.SettingsMenu {
this.updateTextPositionSelection_(); this.updateTextPositionSelection_();
}); });
const previewConfig = {'positionArea': position};
shaka.ui.Utils.addHoverAndFocusListeners(
this.eventManager, button,
() => this.controls.updateTextStylePreview(previewConfig),
() => this.controls.resetTextStylePreview());
this.menu.appendChild(button); this.menu.appendChild(button);
} }
this.updateTextPositionSelection_(); this.updateTextPositionSelection_();
shaka.ui.Utils.focusOnTheChosenItem(this.menu); shaka.ui.Utils.focusOnTheChosenItem(this.menu);
} }
/** @override */
onMenuOpen() {
this.controls.showTextStylePreview();
}
/** @override */
onMenuClose() {
this.controls.hideTextStylePreview();
}
/** @private */ /** @private */
updateTextPositionSelection_() { updateTextPositionSelection_() {
// Remove the old checkmark icon and related tags and classes if it exists. // Remove the old checkmark icon and related tags and classes if it exists.
+16
View File
@@ -133,12 +133,28 @@ shaka.ui.TextSize = class extends shaka.ui.SettingsMenu {
this.updateTextSizeSelection_(); this.updateTextSizeSelection_();
}); });
const previewConfig = {'fontScaleFactor': fontScaleFactor};
shaka.ui.Utils.addHoverAndFocusListeners(
this.eventManager, button,
() => this.controls.updateTextStylePreview(previewConfig),
() => this.controls.resetTextStylePreview());
this.menu.appendChild(button); this.menu.appendChild(button);
} }
this.updateTextSizeSelection_(); this.updateTextSizeSelection_();
shaka.ui.Utils.focusOnTheChosenItem(this.menu); shaka.ui.Utils.focusOnTheChosenItem(this.menu);
} }
/** @override */
onMenuOpen() {
this.controls.showTextStylePreview();
}
/** @override */
onMenuClose() {
this.controls.hideTextStylePreview();
}
/** @private */ /** @private */
updateTextSizeSelection_() { updateTextSizeSelection_() {
// Remove the old checkmark icon and related tags and classes if it exists. // Remove the old checkmark icon and related tags and classes if it exists.
+198
View File
@@ -0,0 +1,198 @@
/*! @license
* Shaka Player
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.ui.TextStylePreview');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.util.EventManager');
goog.requireType('shaka.config.PositionArea');
goog.requireType('shaka.Player');
/**
* Manages the temporary subtitle style preview shown while subtitle style
* settings are hovered or focused.
*
* @final
*/
shaka.ui.TextStylePreview = class {
/**
* @param {!shaka.Player} player
* @param {!shaka.ui.Localization} localization
*/
constructor(player, localization) {
/** @private {?shaka.Player} */
this.player_ = player;
/** @private {?shaka.ui.Localization} */
this.localization_ = localization;
/** @private {?shaka.extern.TextDisplayerConfiguration} */
this.baseConfig_ = null;
/** @private {!shaka.ui.TextStylePreview.Configuration} */
this.previewConfig_ = {};
/** @private {boolean} */
this.shown_ = false;
/** @private {?shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();
this.eventManager_.listen(player, 'configurationchanged', () => {
this.updateBaseConfig_();
});
this.eventManager_.listenMulti(
localization,
[
shaka.ui.Localization.LOCALE_UPDATED,
shaka.ui.Localization.LOCALE_CHANGED,
], () => {
this.apply_();
});
}
/** Releases all resources owned by this preview. */
release() {
this.hide();
this.eventManager_?.release();
this.eventManager_ = null;
this.player_ = null;
this.localization_ = null;
}
/** Shows a temporary subtitle with the current text displayer style. */
show() {
if (!this.player_) {
return;
}
this.shown_ = true;
this.previewConfig_ = {};
this.updateBaseConfig_();
}
/**
* Updates the temporary subtitle style without changing player config.
*
* @param {!shaka.ui.TextStylePreview.Configuration=} config
*/
update(config = {}) {
if (!this.player_) {
return;
}
if (!this.shown_) {
this.show();
}
this.previewConfig_ = Object.assign({}, config);
this.apply_();
}
/** Reverts the temporary subtitle to the style captured when shown. */
reset() {
if (!this.shown_) {
return;
}
this.previewConfig_ = {};
this.apply_();
}
/** Removes the temporary subtitle style preview. */
hide() {
if (!this.shown_) {
return;
}
this.shown_ = false;
this.baseConfig_ = null;
this.previewConfig_ = {};
const displayer = this.getTextDisplayer_();
if (displayer &&
typeof displayer['clearTextStylePreview'] == 'function') {
displayer['clearTextStylePreview']();
}
}
/** @private */
updateBaseConfig_() {
if (!this.shown_ || !this.player_) {
return;
}
this.baseConfig_ = this.getCurrentTextDisplayerConfig_();
this.apply_();
}
/**
* @return {!shaka.extern.TextDisplayerConfiguration}
* @private
*/
getCurrentTextDisplayerConfig_() {
const player = /** @type {!shaka.Player} */(this.player_);
return /** @type {!shaka.extern.TextDisplayerConfiguration} */(
Object.assign({}, player.getConfiguration().textDisplayer));
}
/** @private */
apply_() {
if (!this.shown_ || !this.player_ || !this.baseConfig_) {
return;
}
const previewConfig =
/** @type {!shaka.extern.TextDisplayerConfiguration} */(
Object.assign({}, this.baseConfig_, this.previewConfig_));
const displayer = this.getTextDisplayer_();
if (displayer && typeof displayer['setTextStylePreview'] == 'function') {
displayer['setTextStylePreview'](
previewConfig, this.getLocalizedExampleText_());
}
}
/**
* @return {?}
* @private
*/
getTextDisplayer_() {
const player = /** @type {?} */(this.player_);
if (!player || typeof player.getTextDisplayer != 'function') {
return null;
}
return player.getTextDisplayer();
}
/**
* @return {string}
* @private
*/
getLocalizedExampleText_() {
if (!this.localization_) {
return '';
}
return this.localization_.resolve(
shaka.ui.Locales.Ids.SUBTITLES_EXAMPLE);
}
};
/**
* @typedef {{
* fontScaleFactor: (number|undefined),
* positionArea: (shaka.config.PositionArea|undefined),
* }}
*
* @description
* Text displayer fields that the style preview can temporarily override.
*/
shaka.ui.TextStylePreview.Configuration;
+15
View File
@@ -11,6 +11,7 @@ goog.require('goog.asserts');
goog.require('shaka.ui.Enums'); goog.require('shaka.ui.Enums');
goog.require('shaka.ui.Icon'); goog.require('shaka.ui.Icon');
goog.require('shaka.util.Mp4Parser'); goog.require('shaka.util.Mp4Parser');
goog.requireType('shaka.util.EventManager');
shaka.ui.Utils = class { shaka.ui.Utils = class {
@@ -96,6 +97,20 @@ shaka.ui.Utils = class {
} }
} }
/**
* @param {!shaka.util.EventManager} eventManager
* @param {!Element} element
* @param {function()} onHoverOrFocus
* @param {function()} onLeaveOrBlur
*/
static addHoverAndFocusListeners(
eventManager, element, onHoverOrFocus, onLeaveOrBlur) {
eventManager.listen(element, 'mouseenter', onHoverOrFocus);
eventManager.listen(element, 'mouseleave', onLeaveOrBlur);
eventManager.listen(element, 'focus', onHoverOrFocus);
eventManager.listen(element, 'blur', onLeaveOrBlur);
}
/** /**
* Builds a time string, e.g., 01:04:23, from |displayTime|. * Builds a time string, e.g., 01:04:23, from |displayTime|.