Skip to content

Commit

Permalink
Merge pull request #571 from bitmovin/feature/PW-10573/implementing-e…
Browse files Browse the repository at this point in the history
…co-mode-toggle

Implementing Eco Mode Toggle
  • Loading branch information
Andr3wid authored Jun 6, 2024
2 parents 9d89e86 + 78a7337 commit 42a7a96
Show file tree
Hide file tree
Showing 16 changed files with 443 additions and 85 deletions.
3 changes: 3 additions & 0 deletions assets/skin-modern/images/leaf.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions assets/skin-modern/images/toggleOff.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions assets/skin-modern/images/toggleOn.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/scss/skin-modern/_skin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
35 changes: 35 additions & 0 deletions src/scss/skin-modern/components/_ecomodetogglebutton.scss
Original file line number Diff line number Diff line change
@@ -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 {

Check warning on line 27 in src/scss/skin-modern/components/_ecomodetogglebutton.scss

View workflow job for this annotation

GitHub Actions / test_and_build

ID selectors not allowed

Check warning on line 27 in src/scss/skin-modern/components/_ecomodetogglebutton.scss

View workflow job for this annotation

GitHub Actions / test_and_build

Pseudo-element should be nested within its parent Id
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;
}
7 changes: 7 additions & 0 deletions src/scss/skin-modern/components/_label.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@
.#{$prefix}-ui-label {
@extend %ui-label;
}

.#{$prefix}-ui-label-savedEnergy {
@extend %ui-label;
font-size: 0.8em;

Check warning on line 18 in src/scss/skin-modern/components/_label.scss

View workflow job for this annotation

GitHub Actions / test_and_build

Expected `color`, found `font-size`

Check warning on line 18 in src/scss/skin-modern/components/_label.scss

View workflow job for this annotation

GitHub Actions / test_and_build

Don't include leading zeros on numbers
color: #1fabe2;

Check warning on line 19 in src/scss/skin-modern/components/_label.scss

View workflow job for this annotation

GitHub Actions / test_and_build

Expected `font-size`, found `color`

Check warning on line 19 in src/scss/skin-modern/components/_label.scss

View workflow job for this annotation

GitHub Actions / test_and_build

Color literals such as '#1fabe2' should only be used in variable declarations
margin-left: 2.2em;
}
126 changes: 126 additions & 0 deletions src/ts/components/ecomodecontainer.ts
Original file line number Diff line number Diff line change
@@ -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<ContainerConfig> {
private ecoModeSavedEmissionsItem: SettingsPanelItem;
private ecoModeToggleButtonItem: SettingsPanelItem;
private emissionsSavedLabel: Label<LabelConfig>;
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<LabelConfig>,
) {
// 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];
}
}
86 changes: 86 additions & 0 deletions src/ts/components/ecomodetogglebutton.ts
Original file line number Diff line number Diff line change
@@ -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<ToggleButtonConfig> {
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);
}
}
2 changes: 2 additions & 0 deletions src/ts/localization/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ interface Vocabulary {
'seekBar.value': string;
'seekBar.timeshift': string;
'seekBar.durationText': string;
'ecoMode': string;
'ecoMode.title': string;
}

export type CustomVocabulary<V> = V & Partial<Vocabulary>;
Expand Down
4 changes: 3 additions & 1 deletion src/ts/localization/languages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
2 changes: 2 additions & 0 deletions src/ts/localization/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
"vr" : "VR",
"off": "off",
"auto": "auto",
"ecoMode": "ecoMode",
"ecoMode.title":"Eco Mode",
"back" : "Back",
"reset": "Reset",
"replay": "Replay",
Expand Down
2 changes: 2 additions & 0 deletions src/ts/localization/languages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
"vr" : "VR",
"off": "off",
"auto": "auto",
"ecoMode": "ecoMode",
"ecoMode.title": "Eco Mode",
"back" : "Atrás",
"reset": "Reiniciar",
"replay": "Rebobinar",
Expand Down
4 changes: 4 additions & 0 deletions src/ts/uiconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading

0 comments on commit 42a7a96

Please sign in to comment.