Compare commits

...

42 Commits
v1.2 ... v1.4

Author SHA1 Message Date
1929834c98 Update README.md 2023-07-20 18:50:57 +07:00
6e80ea08e8 Bump version to 1.4 2023-07-20 18:40:27 +07:00
a5caafa93a Stream badges & 1080p stream (#10)
* Get stream statuses

* Render badges

* Add "Force 1080p stream" feature (#9)
2023-07-20 18:38:58 +07:00
f8134c3e5a Update README.md 2023-07-19 13:22:48 +07:00
89ea55774b Make Settings UI can be interacted using controller 2023-07-19 09:03:36 +07:00
2836eeb6ed Update README.md 2023-07-17 20:10:51 +07:00
876b090ad2 Update README.md 2023-07-17 19:54:44 +07:00
e33730b124 Bump version to 1.3.2 2023-07-17 18:46:26 +07:00
8b4f26155c Update README.md 2023-07-17 18:45:54 +07:00
a52ba7dddf Work-around of "Force high quality stream" feature for Firefox (#4) 2023-07-17 18:42:20 +07:00
a8eb296bcf Merge branch 'main' of https://github.com/redphx/better-xcloud 2023-07-17 17:06:37 +07:00
f4a3c38cca Update texts in Settings 2023-07-17 17:06:34 +07:00
e8ab19c318 Update README.md 2023-07-17 15:09:46 +07:00
89e4dd0003 Update README.md 2023-07-17 11:25:54 +07:00
9874d36f3a Update README.md 2023-07-17 09:05:27 +07:00
ed54d1ed38 Fix IPv6 feature not working 2023-07-17 08:54:53 +07:00
4676e42215 Update README.md 2023-07-16 18:36:36 +07:00
ad791bdc45 Work-around for forcing high quality stream 2023-07-16 18:34:11 +07:00
a318db4ec2 Update better-xcloud.user.js 2023-07-16 15:56:08 +07:00
aaa8348984 Update README.md 2023-07-16 15:55:44 +07:00
2cea30cf16 Check support of RTCRtpTransceiver.setCodecPreferences() 2023-07-16 15:50:44 +07:00
0fe99f8f2d Update README.md 2023-07-16 15:16:43 +07:00
0cb09bb455 Update README.md 2023-07-16 12:41:51 +07:00
14cf9d5877 Stop recommending changing User-Agent 2023-07-16 12:39:28 +07:00
b96b115182 Bump version to 1.3 2023-07-16 12:30:55 +07:00
766c5b8682 Update README.md 2023-07-16 12:30:14 +07:00
5f9564de45 Update CSS of reload page button 2023-07-16 12:01:22 +07:00
3a7dea6c75 Add missing code 2023-07-16 11:54:40 +07:00
8dd17bb021 Add setting to force HQ codec 2023-07-16 11:54:23 +07:00
e63f998f5a Update README.md 2023-07-16 06:43:49 +07:00
0685cd8038 Update README.md 2023-07-15 21:48:05 +07:00
f246faf796 Update README.md 2023-07-15 21:26:10 +07:00
68ef703bdb Update README.md 2023-07-15 21:17:01 +07:00
f9d683a2e4 Update README.md 2023-07-15 21:11:51 +07:00
fec2c4a22a Update README.md 2023-07-15 21:04:15 +07:00
c3df18d7be Update README.md 2023-07-15 20:59:10 +07:00
9971fe6c0c Update README.md 2023-07-15 20:55:34 +07:00
cc11bcea41 Bump version to 1.2.1 2023-07-15 20:54:15 +07:00
048d085e91 Merge branch 'main' of https://github.com/redphx/better-xcloud 2023-07-15 20:53:37 +07:00
ed32044480 Update Video settings UI 2023-07-15 20:53:28 +07:00
32c087966b Update README.md 2023-07-15 20:52:51 +07:00
e4ad010e0a Remove video settings in the main Settings UI 2023-07-15 20:29:50 +07:00
2 changed files with 359 additions and 149 deletions

140
README.md
View File

@ -1,37 +1,108 @@
# 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.
Give this project a 🌟 if you like it. Thank you 🙏.
## Features
<img width="474" alt="image" src="https://github.com/redphx/better-xcloud/assets/96280/2793d404-3185-4c91-a500-dde362c661dd">
<img width="500" alt="Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/8fb9f0ac-85f5-4e5a-9570-5a5e119e4fc1">
<img width="500" alt="Video Settings UI" src="https://github.com/redphx/better-xcloud/assets/96280/ed219d50-02ab-40bd-95c5-a010956d77bf">
- Switch region of streaming server.
- Prefer IPv6 streaming server (might reduce latency).
- Force HD stream by disabling bandwidth checking -> xCloud always tries to use the best possible quality.
- 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.
- Stretch video to full sctreen. Useful when you don't have a 16:9 screen.
- Adjust video filters (brightness/contrast/saturation).
- You can change video settings while playing.
- 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.
- **Switch region of streaming server**
> Connect to another server instead of the default one. Check [FAQ section](#faq) for some notes.
> Not working in Hermit ([#5](https://github.com/redphx/better-xcloud/issues/5)).
- **Force 1080p stream**
> By default you only get 1080p stream when playing on desktop.
> This feature will give you 1080p stream even on mobile, without having to change User-Agent.
> Not working in Hermit ([#5](https://github.com/redphx/better-xcloud/issues/5)).
- **Force high quality codec <sup>(\*)</sup>**
> Force xCloud to use the best streaming codec profile (same as desktop & TV). You don't have to change User-Agent anymore.
> You should enable this feature even if you're on desktop.
> Use more bandwidth & battery.
> Comparison video with the setting ON & OFF: https://youtu.be/-9PuBJJSgR4
> Disable if it causes crashes.
- **Prefer IPv6 streaming server**
> Might reduce latency
- **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
- **Display stream's statuses**
> Region/Server/Quality/Dimension...
- **Disable social features**
> Features like friends, chat... Disable these will make the page load faster.
> Not working in Hermit ([#5](https://github.com/redphx/better-xcloud/issues/5)).
- **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.
> Not working in Hermit ([#5](https://github.com/redphx/better-xcloud/issues/5)).
- **Hide footer and other UI elements**
<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. 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) (free & open source).
<sup>3</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). 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.
## 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.
If your browser doesn't support **Force high quality stream** setting, try changing User-Agent to:
```
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
```
This will change your device to a Samsung TV running Tizen OS. It will improve the stream quality.
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
```
@ -43,34 +114,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.
## Compatibility
✅ = confirmed to be working
❓ = not yet tested
❌ = 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
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
- [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)
@ -78,4 +121,3 @@ No. "Clarity Boost" feature uses an exclusive API (`Video.msVideoProcessing`) th
## 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.

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 1.2
// @version 1.4
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -13,15 +13,48 @@
// ==/UserScript==
'use strict';
const SCRIPT_VERSION = '1.2';
const SCRIPT_VERSION = '1.4';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
const SERVER_REGIONS = {};
class StreamStatus {
static ipv6 = false;
static dimension = {width: 0, height: 0};
static hqCodec = false;
static region = '';
static #renderBadge(name, value, color) {
const CE = createElement;
const $badge = CE('div', {'class': 'better_xcloud_badge'},
CE('span', {'class': 'better_xcloud_badge_name'}, name),
CE('span', {'class': 'better_xcloud_badge_value', 'style': `background-color: ${color}`}, value));
return $badge;
}
static render() {
const BADGES = [
['region', StreamStatus.region, '#d7450b'],
['server', StreamStatus.ipv6 ? 'IPv6' : 'IPv4', '#008746'],
['quality', StreamStatus.hqCodec ? 'High' : 'Normal', '#007c8f'],
['dimension', `${StreamStatus.dimension.width}x${StreamStatus.dimension.height}`, '#ff3977'],
];
const $wrapper = createElement('div', {'class': 'better_xcloud_badges'});
BADGES.forEach(item => $wrapper.appendChild(StreamStatus.#renderBadge(...item)));
return $wrapper;
}
}
class Preferences {
static get SERVER_REGION() { return 'server_region'; }
static get PREFER_IPV6_SERVER() { return 'prefer_ipv6_server'; }
static get FORCE_1080P_STREAM() { return 'force_1080p_stream'; }
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'; }
@ -42,6 +75,18 @@ class Preferences {
'default': 'default',
},
{
'id': Preferences.FORCE_1080P_STREAM,
'label': 'Force 1080p stream',
'default': false,
},
{
'id': Preferences.USE_DESKTOP_CODEC,
'label': 'Force high quality codec',
'default': false,
},
{
'id': Preferences.PREFER_IPV6_SERVER,
'label': 'Prefer IPv6 streaming server',
@ -50,7 +95,7 @@ class Preferences {
{
'id': Preferences.DISABLE_BANDWIDTH_CHECKING,
'label': 'Force HD stream',
'label': 'Disable bandwidth checking',
'default': false,
},
@ -74,7 +119,7 @@ class Preferences {
{
'id': Preferences.BLOCK_SOCIAL_FEATURES,
'label': 'Disable social features (Friends, Chat...)',
'label': 'Disable social features',
'default': false,
},
@ -88,6 +133,7 @@ class Preferences {
'id': Preferences.VIDEO_FILL_FULL_SCREEN,
'label': 'Stretch video to full screen',
'default': false,
'hidden': true,
},
{
@ -96,6 +142,7 @@ class Preferences {
'default': 100,
'min': 0,
'max': 150,
'hidden': true,
},
{
@ -104,6 +151,7 @@ class Preferences {
'default': 100,
'min': 0,
'max': 150,
'hidden': true,
},
{
@ -112,6 +160,7 @@ class Preferences {
'default': 100,
'min': 0,
'max': 150,
'hidden': true,
},
]
@ -182,7 +231,7 @@ function addCss() {
padding: 8px;
}
.better_xcloud_settings_button:hover, .better_xlcoud_settings_button:focus {
.better_xcloud_settings_button:hover, .better_xcloud_settings_button:focus {
background-color: #515863;
}
@ -198,9 +247,9 @@ function addCss() {
}
.better_xcloud_settings_wrapper {
width: 400px;
width: 450px;
margin: auto;
padding: 12px;
padding: 12px 6px;
}
.better_xcloud_settings_wrapper *:focus {
@ -214,11 +263,23 @@ function addCss() {
font-weight: bold;
display: block;
margin-bottom: 8px;
color: #5dc21e;
}
@media (hover: hover) {
.better_xcloud_settings_wrapper a:hover {
color: #83f73a;
}
}
.better_xcloud_settings_wrapper a:focus {
color: #83f73a;
}
.better_xcloud_settings_wrapper .setting_row {
display: flex;
margin-bottom: 8px;
padding: 2px 4px;
}
.better_xcloud_settings_wrapper .setting_row label {
@ -227,6 +288,12 @@ function addCss() {
margin-bottom: 0;
}
@media not (hover: hover) {
.better_xcloud_settings_wrapper .setting_row:focus-within {
background-color: #242424;
}
}
.better_xcloud_settings_wrapper .setting_row input {
align-self: center;
}
@ -246,29 +313,54 @@ 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_wrapper .setting_button:focus {
background-color: #00753c;
}
.better_xcloud_settings_preview_video {
display: flex;
aspect-ratio: 16/9;
height: 100%;
margin: auto;
.better_xcloud_settings_wrapper .setting_button:active {
background-color: #00753c;
}
.better_xcloud_settings_preview_video div {
flex: 1;
div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
overflow: visible;
}
.better_xcloud_badges {
position: absolute;
bottom: -35px;
margin-left: 0px;
user-select: none;
}
.better_xcloud_badge {
border: none;
display: inline-block;
line-height: 24px;
color: #fff;
font-family: Bahnschrift Semibold, Arial, Helvetica, sans-serif;
font-weight: 400;
margin-right: 8px;
}
.better_xcloud_badge .better_xcloud_badge_name {
background-color: #2d3036;
display: inline-block;
padding: 2px 8px;
border-radius: 4px 0 0 4px;
text-transform: uppercase;
}
.better_xcloud_badge .better_xcloud_badge_value {
background-color: grey;
display: inline-block;
padding: 2px 8px;
border-radius: 0 4px 4px 0;
}
/* Hide UI elements */
@ -344,7 +436,7 @@ function getPreferredServerRegion() {
function updateIceCandidates(candidates) {
const pattern = new RegExp(/a=candidate:(?<order>\d+) (?<num>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<the_rest>.*)/);
const pattern = new RegExp(/a=candidate:(?<foundation>\d+) (?<component>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<the_rest>.*)/);
const lst = [];
for (let item of candidates) {
@ -359,21 +451,19 @@ function updateIceCandidates(candidates) {
lst.sort((a, b) => (a.ip.includes(':') || a.ip > b.ip) ? -1 : 1);
const newCandidates = [];
let order = 1;
let priority = 100;
let foundation = 1;
lst.forEach(item => {
item.order = order;
item.priority = priority;
item.foundation = foundation;
item.priority = (foundation == 1) ? 100 : 1;
newCandidates.push({
'candidate': `a=candidate:${item.order} 1 UDP ${item.priority} ${item.ip} ${item.the_rest}`,
'candidate': `a=candidate:${item.foundation} 1 UDP ${item.priority} ${item.ip} ${item.the_rest}`,
'messageType': 'iceCandidate',
'sdpMLineIndex': '0',
'sdpMid': '0',
});
++order;
--priority;
++foundation;
});
newCandidates.push({
@ -432,6 +522,9 @@ function interceptHttpRequests() {
};
const PREF_PREFER_IPV6_SERVER = PREFS.get(Preferences.PREFER_IPV6_SERVER);
const PREF_FORCE_1080P_STREAM = PREFS.get(Preferences.FORCE_1080P_STREAM);
const PREF_USE_DESKTOP_CODEC = PREFS.get(Preferences.USE_DESKTOP_CODEC);
const HAS_CODECS_API_SUPPORT = hasRtcSetCodecPreferencesSupport();
const orgFetch = window.fetch;
window.fetch = async (...arg) => {
@ -472,8 +565,59 @@ function interceptHttpRequests() {
});
}
// Get region
if (url.endsWith('/sessions/cloud/play')) {
const parsedUrl = new URL(url);
StreamStatus.region = parsedUrl.host.split('.', 1)[0];
for (let regionName in SERVER_REGIONS) {
const region = SERVER_REGIONS[regionName];
if (parsedUrl.origin == region.baseUri) {
StreamStatus.region = regionName;
break;
}
}
// Force 1080p stream
if (PREF_FORCE_1080P_STREAM) {
// Intercept "osName" value
const clone = request.clone();
const body = await clone.json();
body.settings.osName = 'windows';
const newRequest = new Request(request, {
body: JSON.stringify(body),
});
arg[0] = newRequest;
}
return orgFetch(...arg);
}
// Work-around for browsers with no setCodecPreferences() support
if (PREF_USE_DESKTOP_CODEC && !HAS_CODECS_API_SUPPORT && url.endsWith('/sdp') && url.includes('/sessions/cloud/') && request.method === 'GET') {
const promise = orgFetch(...arg);
return promise.then(response => {
return response.clone().text().then(text => {
if (!text.length) {
return response;
}
const obj = JSON.parse(text);
obj.exchangeResponse = obj.exchangeResponse.replaceAll('profile-level-id=42', 'profile-level-id=4d');
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
});
});
}
// ICE server candidates
if (PREF_PREFER_IPV6_SERVER && url.endsWith('/ice') && url.includes('/sessions/cloud/')) {
if (PREF_PREFER_IPV6_SERVER && url.endsWith('/ice') && url.includes('/sessions/cloud/') && request.method === 'GET') {
const promise = orgFetch(...arg);
return promise.then(response => {
@ -488,6 +632,8 @@ function interceptHttpRequests() {
obj.exchangeResponse = JSON.stringify(exchangeResponse);
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
});
});
@ -536,31 +682,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;
@ -593,8 +714,12 @@ function injectSettingsButton($parent) {
$wrapper.appendChild($title);
for (let setting of Preferences.SETTINGS) {
let $control;
if (setting.hidden) {
continue;
}
let $control;
let labelAttrs = {};
if (setting.id === Preferences.SERVER_REGION) {
$control = CE('select', {id: 'xcloud_setting_' + setting.id});
$control.addEventListener('change', e => {
@ -616,33 +741,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,
@ -660,20 +758,19 @@ function injectSettingsButton($parent) {
setting.value = PREFS.get(setting.id);
$control.checked = setting.value;
labelAttrs = {'for': 'xcloud_setting_' + setting.id, 'tabindex': 0};
}
const $elm = CE('div', {'class': 'setting_row'},
CE('label', {'for': 'xcloud_setting_' + setting.id}, setting.label),
CE('label', labelAttrs, setting.label),
$control
);
$wrapper.appendChild($elm);
}
const $videoPreview = generateVideoPreviewBox();
$wrapper.appendChild($videoPreview);
const $reloadBtn = CE('button', {'class': 'setting_button'}, 'Reload page to reflect changes');
const $reloadBtn = CE('button', {'class': 'setting_button', 'tabindex': 0}, 'Reload page to reflect changes');
$reloadBtn.addEventListener('click', e => window.location.reload());
$wrapper.appendChild($reloadBtn);
@ -846,9 +943,6 @@ function injectVideoSettingsButton() {
// 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);
@ -859,6 +953,15 @@ function injectVideoSettingsButton() {
});
$orgButton.parentElement.insertBefore($button, $orgButton.parentElement.firstChild);
// Hide Quick bar when closing HUD
document.querySelector('button[class*=StreamMenu-module__backButton]').addEventListener('click', e => {
$quickBar.style.display = 'none';
});
// Render stream badges
const $menu = document.querySelector('div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]');
$menu.appendChild(StreamStatus.render());
});
});
@ -875,6 +978,10 @@ function patchVideoApi() {
showFunc = function() {
this.style.visibility = 'visible';
this.removeEventListener('playing', showFunc);
if (this.videoWidth) {
StreamStatus.dimension = {width: this.videoWidth, height: this.videoHeight};
}
}
HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play;
@ -897,6 +1004,40 @@ function patchVideoApi() {
}
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;
@ -907,7 +1048,7 @@ function numberPicker(key) {
const CE = createElement;
const $wrapper = CE('div', {},
$decBtn = CE('button', {'data-type': 'dec'}, '-'),
$text = CE('span', {}, value),
$text = CE('span', {}, value + '%'),
$incBtn = CE('button', {'data-type': 'inc'}, '+'),
);
@ -929,7 +1070,7 @@ function numberPicker(key) {
value = (value >= MAX) ? MAX : value + 1;
}
$text.textContent = value;
$text.textContent = value + '%';
PREFS.set(key, value);
updateVideoPlayerCss();
@ -1022,8 +1163,9 @@ function setupVideoSettingsBar() {
}
.better_xcloud_quick_settings_bar label {
font-size: 24px;
font-size: 20px;
display: block;
margin-bottom: 8px;
}
.better_xcloud_quick_settings_bar input {
@ -1037,8 +1179,9 @@ function setupVideoSettingsBar() {
height: 24px;
margin: 0 8px;
line-height: 24px;
background-color: #fff;
color: #000;
background-color: #515151;
color: #fff;
border-radius: 4px;
}
@media (hover: hover) {
@ -1055,7 +1198,9 @@ function setupVideoSettingsBar() {
.better_xcloud_quick_settings_bar span {
display: inline-block;
width: 26px;
width: 40px;
font-weight: bold;
font-family: Consolas, "Courier New", Courier, monospace;
}
`);
@ -1106,6 +1251,8 @@ if (PREFS.get(Preferences.DISABLE_BANDWIDTH_CHECKING)) {
});
}
patchRtcCodecs();
interceptHttpRequests();
patchVideoApi();
@ -1124,3 +1271,24 @@ window.onload = () => {
if (document.readyState === 'complete' && !onLoadTriggered) {
watchHeader();
}
RTCPeerConnection.prototype.orgSetRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription;
RTCPeerConnection.prototype.setRemoteDescription = function(...args) {
const sdpDesc = args[0];
if (sdpDesc.sdp) {
StreamStatus.hqCodec = sdpDesc.sdp.includes('profile-level-id=4d');
}
return this.orgSetRemoteDescription.apply(this, args);
}
RTCPeerConnection.prototype.orgAddIceCandidate = RTCPeerConnection.prototype.addIceCandidate;
RTCPeerConnection.prototype.addIceCandidate = function(...args) {
const candidate = args[0].candidate;
if (candidate && candidate.startsWith('a=candidate:1 ')) {
StreamStatus.ipv6 = candidate.substring(20).includes(':');
}
return this.orgAddIceCandidate.apply(this, args);
}