mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-28 18:31:44 +02:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
343e243c77 | |||
0722e02ab0 | |||
c520fde438 | |||
2f4396d948 | |||
88d46581bf | |||
0cd6106957 | |||
43b932f5d6 | |||
8315cee7ec | |||
9d6db3ed37 | |||
e85fc9aa47 | |||
2d680d63a1 | |||
aefd9e9320 | |||
ecaead1522 | |||
f71152595f | |||
c633c81c90 | |||
c76cf83c58 | |||
5a5c65e7a2 | |||
da7ff64471 | |||
cd9f53a052 | |||
c782526c32 | |||
e8ad1f9350 |
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,28 +7,15 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Platform**
|
||||
- Device: [e.g. Phone, Laptop, Desktop, TV]
|
||||
- OS: [e.g. Windows, Android, iOS]
|
||||
- Browser: [e.g. Chrome, Kiwi]
|
||||
- Browser Version: [e.g. 100]
|
||||
- Better xCloud Version: [e.g. 1.10]
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
...
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Platform (please complete the following information):**
|
||||
- Device: [e.g. Phone/Laptop/Desktop/TV]
|
||||
- OS: [e.g. Android]
|
||||
- Browser: [e.g. Chrome, Kiwi]
|
||||
- Browser Version: [e.g. 100]
|
||||
- Better xCloud Version: [e.g. 1.4]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
**Screenshots/Videos**
|
||||
If applicable, add screenshots/videos to help explain your problem.
|
||||
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -7,14 +7,10 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
**I'm using:**
|
||||
- Device:
|
||||
- OS:
|
||||
- Browser:
|
||||
|
||||
**I want to suggest this feature:**
|
||||
...
|
||||
|
65
README.md
65
README.md
@ -1,9 +1,9 @@
|
||||
# Better xCloud
|
||||
Improve [Xbox Cloud Gaming (xCloud)](https://www.xbox.com/play/) experience on web browser.
|
||||
The main target of this script is mobile 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.
|
||||
|
||||
This script makes me spend more time with xCloud, and I hope the same thing happens to you.
|
||||
Give this project a 🌟 if you like it. Thank you 🙏.
|
||||
If you like this project please give it a 🌟. Thank you 🙏.
|
||||
|
||||
[](https://github.com/redphx/better-xcloud/releases)
|
||||
[](https://github.com/redphx/better-xcloud/stargazers)
|
||||
@ -11,10 +11,22 @@ Give this project a 🌟 if you like it. Thank you 🙏.
|
||||
[](https://github.com/redphx/better-xcloud/releases)
|
||||
-->
|
||||
|
||||
## Table of Contents
|
||||
- [**Features**](#features)
|
||||
- [**How to install**](#how-to-install)
|
||||
- [**Compatibility**](#compatibility)
|
||||
- [**Stream stats**](#stream-stats)
|
||||
- [**Capture screenshot**](#capture-screenshot)
|
||||
- [**FAQ**](#faq)
|
||||
- [**Donation**](#donation)
|
||||
- [**Acknowledgements**](#acknowledgements)
|
||||
- [**Disclaimers**](#disclaimers)
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
<img width="400" alt="Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/6e74fdaf-dc84-416a-a2bc-5e117a3717e3">
|
||||
<img width="400" alt="Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/65ee4193-c31d-46fb-b580-196614246ee6">
|
||||
|
||||
<br>
|
||||
<img width="600" alt="Stream HUD" src="https://github.com/redphx/better-xcloud/assets/96280/e30f6514-13ca-41c6-bff2-979573cff956">
|
||||
<br>
|
||||
@ -42,7 +54,7 @@ Give this project a 🌟 if you like it. Thank you 🙏.
|
||||
- **Prefer IPv6 server**
|
||||
> Might reduce latency.
|
||||
|
||||
### Stream quality
|
||||
### Stream
|
||||
- **Set target resolution**
|
||||
> By default you only get 1080p stream when playing on desktop.
|
||||
> This feature can give you 1080p stream even on mobile, without having to change User-Agent.
|
||||
@ -54,18 +66,26 @@ Give this project a 🌟 if you like it. Thank you 🙏.
|
||||
> Comparison video with the setting ON & OFF: https://youtu.be/-9PuBJJSgR4
|
||||
- **Disable bandwidth checking**
|
||||
> xCloud won't warn about slow connection speed.
|
||||
- **Enable microphone on game launch**
|
||||
> Automatically enable the mic when starting to play a game.
|
||||
- **Hide mouse cursor on idle**
|
||||
> Hide the mouse cursor after 3 seconds of not moving.
|
||||
|
||||
### Controller
|
||||
- **🔥 Touch controller**
|
||||
> Only for mobile (Android/iOS/iPadOS).
|
||||
|
||||
### 🔥 Touch controller
|
||||
- **Availability**
|
||||
> Only for devices with touch support (Android/iOS/iPadOS/...).
|
||||
> - **Default**: nothing change.
|
||||
> - **Off**: 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, etc.
|
||||
> - **All games**: enable touch controller support for all games. Games with custom layout won't be affected.
|
||||
> Double-tap anywhere at the bottom of the screen to show/hide the controller. Useful when you're viewing cutscenes.
|
||||
> 
|
||||
- **Button styles**
|
||||
> - Default
|
||||
> - Muted
|
||||
> - All white (only for standard/default controller)
|
||||
> <img width="400" alt="Button styles" src="https://github.com/redphx/better-xcloud/assets/96280/2bfef2b3-6712-4924-b067-c2312f8c8062">
|
||||
|
||||
- **Hide mouse cursor on idle**
|
||||
> Hide the mouse cursor after 3 seconds of not moving.
|
||||
|
||||
### UI
|
||||
- **Simplify Stream's menu**
|
||||
@ -93,7 +113,7 @@ Give this project a 🌟 if you like it. Thank you 🙏.
|
||||
- **Display stream's statuses**
|
||||
> Region/Server/Codecs/Resolution...
|
||||
> Current playtime of the session.
|
||||
> Current battery level.
|
||||
> Current battery level. Not working on [some browsers](https://caniuse.com/battery-status).
|
||||
> Estimated total data sent/received.
|
||||
|
||||
### Advanced features
|
||||
@ -105,8 +125,8 @@ Give this project a 🌟 if you like it. Thank you 🙏.
|
||||
|
||||
<sup>(\*)</sup> By default (for compatibility reasons) xCloud only uses high quality codec profile when you use Tizen TV or Chrome/Edge/Chromium browser on Chrome/MacOS. Enable this setting will give you the best experience no matter what platform & browser you're on.
|
||||
|
||||
## How to use
|
||||
1. Install [Tampermonkey extension](https://www.tampermonkey.net/) on suppported browsers. For Safari, use [Userscripts app](https://apps.apple.com/us/app/userscripts/id1463298887).
|
||||
## How to install
|
||||
1. Install [Tampermonkey extension](https://www.tampermonkey.net/) on suppported browsers. For Safari, use [Userscripts app](https://apps.apple.com/us/app/userscripts/id1463298887) (not working properly, see [#81](https://github.com/redphx/better-xcloud/issues/81)).
|
||||
2. Install **Better xCloud**:
|
||||
- [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)
|
||||
@ -117,7 +137,9 @@ Give this project a 🌟 if you like it. Thank you 🙏.
|
||||
|
||||
To update manually, just install the script again (you won't lose your settings).
|
||||
|
||||
## Tutorial videos
|
||||
⚠️⚠️⚠️ If you're using Kiwi Browser on Android, make sure to follow the steps correctly and install the script with Tampermonkey (not installing it as an extension), or else it won't work.
|
||||
|
||||
### Tutorial videos
|
||||
If you still have trouble installing **Better xCloud**, you can follow one of these tutorial videos:
|
||||
- 🇧🇷 [Tudo isso agora tem no xCloud!! (ChipTec)](https://youtu.be/zS8Zy0mYIbU?t=40)
|
||||
- 🇫🇷 [#Tuto Xbox Cloud Gaming : Ecran ultra large et adieu les bandes noires sur smartphone (Cloud Gaming France)](https://www.youtube.com/watch?v=5U05KoTdDHs)
|
||||
@ -140,11 +162,11 @@ If you still have trouble installing **Better xCloud**, you can follow one of th
|
||||
Don't see your browser in the table? If it supports Tampermonkey/Userscript then the answer is likely **"YES"**.
|
||||
|
||||
<sup>1</sup> Follow [this guide](https://support.mozilla.org/en-US/kb/find-and-install-add-ons-firefox-android) to install Tampermonkey on Firefox Android. Its Gamepad API doesn't work properly so it might not recognize your controller.
|
||||
<sup>2, 3</sup> Requires [Userscripts app](https://apps.apple.com/us/app/userscripts/id1463298887) (free & open source).
|
||||
<sup>2, 3</sup> Requires [Userscripts app](https://apps.apple.com/us/app/userscripts/id1463298887) (free & open-source). Check [this page](https://github.com/redphx/better-xcloud/wiki/Using-with-Safari) before using.
|
||||
<sup>4</sup> NOT RECOMMENDED at the moment since its Userscript implementation is not working properly (see https://github.com/redphx/better-xcloud/issues/5 for full details).
|
||||
|
||||
---
|
||||
- **Kiwi Browser** is the best choice on Android. All features work, it means you can get 1080p stream + high quality codec profile (the best possible quality).
|
||||
- **Kiwi Browser** is the best choice on Android. All features work, it means you can get 1080p stream + high-quality codec profile (the best possible quality).
|
||||
- **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
|
||||
@ -205,7 +227,10 @@ I think it's very unlikely that you'll get banned for using this. Most of the fe
|
||||
It's because not many browsers on Android support installing extensions (and not all extensions can be installed).
|
||||
|
||||
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.
|
||||
Think of this project as an unofficial beta version of xCloud.
|
||||
- **Better xCloud** doesn't have to worry about the compatibility much: if it doesn't work on this browser, it can just suggest you switch to another one. xCloud can't do the same.
|
||||
- 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.
|
||||
@ -214,7 +239,13 @@ No, you can't. You'll have to modify the app.
|
||||
No. The "Clarity Boost" feature uses an exclusive API (`Video.msVideoProcessing`) that's only available on Edge browser for desktop at the moment.
|
||||
|
||||
6. **Will it be able to request a lower FPS or increase the maximum bitrate (15Mbps) of the stream?**
|
||||
Sorry, no. The server decides all these settings.
|
||||
Sorry, no. The server decides all these settings.
|
||||
|
||||
7. **What's the meaning behind the name "Better xCloud"?**
|
||||
It's a reference to an Userscript called "better360" that I created many years ago. I regret not choosing the name "xCloud Enhancement Suite", or XES for short.
|
||||
|
||||
## Donation
|
||||
I'm doing this for fun, so you don't have to donate anything. You're already supporting me by using this script. Save that money toward your Xbox Game Pass Ultimate subscription 😄.
|
||||
|
||||
## User-Agent
|
||||
Moved to [wiki](https://github.com/redphx/better-xcloud/wiki/User‐Agent).
|
||||
|
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 1.10.1
|
||||
// @version 1.11
|
||||
// ==/UserScript==
|
||||
|
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 1.10.1
|
||||
// @version 1.11
|
||||
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
||||
// @author redphx
|
||||
// @license MIT
|
||||
@ -13,7 +13,79 @@
|
||||
// ==/UserScript==
|
||||
'use strict';
|
||||
|
||||
const SCRIPT_VERSION = '1.10.1';
|
||||
console.log(`[Better xCloud] readyState: ${document.readyState}`);
|
||||
|
||||
|
||||
// Quickly create a tree of elements without having to use innerHTML
|
||||
function createElement(elmName, props = {}) {
|
||||
const $elm = document.createElement(elmName);
|
||||
for (let key in props) {
|
||||
if (!props.hasOwnProperty(key) || $elm.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$elm.setAttribute(key, props[key]);
|
||||
}
|
||||
|
||||
for (let i = 2, size = arguments.length; i < size; i++) {
|
||||
const arg = arguments[i];
|
||||
const argType = typeof arg;
|
||||
|
||||
if (argType === 'string' || argType === 'number') {
|
||||
$elm.textContent = arg;
|
||||
} else if (arg) {
|
||||
$elm.appendChild(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return $elm;
|
||||
}
|
||||
|
||||
|
||||
const ENABLE_SAFARI_WORKAROUND = true;
|
||||
if (ENABLE_SAFARI_WORKAROUND && document.readyState !== 'loading') {
|
||||
// Stop loading
|
||||
window.stop();
|
||||
|
||||
// Show the reloading overlay
|
||||
const $elm = createElement('div', {'class': 'better-xcloud-reload-overlay'}, 'Failed to run Better xCloud. Retrying, please wait...');
|
||||
const css = `
|
||||
.better-xcloud-reload-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
background: #000000cc;
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
line-height: 100vh;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-family: "Segoe UI", SegoeUI, "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
`;
|
||||
document.documentElement.appendChild(createElement('style', {}, css));
|
||||
document.documentElement.appendChild($elm);
|
||||
|
||||
// Reload the page
|
||||
window.location.reload(true);
|
||||
|
||||
// Stop processing the script
|
||||
throw new Error('[Better xCloud] Executing workaround for Safari');
|
||||
}
|
||||
|
||||
// Automatically reload the page when running into the "We are sorry..." error message
|
||||
window.addEventListener('load', e => {
|
||||
setTimeout(() => {
|
||||
if (document.body.classList.contains('legacyBackground')) {
|
||||
// Has error message -> reload page
|
||||
window.location.reload(true);
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
|
||||
const SCRIPT_VERSION = '1.11';
|
||||
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
|
||||
|
||||
const SERVER_REGIONS = {};
|
||||
@ -22,6 +94,7 @@ var $STREAM_VIDEO;
|
||||
var $SCREENSHOT_CANVAS;
|
||||
var GAME_TITLE_ID;
|
||||
|
||||
const HAS_TOUCH_SUPPORT = ('ontouchstart' in window || navigator.maxTouchPoints > 0);
|
||||
const TOUCH_SUPPORTED_GAME_IDS = new Set();
|
||||
|
||||
// Credit: https://phosphoricons.com
|
||||
@ -99,6 +172,9 @@ class TouchController {
|
||||
}
|
||||
|
||||
static setup() {
|
||||
const $style = document.createElement('style');
|
||||
document.documentElement.appendChild($style);
|
||||
|
||||
const $bar = createElement('div', {'id': 'better-xcloud-touch-controller-bar'});
|
||||
document.documentElement.appendChild($bar);
|
||||
|
||||
@ -120,8 +196,32 @@ class TouchController {
|
||||
|
||||
TouchController.#$bar = $bar;
|
||||
|
||||
const PREF_STYLE_STANDARD = PREFS.get(Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD);
|
||||
const PREF_STYLE_CUSTOM = PREFS.get(Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM);
|
||||
|
||||
RTCPeerConnection.prototype.orgCreateDataChannel = RTCPeerConnection.prototype.createDataChannel;
|
||||
RTCPeerConnection.prototype.createDataChannel = function() {
|
||||
// Apply touch controller's style
|
||||
const $babylonCanvas = document.getElementById('babylon-canvas');
|
||||
let filter = '';
|
||||
if (TouchController.#enable) {
|
||||
if (PREF_STYLE_STANDARD === 'white') {
|
||||
filter = 'grayscale(1) brightness(2)';
|
||||
} else if (PREF_STYLE_STANDARD === 'muted') {
|
||||
filter = 'sepia(0.5)';
|
||||
}
|
||||
} else if (PREF_STYLE_CUSTOM === 'muted') {
|
||||
filter = 'sepia(0.5)';
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
$style.textContent = `
|
||||
#babylon-canvas {
|
||||
filter: ${filter} !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const dataChannel = this.orgCreateDataChannel.apply(this, arguments);
|
||||
if (!TouchController.#enable) {
|
||||
return dataChannel;
|
||||
@ -131,7 +231,7 @@ class TouchController {
|
||||
|
||||
// Fix sometimes the touch controller doesn't show at the beginning
|
||||
dataChannel.addEventListener('open', e => {
|
||||
TouchController.#show();
|
||||
setTimeout(TouchController.#show, 1000);
|
||||
});
|
||||
|
||||
dataChannel.addEventListener('message', msg => {
|
||||
@ -405,7 +505,7 @@ class StreamStats {
|
||||
static #quickGlanceObserver;
|
||||
|
||||
static start(glancing=false) {
|
||||
if (!StreamStats.isHidden() || (glancing && StreamStats.#isGlancing())) {
|
||||
if (!StreamStats.isHidden() || (glancing && StreamStats.isGlancing())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -416,7 +516,7 @@ class StreamStats {
|
||||
}
|
||||
|
||||
static stop(glancing=false) {
|
||||
if (glancing && !StreamStats.#isGlancing()) {
|
||||
if (glancing && !StreamStats.isGlancing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -429,7 +529,7 @@ class StreamStats {
|
||||
}
|
||||
|
||||
static toggle() {
|
||||
if (StreamStats.#isGlancing()) {
|
||||
if (StreamStats.isGlancing()) {
|
||||
StreamStats.#$container.setAttribute('data-display', 'fixed');
|
||||
} else {
|
||||
StreamStats.isHidden() ? StreamStats.start() : StreamStats.stop();
|
||||
@ -443,7 +543,7 @@ class StreamStats {
|
||||
}
|
||||
|
||||
static isHidden = () => StreamStats.#$container.classList.contains('better-xcloud-gone');
|
||||
static #isGlancing = () => StreamStats.#$container.getAttribute('data-display') === 'glancing';
|
||||
static isGlancing = () => StreamStats.#$container.getAttribute('data-display') === 'glancing';
|
||||
|
||||
static quickGlanceSetup() {
|
||||
if (StreamStats.#quickGlanceObserver) {
|
||||
@ -551,7 +651,7 @@ class StreamStats {
|
||||
static hideSettingsUi() {
|
||||
StreamStats.#$settings.style.display = 'none';
|
||||
|
||||
if (StreamStats.#isGlancing() && !PREFS.get(Preferences.STATS_QUICK_GLANCE)) {
|
||||
if (StreamStats.isGlancing() && !PREFS.get(Preferences.STATS_QUICK_GLANCE)) {
|
||||
StreamStats.stop();
|
||||
}
|
||||
}
|
||||
@ -701,15 +801,18 @@ class PreloadedState {
|
||||
static override() {
|
||||
Object.defineProperty(window, '__PRELOADED_STATE__', {
|
||||
configurable: true,
|
||||
get: () => this._state,
|
||||
set: (state) => {
|
||||
get: () => {
|
||||
// Override User-Agent
|
||||
const userAgent = UserAgent.spoof();
|
||||
if (userAgent) {
|
||||
state.appContext.requestInfo.userAgent = userAgent;
|
||||
state.appContext.requestInfo.origin = 'https://www.xbox.com';
|
||||
this._state.appContext.requestInfo.userAgent = userAgent;
|
||||
}
|
||||
|
||||
return this._state;
|
||||
},
|
||||
set: (state) => {
|
||||
this._state = state;
|
||||
|
||||
// Get a list of touch-supported games
|
||||
if (PREFS.get(Preferences.STREAM_TOUCH_CONTROLLER) === 'all') {
|
||||
let titles = {};
|
||||
@ -725,7 +828,6 @@ class PreloadedState {
|
||||
}
|
||||
}
|
||||
}
|
||||
this._state = state;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -745,9 +847,12 @@ class Preferences {
|
||||
static get USER_AGENT_PROFILE() { return 'user_agent_profile'; }
|
||||
static get USER_AGENT_CUSTOM() { return 'user_agent_custom'; }
|
||||
static get STREAM_HIDE_IDLE_CURSOR() { return 'stream_hide_idle_cursor';}
|
||||
static get STREAM_TOUCH_CONTROLLER() { return 'stream_touch_controller'; }
|
||||
static get STREAM_SIMPLIFY_MENU() { return 'stream_simplify_menu'; }
|
||||
|
||||
static get STREAM_TOUCH_CONTROLLER() { return 'stream_touch_controller'; }
|
||||
static get STREAM_TOUCH_CONTROLLER_STYLE_STANDARD() { return 'stream_touch_controller_style_standard'; }
|
||||
static get STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM() { return 'stream_touch_controller_style_custom'; }
|
||||
|
||||
static get SCREENSHOT_BUTTON_POSITION() { return 'screenshot_button_position'; }
|
||||
static get BLOCK_TRACKING() { return 'block_tracking'; }
|
||||
static get BLOCK_SOCIAL_FEATURES() { return 'block_social_features'; }
|
||||
@ -761,6 +866,8 @@ class Preferences {
|
||||
static get VIDEO_CONTRAST() { return 'video_contrast'; }
|
||||
static get VIDEO_SATURATION() { return 'video_saturation'; }
|
||||
|
||||
static get AUDIO_MIC_ON_PLAYING() { return 'audio_mic_on_playing'; }
|
||||
|
||||
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'; }
|
||||
@ -855,6 +962,21 @@ class Preferences {
|
||||
'off': 'Off',
|
||||
},
|
||||
},
|
||||
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: {
|
||||
'default': 'default',
|
||||
'options': {
|
||||
'default': 'Default colors',
|
||||
'white': 'All white',
|
||||
'muted': 'Muted colors',
|
||||
},
|
||||
},
|
||||
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: {
|
||||
'default': 'default',
|
||||
'options': {
|
||||
'default': 'Default colors',
|
||||
'muted': 'Muted colors',
|
||||
},
|
||||
},
|
||||
[Preferences.STREAM_SIMPLIFY_MENU]: {
|
||||
'default': false,
|
||||
},
|
||||
@ -901,6 +1023,9 @@ class Preferences {
|
||||
'min': 0,
|
||||
'max': 150,
|
||||
},
|
||||
[Preferences.AUDIO_MIC_ON_PLAYING]: {
|
||||
'default': false,
|
||||
},
|
||||
[Preferences.STATS_SHOW_WHEN_PLAYING]: {
|
||||
'default': false,
|
||||
},
|
||||
@ -969,6 +1094,11 @@ class Preferences {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return "default" for STREAM_TOUCH_CONTROLLER pref when the browser doesn't support touch
|
||||
if (!HAS_TOUCH_SUPPORT && key === Preferences.STREAM_TOUCH_CONTROLLER) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
const value = this._prefs[key];
|
||||
|
||||
if (typeof value !== 'undefined' && value !== null && value !== '') {
|
||||
@ -1223,6 +1353,13 @@ function addCss() {
|
||||
background-color: #00753c;
|
||||
}
|
||||
|
||||
.better-xcloud-settings-app-version {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
color: #484848;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.better-xcloud-settings-custom-user-agent {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@ -1527,6 +1664,12 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
|
||||
font-family: Consolas, "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
.better-xcloud-stream-menu-button-on {
|
||||
fill: #000 !important;
|
||||
background-color: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
#better-xcloud-touch-controller-bar {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
@ -1777,9 +1920,12 @@ function interceptHttpRequests() {
|
||||
const PREF_PREFER_IPV6_SERVER = PREFS.get(Preferences.PREFER_IPV6_SERVER);
|
||||
const PREF_STREAM_TARGET_RESOLUTION = PREFS.get(Preferences.STREAM_TARGET_RESOLUTION);
|
||||
const PREF_STREAM_PREFERRED_LOCALE = PREFS.get(Preferences.STREAM_PREFERRED_LOCALE);
|
||||
const PREF_STREAM_TOUCH_CONTROLLER = PREFS.get(Preferences.STREAM_TOUCH_CONTROLLER);
|
||||
const PREF_USE_DESKTOP_CODEC = PREFS.get(Preferences.USE_DESKTOP_CODEC);
|
||||
|
||||
const PREF_STREAM_TOUCH_CONTROLLER = PREFS.get(Preferences.STREAM_TOUCH_CONTROLLER);
|
||||
const PREF_AUDIO_MIC_ON_PLAYING = PREFS.get(Preferences.AUDIO_MIC_ON_PLAYING);
|
||||
const PREF_OVERRIDE_CONFIGURATION = PREF_AUDIO_MIC_ON_PLAYING || PREF_STREAM_TOUCH_CONTROLLER === 'all';
|
||||
|
||||
const orgFetch = window.fetch;
|
||||
window.fetch = async (...arg) => {
|
||||
const request = arg[0];
|
||||
@ -1859,21 +2005,27 @@ function interceptHttpRequests() {
|
||||
return orgFetch(...arg);
|
||||
}
|
||||
|
||||
if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && url.endsWith('/configuration') && url.includes('/sessions/cloud/') && request.method === 'GET') {
|
||||
TouchController.disable();
|
||||
// Get game ID from window.location
|
||||
const match = window.location.pathname.match(/\/launch\/[^\/]+\/([\w\d]+)/);
|
||||
// Check touch support
|
||||
if (match && !TOUCH_SUPPORTED_GAME_IDS.has(match[1])) {
|
||||
TouchController.enable();
|
||||
}
|
||||
|
||||
if (PREF_OVERRIDE_CONFIGURATION && url.endsWith('/configuration') && url.includes('/sessions/cloud/') && request.method === 'GET') {
|
||||
const promise = orgFetch(...arg);
|
||||
if (!TouchController.isEnabled()) {
|
||||
return promise;
|
||||
|
||||
// Touch controller for all games
|
||||
if (PREF_STREAM_TOUCH_CONTROLLER === 'all') {
|
||||
TouchController.disable();
|
||||
|
||||
// Get game ID from window.location
|
||||
const match = window.location.pathname.match(/\/launch\/[^\/]+\/([\w\d]+)/);
|
||||
// Check touch support
|
||||
if (match && !TOUCH_SUPPORTED_GAME_IDS.has(match[1])) {
|
||||
TouchController.enable();
|
||||
}
|
||||
|
||||
// If both settings are invalid -> return promise
|
||||
if (!PREF_AUDIO_MIC_ON_PLAYING && !TouchController.isEnabled()) {
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
// Intercept result to make xCloud show the touch controller
|
||||
// Intercept configurations
|
||||
return promise.then(response => {
|
||||
return response.clone().text().then(text => {
|
||||
if (!text.length) {
|
||||
@ -1882,10 +2034,20 @@ function interceptHttpRequests() {
|
||||
|
||||
const obj = JSON.parse(text);
|
||||
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {};
|
||||
overrides.inputConfiguration = {
|
||||
enableTouchInput: true,
|
||||
maxTouchPoints: 10,
|
||||
};
|
||||
|
||||
// Enable touch controller
|
||||
if (TouchController.isEnabled()) {
|
||||
overrides.inputConfiguration = overrides.inputConfiguration || {};
|
||||
overrides.inputConfiguration.enableTouchInput = true;
|
||||
overrides.inputConfiguration.maxTouchPoints = 10;
|
||||
}
|
||||
|
||||
// Enable mic
|
||||
if (PREF_AUDIO_MIC_ON_PLAYING) {
|
||||
overrides.audioConfiguration = overrides.audioConfiguration || {};
|
||||
overrides.audioConfiguration.enableMicrophone = true;
|
||||
}
|
||||
|
||||
obj.clientStreamingConfigOverrides = JSON.stringify(overrides);
|
||||
|
||||
response.json = () => Promise.resolve(obj);
|
||||
@ -1950,33 +2112,6 @@ function interceptHttpRequests() {
|
||||
}
|
||||
|
||||
|
||||
// Quickly create a tree of elements without having to use innerHTML
|
||||
function createElement(elmName, props = {}) {
|
||||
const $elm = document.createElement(elmName);
|
||||
for (let key in props) {
|
||||
if (!props.hasOwnProperty(key) || $elm.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = props[key];
|
||||
$elm.setAttribute(key, value);
|
||||
}
|
||||
|
||||
for (let i = 2, size = arguments.length; i < size; i++) {
|
||||
const arg = arguments[i];
|
||||
const argType = typeof arg;
|
||||
|
||||
if (argType == 'string' || argType == 'number') {
|
||||
$elm.innerText = arg;
|
||||
} else if (arg) {
|
||||
$elm.appendChild(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return $elm;
|
||||
}
|
||||
|
||||
|
||||
function injectSettingsButton($parent) {
|
||||
if (!$parent) {
|
||||
return;
|
||||
@ -2037,15 +2172,18 @@ function injectSettingsButton($parent) {
|
||||
[Preferences.STREAM_PREFERRED_LOCALE]: 'Preferred game\'s language',
|
||||
[Preferences.PREFER_IPV6_SERVER]: 'Prefer IPv6 server',
|
||||
},
|
||||
'Stream quality': {
|
||||
'Stream': {
|
||||
[Preferences.STREAM_TARGET_RESOLUTION]: 'Target resolution',
|
||||
[Preferences.USE_DESKTOP_CODEC]: 'Force high-quality codec',
|
||||
[Preferences.DISABLE_BANDWIDTH_CHECKING]: 'Disable bandwidth checking',
|
||||
},
|
||||
'Controller': {
|
||||
[Preferences.STREAM_TOUCH_CONTROLLER]: 'Touch controller',
|
||||
[Preferences.AUDIO_MIC_ON_PLAYING]: 'Enable microphone on game launch',
|
||||
[Preferences.STREAM_HIDE_IDLE_CURSOR]: 'Hide mouse cursor on idle',
|
||||
},
|
||||
'Touch controller': {
|
||||
[Preferences.STREAM_TOUCH_CONTROLLER]: 'Availability',
|
||||
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: 'Standard layout\'s button style',
|
||||
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: 'Custom layout\'s button style',
|
||||
},
|
||||
'UI': {
|
||||
[Preferences.STREAM_SIMPLIFY_MENU]: 'Simplify Stream\'s menu',
|
||||
[Preferences.SKIP_SPLASH_VIDEO]: 'Skip Xbox splash video',
|
||||
@ -2133,9 +2271,9 @@ function injectSettingsButton($parent) {
|
||||
$control.disabled = true;
|
||||
$control.checked = false;
|
||||
$control.title = 'Your browser doesn\'t support this feature';
|
||||
} else if (settingId === Preferences.STREAM_TOUCH_CONTROLLER) {
|
||||
} else if (!HAS_TOUCH_SUPPORT) {
|
||||
// Disable this setting for non-touchable devices
|
||||
if (!('ontouchstart'in window) && navigator.maxTouchPoints === 0) {
|
||||
if ([Preferences.STREAM_TOUCH_CONTROLLER, Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD, Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM].indexOf(settingId) > -1) {
|
||||
$control.disabled = true;
|
||||
$control.title = 'Your device doesn\'t have touch support';
|
||||
}
|
||||
@ -2166,6 +2304,13 @@ function injectSettingsButton($parent) {
|
||||
});
|
||||
$wrapper.appendChild($reloadBtn);
|
||||
|
||||
// Show Game Pass app version
|
||||
try {
|
||||
const appVersion = document.querySelector('meta[name=gamepass-app-version]').content;
|
||||
const appDate = new Date(document.querySelector('meta[name=gamepass-app-date]').content).toISOString().substring(0, 10);
|
||||
$wrapper.appendChild(CE('div', {'class': 'better-xcloud-settings-app-version'}, `GamePass app ${appVersion} (${appDate})`));
|
||||
} catch (e) {}
|
||||
|
||||
// Add Settings UI to the web page
|
||||
const $pageContent = document.getElementById('PageContent');
|
||||
$pageContent.parentNode.insertBefore($container, $pageContent);
|
||||
@ -2258,7 +2403,7 @@ function cloneStreamMenuButton($orgButton, label, svg_icon) {
|
||||
}
|
||||
|
||||
|
||||
function injectVideoSettingsButton() {
|
||||
function injectStreamMenuButtons() {
|
||||
const $screen = document.querySelector('#PageContent section[class*=PureScreens]');
|
||||
if (!$screen) {
|
||||
return;
|
||||
@ -2300,7 +2445,8 @@ function injectVideoSettingsButton() {
|
||||
return;
|
||||
}
|
||||
|
||||
const $orgButton = node.querySelector('div > div > button');
|
||||
// Get the second last button
|
||||
const $orgButton = node.querySelector('div > div > button:nth-last-child(2)');
|
||||
if (!$orgButton) {
|
||||
return;
|
||||
}
|
||||
@ -2311,6 +2457,9 @@ function injectVideoSettingsButton() {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Close HUD
|
||||
$btnCloseHud.click();
|
||||
|
||||
// Show Quick settings bar
|
||||
$quickBar.style.display = 'flex';
|
||||
|
||||
@ -2342,6 +2491,9 @@ function injectVideoSettingsButton() {
|
||||
StreamStats.toggle();
|
||||
});
|
||||
|
||||
const btnStreamStatsOn = (!StreamStats.isHidden() && !StreamStats.isGlancing());
|
||||
$btnStreamStats.classList.toggle('better-xcloud-stream-menu-button-on', btnStreamStatsOn);
|
||||
|
||||
// Insert after Video Settings button
|
||||
$orgButton.parentElement.insertBefore($btnStreamStats, $btnVideoSettings);
|
||||
|
||||
@ -2413,7 +2565,7 @@ function patchVideoApi() {
|
||||
}
|
||||
|
||||
this.addEventListener('playing', showFunc);
|
||||
injectVideoSettingsButton();
|
||||
injectStreamMenuButtons();
|
||||
|
||||
return this.orgPlay.apply(this);
|
||||
};
|
||||
@ -2784,6 +2936,22 @@ function onStreamStarted($video) {
|
||||
}
|
||||
|
||||
|
||||
function disablePwa() {
|
||||
const userAgent = (window.navigator.orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||
if (!userAgent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's Safari on mobile
|
||||
if (userAgent.includes('mobile') && userAgent.includes('safari') && !userAgent.includes('chrom')) {
|
||||
// Disable the PWA prompt
|
||||
Object.defineProperty(window.navigator, 'standalone', {
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Hide Settings UI when navigate to another page
|
||||
window.addEventListener('xcloud_popstate', onHistoryChanged);
|
||||
window.addEventListener('popstate', onHistoryChanged);
|
||||
@ -2831,7 +2999,4 @@ setupVideoSettingsBar();
|
||||
setupScreenshotButton();
|
||||
StreamStats.render();
|
||||
|
||||
// Disable PWA prompt in Safari on iOS/iPadOS
|
||||
Object.defineProperty(window.navigator, 'standalone', {
|
||||
value: true,
|
||||
});
|
||||
disablePwa();
|
||||
|
Reference in New Issue
Block a user