Compare commits

...

10 Commits

3 changed files with 100 additions and 42 deletions

View File

@ -1,9 +1,12 @@
# Better xCloud # Better xCloud
Improve Xbox Cloud Gaming (xCloud) experience on [xbox.com/play](https://www.xbox.com/play). It also allows you to use Remote Play on the xCloud website. Improve Xbox Cloud Gaming (xCloud) experience on [xbox.com/play](https://www.xbox.com/play). It also allows you to use Remote Play on the xCloud website.
> [!NOTE] > [!TIP]
> The Android app is in development at [redphx/better-xcloud-android](https://github.com/redphx/better-xcloud-android) > The Android app is in development at [redphx/better-xcloud-android](https://github.com/redphx/better-xcloud-android)
> [!IMPORTANT]
> I don't accept pull requests at the moment (except PR for custom touch controls)
**Supported platforms:** **Supported platforms:**
- Windows - Windows
- macOS - macOS

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.2.5 // @version 3.3.0
// ==/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.2.5 // @version 3.3.0
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
@ -14,12 +14,23 @@
// ==/UserScript== // ==/UserScript==
'use strict'; 'use strict';
const SCRIPT_VERSION = '3.2.5'; /* ADDITIONAL CODE */
const SCRIPT_VERSION = '3.3.0';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud'; const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
const ENABLE_XCLOUD_LOGGER = false; // Setup flags
const ENABLE_PRELOAD_BX_UI = false; const DEFAULT_FLAGS = {
const USE_DEV_TOUCH_LAYOUT = false; PreloadUi: false,
EnableXcloudLogging: false,
UseDevTouchLayout: false,
}
const BX_FLAGS = Object.assign(DEFAULT_FLAGS, window.BX_FLAGS || {});
delete window.BX_FLAGS;
const AppInterface = window.AppInterface;
let REMOTE_PLAY_SERVER; let REMOTE_PLAY_SERVER;
@ -89,7 +100,7 @@ const BxEvent = {
} }
} }
window.AppInterface && window.AppInterface.onEvent(eventName); AppInterface && AppInterface.onEvent(eventName);
target.dispatchEvent(event); target.dispatchEvent(event);
}, },
}; };
@ -1041,7 +1052,7 @@ const Translations = {
"Desconectado", "Desconectado",
"Отключен", "Отключен",
"Bağlı değil", "Bağlı değil",
"Роз’єднано", "Від'єднано",
"Đã ngắt kết nối", "Đã ngắt kết nối",
"已断开连接", "已断开连接",
], ],
@ -1179,7 +1190,7 @@ const Translations = {
"\"Uzaktan Oynama\" özelliğini aktive et", "\"Uzaktan Oynama\" özelliğini aktive et",
"Увімкнути функцію \"Remote Play\"", "Увімкнути функцію \"Remote Play\"",
"Bật tính năng \"Chơi Từ Xa\"", "Bật tính năng \"Chơi Từ Xa\"",
"启用\"远程播放\"功能", "启用\"Remote Play\"主机串流",
], ],
"enable-volume-control": [ "enable-volume-control": [
"Lautstärkeregelung aktivieren", "Lautstärkeregelung aktivieren",
@ -1394,7 +1405,7 @@ const Translations = {
, ,
"Android用のBetter xCloudをインストール", "Android用のBetter xCloudをインストール",
, ,
"Zainstaluj aplikację xCloud na Androida", "Zainstaluj aplikację Better xCloud na Androida",
"Instalar o aplicativo Better xCloud para Android", "Instalar o aplicativo Better xCloud para Android",
"Установите приложение Better xCloud для Android", "Установите приложение Better xCloud для Android",
"Better xCloud'un Android uygulamasını indir", "Better xCloud'un Android uygulamasını indir",
@ -2097,7 +2108,7 @@ const Translations = {
"Uzaktan Bağlanma", "Uzaktan Bağlanma",
"Віддалена гра", "Віддалена гра",
"Chơi Từ Xa", "Chơi Từ Xa",
"远程游玩", "远程串流",
], ],
"rename": [ "rename": [
"Umbenennen", "Umbenennen",
@ -2269,6 +2280,23 @@ const Translations = {
"Lưu", "Lưu",
"保存", "保存",
], ],
"screenshot-apply-filters": [
,
,
"Applies video filters to screenshots",
,
,
,
"スクリーンショットにビデオフィルターを適用",
,
,
,
"Применяет фильтры видео к скриншотам",
,
"Застосовує відеофільтри до знімків екрана",
"Á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",
"Posisi tombol Screenshot", "Posisi tombol Screenshot",
@ -2282,7 +2310,7 @@ const Translations = {
"Posição do botão de captura de tela", "Posição do botão de captura de tela",
"Расположение кнопки скриншота", "Расположение кнопки скриншота",
"Ekran görüntüsü düğmesi konumu", "Ekran görüntüsü düğmesi konumu",
"Позиція кнопки скриншоту", "Позиція кнопки знімка екрана",
"Vị trí của nút Chụp màn hình", "Vị trí của nút Chụp màn hình",
"截图按钮位置", "截图按钮位置",
], ],
@ -2299,7 +2327,7 @@ const Translations = {
"Separar o Controle por Toque e o Controle #1", "Separar o Controle por Toque e o Controle #1",
"Раздельный сенсорный контроллер и контроллер #1", "Раздельный сенсорный контроллер и контроллер #1",
"Dokunmatik kumandayı ve birincil kumandayı ayrı tut", "Dokunmatik kumandayı ve birincil kumandayı ayrı tut",
"Окремо Сенсорний контролер та Контролер #1", "Відокремити Сенсорний контролер та Контролер #1",
"Tách biệt Bộ điều khiển cảm ứng và Tay cầm #1", "Tách biệt Bộ điều khiển cảm ứng và Tay cầm #1",
"虚拟摇杆和手柄分别控制不同角色", "虚拟摇杆和手柄分别控制不同角色",
], ],
@ -2945,7 +2973,7 @@ const Translations = {
"Superior-centralizado", "Superior-centralizado",
"Сверху", "Сверху",
"Orta üst", "Orta üst",
"Зверху праворуч", "Зверху по центру",
"Chính giữa phía trên", "Chính giữa phía trên",
"顶部居中", "顶部居中",
], ],
@ -3302,7 +3330,7 @@ const Translations = {
"Tempo estimado de conclusão", "Tempo estimado de conclusão",
"Примерное время запуска", "Примерное время запуска",
"Tahminî bitiş süresi", "Tahminî bitiş süresi",
"Розрахунковий час завершення", "Орієнтовний час завершення",
"Thời gian hoàn thành dự kiến", "Thời gian hoàn thành dự kiến",
"预计等待时间", "预计等待时间",
], ],
@ -4091,7 +4119,7 @@ class TouchController {
return; return;
} }
const baseUrl = `https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts${USE_DEV_TOUCH_LAYOUT ? '/dev' : ''}`; const baseUrl = `https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts${BX_FLAGS.UseDevTouchLayout ? '/dev' : ''}`;
const url = `${baseUrl}/${xboxTitleId}.json`; const url = `${baseUrl}/${xboxTitleId}.json`;
// Get layout info // Get layout info
@ -6191,7 +6219,7 @@ class VibrationManager {
static #playDeviceVibration(data) { static #playDeviceVibration(data) {
// console.log(+new Date, data); // console.log(+new Date, data);
if ('AppInterface' in window) { if (AppInterface) {
AppInterface.vibrate(JSON.stringify(data), window.BX_VIBRATION_INTENSITY); AppInterface.vibrate(JSON.stringify(data), window.BX_VIBRATION_INTENSITY);
return; return;
} }
@ -7020,6 +7048,8 @@ class Preferences {
static get MKB_DEFAULT_PRESET_ID() { return 'mkb_default_preset_id'; } static get MKB_DEFAULT_PRESET_ID() { return 'mkb_default_preset_id'; }
static get SCREENSHOT_BUTTON_POSITION() { return 'screenshot_button_position'; } static get SCREENSHOT_BUTTON_POSITION() { return 'screenshot_button_position'; }
static get SCREENSHOT_APPLY_FILTERS() { return 'screenshot_apply_filters'; }
static get BLOCK_TRACKING() { return 'block_tracking'; } static get BLOCK_TRACKING() { return 'block_tracking'; }
static get BLOCK_SOCIAL_FEATURES() { return 'block_social_features'; } static get BLOCK_SOCIAL_FEATURES() { return 'block_social_features'; }
static get SKIP_SPLASH_VIDEO() { return 'skip_splash_video'; } static get SKIP_SPLASH_VIDEO() { return 'skip_splash_video'; }
@ -7207,6 +7237,7 @@ class Preferences {
[Preferences.PREFER_IPV6_SERVER]: { [Preferences.PREFER_IPV6_SERVER]: {
'default': false, 'default': false,
}, },
[Preferences.SCREENSHOT_BUTTON_POSITION]: { [Preferences.SCREENSHOT_BUTTON_POSITION]: {
'default': 'bottom-left', 'default': 'bottom-left',
'options': { 'options': {
@ -7215,6 +7246,10 @@ class Preferences {
'none': t('disable'), 'none': t('disable'),
}, },
}, },
[Preferences.SCREENSHOT_APPLY_FILTERS]: {
'default': false,
},
[Preferences.SKIP_SPLASH_VIDEO]: { [Preferences.SKIP_SPLASH_VIDEO]: {
'default': false, 'default': false,
}, },
@ -8053,7 +8088,7 @@ if (gamepadFound) {
getPref(Preferences.UI_LAYOUT) === 'tv' && ['tvLayout'], getPref(Preferences.UI_LAYOUT) === 'tv' && ['tvLayout'],
ENABLE_XCLOUD_LOGGER && [ BX_FLAGS.EnableXcloudLogging && [
'enableConsoleLogging', 'enableConsoleLogging',
'enableXcloudLogger', 'enableXcloudLogger',
], ],
@ -8088,7 +8123,7 @@ if (gamepadFound) {
HAS_TOUCH_SUPPORT && getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'all' && ['exposeTouchLayoutManager'], HAS_TOUCH_SUPPORT && getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'all' && ['exposeTouchLayoutManager'],
HAS_TOUCH_SUPPORT && (getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(Preferences.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && ['disableTakRenderer'], HAS_TOUCH_SUPPORT && (getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(Preferences.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && ['disableTakRenderer'],
ENABLE_XCLOUD_LOGGER && ['enableConsoleLogging'], BX_FLAGS.EnableXcloudLogging && ['enableConsoleLogging'],
getPref(Preferences.BLOCK_TRACKING) && ['blockGamepadStatsCollector'], getPref(Preferences.BLOCK_TRACKING) && ['blockGamepadStatsCollector'],
@ -8639,6 +8674,10 @@ a.bx-button.bx-full-width {
color: #828282; color: #828282;
} }
.bx-settings-group-label b {
margin-bottom: 8px;
}
@media not (hover: hover) { @media not (hover: hover) {
.bx-settings-row:focus-within { .bx-settings-row:focus-within {
background-color: #242424; background-color: #242424;
@ -8738,12 +8777,9 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
box-sizing: border-box; box-sizing: border-box;
width: 16vh; width: 60px;
height: 16vh; height: 60px;
max-width: 128px; padding: 16px;
max-height: 128px;
padding: 2vh;
padding: 24px 24px 12px 12px;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
background-origin: content-box; background-origin: content-box;
@ -8760,7 +8796,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
} }
.bx-screenshot-button[data-capturing=true] { .bx-screenshot-button[data-capturing=true] {
padding: 1vh; padding: 8px;
} }
.bx-screenshot-canvas { .bx-screenshot-canvas {
@ -10269,7 +10305,7 @@ function injectSettingsButton($parent) {
} }
// Show link to Android app // Show link to Android app
if (!window.AppInterface) { if (!AppInterface) {
const userAgent = UserAgent.getDefault().toLowerCase(); const userAgent = UserAgent.getDefault().toLowerCase();
if (userAgent.includes('android')) { if (userAgent.includes('android')) {
const $btn = createButton({ const $btn = createButton({
@ -10302,6 +10338,9 @@ function injectSettingsButton($parent) {
[Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: t('enable-volume-control'), [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_APPLY_FILTERS]: t('screenshot-apply-filters'),
}, },
[t('local-co-op')]: { [t('local-co-op')]: {
@ -10310,8 +10349,6 @@ function injectSettingsButton($parent) {
}, },
[t('mouse-and-keyboard')]: { [t('mouse-and-keyboard')]: {
// '_note': '⚠️ ' + t('may-not-work-properly'),
// [Preferences.MKB_ENABLED]: [t('enable-mkb'), t('only-supports-some-games')],
[Preferences.MKB_ENABLED]: t('enable-mkb'), [Preferences.MKB_ENABLED]: t('enable-mkb'),
[Preferences.MKB_HIDE_IDLE_CURSOR]: t('hide-idle-cursor'), [Preferences.MKB_HIDE_IDLE_CURSOR]: t('hide-idle-cursor'),
}, },
@ -10324,6 +10361,7 @@ function injectSettingsButton($parent) {
[t('touch-controller')]: { [t('touch-controller')]: {
_note: !HAS_TOUCH_SUPPORT ? '⚠️ ' + t('device-unsupported-touch') : null, _note: !HAS_TOUCH_SUPPORT ? '⚠️ ' + t('device-unsupported-touch') : null,
_unsupported: !HAS_TOUCH_SUPPORT,
[Preferences.STREAM_TOUCH_CONTROLLER]: t('tc-availability'), [Preferences.STREAM_TOUCH_CONTROLLER]: t('tc-availability'),
[Preferences.STREAM_TOUCH_CONTROLLER_AUTO_OFF]: t('tc-auto-off'), [Preferences.STREAM_TOUCH_CONTROLLER_AUTO_OFF]: t('tc-auto-off'),
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: t('tc-standard-layout-style'), [Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: t('tc-standard-layout-style'),
@ -10341,7 +10379,6 @@ function injectSettingsButton($parent) {
[Preferences.SKIP_SPLASH_VIDEO]: t('skip-splash-video'), [Preferences.SKIP_SPLASH_VIDEO]: t('skip-splash-video'),
[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'),
[Preferences.SCREENSHOT_BUTTON_POSITION]: t('screenshot-button-position'),
}, },
[t('other')]: { [t('other')]: {
[Preferences.BLOCK_SOCIAL_FEATURES]: t('disable-social-features'), [Preferences.BLOCK_SOCIAL_FEATURES]: t('disable-social-features'),
@ -10363,6 +10400,11 @@ function injectSettingsButton($parent) {
$wrapper.appendChild($group); $wrapper.appendChild($group);
// Don't render settings if this is an unsupported feature
if (SETTINGS_UI[groupLabel]._unsupported) {
continue;
}
let onChange = e => { let onChange = e => {
if (!$reloadBtnWrapper) { if (!$reloadBtnWrapper) {
return; return;
@ -10381,6 +10423,7 @@ function injectSettingsButton($parent) {
}; };
for (let settingId in SETTINGS_UI[groupLabel]) { for (let settingId in SETTINGS_UI[groupLabel]) {
// Don't render custom settings
if (settingId.startsWith('_')) { if (settingId.startsWith('_')) {
continue; continue;
} }
@ -10575,6 +10618,11 @@ function updateVideoPlayerCss() {
videoCss += `filter: ${filters} !important;`; videoCss += `filter: ${filters} !important;`;
} }
// Apply video filters to screenshots
if (getPref(Preferences.SCREENSHOT_APPLY_FILTERS)) {
$SCREENSHOT_CANVAS.getContext('2d').filter = filters;
}
const PREF_RATIO = getPref(Preferences.VIDEO_RATIO); const PREF_RATIO = getPref(Preferences.VIDEO_RATIO);
if (PREF_RATIO && PREF_RATIO !== '16:9') { if (PREF_RATIO && PREF_RATIO !== '16:9') {
if (PREF_RATIO.includes(':')) { if (PREF_RATIO.includes(':')) {
@ -11327,6 +11375,15 @@ function takeScreenshot(callback) {
const $canvasContext = $SCREENSHOT_CANVAS.getContext('2d'); const $canvasContext = $SCREENSHOT_CANVAS.getContext('2d');
$canvasContext.drawImage($STREAM_VIDEO, 0, 0, $SCREENSHOT_CANVAS.width, $SCREENSHOT_CANVAS.height); $canvasContext.drawImage($STREAM_VIDEO, 0, 0, $SCREENSHOT_CANVAS.width, $SCREENSHOT_CANVAS.height);
// Get data URL and pass to parent app
if (AppInterface) {
const data = $SCREENSHOT_CANVAS.toDataURL('image/png').split(';base64,')[1];
AppInterface.saveScreenshot(GAME_TITLE_ID, data);
callback && callback();
return;
}
$SCREENSHOT_CANVAS.toBlob(blob => { $SCREENSHOT_CANVAS.toBlob(blob => {
// Download screenshot // Download screenshot
const now = +new Date; const now = +new Date;
@ -11445,20 +11502,17 @@ function disablePwa() {
} }
function setupBxUi() { function setupBxUi() {
updateVideoPlayerCss();
// Prevent initializing multiple times // Prevent initializing multiple times
if (document.querySelector('.bx-quick-settings-bar')) { if (!document.querySelector('.bx-quick-settings-bar')) {
return;
}
window.addEventListener('resize', updateVideoPlayerCss); window.addEventListener('resize', updateVideoPlayerCss);
setupQuickSettingsBar(); setupQuickSettingsBar();
setupScreenshotButton(); setupScreenshotButton();
StreamStats.render(); StreamStats.render();
} }
updateVideoPlayerCss();
}
// Hide Settings UI when navigate to another page // Hide Settings UI when navigate to another page
window.addEventListener(BxEvent.POPSTATE, onHistoryChanged); window.addEventListener(BxEvent.POPSTATE, onHistoryChanged);
@ -11506,6 +11560,7 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
const PREF_SCREENSHOT_BUTTON_POSITION = getPref(Preferences.SCREENSHOT_BUTTON_POSITION); const PREF_SCREENSHOT_BUTTON_POSITION = getPref(Preferences.SCREENSHOT_BUTTON_POSITION);
$SCREENSHOT_CANVAS.width = $video.videoWidth; $SCREENSHOT_CANVAS.width = $video.videoWidth;
$SCREENSHOT_CANVAS.height = $video.videoHeight; $SCREENSHOT_CANVAS.height = $video.videoHeight;
updateVideoPlayerCss();
// Setup screenshot button // Setup screenshot button
if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') { if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
@ -11641,7 +11696,7 @@ patchVideoApi();
// Setup UI // Setup UI
addCss(); addCss();
Toast.setup(); Toast.setup();
ENABLE_PRELOAD_BX_UI && setupBxUi(); BX_FLAGS.PreloadUi && setupBxUi();
disablePwa(); disablePwa();