Compare commits

...

10 Commits

3 changed files with 100 additions and 42 deletions

View File

@ -1,9 +1,12 @@
# 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.
> [!NOTE]
> [!TIP]
> 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:**
- Windows
- macOS

View File

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

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.2.5
// @version 3.3.0
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -14,12 +14,23 @@
// ==/UserScript==
'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 ENABLE_XCLOUD_LOGGER = false;
const ENABLE_PRELOAD_BX_UI = false;
const USE_DEV_TOUCH_LAYOUT = false;
// Setup flags
const DEFAULT_FLAGS = {
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;
@ -89,7 +100,7 @@ const BxEvent = {
}
}
window.AppInterface && window.AppInterface.onEvent(eventName);
AppInterface && AppInterface.onEvent(eventName);
target.dispatchEvent(event);
},
};
@ -1041,7 +1052,7 @@ const Translations = {
"Desconectado",
"Отключен",
"Bağlı değil",
"Роз’єднано",
"Від'єднано",
"Đã ngắt kết nối",
"已断开连接",
],
@ -1179,7 +1190,7 @@ const Translations = {
"\"Uzaktan Oynama\" özelliğini aktive et",
"Увімкнути функцію \"Remote Play\"",
"Bật tính năng \"Chơi Từ Xa\"",
"启用\"远程播放\"功能",
"启用\"Remote Play\"主机串流",
],
"enable-volume-control": [
"Lautstärkeregelung aktivieren",
@ -1394,7 +1405,7 @@ const Translations = {
,
"Android用のBetter xCloudをインストール",
,
"Zainstaluj aplikację xCloud na Androida",
"Zainstaluj aplikację Better xCloud na Androida",
"Instalar o aplicativo Better xCloud para Android",
"Установите приложение Better xCloud для Android",
"Better xCloud'un Android uygulamasını indir",
@ -2097,7 +2108,7 @@ const Translations = {
"Uzaktan Bağlanma",
"Віддалена гра",
"Chơi Từ Xa",
"远程游玩",
"远程串流",
],
"rename": [
"Umbenennen",
@ -2269,6 +2280,23 @@ const Translations = {
"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": [
"Position des Screenshot-Buttons",
"Posisi tombol Screenshot",
@ -2282,7 +2310,7 @@ const Translations = {
"Posição do botão de captura de tela",
"Расположение кнопки скриншота",
"Ekran görüntüsü düğmesi konumu",
"Позиція кнопки скриншоту",
"Позиція кнопки знімка екрана",
"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",
"Раздельный сенсорный контроллер и контроллер #1",
"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",
"虚拟摇杆和手柄分别控制不同角色",
],
@ -2945,7 +2973,7 @@ const Translations = {
"Superior-centralizado",
"Сверху",
"Orta üst",
"Зверху праворуч",
"Зверху по центру",
"Chính giữa phía trên",
"顶部居中",
],
@ -3302,7 +3330,7 @@ const Translations = {
"Tempo estimado de conclusão",
"Примерное время запуска",
"Tahminî bitiş süresi",
"Розрахунковий час завершення",
"Орієнтовний час завершення",
"Thời gian hoàn thành dự kiến",
"预计等待时间",
],
@ -4091,7 +4119,7 @@ class TouchController {
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`;
// Get layout info
@ -6191,7 +6219,7 @@ class VibrationManager {
static #playDeviceVibration(data) {
// console.log(+new Date, data);
if ('AppInterface' in window) {
if (AppInterface) {
AppInterface.vibrate(JSON.stringify(data), window.BX_VIBRATION_INTENSITY);
return;
}
@ -6927,7 +6955,7 @@ class UserAgent {
} else {
return;
}
}
if (!newUserAgent) {
@ -7020,6 +7048,8 @@ class Preferences {
static get MKB_DEFAULT_PRESET_ID() { return 'mkb_default_preset_id'; }
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_SOCIAL_FEATURES() { return 'block_social_features'; }
static get SKIP_SPLASH_VIDEO() { return 'skip_splash_video'; }
@ -7207,6 +7237,7 @@ class Preferences {
[Preferences.PREFER_IPV6_SERVER]: {
'default': false,
},
[Preferences.SCREENSHOT_BUTTON_POSITION]: {
'default': 'bottom-left',
'options': {
@ -7215,6 +7246,10 @@ class Preferences {
'none': t('disable'),
},
},
[Preferences.SCREENSHOT_APPLY_FILTERS]: {
'default': false,
},
[Preferences.SKIP_SPLASH_VIDEO]: {
'default': false,
},
@ -8053,7 +8088,7 @@ if (gamepadFound) {
getPref(Preferences.UI_LAYOUT) === 'tv' && ['tvLayout'],
ENABLE_XCLOUD_LOGGER && [
BX_FLAGS.EnableXcloudLogging && [
'enableConsoleLogging',
'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) === 'off' || getPref(Preferences.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && ['disableTakRenderer'],
ENABLE_XCLOUD_LOGGER && ['enableConsoleLogging'],
BX_FLAGS.EnableXcloudLogging && ['enableConsoleLogging'],
getPref(Preferences.BLOCK_TRACKING) && ['blockGamepadStatsCollector'],
@ -8639,6 +8674,10 @@ a.bx-button.bx-full-width {
color: #828282;
}
.bx-settings-group-label b {
margin-bottom: 8px;
}
@media not (hover: hover) {
.bx-settings-row:focus-within {
background-color: #242424;
@ -8738,12 +8777,9 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
position: fixed;
bottom: 0;
box-sizing: border-box;
width: 16vh;
height: 16vh;
max-width: 128px;
max-height: 128px;
padding: 2vh;
padding: 24px 24px 12px 12px;
width: 60px;
height: 60px;
padding: 16px;
background-size: cover;
background-repeat: no-repeat;
background-origin: content-box;
@ -8760,7 +8796,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
}
.bx-screenshot-button[data-capturing=true] {
padding: 1vh;
padding: 8px;
}
.bx-screenshot-canvas {
@ -10269,7 +10305,7 @@ function injectSettingsButton($parent) {
}
// Show link to Android app
if (!window.AppInterface) {
if (!AppInterface) {
const userAgent = UserAgent.getDefault().toLowerCase();
if (userAgent.includes('android')) {
const $btn = createButton({
@ -10302,6 +10338,9 @@ function injectSettingsButton($parent) {
[Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: t('enable-volume-control'),
[Preferences.AUDIO_MIC_ON_PLAYING]: t('enable-mic-on-startup'),
[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')]: {
@ -10310,8 +10349,6 @@ function injectSettingsButton($parent) {
},
[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_HIDE_IDLE_CURSOR]: t('hide-idle-cursor'),
},
@ -10324,6 +10361,7 @@ function injectSettingsButton($parent) {
[t('touch-controller')]: {
_note: !HAS_TOUCH_SUPPORT ? '⚠️ ' + t('device-unsupported-touch') : null,
_unsupported: !HAS_TOUCH_SUPPORT,
[Preferences.STREAM_TOUCH_CONTROLLER]: t('tc-availability'),
[Preferences.STREAM_TOUCH_CONTROLLER_AUTO_OFF]: t('tc-auto-off'),
[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.HIDE_DOTS_ICON]: t('hide-system-menu-icon'),
[Preferences.REDUCE_ANIMATIONS]: t('reduce-animations'),
[Preferences.SCREENSHOT_BUTTON_POSITION]: t('screenshot-button-position'),
},
[t('other')]: {
[Preferences.BLOCK_SOCIAL_FEATURES]: t('disable-social-features'),
@ -10363,6 +10400,11 @@ function injectSettingsButton($parent) {
$wrapper.appendChild($group);
// Don't render settings if this is an unsupported feature
if (SETTINGS_UI[groupLabel]._unsupported) {
continue;
}
let onChange = e => {
if (!$reloadBtnWrapper) {
return;
@ -10381,6 +10423,7 @@ function injectSettingsButton($parent) {
};
for (let settingId in SETTINGS_UI[groupLabel]) {
// Don't render custom settings
if (settingId.startsWith('_')) {
continue;
}
@ -10575,6 +10618,11 @@ function updateVideoPlayerCss() {
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);
if (PREF_RATIO && PREF_RATIO !== '16:9') {
if (PREF_RATIO.includes(':')) {
@ -11327,6 +11375,15 @@ function takeScreenshot(callback) {
const $canvasContext = $SCREENSHOT_CANVAS.getContext('2d');
$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 => {
// Download screenshot
const now = +new Date;
@ -11445,18 +11502,15 @@ function disablePwa() {
}
function setupBxUi() {
updateVideoPlayerCss();
// Prevent initializing multiple times
if (document.querySelector('.bx-quick-settings-bar')) {
return;
if (!document.querySelector('.bx-quick-settings-bar')) {
window.addEventListener('resize', updateVideoPlayerCss);
setupQuickSettingsBar();
setupScreenshotButton();
StreamStats.render();
}
window.addEventListener('resize', updateVideoPlayerCss);
setupQuickSettingsBar();
setupScreenshotButton();
StreamStats.render();
updateVideoPlayerCss();
}
@ -11506,6 +11560,7 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
const PREF_SCREENSHOT_BUTTON_POSITION = getPref(Preferences.SCREENSHOT_BUTTON_POSITION);
$SCREENSHOT_CANVAS.width = $video.videoWidth;
$SCREENSHOT_CANVAS.height = $video.videoHeight;
updateVideoPlayerCss();
// Setup screenshot button
if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
@ -11641,7 +11696,7 @@ patchVideoApi();
// Setup UI
addCss();
Toast.setup();
ENABLE_PRELOAD_BX_UI && setupBxUi();
BX_FLAGS.PreloadUi && setupBxUi();
disablePwa();