@ -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 : 128 px;
max-height: 128px;
padding: 2vh;
padding: 24px 24px 12px 12px;
width: 60px ;
height: 60px ;
padding : 16 px;
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 ( ) ;