Compare commits

..

29 Commits

Author SHA1 Message Date
35a783c53e Bump version to 3.5.1 2024-04-11 17:42:03 +07:00
afe3809061 Upper case server's short name in dropdown box 2024-04-11 17:31:44 +07:00
5eeb15f201 Prevent Remote Play from breaking in the future 2024-04-11 17:23:21 +07:00
71b4109385 Remove the "Cloud Gaming" text in header 2024-04-11 17:12:29 +07:00
8286429cc3 Add "patchStreamHud" patch 2024-04-11 17:12:15 +07:00
ae24005f08 Fix exception in fetch() 2024-04-11 17:11:59 +07:00
2626408cbe Fix the Guide/Nexus button not working in Remote Play 2024-04-11 15:09:59 +07:00
804f751646 Fix Remote Play 2024-04-11 12:43:46 +07:00
13a20f30e5 Fix Remote Play stopped working 2024-04-11 07:30:16 +07:00
46265f2ccd Show server's short name in header 2024-04-10 20:57:40 +07:00
93d77c3783 Fix not updating SCRIPT_VERSION 2024-04-08 17:50:47 +07:00
eeb7ab749a Bump version to 3.5.0 2024-04-08 17:20:47 +07:00
2af3dc315b Update translations 2024-04-08 16:52:29 +07:00
bd7b7d5ef5 Minor fixes 2024-04-07 15:16:27 +07:00
93b540d995 New setting: "combine audio & video streams" 2024-04-07 11:44:30 +07:00
9f26021ec6 Add Experimental text in Settings 2024-04-07 11:27:52 +07:00
bd8aedaf30 Add CheckForUpdate flag 2024-04-06 17:01:38 +07:00
414bc2268e Fix emitting "playing" event on normal videos 2024-04-06 16:54:41 +07:00
6b12b4add4 Use "await" keyword in interceptHttpRequests() 2024-04-06 16:41:21 +07:00
cdc64da95f Preload Remote Play right after getting server list 2024-04-06 15:31:59 +07:00
1a2fb6c89a Only init Settings UI when necessary 2024-04-06 15:29:40 +07:00
351cb0204b Preload Remote Play after injecting header buttons 2024-04-06 15:08:07 +07:00
f6a7a78be7 Fix exception in settings 2024-04-05 21:45:40 +07:00
66695b2fc2 Minor fix 2024-04-05 21:43:06 +07:00
6f8f425003 Fix scrollbar not hiding in Firefox 2024-04-05 21:31:47 +07:00
1966c7c127 Hide scrollbar setting in the app 2024-04-05 21:31:30 +07:00
67788bd365 Fix exception with Remote Play 2024-04-05 21:18:48 +07:00
bf9942ca4f Remote Play: fix not able to delete session after disconnecting 2024-04-05 17:06:01 +07:00
8d22533d7f Fix problem with Remote Play 2024-04-03 19:54:36 +07:00
2 changed files with 390 additions and 253 deletions

View File

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

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud // @name Better xCloud
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 3.4.0 // @version 3.5.1
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
@ -16,11 +16,12 @@
/* ADDITIONAL CODE */ /* ADDITIONAL CODE */
const SCRIPT_VERSION = '3.4.0'; const SCRIPT_VERSION = '3.5.1';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud'; const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
// Setup flags // Setup flags
const DEFAULT_FLAGS = { const DEFAULT_FLAGS = {
CheckForUpdate: true,
PreloadRemotePlay: true, PreloadRemotePlay: true,
PreloadUi: false, PreloadUi: false,
EnableXcloudLogging: false, EnableXcloudLogging: false,
@ -667,6 +668,40 @@ const Translations = {
"Đóng", "Đóng",
"关闭", "关闭",
], ],
"combine-audio-video-streams": [
,
"Gabung audio & video stream",
"Combine audio & video streams",
,
,
,
"音声を映像ストリーミングと統合",
,
"Połącz strumienie audio i wideo",
"Combinar fluxos de áudio e vídeo",
"Объединить аудио и видео потоки",
"Ses ve görüntü akışını birleştir",
"Поєднайте аудіо та відео потоки",
"Hòa hợp nguồn của âm thanh và hình ảnh",
"合并视频音频流",
],
"combine-audio-video-streams-summary": [
,
"Mungkin memperbaiki masalah lag pada audio",
"May fix the laggy audio problem",
,
,
,
"音声の遅延を改善できる可能性があります",
,
"Może rozwiązać problem z zacinającym dźwiękiem",
"Pode corrigir o problema de áudio Laggy",
"Может исправить проблему подвисания звука",
"Sesteki gecikme sorununa çözüm olabilir",
"Може виправити проблему із затримкою звуку",
"Có thể sửa được lỗi trễ tiếng",
"有助于缓解音频延迟",
],
"conditional-formatting": [ "conditional-formatting": [
"Zustandsabhängige Textfarbe", "Zustandsabhängige Textfarbe",
"Format teks kondisional", "Format teks kondisional",
@ -1228,6 +1263,23 @@ const Translations = {
"Đã bật", "Đã bật",
"启用", "启用",
], ],
"experimental": [
,
"Eksperimental",
"Experimental",
,
,
,
"実験的機能",
,
"Eksperymentalne",
"Experimental",
"Экспериментально",
"Deneme aşamasında",
"Експериментальне",
"Thử nghiệm",
"实验性功能",
],
"export": [ "export": [
"Exportieren", "Exportieren",
"Ekspor", "Ekspor",
@ -1348,21 +1400,21 @@ const Translations = {
"空闲时隐藏鼠标", "空闲时隐藏鼠标",
], ],
"hide-scrollbar": [ "hide-scrollbar": [
, "Scrollbalken der Webseite ausblenden",
, "Sembunyikan bilah gulir halaman",
"Hide web page's scrollbar", "Hide web page's scrollbar",
, ,
, ,
, ,
"Webページのスクロールバーを隠す",
, ,
, "Ukryj pasek przewijania strony",
, "Oculta a barra de rolagem da página",
, "Скрыть полосу прокрутки страницы",
, "Yandaki kaydırma çubuğunu gizle",
, "Приховати смугу прокрутки вебсторінок",
,
"Ẩn thanh cuộn của trang web", "Ẩn thanh cuộn của trang web",
, "隐藏浏览器滚动条",
], ],
"hide-system-menu-icon": [ "hide-system-menu-icon": [
"Symbol des System-Menüs ausblenden", "Symbol des System-Menüs ausblenden",
@ -2308,13 +2360,13 @@ const Translations = {
, ,
"スクリーンショットにビデオフィルターを適用", "スクリーンショットにビデオフィルターを適用",
, ,
, "Stosuje filtry wideo do zrzutów ekranu",
"Aplicar filtros às capturas de tela", "Aplicar filtros às capturas de tela",
"Применяет фильтры видео к скриншотам", "Применяет фильтры видео к скриншотам",
"Görsel filtreleri ekran görüntülerine de uygular", "Görsel filtreleri ekran görüntülerine de uygular",
"Застосовує відеофільтри до знімків екрана", "Застосовує відеофільтри до знімків екрана",
"Áp dụng hiệu ứng video vào ảnh chụp màn hình", "Áp dụng hiệu ứng video vào ảnh chụp màn hình",
, "为截图添加滤镜",
], ],
"screenshot-button-position": [ "screenshot-button-position": [
"Position des Screenshot-Buttons", "Position des Screenshot-Buttons",
@ -3219,15 +3271,15 @@ const Translations = {
], ],
"vibration-status": [ "vibration-status": [
"Vibration", "Vibration",
, "Getaran",
"Vibration", "Vibration",
, ,
, ,
, ,
"振動", "振動",
, ,
, "Wibracje",
, "Vibração",
"Вибрация", "Вибрация",
"Titreşim", "Titreşim",
"Вібрація", "Вібрація",
@ -3548,7 +3600,8 @@ class Dialog {
class RemotePlay { class RemotePlay {
static XCLOUD_TOKEN; static XCLOUD_TOKEN;
static XHOME_TOKEN; static XHOME_TOKEN;
static #CONSOLES; static #CONSOLES = null;
static #REGIONS;
static #STATE_LABELS = { static #STATE_LABELS = {
'On': t('powered-on'), 'On': t('powered-on'),
@ -3611,9 +3664,6 @@ class RemotePlay {
RemotePlay.#getConsolesList(() => { RemotePlay.#getConsolesList(() => {
console.log(RemotePlay.#CONSOLES); console.log(RemotePlay.#CONSOLES);
RemotePlay.#renderConsoles(); RemotePlay.#renderConsoles();
const $btn = document.querySelector('.bx-remote-play-button');
$btn && ($btn.disabled = false);
}); });
}); });
} }
@ -3726,6 +3776,7 @@ class RemotePlay {
}, },
}).then(resp => resp.json()) }).then(resp => resp.json())
.then(json => { .then(json => {
RemotePlay.#REGIONS = json.offeringSettings.regions;
RemotePlay.XHOME_TOKEN = json.gsToken; RemotePlay.XHOME_TOKEN = json.gsToken;
callback(); callback();
}); });
@ -3737,13 +3788,6 @@ class RemotePlay {
return; return;
} }
let servers;
if (!REMOTE_PLAY_SERVER) {
servers = ['wus2', 'eus', 'uks']; // Possible values: wus2 (WestUS2), eus (EastUS), uks (UkSouth)
} else {
servers = REMOTE_PLAY_SERVER;
}
const options = { const options = {
method: 'GET', method: 'GET',
headers: { headers: {
@ -3752,16 +3796,16 @@ class RemotePlay {
}; };
// Test servers one by one // Test servers one by one
for (const server of servers) { for (const region of RemotePlay.#REGIONS) {
try { try {
const url = `https://${server}.gssv-play-prodxhome.xboxlive.com/v6/servers/home?mr=50`; const url = `${region.baseUri}/v6/servers/home?mr=50`;
const resp = await fetch(url, options); const resp = await fetch(url, options);
const json = await resp.json(); const json = await resp.json();
RemotePlay.#CONSOLES = json.results; RemotePlay.#CONSOLES = json.results;
// Store working server // Store working server
REMOTE_PLAY_SERVER = server; REMOTE_PLAY_SERVER = region.baseUri;
callback(); callback();
} catch (e) {} } catch (e) {}
@ -3815,6 +3859,7 @@ class RemotePlay {
static togglePopup(force = null) { static togglePopup(force = null) {
if (!getPref(Preferences.REMOTE_PLAY_ENABLED) || !RemotePlay.isReady()) { if (!getPref(Preferences.REMOTE_PLAY_ENABLED) || !RemotePlay.isReady()) {
Toast.show(t('getting-consoles-list'));
return; return;
} }
@ -7048,7 +7093,7 @@ class UserAgent {
} }
if (!newUserAgent) { if (!newUserAgent) {
newUserAgent = UserAgent.get(profile) || defaultUserAgent; newUserAgent = UserAgent.get(profile);
} }
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent // Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
@ -7116,6 +7161,8 @@ class Preferences {
static get USER_AGENT_CUSTOM() { return 'user_agent_custom'; } static get USER_AGENT_CUSTOM() { return 'user_agent_custom'; }
static get STREAM_SIMPLIFY_MENU() { return 'stream_simplify_menu'; } static get STREAM_SIMPLIFY_MENU() { return 'stream_simplify_menu'; }
static get STREAM_COMBINE_SOURCES() { return 'stream_combine_sources'; }
static get STREAM_TOUCH_CONTROLLER() { return 'stream_touch_controller'; } static get STREAM_TOUCH_CONTROLLER() { return 'stream_touch_controller'; }
static get STREAM_TOUCH_CONTROLLER_AUTO_OFF() { return 'stream_touch_controller_auto_off'; } static get STREAM_TOUCH_CONTROLLER_AUTO_OFF() { return 'stream_touch_controller_auto_off'; }
static get STREAM_TOUCH_CONTROLLER_STYLE_STANDARD() { return 'stream_touch_controller_style_standard'; } static get STREAM_TOUCH_CONTROLLER_STYLE_STANDARD() { return 'stream_touch_controller_style_standard'; }
@ -7347,6 +7394,12 @@ class Preferences {
'default': false, 'default': false,
}, },
[Preferences.STREAM_COMBINE_SOURCES]: {
'default': false,
'experimental': true,
'note': t('combine-audio-video-streams-summary'),
},
[Preferences.STREAM_TOUCH_CONTROLLER]: { [Preferences.STREAM_TOUCH_CONTROLLER]: {
'default': 'all', 'default': 'all',
'options': { 'options': {
@ -7579,6 +7632,7 @@ class Preferences {
}, },
[Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: { [Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: {
'default': false, 'default': false,
'experimental': true,
}, },
[Preferences.AUDIO_VOLUME]: { [Preferences.AUDIO_VOLUME]: {
'type': SettingElement.TYPE_NUMBER_STEPPER, 'type': SettingElement.TYPE_NUMBER_STEPPER,
@ -7908,6 +7962,16 @@ class Patcher {
return funcStr.replace(text, `connectMode:window.BX_REMOTE_PLAY_CONFIG?"xhome-connect":"cloud-connect",remotePlayServerId:(window.BX_REMOTE_PLAY_CONFIG&&window.BX_REMOTE_PLAY_CONFIG.serverId)||''`); return funcStr.replace(text, `connectMode:window.BX_REMOTE_PLAY_CONFIG?"xhome-connect":"cloud-connect",remotePlayServerId:(window.BX_REMOTE_PLAY_CONFIG&&window.BX_REMOTE_PLAY_CONFIG.serverId)||''`);
}, },
// Fix the Guide/Nexus button not working in Remote Play
remotePlayGuideWorkaround: function(funcStr) {
const text = 'nexusButtonHandler:this.featureGates.EnableClientGuideInStream';
if (!funcStr.includes(text)) {
return false;
}
return funcStr.replace(text, `nexusButtonHandler: !window.BX_REMOTE_PLAY_CONFIG && this.featureGates.EnableClientGuideInStream`);
},
// Disable trackEvent() function // Disable trackEvent() function
disableTrackEvent: function(funcStr) { disableTrackEvent: function(funcStr) {
const text = 'this.trackEvent='; const text = 'this.trackEvent=';
@ -8170,6 +8234,32 @@ if (gamepadFound) {
funcStr = funcStr.replace(text, newCode + text); funcStr = funcStr.replace(text, newCode + text);
return funcStr; return funcStr;
}, },
streamCombineSources: function(funcStr) {
const text = 'this.useCombinedAudioVideoStream=!!this.deviceInformation.isTizen';
if (!funcStr.includes(text)) {
return false;
}
funcStr = funcStr.replace(text, 'this.useCombinedAudioVideoStream=true');
return funcStr;
},
patchStreamHud: function(funcStr) {
const text = 'let{onCollapse';
if (!funcStr.includes(text)) {
return false;
}
// Restore the "..." button
funcStr = funcStr.replace(text, 'e.guideUI = null;' + text);
// Remove the TAK Edit button when the touch controller is disabled
if (getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'off') {
funcStr = funcStr.replace(text, 'e.canShowTakHUD = false;' + text);
}
return funcStr;
},
}; };
static #PATCH_ORDERS = [ static #PATCH_ORDERS = [
@ -8210,6 +8300,9 @@ if (gamepadFound) {
// Only when playing // Only when playing
static #PLAYING_PATCH_ORDERS = [ static #PLAYING_PATCH_ORDERS = [
getPref(Preferences.REMOTE_PLAY_ENABLED) && ['remotePlayConnectMode'], getPref(Preferences.REMOTE_PLAY_ENABLED) && ['remotePlayConnectMode'],
getPref(Preferences.REMOTE_PLAY_ENABLED) && ['remotePlayGuideWorkaround'],
['patchStreamHud'],
['playVibration'], ['playVibration'],
HAS_TOUCH_SUPPORT && getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'all' && ['exposeTouchLayoutManager'], HAS_TOUCH_SUPPORT && getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'all' && ['exposeTouchLayoutManager'],
@ -8223,6 +8316,8 @@ if (gamepadFound) {
'disableGamepadDisconnectedScreen', 'disableGamepadDisconnectedScreen',
ENABLE_NATIVE_MKB_BETA && 'mkbMouseAndKeyboardEnabled', ENABLE_NATIVE_MKB_BETA && 'mkbMouseAndKeyboardEnabled',
], ],
getPref(Preferences.STREAM_COMBINE_SOURCES) && ['streamCombineSources'],
]; ];
static #patchFunctionBind() { static #patchFunctionBind() {
@ -8358,7 +8453,7 @@ if (gamepadFound) {
function checkForUpdate() { function checkForUpdate() {
const CHECK_INTERVAL_SECONDS = 4 * 3600; // check every 4 hours const CHECK_INTERVAL_SECONDS = 2 * 3600; // check every 2 hours
const currentVersion = getPref(Preferences.CURRENT_VERSION); const currentVersion = getPref(Preferences.CURRENT_VERSION);
const lastCheck = getPref(Preferences.LAST_UPDATE_CHECK); const lastCheck = getPref(Preferences.LAST_UPDATE_CHECK);
@ -8487,6 +8582,11 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
left: -9999px; left: -9999px;
} }
/* Remove the "Cloud Gaming" text in header */
header a[href="/play"] {
display: none;
}
a.bx-button { a.bx-button {
display: inline-block; display: inline-block;
} }
@ -8577,31 +8677,27 @@ a.bx-button.bx-full-width {
text-align: center; text-align: center;
} }
.bx-remote-play-button { .bx-header-remote-play-button {
height: auto; height: auto;
margin-right: 8px !important; margin-right: 8px !important;
} }
.bx-remote-play-button svg { .bx-header-remote-play-button svg {
width: 28px; width: 24px;
height: 46px; height: 46px;
} }
.bx-remote-play-button[disabled] { .bx-header-settings-button {
opacity: 0.5;
}
.bx-settings-button {
line-height: 30px; line-height: 30px;
font-size: 14px; font-size: 14px;
text-transform: none; text-transform: uppercase;
} }
.bx-settings-button[data-update-available]::after { .bx-header-settings-button[data-update-available]::after {
content: ' 🌟'; content: ' 🌟';
} }
.bx-button.bx-focusable, .bx-settings-button { .bx-button.bx-focusable, .bx-header-settings-button {
position: relative; position: relative;
} }
@ -9817,6 +9913,10 @@ body:not([data-media-type=tv]) div[class*=MenuItem-module__label] {
// Hide scrollbar // Hide scrollbar
if (getPref(Preferences.UI_SCROLLBAR_HIDE)) { if (getPref(Preferences.UI_SCROLLBAR_HIDE)) {
css += ` css += `
html {
scrollbar-width: none;
}
body::-webkit-scrollbar { body::-webkit-scrollbar {
display: none; display: none;
} }
@ -9828,16 +9928,16 @@ body::-webkit-scrollbar {
} }
function getPreferredServerRegion() { function getPreferredServerRegion(shortName = false) {
let preferredRegion = getPref(Preferences.SERVER_REGION); let preferredRegion = getPref(Preferences.SERVER_REGION);
if (preferredRegion in SERVER_REGIONS) { if (preferredRegion in SERVER_REGIONS) {
return preferredRegion; return shortName ? SERVER_REGIONS[preferredRegion].shortName : preferredRegion;
} }
for (let regionName in SERVER_REGIONS) { for (let regionName in SERVER_REGIONS) {
const region = SERVER_REGIONS[regionName]; const region = SERVER_REGIONS[regionName];
if (region.isDefault) { if (region.isDefault) {
return regionName; return shortName ? region.shortName : regionName;
} }
} }
@ -9983,16 +10083,15 @@ function interceptHttpRequests() {
const consoleAddrs = {}; const consoleAddrs = {};
const patchIceCandidates = function(...arg) { const patchIceCandidates = async (...arg) => {
// ICE server candidates // ICE server candidates
const request = arg[0]; const request = arg[0];
const url = (typeof request === 'string') ? request : request.url; const url = (typeof request === 'string') ? request : request.url;
if (url && url.endsWith('/ice') && url.includes('/sessions/') && request.method === 'GET') { if (url && url.endsWith('/ice') && url.includes('/sessions/') && request.method === 'GET') {
const promise = NATIVE_FETCH(...arg); const response = await NATIVE_FETCH(...arg);
const text = await response.clone().text();
return promise.then(response => {
return response.clone().text().then(text => {
if (!text.length) { if (!text.length) {
return response; return response;
} }
@ -10011,8 +10110,6 @@ function interceptHttpRequests() {
response.text = () => Promise.resolve(JSON.stringify(obj)); response.text = () => Promise.resolve(JSON.stringify(obj));
return response; return response;
});
});
} }
return null; return null;
@ -10041,7 +10138,7 @@ function interceptHttpRequests() {
BxEvent.dispatch(window, BxEvent.STREAM_STARTING); BxEvent.dispatch(window, BxEvent.STREAM_STARTING);
} }
if (IS_REMOTE_PLAYING && (url.includes('/sessions/home') || url.includes('inputconfigs'))) { if (url.includes('/sessions/home') || (IS_REMOTE_PLAYING && url.includes('inputconfigs'))) {
TouchController.disable(); TouchController.disable();
const clone = request.clone(); const clone = request.clone();
@ -10069,7 +10166,7 @@ function interceptHttpRequests() {
} }
const index = request.url.indexOf('.xboxlive.com'); const index = request.url.indexOf('.xboxlive.com');
let newUrl = `https://${REMOTE_PLAY_SERVER}.gssv-play-prodxhome` + request.url.substring(index); let newUrl = REMOTE_PLAY_SERVER + request.url.substring(index + 13);
request = new Request(newUrl, opts); request = new Request(newUrl, opts);
@ -10078,10 +10175,9 @@ function interceptHttpRequests() {
// Get console IP // Get console IP
if (url.includes('/configuration')) { if (url.includes('/configuration')) {
const promise = NATIVE_FETCH(...arg); const response = await NATIVE_FETCH(...arg);
return promise.then(response => { const obj = await response.clone().json()
return response.clone().json().then(obj => {
console.log(obj); console.log(obj);
const serverDetails = obj.serverDetails; const serverDetails = obj.serverDetails;
@ -10097,13 +10193,10 @@ function interceptHttpRequests() {
response.text = () => Promise.resolve(JSON.stringify(obj)); response.text = () => Promise.resolve(JSON.stringify(obj));
return response; return response;
});
});
} else if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && url.includes('inputconfigs')) { } else if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && url.includes('inputconfigs')) {
const promise = NATIVE_FETCH(...arg); const response = await NATIVE_FETCH(...arg);
const obj = await response.clone().json();
return promise.then(response => {
return response.clone().json().then(obj => {
const xboxTitleId = JSON.parse(opts.body).titleIds[0]; const xboxTitleId = JSON.parse(opts.body).titleIds[0];
GAME_XBOX_TITLE_ID = xboxTitleId; GAME_XBOX_TITLE_ID = xboxTitleId;
@ -10130,14 +10223,12 @@ function interceptHttpRequests() {
response.text = () => Promise.resolve(JSON.stringify(obj)); response.text = () => Promise.resolve(JSON.stringify(obj));
return response; return response;
});
});
} }
return patchIceCandidates(...arg) || NATIVE_FETCH(...arg); return await patchIceCandidates(...arg) || NATIVE_FETCH(...arg);
} }
if (IS_REMOTE_PLAYING && url.includes('/login/user')) { if (IS_REMOTE_PLAYING && url.includes('xhome') && url.includes('/login/user')) {
try { try {
const clone = request.clone(); const clone = request.clone();
@ -10182,23 +10273,56 @@ function interceptHttpRequests() {
} }
// ICE server candidates // ICE server candidates
const patchedIpv6 = patchIceCandidates(...arg); const patchedIpv6 = await patchIceCandidates(...arg);
if (patchedIpv6) { if (patchedIpv6) {
return patchedIpv6; return patchedIpv6;
} }
// Server list // Server list
if (!url.includes('xhome.') && url.endsWith('/v2/login/user')) { if (!url.includes('xhome.') && url.endsWith('/v2/login/user')) {
const promise = NATIVE_FETCH(...arg); const response = await NATIVE_FETCH(...arg);
const obj = await response.clone().json();
// Preload Remote Play
BX_FLAGS.PreloadRemotePlay && RemotePlay.preload();
return promise.then(response => {
return response.clone().json().then(obj => {
// Store xCloud token // Store xCloud token
RemotePlay.XCLOUD_TOKEN = obj.gsToken; RemotePlay.XCLOUD_TOKEN = obj.gsToken;
// Get server list // Get server list
if (!Object.keys(SERVER_REGIONS).length) { if (!Object.keys(SERVER_REGIONS).length) {
const serverEmojis = {
AustraliaEast: '🇦🇺',
AustraliaSouthEast: '🇦🇺',
BrazilSouth: '🇧🇷',
EastUS: '🇺🇸',
EastUS2: '🇺🇸',
JapanEast: '🇯🇵',
KoreaCentral: '🇰🇷',
MexicoCentral: '🇲🇽',
NorthCentralUs: '🇺🇸',
SouthCentralUS: '🇺🇸',
UKSouth: '🇬🇧',
WestEurope: '🇪🇺',
WestUS: '🇺🇸',
WestUS2: '🇺🇸',
};
const regex = /\/\/(\w+)\./;
for (let region of obj.offeringSettings.regions) { for (let region of obj.offeringSettings.regions) {
let shortName = region.name;
let match = regex.exec(region.baseUri);
if (match) {
shortName = match[1];
if (serverEmojis[region.name]) {
shortName = serverEmojis[region.name] + ' ' + shortName;
}
}
region.shortName = shortName.toUpperCase();
SERVER_REGIONS[region.name] = Object.assign({}, region); SERVER_REGIONS[region.name] = Object.assign({}, region);
} }
@ -10220,8 +10344,6 @@ function interceptHttpRequests() {
response.json = () => Promise.resolve(obj); response.json = () => Promise.resolve(obj);
return response; return response;
});
});
} }
// Get region // Get region
@ -10266,22 +10388,18 @@ function interceptHttpRequests() {
// Get wait time // Get wait time
if (PREF_UI_LOADING_SCREEN_WAIT_TIME && url.includes('xboxlive.com') && url.includes('/waittime/')) { if (PREF_UI_LOADING_SCREEN_WAIT_TIME && url.includes('xboxlive.com') && url.includes('/waittime/')) {
const promise = NATIVE_FETCH(...arg); const response = await NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(json => { const json = await response.clone.json();
if (json.estimatedAllocationTimeInSeconds > 0) { if (json.estimatedAllocationTimeInSeconds > 0) {
// Setup wait time overlay // Setup wait time overlay
LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds); LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds);
} }
return response; return response;
});
});
} }
if (url.endsWith('/configuration') && url.includes('/sessions/cloud/') && request.method === 'GET') { if (url.endsWith('/configuration') && url.includes('/sessions/cloud/') && request.method === 'GET') {
const promise = NATIVE_FETCH(...arg);
// Touch controller for all games // Touch controller for all games
if (PREF_STREAM_TOUCH_CONTROLLER === 'all') { if (PREF_STREAM_TOUCH_CONTROLLER === 'all') {
TouchController.disable(); TouchController.disable();
@ -10296,8 +10414,8 @@ function interceptHttpRequests() {
} }
// Intercept configurations // Intercept configurations
return promise.then(response => { const response = await NATIVE_FETCH(...arg);
return response.clone().text().then(text => { const text = await response.clone().text();
if (!text.length) { if (!text.length) {
return response; return response;
} }
@ -10329,35 +10447,28 @@ function interceptHttpRequests() {
response.text = () => Promise.resolve(JSON.stringify(obj)); response.text = () => Promise.resolve(JSON.stringify(obj));
return response; return response;
});
});
} }
// catalog.gamepass // catalog.gamepass
if (url.startsWith('https://catalog.gamepass.com') && url.includes('/products')) { if (url.startsWith('https://catalog.gamepass.com') && url.includes('/products')) {
const promise = NATIVE_FETCH(...arg); const response = await NATIVE_FETCH(...arg);
return promise.then(response => { const json = await response.clone().json()
return response.clone().json().then(json => {
for (let productId in json.Products) { for (let productId in json.Products) {
TitlesInfo.saveFromCatalogInfo(json.Products[productId]); TitlesInfo.saveFromCatalogInfo(json.Products[productId]);
} }
return response; return response;
});
});
} }
if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && (url.includes('/titles') || url.includes('/mru'))) { if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && (url.includes('/v2/titles') || url.includes('/mru'))) {
const promise = NATIVE_FETCH(...arg); const response = await NATIVE_FETCH(...arg);
return promise.then(response => { const json = await response.clone().json()
return response.clone().json().then(json => {
for (let game of json.results) { for (let game of json.results) {
TitlesInfo.saveFromTitleInfo(game); TitlesInfo.saveFromTitleInfo(game);
} }
return response; return response;
});
});
} }
return NATIVE_FETCH(...arg); return NATIVE_FETCH(...arg);
@ -10365,60 +10476,17 @@ function interceptHttpRequests() {
} }
function injectSettingsButton($parent) { function setupSettingsUi() {
if (!$parent) { // Avoid rendering the Settings multiple times
if (document.querySelector('.bx-settings-container')) {
return; return;
} }
const PREF_PREFERRED_REGION = getPreferredServerRegion(); const PREF_PREFERRED_REGION = getPreferredServerRegion();
const PREF_LATEST_VERSION = getPref(Preferences.LATEST_VERSION); const PREF_LATEST_VERSION = getPref(Preferences.LATEST_VERSION);
const $headerFragment = document.createDocumentFragment();
let $reloadBtnWrapper; let $reloadBtnWrapper;
// Remote Play button
if (getPref(Preferences.REMOTE_PLAY_ENABLED)) {
const $remotePlayBtn = createButton({
classes: ['bx-remote-play-button'],
icon: Icon.REMOTE_PLAY,
title: t('remote-play'),
disabled: !RemotePlay.isReady(),
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE,
onClick: e => {
RemotePlay.togglePopup();
},
});
$headerFragment.appendChild($remotePlayBtn);
}
// Setup Settings button
const $settingsBtn = createButton({
classes: ['bx-settings-button'],
label: PREF_PREFERRED_REGION,
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
onClick: e => {
const $settings = document.querySelector('.bx-settings-container');
$settings.classList.toggle('bx-gone');
window.scrollTo(0, 0);
document.activeElement && document.activeElement.blur();
},
});
// Show new update status
if (PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
$settingsBtn.setAttribute('data-update-available', true);
}
// Add the Settings button to the web page
$headerFragment.appendChild($settingsBtn);
$parent.appendChild($headerFragment);
// Avoid rendering the Settings multiple times
if (document.querySelector('.bx-settings-container')) {
return;
}
// Setup Settings UI // Setup Settings UI
const $container = CE('div', { const $container = CE('div', {
'class': 'bx-settings-container bx-gone', 'class': 'bx-settings-container bx-gone',
@ -10481,12 +10549,14 @@ function injectSettingsButton($parent) {
[Preferences.STREAM_TARGET_RESOLUTION]: t('target-resolution'), [Preferences.STREAM_TARGET_RESOLUTION]: t('target-resolution'),
[Preferences.STREAM_CODEC_PROFILE]: t('visual-quality'), [Preferences.STREAM_CODEC_PROFILE]: t('visual-quality'),
[Preferences.GAME_FORTNITE_FORCE_CONSOLE]: '🎮 ' + t('fortnite-force-console-version'), [Preferences.GAME_FORTNITE_FORCE_CONSOLE]: '🎮 ' + t('fortnite-force-console-version'),
[Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: t('enable-volume-control'),
[Preferences.AUDIO_MIC_ON_PLAYING]: t('enable-mic-on-startup'), [Preferences.AUDIO_MIC_ON_PLAYING]: t('enable-mic-on-startup'),
[Preferences.STREAM_DISABLE_FEEDBACK_DIALOG]: t('disable-post-stream-feedback-dialog'), [Preferences.STREAM_DISABLE_FEEDBACK_DIALOG]: t('disable-post-stream-feedback-dialog'),
[Preferences.SCREENSHOT_BUTTON_POSITION]: t('screenshot-button-position'), [Preferences.SCREENSHOT_BUTTON_POSITION]: t('screenshot-button-position'),
[Preferences.SCREENSHOT_APPLY_FILTERS]: t('screenshot-apply-filters'), [Preferences.SCREENSHOT_APPLY_FILTERS]: t('screenshot-apply-filters'),
[Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: t('enable-volume-control'),
[Preferences.STREAM_COMBINE_SOURCES]: t('combine-audio-video-streams'),
}, },
[t('local-co-op')]: { [t('local-co-op')]: {
@ -10521,9 +10591,9 @@ function injectSettingsButton($parent) {
}, },
[t('ui')]: { [t('ui')]: {
[Preferences.UI_LAYOUT]: t('layout'), [Preferences.UI_LAYOUT]: t('layout'),
[Preferences.UI_SCROLLBAR_HIDE]: t('hide-scrollbar'),
[Preferences.STREAM_SIMPLIFY_MENU]: t('simplify-stream-menu'), [Preferences.STREAM_SIMPLIFY_MENU]: t('simplify-stream-menu'),
[Preferences.SKIP_SPLASH_VIDEO]: t('skip-splash-video'), [Preferences.SKIP_SPLASH_VIDEO]: t('skip-splash-video'),
[!AppInterface && Preferences.UI_SCROLLBAR_HIDE]: t('hide-scrollbar'),
[Preferences.HIDE_DOTS_ICON]: t('hide-system-menu-icon'), [Preferences.HIDE_DOTS_ICON]: t('hide-system-menu-icon'),
[Preferences.REDUCE_ANIMATIONS]: t('reduce-animations'), [Preferences.REDUCE_ANIMATIONS]: t('reduce-animations'),
}, },
@ -10571,14 +10641,27 @@ function injectSettingsButton($parent) {
for (let settingId in SETTINGS_UI[groupLabel]) { for (let settingId in SETTINGS_UI[groupLabel]) {
// Don't render custom settings // Don't render custom settings
if (settingId.startsWith('_')) { if (!settingId || settingId === 'false' || settingId === 'undefined' || settingId.startsWith('_')) {
continue; continue;
} }
const setting = Preferences.SETTINGS[settingId]; const setting = Preferences.SETTINGS[settingId];
if (!setting) {
continue;
}
const settingLabel = SETTINGS_UI[groupLabel][settingId]; let settingLabel = SETTINGS_UI[groupLabel][settingId];
const settingNote = setting.note; let settingNote = setting.note || '';
// Add Experimental text
if (setting.experimental) {
settingLabel = '🧪 ' + settingLabel;
if (!settingNote) {
settingNote = t('experimental')
} else {
settingNote = `${t('experimental')}: ${settingNote}`
}
}
let $control, $inpCustomUserAgent; let $control, $inpCustomUserAgent;
let labelAttrs = {}; let labelAttrs = {};
@ -10623,7 +10706,7 @@ function injectSettingsButton($parent) {
const region = SERVER_REGIONS[regionName]; const region = SERVER_REGIONS[regionName];
let value = regionName; let value = regionName;
let label = regionName; let label = `${region.shortName} - ${regionName}`;
if (region.isDefault) { if (region.isDefault) {
label += ` (${t('default')})`; label += ` (${t('default')})`;
value = 'default'; value = 'default';
@ -10709,6 +10792,57 @@ function injectSettingsButton($parent) {
$pageContent.parentNode.insertBefore($container, $pageContent); $pageContent.parentNode.insertBefore($container, $pageContent);
} }
function injectSettingsButton($parent) {
if (!$parent) {
return;
}
const PREF_PREFERRED_REGION = getPreferredServerRegion(true);
const PREF_LATEST_VERSION = getPref(Preferences.LATEST_VERSION);
const $headerFragment = document.createDocumentFragment();
// Remote Play button
if (getPref(Preferences.REMOTE_PLAY_ENABLED)) {
const $remotePlayBtn = createButton({
classes: ['bx-header-remote-play-button'],
icon: Icon.REMOTE_PLAY,
title: t('remote-play'),
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE,
onClick: e => {
RemotePlay.togglePopup();
},
});
$headerFragment.appendChild($remotePlayBtn);
}
// Setup Settings button
const $settingsBtn = createButton({
classes: ['bx-header-settings-button'],
label: PREF_PREFERRED_REGION,
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
onClick: e => {
setupSettingsUi();
const $settings = document.querySelector('.bx-settings-container');
$settings.classList.toggle('bx-gone');
window.scrollTo(0, 0);
document.activeElement && document.activeElement.blur();
},
});
// Show new update status
if (PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
$settingsBtn.setAttribute('data-update-available', true);
}
// Add the Settings button to the web page
$headerFragment.appendChild($settingsBtn);
$parent.appendChild($headerFragment);
}
function getVideoPlayerFilterStyle() { function getVideoPlayerFilterStyle() {
const filters = []; const filters = [];
@ -10811,7 +10945,7 @@ div[data-testid="media-container"] {
function checkHeader() { function checkHeader() {
const $button = document.querySelector('.bx-settings-button'); const $button = document.querySelector('.bx-header-settings-button');
if (!$button) { if (!$button) {
const $rightHeader = document.querySelector('#PageContent div[class*=EdgewaterHeader-module__rightSectionSpacing]'); const $rightHeader = document.querySelector('#PageContent div[class*=EdgewaterHeader-module__rightSectionSpacing]');
@ -11135,6 +11269,10 @@ function patchVideoApi() {
return nativePlay.apply(this); return nativePlay.apply(this);
} }
if (!!this.src) {
return nativePlay.apply(this);
}
this.addEventListener('playing', showFunc); this.addEventListener('playing', showFunc);
injectStreamMenuButtons(); injectStreamMenuButtons();
@ -11769,7 +11907,7 @@ window.addEventListener(BxEvent.STREAM_STOPPED, e => {
PreloadedState.override(); PreloadedState.override();
// Check for Update // Check for Update
checkForUpdate(); BX_FLAGS.CheckForUpdate && checkForUpdate();
// Monkey patches // Monkey patches
if (getPref(Preferences.AUDIO_ENABLE_VOLUME_CONTROL)) { if (getPref(Preferences.AUDIO_ENABLE_VOLUME_CONTROL)) {
@ -11866,7 +12004,6 @@ Patcher.initialize();
// Preload Remote Play // Preload Remote Play
if (getPref(Preferences.REMOTE_PLAY_ENABLED)) { if (getPref(Preferences.REMOTE_PLAY_ENABLED)) {
BX_FLAGS.PreloadRemotePlay && RemotePlay.preload();
RemotePlay.detect(); RemotePlay.detect();
} }