Compare commits

...

18 Commits

2 changed files with 307 additions and 226 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.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.4.0 // @version 3.5.0
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
@ -21,6 +21,7 @@ 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,7 @@ class Dialog {
class RemotePlay { class RemotePlay {
static XCLOUD_TOKEN; static XCLOUD_TOKEN;
static XHOME_TOKEN; static XHOME_TOKEN;
static #CONSOLES; static #CONSOLES = null;
static #STATE_LABELS = { static #STATE_LABELS = {
'On': t('powered-on'), 'On': t('powered-on'),
@ -3611,9 +3663,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);
}); });
}); });
} }
@ -3815,6 +3864,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 +7098,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 +7166,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 +7399,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 +7637,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,
@ -8170,6 +8229,16 @@ 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;
},
}; };
static #PATCH_ORDERS = [ static #PATCH_ORDERS = [
@ -8223,6 +8292,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 +8429,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);
@ -9817,6 +9888,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;
} }
@ -9983,36 +10058,33 @@ 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 => { if (!text.length) {
return response.clone().text().then(text => { return response;
if (!text.length) { }
return response;
}
const options = { const options = {
preferIpv6Server: PREF_PREFER_IPV6_SERVER, preferIpv6Server: PREF_PREFER_IPV6_SERVER,
consoleAddrs: consoleAddrs, consoleAddrs: consoleAddrs,
}; };
const obj = JSON.parse(text); const obj = JSON.parse(text);
let exchangeResponse = JSON.parse(obj.exchangeResponse); let exchangeResponse = JSON.parse(obj.exchangeResponse);
exchangeResponse = updateIceCandidates(exchangeResponse, options) exchangeResponse = updateIceCandidates(exchangeResponse, options)
obj.exchangeResponse = JSON.stringify(exchangeResponse); obj.exchangeResponse = JSON.stringify(exchangeResponse);
response.json = () => Promise.resolve(obj); response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj)); response.text = () => Promise.resolve(JSON.stringify(obj));
return response; return response;
});
});
} }
return null; return null;
@ -10041,7 +10113,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();
@ -10078,66 +10150,60 @@ 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;
if (serverDetails.ipV4Address) { if (serverDetails.ipV4Address) {
consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port; consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port;
} }
if (serverDetails.ipV6Address) { if (serverDetails.ipV6Address) {
consoleAddrs[serverDetails.ipV6Address] = serverDetails.ipV6Port; consoleAddrs[serverDetails.ipV6Address] = serverDetails.ipV6Port;
} }
response.json = () => Promise.resolve(obj); response.json = () => Promise.resolve(obj);
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 => { const xboxTitleId = JSON.parse(opts.body).titleIds[0];
return response.clone().json().then(obj => { GAME_XBOX_TITLE_ID = xboxTitleId;
const xboxTitleId = JSON.parse(opts.body).titleIds[0];
GAME_XBOX_TITLE_ID = xboxTitleId;
const inputConfigs = obj[0]; const inputConfigs = obj[0];
let hasTouchSupport = inputConfigs.supportedTabs.length > 0; let hasTouchSupport = inputConfigs.supportedTabs.length > 0;
if (!hasTouchSupport) { if (!hasTouchSupport) {
const supportedInputTypes = inputConfigs.supportedInputTypes; const supportedInputTypes = inputConfigs.supportedInputTypes;
hasTouchSupport = supportedInputTypes.includes('NativeTouch'); hasTouchSupport = supportedInputTypes.includes('NativeTouch');
} }
if (hasTouchSupport) { if (hasTouchSupport) {
TouchController.disable(); TouchController.disable();
BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, { BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
data: null, data: null,
});
} else {
TouchController.enable();
TouchController.getCustomLayouts(xboxTitleId);
}
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
}); });
}); } else {
TouchController.enable();
TouchController.getCustomLayouts(xboxTitleId);
}
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
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,46 +10248,46 @@ 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();
return promise.then(response => { // Preload Remote Play
return response.clone().json().then(obj => { BX_FLAGS.PreloadRemotePlay && RemotePlay.preload();
// Store xCloud token
RemotePlay.XCLOUD_TOKEN = obj.gsToken;
// Get server list // Store xCloud token
if (!Object.keys(SERVER_REGIONS).length) { RemotePlay.XCLOUD_TOKEN = obj.gsToken;
for (let region of obj.offeringSettings.regions) {
SERVER_REGIONS[region.name] = Object.assign({}, region);
}
// Start rendering UI // Get server list
if (document.querySelector('div[class^=UnsupportedMarketPage]')) { if (!Object.keys(SERVER_REGIONS).length) {
setTimeout(watchHeader, 2000); for (let region of obj.offeringSettings.regions) {
} else { SERVER_REGIONS[region.name] = Object.assign({}, region);
watchHeader(); }
}
}
const preferredRegion = getPreferredServerRegion(); // Start rendering UI
if (preferredRegion in SERVER_REGIONS) { if (document.querySelector('div[class^=UnsupportedMarketPage]')) {
const tmp = Object.assign({}, SERVER_REGIONS[preferredRegion]); setTimeout(watchHeader, 2000);
tmp.isDefault = true; } else {
watchHeader();
}
}
obj.offeringSettings.regions = [tmp]; const preferredRegion = getPreferredServerRegion();
} if (preferredRegion in SERVER_REGIONS) {
const tmp = Object.assign({}, SERVER_REGIONS[preferredRegion]);
tmp.isDefault = true;
response.json = () => Promise.resolve(obj); obj.offeringSettings.regions = [tmp];
return response; }
});
}); response.json = () => Promise.resolve(obj);
return response;
} }
// Get region // Get region
@ -10266,22 +10332,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 => {
if (json.estimatedAllocationTimeInSeconds > 0) {
// Setup wait time overlay
LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds);
}
return response; const json = await response.clone.json();
}); if (json.estimatedAllocationTimeInSeconds > 0) {
}); // Setup wait time overlay
LoadingScreen.setupWaitTime(json.estimatedTotalWaitTimeInSeconds);
}
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,68 +10358,61 @@ 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;
} }
const obj = JSON.parse(text); const obj = JSON.parse(text);
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {}; let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {};
overrides.inputConfiguration = overrides.inputConfiguration || {}; overrides.inputConfiguration = overrides.inputConfiguration || {};
overrides.inputConfiguration.enableVibration = true; overrides.inputConfiguration.enableVibration = true;
if (ENABLE_NATIVE_MKB_BETA) { if (ENABLE_NATIVE_MKB_BETA) {
overrides.inputConfiguration.enableMouseAndKeyboard = true; overrides.inputConfiguration.enableMouseAndKeyboard = true;
} }
// Enable touch controller // Enable touch controller
if (TouchController.isEnabled()) { if (TouchController.isEnabled()) {
overrides.inputConfiguration.enableTouchInput = true; overrides.inputConfiguration.enableTouchInput = true;
overrides.inputConfiguration.maxTouchPoints = 10; overrides.inputConfiguration.maxTouchPoints = 10;
} }
// Enable mic // Enable mic
if (PREF_AUDIO_MIC_ON_PLAYING) { if (PREF_AUDIO_MIC_ON_PLAYING) {
overrides.audioConfiguration = overrides.audioConfiguration || {}; overrides.audioConfiguration = overrides.audioConfiguration || {};
overrides.audioConfiguration.enableMicrophone = true; overrides.audioConfiguration.enableMicrophone = true;
} }
obj.clientStreamingConfigOverrides = JSON.stringify(overrides); obj.clientStreamingConfigOverrides = JSON.stringify(overrides);
response.json = () => Promise.resolve(obj); response.json = () => Promise.resolve(obj);
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) {
TitlesInfo.saveFromCatalogInfo(json.Products[productId]);
}
return response; for (let productId in json.Products) {
}); TitlesInfo.saveFromCatalogInfo(json.Products[productId]);
}); }
return response;
} }
if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && (url.includes('/titles') || url.includes('/mru'))) { if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && (url.includes('/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 +10420,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 +10493,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 +10535,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 +10585,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 = {};
@ -10709,6 +10736,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();
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-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-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 = [];
@ -11135,6 +11213,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 +11851,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 +11948,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();
} }