Compare commits

...

28 Commits

Author SHA1 Message Date
0f360d4be1 Bump version to 5.1.2 2024-07-07 21:37:15 +07:00
900ab38153 Minor fixes 2024-07-07 19:20:58 +07:00
c03c63f3c3 Update better-xcloud.user.js 2024-07-07 18:23:37 +07:00
d4f4084991 Disable "Most popular" option 2024-07-07 18:22:41 +07:00
975549b4e7 Add option to hide "All games" section 2024-07-07 18:16:50 +07:00
345d0f78dc Add option to hide "News" section 2024-07-07 16:55:57 +07:00
938dfa6aaa Add option to hide "Friends" section 2024-07-07 16:43:56 +07:00
d7ed9e1603 Add "ignorePlayWithFriendsSection" patch 2024-07-07 16:14:08 +07:00
224e98829d Improve "enableXcloudLogger" patch 2024-07-07 15:28:33 +07:00
56a3f1d8c8 Bug fixes 2024-07-07 14:59:12 +07:00
d82a38c0f1 Update better-xcloud.user.js 2024-07-07 11:21:22 +07:00
77729789e3 Fix problems in the Guide menu #436 #438 2024-07-07 11:20:43 +07:00
5763701355 Update better-xcloud.user.js 2024-07-06 20:58:08 +07:00
cafeed1a3c Bug fixes 2024-07-06 20:48:27 +07:00
691f116ea0 Add "enableTvRoutes" patch 2024-07-06 17:14:02 +07:00
481b365e6e Add "IsSupportedTvBrowser" flag 2024-07-06 16:13:53 +07:00
2b63edb7eb Refactor browser & userAgent's capabilities 2024-07-06 15:53:01 +07:00
b6746598a3 Update better-xcloud.user.js 2024-07-13 12:26:53 +07:00
45bda4bb24 Fix script not being loaded after refreshing token 2024-07-13 12:19:07 +07:00
c93db035f3 Update ICE candidates 2024-07-06 11:08:41 +07:00
e75fa397ee Bump version to 5.1.1 2024-07-02 18:11:08 +07:00
98a9f4fc37 Update better-xcloud.user.js 2024-07-02 18:10:52 +07:00
dee8c9dbd0 Refactor buttons in guide-menu 2024-07-02 18:06:33 +07:00
d31a06be89 Use {once: true} in some event listeners 2024-07-02 17:20:23 +07:00
277c777121 Add "Show controller connection status" setting 2024-07-02 17:08:40 +07:00
385fd71e86 Update better-xcloud.user.js 2024-07-02 06:49:40 +07:00
986d9fe088 Show "Stream settings" and "App settings" in the Guide menu 2024-07-02 06:41:23 +07:00
6de235ce2f Fix overriding experimentation stopped working 2024-07-02 05:50:02 +07:00
27 changed files with 626 additions and 212 deletions

View File

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

File diff suppressed because one or more lines are too long

View File

@ -105,6 +105,10 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
font-family: var(--bx-promptfont-font);
}
select[multiple] {
overflow: auto;
}
/* Hide UI elements */
#headerArea, #uhfSkipToMain, .uhf-footer {
display: none;

6
src/enums/ui-sections.ts Normal file
View File

@ -0,0 +1,6 @@
export enum UiSection {
NEWS = 'news',
FRIENDS = 'friends',
MOST_POPULAR = 'most-popular',
ALL_GAMES = 'all-games',
}

View File

@ -33,17 +33,24 @@ import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
import { GuideMenu, GuideMenuTab } from "./modules/ui/guide-menu";
import { StreamSettings } from "./modules/stream/stream-settings";
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
import { UiSection } from "./enums/ui-sections";
// Handle login page
if (window.location.pathname.includes('/auth/msa')) {
window.addEventListener('load', e => {
window.location.search.includes('loggedIn') && window.setTimeout(() => {
const location = window.location;
// @ts-ignore
location.pathname.includes('/play') && location.reload(true);
}, 2000);
});
const nativePushState = window.history['pushState'];
window.history['pushState'] = function(...args: any[]) {
const url = args[2];
if (url && (url.startsWith('/play') || url.substring(6).startsWith('/play'))) {
console.log('Redirecting to xbox.com/play');
window.stop();
window.location.href = 'https://www.xbox.com' + url;
return;
}
// @ts-ignore
return nativePushState.apply(this, arguments);
}
// Stop processing the script
throw new Error('[Better xCloud] Refreshing the page after logging in');
}
@ -94,8 +101,19 @@ window.addEventListener('load', e => {
window.location.reload(true);
}
}, 3000);
});
// Hide "Play with Friends" skeleton section
if (getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS)) {
document.addEventListener('readystatechange', e => {
if (document.readyState === 'interactive') {
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest('div[class*=HomePage-module]') as HTMLElement;
$parent && ($parent.style.display = 'none');
}
})
}
window.BX_EXPOSED = BxExposed;
// Hide Settings UI when navigate to another page
@ -277,7 +295,7 @@ function main() {
getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext();
getPref(PrefKey.BLOCK_TRACKING) && patchMeControl();
STATES.userAgentHasTouchSupport && TouchController.updateCustomList();
STATES.userAgent.capabilities.touch && TouchController.updateCustomList();
overridePreloadState();
VibrationManager.initialSetup();
@ -303,8 +321,10 @@ function main() {
disablePwa();
// Show a toast when connecting/disconecting controller
window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad));
window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad));
if (getPref(PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS)) {
window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad));
window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad));
}
// Preload Remote Play
if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) {

View File

@ -41,7 +41,7 @@ export class GameBar {
this.actions = [
new ScreenshotAction(),
...(STATES.userAgentHasTouchSupport && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'off') ? [new TouchControlAction()] : []),
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'off') ? [new TouchControlAction()] : []),
new MicrophoneAction(),
];

View File

@ -13,6 +13,7 @@ import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type:
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
import { FeatureGates } from "@/utils/feature-gates.js";
import { UiSection } from "@/enums/ui-sections.js";
type PatchArray = (keyof typeof PATCHES)[];
@ -189,12 +190,18 @@ if (!!window.BX_REMOTE_PLAY_CONFIG) {
},
enableXcloudLogger(str: string) {
const text = 'this.telemetryProvider=e}log(e,t,i){';
const text = 'this.telemetryProvider=e}log(e,t,r){';
if (!str.includes(text)) {
return false;
}
str = str.replaceAll(text, text + 'console.log(Array.from(arguments));');
const newCode = `
const [logTag, logLevel, logMessage] = Array.from(arguments);
const logFunc = [console.debug, console.log, console.warn, console.error][logLevel];
logFunc(logTag, '//', logMessage);
`;
str = str.replaceAll(text, text + newCode);
return str;
},
@ -635,6 +642,102 @@ true` + text;
str = str.replace(text, 'if (!e) e = "https://www.xbox.com";');
return str;
},
exposeDialogRoutes(str: string) {
const text = 'return{goBack:function(){';
if (!str.includes(text)) {
return false;
}
str = str.replace(text, 'return window.BX_EXPOSED.dialogRoutes = {goBack:function(){');
return str;
},
/*
(x.AW, {
path: V.LoginDeviceCode.path,
exact: !0,
render: () => (0, n.jsx)(qe, {
children: (0, n.jsx)(Et.R, {})
})
}, V.LoginDeviceCode.name),
const qe = e => {
let {
children: t
} = e;
const {
isTV: a,
isSupportedTVBrowser: r
} = (0, T.d)();
return a && r ? (0, n.jsx)(n.Fragment, {
children: t
}) : (0, n.jsx)(x.l_, {
to: V.Home.getLink()
})
};
*/
enableTvRoutes(str: string) {
let index = str.indexOf('.LoginDeviceCode.path,');
if (index < 0) {
return false;
}
// Find *qe* name
const match = /render:.*?jsx\)\(([^,]+),/.exec(str.substring(index, index + 100));
if (!match) {
return false;
}
const funcName = match[1];
// Replace *qe*'s return value
// `return a && r ?` => `return a && r || true ?`
index = str.indexOf(`const ${funcName}=e=>{`);
index > -1 && (index = str.indexOf('return ', index));
index > -1 && (index = str.indexOf('?', index));
if (index === -1) {
return false;
}
str = str.substring(0, index) + '|| true' + str.substring(index);
return str;
},
// Don't render "Play With Friends" sections
ignorePlayWithFriendsSection(str: string) {
let index = str.indexOf('location:"PlayWithFriendsRow",');
if (index === -1) {
return false;
}
index = str.indexOf('return', index - 50);
if (index === -1) {
return false;
}
str = str.substring(0, index) + 'return null;' + str.substring(index + 6);
return str;
},
// Don't render "All Games" sections
ignoreAllGamesSection(str: string) {
let index = str.indexOf('className:"AllGamesRow-module__allGamesRowContainer');
if (index === -1) {
return false;
}
index = str.indexOf('grid:!0,', index);
index > -1 && (index = str.indexOf('(0,', index - 70));
if (index === -1) {
return false;
}
str = str.substring(0, index) + 'true ? null :' + str.substring(index);
return str;
},
};
let PATCH_ORDERS: PatchArray = [
@ -652,11 +755,17 @@ let PATCH_ORDERS: PatchArray = [
'broadcastPollingMode',
'exposeStreamSession',
'exposeDialogRoutes',
'enableTvRoutes',
getPref(PrefKey.UI_LAYOUT) !== 'default' && 'websiteLayout',
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
...(getPref(PrefKey.BLOCK_TRACKING) ? [
'disableAiTrack',
'disableTelemetry',
@ -672,7 +781,7 @@ let PATCH_ORDERS: PatchArray = [
'remotePlayKeepAlive',
'remotePlayDirectConnectUrl',
'remotePlayDisableAchievementToast',
STATES.userAgentHasTouchSupport && 'patchUpdateInputConfigurationAsync',
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
] : []),
...(BX_FLAGS.EnableXcloudLogging ? [
@ -698,7 +807,7 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
// Skip feedback dialog
getPref(PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG) && 'skipFeedbackDialog',
...(STATES.userAgentHasTouchSupport ? [
...(STATES.userAgent.capabilities.touch ? [
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'patchShowSensorControls',
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'exposeTouchLayoutManager',
(getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',

View File

@ -91,7 +91,7 @@ export class StreamBadges {
let batteryLevel = '100%';
let batteryLevelInt = 100;
let isCharging = false;
if ('getBattery' in navigator) {
if (STATES.browser.capabilities.batteryApi) {
try {
const bm = await (navigator as NavigatorBattery).getBattery();
isCharging = bm.charging;
@ -224,7 +224,7 @@ export class StreamBadges {
// Battery
let batteryLevel = '';
if ('getBattery' in navigator) {
if (STATES.browser.capabilities.batteryApi) {
batteryLevel = '100%';
}
@ -338,7 +338,7 @@ export class StreamBadges {
// Get battery level
try {
'getBattery' in navigator && (navigator as NavigatorBattery).getBattery().then(bm => {
STATES.browser.capabilities.batteryApi && (navigator as NavigatorBattery).getBattery().then(bm => {
streamBadges.startBatteryLevel = Math.round(bm.level * 100);
});
} catch(e) {}

View File

@ -103,7 +103,7 @@ export class StreamSettings {
}],
},
STATES.userAgentHasTouchSupport && {
STATES.userAgent.capabilities.touch && {
group: 'touch-controller',
label: t('touch-controller'),
items: [{
@ -255,6 +255,8 @@ export class StreamSettings {
$container.classList.remove('bx-gone');
document.body.classList.add('bx-no-scroll');
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
}
hide() {
@ -262,6 +264,8 @@ export class StreamSettings {
this.$container!.classList.add('bx-gone');
document.body.classList.remove('bx-no-scroll');
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
}
#setupDialog() {

View File

@ -1,6 +1,5 @@
import { PrefKey } from "@utils/preferences"
import { PrefKey, getPref } from "@utils/preferences"
import { BxEvent } from "@utils/bx-event"
import { getPref } from "@utils/preferences"
import { CE } from "@utils/html"
import { t } from "@utils/translation"
import { STATES } from "@utils/global"

View File

@ -35,7 +35,7 @@ function cloneStreamHudButton($orgButton: HTMLElement, label: string, svgIcon: t
}
};
if (STATES.browserHasTouchSupport) {
if (STATES.browser.capabilities.touch) {
$container.addEventListener('transitionstart', onTransitionStart);
$container.addEventListener('transitionend', onTransitionEnd);
}

View File

@ -66,8 +66,8 @@ const SETTINGS_UI = {
},
[t('touch-controller')]: {
note: !STATES.userAgentHasTouchSupport ? '⚠️ ' + t('device-unsupported-touch') : null,
unsupported: !STATES.userAgentHasTouchSupport,
note: !STATES.userAgent.capabilities.touch ? '⚠️ ' + t('device-unsupported-touch') : null,
unsupported: !STATES.userAgent.capabilities.touch,
items: [
PrefKey.STREAM_TOUCH_CONTROLLER,
PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF,
@ -89,17 +89,19 @@ const SETTINGS_UI = {
items: [
PrefKey.UI_LAYOUT,
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS,
PrefKey.STREAM_SIMPLIFY_MENU,
PrefKey.SKIP_SPLASH_VIDEO,
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
PrefKey.HIDE_DOTS_ICON,
PrefKey.REDUCE_ANIMATIONS,
PrefKey.BLOCK_SOCIAL_FEATURES,
PrefKey.UI_HIDE_SECTIONS,
],
},
[t('other')]: {
items: [
PrefKey.BLOCK_SOCIAL_FEATURES,
PrefKey.BLOCK_TRACKING,
],
},

View File

@ -1,33 +1,97 @@
import { BxEvent } from "@/utils/bx-event";
import { AppInterface, STATES } from "@/utils/global";
import { createButton, ButtonStyle } from "@/utils/html";
import { createButton, ButtonStyle, CE } from "@/utils/html";
import { t } from "@/utils/translation";
import { StreamSettings } from "../stream/stream-settings";
export enum GuideMenuTab {
HOME,
}
export class GuideMenu {
static #BUTTONS = {
streamSetting: createButton({
label: t('stream-settings'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
// Wait until the Guide dialog is closed
window.addEventListener(BxEvent.XCLOUD_DIALOG_DISMISSED, e => {
setTimeout(() => StreamSettings.getInstance().show(), 50);
}, {once: true});
// Close all xCloud's dialogs
window.BX_EXPOSED.dialogRoutes.closeAll();
},
}),
appSettings: createButton({
label: t('android-app-settings'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
// Close all xCloud's dialogs
window.BX_EXPOSED.dialogRoutes.closeAll();
AppInterface.openAppSettings && AppInterface.openAppSettings();
},
}),
closeApp: createButton({
label: t('close-app'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
onClick: e => {
AppInterface.closeApp();
},
}),
reloadStream: createButton({
label: t('reload-stream'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
confirm(t('confirm-reload-stream')) && window.location.reload();
},
}),
backToHome: createButton({
label: t('back-to-home'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
confirm(t('back-to-home-confirm')) && (window.location.href = window.location.href.substring(0, 31));
},
}),
}
static #renderButtons(buttons: HTMLElement[]) {
const $div = CE('div', {});
for (const $button of buttons) {
$div.appendChild($button);
}
return $div;
}
static #injectHome($root: HTMLElement) {
// Find the last divider
const $dividers = $root.querySelectorAll('div[class*=Divider-module__divider]');
if (!$dividers) {
return;
}
const $lastDivider = $dividers[$dividers.length - 1];
// Add "Close app" button
const buttons: HTMLElement[] = [];
// "Stream settings" button
buttons.push(GuideMenu.#BUTTONS.streamSetting);
// "App settings" & "Close app" buttons
if (AppInterface) {
const $btnQuit = createButton({
label: t('close-app'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
onClick: e => {
AppInterface.closeApp();
},
});
$lastDivider.insertAdjacentElement('afterend', $btnQuit);
buttons.push(GuideMenu.#BUTTONS.appSettings);
buttons.push(GuideMenu.#BUTTONS.closeApp);
}
const $buttons = GuideMenu.#renderButtons(buttons);
const $lastDivider = $dividers[$dividers.length - 1];
$lastDivider.insertAdjacentElement('afterend', $buttons);
}
static #injectHomePlaying($root: HTMLElement) {
@ -36,25 +100,19 @@ export class GuideMenu {
return;
}
// Add buttons
const $btnReload = createButton({
label: t('reload-stream'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
confirm(t('confirm-reload-stream')) && window.location.reload();
},
});
const buttons: HTMLElement[] = [];
const $btnHome = createButton({
label: t('back-to-home'),
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
confirm(t('back-to-home-confirm')) && (window.location.href = window.location.href.substring(0, 31));
},
});
buttons.push(GuideMenu.#BUTTONS.streamSetting);
AppInterface && buttons.push(GuideMenu.#BUTTONS.appSettings);
$btnQuit.insertAdjacentElement('afterend', $btnReload);
$btnReload.insertAdjacentElement('afterend', $btnHome);
// Reload stream
buttons.push(GuideMenu.#BUTTONS.reloadStream);
// Back to home
buttons.push(GuideMenu.#BUTTONS.backToHome);
const $buttons = GuideMenu.#renderButtons(buttons);
$btnQuit.insertAdjacentElement('afterend', $buttons);
// Hide xCloud's Home button
const $btnXcloudHome = $root.querySelector('div[class^=HomeButtonWithDivider]') as HTMLElement;
@ -65,11 +123,13 @@ export class GuideMenu {
const where = (e as any).where as GuideMenuTab;
if (where === GuideMenuTab.HOME) {
const $root = document.querySelector('#gamepass-dialog-root div[role=dialog]') as HTMLElement;
if (STATES.isPlaying) {
GuideMenu.#injectHomePlaying($root);
} else {
GuideMenu.#injectHome($root);
const $root = document.querySelector('#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]') as HTMLElement;
if ($root) {
if (STATES.isPlaying) {
GuideMenu.#injectHomePlaying($root);
} else {
GuideMenu.#injectHome($root);
}
}
}
}

View File

@ -26,8 +26,10 @@ export function localRedirect(path: string) {
$anchor.click();
}
export function setupStreamUi() {
StreamSettings.getInstance();
onChangeVideoPlayerType();
}
(window as any).localRedirect = localRedirect;

14
src/types/index.d.ts vendored
View File

@ -28,8 +28,18 @@ type BxStates = {
appContext: any | null;
serverRegions: any;
userAgentHasTouchSupport: boolean;
browserHasTouchSupport: boolean;
browser: {
capabilities: {
touch: boolean;
batteryApi: boolean;
};
};
userAgent: {
capabilities: {
touch: boolean;
};
};
currentStream: Partial<{
titleId: string;

View File

@ -34,7 +34,7 @@ export const BxExposed = {
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
if (STATES.userAgentHasTouchSupport) {
if (STATES.userAgent.capabilities.touch) {
let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
// Disable touch control when gamepad found

View File

@ -11,6 +11,8 @@ type BxFlags = Partial<{
FeatureGates: {[key: string]: boolean} | null,
ScriptUi: 'default' | 'tv',
IsSupportedTvBrowser: boolean,
}>
// Setup flags

View File

@ -1,23 +1,33 @@
import { CE } from "@utils/html";
import { PrefKey, getPref } from "@utils/preferences";
import { renderStylus } from "@macros/build" with {type: "macro"};
import { UiSection } from "@/enums/ui-sections";
export function addCss() {
const STYLUS_CSS = renderStylus();
let css = STYLUS_CSS;
const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
const selectorToHide = [];
// Hide "News" section
if (PREF_HIDE_SECTIONS.includes(UiSection.NEWS)) {
selectorToHide.push('#BodyContent > div[class*=CarouselRow-module]');
}
// Hide "All games" section
if (PREF_HIDE_SECTIONS.includes(UiSection.ALL_GAMES)) {
selectorToHide.push('#BodyContent div[class*=AllGamesRow-module__gridContainer]');
}
// Hide "Start a party" button in the Guide menu
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
css += `
/* Hide "Play with friends" section */
div[class^=HomePage-module__bottomSpacing]:has(button[class*=SocialEmptyCard]),
button[class*=SocialEmptyCard],
/* Hide "Start a party" button in the Guide menu */
#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]
{
display: none;
}
`;
selectorToHide.push('#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]');
}
if (selectorToHide) {
css += selectorToHide.join(',') + '{ display: none; }';
}
// Reduce animations

View File

@ -16,8 +16,19 @@ export const STATES: BxStates = {
isPlaying: false,
appContext: {},
serverRegions: {},
userAgentHasTouchSupport: userAgentHasTouchSupport,
browserHasTouchSupport: browserHasTouchSupport,
browser: {
capabilities: {
touch: browserHasTouchSupport,
batteryApi: 'getBattery' in window.navigator,
},
},
userAgent: {
capabilities: {
touch: userAgentHasTouchSupport,
}
},
currentStream: {},
remotePlay: {},
@ -31,7 +42,7 @@ export function deepClone(obj: any): any {
}
if (!obj) {
return obj;
return {};
}
return JSON.parse(JSON.stringify(obj));

View File

@ -11,8 +11,6 @@ export function patchVideoApi() {
// Show video player when it's ready
const showFunc = function(this: HTMLVideoElement) {
this.style.visibility = 'visible';
this.removeEventListener('playing', showFunc);
if (!this.videoWidth) {
return;
}
@ -49,7 +47,7 @@ export function patchVideoApi() {
const $parent = this.parentElement!!;
// Video tag is stream player
if (!this.src && $parent.dataset.testid === 'media-container') {
this.addEventListener('playing', showFunc);
this.addEventListener('loadedmetadata', showFunc, {once: true});
}
return nativePlay.apply(this);
@ -163,6 +161,8 @@ export function patchMeControl() {
API: {
setDisplayMode: () => {},
setMobileState: () => {},
addEventListener: () => {},
removeEventListener: () => {},
},
},
};

View File

@ -10,6 +10,7 @@ import { getPreferredServerRegion } from "@utils/region";
import { GamePassCloudGallery } from "../enums/game-pass-gallery";
import { InputType } from "./bx-exposed";
import { FeatureGates } from "./feature-gates";
import { BxLogger } from "./bx-logger";
enum RequestType {
XCLOUD = 'xcloud',
@ -95,7 +96,7 @@ function updateIceCandidates(candidates: any, options: any) {
newCandidates.push(newCandidate('a=end-of-candidates'));
console.log(newCandidates);
BxLogger.info('ICE Candidates', newCandidates);
return newCandidates;
}
@ -157,6 +158,10 @@ class XhomeInterceptor {
console.log(obj);
const serverDetails = obj.serverDetails;
if (serverDetails.ipAddress) {
XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = serverDetails.port;
}
if (serverDetails.ipV4Address) {
XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port;
}
@ -441,7 +446,7 @@ class XcloudInterceptor {
let overrideMkb: boolean | null = null;
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' || BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo!.details.productId)) {
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' || (STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId))) {
overrideMkb = true;
}
@ -576,7 +581,7 @@ export function interceptHttpRequests() {
const response = await NATIVE_FETCH(request, init);
const json = await response.json();
if (json && json.exp && json.treatments) {
if (json && json.exp && json.exp.treatments) {
for (const key in FeatureGates) {
json.exp.treatments[key] = FeatureGates[key]
}
@ -590,7 +595,7 @@ export function interceptHttpRequests() {
}
// Add list of games with custom layouts to the official list
if (STATES.userAgentHasTouchSupport && url.includes('catalog.gamepass.com/sigls/')) {
if (STATES.userAgent.capabilities.touch && url.includes('catalog.gamepass.com/sigls/')) {
const response = await NATIVE_FETCH(request, init);
const obj = await response.clone().json();

View File

@ -7,6 +7,7 @@ import type { PreferenceSetting, PreferenceSettings } from "@/types/preferences"
import { AppInterface, STATES } from "@utils/global";
import { StreamPlayerType, StreamVideoProcessing } from "@enums/stream-player";
import { UserAgentProfile } from "@/enums/user-agent";
import { UiSection } from "@/enums/ui-sections";
export enum PrefKey {
LAST_UPDATE_CHECK = 'version_last_check',
@ -45,6 +46,7 @@ export enum PrefKey {
CONTROLLER_ENABLE_VIBRATION = 'controller_enable_vibration',
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
CONTROLLER_SHOW_CONNECTION_STATUS = 'controller_show_connection_status',
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
@ -69,6 +71,7 @@ export enum PrefKey {
UI_LAYOUT = 'ui_layout',
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
UI_HIDE_SECTIONS = 'ui_hide_sections',
UI_HOME_CONTEXT_MENU_DISABLED = 'ui_home_context_menu_disabled',
@ -267,7 +270,7 @@ export class Preferences {
all: t('tc-all-games'),
off: t('off'),
},
unsupported: !STATES.userAgentHasTouchSupport,
unsupported: !STATES.userAgent.capabilities.touch,
ready: (setting: PreferenceSetting) => {
if (setting.unsupported) {
setting.default = 'default';
@ -277,7 +280,7 @@ export class Preferences {
[PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF]: {
label: t('tc-auto-off'),
default: false,
unsupported: !STATES.userAgentHasTouchSupport,
unsupported: !STATES.userAgent.capabilities.touch,
},
[PrefKey.STREAM_TOUCH_CONTROLLER_DEFAULT_OPACITY]: {
type: SettingElementType.NUMBER_STEPPER,
@ -291,7 +294,7 @@ export class Preferences {
ticks: 10,
hideSlider: true,
},
unsupported: !STATES.userAgentHasTouchSupport,
unsupported: !STATES.userAgent.capabilities.touch,
},
[PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: {
label: t('tc-standard-layout-style'),
@ -301,7 +304,7 @@ export class Preferences {
white: t('tc-all-white'),
muted: t('tc-muted-colors'),
},
unsupported: !STATES.userAgentHasTouchSupport,
unsupported: !STATES.userAgent.capabilities.touch,
},
[PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: {
label: t('tc-custom-layout-style'),
@ -310,7 +313,7 @@ export class Preferences {
default: t('default'),
muted: t('tc-muted-colors'),
},
unsupported: !STATES.userAgentHasTouchSupport,
unsupported: !STATES.userAgent.capabilities.touch,
},
[PrefKey.STREAM_SIMPLIFY_MENU]: {
@ -384,6 +387,11 @@ export class Preferences {
},
*/
[PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS]: {
label: t('show-controller-connection-status'),
default: true,
},
[PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: {
default: false,
},
@ -548,7 +556,21 @@ export class Preferences {
[PrefKey.UI_HOME_CONTEXT_MENU_DISABLED]: {
label: t('disable-home-context-menu'),
default: STATES.browserHasTouchSupport,
default: STATES.browser.capabilities.touch,
},
[PrefKey.UI_HIDE_SECTIONS]: {
label: t('hide-sections'),
default: [],
multipleOptions: {
[UiSection.NEWS]: t('section-news'),
[UiSection.FRIENDS]: t('section-play-with-friends'),
// [UiSection.MOST_POPULAR]: t('section-most-popular'),
[UiSection.ALL_GAMES]: t('section-all-games'),
},
params: {
size: 3,
},
},
[PrefKey.BLOCK_SOCIAL_FEATURES]: {

View File

@ -24,7 +24,7 @@ export function overridePreloadState() {
}
// Add list of games with custom layouts to the official list
if (STATES.userAgentHasTouchSupport) {
if (STATES.userAgent.capabilities.touch) {
try {
const sigls = state.xcloud.sigls;
if (GamePassCloudGallery.TOUCH in sigls) {

View File

@ -57,7 +57,7 @@ export class Screenshot {
return;
}
$player.parentElement!.addEventListener('animationend', this.#onAnimationEnd);
$player.parentElement!.addEventListener('animationend', this.#onAnimationEnd, { once: true });
$player.parentElement!.classList.add('bx-taking-screenshot');
const canvasContext = Screenshot.#canvasContext;

View File

@ -27,7 +27,7 @@ export enum SettingElementType {
export class SettingElement {
static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
const $control = CE<HTMLSelectElement>('select', {
title: setting.label,
// title: setting.label,
tabindex: 0,
}) as HTMLSelectElement;
for (let value in setting.options) {
@ -54,7 +54,7 @@ export class SettingElement {
static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}) {
const $control = CE<HTMLSelectElement>('select', {
title: setting.label,
// title: setting.label,
multiple: true,
tabindex: 0,
});

View File

@ -110,6 +110,7 @@ const Texts = {
"hide": "Hide",
"hide-idle-cursor": "Hide mouse cursor on idle",
"hide-scrollbar": "Hide web page's scrollbar",
"hide-sections": "Hide sections",
"hide-system-menu-icon": "Hide System menu's icon",
"hide-touch-controller": "Hide touch controller",
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
@ -164,13 +165,13 @@ const Texts = {
(e: any) => `${e.key} でこの機能を切替`,
(e: any) => `${e.key} 키를 눌러 이 기능을 켜고 끄세요`,
(e: any) => `Naciśnij ${e.key} aby przełączyć tę funkcję`,
,
(e: any) => `Pressione ${e.key} para alternar este recurso`,
(e: any) => `Нажмите ${e.key} для переключения этой функции`,
,
(e: any) => `Etkinleştirmek için ${e.key} tuşuna basın`,
(e: any) => `Натисніть ${e.key} щоб перемкнути цю функцію`,
(e: any) => `Nhấn ${e.key} để bật/tắt tính năng này`,
,
(e: any) => `按下 ${e.key} 来切换此功能`,
],
"press-to-bind": "Press a key or do a mouse click to bind...",
"prompt-preset-name": "Preset's name:",
@ -191,6 +192,10 @@ const Texts = {
"save": "Save",
"screen": "Screen",
"screenshot-apply-filters": "Applies video filters to screenshots",
"section-all-games": "All games",
"section-most-popular": "Most popular",
"section-news": "News",
"section-play-with-friends": "Play with friends",
"separate-touch-controller": "Separate Touch controller & Controller #1",
"separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2",
"server": "Server",
@ -200,6 +205,7 @@ const Texts = {
"sharpness": "Sharpness",
"shortcut-keys": "Shortcut keys",
"show": "Show",
"show-controller-connection-status": "Show controller connection status",
"show-game-art": "Show game art",
"show-hide": "Show/hide",
"show-stats-on-startup": "Show stats when starting the game",

View File

@ -1,11 +1,14 @@
import { UserAgentProfile } from "@enums/user-agent";
import { deepClone } from "./global";
import { BX_FLAGS } from "./bx-flags";
type UserAgentConfig = {
profile: UserAgentProfile,
custom?: string,
};
const SMART_TV_UNIQUE_ID = 'FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA';
let CHROMIUM_VERSION = '123.0.0.0';
if (!!(window as any).chrome || window.navigator.userAgent.includes('Chrome')) {
// Get Chromium version in the original User-Agent value
@ -26,8 +29,8 @@ export class UserAgent {
static #USER_AGENTS: PartialRecord<UserAgentProfile, string> = {
[UserAgentProfile.WINDOWS_EDGE]: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROMIUM_VERSION} Safari/537.36 Edg/${CHROMIUM_VERSION}`,
[UserAgentProfile.MACOS_SAFARI]: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1',
[UserAgentProfile.SMARTTV_GENERIC]: window.navigator.userAgent + ' SmartTV',
[UserAgentProfile.SMARTTV_TIZEN]: `Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) ${CHROMIUM_VERSION}/7.0 TV Safari/537.36`,
[UserAgentProfile.SMARTTV_GENERIC]: `${window.navigator.userAgent} SmartTV ${SMART_TV_UNIQUE_ID}`,
[UserAgentProfile.SMARTTV_TIZEN]: `Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) ${CHROMIUM_VERSION}/7.0 TV Safari/537.36 ${SMART_TV_UNIQUE_ID}`,
[UserAgentProfile.VR_OCULUS]: window.navigator.userAgent + ' OculusBrowser VR',
}
@ -116,7 +119,12 @@ export class UserAgent {
return;
}
const newUserAgent = UserAgent.get(profile);
let newUserAgent = UserAgent.get(profile);
// Pretend to be Tizen TV
if (BX_FLAGS.IsSupportedTvBrowser) {
newUserAgent += ` SmartTV ${SMART_TV_UNIQUE_ID}`;
}
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
(window.navigator as any).orgUserAgentData = (window.navigator as any).userAgentData;