Compare commits

...

22 Commits
v1.0 ... v1.2

Author SHA1 Message Date
d2debc0623 Bump version to 1.2 2023-07-15 17:23:44 +07:00
0c3d8519e1 Update README.md 2023-07-15 17:23:11 +07:00
e323bd5b47 Fix Quick bar not hiding after quitting game 2023-07-15 17:07:08 +07:00
ce54aa4580 Update CSS of Quick video settings bar 2023-07-15 16:59:28 +07:00
d29447173c Fix Quick video settings feature not working with touch screen 2023-07-15 16:56:01 +07:00
2f3a3167e1 Add Quick video settings bar while playing 2023-07-15 16:32:20 +07:00
e17459934b Update README.md 2023-07-15 12:38:01 +07:00
ecf9b414ba Bump version to 1.1.3 2023-07-15 12:27:30 +07:00
f78bd31b58 Fix video not showing then the "Skip splash video" setting is off 2023-07-15 12:27:03 +07:00
da54bd5302 Update README.md 2023-07-15 09:56:59 +07:00
5479eeb66f Bump version to 1.1.2 2023-07-15 09:46:08 +07:00
a9eb70eaae Hide video player until it's ready 2023-07-15 09:40:13 +07:00
0e2066e461 Add Compatibility section 2023-07-15 07:45:54 +07:00
2ee2469a10 Hide the ugly black line in Grip Handle 2023-07-14 18:05:32 +07:00
9056b5926f Bump version to 1.1.1 2023-07-14 17:34:55 +07:00
730799821b Fix not applying video settings in some cases 2023-07-14 17:34:00 +07:00
fed7d489f4 Hide background of GripHandle 2023-07-14 17:31:08 +07:00
42b600714e Update URL of "updateURL" and "downloadURL" 2023-07-14 17:10:11 +07:00
5dd02f04de Bump version to 1.1 2023-07-14 17:09:07 +07:00
c6c7fc7de5 Update README.md 2023-07-14 17:08:02 +07:00
169a0a0d4f Add "Stretch video to full screen" feature 2023-07-14 16:57:12 +07:00
90bb75fe56 Update README.md 2023-07-14 15:24:44 +07:00
2 changed files with 419 additions and 76 deletions

View File

@ -1,23 +1,25 @@
# Better xCloud # Better xCloud
Improve [Xbox Cloud Gaming (xCloud)](https://www.xbox.com/play/) experience. Improve [Xbox Cloud Gaming (xCloud)](https://www.xbox.com/play/) experience on web browser.
The main target of this script is Android users, but it should work great on desktop too. The main target of this script is Android users, but it should work great on desktop too.
## Features: ## Features
<img width="474" alt="screenshot" src="https://github.com/redphx/better-xcloud/assets/96280/a0e85915-4e3f-4c1b-8885-eda1c712eeb6"> <img width="474" alt="image" src="https://github.com/redphx/better-xcloud/assets/96280/2793d404-3185-4c91-a500-dde362c661dd">
- Switch region of streaming server. - Switch region of streaming server.
- Prefer IPv6 streaming server (might improve latency). - Prefer IPv6 streaming server (might reduce latency).
- Force HD stream by disabling bandwidth checking -> xCloud always tries to use the best possible quality. - Force HD stream by disabling bandwidth checking -> xCloud always tries to use the best possible quality.
- Skip Xbox splash video. - Skip Xbox splash video (save 3 seconds).
- Make the top-left dots icon invisible while playing. You can still click on it, but it doesn't block the screen anymore. - Make the top-left dots icon invisible while playing. You can still click on it, but it doesn't block the screen anymore.
- Stretch video to full sctreen. Useful when you don't have a 16:9 screen.
- Adjust video filters (brightness/contrast/saturation). - Adjust video filters (brightness/contrast/saturation).
- You can change video settings while playing.
- Hide footer and other UI elements. - Hide footer and other UI elements.
- Reduce UI animations (the smooth scrolling cannot be disabled). - Reduce UI animations (the smooth scrolling cannot be disabled).
- Disable social features (friends, chat...). - Disable social features (friends, chat...).
- Disable xCloud analytics. The analytics contains statistics of your streaming session, so I'd recommend to enable analytics to help Xbox improve xCloud's experence in the future. - Disable xCloud analytics. The analytics contains statistics of your streaming session, so I'd recommend to allow analytics to help Xbox improve xCloud's experence in the future.
## How to use: ## How to use
1. Install [Tampermonkey extension](https://www.tampermonkey.net/) on suppported browsers. It's also available for Firefox on Android. 1. Install [Tampermonkey extension](https://www.tampermonkey.net/) on suppported browsers. It's also available for Firefox on Android.
2. Install **Better xCloud**: 2. Install **Better xCloud**:
- [Directly on Github](https://github.com/redphx/better-xcloud/releases/latest/download/better-xcloud.user.js) - [Directly on Github](https://github.com/redphx/better-xcloud/releases/latest/download/better-xcloud.user.js)
@ -41,10 +43,20 @@ Other options (only do one of these):
- Add ` 36102dd3-6953-45f6-8b48-031fb95e0e0d` to become a Logitech G Cloud device. - Add ` 36102dd3-6953-45f6-8b48-031fb95e0e0d` to become a Logitech G Cloud device.
- Add ` 0ed22b6f-b61d-41eb-810a-a1ed586a550b` to become a Razer Edge device. - Add ` 0ed22b6f-b61d-41eb-810a-a1ed586a550b` to become a Razer Edge device.
## Tested on: ## Compatibility
- Chrome on macOS. ✅ = confirmed to be working
- Firefox for Android with Tampermonkey add-on. ❓ = not yet tested
- *(NOT RECOMMENDED at the moment since its Userscript implementation is not working properly)* [Hermit Browser](https://hermit.chimbori.com) on Android. It supports custom User-Agent and has built-in Userscript support (premium features, only $7.99) so you don't have to install anything else. I built **Better xCloud** just so I could use it with Hermit. ❌ = not supported (mostly because of lacking Userscript/extension support)
⚠️ = see custom notes
| | Desktop | Android | iOS |
|----------------------------------------|----------|------------------|-----|
| Chrome | ✅ | ❌ | ❌ |
| Firefox | ✅ | ✅ | ❌ |
| Edge | ❓ | ❌ | ❌ |
| Safari | ❓ | ❌ | ❓ |
| [Hermit](https://hermit.chimbori.com) | ❌ | ⚠️<sup>(1)</sup> | ❌ |
<sup>1</sup> NOT RECOMMENDED at the moment since its Userscript implementation is not working properly. Non-network related features (skip splash video, video settings...) still work. It's still my favorite app to play xCloud on because it's lightweight, supports both custom User-Agent and Userscript (premium features, only $1.99 for Userscript feature or $7.99 if you want both) without having to install anything else. I built **Better xCloud** just so I could use it with Hermit.
## FAQ ## FAQ
1. **Why is it an Userscript and not extension?** 1. **Why is it an Userscript and not extension?**
@ -54,11 +66,14 @@ It's because not many browsers on Android support installing extensions (and not
That means Tampermonkey is not working properly. Please make sure you're using the latest version or switch to a well-known browser. That means Tampermonkey is not working properly. Please make sure you're using the latest version or switch to a well-known browser.
3. **Can I use this with the Xbox Android app?** 3. **Can I use this with the Xbox Android app?**
No you can't. You'll have to modidy the app. No you can't. You'll have to modify the app.
4. **Will you able to enable "Clarity Boost" feature on non-Edge browsers?**
No. "Clarity Boost" feature uses an exclusive API (`Video.msVideoProcessing`) that's only available on Edge browser for desktop at the moment.
## Acknowledgements ## Acknowledgements
**Better xCloud** is inspired by these projects: - [n-thumann/xbox-cloud-server-selector](https://github.com/n-thumann/xbox-cloud-server-selector) for the idea of IPv6 feature
- [n-thumann/xbox-cloud-server-selector](https://github.com/n-thumann/xbox-cloud-server-selector) - Icons by [Adam Design](https://www.iconfinder.com/iconsets/user-interface-outline-27)
## Disclaimers ## Disclaimers
- Use as your own risk. - Use as your own risk.

View File

@ -1,19 +1,19 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud // @name Better xCloud
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 1.0 // @version 1.2
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
// @match https://www.xbox.com/*/play* // @match https://www.xbox.com/*/play*
// @run-at document-start // @run-at document-start
// @grant none // @grant none
// @updateURL https://github.com/redphx/better-xcloud/raw/main/better-xcloud.user.js // @updateURL https://github.com/redphx/better-xcloud/releases/latest/download/better-xcloud.user.js
// @downloadURL https://github.com/redphx/better-xcloud/raw/main/better-xcloud.user.js // @downloadURL https://github.com/redphx/better-xcloud/releases/latest/download/better-xcloud.user.js
// ==/UserScript== // ==/UserScript==
'use strict'; 'use strict';
const SCRIPT_VERSION = '1.0'; const SCRIPT_VERSION = '1.2';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud'; const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
const SERVER_REGIONS = {}; const SERVER_REGIONS = {};
@ -30,6 +30,7 @@ class Preferences {
static get HIDE_DOTS_ICON() { return 'hide_dots_icon'; } static get HIDE_DOTS_ICON() { return 'hide_dots_icon'; }
static get REDUCE_ANIMATIONS() { return 'reduce_animations'; } static get REDUCE_ANIMATIONS() { return 'reduce_animations'; }
static get VIDEO_FILL_FULL_SCREEN() { return 'video_fill_full_screen'; }
static get VIDEO_BRIGHTNESS() { return 'video_brightness'; } static get VIDEO_BRIGHTNESS() { return 'video_brightness'; }
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'; }
@ -83,12 +84,18 @@ class Preferences {
'default': false, 'default': false,
}, },
{
'id': Preferences.VIDEO_FILL_FULL_SCREEN,
'label': 'Stretch video to full screen',
'default': false,
},
{ {
'id': Preferences.VIDEO_SATURATION, 'id': Preferences.VIDEO_SATURATION,
'label': 'Video saturation (%)', 'label': 'Video saturation (%)',
'default': 100, 'default': 100,
'min': 0, 'min': 0,
'max': 200, 'max': 150,
}, },
{ {
@ -96,7 +103,7 @@ class Preferences {
'label': 'Video contrast (%)', 'label': 'Video contrast (%)',
'default': 100, 'default': 100,
'min': 0, 'min': 0,
'max': 200, 'max': 150,
}, },
{ {
@ -104,7 +111,7 @@ class Preferences {
'label': 'Video brightness (%)', 'label': 'Video brightness (%)',
'default': 100, 'default': 100,
'min': 0, 'min': 0,
'max': 200, 'max': 150,
}, },
] ]
@ -243,14 +250,24 @@ function addCss() {
background-color: #06743f; background-color: #06743f;
} }
.better_xcloud_settings_color_bars { .better_xcloud_settings_preview_screen {
display: none; display: none;
width: 100%; aspect-ratio: 20/9;
aspect-ratio: 16/6; background: #1e1e1e;
margin-top: 10px; border-radius: 8px;
overflow: hidden;
max-height: 180px;
margin: 10px auto;
} }
.better_xcloud_settings_color_bars div { .better_xcloud_settings_preview_video {
display: flex;
aspect-ratio: 16/9;
height: 100%;
margin: auto;
}
.better_xcloud_settings_preview_video div {
flex: 1; flex: 1;
} }
@ -267,6 +284,9 @@ div[class*=NotFocusedDialog] {
height: 0px !important; height: 0px !important;
} }
#game-stream video {
visibility: hidden;
}
`; `;
// Reduce animations // Reduce animations
@ -290,6 +310,14 @@ div[class*=Grip-module__container] {
button[class*=GripHandle-module__container][aria-expanded=true] div[class*=Grip-module__container] { button[class*=GripHandle-module__container][aria-expanded=true] div[class*=Grip-module__container] {
visibility: visible; visibility: visible;
} }
button[class*=GripHandle-module__container][aria-expanded=false] {
background-color: transparent !important;
}
div[class*=StreamHUD-module__buttonsContainer] {
padding: 0px !important;
}
`; `;
} }
@ -508,6 +536,31 @@ function createElement(elmName, props = {}) {
} }
function generateVideoPreviewBox() {
const $screen = createElement('div', {'class': 'better_xcloud_settings_preview_screen'});
const $video = createElement('div', {'class': 'better_xcloud_settings_preview_video'});
const COLOR_BARS = [
'white',
'yellow',
'cyan',
'green',
'magenta',
'red',
'blue',
'black',
];
COLOR_BARS.forEach(color => {
$video.appendChild(createElement('div', {
style: `background-color: ${color}`,
}));
});
$screen.appendChild($video);
return $screen;
}
function injectSettingsButton($parent) { function injectSettingsButton($parent) {
if (!$parent) { if (!$parent) {
return; return;
@ -587,13 +640,7 @@ function injectSettingsButton($parent) {
} }
PREFS.set(e.target.getAttribute('data-key'), parseInt(e.target.value)); PREFS.set(e.target.getAttribute('data-key'), parseInt(e.target.value));
updateVideoPlayerPreview();
const filters = getVideoPlayerFilterStyle();
const $elm = document.querySelector('.better_xcloud_settings_color_bars');
$elm.style.display = 'flex';
$elm.style.filter = filters;
updateVideoPlayerCss();
}); });
} }
} else { } else {
@ -602,8 +649,13 @@ function injectSettingsButton($parent) {
type: 'checkbox', type: 'checkbox',
'data-key': setting.id, 'data-key': setting.id,
}); });
$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);
if (setting.id == Preferences.VIDEO_FILL_FULL_SCREEN) {
updateVideoPlayerPreview();
}
}); });
setting.value = PREFS.get(setting.id); setting.value = PREFS.get(setting.id);
@ -618,25 +670,8 @@ function injectSettingsButton($parent) {
$wrapper.appendChild($elm); $wrapper.appendChild($elm);
} }
const COLOR_BARS = [ const $videoPreview = generateVideoPreviewBox();
'white', $wrapper.appendChild($videoPreview);
'yellow',
'cyan',
'green',
'magenta',
'red',
'blue',
'black',
];
const $colorBars = CE('div', {'class': 'better_xcloud_settings_color_bars'});
COLOR_BARS.forEach(color => {
$colorBars.appendChild(CE('div', {
style: `background-color: ${color}`,
}));
});
$wrapper.appendChild($colorBars);
const $reloadBtn = CE('button', {'class': 'setting_button'}, 'Reload page to reflect changes'); const $reloadBtn = CE('button', {'class': 'setting_button'}, 'Reload page to reflect changes');
$reloadBtn.addEventListener('click', e => window.location.reload()); $reloadBtn.addEventListener('click', e => window.location.reload());
@ -678,21 +713,45 @@ function updateVideoPlayerCss() {
let filters = getVideoPlayerFilterStyle(); let filters = getVideoPlayerFilterStyle();
let css = ''; let css = '';
if (filters) { if (filters) {
css = `#game-stream video {filter: ${filters}}`; css += `filter: ${filters} !important;`;
}
if (PREFS.get(Preferences.VIDEO_FILL_FULL_SCREEN)) {
css += 'object-fit: fill !important;';
}
if (css) {
css = `#game-stream video {${css}}`;
} }
$elm.textContent = css; $elm.textContent = css;
} }
function updateVideoPlayerPreview() {
const $screen = document.querySelector('.better_xcloud_settings_preview_screen');
$screen.style.display = 'block';
const filters = getVideoPlayerFilterStyle();
const $video = document.querySelector('.better_xcloud_settings_preview_video');
$video.style.filter = filters;
if (PREFS.get(Preferences.VIDEO_FILL_FULL_SCREEN)) {
$video.style.height = 'auto';
} else {
$video.style.height = '100%';
}
updateVideoPlayerCss();
}
function checkHeader() { function checkHeader() {
const $button = document.querySelector('#PageContent header .better_xcloud_settings_button'); const $button = document.querySelector('#PageContent header .better_xcloud_settings_button');
if (!$button) { if (!$button) {
const $rightHeader = document.querySelector('#PageContent header div[class*=EdgewaterHeader-module__rightSectionSpacing]'); const $rightHeader = document.querySelector('#PageContent header div[class*=EdgewaterHeader-module__rightSectionSpacing]');
injectSettingsButton($rightHeader); injectSettingsButton($rightHeader);
updateVideoPlayerCss();
} }
} }
@ -717,19 +776,110 @@ function watchHeader() {
} }
function injectVideoSettingsButton() {
const $screen = document.querySelector('#PageContent section[class*=PureScreens]');
if (!$screen) {
return;
}
if ($screen.xObserving) {
return;
}
$screen.xObserving = true;
const $quickBar = document.querySelector('.better_xcloud_quick_settings_bar');
const $parent = $screen.parentElement;
const hideQuickBarFunc = e => {
if (e.target != $parent && e.target.id !== 'MultiTouchSurface') {
return;
}
// Hide Quick settings bar
$quickBar.style.display = 'none';
$parent.removeEventListener('click', hideQuickBarFunc);
$parent.removeEventListener('touchend', hideQuickBarFunc);
if (e.target.id === 'MultiTouchSurface') {
e.target.removeEventListener('touchstart', hideQuickBarFunc);
}
}
const observer = new MutationObserver(mutationList => {
mutationList.forEach(item => {
if (item.type !== 'childList') {
return;
}
item.addedNodes.forEach(node => {
if (!node.className || !node.className.startsWith('StreamMenu')) {
return;
}
const id = 'better-xcloud-video-settings-btn';
let $wrapper = document.getElementById('#' + id);
if ($wrapper) {
return;
}
const $orgButton = node.querySelector('div > div > button');
if (!$orgButton) {
return;
}
// Clone other button
const $button = $orgButton.cloneNode(true);
$button.setAttribute('aria-label', 'Video settings');
$button.querySelector('div[class*=label]').textContent = 'Video settings';
// Credit: https://www.iconfinder.com/iconsets/user-interface-outline-27
const SVG_ICON = '<path d="M8 2c-1.293 0-2.395.843-2.812 2H3a1 1 0 1 0 0 2h2.186C5.602 7.158 6.706 8 8 8s2.395-.843 2.813-2h10.188a1 1 0 1 0 0-2H10.813C10.395 2.843 9.293 2 8 2zm0 2c.564 0 1 .436 1 1s-.436 1-1 1-1-.436-1-1 .436-1 1-1zm7 5c-1.293 0-2.395.843-2.812 2H3a1 1 0 1 0 0 2h9.186c.417 1.158 1.521 2 2.814 2s2.395-.843 2.813-2H21a1 1 0 1 0 0-2h-3.187c-.418-1.157-1.52-2-2.813-2zm0 2c.564 0 1 .436 1 1s-.436 1-1 1-1-.436-1-1 .436-1 1-1zm-7 5c-1.293 0-2.395.843-2.812 2H3a1 1 0 1 0 0 2h2.188c.417 1.157 1.519 2 2.813 2s2.398-.842 2.814-2H21a1 1 0 1 0 0-2H10.812c-.417-1.157-1.519-2-2.812-2zm0 2c.564 0 1 .436 1 1s-.436 1-1 1-1-.436-1-1 .436-1 1-1z"/>';
const $svg = $button.querySelector('svg');
$svg.innerHTML = SVG_ICON;
$svg.setAttribute('viewBox', '0 0 24 24');
$button.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
// Show Quick settings bar
$quickBar.style.display = 'flex';
// Close HUD
document.querySelector('button[class*=StreamMenu-module__backButton]').click();
$parent.addEventListener('click', hideQuickBarFunc);
$parent.addEventListener('touchend', hideQuickBarFunc);
const $touchSurface = document.querySelector('#MultiTouchSurface');
if ($touchSurface) {
$touchSurface.addEventListener('touchstart', hideQuickBarFunc);
}
});
$orgButton.parentElement.insertBefore($button, $orgButton.parentElement.firstChild);
});
});
});
observer.observe($screen, {subtree: true, childList: true});
}
function patchVideoApi() { function patchVideoApi() {
const PREF_SKIP_SPLASH_VIDEO = PREFS.get(Preferences.SKIP_SPLASH_VIDEO); const PREF_SKIP_SPLASH_VIDEO = PREFS.get(Preferences.SKIP_SPLASH_VIDEO);
// Do nothing if the "Skip splash video" setting is off
if (!PREF_SKIP_SPLASH_VIDEO) { // Show video player when it's ready
return; var showFunc;
showFunc = function() {
this.style.visibility = 'visible';
this.removeEventListener('playing', showFunc);
} }
HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play; HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function() { HTMLMediaElement.prototype.play = function() {
if (!this.className.startsWith('XboxSplashVideo')) { if (PREF_SKIP_SPLASH_VIDEO && this.className.startsWith('XboxSplashVideo')) {
return this.orgPlay.apply(this);
}
this.volume = 0; this.volume = 0;
this.style.display = 'none'; this.style.display = 'none';
this.dispatchEvent(new Event('ended')); this.dispatchEvent(new Event('ended'));
@ -737,42 +887,215 @@ function patchVideoApi() {
return { return {
catch: () => {}, catch: () => {},
}; };
}
this.addEventListener('playing', showFunc);
injectVideoSettingsButton();
return this.orgPlay.apply(this);
}; };
} }
function numberPicker(key) {
let value = PREFS.get(key);
let $text, $decBtn, $incBtn;
const MIN = 0;
const MAX= 150;
const CE = createElement;
const $wrapper = CE('div', {},
$decBtn = CE('button', {'data-type': 'dec'}, '-'),
$text = CE('span', {}, value),
$incBtn = CE('button', {'data-type': 'inc'}, '+'),
);
let interval;
let isHolding = false;
const onClick = e => {
if (isHolding) {
e.preventDefault();
isHolding = false;
return;
}
const btnType = e.target.getAttribute('data-type');
if (btnType === 'dec') {
value = (value <= MIN) ? MIN : value - 1;
} else {
value = (value >= MAX) ? MAX : value + 1;
}
$text.textContent = value;
PREFS.set(key, value);
updateVideoPlayerCss();
isHolding = false;
}
const onMouseDown = e => {
isHolding = true;
const args = arguments;
interval = setInterval(() => {
const event = new Event('click');
event.arguments = args;
e.target.dispatchEvent(event);
}, 200);
};
const onMouseUp = e => {
clearInterval(interval);
isHolding = false;
};
$decBtn.addEventListener('click', onClick);
$decBtn.addEventListener('mousedown', onMouseDown);
$decBtn.addEventListener('mouseup', onMouseUp);
$decBtn.addEventListener('touchstart', onMouseDown);
$decBtn.addEventListener('touchend', onMouseUp);
$incBtn.addEventListener('click', onClick);
$incBtn.addEventListener('mousedown', onMouseDown);
$incBtn.addEventListener('mouseup', onMouseUp);
$incBtn.addEventListener('touchstart', onMouseDown);
$incBtn.addEventListener('touchend', onMouseUp);
return $wrapper;
}
function setupVideoSettingsBar() {
const CE = createElement;
let $stretchInp;
const $wrapper = CE('div', {'class': 'better_xcloud_quick_settings_bar'},
CE('div', {},
CE('label', {'for': 'better-xcloud-quick-setting-stretch'}, 'Stretch Video'),
$stretchInp = CE('input', {'id': 'better-xcloud-quick-setting-stretch', 'type': 'checkbox'})),
CE('div', {},
CE('label', {}, 'Saturation'),
numberPicker(Preferences.VIDEO_SATURATION)),
CE('div', {},
CE('label', {}, 'Contrast'),
numberPicker(Preferences.VIDEO_CONTRAST)),
CE('div', {},
CE('label', {}, 'Brightness'),
numberPicker(Preferences.VIDEO_BRIGHTNESS))
);
$stretchInp.checked = PREFS.get(Preferences.VIDEO_FILL_FULL_SCREEN);
$stretchInp.addEventListener('change', e => {
PREFS.set(Preferences.VIDEO_FILL_FULL_SCREEN, e.target.checked);
updateVideoPlayerCss();
});
const $style = CE('style', {}, `
.better_xcloud_quick_settings_bar {
display: none;
user-select: none;
position: fixed;
bottom: 20px;
left: 50%;
transform: translate(-50%, 0);
z-index: 9999;
padding: 20px;
width: 620px;
background: #1a1b1e;
color: #fff;
border-radius: 8px;
font-weight: 400;
font-size: 16px;
font-family: Bahnschrift, Arial, Helvetica, sans-serif;
text-align: center;
}
.better_xcloud_quick_settings_bar *:focus {
outline: none !important;
}
.better_xcloud_quick_settings_bar > div {
flex: 1;
}
.better_xcloud_quick_settings_bar label {
font-size: 24px;
display: block;
}
.better_xcloud_quick_settings_bar input {
width: 24px;
height: 24px;
}
.better_xcloud_quick_settings_bar button {
border: none;
width: 24px;
height: 24px;
margin: 0 8px;
line-height: 24px;
background-color: #fff;
color: #000;
}
@media (hover: hover) {
.better_xcloud_quick_settings_bar button:hover {
background-color: #414141;
color: white;
}
}
.better_xcloud_quick_settings_bar button:active {
background-color: #414141;
color: white;
}
.better_xcloud_quick_settings_bar span {
display: inline-block;
width: 26px;
}
`);
document.documentElement.appendChild($wrapper);
document.documentElement.appendChild($style);
}
function patchHistoryMethod(type) { function patchHistoryMethod(type) {
var orig = window.history[type]; var orig = window.history[type];
return function(...args) { return function(...args) {
const rv = orig.apply(this, arguments);
const event = new Event('xcloud_popstate'); const event = new Event('xcloud_popstate');
event.arguments = args; event.arguments = args;
window.dispatchEvent(event); window.dispatchEvent(event);
return rv; return orig.apply(this, arguments);
}; };
}; };
function hideSettingsOnPageChange() { function hideUiOnPageChange() {
const $settings = document.querySelector('.better_xcloud_settings'); const $settings = document.querySelector('.better_xcloud_settings');
if ($settings) { if ($settings) {
$settings.classList.add('better_xcloud_settings_gone'); $settings.classList.add('better_xcloud_settings_gone');
} }
const $quickBar = document.querySelector('.better_xcloud_quick_settings_bar');
if ($quickBar) {
$quickBar.style.display = 'none';
}
} }
// Hide Settings UI when navigate to another page // Hide Settings UI when navigate to another page
window.addEventListener('xcloud_popstate', hideSettingsOnPageChange); window.addEventListener('xcloud_popstate', hideUiOnPageChange);
window.addEventListener('popstate', hideSettingsOnPageChange); window.addEventListener('popstate', hideUiOnPageChange);
// Make pushState/replaceState methods dispatch "xcloud_popstate" event // Make pushState/replaceState methods dispatch "xcloud_popstate" event
window.history.pushState = patchHistoryMethod('pushState'); window.history.pushState = patchHistoryMethod('pushState');
window.history.replaceState = patchHistoryMethod('replaceState'); window.history.replaceState = patchHistoryMethod('replaceState');
// Add additional CSS
addCss();
// Clear data of window.navigator.userAgentData, force Xcloud to detect browser based on User-Agent header // Clear data of window.navigator.userAgentData, force Xcloud to detect browser based on User-Agent header
Object.defineProperty(window.navigator, 'userAgentData', {}); Object.defineProperty(window.navigator, 'userAgentData', {});
@ -787,6 +1110,11 @@ interceptHttpRequests();
patchVideoApi(); patchVideoApi();
// Setup UI
addCss();
updateVideoPlayerCss();
setupVideoSettingsBar();
// Workaround for Hermit browser // Workaround for Hermit browser
var onLoadTriggered = false; var onLoadTriggered = false;
window.onload = () => { window.onload = () => {