Update Stream stats (#24)

* Change label's font

* Add Stats settings popup

* Toggle Stats settings dialog

* Update structure of Preferences.SETTINGS

* Add dialog title

* Add "Show stats when starting the game" setting

* Update texts

* Validate setting value before saving

* Add Preferences.toElement()

* Add "Text size" setting

* Add "Decode time" stat

* Add "Conditional formatting text color" feature

* Add "for" attributes to labels

* Update grade's colors

* Fix text wrapping

* Update stats using setInterval() instead of setTimeout()
This commit is contained in:
redphx 2023-07-27 07:18:06 +07:00 committed by GitHub
parent e98ad00bb9
commit e73e018380
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -74,28 +74,32 @@ class StreamBadges {
class StreamStats { class StreamStats {
static #timeout; static #interval;
static #updateInterval = 1000; static #updateInterval = 1000;
static #$container; static #$container;
static #$fps; static #$fps;
static #$rtt; static #$rtt;
static #$dt;
static #$pl; static #$pl;
static #$fl; static #$fl;
static #$br; static #$br;
static #lastInbound; static #$settings;
static #lastStat;
static start() { static start() {
StreamStats.#$container.style.display = 'block'; StreamStats.#$container.style.display = 'block';
StreamStats.update(); StreamStats.#interval = setInterval(StreamStats.update, StreamStats.#updateInterval);
} }
static stop() { static stop() {
clearInterval(StreamStats.#interval);
StreamStats.#$container.style.display = 'none'; StreamStats.#$container.style.display = 'none';
clearTimeout(StreamStats.#timeout); StreamStats.#interval = null;
StreamStats.#timeout = null; StreamStats.#lastStat = null;
StreamStats.#lastInbound = null;
} }
static toggle() { static toggle() {
@ -105,22 +109,20 @@ class StreamStats {
static #isHidden = () => StreamStats.#$container.style.display === 'none'; static #isHidden = () => StreamStats.#$container.style.display === 'none';
static update() { static update() {
if (StreamStats.#isHidden()) { if (StreamStats.#isHidden() || !STREAM_WEBRTC) {
return; StreamStats.stop();
}
if (!STREAM_WEBRTC) {
StreamStats.#timeout = setTimeout(StreamStats.update, StreamStats.#updateInterval);
return; return;
} }
const PREF_STATS_CONDITIONAL_FORMATTING = PREFS.get(Preferences.STATS_CONDITIONAL_FORMATTING);
STREAM_WEBRTC.getStats().then(stats => { STREAM_WEBRTC.getStats().then(stats => {
stats.forEach(stat => { stats.forEach(stat => {
let grade = '';
if (stat.type === 'inbound-rtp' && stat.kind === 'video') { if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
// FPS // FPS
StreamStats.#$fps.textContent = stat.framesPerSecond || 0; StreamStats.#$fps.textContent = stat.framesPerSecond || 0;
// Packets Loss // Packets Lost
const packetsLost = stat.packetsLost; const packetsLost = stat.packetsLost;
const packetsReceived = stat.packetsReceived || 1; const packetsReceived = stat.packetsReceived || 1;
StreamStats.#$pl.textContent = `${packetsLost} (${(packetsLost * 100 / packetsReceived).toFixed(2)}%)`; StreamStats.#$pl.textContent = `${packetsLost} (${(packetsLost * 100 / packetsReceived).toFixed(2)}%)`;
@ -130,25 +132,61 @@ class StreamStats {
const framesReceived = stat.framesReceived || 1; const framesReceived = stat.framesReceived || 1;
StreamStats.#$fl.textContent = `${framesDropped} (${(framesDropped * 100 / framesReceived).toFixed(2)}%)`; StreamStats.#$fl.textContent = `${framesDropped} (${(framesDropped * 100 / framesReceived).toFixed(2)}%)`;
// Bitrate if (StreamStats.#lastStat) {
if (StreamStats.#lastInbound) { const lastStat = StreamStats.#lastStat;
const timeDiff = stat.timestamp - StreamStats.#lastInbound.timestamp; // Bitrate
const bitrate = 8 * (stat.bytesReceived - StreamStats.#lastInbound.bytesReceived) / timeDiff / 1000; const timeDiff = stat.timestamp - lastStat.timestamp;
const bitrate = 8 * (stat.bytesReceived - lastStat.bytesReceived) / timeDiff / 1000;
StreamStats.#$br.textContent = `${bitrate.toFixed(2)} Mbps`; StreamStats.#$br.textContent = `${bitrate.toFixed(2)} Mbps`;
// Decode time
const totalDecodeTimeDiff = stat.totalDecodeTime - lastStat.totalDecodeTime;
const framesDecodedDiff = stat.framesDecoded - lastStat.framesDecoded;
const currentDecodeTime = totalDecodeTimeDiff / framesDecodedDiff * 1000;
StreamStats.#$dt.textContent = `${currentDecodeTime.toFixed(2)}ms`;
if (PREF_STATS_CONDITIONAL_FORMATTING) {
grade = (currentDecodeTime > 12) ? 'bad' : (currentDecodeTime > 9) ? 'ok' : (currentDecodeTime > 6) ? 'good' : '';
}
StreamStats.#$dt.setAttribute('data-grade', grade);
} }
StreamStats.#lastInbound = stat; StreamStats.#lastStat = stat;
} else if (stat.type === 'candidate-pair' && stat.state === 'succeeded') { } else if (stat.type === 'candidate-pair' && stat.state === 'succeeded') {
// Round Trip Time // Round Trip Time
const roundTripTime = typeof stat.currentRoundTripTime !== 'undefined' ? stat.currentRoundTripTime * 1000 : '???'; const roundTripTime = typeof stat.currentRoundTripTime !== 'undefined' ? stat.currentRoundTripTime * 1000 : '???';
StreamStats.#$rtt.textContent = `${roundTripTime}ms`; StreamStats.#$rtt.textContent = `${roundTripTime}ms`;
if (PREF_STATS_CONDITIONAL_FORMATTING) {
grade = (roundTripTime > 100) ? 'bad' : (roundTripTime > 75) ? 'ok' : (roundTripTime > 40) ? 'good' : '';
}
StreamStats.#$rtt.setAttribute('data-grade', grade);
} }
}); });
StreamStats.#timeout = setTimeout(StreamStats.update, StreamStats.#updateInterval);
}); });
} }
static #refreshStyles() {
const PREF_POSITION = PREFS.get(Preferences.STATS_POSITION);
const PREF_TRANSPARENT = PREFS.get(Preferences.STATS_TRANSPARENT);
const PREF_OPACITY = PREFS.get(Preferences.STATS_OPACITY);
const PREF_TEXT_SIZE = PREFS.get(Preferences.STATS_TEXT_SIZE);
StreamStats.#$container.setAttribute('data-position', PREF_POSITION);
StreamStats.#$container.setAttribute('data-transparent', PREF_TRANSPARENT);
StreamStats.#$container.style.opacity = PREF_OPACITY + '%';
StreamStats.#$container.style.fontSize = PREF_TEXT_SIZE;
}
static hideSettingsUi() {
StreamStats.#$settings.style.display = 'none';
}
static #toggleSettingsUi() {
const display = StreamStats.#$settings.style.display;
StreamStats.#$settings.style.display = display === 'block' ? 'none' : 'block';
}
static render() { static render() {
if (StreamStats.#$container) { if (StreamStats.#$container) {
return; return;
@ -160,6 +198,8 @@ class StreamStats {
StreamStats.#$fps = CE('span', {}, 0), StreamStats.#$fps = CE('span', {}, 0),
CE('label', {}, 'RTT'), CE('label', {}, 'RTT'),
StreamStats.#$rtt = CE('span', {}, '0ms'), StreamStats.#$rtt = CE('span', {}, '0ms'),
CE('label', {}, 'DT'),
StreamStats.#$dt = CE('span', {}, '0ms'),
CE('label', {}, 'BR'), CE('label', {}, 'BR'),
StreamStats.#$br = CE('span', {}, '0 Mbps'), StreamStats.#$br = CE('span', {}, '0 Mbps'),
CE('label', {}, 'PL'), CE('label', {}, 'PL'),
@ -167,7 +207,67 @@ class StreamStats {
CE('label', {}, 'FL'), CE('label', {}, 'FL'),
StreamStats.#$fl = CE('span', {}, '0 (0.00%)')); StreamStats.#$fl = CE('span', {}, '0 (0.00%)'));
let clickTimeout;
StreamStats.#$container.addEventListener('mousedown', e => {
clearTimeout(clickTimeout);
if (clickTimeout) {
// Double-clicked
clickTimeout = null;
StreamStats.#toggleSettingsUi();
return;
}
clickTimeout = setTimeout(() => {
clickTimeout = null;
}, 400);
});
document.documentElement.appendChild(StreamStats.#$container); document.documentElement.appendChild(StreamStats.#$container);
const refreshFunc = e => {
StreamStats.#refreshStyles()
};
const $position = PREFS.toElement(Preferences.STATS_POSITION, refreshFunc);
let $close;
const $showStartup = PREFS.toElement(Preferences.STATS_SHOW_WHEN_PLAYING, refreshFunc);
const $transparent = PREFS.toElement(Preferences.STATS_TRANSPARENT, refreshFunc);
const $formatting = PREFS.toElement(Preferences.STATS_CONDITIONAL_FORMATTING, refreshFunc);
const $opacity = PREFS.toElement(Preferences.STATS_OPACITY, refreshFunc);
const $textSize = PREFS.toElement(Preferences.STATS_TEXT_SIZE, refreshFunc);
StreamStats.#$settings = CE('div', {'class': 'better_xcloud_stats_settings'},
CE('b', {}, 'Stream Stats Settings'),
CE('div', {},
CE('label', {'for': `xcloud_setting_${Preferences.STATS_SHOW_WHEN_PLAYING}`}, 'Show stats when starting the game'),
$showStartup
),
CE('div', {},
CE('label', {}, 'Position'),
$position
),
CE('div', {},
CE('label', {}, 'Text size'),
$textSize
),
CE('div', {},
CE('label', {'for': `xcloud_setting_${Preferences.STATS_OPACITY}`}, 'Opacity (50-100%)'),
$opacity
),
CE('div', {},
CE('label', {'for': `xcloud_setting_${Preferences.STATS_TRANSPARENT}`}, 'Transparent background'),
$transparent
),
CE('div', {},
CE('label', {'for': `xcloud_setting_${Preferences.STATS_CONDITIONAL_FORMATTING}`}, 'Conditional formatting text color'),
$formatting
),
$close = CE('button', {}, 'Close'));
$close.addEventListener('click', e => StreamStats.hideSettingsUi());
document.documentElement.appendChild(StreamStats.#$settings);
StreamStats.#refreshStyles();
} }
} }
@ -194,84 +294,127 @@ class Preferences {
static get VIDEO_CONTRAST() { return 'video_contrast'; } static get VIDEO_CONTRAST() { return 'video_contrast'; }
static get VIDEO_SATURATION() { return 'video_saturation'; } static get VIDEO_SATURATION() { return 'video_saturation'; }
static SETTINGS = [ static get STATS_SHOW_WHEN_PLAYING() { return 'stats_show_when_playing'; }
{ static get STATS_POSITION() { return 'stats_position'; }
'id': Preferences.SERVER_REGION, static get STATS_TEXT_SIZE() { return 'stats_text_size'; }
static get STATS_TRANSPARENT() { return 'stats_transparent'; }
static get STATS_OPACITY() { return 'stats_opacity'; }
static get STATS_CONDITIONAL_FORMATTING() { return 'stats_conditional_formatting'; }
static SETTINGS = {
[Preferences.SERVER_REGION]: {
'label': 'Region of streaming server', 'label': 'Region of streaming server',
'default': 'default', 'default': 'default',
}, { },
'id': Preferences.FORCE_1080P_STREAM, [Preferences.FORCE_1080P_STREAM]: {
'label': 'Force 1080p stream', 'label': 'Force 1080p stream',
'default': false, 'default': false,
}, { },
'id': Preferences.USE_DESKTOP_CODEC, [Preferences.USE_DESKTOP_CODEC]: {
'label': 'Force high quality codec (if possible)', 'label': 'Force high-quality codec (if supported)',
'default': false, 'default': false,
}, { },
'id': Preferences.PREFER_IPV6_SERVER, [Preferences.PREFER_IPV6_SERVER]: {
'label': 'Prefer IPv6 streaming server', 'label': 'Prefer IPv6 streaming server',
'default': false, 'default': false,
}, { },
'id': Preferences.DISABLE_BANDWIDTH_CHECKING, [Preferences.DISABLE_BANDWIDTH_CHECKING]: {
'label': 'Disable bandwidth checking', 'label': 'Disable bandwidth checking',
'default': false, 'default': false,
}, { },
'id': Preferences.SCREENSHOT_BUTTON_POSITION, [Preferences.SCREENSHOT_BUTTON_POSITION]: {
'label': 'Screenshot button\'s position', 'label': 'Screenshot button\'s position',
'default': 'bottom-left', 'default': 'bottom-left',
'options': { 'options':
{
'bottom-left': 'Bottom Left', 'bottom-left': 'Bottom Left',
'bottom-right': 'Bottom Right', 'bottom-right': 'Bottom Right',
'none': 'Disable', 'none': 'Disable',
}, },
}, { },
'id': Preferences.SKIP_SPLASH_VIDEO, [Preferences.SKIP_SPLASH_VIDEO]: {
'label': 'Skip Xbox splash video', 'label': 'Skip Xbox splash video',
'default': false, 'default': false,
}, { },
'id': Preferences.HIDE_DOTS_ICON, [Preferences.HIDE_DOTS_ICON]: {
'label': 'Hide Dots icon while playing', 'label': 'Hide Dots icon while playing',
'default': false, 'default': false,
}, { },
'id': Preferences.REDUCE_ANIMATIONS, [Preferences.REDUCE_ANIMATIONS]: {
'label': 'Reduce UI animations', 'label': 'Reduce UI animations',
'default': false, 'default': false,
}, { },
'id': Preferences.BLOCK_SOCIAL_FEATURES, [Preferences.BLOCK_SOCIAL_FEATURES]: {
'label': 'Disable social features', 'label': 'Disable social features',
'default': false, 'default': false,
}, { },
'id': Preferences.BLOCK_TRACKING, [Preferences.BLOCK_TRACKING]: {
'label': 'Disable xCloud analytics', 'label': 'Disable xCloud analytics',
'default': false, 'default': false,
}, { },
'id': Preferences.VIDEO_FILL_FULL_SCREEN, [Preferences.VIDEO_FILL_FULL_SCREEN]: {
'label': 'Stretch video to full screen', 'label': 'Stretch video to full screen',
'default': false, 'default': false,
'hidden': true, 'hidden': true,
}, { },
'id': Preferences.VIDEO_SATURATION, [Preferences.VIDEO_SATURATION]: {
'label': 'Video saturation (%)', 'label': 'Video saturation (%)',
'default': 100, 'default': 100,
'min': 0, 'min': 0,
'max': 150, 'max': 150,
'hidden': true, 'hidden': true,
}, { },
'id': Preferences.VIDEO_CONTRAST, [Preferences.VIDEO_CONTRAST]: {
'label': 'Video contrast (%)', 'label': 'Video contrast (%)',
'default': 100, 'default': 100,
'min': 0, 'min': 0,
'max': 150, 'max': 150,
'hidden': true, 'hidden': true,
}, { },
'id': Preferences.VIDEO_BRIGHTNESS, [Preferences.VIDEO_BRIGHTNESS]: {
'label': 'Video brightness (%)', 'label': 'Video brightness (%)',
'default': 100, 'default': 100,
'min': 0, 'min': 0,
'max': 150, 'max': 150,
'hidden': true, 'hidden': true,
}, },
] [Preferences.STATS_SHOW_WHEN_PLAYING]: {
'default': false,
'hidden': true,
},
[Preferences.STATS_POSITION]: {
'default': 'top-left',
'options': {
'top-left': 'Top Left',
'top-center': 'Top Center',
'top-right': 'Top Right',
},
'hidden': true,
},
[Preferences.STATS_TEXT_SIZE]: {
'default': '0.9rem',
'options': {
'0.9rem': 'Small',
'1.0rem': 'Normal',
'1.1rem': 'Large',
},
'hidden': true,
},
[Preferences.STATS_TRANSPARENT]: {
'default': false,
'hidden': true,
},
[Preferences.STATS_OPACITY]: {
'default': 80,
'min': 50,
'max': 100,
'hidden': true,
},
[Preferences.STATS_CONDITIONAL_FORMATTING]: {
'default': false,
'hidden': true,
},
}
constructor() { constructor() {
this._storage = localStorage; this._storage = localStorage;
@ -284,11 +427,12 @@ class Preferences {
savedPrefs = JSON.parse(savedPrefs); savedPrefs = JSON.parse(savedPrefs);
this._prefs = {}; this._prefs = {};
for (let setting of Preferences.SETTINGS) { for (let settingId in Preferences.SETTINGS) {
if (setting.id in savedPrefs) { const setting = Preferences.SETTINGS[settingId];
this._prefs[setting.id] = savedPrefs[setting.id]; if (settingId in savedPrefs) {
this._prefs[settingId] = savedPrefs[settingId];
} else { } else {
this._prefs[setting.id] = setting.default; this._prefs[settingId] = setting.default;
} }
} }
} }
@ -304,17 +448,26 @@ class Preferences {
return defaultValue; return defaultValue;
} }
// Get default value // Return default value
for (let setting of Preferences.SETTINGS) { return Preferences.SETTINGS[key].default;
if (setting.id == key) {
return setting.default;
}
}
return null;
} }
set(key, value) { set(key, value) {
const config = Preferences.SETTINGS[key];
if (config) {
if ('min' in config) {
value = Math.max(config.min, value);
}
if ('max' in config) {
value = Math.min(config.max, value);
}
if ('options' in config && !(value in config.options)) {
value = config.default;
}
}
this._prefs[key] = value; this._prefs[key] = value;
this._update_storage(); this._update_storage();
} }
@ -322,6 +475,51 @@ class Preferences {
_update_storage() { _update_storage() {
this._storage.setItem(this._key, JSON.stringify(this._prefs)); this._storage.setItem(this._key, JSON.stringify(this._prefs));
} }
toElement(key, onChange) {
const CE = createElement;
const setting = Preferences.SETTINGS[key];
const currentValue = PREFS.get(key);
let $control;
if ('options' in setting) {
$control = CE('select', {id: 'xcloud_setting_' + key});
for (let value in setting.options) {
const label = setting.options[value];
const $option = CE('option', {value: value}, label);
$control.appendChild($option);
}
$control.value = currentValue;
$control.addEventListener('change', e => {
PREFS.set(key, e.target.value);
onChange && onChange(e);
});
} else if (typeof setting.default === 'number') {
$control = CE('input', {'type': 'number', 'min': setting.min, 'max': setting.max});
$control.value = currentValue;
$control.addEventListener('change', e => {
let value = Math.max(setting.min, Math.min(setting.max, parseInt(e.target.value)));
e.target.value = value;
PREFS.set(key, e.target.value);
onChange && onChange(e);
});
} else {
$control = CE('input', {'type': 'checkbox'});
$control.checked = currentValue;
$control.addEventListener('change', e => {
PREFS.set(key, e.target.checked);
onChange && onChange(e);
});
}
$control.id = `xcloud_setting_${key}`;
return $control;
}
} }
@ -554,22 +752,42 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.better_xcloud_stats_bar { .better_xcloud_stats_bar {
display: none; display: none;
position: absolute; user-select: none;
position: fixed;
top: 0; top: 0;
left: 0;
opacity: 0.8;
background-color: #000; background-color: #000;
color: #fff; color: #fff;
font-family: Consolas, "Courier New", Courier, monospace; font-family: Consolas, "Courier New", Courier, monospace;
font-size: 0.9rem; font-size: 0.9rem;
padding-left: 8px; padding-left: 8px;
z-index: 1000; z-index: 1000;
text-wrap: nowrap;
}
.better_xcloud_stats_bar[data-position=top-left] {
left: 0;
}
.better_xcloud_stats_bar[data-position=top-right] {
right: 0;
}
.better_xcloud_stats_bar[data-position=top-center] {
transform: translate(-50%, 0);
left: 50%;
}
.better_xcloud_stats_bar[data-transparent=true] {
background: none;
filter: drop-shadow(1px 0 0 #000) drop-shadow(-1px 0 0 #000) drop-shadow(0 1px 0 #000) drop-shadow(0 -1px 0 #000);
} }
.better_xcloud_stats_bar label { .better_xcloud_stats_bar label {
font-weight: bold;
margin: 0 8px 0 0; margin: 0 8px 0 0;
font-size: 0.9rem; font-family: Bahnschrift, Arial, Helvetica, sans-serif;
font-size: inherit;
font-weight: bold;
vertical-align: middle;
} }
.better_xcloud_stats_bar span { .better_xcloud_stats_bar span {
@ -579,6 +797,23 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
padding-right: 8px; padding-right: 8px;
margin-right: 8px; margin-right: 8px;
border-right: 2px solid #fff; border-right: 2px solid #fff;
vertical-align: middle;
}
.better_xcloud_stats_bar span[data-grade=good] {
color: #6bffff;
}
.better_xcloud_stats_bar span[data-grade=ok] {
color: #fff16b;
}
.better_xcloud_stats_bar span[data-grade=bad] {
color: #ff5f5f;
}
.better_xcloud_stats_bar span:first-of-type {
min-width: 30px;
} }
.better_xcloud_stats_bar span:last-of-type { .better_xcloud_stats_bar span:last-of-type {
@ -586,6 +821,78 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
margin-right: 0; margin-right: 0;
} }
.better_xcloud_stats_settings {
display: none;
position: fixed;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%);
width: 420px;
padding: 20px;
border-radius: 8px;
z-index: 1000;
background: #1a1b1e;
color: #fff;
font-weight: 400;
font-size: 16px;
font-family: "Segoe UI", Arial, Helvetica, sans-serif;
box-shadow: 0 0 6px #000;
user-select: none;
}
.better_xcloud_stats_settings *:focus {
outline: none !important;
}
.better_xcloud_stats_settings > b {
color: #fff;
display: block;
font-family: Bahnschrift, Arial, Helvetica, sans-serif;
font-size: 26px;
font-weight: 400;
line-height: 32px;
margin-bottom: 12px;
}
.better_xcloud_stats_settings > div {
display: flex;
margin-bottom: 8px;
padding: 2px 4px;
}
.better_xcloud_stats_settings label {
flex: 1;
margin-bottom: 0;
align-self: center;
}
.better_xcloud_stats_settings button {
padding: 8px 32px;
margin: 20px auto 0;
border: none;
border-radius: 4px;
display: block;
background-color: #2d3036;
text-align: center;
color: white;
text-transform: uppercase;
font-family: Bahnschrift, Arial, Helvetica, sans-serif;
font-weight: 400;
line-height: 18px;
font-size: 14px;
}
@media (hover: hover) {
.better_xcloud_stats_settings button:hover {
background-color: #515863;
}
}
.better_xcloud_stats_settings button:focus {
background-color: #515863;
}
/* Hide UI elements */ /* Hide UI elements */
#headerArea, #uhfSkipToMain, .uhf-footer { #headerArea, #uhfSkipToMain, .uhf-footer {
display: none; display: none;
@ -937,22 +1244,23 @@ function injectSettingsButton($parent) {
$updateAvailable.style.display = 'block'; $updateAvailable.style.display = 'block';
} }
for (let setting of Preferences.SETTINGS) { for (let settingId in Preferences.SETTINGS) {
const setting = Preferences.SETTINGS[settingId];
if (setting.hidden) { if (setting.hidden) {
continue; continue;
} }
let $control; let $control;
let labelAttrs = {}; let labelAttrs = {};
if (setting.id === Preferences.SERVER_REGION || setting.options) { if (settingId === Preferences.SERVER_REGION || setting.options) {
let selectedValue; let selectedValue;
$control = CE('select', {id: 'xcloud_setting_' + setting.id}); $control = CE('select', {id: 'xcloud_setting_' + settingId});
$control.addEventListener('change', e => { $control.addEventListener('change', e => {
PREFS.set(setting.id, e.target.value); PREFS.set(settingId, e.target.value);
}); });
if (setting.id === Preferences.SERVER_REGION) { if (settingId === Preferences.SERVER_REGION) {
selectedValue = PREF_PREFERRED_REGION; selectedValue = PREF_PREFERRED_REGION;
setting.options = {}; setting.options = {};
for (let regionName in SERVER_REGIONS) { for (let regionName in SERVER_REGIONS) {
@ -968,7 +1276,7 @@ function injectSettingsButton($parent) {
setting.options[value] = label; setting.options[value] = label;
} }
} else { } else {
selectedValue = PREFS.get(setting.id); selectedValue = PREFS.get(settingId);
} }
for (let value in setting.options) { for (let value in setting.options) {
@ -981,21 +1289,21 @@ function injectSettingsButton($parent) {
} else { } else {
$control = CE('input', { $control = CE('input', {
id: 'xcloud_setting_' + setting.id, id: 'xcloud_setting_' + settingId,
type: 'checkbox', type: 'checkbox',
'data-key': setting.id, 'data-key': settingId,
}); });
$control.addEventListener('change', e => { $control.addEventListener('change', e => {
PREFS.set(e.target.getAttribute('data-key'), e.target.checked); PREFS.set(e.target.getAttribute('data-key'), e.target.checked);
}); });
setting.value = PREFS.get(setting.id); setting.value = PREFS.get(settingId);
$control.checked = setting.value; $control.checked = setting.value;
labelAttrs = {'for': 'xcloud_setting_' + setting.id, 'tabindex': 0}; labelAttrs = {'for': 'xcloud_setting_' + settingId, 'tabindex': 0};
if (setting.id === Preferences.USE_DESKTOP_CODEC && !hasRtcSetCodecPreferencesSupport()) { if (settingId === Preferences.USE_DESKTOP_CODEC && !hasRtcSetCodecPreferencesSupport()) {
$control.checked = false; $control.checked = false;
$control.disabled = true; $control.disabled = true;
$control.title = 'Your browser doesn\'t support this feature'; $control.title = 'Your browser doesn\'t support this feature';
@ -1227,7 +1535,7 @@ function patchVideoApi() {
$SCREENSHOT_CANVAS.height = this.videoHeight; $SCREENSHOT_CANVAS.height = this.videoHeight;
StreamBadges.resolution = {width: this.videoWidth, height: this.videoHeight}; StreamBadges.resolution = {width: this.videoWidth, height: this.videoHeight};
const stats = STREAM_WEBRTC.getStats().then(stats => { STREAM_WEBRTC.getStats().then(stats => {
stats.forEach(stat => { stats.forEach(stat => {
if (stat.type !== 'codec') { if (stat.type !== 'codec') {
return; return;
@ -1252,6 +1560,10 @@ function patchVideoApi() {
}; };
} }
}); });
if (PREFS.get(Preferences.STATS_SHOW_WHEN_PLAYING)) {
StreamStats.start();
}
}); });
if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') { if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
@ -1308,7 +1620,7 @@ function patchRtcCodecs() {
const newCodecs = codecs.slice(); const newCodecs = codecs.slice();
let pos = 0; let pos = 0;
newCodecs.forEach((codec, i) => { newCodecs.forEach((codec, i) => {
// Find high quality codecs // Find high-quality codecs
if (codec.sdpFmtpLine && codec.sdpFmtpLine.includes('profile-level-id=4d')) { if (codec.sdpFmtpLine && codec.sdpFmtpLine.includes('profile-level-id=4d')) {
// Move it to the top of the array // Move it to the top of the array
newCodecs.splice(i, 1); newCodecs.splice(i, 1);
@ -1593,6 +1905,7 @@ function hideUiOnPageChange() {
STREAM_WEBRTC = null; STREAM_WEBRTC = null;
$STREAM_VIDEO = null; $STREAM_VIDEO = null;
StreamStats.stop(); StreamStats.stop();
StreamStats.hideSettingsUi();
document.querySelector('.better_xcloud_screenshot_button').style = ''; document.querySelector('.better_xcloud_screenshot_button').style = '';
} }