mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-28 18:31:44 +02:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
a318db4ec2 | |||
aaa8348984 | |||
2cea30cf16 | |||
0fe99f8f2d | |||
0cb09bb455 | |||
14cf9d5877 | |||
b96b115182 | |||
766c5b8682 | |||
5f9564de45 | |||
3a7dea6c75 | |||
8dd17bb021 | |||
e63f998f5a | |||
0685cd8038 | |||
f246faf796 | |||
68ef703bdb | |||
f9d683a2e4 | |||
fec2c4a22a | |||
c3df18d7be | |||
9971fe6c0c | |||
cc11bcea41 | |||
048d085e91 | |||
ed32044480 | |||
32c087966b | |||
e4ad010e0a | |||
d2debc0623 | |||
0c3d8519e1 | |||
e323bd5b47 | |||
ce54aa4580 | |||
d29447173c | |||
2f3a3167e1 | |||
e17459934b | |||
ecf9b414ba | |||
f78bd31b58 | |||
da54bd5302 | |||
5479eeb66f | |||
a9eb70eaae | |||
0e2066e461 | |||
2ee2469a10 | |||
9056b5926f | |||
730799821b | |||
fed7d489f4 |
121
README.md
121
README.md
@ -1,36 +1,93 @@
|
||||
# Better xCloud
|
||||
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 mobile users, but it should work great on desktop too.
|
||||
|
||||
## Features:
|
||||
Give this project a 🌟 if you like it. Thank you.
|
||||
|
||||
<img width="474" alt="image" src="https://github.com/redphx/better-xcloud/assets/96280/2793d404-3185-4c91-a500-dde362c661dd">
|
||||
## Features
|
||||
|
||||
- Switch region of streaming server.
|
||||
- Prefer IPv6 streaming server (might improve latency).
|
||||
- Force HD stream by disabling bandwidth checking -> xCloud always tries to use the best possible quality.
|
||||
- Skip Xbox splash video.
|
||||
- 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).
|
||||
- Hide footer and other UI elements.
|
||||
- Reduce UI animations (the smooth scrolling cannot be disabled).
|
||||
- Disable social features (friends, chat...).
|
||||
- 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.
|
||||
<img width="500" alt="Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/4f60c2e6-9706-4326-940f-f46998177633">
|
||||
<img width="500" alt="Video Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/130aa870-6938-4604-9e23-45e217b800cc">
|
||||
|
||||
## How to use:
|
||||
- **Switch region of streaming server**
|
||||
> Connect to another server instead of the default one. Check [FAQ section](#faq) for some notes.
|
||||
- **Prefer IPv6 streaming server**
|
||||
> Might reduce latency
|
||||
- **Force high quality stream**
|
||||
> Force xCloud to use the best streaming codec profile (same as desktop). You don't have to change User-Agent anymore.
|
||||
> Some browsers (like Firefox) don't support this feature. Check [the full list](https://caniuse.com/?search=setCodecPreferences).
|
||||
> If you're on Android and want to use it, try [Hermit](https://hermit.chimbori.com).
|
||||
- **Disable bandwidth checking**
|
||||
> xCloud won't reduce quality when the internet speed is slow
|
||||
- **Skip Xbox splash video**
|
||||
> Save 3 seconds
|
||||
- **Hide Dots icon while playing**
|
||||
> You can still click on it, but it doesn't block the screen anymore
|
||||
- **Reduce UI animations**
|
||||
> Disable `transition` CSS property in some elements. The smooth scrolling cannot be disabled.
|
||||
- **Stretch video to full sctreen**
|
||||
> Useful when you don't have a 16:9 screen
|
||||
- **Adjust video filters**
|
||||
> Brightness/Contrast/Saturation
|
||||
- **Disable social features**
|
||||
> Features like friends, chat... Disable these will make the page load faster.
|
||||
- **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.
|
||||
- **Hide footer and other UI elements**
|
||||
|
||||
## How to use
|
||||
1. Install [Tampermonkey extension](https://www.tampermonkey.net/) on suppported browsers. It's also available for Firefox on Android.
|
||||
2. Install **Better xCloud**:
|
||||
- [Directly on Github](https://github.com/redphx/better-xcloud/releases/latest/download/better-xcloud.user.js)
|
||||
- [Stable version](https://github.com/redphx/better-xcloud/releases/latest/download/better-xcloud.user.js)
|
||||
- [Dev version](https://github.com/redphx/better-xcloud/raw/main/better-xcloud.user.js)
|
||||
4. Refresh [xCloud web page](https://www.xbox.com/play/).
|
||||
5. Click on the new "SERVER NAME" button next to your profile picture to adjust settings.
|
||||
6. Optional but recommended: change your browser's User-Agent. Check the [User-Agent section](#user-agent) below for more info.
|
||||
7. Don't forget to reload the page after changing settings.
|
||||
5. Click on the new "SERVER NAME" button next to your profile picture to adjust settings.
|
||||
6. Don't forget to enable auto updating for the script in Tampermonkey.
|
||||
|
||||
To update manually, just install the script again (you won't lose your settings).
|
||||
|
||||
## Compatibility
|
||||
✅ = confirmed to be working
|
||||
❓ = not yet tested
|
||||
❌ = not supported (mostly because of lacking Userscript/extension support)
|
||||
⚠️ = see custom notes
|
||||
| | Desktop | Android | iOS |
|
||||
|----------------------------------------|------------------|------------------|-----------------|
|
||||
| Chrome/Edge/Chromium variants | ✅ | ❌ | ❌ |
|
||||
| Firefox | ✅ | ✅ | ❌ |
|
||||
| Safari | ✅<sup>(1)</sup> | ❌ | ✅<sup>(2)</sup> |
|
||||
| [Hermit](https://hermit.chimbori.com) | ❌ | ⚠️<sup>(3)</sup> | ❌ |
|
||||
|
||||
Don't see your browser in the table? If it supports Tampermonkey/Userscript then the answer is likely **"YES"**.
|
||||
|
||||
<sup>1, 2</sup> Requires [Userscripts app](https://apps.apple.com/us/app/userscripts/id1463298887).
|
||||
<sup>3</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 Userscript (premium features, only $1.99) without having to install anything else. I built **Better xCloud** just so I could use it with Hermit.
|
||||
|
||||
## FAQ
|
||||
1. **Will I get banned for using this?**
|
||||
I think it's very unlikely that you'll get banned for using this. Most of the features only affect client-side, except for switching region of streaming server (you'll connect to another server instead of the default one). If you want to be safe just avoid using that. As always, use as your own risk.
|
||||
|
||||
2. **Why is it an Userscript and not 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 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.
|
||||
|
||||
4. **Can I use this with the Xbox Android app?**
|
||||
No you can't. You'll have to modify the app.
|
||||
|
||||
5. **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
|
||||
- [n-thumann/xbox-cloud-server-selector](https://github.com/n-thumann/xbox-cloud-server-selector) for the idea of IPv6 feature
|
||||
- Icons by [Adam Design](https://www.iconfinder.com/iconsets/user-interface-outline-27)
|
||||
|
||||
## User-Agent
|
||||
Optional, as changing User-Agent won't guarantee a better streaming experience, but it's worth a try. You might need to install an external extension to do that.
|
||||
> You're no longer needed to change User-Agent since you can just use the **Force high quality stream** setting.
|
||||
> I'll still keep this section because it has some interesting info.
|
||||
|
||||
It's recommended to change User-Agent to:
|
||||
Change User-Agent to:
|
||||
```
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.67
|
||||
```
|
||||
@ -42,28 +99,6 @@ Other options (only do one of these):
|
||||
- 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.
|
||||
|
||||
## Tested on:
|
||||
- Chrome on macOS.
|
||||
- Firefox for Android with Tampermonkey add-on.
|
||||
- *(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.
|
||||
|
||||
## FAQ
|
||||
1. **Why is it an Userscript and not extension?**
|
||||
It's because not many browsers on Android support installing extensions (and not all extensions can be installed).
|
||||
|
||||
2. **I see "???" button instead of 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. **Can I use this with the Xbox Android 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
|
||||
**Better xCloud** is inspired by these projects:
|
||||
- [n-thumann/xbox-cloud-server-selector](https://github.com/n-thumann/xbox-cloud-server-selector)
|
||||
|
||||
## Disclaimers
|
||||
- Use as your own risk.
|
||||
- This project is not affiliated with Xbox in any way. All Xbox logos/icons/trademarks are copyright of their respective owners.
|
||||
|
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 1.1
|
||||
// @version 1.3.1
|
||||
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
||||
// @author redphx
|
||||
// @license MIT
|
||||
@ -13,7 +13,7 @@
|
||||
// ==/UserScript==
|
||||
'use strict';
|
||||
|
||||
const SCRIPT_VERSION = '1.1';
|
||||
const SCRIPT_VERSION = '1.3.1';
|
||||
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
|
||||
|
||||
const SERVER_REGIONS = {};
|
||||
@ -22,6 +22,7 @@ const SERVER_REGIONS = {};
|
||||
class Preferences {
|
||||
static get SERVER_REGION() { return 'server_region'; }
|
||||
static get PREFER_IPV6_SERVER() { return 'prefer_ipv6_server'; }
|
||||
static get USE_DESKTOP_CODEC() { return 'use_desktop_codec'; }
|
||||
|
||||
static get BLOCK_TRACKING() { return 'block_tracking'; }
|
||||
static get BLOCK_SOCIAL_FEATURES() { return 'block_social_features'; }
|
||||
@ -48,9 +49,15 @@ class Preferences {
|
||||
'default': false,
|
||||
},
|
||||
|
||||
{
|
||||
'id': Preferences.USE_DESKTOP_CODEC,
|
||||
'label': 'Force high quality stream (same as desktop)',
|
||||
'default': false,
|
||||
},
|
||||
|
||||
{
|
||||
'id': Preferences.DISABLE_BANDWIDTH_CHECKING,
|
||||
'label': 'Force HD stream',
|
||||
'label': 'Disable bandwitdh checking',
|
||||
'default': false,
|
||||
},
|
||||
|
||||
@ -88,6 +95,7 @@ class Preferences {
|
||||
'id': Preferences.VIDEO_FILL_FULL_SCREEN,
|
||||
'label': 'Stretch video to full screen',
|
||||
'default': false,
|
||||
'hidden': true,
|
||||
},
|
||||
|
||||
{
|
||||
@ -95,7 +103,8 @@ class Preferences {
|
||||
'label': 'Video saturation (%)',
|
||||
'default': 100,
|
||||
'min': 0,
|
||||
'max': 200,
|
||||
'max': 150,
|
||||
'hidden': true,
|
||||
},
|
||||
|
||||
{
|
||||
@ -103,7 +112,8 @@ class Preferences {
|
||||
'label': 'Video contrast (%)',
|
||||
'default': 100,
|
||||
'min': 0,
|
||||
'max': 200,
|
||||
'max': 150,
|
||||
'hidden': true,
|
||||
},
|
||||
|
||||
{
|
||||
@ -111,7 +121,8 @@ class Preferences {
|
||||
'label': 'Video brightness (%)',
|
||||
'default': 100,
|
||||
'min': 0,
|
||||
'max': 200,
|
||||
'max': 150,
|
||||
'hidden': true,
|
||||
},
|
||||
]
|
||||
|
||||
@ -246,29 +257,14 @@ function addCss() {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.better_xcloud_settings_wrapper .setting_button:hover {
|
||||
background-color: #06743f;
|
||||
@media (hover: hover) {
|
||||
.better_xcloud_settings_wrapper .setting_button:hover {
|
||||
background-color: #00753c;
|
||||
}
|
||||
}
|
||||
|
||||
.better_xcloud_settings_preview_screen {
|
||||
display: none;
|
||||
aspect-ratio: 20/9;
|
||||
background: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
max-height: 180px;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.better_xcloud_settings_preview_video {
|
||||
display: flex;
|
||||
aspect-ratio: 16/9;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.better_xcloud_settings_preview_video div {
|
||||
flex: 1;
|
||||
.better_xcloud_settings_wrapper .setting_button:active {
|
||||
background-color: #00753c;
|
||||
}
|
||||
|
||||
/* Hide UI elements */
|
||||
@ -284,6 +280,9 @@ div[class*=NotFocusedDialog] {
|
||||
height: 0px !important;
|
||||
}
|
||||
|
||||
#game-stream video {
|
||||
visibility: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
// Reduce animations
|
||||
@ -307,6 +306,14 @@ div[class*=Grip-module__container] {
|
||||
button[class*=GripHandle-module__container][aria-expanded=true] div[class*=Grip-module__container] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
button[class*=GripHandle-module__container][aria-expanded=false] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
div[class*=StreamHUD-module__buttonsContainer] {
|
||||
padding: 0px !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -525,31 +532,6 @@ 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) {
|
||||
if (!$parent) {
|
||||
return;
|
||||
@ -582,8 +564,11 @@ function injectSettingsButton($parent) {
|
||||
$wrapper.appendChild($title);
|
||||
|
||||
for (let setting of Preferences.SETTINGS) {
|
||||
let $control;
|
||||
if (setting.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let $control;
|
||||
if (setting.id === Preferences.SERVER_REGION) {
|
||||
$control = CE('select', {id: 'xcloud_setting_' + setting.id});
|
||||
$control.addEventListener('change', e => {
|
||||
@ -605,33 +590,6 @@ function injectSettingsButton($parent) {
|
||||
|
||||
$control.appendChild($option);
|
||||
}
|
||||
} else if (typeof setting.default === 'number') {
|
||||
$control = CE('input', {
|
||||
id: 'xcloud_setting_' + setting.id,
|
||||
type: 'number',
|
||||
size: 5,
|
||||
'data-key': setting.id,
|
||||
});
|
||||
|
||||
if ('min' in setting) {
|
||||
$control.setAttribute('min', setting.min);
|
||||
}
|
||||
|
||||
if ('max' in setting) {
|
||||
$control.setAttribute('max', setting.max);
|
||||
}
|
||||
|
||||
$control.value = PREFS.get(setting.id);
|
||||
if (setting.id.startsWith('video_')) {
|
||||
$control.addEventListener('change', e => {
|
||||
if (!e.target.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
PREFS.set(e.target.getAttribute('data-key'), parseInt(e.target.value));
|
||||
updateVideoPlayerPreview();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$control = CE('input', {
|
||||
id: 'xcloud_setting_' + setting.id,
|
||||
@ -649,6 +607,12 @@ function injectSettingsButton($parent) {
|
||||
|
||||
setting.value = PREFS.get(setting.id);
|
||||
$control.checked = setting.value;
|
||||
|
||||
if (setting.id === Preferences.USE_DESKTOP_CODEC && !hasRtcSetCodecPreferencesSupport()) {
|
||||
$control.disabled = true;
|
||||
$control.checked = false;
|
||||
$control.title = 'Not supported by this browser';
|
||||
}
|
||||
}
|
||||
|
||||
const $elm = CE('div', {'class': 'setting_row'},
|
||||
@ -659,9 +623,6 @@ function injectSettingsButton($parent) {
|
||||
$wrapper.appendChild($elm);
|
||||
}
|
||||
|
||||
const $videoPreview = generateVideoPreviewBox();
|
||||
$wrapper.appendChild($videoPreview);
|
||||
|
||||
const $reloadBtn = CE('button', {'class': 'setting_button'}, 'Reload page to reflect changes');
|
||||
$reloadBtn.addEventListener('click', e => window.location.reload());
|
||||
$wrapper.appendChild($reloadBtn);
|
||||
@ -741,8 +702,6 @@ function checkHeader() {
|
||||
if (!$button) {
|
||||
const $rightHeader = document.querySelector('#PageContent header div[class*=EdgewaterHeader-module__rightSectionSpacing]');
|
||||
injectSettingsButton($rightHeader);
|
||||
|
||||
updateVideoPlayerCss();
|
||||
}
|
||||
}
|
||||
|
||||
@ -761,68 +720,370 @@ function watchHeader() {
|
||||
|
||||
timeout = setTimeout(checkHeader, 2000);
|
||||
});
|
||||
observer.observe($header, { subtree: true, childList: true});
|
||||
observer.observe($header, {subtree: true, childList: true});
|
||||
|
||||
checkHeader();
|
||||
}
|
||||
|
||||
|
||||
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() {
|
||||
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) {
|
||||
return;
|
||||
|
||||
// Show video player when it's ready
|
||||
var showFunc;
|
||||
showFunc = function() {
|
||||
this.style.visibility = 'visible';
|
||||
this.removeEventListener('playing', showFunc);
|
||||
}
|
||||
|
||||
HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play;
|
||||
HTMLMediaElement.prototype.play = function() {
|
||||
if (!this.className.startsWith('XboxSplashVideo')) {
|
||||
return this.orgPlay.apply(this);
|
||||
if (PREF_SKIP_SPLASH_VIDEO && this.className.startsWith('XboxSplashVideo')) {
|
||||
this.volume = 0;
|
||||
this.style.display = 'none';
|
||||
this.dispatchEvent(new Event('ended'));
|
||||
|
||||
return {
|
||||
catch: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
this.volume = 0;
|
||||
this.style.display = 'none';
|
||||
this.dispatchEvent(new Event('ended'));
|
||||
this.addEventListener('playing', showFunc);
|
||||
injectVideoSettingsButton();
|
||||
|
||||
return {
|
||||
catch: () => {},
|
||||
};
|
||||
return this.orgPlay.apply(this);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function hasRtcSetCodecPreferencesSupport() {
|
||||
return (typeof RTCRtpTransceiver !== 'undefined' && 'setCodecPreferences' in RTCRtpTransceiver.prototype)
|
||||
}
|
||||
|
||||
function patchRtcCodecs() {
|
||||
if (!PREFS.get(Preferences.USE_DESKTOP_CODEC)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasRtcSetCodecPreferencesSupport()) {
|
||||
console.log('[Better xCloud] RTCRtpTransceiver.setCodecPreferences() is not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
RTCRtpTransceiver.prototype.orgSetCodecPreferences = RTCRtpTransceiver.prototype.setCodecPreferences;
|
||||
RTCRtpTransceiver.prototype.setCodecPreferences = function(codecs) {
|
||||
// Use the same codecs as desktop
|
||||
codecs = [
|
||||
{
|
||||
'clockRate': 90000,
|
||||
'mimeType': 'video/H264',
|
||||
'sdpFmtpLine': 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f',
|
||||
},
|
||||
{
|
||||
'clockRate': 90000,
|
||||
'mimeType': 'video/H264',
|
||||
'sdpFmtpLine': 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f',
|
||||
}
|
||||
].concat(codecs);
|
||||
this.orgSetCodecPreferences(codecs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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: 20px;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.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: #515151;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@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: 40px;
|
||||
font-weight: bold;
|
||||
font-family: Consolas, "Courier New", Courier, monospace;
|
||||
}
|
||||
`);
|
||||
|
||||
document.documentElement.appendChild($wrapper);
|
||||
document.documentElement.appendChild($style);
|
||||
}
|
||||
|
||||
|
||||
function patchHistoryMethod(type) {
|
||||
var orig = window.history[type];
|
||||
return function(...args) {
|
||||
const rv = orig.apply(this, arguments);
|
||||
|
||||
const event = new Event('xcloud_popstate');
|
||||
event.arguments = args;
|
||||
window.dispatchEvent(event);
|
||||
|
||||
return rv;
|
||||
return orig.apply(this, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function hideSettingsOnPageChange() {
|
||||
function hideUiOnPageChange() {
|
||||
const $settings = document.querySelector('.better_xcloud_settings');
|
||||
if ($settings) {
|
||||
$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
|
||||
window.addEventListener('xcloud_popstate', hideSettingsOnPageChange);
|
||||
window.addEventListener('popstate', hideSettingsOnPageChange);
|
||||
window.addEventListener('xcloud_popstate', hideUiOnPageChange);
|
||||
window.addEventListener('popstate', hideUiOnPageChange);
|
||||
// Make pushState/replaceState methods dispatch "xcloud_popstate" event
|
||||
window.history.pushState = patchHistoryMethod('pushState');
|
||||
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
|
||||
Object.defineProperty(window.navigator, 'userAgentData', {});
|
||||
|
||||
@ -833,10 +1094,17 @@ if (PREFS.get(Preferences.DISABLE_BANDWIDTH_CHECKING)) {
|
||||
});
|
||||
}
|
||||
|
||||
patchRtcCodecs();
|
||||
|
||||
interceptHttpRequests();
|
||||
|
||||
patchVideoApi();
|
||||
|
||||
// Setup UI
|
||||
addCss();
|
||||
updateVideoPlayerCss();
|
||||
setupVideoSettingsBar();
|
||||
|
||||
// Workaround for Hermit browser
|
||||
var onLoadTriggered = false;
|
||||
window.onload = () => {
|
||||
|
Reference in New Issue
Block a user