mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-13 00:19:17 +02:00
Refactor Remote Play feature
This commit is contained in:
@@ -79,7 +79,6 @@ export const enum GlobalPref {
|
||||
AUDIO_MIC_ON_PLAYING = 'audio.mic.onPlaying',
|
||||
AUDIO_VOLUME_CONTROL_ENABLED = 'audio.volume.booster.enabled',
|
||||
|
||||
REMOTE_PLAY_ENABLED = 'xhome.enabled',
|
||||
REMOTE_PLAY_STREAM_RESOLUTION = 'xhome.video.resolution',
|
||||
|
||||
GAME_FORTNITE_FORCE_CONSOLE = 'game.fortnite.forceConsole',
|
||||
@@ -99,7 +98,6 @@ export type GlobalPrefTypeMap = {
|
||||
[GlobalPref.MKB_HIDE_IDLE_CURSOR]: boolean;
|
||||
[GlobalPref.NATIVE_MKB_FORCED_GAMES]: string[];
|
||||
[GlobalPref.NATIVE_MKB_MODE]: NativeMkbMode;
|
||||
[GlobalPref.REMOTE_PLAY_ENABLED]: boolean;
|
||||
[GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: StreamResolution;
|
||||
[GlobalPref.SCREENSHOT_APPLY_FILTERS]: boolean;
|
||||
[GlobalPref.SERVER_BYPASS_RESTRICTION]: string;
|
||||
@@ -232,7 +230,6 @@ export const ALL_PREFS: {
|
||||
GlobalPref.MKB_HIDE_IDLE_CURSOR,
|
||||
GlobalPref.NATIVE_MKB_FORCED_GAMES,
|
||||
GlobalPref.NATIVE_MKB_MODE,
|
||||
GlobalPref.REMOTE_PLAY_ENABLED,
|
||||
GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION,
|
||||
GlobalPref.SCREENSHOT_APPLY_FILTERS,
|
||||
GlobalPref.SERVER_BYPASS_RESTRICTION,
|
||||
|
@@ -133,6 +133,7 @@ export const enum BlockFeature {
|
||||
BYOG = 'byog',
|
||||
NOTIFICATIONS_INVITES = 'notifications-invites',
|
||||
NOTIFICATIONS_ACHIEVEMENTS = 'notifications-achievements',
|
||||
REMOTE_PLAY = 'remote-play',
|
||||
}
|
||||
|
||||
export const enum UiTheme {
|
||||
|
@@ -329,7 +329,7 @@ BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||
let newId: number = parseInt(json.titleid, 16);
|
||||
|
||||
// Get titleSlug for Remote Play
|
||||
if (STATES.remotePlay.isPlaying) {
|
||||
if (window.location.pathname.includes('/play/consoles/launch/')) {
|
||||
currentStream.titleSlug = 'remote-play';
|
||||
if (json.focused) {
|
||||
const productTitle = await XboxApi.getProductTitle(newId);
|
||||
@@ -448,11 +448,6 @@ function main() {
|
||||
Patcher.init();
|
||||
disablePwa();
|
||||
|
||||
// Preload Remote Play
|
||||
if (getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
|
||||
RemotePlayManager.detect();
|
||||
}
|
||||
|
||||
if (getGlobalPref(GlobalPref.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
|
||||
TouchController.setup();
|
||||
}
|
||||
|
@@ -36,19 +36,25 @@ export class PatcherUtils {
|
||||
return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
|
||||
}
|
||||
|
||||
static replaceAfterIndex(txt: string, search: string, replaceWith: string, index: number) {
|
||||
const before = txt.slice(0, index);
|
||||
const after = txt.slice(index).replace(search, replaceWith);
|
||||
return before + after;
|
||||
}
|
||||
|
||||
static filterPatches(patches: Array<PatchName | false>): PatchArray {
|
||||
return patches.filter((item): item is PatchName => !!item);
|
||||
}
|
||||
|
||||
static patchBeforePageLoad(str: string, page: PatchPage): string | false {
|
||||
let text = `chunkName:()=>"${page}-page",`;
|
||||
if (!str.includes(text)) {
|
||||
const index = str.indexOf(`chunkName:()=>"${page}-page",`);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.replace('requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
|
||||
str = str.replace('requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
|
||||
|
||||
str = PatcherUtils.replaceAfterIndex(str, 'requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`, index);
|
||||
str = PatcherUtils.replaceAfterIndex(str, 'requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`, index);
|
||||
console.log(str);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ import { PatcherUtils } from "./patcher-utils.js";
|
||||
|
||||
export type PatchName = keyof typeof PATCHES;
|
||||
export type PatchArray = PatchName[];
|
||||
export type PatchPage = 'home' | 'stream' | 'product-detail';
|
||||
export type PatchPage = 'home' | 'stream' | 'remote-play-stream' | 'product-detail';
|
||||
type PatchFunction = (str: string) => string | false;
|
||||
|
||||
const LOG_TAG = 'Patcher';
|
||||
@@ -114,18 +114,6 @@ const PATCHES = {
|
||||
return str;
|
||||
},
|
||||
|
||||
// Enable Remote Play feature
|
||||
remotePlayConnectMode(str: string) {
|
||||
let text = 'connectMode:"cloud-connect",';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newCode = `connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
|
||||
remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',`;
|
||||
return str.replace(text, newCode);
|
||||
},
|
||||
|
||||
// Remote Play: Disable achievement toast
|
||||
remotePlayDisableAchievementToast(str: string) {
|
||||
let text = '.AchievementUnlock:{';
|
||||
@@ -133,7 +121,7 @@ remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFI
|
||||
return false;
|
||||
}
|
||||
|
||||
const newCode = `if (window.location.pathname.includes('/consoles/launch/')) return;`;
|
||||
const newCode = `if (window.location.pathname.includes('/play/consoles/launch/')) return;`;
|
||||
return str.replace(text, text + newCode);
|
||||
},
|
||||
|
||||
@@ -936,6 +924,10 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
||||
return PatcherUtils.patchBeforePageLoad(str, 'stream');
|
||||
},
|
||||
|
||||
remotePlayStreamPageBeforeLoad(str: string) {
|
||||
return PatcherUtils.patchBeforePageLoad(str, 'remote-play-stream');
|
||||
},
|
||||
|
||||
disableAbsoluteMouse(str: string) {
|
||||
let text = 'sendAbsoluteMouseCapableMessage(e){';
|
||||
if (!str.includes(text)) {
|
||||
@@ -1260,6 +1252,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
'injectErrorPageUseEffect',
|
||||
|
||||
'streamPageBeforeLoad',
|
||||
'remotePlayStreamPageBeforeLoad',
|
||||
|
||||
'injectGuideHomeUseEffect',
|
||||
'injectAchievementsProgressUseEffect',
|
||||
@@ -1298,7 +1291,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
'disableTelemetryProvider',
|
||||
] : []) as PatchArray,
|
||||
|
||||
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
|
||||
...(!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? [
|
||||
'remotePlayKeepAlive',
|
||||
'remotePlayDisableAchievementToast',
|
||||
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
|
||||
@@ -1365,10 +1358,9 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||
|
||||
getGlobalPref(GlobalPref.STREAM_COMBINE_SOURCES) && 'streamCombineSources',
|
||||
|
||||
...(getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? [
|
||||
...(!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? [
|
||||
'remotePlayPostStreamRedirectUrl',
|
||||
'patchRemotePlayMkb',
|
||||
'remotePlayConnectMode',
|
||||
] : []) as PatchArray,
|
||||
|
||||
// Native MKB
|
||||
@@ -1388,6 +1380,7 @@ export class Patcher {
|
||||
private static remainingPatches: { [key in PatchPage]: PatchArray } = {
|
||||
home: HOME_PAGE_PATCH_ORDERS,
|
||||
stream: STREAM_PAGE_PATCH_ORDERS,
|
||||
'remote-play-stream': STREAM_PAGE_PATCH_ORDERS,
|
||||
'product-detail': PRODUCT_DETAIL_PAGE_PATCH_ORDERS,
|
||||
};
|
||||
|
||||
@@ -1551,7 +1544,9 @@ export class PatcherCache {
|
||||
BxLogger.info(LOG_TAG, 'Cache', this.CACHE);
|
||||
|
||||
const pathName = window.location.pathname;
|
||||
if (pathName.includes('/play/launch/')) {
|
||||
if (pathName.includes('/play/consoles/launch/')) {
|
||||
Patcher.patchPage('remote-play-stream');
|
||||
} else if (pathName.includes('/play/launch/')) {
|
||||
Patcher.patchPage('stream');
|
||||
} else if (pathName.includes('/play/games/')) {
|
||||
Patcher.patchPage('product-detail');
|
||||
|
@@ -3,7 +3,7 @@ declare const e: string;
|
||||
|
||||
try {
|
||||
const msg = JSON.parse(e);
|
||||
if (msg.reason === 'WarningForBeingIdle' && window.location.pathname.includes('/consoles/launch/')) {
|
||||
if (msg.reason === 'WarningForBeingIdle' && window.location.pathname.includes('/play/consoles/launch/')) {
|
||||
$this$.sendKeepAlive();
|
||||
// @ts-ignore
|
||||
return;
|
||||
|
@@ -8,6 +8,7 @@ import { HeaderSection } from "./ui/header";
|
||||
import { GlobalPref } from "@/enums/pref-keys";
|
||||
import { getGlobalPref, setGlobalPref } from "@/utils/pref-utils";
|
||||
import { RemotePlayDialog } from "./ui/dialog/remote-play-dialog";
|
||||
import { BlockFeature } from "@/enums/pref-values";
|
||||
|
||||
export const enum RemotePlayConsoleState {
|
||||
ON = 'On',
|
||||
@@ -37,7 +38,7 @@ export class RemotePlayManager {
|
||||
private static instance: RemotePlayManager | null | undefined;
|
||||
public static getInstance(): typeof RemotePlayManager['instance'] {
|
||||
if (typeof RemotePlayManager.instance === 'undefined') {
|
||||
if (getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
|
||||
if (!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY)) {
|
||||
RemotePlayManager.instance = new RemotePlayManager();
|
||||
} else {
|
||||
RemotePlayManager.instance = null;
|
||||
@@ -194,13 +195,7 @@ export class RemotePlayManager {
|
||||
setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, resolution, 'ui');
|
||||
}
|
||||
|
||||
STATES.remotePlay.config = {
|
||||
serverId: serverId,
|
||||
};
|
||||
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
|
||||
|
||||
localRedirect('/launch/fortnite/BT5P2X999VH2#remote-play');
|
||||
setTimeout(() => localRedirect('/consoles/launch/' + serverId), 100);
|
||||
localRedirect('/consoles/launch/' + serverId);
|
||||
}
|
||||
|
||||
togglePopup(force = null) {
|
||||
@@ -226,21 +221,6 @@ export class RemotePlayManager {
|
||||
RemotePlayDialog.getInstance().show();
|
||||
}
|
||||
|
||||
static detect() {
|
||||
if (!getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
STATES.remotePlay.isPlaying = window.location.pathname.includes('/launch/') && window.location.hash.startsWith('#remote-play');
|
||||
if (STATES.remotePlay?.isPlaying) {
|
||||
window.BX_REMOTE_PLAY_CONFIG = STATES.remotePlay.config;
|
||||
// Remove /launch/... from URL
|
||||
window.history.replaceState({origin: 'better-xcloud'}, '', 'https://www.xbox.com/' + location.pathname.substring(1, 6) + '/play');
|
||||
} else {
|
||||
window.BX_REMOTE_PLAY_CONFIG = null;
|
||||
}
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.consoles !== null;
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ export class RemotePlayDialog extends NavigationDialog {
|
||||
$resolutions.addEventListener('input', (e: Event) => {
|
||||
const value = (e.target as HTMLSelectElement).value;
|
||||
|
||||
$settingNote.textContent = value === StreamResolution.DIM_1080P ? '✅ ' + t('can-stream-xbox-360-games') : '❌ ' + t('cant-stream-xbox-360-games');
|
||||
$settingNote.textContent = `✅ ${t('xbox-360-games')} ${value === StreamResolution.DIM_1080P_HQ ? '❌' : '✅'} ${t('xbox-apps')}`;
|
||||
setGlobalPref(GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION, value, 'ui');
|
||||
});
|
||||
|
||||
|
@@ -191,7 +191,6 @@ export class SettingsDialog extends NavigationDialog {
|
||||
},
|
||||
GlobalPref.SERVER_BYPASS_RESTRICTION,
|
||||
GlobalPref.UI_CONTROLLER_FRIENDLY,
|
||||
GlobalPref.REMOTE_PLAY_ENABLED,
|
||||
],
|
||||
}, {
|
||||
group: 'server',
|
||||
|
@@ -11,6 +11,7 @@ import { GlobalPref } from "@/enums/pref-keys";
|
||||
import { getGlobalPref } from "@/utils/pref-utils";
|
||||
import { BxLogger } from "@/utils/bx-logger";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
import { BlockFeature } from "@/enums/pref-values";
|
||||
|
||||
export class HeaderSection {
|
||||
private static instance: HeaderSection;
|
||||
@@ -44,7 +45,7 @@ export class HeaderSection {
|
||||
});
|
||||
|
||||
this.$buttonsWrapper = CE('div', false,
|
||||
getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED) ? this.$btnRemotePlay : null,
|
||||
!getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY) ? this.$btnRemotePlay : null,
|
||||
this.$btnSettings,
|
||||
);
|
||||
|
||||
|
1
src/types/global.d.ts
vendored
1
src/types/global.d.ts
vendored
@@ -26,7 +26,6 @@ declare global {
|
||||
touchLayoutManager: any;
|
||||
}>;
|
||||
|
||||
BX_REMOTE_PLAY_CONFIG: BxStates.remotePlay.config;
|
||||
BX_STREAM_SETTINGS: StreamSettingsData;
|
||||
|
||||
BX_FETCH: typeof window['fetch'];
|
||||
|
4
src/types/states.d.ts
vendored
4
src/types/states.d.ts
vendored
@@ -41,11 +41,7 @@ type BxStates = {
|
||||
}>;
|
||||
|
||||
remotePlay: Partial<{
|
||||
isPlaying: boolean;
|
||||
server: string;
|
||||
config: {
|
||||
serverId: string;
|
||||
};
|
||||
titleId?: string;
|
||||
}>;
|
||||
|
||||
|
@@ -10,8 +10,8 @@ export let FeatureGates: { [key: string]: boolean } = {
|
||||
ShowForcedUpdateScreen: false,
|
||||
EnableTakControlResizing: true, // Experimenting
|
||||
EnableLazyLoadedHome: false,
|
||||
EnableRemotePlay: getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED),
|
||||
EnableConsoles: getGlobalPref(GlobalPref.REMOTE_PLAY_ENABLED),
|
||||
EnableRemotePlay: !getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY),
|
||||
EnableConsoles: !getGlobalPref(GlobalPref.BLOCK_FEATURES).includes(BlockFeature.REMOTE_PLAY),
|
||||
};
|
||||
|
||||
// Enable Native Mouse & Keyboard
|
||||
|
@@ -1,8 +1,5 @@
|
||||
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { LoadingScreen } from "@modules/loading-screen";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
@@ -26,10 +23,6 @@ export function onHistoryChanged(e: PopStateEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFullVersion()) {
|
||||
window.setTimeout(RemotePlayManager.detect, 10);
|
||||
}
|
||||
|
||||
// Hide Navigation dialog
|
||||
NavigationDialogManager.getInstance().hide();
|
||||
|
||||
|
@@ -297,7 +297,7 @@ export function interceptHttpRequests() {
|
||||
}
|
||||
|
||||
let requestType: RequestType;
|
||||
if (url.includes('/sessions/home') || url.includes('xhome.') || (STATES.remotePlay.isPlaying && url.endsWith('/inputconfigs'))) {
|
||||
if (url.includes('/sessions/home') || url.includes('xhome.') || (window.location.pathname.includes('/play/consoles/launch/') && url.endsWith('/inputconfigs'))) {
|
||||
requestType = 'xhome';
|
||||
} else {
|
||||
requestType = 'xcloud';
|
||||
|
@@ -11,7 +11,6 @@ import { BaseSettingsStorage } from "./base-settings-storage";
|
||||
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, NativeMkbMode, UiLayout, UiSection, BlockFeature, UiTheme } from "@/enums/pref-values";
|
||||
import { GhPagesUtils } from "../gh-pages";
|
||||
import { BxEventBus } from "../bx-event-bus";
|
||||
import { BxIcon } from "../bx-icon";
|
||||
|
||||
|
||||
function getSupportedCodecProfiles() {
|
||||
@@ -497,6 +496,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage<GlobalPref> {
|
||||
[BlockFeature.BYOG]: t('stream-your-own-game'),
|
||||
[BlockFeature.NOTIFICATIONS_INVITES]: t('notifications') + ': ' + t('invites'),
|
||||
[BlockFeature.NOTIFICATIONS_ACHIEVEMENTS]: t('notifications') + ': ' + t('achievements'),
|
||||
[BlockFeature.REMOTE_PLAY]: t('remote-play'),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -525,13 +525,6 @@ export class GlobalSettingsStorage extends BaseSettingsStorage<GlobalPref> {
|
||||
default: false,
|
||||
},
|
||||
|
||||
[GlobalPref.REMOTE_PLAY_ENABLED]: {
|
||||
requiredVariants: 'full',
|
||||
label: t('enable-remote-play-feature'),
|
||||
labelIcon: BxIcon.REMOTE_PLAY,
|
||||
default: false,
|
||||
},
|
||||
|
||||
[GlobalPref.REMOTE_PLAY_STREAM_RESOLUTION]: {
|
||||
requiredVariants: 'full',
|
||||
default: StreamResolution.DIM_1080P,
|
||||
|
@@ -28,6 +28,8 @@ export const SUPPORTED_LANGUAGES = {
|
||||
};
|
||||
|
||||
const Texts = {
|
||||
"xbox-360-games": "Xbox 360 games",
|
||||
"xbox-apps": "Xbox apps",
|
||||
"achievements": "Achievements",
|
||||
"activate": "Activate",
|
||||
"activated": "Activated",
|
||||
@@ -61,9 +63,7 @@ const Texts = {
|
||||
"browser-unsupported-feature": "Your browser doesn't support this feature",
|
||||
"button-xbox": "Xbox button",
|
||||
"bypass-region-restriction": "Bypass region restriction",
|
||||
"can-stream-xbox-360-games": "Can stream Xbox 360 games",
|
||||
"cancel": "Cancel",
|
||||
"cant-stream-xbox-360-games": "Can't stream Xbox 360 games",
|
||||
"center": "Center",
|
||||
"chat": "Chat",
|
||||
"clarity-boost": "Clarity boost",
|
||||
|
Reference in New Issue
Block a user