Compare commits

...

18 Commits

2 changed files with 307 additions and 226 deletions

View File

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

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.4.0
// @version 3.5.0
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -21,6 +21,7 @@ const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
// Setup flags
const DEFAULT_FLAGS = {
CheckForUpdate: true,
PreloadRemotePlay: true,
PreloadUi: false,
EnableXcloudLogging: false,
@ -667,6 +668,40 @@ const Translations = {
"Đó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": [
"Zustandsabhängige Textfarbe",
"Format teks kondisional",
@ -1228,6 +1263,23 @@ const Translations = {
"Đã bật",
"启用",
],
"experimental": [
,
"Eksperimental",
"Experimental",
,
,
,
"実験的機能",
,
"Eksperymentalne",
"Experimental",
"Экспериментально",
"Deneme aşamasında",
"Експериментальне",
"Thử nghiệm",
"实验性功能",
],
"export": [
"Exportieren",
"Ekspor",
@ -1348,21 +1400,21 @@ const Translations = {
"空闲时隐藏鼠标",
],
"hide-scrollbar": [
,
,
"Scrollbalken der Webseite ausblenden",
"Sembunyikan bilah gulir halaman",
"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",
,
"隐藏浏览器滚动条",
],
"hide-system-menu-icon": [
"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",
"Применяет фильтры видео к скриншотам",
"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",
,
"为截图添加滤镜",
],
"screenshot-button-position": [
"Position des Screenshot-Buttons",
@ -3219,15 +3271,15 @@ const Translations = {
],
"vibration-status": [
"Vibration",
,
"Getaran",
"Vibration",
,
,
,
"振動",
,
,
,
"Wibracje",
"Vibração",
"Вибрация",
"Titreşim",
"Вібрація",
@ -3548,7 +3600,7 @@ class Dialog {
class RemotePlay {
static XCLOUD_TOKEN;
static XHOME_TOKEN;
static #CONSOLES;
static #CONSOLES = null;
static #STATE_LABELS = {
'On': t('powered-on'),
@ -3611,9 +3663,6 @@ class RemotePlay {
RemotePlay.#getConsolesList(() => {
console.log(RemotePlay.#CONSOLES);
RemotePlay.#renderConsoles();
const $btn = document.querySelector('.bx-remote-play-button');
$btn && ($btn.disabled = false);
});
});
}
@ -3815,6 +3864,7 @@ class RemotePlay {
static togglePopup(force = null) {
if (!getPref(Preferences.REMOTE_PLAY_ENABLED) || !RemotePlay.isReady()) {
Toast.show(t('getting-consoles-list'));
return;
}
@ -7048,7 +7098,7 @@ class UserAgent {
}
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
@ -7116,6 +7166,8 @@ class Preferences {
static get USER_AGENT_CUSTOM() { return 'user_agent_custom'; }
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_AUTO_OFF() { return 'stream_touch_controller_auto_off'; }
static get STREAM_TOUCH_CONTROLLER_STYLE_STANDARD() { return 'stream_touch_controller_style_standard'; }
@ -7347,6 +7399,12 @@ class Preferences {
'default': false,
},
[Preferences.STREAM_COMBINE_SOURCES]: {
'default': false,
'experimental': true,
'note': t('combine-audio-video-streams-summary'),
},
[Preferences.STREAM_TOUCH_CONTROLLER]: {
'default': 'all',
'options': {
@ -7579,6 +7637,7 @@ class Preferences {
},
[Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: {
'default': false,
'experimental': true,
},
[Preferences.AUDIO_VOLUME]: {
'type': SettingElement.TYPE_NUMBER_STEPPER,
@ -8170,6 +8229,16 @@ if (gamepadFound) {
funcStr = funcStr.replace(text, newCode + text);
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 = [
@ -8223,6 +8292,8 @@ if (gamepadFound) {
'disableGamepadDisconnectedScreen',
ENABLE_NATIVE_MKB_BETA && 'mkbMouseAndKeyboardEnabled',
],
getPref(Preferences.STREAM_COMBINE_SOURCES) && ['streamCombineSources'],
];
static #patchFunctionBind() {
@ -8358,7 +8429,7 @@ if (gamepadFound) {
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 lastCheck = getPref(Preferences.LAST_UPDATE_CHECK);
@ -9817,6 +9888,10 @@ body:not([data-media-type=tv]) div[class*=MenuItem-module__label] {
// Hide scrollbar
if (getPref(Preferences.UI_SCROLLBAR_HIDE)) {
css += `
html {
scrollbar-width: none;
}
body::-webkit-scrollbar {
display: none;
}
@ -9983,36 +10058,33 @@ function interceptHttpRequests() {
const consoleAddrs = {};
const patchIceCandidates = function(...arg) {
const patchIceCandidates = async (...arg) => {
// ICE server candidates
const request = arg[0];
const url = (typeof request === 'string') ? request : request.url;
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 => {
return response.clone().text().then(text => {
if (!text.length) {
return response;
}
if (!text.length) {
return response;
}
const options = {
preferIpv6Server: PREF_PREFER_IPV6_SERVER,
consoleAddrs: consoleAddrs,
};
const options = {
preferIpv6Server: PREF_PREFER_IPV6_SERVER,
consoleAddrs: consoleAddrs,
};
const obj = JSON.parse(text);
let exchangeResponse = JSON.parse(obj.exchangeResponse);
exchangeResponse = updateIceCandidates(exchangeResponse, options)
obj.exchangeResponse = JSON.stringify(exchangeResponse);
const obj = JSON.parse(text);
let exchangeResponse = JSON.parse(obj.exchangeResponse);
exchangeResponse = updateIceCandidates(exchangeResponse, options)
obj.exchangeResponse = JSON.stringify(exchangeResponse);
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
});
});
return response;
}
return null;
@ -10041,7 +10113,7 @@ function interceptHttpRequests() {
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();
const clone = request.clone();
@ -10078,66 +10150,60 @@ function interceptHttpRequests() {
// Get console IP
if (url.includes('/configuration')) {
const promise = NATIVE_FETCH(...arg);
const response = await NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(obj => {
console.log(obj);
const obj = await response.clone().json()
console.log(obj);
const serverDetails = obj.serverDetails;
if (serverDetails.ipV4Address) {
consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port;
}
const serverDetails = obj.serverDetails;
if (serverDetails.ipV4Address) {
consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port;
}
if (serverDetails.ipV6Address) {
consoleAddrs[serverDetails.ipV6Address] = serverDetails.ipV6Port;
}
if (serverDetails.ipV6Address) {
consoleAddrs[serverDetails.ipV6Address] = serverDetails.ipV6Port;
}
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
});
});
return response;
} 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 => {
return response.clone().json().then(obj => {
const xboxTitleId = JSON.parse(opts.body).titleIds[0];
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;
if (!hasTouchSupport) {
const supportedInputTypes = inputConfigs.supportedInputTypes;
hasTouchSupport = supportedInputTypes.includes('NativeTouch');
}
let hasTouchSupport = inputConfigs.supportedTabs.length > 0;
if (!hasTouchSupport) {
const supportedInputTypes = inputConfigs.supportedInputTypes;
hasTouchSupport = supportedInputTypes.includes('NativeTouch');
}
if (hasTouchSupport) {
TouchController.disable();
if (hasTouchSupport) {
TouchController.disable();
BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
data: null,
});
} else {
TouchController.enable();
TouchController.getCustomLayouts(xboxTitleId);
}
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
data: null,
});
});
} 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 {
const clone = request.clone();
@ -10182,46 +10248,46 @@ function interceptHttpRequests() {
}
// ICE server candidates
const patchedIpv6 = patchIceCandidates(...arg);
const patchedIpv6 = await patchIceCandidates(...arg);
if (patchedIpv6) {
return patchedIpv6;
}
// Server list
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 => {
return response.clone().json().then(obj => {
// Store xCloud token
RemotePlay.XCLOUD_TOKEN = obj.gsToken;
// Preload Remote Play
BX_FLAGS.PreloadRemotePlay && RemotePlay.preload();
// Get server list
if (!Object.keys(SERVER_REGIONS).length) {
for (let region of obj.offeringSettings.regions) {
SERVER_REGIONS[region.name] = Object.assign({}, region);
}
// Store xCloud token
RemotePlay.XCLOUD_TOKEN = obj.gsToken;
// Start rendering UI
if (document.querySelector('div[class^=UnsupportedMarketPage]')) {
setTimeout(watchHeader, 2000);
} else {
watchHeader();
}
}
// Get server list
if (!Object.keys(SERVER_REGIONS).length) {
for (let region of obj.offeringSettings.regions) {
SERVER_REGIONS[region.name] = Object.assign({}, region);
}
const preferredRegion = getPreferredServerRegion();
if (preferredRegion in SERVER_REGIONS) {
const tmp = Object.assign({}, SERVER_REGIONS[preferredRegion]);
tmp.isDefault = true;
// Start rendering UI
if (document.querySelector('div[class^=UnsupportedMarketPage]')) {
setTimeout(watchHeader, 2000);
} 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);
return response;
});
});
obj.offeringSettings.regions = [tmp];
}
response.json = () => Promise.resolve(obj);
return response;
}
// Get region
@ -10266,22 +10332,18 @@ function interceptHttpRequests() {
// Get wait time
if (PREF_UI_LOADING_SCREEN_WAIT_TIME && url.includes('xboxlive.com') && url.includes('/waittime/')) {
const promise = 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);
}
const response = await NATIVE_FETCH(...arg);
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') {
const promise = NATIVE_FETCH(...arg);
// Touch controller for all games
if (PREF_STREAM_TOUCH_CONTROLLER === 'all') {
TouchController.disable();
@ -10296,68 +10358,61 @@ function interceptHttpRequests() {
}
// Intercept configurations
return promise.then(response => {
return response.clone().text().then(text => {
if (!text.length) {
return response;
}
const response = await NATIVE_FETCH(...arg);
const text = await response.clone().text();
if (!text.length) {
return response;
}
const obj = JSON.parse(text);
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {};
const obj = JSON.parse(text);
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {};
overrides.inputConfiguration = overrides.inputConfiguration || {};
overrides.inputConfiguration.enableVibration = true;
if (ENABLE_NATIVE_MKB_BETA) {
overrides.inputConfiguration.enableMouseAndKeyboard = true;
}
overrides.inputConfiguration = overrides.inputConfiguration || {};
overrides.inputConfiguration.enableVibration = true;
if (ENABLE_NATIVE_MKB_BETA) {
overrides.inputConfiguration.enableMouseAndKeyboard = true;
}
// Enable touch controller
if (TouchController.isEnabled()) {
overrides.inputConfiguration.enableTouchInput = true;
overrides.inputConfiguration.maxTouchPoints = 10;
}
// Enable touch controller
if (TouchController.isEnabled()) {
overrides.inputConfiguration.enableTouchInput = true;
overrides.inputConfiguration.maxTouchPoints = 10;
}
// Enable mic
if (PREF_AUDIO_MIC_ON_PLAYING) {
overrides.audioConfiguration = overrides.audioConfiguration || {};
overrides.audioConfiguration.enableMicrophone = true;
}
// Enable mic
if (PREF_AUDIO_MIC_ON_PLAYING) {
overrides.audioConfiguration = overrides.audioConfiguration || {};
overrides.audioConfiguration.enableMicrophone = true;
}
obj.clientStreamingConfigOverrides = JSON.stringify(overrides);
obj.clientStreamingConfigOverrides = JSON.stringify(overrides);
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
});
});
return response;
}
// catalog.gamepass
if (url.startsWith('https://catalog.gamepass.com') && url.includes('/products')) {
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(json => {
for (let productId in json.Products) {
TitlesInfo.saveFromCatalogInfo(json.Products[productId]);
}
const response = await NATIVE_FETCH(...arg);
const json = await response.clone().json()
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'))) {
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(json => {
for (let game of json.results) {
TitlesInfo.saveFromTitleInfo(game);
}
const response = await NATIVE_FETCH(...arg);
const json = await response.clone().json()
for (let game of json.results) {
TitlesInfo.saveFromTitleInfo(game);
}
return response;
});
});
return response;
}
return NATIVE_FETCH(...arg);
@ -10365,60 +10420,17 @@ function interceptHttpRequests() {
}
function injectSettingsButton($parent) {
if (!$parent) {
function setupSettingsUi() {
// Avoid rendering the Settings multiple times
if (document.querySelector('.bx-settings-container')) {
return;
}
const PREF_PREFERRED_REGION = getPreferredServerRegion();
const PREF_LATEST_VERSION = getPref(Preferences.LATEST_VERSION);
const $headerFragment = document.createDocumentFragment();
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
const $container = CE('div', {
'class': 'bx-settings-container bx-gone',
@ -10481,12 +10493,14 @@ function injectSettingsButton($parent) {
[Preferences.STREAM_TARGET_RESOLUTION]: t('target-resolution'),
[Preferences.STREAM_CODEC_PROFILE]: t('visual-quality'),
[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.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'),
[Preferences.AUDIO_ENABLE_VOLUME_CONTROL]: t('enable-volume-control'),
[Preferences.STREAM_COMBINE_SOURCES]: t('combine-audio-video-streams'),
},
[t('local-co-op')]: {
@ -10521,9 +10535,9 @@ function injectSettingsButton($parent) {
},
[t('ui')]: {
[Preferences.UI_LAYOUT]: t('layout'),
[Preferences.UI_SCROLLBAR_HIDE]: t('hide-scrollbar'),
[Preferences.STREAM_SIMPLIFY_MENU]: t('simplify-stream-menu'),
[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.REDUCE_ANIMATIONS]: t('reduce-animations'),
},
@ -10571,14 +10585,27 @@ function injectSettingsButton($parent) {
for (let settingId in SETTINGS_UI[groupLabel]) {
// Don't render custom settings
if (settingId.startsWith('_')) {
if (!settingId || settingId === 'false' || settingId === 'undefined' || settingId.startsWith('_')) {
continue;
}
const setting = Preferences.SETTINGS[settingId];
if (!setting) {
continue;
}
const settingLabel = SETTINGS_UI[groupLabel][settingId];
const settingNote = setting.note;
let settingLabel = SETTINGS_UI[groupLabel][settingId];
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 labelAttrs = {};
@ -10709,6 +10736,57 @@ function injectSettingsButton($parent) {
$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() {
const filters = [];
@ -11135,6 +11213,10 @@ function patchVideoApi() {
return nativePlay.apply(this);
}
if (!!this.src) {
return nativePlay.apply(this);
}
this.addEventListener('playing', showFunc);
injectStreamMenuButtons();
@ -11769,7 +11851,7 @@ window.addEventListener(BxEvent.STREAM_STOPPED, e => {
PreloadedState.override();
// Check for Update
checkForUpdate();
BX_FLAGS.CheckForUpdate && checkForUpdate();
// Monkey patches
if (getPref(Preferences.AUDIO_ENABLE_VOLUME_CONTROL)) {
@ -11866,7 +11948,6 @@ Patcher.initialize();
// Preload Remote Play
if (getPref(Preferences.REMOTE_PLAY_ENABLED)) {
BX_FLAGS.PreloadRemotePlay && RemotePlay.preload();
RemotePlay.detect();
}