Compare commits

...

8 Commits
v1.8.2 ... v1.9

Author SHA1 Message Date
ecad1dc51b Bump version to 1.9 2023-08-04 18:40:24 +07:00
e1c1d74a22 Bump version to 1.9 2023-08-04 18:40:08 +07:00
b1881678b1 Update README.md 2023-08-04 18:30:08 +07:00
9d8d9680d3 Add "Quick glance" setting for Stream stats (#59) 2023-08-04 18:16:12 +07:00
4d2b6c5ef7 Simplify stream menu (#58)
* Add "Simplify Stream's menu" setting

* Fix Smart TV layout

* Fix not able to hide Video bar if the "Disable touch controller" is on

* Combine Region + Server badges into one

* Reduce badge's font-size from 16 to 14px

* Fix battery level showing .99999

* Don't show battery badge when it's 100%

* Fix showing incorrect Audio codec in Safari

* Add "Safari on macOS" User-Agent profile

* Fix showing incorrect Video codec in Safari

* Use "-webkit-user-select" for Safari

* Update Video badge's color
2023-08-04 16:19:18 +07:00
0c80e3ab1d Disable PWA prompt in Safari on iOS/iPadOS (#52) 2023-08-04 07:17:59 +07:00
6f326e8f2a Update README.md 2023-08-03 09:37:28 +07:00
92fe3756cf Update README.md 2023-08-02 20:35:27 +07:00
3 changed files with 260 additions and 70 deletions

View File

@ -14,10 +14,9 @@ Give this project a 🌟 if you like it. Thank you 🙏.
## Features
<img width="475" alt="Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/f4187ef8-fa10-4eab-b085-ef3c3aed3201">
<img width="475" alt="Stream HUD UI" src="https://github.com/redphx/better-xcloud/assets/96280/7e0fe3e1-b826-4a69-9843-a3acb866a2f9">
<img width="475" alt="Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/575d566a-7759-4cce-962d-7e5f55a70d9e">
<img width="475" alt="Stream HUD UI" src="https://github.com/redphx/better-xcloud/assets/96280/b4f943f1-d0b4-4401-a8cb-0fd677a5c6f0">
&nbsp;
@ -55,6 +54,8 @@ Give this project a 🌟 if you like it. Thank you 🙏.
- **Disable touch controller**
> Stop the touch controller from showing when touching the screen.
> Useful when you play on a device with a built-in controller like Logitech G Cloud, Steam Deck, Retroid, etc.
- **Simplify Stream's menu**
> Hide the labels of the menu buttons.
- **Hide mouse cursor while playing**
> Hide the mouse cursor after 3 seconds of not moving.
- **Stretch video to full sctreen**
@ -72,7 +73,6 @@ Give this project a 🌟 if you like it. Thank you 🙏.
> The analytics contains statistics of your streaming session, so I'd recommend allowing analytics to help Xbox improve xCloud's experience in the future.
- **Change User-Agent**
> Useful when you're using unsupported browsers.
> If you're on Safari, changing User-Agent to "Edge on Windows" will allow you to use Mic feature.
> This setting only affects xCloud, and it doesn't change browser's global User-Agent.
> 📝 If you get 404 error after using this feature, try refreshing the page a few times. See [#34](https://github.com/redphx/better-xcloud/issues/34).
- **Reduce UI animations**
@ -123,11 +123,12 @@ Don't see your browser in the table? If it supports Tampermonkey/Userscript then
- **Better xCloud** also works on Android TV, but you'll have to sideload the browser APK and need a Bluetooth mouse if you want to interact with the Settings.
## Stream stats
<img width="500" alt="Stream stats" src="https://github.com/redphx/better-xcloud/assets/96280/70f4b1bb-4e3d-4f27-9b2f-afcfe1b8b261">
- While playing > `...` > `Stream Stats`.
- Double-click on the stats bar to show Settings dialog.
<img width="500" alt="Stream stats" src="https://github.com/redphx/better-xcloud/assets/96280/0d4abb6b-49ab-4c9a-a52d-df7e396d2145">
- While playing > `...` > `Stream Stats` (the one with the eye icon).
- Double-click on the stats bar to show the Settings dialog.
- This bar is updated every second.
- **Quick glance** feature: only show the stats bar when the System buttons bar is expanded. The 👀 emoji at the beginning indicates that the stats bar is in the quick glance mode.
- ⚠️ Using **Better xCloud** or showing the stats bar also affects the performance of the stream.
| Abbr. | Full name | Explain |
@ -178,8 +179,8 @@ I think it's very unlikely that you'll get banned for using this. Most of the fe
2. **Why is it an Userscript and not an extension?**
It's because not many browsers on Android support installing extensions (and not all extensions can be installed).
3. **I see "???" button instead of the server's name**
That means Tampermonkey is not working properly. Please make sure you're using the latest version or switch to a well-known browser.
3. **Why doesn't the xCloud website implement *this* or *that* feature from Better xCloud?**
For being an unofficial tool, **Better xCloud** has the luxury to implement anything on the xCloud website. On the xCloud's side, they have a lot more users and devices to support, so it's more difficult for them to implement a new feature. Also it's not easy to explain some of the features of **Better xCloud** to normal xCloud users.
4. **Can I use this with the Xbox Android app?**
No, you can't. You'll have to modify the app.

View File

@ -1,5 +1,5 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 1.8.2
// @version 1.9
// ==/UserScript==

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 1.8.2
// @version 1.9
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -13,7 +13,7 @@
// ==/UserScript==
'use strict';
const SCRIPT_VERSION = '1.8.2';
const SCRIPT_VERSION = '1.9';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
const SERVER_REGIONS = {};
@ -69,7 +69,6 @@ class StreamBadges {
static get BADGE_IN() { return 'in'; };
static get BADGE_OUT() { return 'out'; };
static get BADGE_REGION() { return 'region'; };
static get BADGE_SERVER() { return 'server'; };
static get BADGE_VIDEO() { return 'video'; };
static get BADGE_AUDIO() { return 'audio'; };
@ -126,13 +125,14 @@ class StreamBadges {
// Battery
let batteryLevel = '100%';
let batteryLevelInt = 100;
if (navigator.getBattery) {
try {
const currentLevel = (await navigator.getBattery()).level * 100;
batteryLevel = `${currentLevel}%`;
batteryLevelInt = Math.round((await navigator.getBattery()).level * 100);
batteryLevel = `${batteryLevelInt}%`;
if (currentLevel != StreamBadges.startBatteryLevel) {
const diffLevel = currentLevel - StreamBadges.startBatteryLevel;
if (batteryLevelInt != StreamBadges.startBatteryLevel) {
const diffLevel = Math.round(batteryLevelInt - StreamBadges.startBatteryLevel);
const sign = diffLevel > 0 ? '+' : '';
batteryLevel += ` (${sign}${diffLevel}%)`;
}
@ -164,6 +164,14 @@ class StreamBadges {
const $elm = StreamBadges.#cachedDoms[name];
$elm && ($elm.lastElementChild.textContent = value);
if (name === StreamBadges.BADGE_BATTERY) {
if (StreamBadges.startBatteryLevel === 100 && batteryLevelInt === 100) {
$elm.style.display = 'none';
} else {
$elm.style = '';
}
}
}
}
@ -218,15 +226,18 @@ class StreamBadges {
batteryLevel = '100%';
}
// Server + Region
let server = StreamBadges.region;
server += '@' + (StreamBadges.ipv6 ? 'IPv6' : 'IPv4');
const BADGES = [
[StreamBadges.BADGE_PLAYTIME, '1m', '#ff004d'],
[StreamBadges.BADGE_BATTERY, batteryLevel, '#00b543'],
[StreamBadges.BADGE_IN, StreamBadges.#humanFileSize(0), '#29adff'],
[StreamBadges.BADGE_OUT, StreamBadges.#humanFileSize(0), '#ff77a8'],
[StreamBadges.BADGE_BREAK],
[StreamBadges.BADGE_REGION, StreamBadges.region, '#ff6c24'],
[StreamBadges.BADGE_SERVER, StreamBadges.ipv6 ? 'IPv6' : 'IPv4', '#065ab5'],
video ? [StreamBadges.BADGE_VIDEO, video, '#754665'] : null,
[StreamBadges.BADGE_SERVER, server, '#ff6c24'],
video ? [StreamBadges.BADGE_VIDEO, video, '#742f29'] : null,
audio ? [StreamBadges.BADGE_AUDIO, audio, '#5f574f'] : null,
];
@ -258,28 +269,83 @@ class StreamStats {
static #lastStat;
static start() {
static #quickGlanceObserver;
static start(glancing=false) {
if (!StreamStats.isHidden() || (glancing && StreamStats.#isGlancing())) {
return;
}
StreamStats.#$container.classList.remove('better-xcloud-gone');
StreamStats.#$container.setAttribute('data-display', glancing ? 'glancing' : 'fixed');
StreamStats.#interval = setInterval(StreamStats.update, StreamStats.#updateInterval);
}
static stop() {
clearInterval(StreamStats.#interval);
static stop(glancing=false) {
if (glancing && !StreamStats.#isGlancing()) {
return;
}
StreamStats.#$container.classList.add('better-xcloud-gone');
clearInterval(StreamStats.#interval);
StreamStats.#interval = null;
StreamStats.#lastStat = null;
StreamStats.#$container.removeAttribute('data-display');
StreamStats.#$container.classList.add('better-xcloud-gone');
}
static toggle() {
StreamStats.#isHidden() ? StreamStats.start() : StreamStats.stop();
if (StreamStats.#isGlancing()) {
StreamStats.#$container.setAttribute('data-display', 'fixed');
} else {
StreamStats.isHidden() ? StreamStats.start() : StreamStats.stop();
}
}
static #isHidden = () => StreamStats.#$container.classList.contains('better-xcloud-gone');
static onStoppedPlaying() {
StreamStats.stop();
StreamStats.quickGlanceStop();
StreamStats.hideSettingsUi();
}
static isHidden = () => StreamStats.#$container.classList.contains('better-xcloud-gone');
static #isGlancing = () => StreamStats.#$container.getAttribute('data-display') === 'glancing';
static quickGlanceSetup() {
if (StreamStats.#quickGlanceObserver) {
return;
}
const $uiContainer = document.querySelector('div[data-testid=ui-container]');
StreamStats.#quickGlanceObserver = new MutationObserver((mutationList, observer) => {
for (let record of mutationList) {
if (record.attributeName && record.attributeName === 'aria-expanded') {
const expanded = record.target.ariaExpanded;
if (expanded === 'true') {
StreamStats.isHidden() && StreamStats.start(true);
} else {
StreamStats.stop(true);
}
}
}
});
StreamStats.#quickGlanceObserver.observe($uiContainer, {
attributes: true,
attributeFilter: ['aria-expanded'],
subtree: true,
});
}
static quickGlanceStop() {
StreamStats.#quickGlanceObserver && StreamStats.#quickGlanceObserver.disconnect();
StreamStats.#quickGlanceObserver = null;
}
static update() {
if (StreamStats.#isHidden() || !STREAM_WEBRTC) {
StreamStats.stop();
if (StreamStats.isHidden() || !STREAM_WEBRTC) {
StreamStats.onStoppedPlaying();
return;
}
@ -351,6 +417,10 @@ class StreamStats {
static hideSettingsUi() {
StreamStats.#$settings.style.display = 'none';
if (StreamStats.#isGlancing() && !PREFS.get(Preferences.STATS_QUICK_GLANCE)) {
StreamStats.stop();
}
}
static #toggleSettingsUi() {
@ -401,7 +471,10 @@ class StreamStats {
const $position = PREFS.toElement(Preferences.STATS_POSITION, refreshFunc);
let $close;
const $showStartup = PREFS.toElement(Preferences.STATS_SHOW_WHEN_PLAYING, refreshFunc);
const $showStartup = PREFS.toElement(Preferences.STATS_SHOW_WHEN_PLAYING);
const $quickGlance = PREFS.toElement(Preferences.STATS_QUICK_GLANCE, e => {
e.target.checked ? StreamStats.quickGlanceSetup() : StreamStats.quickGlanceStop();
});
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);
@ -413,6 +486,10 @@ class StreamStats {
CE('label', {'for': `xcloud_setting_${Preferences.STATS_SHOW_WHEN_PLAYING}`}, 'Show stats when starting the game'),
$showStartup
),
CE('div', {},
CE('label', {'for': `xcloud_setting_${Preferences.STATS_QUICK_GLANCE}`}, 'Enable quick glance'),
$quickGlance
),
CE('div', {},
CE('label', {}, 'Position'),
$position
@ -442,15 +519,16 @@ class StreamStats {
}
}
class UserAgent {
static get PROFILE_EDGE_WINDOWS() { return 'edge-windows'; }
static get PROFILE_SAFARI_MACOS() { return 'safari-macos'; }
static get PROFILE_SMARTTV_TIZEN() { return 'smarttv-tizen'; }
static get PROFILE_DEFAULT() { return 'default'; }
static get PROFILE_CUSTOM() { return 'custom'; }
static #USER_AGENTS = {
[UserAgent.PROFILE_EDGE_WINDOWS]: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.188',
[UserAgent.PROFILE_SAFARI_MACOS]: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1',
[UserAgent.PROFILE_SMARTTV_TIZEN]: 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) 94.0.4606.31/7.0 TV Safari/537.36',
}
@ -506,6 +584,7 @@ class Preferences {
static get USER_AGENT_PROFILE() { return 'user_agent_profile'; }
static get USER_AGENT_CUSTOM() { return 'user_agent_custom'; }
static get STREAM_HIDE_TOUCH_CONTROLLER() { return 'stream_hide_touch_controller'; }
static get STREAM_SIMPLIFY_MENU() { return 'stream_simplify_menu'; }
static get SCREENSHOT_BUTTON_POSITION() { return 'screenshot_button_position'; }
static get BLOCK_TRACKING() { return 'block_tracking'; }
@ -521,6 +600,7 @@ class Preferences {
static get VIDEO_SATURATION() { return 'video_saturation'; }
static get STATS_SHOW_WHEN_PLAYING() { return 'stats_show_when_playing'; }
static get STATS_QUICK_GLANCE() { return 'stats_quick_glance'; }
static get STATS_POSITION() { return 'stats_position'; }
static get STATS_TEXT_SIZE() { return 'stats_text_size'; }
static get STATS_TRANSPARENT() { return 'stats_transparent'; }
@ -617,6 +697,10 @@ class Preferences {
'label': 'Disable touch controller',
'default': false,
},
[Preferences.STREAM_SIMPLIFY_MENU]: {
'label': 'Simplify Stream\'s menu',
'default': false,
},
[Preferences.HIDE_IDLE_CURSOR]: {
'label': 'Hide mouse cursor while playing',
'default': false,
@ -639,6 +723,7 @@ class Preferences {
'options': {
[UserAgent.PROFILE_DEFAULT]: 'Default',
[UserAgent.PROFILE_EDGE_WINDOWS]: 'Edge on Windows',
[UserAgent.PROFILE_SAFARI_MACOS]: 'Safari on macOS',
[UserAgent.PROFILE_SMARTTV_TIZEN]: 'Samsung Smart TV',
[UserAgent.PROFILE_CUSTOM]: 'Custom',
},
@ -677,6 +762,10 @@ class Preferences {
'default': false,
'hidden': true,
},
[Preferences.STATS_QUICK_GLANCE]: {
'default': false,
'hidden': true,
},
[Preferences.STATS_POSITION]: {
'default': 'top-left',
'options': {
@ -864,6 +953,7 @@ function addCss() {
.better_xcloud_settings {
background-color: #151515;
user-select: none;
-webkit-user-select: none;
color: #fff;
font-family: "Segoe UI", Arial, Helvetica, sans-serif
}
@ -987,9 +1077,9 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.better-xcloud-badges {
position: absolute;
top: 155px;
margin-left: 0px;
user-select: none;
-webkit-user-select: none;
}
.better-xcloud-badge {
@ -998,6 +1088,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
line-height: 24px;
color: #fff;
font-family: Bahnschrift Semibold, Arial, Helvetica, sans-serif;
font-size: 14px;
font-weight: 400;
margin: 0 8px 8px 0;
box-shadow: 0px 0px 6px #000;
@ -1053,6 +1144,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.better-xcloud-stats-bar {
display: block;
user-select: none;
-webkit-user-select: none;
position: fixed;
top: 0;
background-color: #000;
@ -1064,6 +1156,11 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
text-wrap: nowrap;
}
.better-xcloud-stats-bar[data-display=glancing]::before {
content: '👀 ';
vertical-align: middle;
}
.better-xcloud-stats-bar[data-position=top-left] {
left: 0;
}
@ -1139,6 +1236,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
font-family: "Segoe UI", Arial, Helvetica, sans-serif;
box-shadow: 0 0 6px #000;
user-select: none;
-webkit-user-select: none;
}
.better-xcloud-stats-settings *:focus {
@ -1196,6 +1294,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.better-xcloud-quick-settings-bar {
display: none;
user-select: none;
-webkit-user-select: none;
position: fixed;
bottom: 20px;
left: 50%;
@ -1279,17 +1378,6 @@ div[class*=NotFocusedDialog] {
#game-stream video {
visibility: hidden;
}
/* Adjust Stream menu icon's size */
button[class*=MenuItem-module__container] {
min-width: auto !important;
width: 100px !important;
}
button[class*=MenuItem-module__container] div[class*=MenuItem-module__label] {
margin-left: 8px !important;
margin-right: 8px !important;
}
`;
// Reduce animations
@ -1333,6 +1421,71 @@ div[class*=StreamHUD-module__buttonsContainer] {
`;
}
// Simplify Stream's menu
css += `
div[class*=StreamMenu-module__menu] {
min-width: 100vw !important;
}
`;
if (PREFS.get(Preferences.STREAM_SIMPLIFY_MENU)) {
css += `
div[class*=Menu-module__scrollable] {
--bxStreamMenuItemSize: 80px;
--streamMenuItemSize: calc(var(--bxStreamMenuItemSize) + 40px) !important;
}
.better-xcloud-badges {
top: calc(var(--streamMenuItemSize) - 20px);
}
body[data-media-type=tv] .better-xcloud-badges {
top: calc(var(--streamMenuItemSize) - 10px) !important;
}
button[class*=MenuItem-module__container] {
min-width: auto !important;
min-height: auto !important;
width: var(--bxStreamMenuItemSize) !important;
height: var(--bxStreamMenuItemSize) !important;
}
div[class*=MenuItem-module__label] {
display: none !important;
}
svg[class*=MenuItem-module__icon] {
width: 36px;
height: 100% !important;
padding: 0 !important;
margin: 0 !important;
}
`;
} else {
css += `
body[data-media-type=tv] .better-xcloud-badges {
top: calc(var(--streamMenuItemSize) + 30px);
}
body:not([data-media-type=tv]) .better-xcloud-badges {
top: calc(var(--streamMenuItemSize) + 20px);
}
body:not([data-media-type=tv]) button[class*=MenuItem-module__container] {
min-width: auto !important;
width: 100px !important;
}
body:not([data-media-type=tv]) button[class*=MenuItem-module__container]:nth-child(n+2) {
margin-left: 10px !important;
}
body:not([data-media-type=tv]) div[class*=MenuItem-module__label] {
margin-left: 8px !important;
margin-right: 8px !important;
}
`;
}
const $style = createElement('style', {}, css);
document.documentElement.appendChild($style);
}
@ -1855,7 +2008,8 @@ function injectVideoSettingsButton() {
const $quickBar = document.querySelector('.better-xcloud-quick-settings-bar');
const $parent = $screen.parentElement;
const hideQuickBarFunc = e => {
if (e.target != $parent && e.target.id !== 'MultiTouchSurface') {
e.stopPropagation();
if (e.target != $parent && e.target.id !== 'MultiTouchSurface' && !e.target.querySelector('#BabylonCanvasContainer-main')) {
return;
}
@ -1863,7 +2017,7 @@ function injectVideoSettingsButton() {
$quickBar.style.display = 'none';
$parent.removeEventListener('click', hideQuickBarFunc);
$parent.removeEventListener('touchend', hideQuickBarFunc);
$parent.removeEventListener('touchstart', hideQuickBarFunc);
if (e.target.id === 'MultiTouchSurface') {
e.target.removeEventListener('touchstart', hideQuickBarFunc);
@ -1896,10 +2050,10 @@ function injectVideoSettingsButton() {
$quickBar.style.display = 'flex';
$parent.addEventListener('click', hideQuickBarFunc);
$parent.addEventListener('touchend', hideQuickBarFunc);
$parent.addEventListener('touchstart', hideQuickBarFunc);
const $touchSurface = document.querySelector('#MultiTouchSurface');
$touchSurface && $touchSurface.addEventListener('touchstart', hideQuickBarFunc);
$touchSurface && $touchSurface.style.display != 'none' && $touchSurface.addEventListener('touchstart', hideQuickBarFunc);
});
// Add button at the beginning
@ -1967,6 +2121,7 @@ function injectVideoSettingsButton() {
function patchVideoApi() {
const PREF_SKIP_SPLASH_VIDEO = PREFS.get(Preferences.SKIP_SPLASH_VIDEO);
const PREF_SCREENSHOT_BUTTON_POSITION = PREFS.get(Preferences.SCREENSHOT_BUTTON_POSITION);
const PREF_STATS_QUICK_GLANCE = PREFS.get(Preferences.STATS_QUICK_GLANCE);
// Show video player when it's ready
var showFunc;
@ -1978,47 +2133,77 @@ function patchVideoApi() {
return;
}
if (PREF_STATS_QUICK_GLANCE) {
StreamStats.quickGlanceSetup();
}
$STREAM_VIDEO = this;
$SCREENSHOT_CANVAS.width = this.videoWidth;
$SCREENSHOT_CANVAS.height = this.videoHeight;
StreamBadges.resolution = {width: this.videoWidth, height: this.videoHeight};
StreamBadges.startTimestamp = +new Date;
// Get battery level
if (navigator.getBattery) {
try {
navigator.getBattery().then(bm => {
StreamBadges.startBatteryLevel = bm.level * 100;
StreamBadges.startBatteryLevel = Math.round(bm.level * 100);
});
} catch(e) {}
}
STREAM_WEBRTC.getStats().then(stats => {
const allVideoCodecs = {};
let videoCodecId;
const allAudioCodecs = {};
let audioCodecId;
stats.forEach(stat => {
if (stat.type !== 'codec') {
return;
}
const mimeType = stat.mimeType.split('/');
if (mimeType[0] === 'video') {
const video = {
codec: mimeType[1],
};
if (video.codec === 'H264') {
const match = /profile-level-id=([0-9a-f]{6})/.exec(stat.sdpFmtpLine);
video.profile = match ? match[1] : null;
if (stat.type == 'codec') {
const mimeType = stat.mimeType.split('/');
if (mimeType[0] === 'video') {
// Store all video stats
allVideoCodecs[stat.id] = stat;
} else if (mimeType[0] === 'audio') {
// Store all audio stats
allAudioCodecs[stat.id] = stat;
}
} else if (stat.type === 'inbound-rtp' && stat.packetsReceived > 0) {
// Get the codecId of the video/audio track currently being used
if (stat.kind === 'video') {
videoCodecId = stat.codecId;
} else if (stat.kind === 'audio') {
audioCodecId = stat.codecId;
}
StreamBadges.video = video;
} else if (!StreamBadges.audio && mimeType[0] === 'audio') {
StreamBadges.audio = {
codec: mimeType[1],
bitrate: stat.clockRate,
};
}
});
// Get video codec from codecId
if (videoCodecId) {
const videoStat = allVideoCodecs[videoCodecId];
const video = {
codec: videoStat.mimeType.substring(6),
};
if (video.codec === 'H264') {
const match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine);
video.profile = match ? match[1] : null;
}
StreamBadges.video = video;
}
// Get audio codec from codecId
if (audioCodecId) {
const audioStat = allAudioCodecs[audioCodecId];
StreamBadges.audio = {
codec: audioStat.mimeType.substring(6),
bitrate: audioStat.clockRate,
}
}
if (PREFS.get(Preferences.STATS_SHOW_WHEN_PLAYING)) {
StreamStats.start();
}
@ -2291,8 +2476,7 @@ function onHistoryChange() {
STREAM_WEBRTC = null;
$STREAM_VIDEO = null;
StreamStats.stop();
StreamStats.hideSettingsUi();
StreamStats.onStoppedPlaying();
document.querySelector('.better-xcloud-screenshot-button').style = '';
MouseCursorHider.stop();
@ -2340,3 +2524,8 @@ updateVideoPlayerCss();
setupVideoSettingsBar();
setupScreenshotButton();
StreamStats.render();
// Disable PWA prompt in Safari on iOS/iPadOS
Object.defineProperty(window.navigator, 'standalone', {
value: true,
});