@ -1,7 +1,7 @@
// ==UserScript==
// ==UserScript==
// @name Better xCloud
// @name Better xCloud
// @namespace https://github.com/redphx
// @namespace https://github.com/redphx
// @version 2.0.5
// @version 2.1
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @author redphx
// @license MIT
// @license MIT
@ -13,7 +13,7 @@
// ==/UserScript==
// ==/UserScript==
'use strict' ;
'use strict' ;
const SCRIPT _VERSION = '2.0.5 ' ;
const SCRIPT _VERSION = '2.1 ' ;
const SCRIPT _HOME = 'https://github.com/redphx/better-xcloud' ;
const SCRIPT _HOME = 'https://github.com/redphx/better-xcloud' ;
const ENABLE _MKB = false ;
const ENABLE _MKB = false ;
@ -54,7 +54,7 @@ function createElement(elmName, props = {}) {
const argType = typeof arg ;
const argType = typeof arg ;
if ( argType === 'string' || argType === 'number' ) {
if ( argType === 'string' || argType === 'number' ) {
$elm . textContent = arg ;
$elm . appendChild ( document . createTextNode ( arg ) ) ;
} else if ( arg ) {
} else if ( arg ) {
$elm . appendChild ( arg ) ;
$elm . appendChild ( arg ) ;
}
}
@ -486,6 +486,13 @@ const Translations = {
"uk-UA" : "Частота опитувань контролера" ,
"uk-UA" : "Частота опитувань контролера" ,
"vi-VN" : "Tần suất cập nhật của bộ điều khiển" ,
"vi-VN" : "Tần suất cập nhật của bộ điều khiển" ,
} ,
} ,
"controller-vibration" : {
"de-DE" : "Vibration des Controllers" ,
"en-US" : "Controller vibration" ,
"ja-JP" : "コントローラーの振動" ,
"tr-TR" : "Oyun kumandası titreşimi" ,
"vi-VN" : "Rung bộ điều khiển" ,
} ,
"custom" : {
"custom" : {
"de-DE" : "Benutzerdefiniert" ,
"de-DE" : "Benutzerdefiniert" ,
"en-US" : "Custom" ,
"en-US" : "Custom" ,
@ -534,6 +541,22 @@ const Translations = {
"vi-VN" : "Thiết bị này không hỗ trợ cảm ứng" ,
"vi-VN" : "Thiết bị này không hỗ trợ cảm ứng" ,
"zh-CN" : "您的设备不支持触摸" ,
"zh-CN" : "您的设备不支持触摸" ,
} ,
} ,
"device-vibration" : {
"de-DE" : "Vibration des Geräts" ,
"en-US" : "Device vibration" ,
"ja-JP" : "デバイスの振動" ,
"tr-TR" : "Cihaz titreşimi" ,
"uk-UA" : "Вібрація пристрою" ,
"vi-VN" : "Rung thiết bị" ,
} ,
"device-vibration-not-using-gamepad" : {
"de-DE" : "Aktiviert, wenn kein Gamepad verwendet wird" ,
"en-US" : "On when not using gamepad" ,
"ja-JP" : "ゲームパッド未使用時にオン" ,
"tr-TR" : "Oyun kumandası bağlanmadan titreşim" ,
"uk-UA" : "Увімкнена, коли не використовується геймпад" ,
"vi-VN" : "Bật khi không dùng tay cầm" ,
} ,
"disable" : {
"disable" : {
"de-DE" : "Deaktiviert" ,
"de-DE" : "Deaktiviert" ,
"en-US" : "Disable" ,
"en-US" : "Disable" ,
@ -647,6 +670,7 @@ const Translations = {
"de-DE" : "Maus- und Tastaturunterstützung aktivieren" ,
"de-DE" : "Maus- und Tastaturunterstützung aktivieren" ,
"en-US" : "Enable Mouse & Keyboard support" ,
"en-US" : "Enable Mouse & Keyboard support" ,
"es-ES" : "Habilitar soporte para ratón y teclado" ,
"es-ES" : "Habilitar soporte para ratón y teclado" ,
"it-IT" : "Abilitare il supporto di mouse e tastiera" ,
"ja-JP" : "マウス&キーボードのサポートを有効化" ,
"ja-JP" : "マウス&キーボードのサポートを有効化" ,
"pl-PL" : "Włącz obsługę myszy i klawiatury" ,
"pl-PL" : "Włącz obsługę myszy i klawiatury" ,
"pt-BR" : "Habilitar suporte ao Mouse & Teclado" ,
"pt-BR" : "Habilitar suporte ao Mouse & Teclado" ,
@ -675,6 +699,7 @@ const Translations = {
"de-DE" : "\"Remote Play\" Funktion aktivieren" ,
"de-DE" : "\"Remote Play\" Funktion aktivieren" ,
"en-US" : "Enable the \"Remote Play\" feature" ,
"en-US" : "Enable the \"Remote Play\" feature" ,
"es-ES" : "Activar la función \"Reproducción remota\"" ,
"es-ES" : "Activar la función \"Reproducción remota\"" ,
"it-IT" : "Abilitare la funzione \"Riproduzione remota\"" ,
"ja-JP" : "リモートプレイ機能を有効化" ,
"ja-JP" : "リモートプレイ機能を有効化" ,
"pl-PL" : "Włącz funkcję \"Gra zdalna\"" ,
"pl-PL" : "Włącz funkcję \"Gra zdalna\"" ,
"pt-BR" : "Ativar o recurso \"Reprodução Remota\"" ,
"pt-BR" : "Ativar o recurso \"Reprodução Remota\"" ,
@ -703,6 +728,7 @@ const Translations = {
"de-DE" : "Schnell" ,
"de-DE" : "Schnell" ,
"en-US" : "Fast" ,
"en-US" : "Fast" ,
"es-ES" : "Rápido" ,
"es-ES" : "Rápido" ,
"it-IT" : "Veloce" ,
"ja-JP" : "高速" ,
"ja-JP" : "高速" ,
"pl-PL" : "Szybko" ,
"pl-PL" : "Szybko" ,
"pt-BR" : "Rápido" ,
"pt-BR" : "Rápido" ,
@ -792,6 +818,7 @@ const Translations = {
"de-DE" : "Layout" ,
"de-DE" : "Layout" ,
"en-US" : "Layout" ,
"en-US" : "Layout" ,
"es-ES" : "Diseño" ,
"es-ES" : "Diseño" ,
"it-IT" : "Layout" ,
"ja-JP" : "レイアウト" ,
"ja-JP" : "レイアウト" ,
"pl-PL" : "Układ" ,
"pl-PL" : "Układ" ,
"pt-BR" : "Layout" ,
"pt-BR" : "Layout" ,
@ -820,6 +847,7 @@ const Translations = {
"de-DE" : "Max. Bitrate" ,
"de-DE" : "Max. Bitrate" ,
"en-US" : "Max bitrate" ,
"en-US" : "Max bitrate" ,
"es-ES" : "Tasa de bits máxima" ,
"es-ES" : "Tasa de bits máxima" ,
"it-IT" : "Bitrate massimo" ,
"ja-JP" : "最大ビットレート" ,
"ja-JP" : "最大ビットレート" ,
"pl-PL" : "Maksymalny bitrate" ,
"pl-PL" : "Maksymalny bitrate" ,
"pt-BR" : "Taxa máxima dos bits" ,
"pt-BR" : "Taxa máxima dos bits" ,
@ -832,6 +860,7 @@ const Translations = {
"de-DE" : "Funktioniert evtl. nicht fehlerfrei!" ,
"de-DE" : "Funktioniert evtl. nicht fehlerfrei!" ,
"en-US" : "May not work properly!" ,
"en-US" : "May not work properly!" ,
"es-ES" : "¡Puede que no funcione correctamente!" ,
"es-ES" : "¡Puede que no funcione correctamente!" ,
"it-IT" : "Potrebbe non funzionare correttamente!" ,
"ja-JP" : "正常に動作しない場合があります!" ,
"ja-JP" : "正常に動作しない場合があります!" ,
"pl-PL" : "Może nie działać poprawnie!" ,
"pl-PL" : "Może nie działać poprawnie!" ,
"pt-BR" : "Pode não funcionar corretamente!" ,
"pt-BR" : "Pode não funcionar corretamente!" ,
@ -890,6 +919,7 @@ const Translations = {
"de-DE" : "Maus & Tastatur" ,
"de-DE" : "Maus & Tastatur" ,
"en-US" : "Mouse & Keyboard" ,
"en-US" : "Mouse & Keyboard" ,
"es-ES" : "Ratón y teclado" ,
"es-ES" : "Ratón y teclado" ,
"it-IT" : "Mouse e tastiera" ,
"ja-JP" : "マウス&キーボード" ,
"ja-JP" : "マウス&キーボード" ,
"pl-PL" : "Mysz i klawiatura" ,
"pl-PL" : "Mysz i klawiatura" ,
"pt-BR" : "Mouse e Teclado" ,
"pt-BR" : "Mouse e Teclado" ,
@ -976,6 +1006,7 @@ const Translations = {
"de-DE" : "Unterstützt nur einige Spiele" ,
"de-DE" : "Unterstützt nur einige Spiele" ,
"en-US" : "Only supports some games" ,
"en-US" : "Only supports some games" ,
"es-ES" : "Sólo soporta algunos juegos" ,
"es-ES" : "Sólo soporta algunos juegos" ,
"it-IT" : "Supporta solo alcuni giochi" ,
"ja-JP" : "一部のゲームのみサポート" ,
"ja-JP" : "一部のゲームのみサポート" ,
"pl-PL" : "Wspiera tylko niektóre gry" ,
"pl-PL" : "Wspiera tylko niektóre gry" ,
"pt-BR" : "Suporta apenas alguns jogos" ,
"pt-BR" : "Suporta apenas alguns jogos" ,
@ -1398,6 +1429,7 @@ const Translations = {
"de-DE" : "Langsam" ,
"de-DE" : "Langsam" ,
"en-US" : "Slow" ,
"en-US" : "Slow" ,
"es-ES" : "Lento" ,
"es-ES" : "Lento" ,
"it-IT" : "Lento" ,
"ja-JP" : "低速" ,
"ja-JP" : "低速" ,
"pl-PL" : "Wolno" ,
"pl-PL" : "Wolno" ,
"pt-BR" : "Lento" ,
"pt-BR" : "Lento" ,
@ -1426,6 +1458,7 @@ const Translations = {
"de-DE" : "Smart TV" ,
"de-DE" : "Smart TV" ,
"en-US" : "Smart TV" ,
"en-US" : "Smart TV" ,
"es-ES" : "Smart TV" ,
"es-ES" : "Smart TV" ,
"it-IT" : "Smart TV" ,
"ja-JP" : "スマートTV" ,
"ja-JP" : "スマートTV" ,
"pl-PL" : "Smart TV" ,
"pl-PL" : "Smart TV" ,
"pt-BR" : "Smart TV" ,
"pt-BR" : "Smart TV" ,
@ -1622,6 +1655,15 @@ const Translations = {
"vi-VN" : "Kéo giãn" ,
"vi-VN" : "Kéo giãn" ,
"zh-CN" : "拉伸" ,
"zh-CN" : "拉伸" ,
} ,
} ,
"swap-buttons" : {
"de-DE" : "Tasten tauschen" ,
"en-US" : "Swap buttons" ,
"ja-JP" : "ボタン入れ替え" ,
"pt-BR" : "Trocar botões" ,
"tr-TR" : "Düğme düzenini ters çevir" ,
"uk-UA" : "Поміняти кнопки місцями" ,
"vi-VN" : "Hoán đổi nút" ,
} ,
"target-resolution" : {
"target-resolution" : {
"de-DE" : "Festgelegte Auflösung" ,
"de-DE" : "Festgelegte Auflösung" ,
"en-US" : "Target resolution" ,
"en-US" : "Target resolution" ,
@ -1864,6 +1906,7 @@ const Translations = {
"de-DE" : "Unbegrenzt" ,
"de-DE" : "Unbegrenzt" ,
"en-US" : "Unlimited" ,
"en-US" : "Unlimited" ,
"es-ES" : "Ilimitado" ,
"es-ES" : "Ilimitado" ,
"it-IT" : "Illimitato" ,
"ja-JP" : "無制限" ,
"ja-JP" : "無制限" ,
"pl-PL" : "Bez ograniczeń" ,
"pl-PL" : "Bez ograniczeń" ,
"pt-BR" : "Ilimitado" ,
"pt-BR" : "Ilimitado" ,
@ -1914,6 +1957,13 @@ const Translations = {
"vi-VN" : "User-Agent" ,
"vi-VN" : "User-Agent" ,
"zh-CN" : "浏览器UA伪装" ,
"zh-CN" : "浏览器UA伪装" ,
} ,
} ,
"vibration-intensity" : {
"de-DE" : "Vibrationsstärke" ,
"en-US" : "Vibration intensity" ,
"ja-JP" : "振動の強さ" ,
"tr-TR" : "Titreşim gücü" ,
"vi-VN" : "Cường độ rung" ,
} ,
"video" : {
"video" : {
"de-DE" : "Video" ,
"de-DE" : "Video" ,
"en-US" : "Video" ,
"en-US" : "Video" ,
@ -2910,6 +2960,147 @@ class GamepadHandler {
}
}
}
}
class VibrationManager {
static # playDeviceVibration ( data ) {
// console.log(+new Date, data);
const intensity = Math . min ( 100 , data . leftMotorPercent + data . rightMotorPercent / 2 ) * window . BX _VIBRATION _INTENSITY ;
if ( intensity === 0 || intensity === 100 ) {
// Stop vibration
window . navigator . vibrate ( intensity ? data . durationMs : 0 ) ;
return ;
}
const pulseDuration = 200 ;
const onDuration = Math . floor ( pulseDuration * intensity / 100 ) ;
const offDuration = pulseDuration - onDuration ;
const repeats = Math . ceil ( data . durationMs / pulseDuration ) ;
const pulses = Array ( repeats ) . fill ( [ onDuration , offDuration ] ) . flat ( ) ;
// console.log(pulses);
window . navigator . vibrate ( pulses ) ;
}
static supportControllerVibration ( ) {
return Gamepad . prototype . hasOwnProperty ( 'vibrationActuator' ) ;
}
static supportDeviceVibration ( ) {
return ! ! window . navigator . vibrate ;
}
static updateGlobalVars ( ) {
window . BX _ENABLE _CONTROLLER _VIBRATION = VibrationManager . supportControllerVibration ( ) ? PREFS . get ( Preferences . CONTROLLER _ENABLE _VIBRATION ) : false ;
window . BX _VIBRATION _INTENSITY = PREFS . get ( Preferences . CONTROLLER _VIBRATION _INTENSITY ) / 100 ;
if ( ! VibrationManager . supportDeviceVibration ( ) ) {
window . BX _ENABLE _DEVICE _VIBRATION = false ;
return ;
}
// Stop vibration
window . navigator . vibrate ( 0 ) ;
const value = PREFS . get ( Preferences . CONTROLLER _DEVICE _VIBRATION ) ;
let enabled ;
if ( value === 'on' ) {
enabled = true ;
} else if ( value === 'auto' ) {
enabled = true ;
const gamepads = window . navigator . getGamepads ( ) ;
for ( const gamepad of gamepads ) {
if ( gamepad ) {
enabled = false ;
break ;
}
}
} else {
enabled = false ;
}
window . BX _ENABLE _DEVICE _VIBRATION = enabled ;
}
static initialSetup ( ) {
window . addEventListener ( 'gamepadconnected' , VibrationManager . updateGlobalVars ) ;
window . addEventListener ( 'gamepaddisconnected' , VibrationManager . updateGlobalVars ) ;
VibrationManager . updateGlobalVars ( ) ;
const orgCreateDataChannel = RTCPeerConnection . prototype . createDataChannel ;
RTCPeerConnection . prototype . createDataChannel = function ( ) {
const dataChannel = orgCreateDataChannel . apply ( this , arguments ) ;
if ( dataChannel . label !== 'input' ) {
return dataChannel ;
}
const VIBRATION _DATA _MAP = {
'gamepadIndex' : 8 ,
'leftMotorPercent' : 8 ,
'rightMotorPercent' : 8 ,
'leftTriggerMotorPercent' : 8 ,
'rightTriggerMotorPercent' : 8 ,
'durationMs' : 16 ,
// 'delayMs': 16,
// 'repeat': 8,
} ;
dataChannel . addEventListener ( 'message' , e => {
if ( ! window . BX _ENABLE _DEVICE _VIBRATION ) {
return ;
}
if ( typeof e !== 'object' || ! ( e . data instanceof ArrayBuffer ) ) {
return ;
}
const dataView = new DataView ( e . data ) ;
let offset = 0 ;
let messageType ;
if ( dataView . byteLength === 13 ) { // version >= 8
messageType = dataView . getUint16 ( offset , true ) ;
offset += Uint16Array . BYTES _PER _ELEMENT ;
} else {
messageType = dataView . getUint8 ( offset ) ;
offset += Uint8Array . BYTES _PER _ELEMENT ;
}
if ( ! ( messageType & 128 ) ) { // Vibration
return ;
}
const vibrationType = dataView . getUint8 ( offset ) ;
offset += Uint8Array . BYTES _PER _ELEMENT ;
if ( vibrationType !== 0 ) { // FourMotorRumble
return ;
}
const data = { } ;
for ( const key in VIBRATION _DATA _MAP ) {
if ( VIBRATION _DATA _MAP [ key ] === 16 ) {
data [ key ] = dataView . getUint16 ( offset , true ) ;
offset += Uint16Array . BYTES _PER _ELEMENT ;
} else {
data [ key ] = dataView . getUint8 ( offset ) ;
offset += Uint8Array . BYTES _PER _ELEMENT ;
}
}
VibrationManager . # playDeviceVibration ( data ) ;
} ) ;
return dataChannel ;
} ;
}
}
class MouseCursorHider {
class MouseCursorHider {
static # timeout ;
static # timeout ;
static # cursorVisible = true ;
static # cursorVisible = true ;
@ -3033,7 +3224,7 @@ class StreamBadges {
let totalIn = 0 ;
let totalIn = 0 ;
let totalOut = 0 ;
let totalOut = 0 ;
stats . forEach ( stat => {
stats . forEach ( stat => {
if ( stat . type === 'candidate-pair' && stat . state == 'succeeded' ) {
if ( stat . type === 'candidate-pair' && stat . state === 'succeeded' ) {
totalIn += stat . bytesReceived ;
totalIn += stat . bytesReceived ;
totalOut += stat . bytesSent ;
totalOut += stat . bytesSent ;
}
}
@ -3553,6 +3744,9 @@ class Preferences {
static get STREAM _DISABLE _FEEDBACK _DIALOG ( ) { return 'stream_disable_feedback_dialog' ; }
static get STREAM _DISABLE _FEEDBACK _DIALOG ( ) { return 'stream_disable_feedback_dialog' ; }
static get CONTROLLER _ENABLE _SHORTCUTS ( ) { return 'controller_enable_shortcuts' ; }
static get CONTROLLER _ENABLE _SHORTCUTS ( ) { return 'controller_enable_shortcuts' ; }
static get CONTROLLER _ENABLE _VIBRATION ( ) { return 'controller_enable_vibration' ; }
static get CONTROLLER _DEVICE _VIBRATION ( ) { return 'controller_device_vibration' ; }
static get CONTROLLER _VIBRATION _INTENSITY ( ) { return 'controller_vibration_intensity' ; }
static get MKB _ENABLED ( ) { return 'mkb_enabled' ; }
static get MKB _ENABLED ( ) { return 'mkb_enabled' ; }
static get MKB _ABSOLUTE _MOUSE ( ) { return 'mkb_absolute_mouse' ; }
static get MKB _ABSOLUTE _MOUSE ( ) { return 'mkb_absolute_mouse' ; }
@ -3786,6 +3980,26 @@ class Preferences {
'default' : false ,
'default' : false ,
} ,
} ,
[ Preferences . CONTROLLER _ENABLE _VIBRATION ] : {
'default' : true ,
} ,
[ Preferences . CONTROLLER _DEVICE _VIBRATION ] : {
'default' : 'off' ,
'options' : {
'on' : _ _ ( 'on' ) ,
'auto' : _ _ ( 'device-vibration-not-using-gamepad' ) ,
'off' : _ _ ( 'off' ) ,
} ,
} ,
[ Preferences . CONTROLLER _VIBRATION _INTENSITY ] : {
'default' : 100 ,
'min' : 0 ,
'max' : 100 ,
'steps' : 10 ,
} ,
[ Preferences . MKB _ENABLED ] : {
[ Preferences . MKB _ENABLED ] : {
'default' : false ,
'default' : false ,
} ,
} ,
@ -4164,7 +4378,7 @@ class Preferences {
) ;
) ;
if ( ! options . disabled && ! options . hideSlider ) {
if ( ! options . disabled && ! options . hideSlider ) {
$range = CE ( 'input' , { 'type' : 'range' , 'min' : MIN , 'max' : MAX , 'value' : value } ) ;
$range = CE ( 'input' , { 'type' : 'range' , 'min' : MIN , 'max' : MAX , 'value' : value , 'step' : STEPS }) ;
$range . addEventListener ( 'input' , e => {
$range . addEventListener ( 'input' , e => {
value = parseInt ( e . target . value ) ;
value = parseInt ( e . target . value ) ;
@ -4373,6 +4587,48 @@ class Patcher {
return funcStr ;
return funcStr ;
} ,
} ,
// Control controller vibration
playVibration : function ( funcStr ) {
const text = '}playVibration(e){' ;
if ( ! funcStr . includes ( text ) ) {
return false ;
}
const newCode = `
if (!window.BX_ENABLE_CONTROLLER_VIBRATION) {
return void(0);
}
if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
e.leftMotorPercent = e.leftMotorPercent * window.BX_VIBRATION_INTENSITY;
e.rightMotorPercent = e.rightMotorPercent * window.BX_VIBRATION_INTENSITY;
e.leftTriggerMotorPercent = e.leftTriggerMotorPercent * window.BX_VIBRATION_INTENSITY;
e.rightTriggerMotorPercent = e.rightTriggerMotorPercent * window.BX_VIBRATION_INTENSITY;
}
` ;
VibrationManager . updateGlobalVars ( ) ;
funcStr = funcStr . replaceAll ( text , text + newCode ) ;
return funcStr ;
} ,
// Override website's settings
overrideSettings : function ( funcStr ) {
const index = funcStr . indexOf ( ',EnableStreamGate:' ) ;
if ( index === - 1 ) {
return false ;
}
// Find the next "},"
const endIndex = funcStr . indexOf ( '},' , index ) ;
const newCode = `
EnableStreamGate: false,
PwaPrompt: false,
` ;
funcStr = funcStr . substring ( 0 , endIndex ) + ',' + newCode + funcStr . substring ( endIndex ) ;
return funcStr ;
} ,
// Enable Mouse and Keyboard support
// Enable Mouse and Keyboard support
enableMouseAndKeyboard : PREFS . get ( Preferences . MKB _ENABLED ) && function ( funcStr ) {
enableMouseAndKeyboard : PREFS . get ( Preferences . MKB _ENABLED ) && function ( funcStr ) {
if ( ! funcStr . includes ( 'EnableMouseAndKeyboard:' ) ) {
if ( ! funcStr . includes ( 'EnableMouseAndKeyboard:' ) ) {
@ -4399,7 +4655,8 @@ class Patcher {
[ 'enableXcloudLogger' ] ,
[ 'enableXcloudLogger' ] ,
[
[
'enableMouseAndKeyboard',
// 'enableMouseAndKeyboard',
'overrideSettings' ,
'remotePlayDirectConnectUrl' ,
'remotePlayDirectConnectUrl' ,
'disableTrackEvent' ,
'disableTrackEvent' ,
'enableConsoleLogging' ,
'enableConsoleLogging' ,
@ -4409,6 +4666,7 @@ class Patcher {
// Only when playing
// Only when playing
[ 'remotePlayConnectMode' ] ,
[ 'remotePlayConnectMode' ] ,
[ 'playVibration' ] ,
[ 'enableConsoleLogging' ] ,
[ 'enableConsoleLogging' ] ,
] ;
] ;
@ -5091,7 +5349,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
bottom: 20px;
bottom: 20px;
z-index: var(--bx-stream-settings-z-index);
z-index: var(--bx-stream-settings-z-index);
padding: 8px;
padding: 8px;
width: 2 20px;
width: 3 20px;
background: #1a1b1e;
background: #1a1b1e;
color: #fff;
color: #fff;
border-radius: 8px 0 0 8px;
border-radius: 8px 0 0 8px;
@ -5164,6 +5422,12 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
font-family: var(--bx-monospaced-font);
font-family: var(--bx-monospaced-font);
}
}
.bx-quick-settings-bar-note {
font-size: 12px;
font-weight: lighter;
font-style: italic;
}
.bx-toast {
.bx-toast {
position: fixed;
position: fixed;
left: 50%;
left: 50%;
@ -5896,6 +6160,7 @@ function interceptHttpRequests() {
// Enable touch controller
// Enable touch controller
if ( TouchController . isEnabled ( ) ) {
if ( TouchController . isEnabled ( ) ) {
overrides . inputConfiguration = overrides . inputConfiguration || { } ;
overrides . inputConfiguration = overrides . inputConfiguration || { } ;
overrides . enableVibration = true ;
overrides . inputConfiguration . enableTouchInput = true ;
overrides . inputConfiguration . enableTouchInput = true ;
overrides . inputConfiguration . maxTouchPoints = 10 ;
overrides . inputConfiguration . maxTouchPoints = 10 ;
}
}
@ -6593,30 +6858,54 @@ function setupVideoSettingsBar() {
let $stretchInp ;
let $stretchInp ;
const $wrapper = CE ( 'div' , { 'class' : 'bx-quick-settings-bar' } ,
const $wrapper = CE ( 'div' , { 'class' : 'bx-quick-settings-bar' } ,
CE ( 'h2' , { } , _ _ ( 'controller' ) ) ,
CE ( 'div' , { } ,
CE ( 'label' , { } , _ _ ( 'controller-vibration' ) ) ,
VibrationManager . supportControllerVibration ( ) && PREFS . toElement ( Preferences . CONTROLLER _ENABLE _VIBRATION , VibrationManager . updateGlobalVars ) ,
! VibrationManager . supportControllerVibration ( ) && CE ( 'div' , { 'class' : 'bx-quick-settings-bar-note' } , _ _ ( 'browser-unsupported-feature' ) ) ,
) ,
CE ( 'div' , { } ,
CE ( 'label' , { } , _ _ ( 'device-vibration' ) ) ,
VibrationManager . supportDeviceVibration ( ) && PREFS . toElement ( Preferences . CONTROLLER _DEVICE _VIBRATION , VibrationManager . updateGlobalVars ) ,
! VibrationManager . supportDeviceVibration ( ) && CE ( 'div' , { 'class' : 'bx-quick-settings-bar-note' } , _ _ ( 'browser-unsupported-feature' ) ) ,
) ,
( VibrationManager . supportControllerVibration ( ) || VibrationManager . supportDeviceVibration ( ) ) &&
CE ( 'div' , { } ,
CE ( 'label' , { } , _ _ ( 'vibration-intensity' ) ) ,
PREFS . toNumberStepper ( Preferences . CONTROLLER _VIBRATION _INTENSITY , VibrationManager . updateGlobalVars , { suffix : '%' , ticks : 50 } ) ,
) ,
CE ( 'h2' , { } , _ _ ( 'audio' ) ) ,
CE ( 'h2' , { } , _ _ ( 'audio' ) ) ,
CE ( 'div' , { } ,
CE ( 'div' , { } ,
CE ( 'label' , { } , _ _ ( 'volume' ) ) ,
CE ( 'label' , { } , _ _ ( 'volume' ) ) ,
PREFS . toNumberStepper ( Preferences . AUDIO _VOLUME , ( e , value ) => {
PREFS . toNumberStepper ( Preferences . AUDIO _VOLUME , ( e , value ) => {
STREAM _AUDIO _GAIN _NODE && ( STREAM _AUDIO _GAIN _NODE . gain . value = ( value / 100 ) . toFixed ( 2 ) ) ;
STREAM _AUDIO _GAIN _NODE && ( STREAM _AUDIO _GAIN _NODE . gain . value = ( value / 100 ) . toFixed ( 2 ) ) ;
} , { suffix : '%' , ticks : 100 , disabled : ! PREFS . get ( Preferences . AUDIO _ENABLE _VOLUME _CONTROL ) } ) ) ,
} , { suffix : '%' , ticks : 100 , disabled : ! PREFS . get ( Preferences . AUDIO _ENABLE _VOLUME _CONTROL ) } ) ,
) ,
CE ( 'h2' , { } , _ _ ( 'video' ) ) ,
CE ( 'h2' , { } , _ _ ( 'video' ) ) ,
CE ( 'div' , { 'class' : 'bx-clarity-boost-warning' } , ` ⚠️ ${ _ _ ( 'clarity-boost-warning' ) } ` ) ,
CE ( 'div' , { 'class' : 'bx-quick-settings-bar-note bx-clarity-boost-warning' } , ` ⚠️ ${ _ _ ( 'clarity-boost-warning' ) } ` ) ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'label' , { 'for' : 'bx-quick-setting-stretch' } , _ _ ( 'ratio' ) ) ,
CE ( 'label' , { } , _ _ ( 'ratio' ) ) ,
PREFS . toElement ( Preferences . VIDEO _RATIO , onVideoChange ) ) ,
PREFS . toElement ( Preferences . VIDEO _RATIO , onVideoChange ) ,
) ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'label' , { } , _ _ ( 'clarity' ) ) ,
CE ( 'label' , { } , _ _ ( 'clarity' ) ) ,
PREFS . toNumberStepper ( Preferences . VIDEO _CLARITY , onVideoChange , { disabled : isSafari , hideSlider : true } ) ) , // disable this feature in Safari
PREFS . toNumberStepper ( Preferences . VIDEO _CLARITY , onVideoChange , { disabled : isSafari , hideSlider : true } ) , // disable this feature in Safari
) ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'label' , { } , _ _ ( 'saturation' ) ) ,
CE ( 'label' , { } , _ _ ( 'saturation' ) ) ,
PREFS . toNumberStepper ( Preferences . VIDEO _SATURATION , onVideoChange , { suffix : '%' , ticks : 25 } ) ) ,
PREFS . toNumberStepper ( Preferences . VIDEO _SATURATION , onVideoChange , { suffix : '%' , ticks : 25 } ) ,
) ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'label' , { } , _ _ ( 'contrast' ) ) ,
CE ( 'label' , { } , _ _ ( 'contrast' ) ) ,
PREFS . toNumberStepper ( Preferences . VIDEO _CONTRAST , onVideoChange , { suffix : '%' , ticks : 25 } ) ) ,
PREFS . toNumberStepper ( Preferences . VIDEO _CONTRAST , onVideoChange , { suffix : '%' , ticks : 25 } ) ,
) ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'div' , { 'data-type' : 'video' } ,
CE ( 'label' , { } , _ _ ( 'brightness' ) ) ,
CE ( 'label' , { } , _ _ ( 'brightness' ) ) ,
PREFS . toNumberStepper ( Preferences . VIDEO _BRIGHTNESS , onVideoChange , { suffix : '%' , ticks : 25 } ) )
PREFS . toNumberStepper ( Preferences . VIDEO _BRIGHTNESS , onVideoChange , { suffix : '%' , ticks : 25 } ) ,
) ,
) ;
) ;
document . documentElement . appendChild ( $wrapper ) ;
document . documentElement . appendChild ( $wrapper ) ;
@ -6793,6 +7082,9 @@ function onStreamStarted($video) {
const allAudioCodecs = { } ;
const allAudioCodecs = { } ;
let audioCodecId ;
let audioCodecId ;
const allCandidates = { } ;
let candidateId ;
stats . forEach ( stat => {
stats . forEach ( stat => {
if ( stat . type == 'codec' ) {
if ( stat . type == 'codec' ) {
const mimeType = stat . mimeType . split ( '/' ) ;
const mimeType = stat . mimeType . split ( '/' ) ;
@ -6810,6 +7102,10 @@ function onStreamStarted($video) {
} else if ( stat . kind === 'audio' ) {
} else if ( stat . kind === 'audio' ) {
audioCodecId = stat . codecId ;
audioCodecId = stat . codecId ;
}
}
} else if ( stat . type === 'candidate-pair' && stat . state === 'succeeded' ) {
candidateId = stat . remoteCandidateId ;
} else if ( stat . type === 'remote-candidate' ) {
allCandidates [ stat . id ] = stat . address ;
}
}
} ) ;
} ) ;
@ -6837,6 +7133,12 @@ function onStreamStarted($video) {
}
}
}
}
// Get server type
if ( candidateId ) {
console . log ( candidateId , allCandidates ) ;
StreamBadges . ipv6 = allCandidates [ candidateId ] . includes ( ':' ) ;
}
if ( PREFS . get ( Preferences . STATS _SHOW _WHEN _PLAYING ) ) {
if ( PREFS . get ( Preferences . STATS _SHOW _WHEN _PLAYING ) ) {
StreamStats . start ( ) ;
StreamStats . start ( ) ;
}
}
@ -6934,20 +7236,12 @@ if (PREFS.get(Preferences.AUDIO_ENABLE_VOLUME_CONTROL)) {
}
}
}
}
RTCPeerConnection . prototype . orgAddIceCandidate = RTCPeerConnection . prototype . addIceCandidate ;
RTCPeerConnection . prototype . addIceCandidate = function ( ... args ) {
const candidate = args [ 0 ] . candidate ;
if ( candidate && candidate . startsWith ( 'a=candidate:1 ' ) ) {
StreamBadges . ipv6 = candidate . substring ( 20 ) . includes ( ':' ) ;
}
return this . orgAddIceCandidate . apply ( this , args ) ;
}
if ( PREFS . get ( Preferences . STREAM _TOUCH _CONTROLLER ) === 'all' ) {
if ( PREFS . get ( Preferences . STREAM _TOUCH _CONTROLLER ) === 'all' ) {
TouchController . setup ( ) ;
TouchController . setup ( ) ;
}
}
VibrationManager . initialSetup ( ) ;
const OrgRTCPeerConnection = window . RTCPeerConnection ;
const OrgRTCPeerConnection = window . RTCPeerConnection ;
window . RTCPeerConnection = function ( ) {
window . RTCPeerConnection = function ( ) {
const peer = new OrgRTCPeerConnection ( ) ;
const peer = new OrgRTCPeerConnection ( ) ;