diff --git a/assets/skin-modern/images/leaf.svg b/assets/skin-modern/images/leaf.svg new file mode 100644 index 000000000..f143e91e9 --- /dev/null +++ b/assets/skin-modern/images/leaf.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/skin-modern/images/toggleOff.svg b/assets/skin-modern/images/toggleOff.svg new file mode 100644 index 000000000..98254901a --- /dev/null +++ b/assets/skin-modern/images/toggleOff.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/skin-modern/images/toggleOn.svg b/assets/skin-modern/images/toggleOn.svg new file mode 100644 index 000000000..99b015ab3 --- /dev/null +++ b/assets/skin-modern/images/toggleOn.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 2d2cd9a2d..e02d7e79e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@types/jest": "^29.5.0", "@types/jsdom": "^21.1.0", "autoprefixer": "^10.4.14", - "bitmovin-player": "^8.109.0", + "bitmovin-player": "^8.129.0", "browser-sync": "^2.29.0", "browserify": "^17.0.0", "cssnano": "^5.1.15", @@ -4502,9 +4502,9 @@ } }, "node_modules/bitmovin-player": { - "version": "8.109.0", - "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.109.0.tgz", - "integrity": "sha512-GhsY2XAtcsarI4TqR+Lc5qXPgvKTW2yL932sHifI1dnYzczGO0vbbYkdG+zP/IidNiov8zJSFl84g+ozUFEbXA==", + "version": "8.129.0", + "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.129.0.tgz", + "integrity": "sha512-4/bOVjPRa8MtJDeur1cWs2IjIOn5vg3NOwQMcEms6OwurjcPTPP7AWGahlPl6cPf1X7Y83ce5S+V4yfvoAfBQQ==", "dev": true }, "node_modules/bl": { @@ -23267,9 +23267,9 @@ "dev": true }, "bitmovin-player": { - "version": "8.109.0", - "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.109.0.tgz", - "integrity": "sha512-GhsY2XAtcsarI4TqR+Lc5qXPgvKTW2yL932sHifI1dnYzczGO0vbbYkdG+zP/IidNiov8zJSFl84g+ozUFEbXA==", + "version": "8.129.0", + "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.129.0.tgz", + "integrity": "sha512-4/bOVjPRa8MtJDeur1cWs2IjIOn5vg3NOwQMcEms6OwurjcPTPP7AWGahlPl6cPf1X7Y83ce5S+V4yfvoAfBQQ==", "dev": true }, "bl": { diff --git a/package.json b/package.json index c5cd04806..5a63b78ab 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@types/jest": "^29.5.0", "@types/jsdom": "^21.1.0", "autoprefixer": "^10.4.14", - "bitmovin-player": "^8.109.0", + "bitmovin-player": "^8.129.0", "browser-sync": "^2.29.0", "browserify": "^17.0.0", "cssnano": "^5.1.15", diff --git a/src/scss/skin-modern/_skin.scss b/src/scss/skin-modern/_skin.scss index bb0d5c21b..ae1890277 100644 --- a/src/scss/skin-modern/_skin.scss +++ b/src/scss/skin-modern/_skin.scss @@ -10,6 +10,7 @@ @import 'components/fullscreentogglebutton'; @import 'components/vrtogglebutton'; @import 'components/volumetogglebutton'; +@import 'components/ecomodetogglebutton'; @import 'components/seekbar'; @import 'components/watermark'; @import 'components/hugeplaybacktogglebutton'; diff --git a/src/scss/skin-modern/components/_ecomodetogglebutton.scss b/src/scss/skin-modern/components/_ecomodetogglebutton.scss new file mode 100644 index 000000000..bb70a2cf2 --- /dev/null +++ b/src/scss/skin-modern/components/_ecomodetogglebutton.scss @@ -0,0 +1,35 @@ +@import '../variables'; +@import '../mixins'; + +.#{$prefix}-ui-ecomodetogglebutton { + @extend %ui-button; + height: 1em; + min-width: 5em; + + &:hover { + @include svg-icon-shadow; + } + + &.#{$prefix}-on { + background-image: url('../../assets/skin-modern/images/toggleOn.svg'); + background-position: 20px center; + background-size: 45% auto; + margin-left: 2%; + } + + &.#{$prefix}-off { + background-image: url('../../assets/skin-modern/images/toggleOff.svg'); + background-position: 20px center; + background-size: 45% auto; + } +} + +#ecomodelabel::before { + background-image: url('../../assets/skin-modern/images/leaf.svg'); + background-repeat: no-repeat; + background-size: 1.7em auto; + content: ' '; + display: inline-block; + height: 1.5em; + width: 2em; +} diff --git a/src/scss/skin-modern/components/_label.scss b/src/scss/skin-modern/components/_label.scss index 70e26540d..2d1865357 100644 --- a/src/scss/skin-modern/components/_label.scss +++ b/src/scss/skin-modern/components/_label.scss @@ -12,3 +12,10 @@ .#{$prefix}-ui-label { @extend %ui-label; } + +.#{$prefix}-ui-label-savedEnergy { + @extend %ui-label; + font-size: 0.8em; + color: #1fabe2; + margin-left: 2.2em; +} diff --git a/src/ts/components/ecomodecontainer.ts b/src/ts/components/ecomodecontainer.ts new file mode 100644 index 000000000..3b7829de9 --- /dev/null +++ b/src/ts/components/ecomodecontainer.ts @@ -0,0 +1,126 @@ +import { PlayerAPI, SegmentPlaybackEvent, VideoQuality } from 'bitmovin-player'; +import { i18n } from '../localization/i18n'; +import { Container, ContainerConfig } from './container'; +import { EcoModeToggleButton } from './ecomodetogglebutton'; +import { Label, LabelConfig } from './label'; +import { SettingsPanelItem } from './settingspanelitem'; + +export class EcoModeContainer extends Container { + private ecoModeSavedEmissionsItem: SettingsPanelItem; + private ecoModeToggleButtonItem: SettingsPanelItem; + private emissionsSavedLabel: Label; + private savedEmissons = 0; + private currentEnergyEmission: number; + + constructor(config: ContainerConfig = {}) { + super(config); + + const ecoModeToggleButton = new EcoModeToggleButton(); + const labelEcoMode = new Label({ + text: i18n.getLocalizer('ecoMode.title'), + for: ecoModeToggleButton.getConfig().id, + id: 'ecomodelabel', + }); + this.emissionsSavedLabel = new Label({ + text: `${this.savedEmissons.toFixed(4)} gCO2`, + cssClass: 'ui-label-savedEnergy', + }); + + this.ecoModeToggleButtonItem = new SettingsPanelItem(labelEcoMode, ecoModeToggleButton); + this.ecoModeSavedEmissionsItem = new SettingsPanelItem('Saved Emissions', this.emissionsSavedLabel, { + hidden: true, + }); + + this.addComponent(this.ecoModeToggleButtonItem); + this.addComponent(this.ecoModeSavedEmissionsItem); + + ecoModeToggleButton.onToggleOn.subscribe(() => { + this.ecoModeSavedEmissionsItem.show(); + this.onToggleCallback(); + }); + + ecoModeToggleButton.onToggleOff.subscribe(() => { + this.ecoModeSavedEmissionsItem.hide(); + this.onToggleCallback(); + }); + } + + private onToggleCallback: () => void; + + public setOnToggleCallback(callback: () => void) { + this.onToggleCallback = callback; + } + + configure(player: PlayerAPI): void { + player.on(player.exports.PlayerEvent.SegmentPlayback, (segment: SegmentPlaybackEvent) => { + if (!segment.mimeType.includes('video')) { + return; + } + + const { height, width, bitrate, frameRate } = segment.mediaInfo; + const { + height: maxHeight, + bitrate: maxBitrate, + width: maxWidth, + } = this.getMaxQualityAvailable(player.getAvailableVideoQualities()); + + const currentEnergyKwh = this.calculateEnergyConsumption(frameRate, height, width, bitrate, segment.duration); + + const maxEnergyKwh = this.calculateEnergyConsumption( + frameRate, + maxHeight, + maxWidth, + maxBitrate, + segment.duration, + ); + + if (this.ecoModeSavedEmissionsItem.isShown()) { + this.updateSavedEmissions(currentEnergyKwh, maxEnergyKwh, this.emissionsSavedLabel); + } + }); + } + + updateSavedEmissions( + currentEnergyConsuption: number, + maxEnergyConsuption: number, + emissionsSavedLabel: Label, + ) { + // 475 is the average carbon intensity of all countries in gCO2/kWh + const averageCarbonIntensity = 475; + + this.currentEnergyEmission = currentEnergyConsuption * averageCarbonIntensity; + const maxEnergyEmisson = maxEnergyConsuption * averageCarbonIntensity; + this.savedEmissons += maxEnergyEmisson - this.currentEnergyEmission; + emissionsSavedLabel.setText(this.savedEmissons.toFixed(4) + ' gCO2'); + } + + /** + * The calculations are based on the following paper: https://arxiv.org/pdf/2210.05444.pdf + */ + calculateEnergyConsumption(fps: number, height: number, width: number, bitrate: number, duration: number): number { + const fpsWeight = 0.035; + const pixeldWeight = 5.76e-9; + const birateWeight = 6.97e-6; + const constantOffset = 8.52; + const bitrateInternetWeight = 3.24e-5; + const internetConnectionOffset = 1.15; + const videoCodec = 4.16; + + const energyConsumptionW = + fpsWeight * fps + + pixeldWeight * height * width + + (birateWeight + bitrateInternetWeight) * (bitrate / 1000) + + videoCodec + + constantOffset + + internetConnectionOffset; + + // Convert energy consumption from Watts (W) to Kilowatt-hours (kWh) for the given time duration of the segment + const energyConsumptionKwh = (energyConsumptionW * duration) / 3.6e6; + + return energyConsumptionKwh; + } + getMaxQualityAvailable(availableVideoQualities: VideoQuality[]) { + const sortedQualities = availableVideoQualities.sort((a, b) => a.bitrate - b.bitrate); + return sortedQualities[sortedQualities.length - 1]; + } +} diff --git a/src/ts/components/ecomodetogglebutton.ts b/src/ts/components/ecomodetogglebutton.ts new file mode 100644 index 000000000..7f1d90fc1 --- /dev/null +++ b/src/ts/components/ecomodetogglebutton.ts @@ -0,0 +1,86 @@ +import { ToggleButton, ToggleButtonConfig } from './togglebutton'; +import { UIInstanceManager } from '../uimanager'; +import { DynamicAdaptationConfig, PlayerAPI, VideoQualityChangedEvent } from 'bitmovin-player'; +import { i18n } from '../localization/i18n'; + +export class EcoModeToggleButton extends ToggleButton { + private adaptationConfig: DynamicAdaptationConfig; + + constructor(config: ToggleButtonConfig = {}) { + super(config); + + const defaultConfig: ToggleButtonConfig = { + text: i18n.getLocalizer('ecoMode'), + cssClass: 'ui-ecomodetogglebutton', + onClass: 'on', + offClass: 'off', + ariaLabel: i18n.getLocalizer('ecoMode'), + }; + + this.config = this.mergeConfig(config, defaultConfig, this.config); + } + + configure(player: PlayerAPI, uimanager: UIInstanceManager): void { + super.configure(player, uimanager); + + if (this.areAdaptationApisAvailable(player)) { + this.onClick.subscribe(() => { + this.toggle(); + }); + + this.onToggleOn.subscribe(() => { + this.enableEcoMode(player); + player.setVideoQuality('auto'); + }); + + this.onToggleOff.subscribe(() => { + this.disableEcoMode(player); + }); + + player.on(player.exports.PlayerEvent.VideoQualityChanged, (quality: VideoQualityChangedEvent) => { + if (quality.targetQuality.id !== 'auto') { + this.off(); + this.disableEcoMode(player); + } + }); + } else { + super.disable(); + } + + } + + private areAdaptationApisAvailable(player: PlayerAPI): boolean { + const isGetConfigAvailable = Boolean(player.adaptation.getConfig && typeof player.adaptation.getConfig === 'function'); + const isSetConfigAvailable = Boolean(player.adaptation.setConfig && typeof player.adaptation.setConfig === 'function'); + + return Boolean(player.adaptation && isGetConfigAvailable && isSetConfigAvailable); + } + + enableEcoMode(player: PlayerAPI): void { + this.adaptationConfig = player.adaptation.getConfig(); + const codec = player.getAvailableVideoQualities()[0].codec; + + if (codec.includes('avc')) { + player.adaptation.setConfig({ + resolution: { maxSelectableVideoHeight: 720 }, + limitToPlayerSize: true, + }); + } + if (codec.includes('hvc') || codec.includes('hev')) { + player.adaptation.setConfig({ + resolution: { maxSelectableVideoHeight: 1080 }, + limitToPlayerSize: true, + }); + } + if (codec.includes('av1') || codec.includes('av01')) { + player.adaptation.setConfig({ + resolution: { maxSelectableVideoHeight: 1440 }, + limitToPlayerSize: true, + }); + } + } + + disableEcoMode(player: PlayerAPI): void { + player.adaptation.setConfig(this.adaptationConfig); + } +} diff --git a/src/ts/localization/i18n.ts b/src/ts/localization/i18n.ts index aef22f73c..2980dff92 100644 --- a/src/ts/localization/i18n.ts +++ b/src/ts/localization/i18n.ts @@ -87,6 +87,8 @@ interface Vocabulary { 'seekBar.value': string; 'seekBar.timeshift': string; 'seekBar.durationText': string; + 'ecoMode': string; + 'ecoMode.title': string; } export type CustomVocabulary = V & Partial; diff --git a/src/ts/localization/languages/de.json b/src/ts/localization/languages/de.json index 48348d44a..f8fe68b80 100644 --- a/src/ts/localization/languages/de.json +++ b/src/ts/localization/languages/de.json @@ -53,5 +53,7 @@ "seekBar.timeshift": "Timeshift", "seekBar.durationText": "aus", "quickseek.forward": "Vor", - "quickseek.rewind": "Zurück" + "quickseek.rewind": "Zurück", + "ecoMode": "ecoMode", + "ecoMode.title":"Eco Mode" } diff --git a/src/ts/localization/languages/en.json b/src/ts/localization/languages/en.json index 5c570b225..ffc1167dc 100644 --- a/src/ts/localization/languages/en.json +++ b/src/ts/localization/languages/en.json @@ -52,6 +52,8 @@ "vr" : "VR", "off": "off", "auto": "auto", + "ecoMode": "ecoMode", + "ecoMode.title":"Eco Mode", "back" : "Back", "reset": "Reset", "replay": "Replay", diff --git a/src/ts/localization/languages/es.json b/src/ts/localization/languages/es.json index 9fa97a53f..22224d616 100644 --- a/src/ts/localization/languages/es.json +++ b/src/ts/localization/languages/es.json @@ -52,6 +52,8 @@ "vr" : "VR", "off": "off", "auto": "auto", + "ecoMode": "ecoMode", + "ecoMode.title": "Eco Mode", "back" : "Atrás", "reset": "Reiniciar", "replay": "Rebobinar", diff --git a/src/ts/uiconfig.ts b/src/ts/uiconfig.ts index b0ae3d713..7fb5157cc 100644 --- a/src/ts/uiconfig.ts +++ b/src/ts/uiconfig.ts @@ -121,4 +121,8 @@ export interface UIConfig { * If set to true, prevents the UI from using `localStorage`. */ disableStorageApi?: boolean; + /** + * Specifies if the `EcoModeToggleButton` should be displayed within the `SettingsPanel` + */ + ecoMode?: boolean; } diff --git a/src/ts/uifactory.ts b/src/ts/uifactory.ts index 78cb36c67..3e1266a09 100644 --- a/src/ts/uifactory.ts +++ b/src/ts/uifactory.ts @@ -11,7 +11,7 @@ import { SettingsPanelPageOpenButton } from './components/settingspanelpageopenb import { SubtitleSettingsLabel } from './components/subtitlesettings/subtitlesettingslabel'; import { SubtitleSelectBox } from './components/subtitleselectbox'; import { ControlBar } from './components/controlbar'; -import { Container } from './components/container'; +import { Container, ContainerConfig } from './components/container'; import { PlaybackTimeLabel, PlaybackTimeLabelMode } from './components/playbacktimelabel'; import { SeekBar } from './components/seekbar'; import { SeekBarLabel } from './components/seekbarlabel'; @@ -50,9 +50,9 @@ import { AudioTrackListBox } from './components/audiotracklistbox'; import { SpatialNavigation } from './spatialnavigation/spatialnavigation'; import { RootNavigationGroup } from './spatialnavigation/rootnavigationgroup'; import { ListNavigationGroup, ListOrientation } from './spatialnavigation/ListNavigationGroup'; +import { EcoModeContainer } from './components/ecomodecontainer'; export namespace UIFactory { - export function buildDefaultUI(player: PlayerAPI, config: UIConfig = {}): UIManager { return UIFactory.buildModernUI(player, config); } @@ -69,22 +69,35 @@ export namespace UIFactory { return UIFactory.buildModernTvUI(player, config); } - export function modernUI() { + export function modernUI(config: UIConfig) { let subtitleOverlay = new SubtitleOverlay(); - let mainSettingsPanelPage = new SettingsPanelPage({ - components: [ - new SettingsPanelItem(i18n.getLocalizer('settings.video.quality'), new VideoQualitySelectBox()), - new SettingsPanelItem(i18n.getLocalizer('speed'), new PlaybackSpeedSelectBox()), - new SettingsPanelItem(i18n.getLocalizer('settings.audio.track'), new AudioTrackSelectBox()), - new SettingsPanelItem(i18n.getLocalizer('settings.audio.quality'), new AudioQualitySelectBox()), - ], + let mainSettingsPanelPage: SettingsPanelPage; + + const components: Container[] = [ + new SettingsPanelItem(i18n.getLocalizer('settings.video.quality'), new VideoQualitySelectBox()), + new SettingsPanelItem(i18n.getLocalizer('speed'), new PlaybackSpeedSelectBox()), + new SettingsPanelItem(i18n.getLocalizer('settings.audio.track'), new AudioTrackSelectBox()), + new SettingsPanelItem(i18n.getLocalizer('settings.audio.quality'), new AudioQualitySelectBox()), + ]; + + if (config.ecoMode) { + const ecoModeContainer = new EcoModeContainer(); + + ecoModeContainer.setOnToggleCallback(() => { + // forces the browser to re-calculate the height of the settings panel when adding/removing elements + settingsPanel.getDomElement().css({ width: '', height: '' }); + }); + + components.unshift(ecoModeContainer); + } + + mainSettingsPanelPage = new SettingsPanelPage({ + components, }); let settingsPanel = new SettingsPanel({ - components: [ - mainSettingsPanelPage, - ], + components: [mainSettingsPanelPage], hidden: true, }); @@ -112,7 +125,8 @@ export namespace UIFactory { { role: 'menubar', }, - )); + ), + ); settingsPanel.addComponent(subtitleSettingsPanelPage); @@ -121,9 +135,15 @@ export namespace UIFactory { settingsPanel, new Container({ components: [ - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.CurrentTime, hideInLivePlayback: true }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.CurrentTime, + hideInLivePlayback: true, + }), new SeekBar({ label: new SeekBarLabel() }), - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.TotalTime, cssClasses: ['text-right'] }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.TotalTime, + cssClasses: ['text-right'], + }), ], cssClasses: ['controlbar-top'], }), @@ -173,10 +193,7 @@ export namespace UIFactory { new AdClickOverlay(), new PlaybackToggleOverlay(), new Container({ - components: [ - new AdMessageLabel({ text: i18n.getLocalizer('ads.remainingTime')}), - new AdSkipButton(), - ], + components: [new AdMessageLabel({ text: i18n.getLocalizer('ads.remainingTime') }), new AdSkipButton()], cssClass: 'ui-ads-status', }), new ControlBar({ @@ -217,9 +234,7 @@ export namespace UIFactory { }); let settingsPanel = new SettingsPanel({ - components: [ - mainSettingsPanelPage, - ], + components: [mainSettingsPanelPage], hidden: true, pageTransitionAnimation: false, hideDelay: -1, @@ -249,7 +264,8 @@ export namespace UIFactory { { role: 'menubar', }, - )); + ), + ); settingsPanel.addComponent(subtitleSettingsPanelPage); @@ -260,9 +276,15 @@ export namespace UIFactory { components: [ new Container({ components: [ - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.CurrentTime, hideInLivePlayback: true }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.CurrentTime, + hideInLivePlayback: true, + }), new SeekBar({ label: new SeekBarLabel() }), - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.TotalTime, cssClasses: ['text-right'] }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.TotalTime, + cssClasses: ['text-right'], + }), ], cssClasses: ['controlbar-top'], }), @@ -317,10 +339,7 @@ export namespace UIFactory { ], }), new Container({ - components: [ - new AdMessageLabel({ text: 'Ad: {remainingTime} secs' }), - new AdSkipButton(), - ], + components: [new AdMessageLabel({ text: 'Ad: {remainingTime} secs' }), new AdSkipButton()], cssClass: 'ui-ads-status', }), ], @@ -339,9 +358,15 @@ export namespace UIFactory { components: [ new Container({ components: [ - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.CurrentTime, hideInLivePlayback: true }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.CurrentTime, + hideInLivePlayback: true, + }), new SeekBar({ smoothPlaybackPositionUpdateIntervalMs: -1 }), - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.TotalTime, cssClasses: ['text-right'] }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.TotalTime, + cssClasses: ['text-right'], + }), ], cssClasses: ['controlbar-top'], }), @@ -372,43 +397,64 @@ export namespace UIFactory { // show smallScreen UI only on mobile/handheld devices let smallScreenSwitchWidth = 600; - return new UIManager(player, [{ - ui: modernSmallScreenAdsUI(), - condition: (context: UIConditionContext) => { - return context.isMobile && context.documentWidth < smallScreenSwitchWidth && context.isAd - && context.adRequiresUi; - }, - }, { - ui: modernAdsUI(), - condition: (context: UIConditionContext) => { - return context.isAd && context.adRequiresUi; - }, - }, { - ui: modernSmallScreenUI(), - condition: (context: UIConditionContext) => { - return !context.isAd && !context.adRequiresUi && context.isMobile - && context.documentWidth < smallScreenSwitchWidth; - }, - }, { - ui: modernUI(), - condition: (context: UIConditionContext) => { - return !context.isAd && !context.adRequiresUi; - }, - }], config); + return new UIManager( + player, + [ + { + ui: modernSmallScreenAdsUI(), + condition: (context: UIConditionContext) => { + return ( + context.isMobile && context.documentWidth < smallScreenSwitchWidth && context.isAd && context.adRequiresUi + ); + }, + }, + { + ui: modernAdsUI(), + condition: (context: UIConditionContext) => { + return context.isAd && context.adRequiresUi; + }, + }, + { + ui: modernSmallScreenUI(), + condition: (context: UIConditionContext) => { + return ( + !context.isAd && + !context.adRequiresUi && + context.isMobile && + context.documentWidth < smallScreenSwitchWidth + ); + }, + }, + { + ui: modernUI(config), + condition: (context: UIConditionContext) => { + return !context.isAd && !context.adRequiresUi; + }, + }, + ], + config, + ); } export function buildModernSmallScreenUI(player: PlayerAPI, config: UIConfig = {}): UIManager { - return new UIManager(player, [{ - ui: modernSmallScreenAdsUI(), - condition: (context: UIConditionContext) => { - return context.isAd && context.adRequiresUi; - }, - }, { - ui: modernSmallScreenUI(), - condition: (context: UIConditionContext) => { - return !context.isAd && !context.adRequiresUi; - }, - }], config); + return new UIManager( + player, + [ + { + ui: modernSmallScreenAdsUI(), + condition: (context: UIConditionContext) => { + return context.isAd && context.adRequiresUi; + }, + }, + { + ui: modernSmallScreenUI(), + condition: (context: UIConditionContext) => { + return !context.isAd && !context.adRequiresUi; + }, + }, + ], + config, + ); } export function buildModernCastReceiverUI(player: PlayerAPI, config: UIConfig = {}): UIManager { @@ -416,9 +462,15 @@ export namespace UIFactory { } export function buildModernTvUI(player: PlayerAPI, config: UIConfig = {}): UIManager { - return new UIManager(player, [{ + return new UIManager( + player, + [ + { ...modernTvUI(), - }], config); + }, + ], + config, + ); } export function modernTvUI() { @@ -426,9 +478,7 @@ export namespace UIFactory { const subtitleListPanel = new SettingsPanel({ components: [ new SettingsPanelPage({ - components: [ - new SettingsPanelItem(null, subtitleListBox), - ], + components: [new SettingsPanelItem(null, subtitleListBox)], }), ], hidden: true, @@ -438,9 +488,7 @@ export namespace UIFactory { const audioTrackListPanel = new SettingsPanel({ components: [ new SettingsPanelPage({ - components: [ - new SettingsPanelItem(null, audioTrackListBox), - ], + components: [new SettingsPanelItem(null, audioTrackListBox)], }), ], hidden: true, @@ -470,9 +518,15 @@ export namespace UIFactory { components: [ new Container({ components: [ - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.CurrentTime, hideInLivePlayback: true }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.CurrentTime, + hideInLivePlayback: true, + }), seekBar, - new PlaybackTimeLabel({ timeLabelMode: PlaybackTimeLabelMode.RemainingTime, cssClasses: ['text-right'] }), + new PlaybackTimeLabel({ + timeLabelMode: PlaybackTimeLabelMode.RemainingTime, + cssClasses: ['text-right'], + }), ], cssClasses: ['controlbar-top'], }),