mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-03 04:41:42 +02:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
6bd658e8a6 | |||
7e6b89b357 | |||
4271583a5a | |||
1b2cf70248 | |||
87447df7fd | |||
8664c1a60f | |||
602c31dc7f | |||
bbaea5f629 | |||
03efa528c8 | |||
63aaca7d61 | |||
15ae88e9e6 | |||
7578671cc3 | |||
82cfb11a6d | |||
15700e736d | |||
b27cfc8215 | |||
1e644504ec | |||
7206d11825 | |||
fa19a5a68e | |||
3f834f74b6 | |||
749d5d720e | |||
b969d52a3c | |||
e5bd7e64a7 | |||
82ee00b4ae | |||
8e88af5f8c | |||
927eae3f2f | |||
9f440e9cf4 | |||
1acb30e3af | |||
34159fad22 | |||
741538ebcf | |||
6d2e04aff1 | |||
f2bc98229f | |||
49fb8e2818 | |||
d012d96675 | |||
c129feaf2d | |||
4f7b23912d | |||
e4d73f9e36 |
21
build.ts
21
build.ts
@ -20,6 +20,8 @@ enum BuildTarget {
|
|||||||
|
|
||||||
type BuildVariant = 'full' | 'lite';
|
type BuildVariant = 'full' | 'lite';
|
||||||
|
|
||||||
|
const MINIFY_SYNTAX = true;
|
||||||
|
|
||||||
const postProcess = (str: string): string => {
|
const postProcess = (str: string): string => {
|
||||||
// Unescape unicode charaters
|
// Unescape unicode charaters
|
||||||
str = unescape((str.replace(/\\u/g, '%u')));
|
str = unescape((str.replace(/\\u/g, '%u')));
|
||||||
@ -70,9 +72,6 @@ const postProcess = (str: string): string => {
|
|||||||
// Collapse empty brackets
|
// Collapse empty brackets
|
||||||
str = str.replaceAll(/\{[\s\n]+\}/g, '{}');
|
str = str.replaceAll(/\{[\s\n]+\}/g, '{}');
|
||||||
|
|
||||||
// Collapse if/else blocks without curly braces
|
|
||||||
str = str.replaceAll(/((if \(.*?\)|else)\n\s+)/g, '$2 ');
|
|
||||||
|
|
||||||
// Remove blank lines
|
// Remove blank lines
|
||||||
str = str.replaceAll(/\n([\s]*)\n/g, "\n");
|
str = str.replaceAll(/\n([\s]*)\n/g, "\n");
|
||||||
|
|
||||||
@ -87,6 +86,20 @@ const postProcess = (str: string): string => {
|
|||||||
return p1.toUpperCase();
|
return p1.toUpperCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Replace " (e) =>" to " e =>"
|
||||||
|
// str = str.replaceAll(/ \(([^\s,.$()]+)\) =>/g, ' $1 =>');
|
||||||
|
|
||||||
|
// Set indent to 1 space
|
||||||
|
if (MINIFY_SYNTAX) {
|
||||||
|
// Collapse if/else blocks without curly braces
|
||||||
|
str = str.replaceAll(/((if \(.*?\)|else)\n\s+)/g, '$2 ');
|
||||||
|
|
||||||
|
str = str.replaceAll(/\n(\s+)/g, (match, p1) => {
|
||||||
|
const len = p1.length / 2;
|
||||||
|
return '\n' + ' '.repeat(len);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
assert(str.includes('/* ADDITIONAL CODE */'));
|
assert(str.includes('/* ADDITIONAL CODE */'));
|
||||||
assert(str.includes('window.BX_EXPOSED = BxExposed'));
|
assert(str.includes('window.BX_EXPOSED = BxExposed'));
|
||||||
assert(str.includes('window.BxEvent = BxEvent'));
|
assert(str.includes('window.BxEvent = BxEvent'));
|
||||||
@ -119,7 +132,7 @@ const build = async (target: BuildTarget, version: string, variant: BuildVariant
|
|||||||
outdir: outDir,
|
outdir: outDir,
|
||||||
naming: outputScriptName,
|
naming: outputScriptName,
|
||||||
minify: {
|
minify: {
|
||||||
syntax: true,
|
syntax: MINIFY_SYNTAX,
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
'Bun.env.BUILD_TARGET': JSON.stringify(target),
|
'Bun.env.BUILD_TARGET': JSON.stringify(target),
|
||||||
|
1301
dist/better-xcloud.lite.user.js
vendored
1301
dist/better-xcloud.lite.user.js
vendored
File diff suppressed because one or more lines are too long
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 5.8.3
|
// @version 5.8.6
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
2018
dist/better-xcloud.user.js
vendored
2018
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -10,14 +10,14 @@
|
|||||||
"build": "build.ts"
|
"build": "build.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.1.10",
|
"@types/bun": "^1.1.11",
|
||||||
"@types/node": "^22.7.5",
|
"@types/node": "^22.7.6",
|
||||||
"@types/stylus": "^0.48.43",
|
"@types/stylus": "^0.48.43",
|
||||||
"eslint": "^9.12.0",
|
"eslint": "^9.12.0",
|
||||||
"eslint-plugin-compat": "^6.0.1",
|
"eslint-plugin-compat": "^6.0.1",
|
||||||
"stylus": "^0.63.0"
|
"stylus": "^0.63.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,21 +76,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Touch controller buttons */
|
/* Touch controller buttons */
|
||||||
div[data-enabled] {
|
div[data-activated] {
|
||||||
button {
|
button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show enabled button */
|
/* Show default button */
|
||||||
div[data-enabled='true'] {
|
div[data-activated='false'] {
|
||||||
button:first-of-type {
|
button:first-of-type {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show enable button */
|
/* Show activated button */
|
||||||
div[data-enabled='false'] {
|
div[data-activated='true'] {
|
||||||
button:last-of-type {
|
button:last-of-type {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
8
src/assets/svg/eye-slash.svg
Normal file
8
src/assets/svg/eye-slash.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' fill='#fff' stroke='none ' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||||
|
<clipPath id='A'>
|
||||||
|
<path d='M0 0h32v32H0z'/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path='url(#A)'>
|
||||||
|
<path d='M6.123 3.549a1.07 1.07 0 0 0-.798-.359c-.585 0-1.067.482-1.067 1.067 0 .27.102.53.286.727l2.565 2.823C2.267 10.779.184 15.36.092 15.568c-.123.276-.123.591 0 .867.047.105 1.176 2.609 3.687 5.12 3.345 3.344 7.57 5.112 12.221 5.112a16.97 16.97 0 0 0 6.943-1.444l2.933 3.228c.202.228.493.359.798.359.585 0 1.067-.482 1.067-1.067a1.07 1.07 0 0 0-.286-.727L6.123 3.549zm6.31 10.112l5.556 6.114c-.612.322-1.294.49-1.986.49a4.29 4.29 0 0 1-4.267-4.266c0-.831.242-1.643.697-2.338zM16 24.533c-4.104 0-7.689-1.492-10.657-4.433A17.73 17.73 0 0 1 2.267 16c.625-1.172 2.621-4.452 6.313-6.584l2.4 2.633c-.878 1.125-1.356 2.512-1.356 3.939 0 3.511 2.89 6.4 6.4 6.4 1.221 0 2.416-.349 3.444-1.005l1.964 2.16a14.92 14.92 0 0 1-5.432.99zm.8-12.724a1.07 1.07 0 0 1-.867-1.048c0-.585.482-1.067 1.067-1.067a1.12 1.12 0 0 1 .2.019c2.784.54 4.896 2.863 5.169 5.686a1.07 1.07 0 0 1-.962 1.161c-.034.002-.067.002-.1 0a1.07 1.07 0 0 1-1.067-.968 4.29 4.29 0 0 0-3.44-3.783zm15.104 4.626c-.056.125-1.407 3.116-4.448 5.84a1.07 1.07 0 0 1-.724.283c-.585 0-1.067-.482-1.067-1.067a1.07 1.07 0 0 1 .368-.806A17.7 17.7 0 0 0 29.74 16a17.73 17.73 0 0 0-3.083-4.103C23.689 8.959 20.104 7.467 16 7.467a15.82 15.82 0 0 0-2.581.209 1.06 1.06 0 0 1-.186.016 1.07 1.07 0 0 1-1.067-1.066 1.07 1.07 0 0 1 .901-1.054A17.89 17.89 0 0 1 16 5.333c4.651 0 8.876 1.768 12.221 5.114 2.511 2.51 3.64 5.016 3.687 5.121.123.276.123.591 0 .867h-.004z' fill-rule='nonzero'/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
8
src/assets/svg/eye.svg
Normal file
8
src/assets/svg/eye.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' fill='#fff' stroke='none ' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||||
|
<clipPath id='A'>
|
||||||
|
<path d='M0 0h32v32H0z'/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path='url(#A)'>
|
||||||
|
<path d='M31.908 15.568c-.047-.105-1.176-2.611-3.687-5.121C24.876 7.101 20.651 5.333 16 5.333S7.124 7.101 3.779 10.447c-2.511 2.51-3.646 5.02-3.687 5.121-.123.276-.123.591 0 .867.047.105 1.176 2.609 3.687 5.12 3.345 3.344 7.57 5.112 12.221 5.112s8.876-1.768 12.221-5.112c2.511-2.511 3.64-5.015 3.687-5.12.123-.276.123-.591 0-.867zM16 24.533c-4.104 0-7.689-1.492-10.657-4.433-1.218-1.211-2.254-2.592-3.076-4.1.822-1.508 1.858-2.889 3.076-4.1C8.311 8.959 11.896 7.467 16 7.467s7.689 1.492 10.657 4.433c1.221 1.211 2.259 2.592 3.083 4.1-.961 1.795-5.149 8.533-13.74 8.533zM16 9.6c-3.511 0-6.4 2.889-6.4 6.4s2.889 6.4 6.4 6.4 6.4-2.889 6.4-6.4A6.44 6.44 0 0 0 16 9.6zm0 10.667A4.29 4.29 0 0 1 11.733 16 4.29 4.29 0 0 1 16 11.733 4.29 4.29 0 0 1 20.267 16 4.29 4.29 0 0 1 16 20.267z' fill-rule='nonzero'/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -69,7 +69,6 @@ export enum PrefKey {
|
|||||||
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
|
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
|
||||||
UI_HIDE_SECTIONS = 'ui_hide_sections',
|
UI_HIDE_SECTIONS = 'ui_hide_sections',
|
||||||
|
|
||||||
UI_HOME_CONTEXT_MENU_DISABLED = 'ui_home_context_menu_disabled',
|
|
||||||
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui_game_card_show_wait_time',
|
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui_game_card_show_wait_time',
|
||||||
|
|
||||||
VIDEO_PLAYER_TYPE = 'video_player_type',
|
VIDEO_PLAYER_TYPE = 'video_player_type',
|
||||||
|
55
src/index.ts
55
src/index.ts
@ -42,6 +42,7 @@ import { StreamUiHandler } from "./modules/stream/stream-ui";
|
|||||||
import { UserAgent } from "./utils/user-agent";
|
import { UserAgent } from "./utils/user-agent";
|
||||||
import { XboxApi } from "./utils/xbox-api";
|
import { XboxApi } from "./utils/xbox-api";
|
||||||
import { StreamStatsCollector } from "./utils/stream-stats-collector";
|
import { StreamStatsCollector } from "./utils/stream-stats-collector";
|
||||||
|
import { RootDialogObserver } from "./utils/root-dialog-observer";
|
||||||
|
|
||||||
// Handle login page
|
// Handle login page
|
||||||
if (window.location.pathname.includes('/auth/msa')) {
|
if (window.location.pathname.includes('/auth/msa')) {
|
||||||
@ -309,7 +310,8 @@ function unload() {
|
|||||||
window.BX_EXPOSED.stopTakRendering = false;
|
window.BX_EXPOSED.stopTakRendering = false;
|
||||||
|
|
||||||
NavigationDialogManager.getInstance().hide();
|
NavigationDialogManager.getInstance().hide();
|
||||||
StreamStats.getInstance().onStoppedPlaying();
|
StreamStats.getInstance().destroy();
|
||||||
|
StreamBadges.getInstance().destroy();
|
||||||
|
|
||||||
if (isFullVersion()) {
|
if (isFullVersion()) {
|
||||||
MouseCursorHider.stop();
|
MouseCursorHider.stop();
|
||||||
@ -328,55 +330,6 @@ isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function observeRootDialog($root: HTMLElement) {
|
|
||||||
let beingShown = false;
|
|
||||||
|
|
||||||
const observer = new MutationObserver(mutationList => {
|
|
||||||
for (const mutation of mutationList) {
|
|
||||||
if (mutation.type !== 'childList') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BX_FLAGS.Debug && BxLogger.warning('RootDialog', 'added', mutation.addedNodes);
|
|
||||||
if (mutation.addedNodes.length === 1) {
|
|
||||||
const $addedElm = mutation.addedNodes[0];
|
|
||||||
if ($addedElm instanceof HTMLElement && $addedElm.className) {
|
|
||||||
// Make sure it's Guide dialog
|
|
||||||
if ($root.querySelector('div[class*=GuideDialog]')) {
|
|
||||||
GuideMenu.observe($addedElm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
|
||||||
if (shown !== beingShown) {
|
|
||||||
beingShown = shown;
|
|
||||||
BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe($root, {subtree: true, childList: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitForRootDialog() {
|
|
||||||
const observer = new MutationObserver(mutationList => {
|
|
||||||
for (const mutation of mutationList) {
|
|
||||||
if (mutation.type !== 'childList') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $target = mutation.target as HTMLElement;
|
|
||||||
if ($target.id && $target.id === 'gamepass-dialog-root') {
|
|
||||||
observer.disconnect();
|
|
||||||
observeRootDialog($target);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
observer.observe(document.documentElement, {subtree: true, childList: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
if (getPref(PrefKey.GAME_MSFS2020_FORCE_NATIVE_MKB)) {
|
if (getPref(PrefKey.GAME_MSFS2020_FORCE_NATIVE_MKB)) {
|
||||||
BX_FLAGS.ForceNativeMkbTitles.push('9PMQDM08SNK9');
|
BX_FLAGS.ForceNativeMkbTitles.push('9PMQDM08SNK9');
|
||||||
@ -397,7 +350,7 @@ function main() {
|
|||||||
disableAdobeAudienceManager();
|
disableAdobeAudienceManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForRootDialog();
|
RootDialogObserver.waitForRootDialog();
|
||||||
|
|
||||||
// Setup UI
|
// Setup UI
|
||||||
addCss();
|
addCss();
|
||||||
|
@ -38,37 +38,37 @@ const enum ShortcutAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ControllerShortcut {
|
export class ControllerShortcut {
|
||||||
static readonly #STORAGE_KEY = 'better_xcloud_controller_shortcuts';
|
private static readonly STORAGE_KEY = 'better_xcloud_controller_shortcuts';
|
||||||
|
|
||||||
static #buttonsCache: {[key: string]: boolean[]} = {};
|
private static buttonsCache: {[key: string]: boolean[]} = {};
|
||||||
static #buttonsStatus: {[key: string]: boolean[]} = {};
|
private static buttonsStatus: {[key: string]: boolean[]} = {};
|
||||||
|
|
||||||
static #$selectProfile: HTMLSelectElement;
|
private static $selectProfile: HTMLSelectElement;
|
||||||
static #$selectActions: Partial<{[key in GamepadKey]: HTMLSelectElement}> = {};
|
private static $selectActions: Partial<{[key in GamepadKey]: HTMLSelectElement}> = {};
|
||||||
static #$container: HTMLElement;
|
private static $container: HTMLElement;
|
||||||
|
|
||||||
static #ACTIONS: {[key: string]: (ShortcutAction | null)[]} | null = null;
|
private static ACTIONS: {[key: string]: (ShortcutAction | null)[]} | null = null;
|
||||||
|
|
||||||
static reset(index: number) {
|
static reset(index: number) {
|
||||||
ControllerShortcut.#buttonsCache[index] = [];
|
ControllerShortcut.buttonsCache[index] = [];
|
||||||
ControllerShortcut.#buttonsStatus[index] = [];
|
ControllerShortcut.buttonsStatus[index] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static handle(gamepad: Gamepad): boolean {
|
static handle(gamepad: Gamepad): boolean {
|
||||||
if (!ControllerShortcut.#ACTIONS) {
|
if (!ControllerShortcut.ACTIONS) {
|
||||||
ControllerShortcut.#ACTIONS = ControllerShortcut.#getActionsFromStorage();
|
ControllerShortcut.ACTIONS = ControllerShortcut.getActionsFromStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const gamepadIndex = gamepad.index;
|
const gamepadIndex = gamepad.index;
|
||||||
const actions = ControllerShortcut.#ACTIONS![gamepad.id];
|
const actions = ControllerShortcut.ACTIONS![gamepad.id];
|
||||||
if (!actions) {
|
if (!actions) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the buttons status from the previous frame to the cache
|
// Move the buttons status from the previous frame to the cache
|
||||||
ControllerShortcut.#buttonsCache[gamepadIndex] = ControllerShortcut.#buttonsStatus[gamepadIndex].slice(0);
|
ControllerShortcut.buttonsCache[gamepadIndex] = ControllerShortcut.buttonsStatus[gamepadIndex].slice(0);
|
||||||
// Clear the buttons status
|
// Clear the buttons status
|
||||||
ControllerShortcut.#buttonsStatus[gamepadIndex] = [];
|
ControllerShortcut.buttonsStatus[gamepadIndex] = [];
|
||||||
|
|
||||||
const pressed: boolean[] = [];
|
const pressed: boolean[] = [];
|
||||||
let otherButtonPressed = false;
|
let otherButtonPressed = false;
|
||||||
@ -80,17 +80,17 @@ export class ControllerShortcut {
|
|||||||
pressed[index] = true;
|
pressed[index] = true;
|
||||||
|
|
||||||
// If this is newly pressed button -> run action
|
// If this is newly pressed button -> run action
|
||||||
if (actions[index] && !ControllerShortcut.#buttonsCache[gamepadIndex][index]) {
|
if (actions[index] && !ControllerShortcut.buttonsCache[gamepadIndex][index]) {
|
||||||
setTimeout(() => ControllerShortcut.#runAction(actions[index]!), 0);
|
setTimeout(() => ControllerShortcut.runAction(actions[index]!), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ControllerShortcut.#buttonsStatus[gamepadIndex] = pressed;
|
ControllerShortcut.buttonsStatus[gamepadIndex] = pressed;
|
||||||
return otherButtonPressed;
|
return otherButtonPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static #runAction(action: ShortcutAction) {
|
private static runAction(action: ShortcutAction) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW:
|
case ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW:
|
||||||
SettingsNavigationDialog.getInstance().show();
|
SettingsNavigationDialog.getInstance().show();
|
||||||
@ -134,8 +134,8 @@ export class ControllerShortcut {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static #updateAction(profile: string, button: GamepadKey, action: ShortcutAction | null) {
|
private static updateAction(profile: string, button: GamepadKey, action: ShortcutAction | null) {
|
||||||
const actions = ControllerShortcut.#ACTIONS!;
|
const actions = ControllerShortcut.ACTIONS!;
|
||||||
if (!(profile in actions)) {
|
if (!(profile in actions)) {
|
||||||
actions[profile] = [];
|
actions[profile] = [];
|
||||||
}
|
}
|
||||||
@ -147,9 +147,9 @@ export class ControllerShortcut {
|
|||||||
actions[profile][button] = action;
|
actions[profile][button] = action;
|
||||||
|
|
||||||
// Remove empty profiles
|
// Remove empty profiles
|
||||||
for (const key in ControllerShortcut.#ACTIONS) {
|
for (const key in ControllerShortcut.ACTIONS) {
|
||||||
let empty = true;
|
let empty = true;
|
||||||
for (const value of ControllerShortcut.#ACTIONS[key]) {
|
for (const value of ControllerShortcut.ACTIONS[key]) {
|
||||||
if (!!value) {
|
if (!!value) {
|
||||||
empty = false;
|
empty = false;
|
||||||
break;
|
break;
|
||||||
@ -157,19 +157,19 @@ export class ControllerShortcut {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
delete ControllerShortcut.#ACTIONS[key];
|
delete ControllerShortcut.ACTIONS[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
window.localStorage.setItem(ControllerShortcut.#STORAGE_KEY, JSON.stringify(ControllerShortcut.#ACTIONS));
|
window.localStorage.setItem(ControllerShortcut.STORAGE_KEY, JSON.stringify(ControllerShortcut.ACTIONS));
|
||||||
|
|
||||||
console.log(ControllerShortcut.#ACTIONS);
|
console.log(ControllerShortcut.ACTIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static #updateProfileList(e?: GamepadEvent) {
|
private static updateProfileList(e?: GamepadEvent) {
|
||||||
const $select = ControllerShortcut.#$selectProfile;
|
const $select = ControllerShortcut.$selectProfile;
|
||||||
const $container = ControllerShortcut.#$container;
|
const $container = ControllerShortcut.$container;
|
||||||
|
|
||||||
const $fragment = document.createDocumentFragment();
|
const $fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
@ -205,16 +205,16 @@ export class ControllerShortcut {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static #switchProfile(profile: string) {
|
private static switchProfile(profile: string) {
|
||||||
let actions = ControllerShortcut.#ACTIONS![profile];
|
let actions = ControllerShortcut.ACTIONS![profile];
|
||||||
if (!actions) {
|
if (!actions) {
|
||||||
actions = [];
|
actions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset selects' values
|
// Reset selects' values
|
||||||
let button: any;
|
let button: any;
|
||||||
for (button in ControllerShortcut.#$selectActions) {
|
for (button in ControllerShortcut.$selectActions) {
|
||||||
const $select = ControllerShortcut.#$selectActions[button as GamepadKey]!;
|
const $select = ControllerShortcut.$selectActions[button as GamepadKey]!;
|
||||||
$select.value = actions[button] || '';
|
$select.value = actions[button] || '';
|
||||||
|
|
||||||
BxEvent.dispatch($select, 'input', {
|
BxEvent.dispatch($select, 'input', {
|
||||||
@ -224,15 +224,15 @@ export class ControllerShortcut {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static #getActionsFromStorage() {
|
private static getActionsFromStorage() {
|
||||||
return JSON.parse(window.localStorage.getItem(ControllerShortcut.#STORAGE_KEY) || '{}');
|
return JSON.parse(window.localStorage.getItem(ControllerShortcut.STORAGE_KEY) || '{}');
|
||||||
}
|
}
|
||||||
|
|
||||||
static renderSettings() {
|
static renderSettings() {
|
||||||
const PREF_CONTROLLER_FRIENDLY_UI = getPref(PrefKey.UI_CONTROLLER_FRIENDLY);
|
const PREF_CONTROLLER_FRIENDLY_UI = getPref(PrefKey.UI_CONTROLLER_FRIENDLY);
|
||||||
|
|
||||||
// Read actions from localStorage
|
// Read actions from localStorage
|
||||||
ControllerShortcut.#ACTIONS = ControllerShortcut.#getActionsFromStorage();
|
ControllerShortcut.ACTIONS = ControllerShortcut.getActionsFromStorage();
|
||||||
|
|
||||||
const buttons: Map<GamepadKey, PrompFont> = new Map();
|
const buttons: Map<GamepadKey, PrompFont> = new Map();
|
||||||
buttons.set(GamepadKey.Y, PrompFont.Y);
|
buttons.set(GamepadKey.Y, PrompFont.Y);
|
||||||
@ -340,7 +340,7 @@ export class ControllerShortcut {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$selectProfile.addEventListener('input', e => {
|
$selectProfile.addEventListener('input', e => {
|
||||||
ControllerShortcut.#switchProfile($selectProfile.value);
|
ControllerShortcut.switchProfile($selectProfile.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onActionChanged = (e: Event) => {
|
const onActionChanged = (e: Event) => {
|
||||||
@ -361,7 +361,7 @@ export class ControllerShortcut {
|
|||||||
($fakeSelect.firstElementChild as HTMLOptionElement).text = fakeText;
|
($fakeSelect.firstElementChild as HTMLOptionElement).text = fakeText;
|
||||||
}
|
}
|
||||||
|
|
||||||
!(e as any).ignoreOnChange && ControllerShortcut.#updateAction(profile, button as GamepadKey, action);
|
!(e as any).ignoreOnChange && ControllerShortcut.updateAction(profile, button as GamepadKey, action);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -387,7 +387,7 @@ export class ControllerShortcut {
|
|||||||
$select.dataset.button = button.toString();
|
$select.dataset.button = button.toString();
|
||||||
$select.addEventListener('input', onActionChanged);
|
$select.addEventListener('input', onActionChanged);
|
||||||
|
|
||||||
ControllerShortcut.#$selectActions[button] = $select;
|
ControllerShortcut.$selectActions[button] = $select;
|
||||||
|
|
||||||
if (PREF_CONTROLLER_FRIENDLY_UI) {
|
if (PREF_CONTROLLER_FRIENDLY_UI) {
|
||||||
const $bxSelect = BxSelectElement.wrap($select);
|
const $bxSelect = BxSelectElement.wrap($select);
|
||||||
@ -412,14 +412,14 @@ export class ControllerShortcut {
|
|||||||
|
|
||||||
$container.appendChild($remap);
|
$container.appendChild($remap);
|
||||||
|
|
||||||
ControllerShortcut.#$selectProfile = $selectProfile;
|
ControllerShortcut.$selectProfile = $selectProfile;
|
||||||
ControllerShortcut.#$container = $container;
|
ControllerShortcut.$container = $container;
|
||||||
|
|
||||||
// Detect when gamepad connected/disconnect
|
// Detect when gamepad connected/disconnect
|
||||||
window.addEventListener('gamepadconnected', ControllerShortcut.#updateProfileList);
|
window.addEventListener('gamepadconnected', ControllerShortcut.updateProfileList);
|
||||||
window.addEventListener('gamepaddisconnected', ControllerShortcut.#updateProfileList);
|
window.addEventListener('gamepaddisconnected', ControllerShortcut.updateProfileList);
|
||||||
|
|
||||||
ControllerShortcut.#updateProfileList();
|
ControllerShortcut.updateProfileList();
|
||||||
|
|
||||||
return $container;
|
return $container;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
|
|
||||||
export abstract class BaseGameBarAction {
|
export abstract class BaseGameBarAction {
|
||||||
|
abstract $content: HTMLElement;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
reset() {}
|
reset() {}
|
||||||
|
|
||||||
abstract render(): HTMLElement;
|
onClick(e: Event) {
|
||||||
|
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||||
|
};
|
||||||
|
|
||||||
|
render(): HTMLElement {
|
||||||
|
return this.$content;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -8,56 +8,42 @@ import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/shortcut-micro
|
|||||||
export class MicrophoneAction extends BaseGameBarAction {
|
export class MicrophoneAction extends BaseGameBarAction {
|
||||||
$content: HTMLElement;
|
$content: HTMLElement;
|
||||||
|
|
||||||
visible: boolean = false;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const onClick = (e: Event) => {
|
|
||||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
|
||||||
|
|
||||||
const enabled = MicrophoneShortcut.toggle(false);
|
|
||||||
this.$content.setAttribute('data-enabled', enabled.toString());
|
|
||||||
};
|
|
||||||
|
|
||||||
const $btnDefault = createButton({
|
const $btnDefault = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.MICROPHONE,
|
icon: BxIcon.MICROPHONE,
|
||||||
onClick: onClick,
|
onClick: this.onClick.bind(this),
|
||||||
classes: ['bx-activated'],
|
classes: ['bx-activated'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const $btnMuted = createButton({
|
const $btnMuted = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.MICROPHONE_MUTED,
|
icon: BxIcon.MICROPHONE_MUTED,
|
||||||
onClick: onClick,
|
onClick: this.onClick.bind(this),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$content = CE('div', {},
|
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
||||||
$btnDefault,
|
|
||||||
$btnMuted,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
|
|
||||||
window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, e => {
|
window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, e => {
|
||||||
const microphoneState = (e as any).microphoneState;
|
const microphoneState = (e as any).microphoneState;
|
||||||
const enabled = microphoneState === MicrophoneState.ENABLED;
|
const enabled = microphoneState === MicrophoneState.ENABLED;
|
||||||
|
this.$content.dataset.activated = enabled.toString();
|
||||||
this.$content.setAttribute('data-enabled', enabled.toString());
|
|
||||||
|
|
||||||
// Show the button in Game Bar if the mic is enabled
|
// Show the button in Game Bar if the mic is enabled
|
||||||
this.$content.classList.remove('bx-gone');
|
this.$content.classList.remove('bx-gone');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): HTMLElement {
|
onClick(e: Event) {
|
||||||
return this.$content;
|
super.onClick(e);
|
||||||
|
const enabled = MicrophoneShortcut.toggle(false);
|
||||||
|
this.$content.dataset.activated = enabled.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.visible = false;
|
|
||||||
this.$content.classList.add('bx-gone');
|
this.$content.classList.add('bx-gone');
|
||||||
this.$content.setAttribute('data-enabled', 'false');
|
this.$content.dataset.activated = 'false';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
src/modules/game-bar/action-renderer.ts
Normal file
38
src/modules/game-bar/action-renderer.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
|
import { BaseGameBarAction } from "./action-base";
|
||||||
|
import { RendererShortcut } from "../shortcuts/shortcut-renderer";
|
||||||
|
|
||||||
|
|
||||||
|
export class RendererAction extends BaseGameBarAction {
|
||||||
|
$content: HTMLElement;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const $btnDefault = createButton({
|
||||||
|
style: ButtonStyle.GHOST,
|
||||||
|
icon: BxIcon.EYE,
|
||||||
|
onClick: this.onClick.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
const $btnActivated = createButton({
|
||||||
|
style: ButtonStyle.GHOST,
|
||||||
|
icon: BxIcon.EYE_SLASH,
|
||||||
|
onClick: this.onClick.bind(this),
|
||||||
|
classes: ['bx-activated'],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$content = CE('div', {}, $btnDefault, $btnActivated);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(e: Event) {
|
||||||
|
super.onClick(e);
|
||||||
|
const isVisible = RendererShortcut.toggleVisibility();
|
||||||
|
this.$content.dataset.activated = (!isVisible).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.$content.dataset.activated = 'false';
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import { BxEvent } from "@utils/bx-event";
|
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
import { createButton, ButtonStyle } from "@utils/html";
|
import { createButton, ButtonStyle } from "@utils/html";
|
||||||
import { BaseGameBarAction } from "./action-base";
|
import { BaseGameBarAction } from "./action-base";
|
||||||
@ -11,20 +10,16 @@ export class ScreenshotAction extends BaseGameBarAction {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const onClick = (e: Event) => {
|
|
||||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
|
||||||
Screenshot.takeScreenshot();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$content = createButton({
|
this.$content = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.SCREENSHOT,
|
icon: BxIcon.SCREENSHOT,
|
||||||
title: t('take-screenshot'),
|
title: t('take-screenshot'),
|
||||||
onClick: onClick,
|
onClick: this.onClick.bind(this),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): HTMLElement {
|
onClick(e: Event): void {
|
||||||
return this.$content;
|
super.onClick(e);
|
||||||
|
Screenshot.takeScreenshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,44 +11,35 @@ export class SpeakerAction extends BaseGameBarAction {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const onClick = (e: Event) => {
|
|
||||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
|
||||||
SoundShortcut.muteUnmute();
|
|
||||||
};
|
|
||||||
|
|
||||||
const $btnEnable = createButton({
|
const $btnEnable = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.AUDIO,
|
icon: BxIcon.AUDIO,
|
||||||
onClick: onClick,
|
onClick: this.onClick.bind(this),
|
||||||
});
|
});
|
||||||
|
|
||||||
const $btnMuted = createButton({
|
const $btnMuted = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.SPEAKER_MUTED,
|
icon: BxIcon.SPEAKER_MUTED,
|
||||||
onClick: onClick,
|
onClick: this.onClick.bind(this),
|
||||||
classes: ['bx-activated'],
|
classes: ['bx-activated'],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$content = CE('div', {},
|
this.$content = CE('div', {}, $btnEnable, $btnMuted);
|
||||||
$btnEnable,
|
|
||||||
$btnMuted,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
|
|
||||||
window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, e => {
|
window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, e => {
|
||||||
const speakerState = (e as any).speakerState;
|
const speakerState = (e as any).speakerState;
|
||||||
const enabled = speakerState === SpeakerState.ENABLED;
|
const enabled = speakerState === SpeakerState.ENABLED;
|
||||||
|
|
||||||
this.$content.dataset.enabled = enabled.toString();
|
this.$content.dataset.activated = (!enabled).toString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): HTMLElement {
|
onClick(e: Event) {
|
||||||
return this.$content;
|
super.onClick(e);
|
||||||
|
SoundShortcut.muteUnmute();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.$content.dataset.enabled = 'true';
|
this.$content.dataset.activated = 'false';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { BxEvent } from "@utils/bx-event";
|
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
import { TouchController } from "@modules/touch-controller";
|
import { TouchController } from "@modules/touch-controller";
|
||||||
@ -11,44 +10,31 @@ export class TouchControlAction extends BaseGameBarAction {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const onClick = (e: Event) => {
|
|
||||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
|
||||||
|
|
||||||
const $parent = (e as any).target.closest('div[data-enabled]');
|
|
||||||
let enabled = $parent.getAttribute('data-enabled', 'true') === 'true';
|
|
||||||
$parent.setAttribute('data-enabled', (!enabled).toString());
|
|
||||||
|
|
||||||
TouchController.toggleVisibility(enabled);
|
|
||||||
};
|
|
||||||
|
|
||||||
const $btnEnable = createButton({
|
const $btnEnable = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.TOUCH_CONTROL_ENABLE,
|
icon: BxIcon.TOUCH_CONTROL_ENABLE,
|
||||||
title: t('show-touch-controller'),
|
title: t('show-touch-controller'),
|
||||||
onClick: onClick,
|
onClick: this.onClick.bind(this),
|
||||||
});
|
});
|
||||||
|
|
||||||
const $btnDisable = createButton({
|
const $btnDisable = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.TOUCH_CONTROL_DISABLE,
|
icon: BxIcon.TOUCH_CONTROL_DISABLE,
|
||||||
title: t('hide-touch-controller'),
|
title: t('hide-touch-controller'),
|
||||||
onClick: onClick,
|
onClick: this.onClick.bind(this),
|
||||||
classes: ['bx-activated'],
|
classes: ['bx-activated'],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$content = CE('div', {},
|
this.$content = CE('div', {}, $btnEnable, $btnDisable);
|
||||||
$btnEnable,
|
|
||||||
$btnDisable,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): HTMLElement {
|
onClick(e: Event) {
|
||||||
return this.$content;
|
super.onClick(e);
|
||||||
|
const isVisible = TouchController.toggleVisibility();
|
||||||
|
this.$content.dataset.activated = (!isVisible).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.$content.setAttribute('data-enabled', 'true');
|
this.$content.dataset.activated = 'false';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { BxEvent } from "@/utils/bx-event";
|
|
||||||
import { BxIcon } from "@/utils/bx-icon";
|
import { BxIcon } from "@/utils/bx-icon";
|
||||||
import { createButton, ButtonStyle } from "@/utils/html";
|
import { createButton, ButtonStyle } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
|
||||||
import { BaseGameBarAction } from "./action-base";
|
import { BaseGameBarAction } from "./action-base";
|
||||||
import { TrueAchievements } from "@/utils/true-achievements";
|
import { TrueAchievements } from "@/utils/true-achievements";
|
||||||
|
|
||||||
@ -11,20 +9,15 @@ export class TrueAchievementsAction extends BaseGameBarAction {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const onClick = (e: Event) => {
|
|
||||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
|
||||||
TrueAchievements.open(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$content = createButton({
|
this.$content = createButton({
|
||||||
style: ButtonStyle.GHOST,
|
style: ButtonStyle.GHOST,
|
||||||
icon: BxIcon.TRUE_ACHIEVEMENTS,
|
icon: BxIcon.TRUE_ACHIEVEMENTS,
|
||||||
title: t('true-achievements'),
|
onClick: this.onClick.bind(this),
|
||||||
onClick: onClick,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): HTMLElement {
|
onClick(e: Event) {
|
||||||
return this.$content;
|
super.onClick(e);
|
||||||
|
TrueAchievements.open(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { CE, clearFocus, createSvgIcon } from "@utils/html";
|
import { CE, createSvgIcon } from "@utils/html";
|
||||||
import { ScreenshotAction } from "./action-screenshot";
|
import { ScreenshotAction } from "./action-screenshot";
|
||||||
import { TouchControlAction } from "./action-touch-control";
|
import { TouchControlAction } from "./action-touch-control";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
@ -7,34 +7,29 @@ import type { BaseGameBarAction } from "./action-base";
|
|||||||
import { STATES } from "@utils/global";
|
import { STATES } from "@utils/global";
|
||||||
import { MicrophoneAction } from "./action-microphone";
|
import { MicrophoneAction } from "./action-microphone";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref, StreamTouchController } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref, StreamTouchController, type GameBarPosition } from "@/utils/settings-storages/global-settings-storage";
|
||||||
import { TrueAchievementsAction } from "./action-true-achievements";
|
import { TrueAchievementsAction } from "./action-true-achievements";
|
||||||
import { SpeakerAction } from "./action-speaker";
|
import { SpeakerAction } from "./action-speaker";
|
||||||
|
import { RendererAction } from "./action-renderer";
|
||||||
|
|
||||||
|
|
||||||
export class GameBar {
|
export class GameBar {
|
||||||
private static instance: GameBar;
|
private static instance: GameBar;
|
||||||
public static getInstance(): GameBar {
|
public static getInstance = () => GameBar.instance ?? (GameBar.instance = new GameBar());
|
||||||
if (!GameBar.instance) {
|
|
||||||
GameBar.instance = new GameBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
return GameBar.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly VISIBLE_DURATION = 2000;
|
private static readonly VISIBLE_DURATION = 2000;
|
||||||
|
|
||||||
private $gameBar: HTMLElement;
|
private $gameBar: HTMLElement;
|
||||||
private $container: HTMLElement;
|
private $container: HTMLElement;
|
||||||
|
|
||||||
private timeout: number | null = null;
|
private timeoutId: number | null = null;
|
||||||
|
|
||||||
private actions: BaseGameBarAction[] = [];
|
private actions: BaseGameBarAction[] = [];
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
let $container;
|
let $container;
|
||||||
|
|
||||||
const position = getPref(PrefKey.GAME_BAR_POSITION);
|
const position = getPref(PrefKey.GAME_BAR_POSITION) as GameBarPosition;
|
||||||
|
|
||||||
const $gameBar = CE('div', {id: 'bx-game-bar', class: 'bx-gone', 'data-position': position},
|
const $gameBar = CE('div', {id: 'bx-game-bar', class: 'bx-gone', 'data-position': position},
|
||||||
$container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
|
$container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
|
||||||
@ -45,6 +40,7 @@ export class GameBar {
|
|||||||
new ScreenshotAction(),
|
new ScreenshotAction(),
|
||||||
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.OFF) ? [new TouchControlAction()] : []),
|
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.OFF) ? [new TouchControlAction()] : []),
|
||||||
new SpeakerAction(),
|
new SpeakerAction(),
|
||||||
|
new RendererAction(),
|
||||||
new MicrophoneAction(),
|
new MicrophoneAction(),
|
||||||
new TrueAchievementsAction(),
|
new TrueAchievementsAction(),
|
||||||
];
|
];
|
||||||
@ -76,11 +72,7 @@ export class GameBar {
|
|||||||
|
|
||||||
// Add animation when hiding game bar
|
// Add animation when hiding game bar
|
||||||
$container.addEventListener('transitionend', e => {
|
$container.addEventListener('transitionend', e => {
|
||||||
const classList = $container.classList;
|
$container.classList.replace('bx-hide', 'bx-offscreen');
|
||||||
if (classList.contains('bx-hide')) {
|
|
||||||
classList.remove('bx-hide');
|
|
||||||
classList.add('bx-offscreen');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.documentElement.appendChild($gameBar);
|
document.documentElement.appendChild($gameBar);
|
||||||
@ -89,45 +81,38 @@ export class GameBar {
|
|||||||
|
|
||||||
// Enable/disable Game Bar when playing/pausing
|
// Enable/disable Game Bar when playing/pausing
|
||||||
getPref(PrefKey.GAME_BAR_POSITION) !== 'off' && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e: Event) => {
|
getPref(PrefKey.GAME_BAR_POSITION) !== 'off' && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e: Event) => {
|
||||||
if (!STATES.isPlaying) {
|
|
||||||
this.disable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle Game bar
|
// Toggle Game bar
|
||||||
|
if (STATES.isPlaying) {
|
||||||
const mode = (e as any).mode;
|
const mode = (e as any).mode;
|
||||||
mode !== 'none' ? this.disable() : this.enable();
|
mode !== 'none' ? this.disable() : this.enable();
|
||||||
|
}
|
||||||
}).bind(this));
|
}).bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private beginHideTimeout() {
|
private beginHideTimeout() {
|
||||||
this.clearHideTimeout();
|
this.clearHideTimeout();
|
||||||
|
|
||||||
this.timeout = window.setTimeout(() => {
|
this.timeoutId = window.setTimeout(() => {
|
||||||
this.timeout = null;
|
this.timeoutId = null;
|
||||||
this.hideBar();
|
this.hideBar();
|
||||||
}, GameBar.VISIBLE_DURATION);
|
}, GameBar.VISIBLE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearHideTimeout() {
|
private clearHideTimeout() {
|
||||||
this.timeout && clearTimeout(this.timeout);
|
this.timeoutId && clearTimeout(this.timeoutId);
|
||||||
this.timeout = null;
|
this.timeoutId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
this.$gameBar && this.$gameBar.classList.remove('bx-gone');
|
this.$gameBar.classList.remove('bx-gone');
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
this.hideBar();
|
this.hideBar();
|
||||||
this.$gameBar && this.$gameBar.classList.add('bx-gone');
|
this.$gameBar.classList.add('bx-gone');
|
||||||
}
|
}
|
||||||
|
|
||||||
showBar() {
|
showBar() {
|
||||||
if (!this.$container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$container.classList.remove('bx-offscreen', 'bx-hide' , 'bx-gone');
|
this.$container.classList.remove('bx-offscreen', 'bx-hide' , 'bx-gone');
|
||||||
this.$container.classList.add('bx-show');
|
this.$container.classList.add('bx-show');
|
||||||
|
|
||||||
@ -136,22 +121,11 @@ export class GameBar {
|
|||||||
|
|
||||||
hideBar() {
|
hideBar() {
|
||||||
this.clearHideTimeout();
|
this.clearHideTimeout();
|
||||||
|
this.$container.classList.replace('bx-show', 'bx-hide');
|
||||||
// Stop focusing Game Bar
|
|
||||||
clearFocus();
|
|
||||||
|
|
||||||
if (!this.$container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$container.classList.remove('bx-show');
|
|
||||||
this.$container.classList.add('bx-hide');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all states
|
// Reset all states
|
||||||
reset() {
|
reset() {
|
||||||
for (const action of this.actions) {
|
this.actions.forEach(action => action.reset());
|
||||||
action.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,13 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
|||||||
import { compressCss } from "@macros/build" with {type: "macro"};
|
import { compressCss } from "@macros/build" with {type: "macro"};
|
||||||
|
|
||||||
export class LoadingScreen {
|
export class LoadingScreen {
|
||||||
static #$bgStyle: HTMLElement;
|
private static $bgStyle: HTMLElement;
|
||||||
static #$waitTimeBox: HTMLElement;
|
private static $waitTimeBox: HTMLElement;
|
||||||
|
|
||||||
static #waitTimeInterval?: number | null = null;
|
private static waitTimeInterval?: number | null = null;
|
||||||
static #orgWebTitle: string;
|
private static orgWebTitle: string;
|
||||||
|
|
||||||
static #secondsToString(seconds: number) {
|
private static secondsToString(seconds: number) {
|
||||||
const m = Math.floor(seconds / 60);
|
const m = Math.floor(seconds / 60);
|
||||||
const s = Math.floor(seconds % 60);
|
const s = Math.floor(seconds % 60);
|
||||||
|
|
||||||
@ -28,21 +28,21 @@ export class LoadingScreen {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LoadingScreen.#$bgStyle) {
|
if (!LoadingScreen.$bgStyle) {
|
||||||
const $bgStyle = CE('style');
|
const $bgStyle = CE('style');
|
||||||
document.documentElement.appendChild($bgStyle);
|
document.documentElement.appendChild($bgStyle);
|
||||||
LoadingScreen.#$bgStyle = $bgStyle;
|
LoadingScreen.$bgStyle = $bgStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadingScreen.#setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl);
|
LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl);
|
||||||
|
|
||||||
if (getPref(PrefKey.UI_LOADING_SCREEN_ROCKET) === 'hide') {
|
if (getPref(PrefKey.UI_LOADING_SCREEN_ROCKET) === 'hide') {
|
||||||
LoadingScreen.#hideRocket();
|
LoadingScreen.hideRocket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static #hideRocket() {
|
private static hideRocket() {
|
||||||
let $bgStyle = LoadingScreen.#$bgStyle;
|
let $bgStyle = LoadingScreen.$bgStyle;
|
||||||
|
|
||||||
$bgStyle.textContent! += compressCss(`
|
$bgStyle.textContent! += compressCss(`
|
||||||
#game-stream div[class*=RocketAnimation-module__container] > svg {
|
#game-stream div[class*=RocketAnimation-module__container] > svg {
|
||||||
@ -55,9 +55,9 @@ export class LoadingScreen {
|
|||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static #setBackground(imageUrl: string) {
|
private static setBackground(imageUrl: string) {
|
||||||
// Setup style tag
|
// Setup style tag
|
||||||
let $bgStyle = LoadingScreen.#$bgStyle;
|
let $bgStyle = LoadingScreen.$bgStyle;
|
||||||
|
|
||||||
// Limit max width to reduce image size
|
// Limit max width to reduce image size
|
||||||
imageUrl = imageUrl + '?w=1920';
|
imageUrl = imageUrl + '?w=1920';
|
||||||
@ -89,14 +89,14 @@ export class LoadingScreen {
|
|||||||
static setupWaitTime(waitTime: number) {
|
static setupWaitTime(waitTime: number) {
|
||||||
// Hide rocket when queing
|
// Hide rocket when queing
|
||||||
if (getPref(PrefKey.UI_LOADING_SCREEN_ROCKET) === 'hide-queue') {
|
if (getPref(PrefKey.UI_LOADING_SCREEN_ROCKET) === 'hide-queue') {
|
||||||
LoadingScreen.#hideRocket();
|
LoadingScreen.hideRocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
let secondsLeft = waitTime;
|
let secondsLeft = waitTime;
|
||||||
let $countDown;
|
let $countDown;
|
||||||
let $estimated;
|
let $estimated;
|
||||||
|
|
||||||
LoadingScreen.#orgWebTitle = document.title;
|
LoadingScreen.orgWebTitle = document.title;
|
||||||
|
|
||||||
const endDate = new Date();
|
const endDate = new Date();
|
||||||
const timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60;
|
const timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60;
|
||||||
@ -104,9 +104,9 @@ export class LoadingScreen {
|
|||||||
|
|
||||||
let endDateStr = endDate.toISOString().slice(0, 19);
|
let endDateStr = endDate.toISOString().slice(0, 19);
|
||||||
endDateStr = endDateStr.substring(0, 10) + ' ' + endDateStr.substring(11, 19);
|
endDateStr = endDateStr.substring(0, 10) + ' ' + endDateStr.substring(11, 19);
|
||||||
endDateStr += ` (${LoadingScreen.#secondsToString(waitTime)})`;
|
endDateStr += ` (${LoadingScreen.secondsToString(waitTime)})`;
|
||||||
|
|
||||||
let $waitTimeBox = LoadingScreen.#$waitTimeBox;
|
let $waitTimeBox = LoadingScreen.$waitTimeBox;
|
||||||
if (!$waitTimeBox) {
|
if (!$waitTimeBox) {
|
||||||
$waitTimeBox = CE('div', {'class': 'bx-wait-time-box'},
|
$waitTimeBox = CE('div', {'class': 'bx-wait-time-box'},
|
||||||
CE('label', {}, t('server')),
|
CE('label', {}, t('server')),
|
||||||
@ -118,7 +118,7 @@ export class LoadingScreen {
|
|||||||
);
|
);
|
||||||
|
|
||||||
document.documentElement.appendChild($waitTimeBox);
|
document.documentElement.appendChild($waitTimeBox);
|
||||||
LoadingScreen.#$waitTimeBox = $waitTimeBox;
|
LoadingScreen.$waitTimeBox = $waitTimeBox;
|
||||||
} else {
|
} else {
|
||||||
$waitTimeBox.classList.remove('bx-gone');
|
$waitTimeBox.classList.remove('bx-gone');
|
||||||
$estimated = $waitTimeBox.querySelector('.bx-wait-time-estimated')!;
|
$estimated = $waitTimeBox.querySelector('.bx-wait-time-estimated')!;
|
||||||
@ -126,36 +126,36 @@ export class LoadingScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$estimated.textContent = endDateStr;
|
$estimated.textContent = endDateStr;
|
||||||
$countDown.textContent = LoadingScreen.#secondsToString(secondsLeft);
|
$countDown.textContent = LoadingScreen.secondsToString(secondsLeft);
|
||||||
document.title = `[${$countDown.textContent}] ${LoadingScreen.#orgWebTitle}`;
|
document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`;
|
||||||
|
|
||||||
LoadingScreen.#waitTimeInterval = window.setInterval(() => {
|
LoadingScreen.waitTimeInterval = window.setInterval(() => {
|
||||||
secondsLeft--;
|
secondsLeft--;
|
||||||
$countDown.textContent = LoadingScreen.#secondsToString(secondsLeft);
|
$countDown.textContent = LoadingScreen.secondsToString(secondsLeft);
|
||||||
document.title = `[${$countDown.textContent}] ${LoadingScreen.#orgWebTitle}`;
|
document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`;
|
||||||
|
|
||||||
if (secondsLeft <= 0) {
|
if (secondsLeft <= 0) {
|
||||||
LoadingScreen.#waitTimeInterval && clearInterval(LoadingScreen.#waitTimeInterval);
|
LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval);
|
||||||
LoadingScreen.#waitTimeInterval = null;
|
LoadingScreen.waitTimeInterval = null;
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
static hide() {
|
static hide() {
|
||||||
LoadingScreen.#orgWebTitle && (document.title = LoadingScreen.#orgWebTitle);
|
LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle);
|
||||||
LoadingScreen.#$waitTimeBox && LoadingScreen.#$waitTimeBox.classList.add('bx-gone');
|
LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add('bx-gone');
|
||||||
|
|
||||||
if (getPref(PrefKey.UI_LOADING_SCREEN_GAME_ART) && LoadingScreen.#$bgStyle) {
|
if (getPref(PrefKey.UI_LOADING_SCREEN_GAME_ART) && LoadingScreen.$bgStyle) {
|
||||||
const $rocketBg = document.querySelector('#game-stream rect[width="800"]');
|
const $rocketBg = document.querySelector('#game-stream rect[width="800"]');
|
||||||
$rocketBg && $rocketBg.addEventListener('transitionend', e => {
|
$rocketBg && $rocketBg.addEventListener('transitionend', e => {
|
||||||
LoadingScreen.#$bgStyle.textContent += compressCss(`
|
LoadingScreen.$bgStyle.textContent += compressCss(`
|
||||||
#game-stream {
|
#game-stream {
|
||||||
background: #000 !important;
|
background: #000 !important;
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
LoadingScreen.#$bgStyle.textContent += compressCss(`
|
LoadingScreen.$bgStyle.textContent += compressCss(`
|
||||||
#game-stream rect[width="800"] {
|
#game-stream rect[width="800"] {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
@ -166,10 +166,10 @@ export class LoadingScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static reset() {
|
static reset() {
|
||||||
LoadingScreen.#$bgStyle && (LoadingScreen.#$bgStyle.textContent = '');
|
LoadingScreen.$bgStyle && (LoadingScreen.$bgStyle.textContent = '');
|
||||||
|
|
||||||
LoadingScreen.#$waitTimeBox && LoadingScreen.#$waitTimeBox.classList.add('bx-gone');
|
LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add('bx-gone');
|
||||||
LoadingScreen.#waitTimeInterval && clearInterval(LoadingScreen.#waitTimeInterval);
|
LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval);
|
||||||
LoadingScreen.#waitTimeInterval = null;
|
LoadingScreen.waitTimeInterval = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,14 +124,8 @@ This class uses some code from Yuzu emulator to handle mouse's movements
|
|||||||
Source: https://github.com/yuzu-emu/yuzu-mainline/blob/master/src/input_common/drivers/mouse.cpp
|
Source: https://github.com/yuzu-emu/yuzu-mainline/blob/master/src/input_common/drivers/mouse.cpp
|
||||||
*/
|
*/
|
||||||
export class EmulatedMkbHandler extends MkbHandler {
|
export class EmulatedMkbHandler extends MkbHandler {
|
||||||
static #instance: EmulatedMkbHandler;
|
private static instance: EmulatedMkbHandler;
|
||||||
public static getInstance(): EmulatedMkbHandler {
|
public static getInstance = () => EmulatedMkbHandler.instance ?? (EmulatedMkbHandler.instance = new EmulatedMkbHandler());
|
||||||
if (!EmulatedMkbHandler.#instance) {
|
|
||||||
EmulatedMkbHandler.#instance = new EmulatedMkbHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
return EmulatedMkbHandler.#instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ type XcloudInputSink = {
|
|||||||
|
|
||||||
export class NativeMkbHandler extends MkbHandler {
|
export class NativeMkbHandler extends MkbHandler {
|
||||||
private static instance: NativeMkbHandler;
|
private static instance: NativeMkbHandler;
|
||||||
|
public static getInstance = () => NativeMkbHandler.instance ?? (NativeMkbHandler.instance = new NativeMkbHandler());
|
||||||
|
|
||||||
#pointerClient: PointerClient | undefined;
|
#pointerClient: PointerClient | undefined;
|
||||||
#enabled: boolean = false;
|
#enabled: boolean = false;
|
||||||
|
|
||||||
@ -37,14 +39,6 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
#$message?: HTMLElement;
|
#$message?: HTMLElement;
|
||||||
|
|
||||||
public static getInstance(): NativeMkbHandler {
|
|
||||||
if (!NativeMkbHandler.instance) {
|
|
||||||
NativeMkbHandler.instance = new NativeMkbHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeMkbHandler.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
#onKeyboardEvent(e: KeyboardEvent) {
|
#onKeyboardEvent(e: KeyboardEvent) {
|
||||||
if (e.type === 'keyup' && e.code === 'F8') {
|
if (e.type === 'keyup' && e.code === 'F8') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -15,45 +15,39 @@ enum PointerAction {
|
|||||||
|
|
||||||
export class PointerClient {
|
export class PointerClient {
|
||||||
private static instance: PointerClient;
|
private static instance: PointerClient;
|
||||||
public static getInstance(): PointerClient {
|
public static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient());
|
||||||
if (!PointerClient.instance) {
|
|
||||||
PointerClient.instance = new PointerClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
return PointerClient.instance;
|
private socket: WebSocket | undefined | null;
|
||||||
}
|
private mkbHandler: MkbHandler | undefined;
|
||||||
|
|
||||||
#socket: WebSocket | undefined | null;
|
|
||||||
#mkbHandler: MkbHandler | undefined;
|
|
||||||
|
|
||||||
start(port: number, mkbHandler: MkbHandler) {
|
start(port: number, mkbHandler: MkbHandler) {
|
||||||
if (!port) {
|
if (!port) {
|
||||||
throw new Error('PointerServer port is 0');
|
throw new Error('PointerServer port is 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#mkbHandler = mkbHandler;
|
this.mkbHandler = mkbHandler;
|
||||||
|
|
||||||
// Create WebSocket connection.
|
// Create WebSocket connection.
|
||||||
this.#socket = new WebSocket(`ws://localhost:${port}`);
|
this.socket = new WebSocket(`ws://localhost:${port}`);
|
||||||
this.#socket.binaryType = 'arraybuffer';
|
this.socket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
// Connection opened
|
// Connection opened
|
||||||
this.#socket.addEventListener('open', (event) => {
|
this.socket.addEventListener('open', (event) => {
|
||||||
BxLogger.info(LOG_TAG, 'connected')
|
BxLogger.info(LOG_TAG, 'connected')
|
||||||
});
|
});
|
||||||
|
|
||||||
// Error
|
// Error
|
||||||
this.#socket.addEventListener('error', (event) => {
|
this.socket.addEventListener('error', (event) => {
|
||||||
BxLogger.error(LOG_TAG, event);
|
BxLogger.error(LOG_TAG, event);
|
||||||
Toast.show('Cannot setup mouse: ' + event);
|
Toast.show('Cannot setup mouse: ' + event);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#socket.addEventListener('close', (event) => {
|
this.socket.addEventListener('close', (event) => {
|
||||||
this.#socket = null;
|
this.socket = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for messages
|
// Listen for messages
|
||||||
this.#socket.addEventListener('message', (event) => {
|
this.socket.addEventListener('message', (event) => {
|
||||||
const dataView = new DataView(event.data);
|
const dataView = new DataView(event.data);
|
||||||
|
|
||||||
let messageType = dataView.getInt8(0);
|
let messageType = dataView.getInt8(0);
|
||||||
@ -84,7 +78,7 @@ export class PointerClient {
|
|||||||
offset += Int16Array.BYTES_PER_ELEMENT;
|
offset += Int16Array.BYTES_PER_ELEMENT;
|
||||||
const y = dataView.getInt16(offset);
|
const y = dataView.getInt16(offset);
|
||||||
|
|
||||||
this.#mkbHandler?.handleMouseMove({
|
this.mkbHandler?.handleMouseMove({
|
||||||
movementX: x,
|
movementX: x,
|
||||||
movementY: y,
|
movementY: y,
|
||||||
});
|
});
|
||||||
@ -94,7 +88,7 @@ export class PointerClient {
|
|||||||
onPress(messageType: PointerAction, dataView: DataView, offset: number) {
|
onPress(messageType: PointerAction, dataView: DataView, offset: number) {
|
||||||
const button = dataView.getUint8(offset);
|
const button = dataView.getUint8(offset);
|
||||||
|
|
||||||
this.#mkbHandler?.handleMouseClick({
|
this.mkbHandler?.handleMouseClick({
|
||||||
pointerButton: button,
|
pointerButton: button,
|
||||||
pressed: messageType === PointerAction.BUTTON_PRESS,
|
pressed: messageType === PointerAction.BUTTON_PRESS,
|
||||||
});
|
});
|
||||||
@ -108,7 +102,7 @@ export class PointerClient {
|
|||||||
offset += Int16Array.BYTES_PER_ELEMENT;
|
offset += Int16Array.BYTES_PER_ELEMENT;
|
||||||
const hScroll = dataView.getInt16(offset);
|
const hScroll = dataView.getInt16(offset);
|
||||||
|
|
||||||
this.#mkbHandler?.handleMouseWheel({
|
this.mkbHandler?.handleMouseWheel({
|
||||||
vertical: vScroll,
|
vertical: vScroll,
|
||||||
horizontal: hScroll,
|
horizontal: hScroll,
|
||||||
});
|
});
|
||||||
@ -118,13 +112,13 @@ export class PointerClient {
|
|||||||
|
|
||||||
onPointerCaptureChanged(dataView: DataView, offset: number) {
|
onPointerCaptureChanged(dataView: DataView, offset: number) {
|
||||||
const hasCapture = dataView.getInt8(offset) === 1;
|
const hasCapture = dataView.getInt8(offset) === 1;
|
||||||
!hasCapture && this.#mkbHandler?.stop();
|
!hasCapture && this.mkbHandler?.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
try {
|
try {
|
||||||
this.#socket?.close();
|
this.socket?.close();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
this.#socket = null;
|
this.socket = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -632,12 +632,12 @@ true` + text;
|
|||||||
},
|
},
|
||||||
|
|
||||||
skipFeedbackDialog(str: string) {
|
skipFeedbackDialog(str: string) {
|
||||||
let text = '&&this.shouldTransitionToFeedback(';
|
let text = 'shouldTransitionToFeedback(e){';
|
||||||
if (!str.includes(text)) {
|
if (!str.includes(text)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
str = str.replace(text, '&& false ' + text);
|
str = str.replace(text, text + 'return !1;');
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -951,7 +951,20 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
|||||||
|
|
||||||
str = PatcherUtils.replaceWith(str, index, '.All', '.Locked');
|
str = PatcherUtils.replaceWith(str, index, '.All', '.Locked');
|
||||||
return str;
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Disable long touch activating context menu
|
||||||
|
disableTouchContextMenu(str: string) {
|
||||||
|
let index = str.indexOf('"ContextualCardActions-module__container');
|
||||||
|
index >= 0 && (index = str.indexOf('addEventListener("touchstart"', index));
|
||||||
|
index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'return ', index, 50));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
str = PatcherUtils.replaceWith(str, index, 'return', 'return () => {};');
|
||||||
|
return str;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let PATCH_ORDERS: PatchArray = [
|
let PATCH_ORDERS: PatchArray = [
|
||||||
@ -990,6 +1003,10 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
||||||
(getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.NATIVE_MKB) || getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.MOST_POPULAR)) && 'ignoreSiglSections',
|
(getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.NATIVE_MKB) || getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.MOST_POPULAR)) && 'ignoreSiglSections',
|
||||||
|
|
||||||
|
...(STATES.userAgent.capabilities.touch ? [
|
||||||
|
'disableTouchContextMenu',
|
||||||
|
] : []),
|
||||||
|
|
||||||
...(getPref(PrefKey.BLOCK_TRACKING) ? [
|
...(getPref(PrefKey.BLOCK_TRACKING) ? [
|
||||||
'disableAiTrack',
|
'disableAiTrack',
|
||||||
'disableTelemetry',
|
'disableTelemetry',
|
||||||
|
@ -5,9 +5,9 @@ import { PrefKey } from "@/enums/pref-keys";
|
|||||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
|
|
||||||
|
|
||||||
const LOG_TAG = 'WebGL2Player';
|
|
||||||
|
|
||||||
export class WebGL2Player {
|
export class WebGL2Player {
|
||||||
|
private readonly LOG_TAG = 'WebGL2Player';
|
||||||
|
|
||||||
private $video: HTMLVideoElement;
|
private $video: HTMLVideoElement;
|
||||||
private $canvas: HTMLCanvasElement;
|
private $canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
@ -26,13 +26,13 @@ export class WebGL2Player {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private targetFps = 60;
|
private targetFps = 60;
|
||||||
private frameInterval = Math.ceil(1000 / this.targetFps);
|
private frameInterval = 0;
|
||||||
private lastFrameTime = 0;
|
private lastFrameTime = 0;
|
||||||
|
|
||||||
private animFrameId: number | null = null;
|
private animFrameId: number | null = null;
|
||||||
|
|
||||||
constructor($video: HTMLVideoElement) {
|
constructor($video: HTMLVideoElement) {
|
||||||
BxLogger.info(LOG_TAG, 'Initialize');
|
BxLogger.info(this.LOG_TAG, 'Initialize');
|
||||||
this.$video = $video;
|
this.$video = $video;
|
||||||
|
|
||||||
const $canvas = document.createElement('canvas');
|
const $canvas = document.createElement('canvas');
|
||||||
@ -73,7 +73,8 @@ export class WebGL2Player {
|
|||||||
|
|
||||||
setTargetFps(target: number) {
|
setTargetFps(target: number) {
|
||||||
this.targetFps = target;
|
this.targetFps = target;
|
||||||
this.frameInterval = Math.ceil(1000 / target);
|
this.lastFrameTime = 0;
|
||||||
|
this.frameInterval = target ? Math.floor(1000 / target) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCanvas() {
|
getCanvas() {
|
||||||
@ -93,7 +94,13 @@ export class WebGL2Player {
|
|||||||
gl.uniform1f(gl.getUniformLocation(program, 'saturation'), this.options.saturation);
|
gl.uniform1f(gl.getUniformLocation(program, 'saturation'), this.options.saturation);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawFrame() {
|
drawFrame(force=false) {
|
||||||
|
if (!force) {
|
||||||
|
// Don't draw when FPS is 0
|
||||||
|
if (this.targetFps === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Limit FPS
|
// Limit FPS
|
||||||
if (this.targetFps < 60) {
|
if (this.targetFps < 60) {
|
||||||
const currentTime = performance.now();
|
const currentTime = performance.now();
|
||||||
@ -103,11 +110,10 @@ export class WebGL2Player {
|
|||||||
}
|
}
|
||||||
this.lastFrameTime = currentTime;
|
this.lastFrameTime = currentTime;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const gl = this.gl!;
|
const gl = this.gl!;
|
||||||
const $video = this.$video;
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.$video);
|
||||||
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, $video);
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,31 +123,27 @@ export class WebGL2Player {
|
|||||||
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
|
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
|
||||||
const $video = this.$video;
|
const $video = this.$video;
|
||||||
animate = () => {
|
animate = () => {
|
||||||
if (this.stopped) {
|
if (!this.stopped) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drawFrame();
|
this.drawFrame();
|
||||||
this.animFrameId = $video.requestVideoFrameCallback(animate);
|
this.animFrameId = $video.requestVideoFrameCallback(animate);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.animFrameId = $video.requestVideoFrameCallback(animate);
|
this.animFrameId = $video.requestVideoFrameCallback(animate);
|
||||||
} else {
|
} else {
|
||||||
animate = () => {
|
animate = () => {
|
||||||
if (this.stopped) {
|
if (!this.stopped) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drawFrame();
|
this.drawFrame();
|
||||||
this.animFrameId = requestAnimationFrame(animate);
|
this.animFrameId = requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.animFrameId = requestAnimationFrame(animate);
|
this.animFrameId = requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupShaders() {
|
private setupShaders() {
|
||||||
BxLogger.info(LOG_TAG, 'Setting up', getPref(PrefKey.VIDEO_POWER_PREFERENCE));
|
BxLogger.info(this.LOG_TAG, 'Setting up', getPref(PrefKey.VIDEO_POWER_PREFERENCE));
|
||||||
|
|
||||||
const gl = this.$canvas.getContext('webgl2', {
|
const gl = this.$canvas.getContext('webgl2', {
|
||||||
isBx: true,
|
isBx: true,
|
||||||
@ -184,14 +186,7 @@ export class WebGL2Player {
|
|||||||
this.resources.push(buffer);
|
this.resources.push(buffer);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
|
||||||
-1, -1,
|
|
||||||
1, -1,
|
|
||||||
-1, 1,
|
|
||||||
-1, 1,
|
|
||||||
1, -1,
|
|
||||||
1, 1,
|
|
||||||
]), gl.STATIC_DRAW);
|
|
||||||
|
|
||||||
gl.enableVertexAttribArray(0);
|
gl.enableVertexAttribArray(0);
|
||||||
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
||||||
@ -217,14 +212,14 @@ export class WebGL2Player {
|
|||||||
resume() {
|
resume() {
|
||||||
this.stop();
|
this.stop();
|
||||||
this.stopped = false;
|
this.stopped = false;
|
||||||
BxLogger.info(LOG_TAG, 'Resume');
|
BxLogger.info(this.LOG_TAG, 'Resume');
|
||||||
|
|
||||||
this.$canvas.classList.remove('bx-gone');
|
this.$canvas.classList.remove('bx-gone');
|
||||||
this.setupRendering();
|
this.setupRendering();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
BxLogger.info(LOG_TAG, 'Stop');
|
BxLogger.info(this.LOG_TAG, 'Stop');
|
||||||
this.$canvas.classList.add('bx-gone');
|
this.$canvas.classList.add('bx-gone');
|
||||||
|
|
||||||
this.stopped = true;
|
this.stopped = true;
|
||||||
@ -240,16 +235,16 @@ export class WebGL2Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
BxLogger.info(LOG_TAG, 'Destroy');
|
BxLogger.info(this.LOG_TAG, 'Destroy');
|
||||||
this.stop();
|
this.stop();
|
||||||
|
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
if (gl) {
|
if (gl) {
|
||||||
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
|
gl.useProgram(null);
|
||||||
|
|
||||||
for (const resource of this.resources) {
|
for (const resource of this.resources) {
|
||||||
if (resource instanceof WebGLProgram) {
|
if (resource instanceof WebGLProgram) {
|
||||||
gl.useProgram(null);
|
|
||||||
gl.deleteProgram(resource);
|
gl.deleteProgram(resource);
|
||||||
} else if (resource instanceof WebGLShader) {
|
} else if (resource instanceof WebGLShader) {
|
||||||
gl.deleteShader(resource);
|
gl.deleteShader(resource);
|
||||||
|
@ -37,13 +37,7 @@ type RemotePlayConsole = {
|
|||||||
|
|
||||||
export class RemotePlayManager {
|
export class RemotePlayManager {
|
||||||
private static instance: RemotePlayManager;
|
private static instance: RemotePlayManager;
|
||||||
public static getInstance(): RemotePlayManager {
|
public static getInstance = () => RemotePlayManager.instance ?? (RemotePlayManager.instance = new RemotePlayManager());
|
||||||
if (!this.instance) {
|
|
||||||
this.instance = new RemotePlayManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isInitialized = false;
|
private isInitialized = false;
|
||||||
|
|
||||||
|
18
src/modules/shortcuts/shortcut-renderer.ts
Normal file
18
src/modules/shortcuts/shortcut-renderer.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
|
import { limitVideoPlayerFps } from "../stream/stream-settings-utils";
|
||||||
|
|
||||||
|
export class RendererShortcut {
|
||||||
|
static toggleVisibility(): boolean {
|
||||||
|
const $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]');
|
||||||
|
if (!$mediaContainer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaContainer.classList.toggle('bx-gone');
|
||||||
|
const isShowing = !$mediaContainer.classList.contains('bx-gone');
|
||||||
|
// Switch FPS
|
||||||
|
limitVideoPlayerFps(isShowing ? getPref(PrefKey.VIDEO_MAX_FPS) : 0);
|
||||||
|
return isShowing;
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import { StreamPlayerType, StreamVideoProcessing } from "@enums/stream-player";
|
|||||||
import { STATES } from "@/utils/global";
|
import { STATES } from "@/utils/global";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
|
import { BX_FLAGS } from "@/utils/bx-flags";
|
||||||
|
|
||||||
export type StreamPlayerOptions = Partial<{
|
export type StreamPlayerOptions = Partial<{
|
||||||
processing: string,
|
processing: string,
|
||||||
@ -173,6 +174,8 @@ export class StreamPlayer {
|
|||||||
|
|
||||||
setPlayerType(type: StreamPlayerType, refreshPlayer: boolean = false) {
|
setPlayerType(type: StreamPlayerType, refreshPlayer: boolean = false) {
|
||||||
if (this.playerType !== type) {
|
if (this.playerType !== type) {
|
||||||
|
const videoClass = BX_FLAGS.DeviceInfo.deviceType === 'android-tv' ? 'bx-pixel' : 'bx-gone';
|
||||||
|
|
||||||
// Switch from Video -> WebGL2
|
// Switch from Video -> WebGL2
|
||||||
if (type === StreamPlayerType.WEBGL2) {
|
if (type === StreamPlayerType.WEBGL2) {
|
||||||
// Initialize WebGL2 player
|
// Initialize WebGL2 player
|
||||||
@ -184,12 +187,12 @@ export class StreamPlayer {
|
|||||||
|
|
||||||
this.$videoCss!.textContent = '';
|
this.$videoCss!.textContent = '';
|
||||||
|
|
||||||
this.$video.classList.add('bx-pixel');
|
this.$video.classList.add(videoClass);
|
||||||
} else {
|
} else {
|
||||||
// Cleanup WebGL2 Player
|
// Cleanup WebGL2 Player
|
||||||
this.webGL2Player?.stop();
|
this.webGL2Player?.stop();
|
||||||
|
|
||||||
this.$video.classList.remove('bx-pixel');
|
this.$video.classList.remove(videoClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ type StreamBadgeInfo = {
|
|||||||
|
|
||||||
type StreamServerInfo = {
|
type StreamServerInfo = {
|
||||||
server?: {
|
server?: {
|
||||||
ipv6: boolean,
|
|
||||||
region?: string,
|
region?: string,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -50,13 +49,7 @@ enum StreamBadge {
|
|||||||
|
|
||||||
export class StreamBadges {
|
export class StreamBadges {
|
||||||
private static instance: StreamBadges;
|
private static instance: StreamBadges;
|
||||||
public static getInstance(): StreamBadges {
|
public static getInstance = () => StreamBadges.instance ?? (StreamBadges.instance = new StreamBadges());
|
||||||
if (!StreamBadges.instance) {
|
|
||||||
StreamBadges.instance = new StreamBadges();
|
|
||||||
}
|
|
||||||
|
|
||||||
return StreamBadges.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private serverInfo: StreamServerInfo = {};
|
private serverInfo: StreamServerInfo = {};
|
||||||
|
|
||||||
@ -106,7 +99,6 @@ export class StreamBadges {
|
|||||||
setRegion(region: string) {
|
setRegion(region: string) {
|
||||||
this.serverInfo.server = {
|
this.serverInfo.server = {
|
||||||
region: region,
|
region: region,
|
||||||
ipv6: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +184,11 @@ export class StreamBadges {
|
|||||||
this.intervalId = null;
|
this.intervalId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.serverInfo = {};
|
||||||
|
delete this.$container;
|
||||||
|
}
|
||||||
|
|
||||||
async render() {
|
async render() {
|
||||||
if (this.$container) {
|
if (this.$container) {
|
||||||
this.start();
|
this.start();
|
||||||
@ -211,7 +208,7 @@ export class StreamBadges {
|
|||||||
[StreamBadge.BATTERY, batteryLevel],
|
[StreamBadge.BATTERY, batteryLevel],
|
||||||
[StreamBadge.DOWNLOAD, humanFileSize(0)],
|
[StreamBadge.DOWNLOAD, humanFileSize(0)],
|
||||||
[StreamBadge.UPLOAD, humanFileSize(0)],
|
[StreamBadge.UPLOAD, humanFileSize(0)],
|
||||||
this.serverInfo.server ? this.badges.server.$element : [StreamBadge.SERVER, '?'],
|
this.badges.server.$element ?? [StreamBadge.SERVER, '?'],
|
||||||
this.serverInfo.video ? this.badges.video.$element : [StreamBadge.VIDEO, '?'],
|
this.serverInfo.video ? this.badges.video.$element : [StreamBadge.VIDEO, '?'],
|
||||||
this.serverInfo.audio ? this.badges.audio.$element : [StreamBadge.AUDIO, '?'],
|
this.serverInfo.audio ? this.badges.audio.$element : [StreamBadge.AUDIO, '?'],
|
||||||
];
|
];
|
||||||
@ -336,20 +333,18 @@ export class StreamBadges {
|
|||||||
BxLogger.info('candidate', candidateId, allCandidates);
|
BxLogger.info('candidate', candidateId, allCandidates);
|
||||||
|
|
||||||
// Server + Region
|
// Server + Region
|
||||||
const server = this.serverInfo.server;
|
|
||||||
if (server) {
|
|
||||||
server.ipv6 = allCandidates[candidateId].includes(':');
|
|
||||||
|
|
||||||
let text = '';
|
let text = '';
|
||||||
if (server.region) {
|
const isIpv6 = allCandidates[candidateId].includes(':');
|
||||||
|
|
||||||
|
const server = this.serverInfo.server;
|
||||||
|
if (server && server.region) {
|
||||||
text += server.region;
|
text += server.region;
|
||||||
}
|
}
|
||||||
|
|
||||||
text += '@' + (server.ipv6 ? 'IPv6' : 'IPv4');
|
text += '@' + (isIpv6 ? 'IPv6' : 'IPv4');
|
||||||
this.badges.server.$element = this.renderBadge(StreamBadge.SERVER, text);
|
this.badges.server.$element = this.renderBadge(StreamBadge.SERVER, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static setupEvents() {
|
static setupEvents() {
|
||||||
// Since the Lite version doesn't have the "..." button on System menu
|
// Since the Lite version doesn't have the "..." button on System menu
|
||||||
|
@ -45,8 +45,7 @@ export function onChangeVideoPlayerType() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function limitVideoPlayerFps() {
|
export function limitVideoPlayerFps(targetFps: number) {
|
||||||
const targetFps = getPref(PrefKey.VIDEO_MAX_FPS);
|
|
||||||
const streamPlayer = STATES.currentStream.streamPlayer;
|
const streamPlayer = STATES.currentStream.streamPlayer;
|
||||||
streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps);
|
streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps);
|
||||||
}
|
}
|
||||||
@ -58,7 +57,7 @@ export function updateVideoPlayer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
limitVideoPlayerFps();
|
limitVideoPlayerFps(getPref(PrefKey.VIDEO_MAX_FPS));
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
processing: getPref(PrefKey.VIDEO_PROCESSING),
|
processing: getPref(PrefKey.VIDEO_PROCESSING),
|
||||||
|
@ -9,13 +9,7 @@ import { StreamStat, StreamStatsCollector, type StreamStatGrade } from "@/utils/
|
|||||||
|
|
||||||
export class StreamStats {
|
export class StreamStats {
|
||||||
private static instance: StreamStats;
|
private static instance: StreamStats;
|
||||||
public static getInstance(): StreamStats {
|
public static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats());
|
||||||
if (!StreamStats.instance) {
|
|
||||||
StreamStats.instance = new StreamStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
return StreamStats.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private intervalId?: number | null;
|
private intervalId?: number | null;
|
||||||
private readonly REFRESH_INTERVAL = 1 * 1000;
|
private readonly REFRESH_INTERVAL = 1 * 1000;
|
||||||
@ -113,7 +107,7 @@ export class StreamStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onStoppedPlaying() {
|
destroy() {
|
||||||
this.stop();
|
this.stop();
|
||||||
this.quickGlanceStop();
|
this.quickGlanceStop();
|
||||||
this.hideSettingsUi();
|
this.hideSettingsUi();
|
||||||
@ -162,7 +156,7 @@ export class StreamStats {
|
|||||||
|
|
||||||
private async update(forceUpdate=false) {
|
private async update(forceUpdate=false) {
|
||||||
if ((!forceUpdate && this.isHidden()) || !STATES.currentStream.peerConnection) {
|
if ((!forceUpdate && this.isHidden()) || !STATES.currentStream.peerConnection) {
|
||||||
this.onStoppedPlaying();
|
this.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,16 +85,24 @@ export class TouchController {
|
|||||||
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.remove('bx-offscreen');
|
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.remove('bx-offscreen');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
static #hide() {
|
static #hide() {
|
||||||
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.add('bx-offscreen');
|
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.add('bx-offscreen');
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
static toggleVisibility(status: boolean) {
|
static toggleVisibility(): boolean {
|
||||||
if (!TouchController.#dataChannel) {
|
if (!TouchController.#dataChannel) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
status ? TouchController.#hide() : TouchController.#show();
|
const $container = document.querySelector('#BabylonCanvasContainer-main')?.parentElement;
|
||||||
|
if (!$container) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$container.classList.toggle('bx-offscreen');
|
||||||
|
return !$container.classList.contains('bx-offscreen');
|
||||||
}
|
}
|
||||||
|
|
||||||
static reset() {
|
static reset() {
|
||||||
|
@ -88,12 +88,7 @@ export abstract class NavigationDialog {
|
|||||||
|
|
||||||
export class NavigationDialogManager {
|
export class NavigationDialogManager {
|
||||||
private static instance: NavigationDialogManager;
|
private static instance: NavigationDialogManager;
|
||||||
public static getInstance(): NavigationDialogManager {
|
public static getInstance = () => NavigationDialogManager.instance ?? (NavigationDialogManager.instance = new NavigationDialogManager());
|
||||||
if (!NavigationDialogManager.instance) {
|
|
||||||
NavigationDialogManager.instance = new NavigationDialogManager();
|
|
||||||
}
|
|
||||||
return NavigationDialogManager.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly GAMEPAD_POLLING_INTERVAL = 50;
|
private static readonly GAMEPAD_POLLING_INTERVAL = 50;
|
||||||
private static readonly GAMEPAD_KEYS = [
|
private static readonly GAMEPAD_KEYS = [
|
||||||
|
@ -11,12 +11,7 @@ import { BxEvent } from "@/utils/bx-event";
|
|||||||
|
|
||||||
export class RemotePlayNavigationDialog extends NavigationDialog {
|
export class RemotePlayNavigationDialog extends NavigationDialog {
|
||||||
private static instance: RemotePlayNavigationDialog;
|
private static instance: RemotePlayNavigationDialog;
|
||||||
public static getInstance(): RemotePlayNavigationDialog {
|
public static getInstance = () => RemotePlayNavigationDialog.instance ?? (RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog());
|
||||||
if (!RemotePlayNavigationDialog.instance) {
|
|
||||||
RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog();
|
|
||||||
}
|
|
||||||
return RemotePlayNavigationDialog.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly STATE_LABELS: Record<RemotePlayConsoleState, string> = {
|
private readonly STATE_LABELS: Record<RemotePlayConsoleState, string> = {
|
||||||
[RemotePlayConsoleState.ON]: t('powered-on'),
|
[RemotePlayConsoleState.ON]: t('powered-on'),
|
||||||
|
@ -64,12 +64,7 @@ type SettingTab = {
|
|||||||
|
|
||||||
export class SettingsNavigationDialog extends NavigationDialog {
|
export class SettingsNavigationDialog extends NavigationDialog {
|
||||||
private static instance: SettingsNavigationDialog;
|
private static instance: SettingsNavigationDialog;
|
||||||
public static getInstance(): SettingsNavigationDialog {
|
public static getInstance = () => SettingsNavigationDialog.instance ?? (SettingsNavigationDialog.instance = new SettingsNavigationDialog());
|
||||||
if (!SettingsNavigationDialog.instance) {
|
|
||||||
SettingsNavigationDialog.instance = new SettingsNavigationDialog();
|
|
||||||
}
|
|
||||||
return SettingsNavigationDialog.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
$container!: HTMLElement;
|
$container!: HTMLElement;
|
||||||
private $tabs!: HTMLElement;
|
private $tabs!: HTMLElement;
|
||||||
@ -251,7 +246,6 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
|||||||
items: [
|
items: [
|
||||||
PrefKey.UI_LAYOUT,
|
PrefKey.UI_LAYOUT,
|
||||||
PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME,
|
PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME,
|
||||||
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
|
|
||||||
PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS,
|
PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS,
|
||||||
PrefKey.STREAM_SIMPLIFY_MENU,
|
PrefKey.STREAM_SIMPLIFY_MENU,
|
||||||
PrefKey.SKIP_SPLASH_VIDEO,
|
PrefKey.SKIP_SPLASH_VIDEO,
|
||||||
@ -409,7 +403,9 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
|||||||
onChange: onChangeVideoPlayerType,
|
onChange: onChangeVideoPlayerType,
|
||||||
}, {
|
}, {
|
||||||
pref: PrefKey.VIDEO_MAX_FPS,
|
pref: PrefKey.VIDEO_MAX_FPS,
|
||||||
onChange: limitVideoPlayerFps,
|
onChange: e => {
|
||||||
|
limitVideoPlayerFps(parseInt(e.target.value));
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
pref: PrefKey.VIDEO_POWER_PREFERENCE,
|
pref: PrefKey.VIDEO_POWER_PREFERENCE,
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
|
@ -2,12 +2,7 @@ import { CE } from "@/utils/html";
|
|||||||
|
|
||||||
export class FullscreenText {
|
export class FullscreenText {
|
||||||
private static instance: FullscreenText;
|
private static instance: FullscreenText;
|
||||||
public static getInstance(): FullscreenText {
|
public static getInstance = () => FullscreenText.instance ?? (FullscreenText.instance = new FullscreenText());
|
||||||
if (!FullscreenText.instance) {
|
|
||||||
FullscreenText.instance = new FullscreenText();
|
|
||||||
}
|
|
||||||
return FullscreenText.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
$text: HTMLElement;
|
$text: HTMLElement;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { BxIcon } from "@/utils/bx-icon";
|
|||||||
import { AppInterface } from "@/utils/global";
|
import { AppInterface } from "@/utils/global";
|
||||||
import { ButtonStyle, CE, createButton } from "@/utils/html";
|
import { ButtonStyle, CE, createButton } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
|
import { parseDetailsPath } from "@/utils/utils";
|
||||||
|
|
||||||
export class ProductDetailsPage {
|
export class ProductDetailsPage {
|
||||||
private static $btnShortcut = AppInterface && createButton({
|
private static $btnShortcut = AppInterface && createButton({
|
||||||
@ -20,17 +21,9 @@ export class ProductDetailsPage {
|
|||||||
label: t('wallpaper'),
|
label: t('wallpaper'),
|
||||||
style: ButtonStyle.FOCUSABLE,
|
style: ButtonStyle.FOCUSABLE,
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
onClick: async e => {
|
onClick: e => {
|
||||||
try {
|
const details = parseDetailsPath(window.location.pathname);
|
||||||
const matches = /\/games\/(?<titleSlug>[^\/]+)\/(?<productId>\w+)/.exec(window.location.pathname);
|
details && AppInterface.downloadWallpapers(details.titleSlug, details.productId);
|
||||||
if (!matches?.groups) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const titleSlug = matches.groups.titleSlug.replaceAll('\%' + '7C', '-');
|
|
||||||
const productId = matches.groups.productId;
|
|
||||||
AppInterface.downloadWallpapers(titleSlug, productId);
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// Credit: https://phosphoricons.com
|
||||||
import iconBetterXcloud from "@assets/svg/better-xcloud.svg" with { type: "text" };
|
import iconBetterXcloud from "@assets/svg/better-xcloud.svg" with { type: "text" };
|
||||||
import iconTrueAchievements from "@assets/svg/true-achievements.svg" with { type: "text" };
|
import iconTrueAchievements from "@assets/svg/true-achievements.svg" with { type: "text" };
|
||||||
import iconClose from "@assets/svg/close.svg" with { type: "text" };
|
import iconClose from "@assets/svg/close.svg" with { type: "text" };
|
||||||
@ -7,6 +8,8 @@ import iconCopy from "@assets/svg/copy.svg" with { type: "text" };
|
|||||||
import iconCreateShortcut from "@assets/svg/create-shortcut.svg" with { type: "text" };
|
import iconCreateShortcut from "@assets/svg/create-shortcut.svg" with { type: "text" };
|
||||||
import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
|
import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
|
||||||
import iconDisplay from "@assets/svg/display.svg" with { type: "text" };
|
import iconDisplay from "@assets/svg/display.svg" with { type: "text" };
|
||||||
|
import iconEye from "@assets/svg/eye.svg" with { type: "text" };
|
||||||
|
import iconEyeSlash from "@assets/svg/eye-slash.svg" with { type: "text" };
|
||||||
import iconHome from "@assets/svg/home.svg" with { type: "text" };
|
import iconHome from "@assets/svg/home.svg" with { type: "text" };
|
||||||
import iconNativeMkb from "@assets/svg/native-mkb.svg" with { type: "text" };
|
import iconNativeMkb from "@assets/svg/native-mkb.svg" with { type: "text" };
|
||||||
import iconNew from "@assets/svg/new.svg" with { type: "text" };
|
import iconNew from "@assets/svg/new.svg" with { type: "text" };
|
||||||
@ -48,6 +51,8 @@ export const BxIcon = {
|
|||||||
CONTROLLER: iconController,
|
CONTROLLER: iconController,
|
||||||
CREATE_SHORTCUT: iconCreateShortcut,
|
CREATE_SHORTCUT: iconCreateShortcut,
|
||||||
DISPLAY: iconDisplay,
|
DISPLAY: iconDisplay,
|
||||||
|
EYE: iconEye,
|
||||||
|
EYE_SLASH: iconEyeSlash,
|
||||||
HOME: iconHome,
|
HOME: iconHome,
|
||||||
NATIVE_MKB: iconNativeMkb,
|
NATIVE_MKB: iconNativeMkb,
|
||||||
NEW: iconNew,
|
NEW: iconNew,
|
||||||
|
@ -5,22 +5,12 @@ const enum TextColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BxLogger {
|
export class BxLogger {
|
||||||
static #PREFIX = '[BxC]';
|
static info = (tag: string, ...args: any[]) => BxLogger.log(TextColor.INFO, tag, ...args);
|
||||||
|
static warning = (tag: string, ...args: any[]) => BxLogger.log(TextColor.WARNING, tag, ...args);
|
||||||
|
static error = (tag: string, ...args: any[]) => BxLogger.log(TextColor.ERROR, tag, ...args);
|
||||||
|
|
||||||
static info(tag: string, ...args: any[]) {
|
private static log(color: string, tag: string, ...args: any) {
|
||||||
BxLogger.#log(TextColor.INFO, tag, ...args);
|
console.log(`%c[BxC]`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||||
}
|
|
||||||
|
|
||||||
static warning(tag: string, ...args: any[]) {
|
|
||||||
BxLogger.#log(TextColor.WARNING, tag, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
static error(tag: string, ...args: any[]) {
|
|
||||||
BxLogger.#log(TextColor.ERROR, tag, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
static #log(color: string, tag: string, ...args: any) {
|
|
||||||
console.log(`%c${BxLogger.#PREFIX}`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,6 @@ export let FeatureGates: {[key: string]: boolean} = {
|
|||||||
'ShowForcedUpdateScreen': false,
|
'ShowForcedUpdateScreen': false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disable context menu in Home page
|
|
||||||
if (getPref(PrefKey.UI_HOME_CONTEXT_MENU_DISABLED)) {
|
|
||||||
FeatureGates['EnableHomeContextMenu'] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable chat feature
|
// Disable chat feature
|
||||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
||||||
FeatureGates['EnableGuideChatTab'] = false;
|
FeatureGates['EnableGuideChatTab'] = false;
|
||||||
|
@ -101,16 +101,14 @@ function createElement<T=HTMLElement>(elmName: string, props: CreateElementOptio
|
|||||||
|
|
||||||
export const CE = createElement;
|
export const CE = createElement;
|
||||||
|
|
||||||
// Credit: https://phosphoricons.com
|
const domParser = new DOMParser();
|
||||||
const svgParser = (svg: string) => new DOMParser().parseFromString(svg, 'image/svg+xml').documentElement;
|
export function createSvgIcon(icon: typeof BxIcon) {
|
||||||
|
return domParser.parseFromString(icon.toString(), 'image/svg+xml').documentElement;
|
||||||
export const createSvgIcon = (icon: typeof BxIcon) => {
|
|
||||||
return svgParser(icon.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonStyleIndices = Object.keys(ButtonStyleClass).map(i => parseInt(i));
|
const ButtonStyleIndices = Object.keys(ButtonStyleClass).map(i => parseInt(i));
|
||||||
|
|
||||||
export const createButton = <T=HTMLButtonElement>(options: BxButton): T => {
|
export function createButton<T=HTMLButtonElement>(options: BxButton): T {
|
||||||
let $btn;
|
let $btn;
|
||||||
if (options.url) {
|
if (options.url) {
|
||||||
$btn = CE('a', {'class': 'bx-button'}) as HTMLAnchorElement;
|
$btn = CE('a', {'class': 'bx-button'}) as HTMLAnchorElement;
|
||||||
|
@ -3,8 +3,6 @@ import { BxLogger } from "./bx-logger";
|
|||||||
import { TouchController } from "@modules/touch-controller";
|
import { TouchController } from "@modules/touch-controller";
|
||||||
import { GamePassCloudGallery } from "../enums/game-pass-gallery";
|
import { GamePassCloudGallery } from "../enums/game-pass-gallery";
|
||||||
import { BX_FLAGS } from "./bx-flags";
|
import { BX_FLAGS } from "./bx-flags";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
|
||||||
|
|
||||||
const LOG_TAG = 'PreloadState';
|
const LOG_TAG = 'PreloadState';
|
||||||
|
|
||||||
@ -50,14 +48,6 @@ export function overridePreloadState() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getPref(PrefKey.UI_HOME_CONTEXT_MENU_DISABLED)) {
|
|
||||||
try {
|
|
||||||
state.experiments.experimentationInfo.data.treatments.EnableHomeContextMenu = false;
|
|
||||||
} catch (e) {
|
|
||||||
BxLogger.error(LOG_TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
_state = state;
|
_state = state;
|
||||||
STATES.appContext = deepClone(state.appContext);
|
STATES.appContext = deepClone(state.appContext);
|
||||||
|
114
src/utils/root-dialog-observer.ts
Normal file
114
src/utils/root-dialog-observer.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { GuideMenu } from "@/modules/ui/guide-menu";
|
||||||
|
import { BxEvent } from "./bx-event";
|
||||||
|
import { BX_FLAGS } from "./bx-flags";
|
||||||
|
import { BxLogger } from "./bx-logger";
|
||||||
|
import { BxIcon } from "./bx-icon";
|
||||||
|
import { AppInterface } from "./global";
|
||||||
|
import { createButton, ButtonStyle } from "./html";
|
||||||
|
import { t } from "./translation";
|
||||||
|
import { parseDetailsPath } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
|
export class RootDialogObserver {
|
||||||
|
private static $btnShortcut = AppInterface && createButton({
|
||||||
|
icon: BxIcon.CREATE_SHORTCUT,
|
||||||
|
label: t('create-shortcut'),
|
||||||
|
style: ButtonStyle.FOCUSABLE | ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.NORMAL_CASE | ButtonStyle.NORMAL_LINK,
|
||||||
|
tabIndex: 0,
|
||||||
|
onClick: e => {
|
||||||
|
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
||||||
|
|
||||||
|
const $btn = (e.target as HTMLElement).closest('button');
|
||||||
|
AppInterface.createShortcut($btn?.dataset.path);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
private static $btnWallpaper = AppInterface && createButton({
|
||||||
|
icon: BxIcon.DOWNLOAD,
|
||||||
|
label: t('wallpaper'),
|
||||||
|
style: ButtonStyle.FOCUSABLE | ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.NORMAL_CASE | ButtonStyle.NORMAL_LINK,
|
||||||
|
tabIndex: 0,
|
||||||
|
onClick: e => {
|
||||||
|
window.BX_EXPOSED.dialogRoutes?.closeAll();
|
||||||
|
|
||||||
|
const $btn = (e.target as HTMLElement).closest('button');
|
||||||
|
const details = parseDetailsPath($btn!.dataset.path!);
|
||||||
|
details && AppInterface.downloadWallpapers(details.titleSlug, details.productId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
private static handleGameCardMenu($root: HTMLElement) {
|
||||||
|
const $detail = $root.querySelector('a[href^="/play/"]') as HTMLAnchorElement;
|
||||||
|
if (!$detail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = $detail.getAttribute('href')!;
|
||||||
|
RootDialogObserver.$btnShortcut.dataset.path = path;
|
||||||
|
RootDialogObserver.$btnWallpaper.dataset.path = path;
|
||||||
|
|
||||||
|
$root.append(RootDialogObserver.$btnShortcut, RootDialogObserver.$btnWallpaper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static handleAddedElement($root: HTMLElement, $addedElm: HTMLElement): boolean {
|
||||||
|
if (AppInterface && $addedElm.className.startsWith('SlideSheet-module__container')) {
|
||||||
|
// Game card's context menu
|
||||||
|
const $gameCardMenu = $addedElm.querySelector<HTMLElement>('div[class^=MruContextMenu],div[class^=GameCardContextMenu]');
|
||||||
|
if ($gameCardMenu) {
|
||||||
|
RootDialogObserver.handleGameCardMenu($gameCardMenu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if ($root.querySelector('div[class*=GuideDialog]')) {
|
||||||
|
// Guide menu
|
||||||
|
GuideMenu.observe($addedElm);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static observe($root: HTMLElement) {
|
||||||
|
let beingShown = false;
|
||||||
|
|
||||||
|
const observer = new MutationObserver(mutationList => {
|
||||||
|
for (const mutation of mutationList) {
|
||||||
|
if (mutation.type !== 'childList') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BX_FLAGS.Debug && BxLogger.warning('RootDialog', 'added', mutation.addedNodes);
|
||||||
|
if (mutation.addedNodes.length === 1) {
|
||||||
|
const $addedElm = mutation.addedNodes[0];
|
||||||
|
if ($addedElm instanceof HTMLElement && $addedElm.className) {
|
||||||
|
RootDialogObserver.handleAddedElement($root, $addedElm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
||||||
|
if (shown !== beingShown) {
|
||||||
|
beingShown = shown;
|
||||||
|
BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe($root, {subtree: true, childList: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static waitForRootDialog() {
|
||||||
|
const observer = new MutationObserver(mutationList => {
|
||||||
|
for (const mutation of mutationList) {
|
||||||
|
if (mutation.type !== 'childList') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $target = mutation.target as HTMLElement;
|
||||||
|
if ($target.id && $target.id === 'gamepass-dialog-root') {
|
||||||
|
observer.disconnect();
|
||||||
|
RootDialogObserver.observe($target);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
observer.observe(document.documentElement, {subtree: true, childList: true});
|
||||||
|
}
|
||||||
|
}
|
@ -64,7 +64,7 @@ export class Screenshot {
|
|||||||
const canvasContext = Screenshot.#canvasContext;
|
const canvasContext = Screenshot.#canvasContext;
|
||||||
|
|
||||||
if ($player instanceof HTMLCanvasElement) {
|
if ($player instanceof HTMLCanvasElement) {
|
||||||
streamPlayer.getWebGL2Player().drawFrame();
|
streamPlayer.getWebGL2Player().drawFrame(true);
|
||||||
}
|
}
|
||||||
canvasContext.drawImage($player, 0, 0, $canvas.width, $canvas.height);
|
canvasContext.drawImage($player, 0, 0, $canvas.width, $canvas.height);
|
||||||
|
|
||||||
|
@ -39,6 +39,10 @@ export const enum ControllerDeviceVibration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type GameBarPosition = 'bottom-left' | 'bottom-right' | 'off';
|
||||||
|
export type GameBarPositionOptions = Record<GameBarPosition, string>;
|
||||||
|
|
||||||
|
|
||||||
function getSupportedCodecProfiles() {
|
function getSupportedCodecProfiles() {
|
||||||
const options: PartialRecord<CodecProfile, string> = {
|
const options: PartialRecord<CodecProfile, string> = {
|
||||||
default: t('default'),
|
default: t('default'),
|
||||||
@ -323,12 +327,12 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
[PrefKey.GAME_BAR_POSITION]: {
|
[PrefKey.GAME_BAR_POSITION]: {
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
label: t('position'),
|
label: t('position'),
|
||||||
default: 'bottom-left',
|
default: 'bottom-left' satisfies GameBarPosition,
|
||||||
options: {
|
options: {
|
||||||
'bottom-left': t('bottom-left'),
|
'bottom-left': t('bottom-left'),
|
||||||
'bottom-right': t('bottom-right'),
|
'bottom-right': t('bottom-right'),
|
||||||
'off': t('off'),
|
'off': t('off'),
|
||||||
},
|
} satisfies GameBarPositionOptions,
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.LOCAL_CO_OP_ENABLED]: {
|
[PrefKey.LOCAL_CO_OP_ENABLED]: {
|
||||||
@ -529,12 +533,6 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.UI_HOME_CONTEXT_MENU_DISABLED]: {
|
|
||||||
requiredVariants: 'full',
|
|
||||||
label: t('disable-home-context-menu'),
|
|
||||||
default: STATES.browser.capabilities.touch,
|
|
||||||
},
|
|
||||||
|
|
||||||
[PrefKey.UI_HIDE_SECTIONS]: {
|
[PrefKey.UI_HIDE_SECTIONS]: {
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
label: t('hide-sections'),
|
label: t('hide-sections'),
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { BxEvent } from "./bx-event";
|
import { BxEvent } from "./bx-event";
|
||||||
import { STATES } from "./global";
|
import { STATES } from "./global";
|
||||||
import { humanFileSize, secondsToHm } from "./html";
|
import { humanFileSize, secondsToHm } from "./html";
|
||||||
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
|
|
||||||
export enum StreamStat {
|
export enum StreamStat {
|
||||||
PING = 'ping',
|
PING = 'ping',
|
||||||
@ -92,13 +94,7 @@ type CurrentStats = {
|
|||||||
|
|
||||||
export class StreamStatsCollector {
|
export class StreamStatsCollector {
|
||||||
private static instance: StreamStatsCollector;
|
private static instance: StreamStatsCollector;
|
||||||
public static getInstance(): StreamStatsCollector {
|
public static getInstance = () => StreamStatsCollector.instance ?? (StreamStatsCollector.instance = new StreamStatsCollector());
|
||||||
if (!StreamStatsCollector.instance) {
|
|
||||||
StreamStatsCollector.instance = new StreamStatsCollector();
|
|
||||||
}
|
|
||||||
|
|
||||||
return StreamStatsCollector.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect in background - 60 seconds
|
// Collect in background - 60 seconds
|
||||||
static readonly INTERVAL_BACKGROUND = 60 * 1000;
|
static readonly INTERVAL_BACKGROUND = 60 * 1000;
|
||||||
@ -127,7 +123,8 @@ export class StreamStatsCollector {
|
|||||||
[StreamStat.FPS]: {
|
[StreamStat.FPS]: {
|
||||||
current: 0,
|
current: 0,
|
||||||
toString() {
|
toString() {
|
||||||
return this.current.toString();
|
const maxFps = getPref(PrefKey.VIDEO_MAX_FPS);
|
||||||
|
return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ type ToastOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Toast {
|
export class Toast {
|
||||||
static #$wrapper: HTMLElement;
|
private static $wrapper: HTMLElement;
|
||||||
static #$msg: HTMLElement;
|
private static $msg: HTMLElement;
|
||||||
static #$status: HTMLElement;
|
private static $status: HTMLElement;
|
||||||
static #stack: Array<[string, string, ToastOptions]> = [];
|
private static stack: Array<[string, string, ToastOptions]> = [];
|
||||||
static #isShowing = false;
|
private static isShowing = false;
|
||||||
|
|
||||||
static #timeout?: number | null;
|
private static timeout?: number | null;
|
||||||
static #DURATION = 3000;
|
private static DURATION = 3000;
|
||||||
|
|
||||||
static show(msg: string, status?: string, options: Partial<ToastOptions> = {}) {
|
static show(msg: string, status?: string, options: Partial<ToastOptions> = {}) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
@ -21,69 +21,70 @@ export class Toast {
|
|||||||
const args = Array.from(arguments) as [string, string, ToastOptions];
|
const args = Array.from(arguments) as [string, string, ToastOptions];
|
||||||
if (options.instant) {
|
if (options.instant) {
|
||||||
// Clear stack
|
// Clear stack
|
||||||
Toast.#stack = [args];
|
Toast.stack = [args];
|
||||||
Toast.#showNext();
|
Toast.showNext();
|
||||||
} else {
|
} else {
|
||||||
Toast.#stack.push(args);
|
Toast.stack.push(args);
|
||||||
!Toast.#isShowing && Toast.#showNext();
|
!Toast.isShowing && Toast.showNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static #showNext() {
|
private static showNext() {
|
||||||
if (!Toast.#stack.length) {
|
if (!Toast.stack.length) {
|
||||||
Toast.#isShowing = false;
|
Toast.isShowing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast.#isShowing = true;
|
Toast.isShowing = true;
|
||||||
|
|
||||||
Toast.#timeout && clearTimeout(Toast.#timeout);
|
Toast.timeout && clearTimeout(Toast.timeout);
|
||||||
Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
|
Toast.timeout = window.setTimeout(Toast.hide, Toast.DURATION);
|
||||||
|
|
||||||
// Get values from item
|
// Get values from item
|
||||||
const [msg, status, options] = Toast.#stack.shift()!;
|
const [msg, status, options] = Toast.stack.shift()!;
|
||||||
|
|
||||||
if (options && options.html) {
|
if (options && options.html) {
|
||||||
Toast.#$msg.innerHTML = msg;
|
Toast.$msg.innerHTML = msg;
|
||||||
} else {
|
} else {
|
||||||
Toast.#$msg.textContent = msg;
|
Toast.$msg.textContent = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
Toast.#$status.classList.remove('bx-gone');
|
Toast.$status.classList.remove('bx-gone');
|
||||||
Toast.#$status.textContent = status;
|
Toast.$status.textContent = status;
|
||||||
} else {
|
} else {
|
||||||
Toast.#$status.classList.add('bx-gone');
|
Toast.$status.classList.add('bx-gone');
|
||||||
}
|
}
|
||||||
|
|
||||||
const classList = Toast.#$wrapper.classList;
|
const classList = Toast.$wrapper.classList;
|
||||||
classList.remove('bx-offscreen', 'bx-hide');
|
classList.remove('bx-offscreen', 'bx-hide');
|
||||||
classList.add('bx-show');
|
classList.add('bx-show');
|
||||||
}
|
}
|
||||||
|
|
||||||
static #hide() {
|
private static hide() {
|
||||||
Toast.#timeout = null;
|
Toast.timeout = null;
|
||||||
|
|
||||||
const classList = Toast.#$wrapper.classList;
|
const classList = Toast.$wrapper.classList;
|
||||||
classList.remove('bx-show');
|
classList.remove('bx-show');
|
||||||
classList.add('bx-hide');
|
classList.add('bx-hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
static setup() {
|
static setup() {
|
||||||
Toast.#$wrapper = CE('div', {'class': 'bx-toast bx-offscreen'},
|
Toast.$wrapper = CE('div', {'class': 'bx-toast bx-offscreen'},
|
||||||
Toast.#$msg = CE('span', {'class': 'bx-toast-msg'}),
|
Toast.$msg = CE('span', {'class': 'bx-toast-msg'}),
|
||||||
Toast.#$status = CE('span', {'class': 'bx-toast-status'}));
|
Toast.$status = CE('span', {'class': 'bx-toast-status'}),
|
||||||
|
);
|
||||||
|
|
||||||
Toast.#$wrapper.addEventListener('transitionend', e => {
|
Toast.$wrapper.addEventListener('transitionend', e => {
|
||||||
const classList = Toast.#$wrapper.classList;
|
const classList = Toast.$wrapper.classList;
|
||||||
if (classList.contains('bx-hide')) {
|
if (classList.contains('bx-hide')) {
|
||||||
classList.remove('bx-offscreen', 'bx-hide');
|
classList.remove('bx-offscreen', 'bx-hide');
|
||||||
classList.add('bx-offscreen');
|
classList.add('bx-offscreen');
|
||||||
|
|
||||||
Toast.#showNext();
|
Toast.showNext();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.documentElement.appendChild(Toast.#$wrapper);
|
document.documentElement.appendChild(Toast.$wrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export class UserAgent {
|
|||||||
static #USER_AGENTS: PartialRecord<UserAgentProfile, string> = {
|
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.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.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.SMART_TV_GENERIC]: `${window.navigator.userAgent} SmartTV`,
|
[UserAgentProfile.SMART_TV_GENERIC]: `${window.navigator.userAgent} Smart-TV`,
|
||||||
[UserAgentProfile.SMART_TV_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.SMART_TV_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',
|
[UserAgentProfile.VR_OCULUS]: window.navigator.userAgent + ' OculusBrowser VR',
|
||||||
}
|
}
|
||||||
|
@ -120,3 +120,15 @@ export function productTitleToSlug(title: string): string {
|
|||||||
.replace(/ /g, '-')
|
.replace(/ /g, '-')
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseDetailsPath(path: string) {
|
||||||
|
const matches = /\/games\/(?<titleSlug>[^\/]+)\/(?<productId>\w+)/.exec(path);
|
||||||
|
if (!matches?.groups) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleSlug = matches.groups.titleSlug.replaceAll('\%' + '7C', '-');
|
||||||
|
const productId = matches.groups.productId;
|
||||||
|
|
||||||
|
return {titleSlug, productId};
|
||||||
|
}
|
||||||
|
@ -3,21 +3,14 @@ import { STATES } from "./global";
|
|||||||
|
|
||||||
export class XcloudApi {
|
export class XcloudApi {
|
||||||
private static instance: XcloudApi;
|
private static instance: XcloudApi;
|
||||||
|
public static getInstance = () => XcloudApi.instance ?? (XcloudApi.instance = new XcloudApi());
|
||||||
|
|
||||||
public static getInstance(): XcloudApi {
|
private CACHE_TITLES: {[key: string]: XcloudTitleInfo} = {};
|
||||||
if (!XcloudApi.instance) {
|
private CACHE_WAIT_TIME: {[key: string]: XcloudWaitTimeInfo} = {};
|
||||||
XcloudApi.instance = new XcloudApi();
|
|
||||||
}
|
|
||||||
|
|
||||||
return XcloudApi.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
#CACHE_TITLES: {[key: string]: XcloudTitleInfo} = {};
|
|
||||||
#CACHE_WAIT_TIME: {[key: string]: XcloudWaitTimeInfo} = {};
|
|
||||||
|
|
||||||
async getTitleInfo(id: string): Promise<XcloudTitleInfo | null> {
|
async getTitleInfo(id: string): Promise<XcloudTitleInfo | null> {
|
||||||
if (id in this.#CACHE_TITLES) {
|
if (id in this.CACHE_TITLES) {
|
||||||
return this.#CACHE_TITLES[id];
|
return this.CACHE_TITLES[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUri = STATES.selectedRegion.baseUri;
|
const baseUri = STATES.selectedRegion.baseUri;
|
||||||
@ -45,13 +38,13 @@ export class XcloudApi {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
json = {}
|
json = {}
|
||||||
}
|
}
|
||||||
this.#CACHE_TITLES[id] = json;
|
this.CACHE_TITLES[id] = json;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWaitTime(id: string): Promise<XcloudWaitTimeInfo | null> {
|
async getWaitTime(id: string): Promise<XcloudWaitTimeInfo | null> {
|
||||||
if (id in this.#CACHE_WAIT_TIME) {
|
if (id in this.CACHE_WAIT_TIME) {
|
||||||
return this.#CACHE_WAIT_TIME[id];
|
return this.CACHE_WAIT_TIME[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUri = STATES.selectedRegion.baseUri;
|
const baseUri = STATES.selectedRegion.baseUri;
|
||||||
@ -73,7 +66,7 @@ export class XcloudApi {
|
|||||||
json = {};
|
json = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#CACHE_WAIT_TIME[id] = json;
|
this.CACHE_WAIT_TIME[id] = json;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,25 @@ import { BypassServerIps } from "@/enums/bypass-servers";
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||||
|
|
||||||
export
|
export class XcloudInterceptor {
|
||||||
class XcloudInterceptor {
|
private static readonly SERVER_EMOJIS = {
|
||||||
static async #handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
AustraliaEast: '🇦🇺',
|
||||||
|
AustraliaSouthEast: '🇦🇺',
|
||||||
|
BrazilSouth: '🇧🇷',
|
||||||
|
EastUS: '🇺🇸',
|
||||||
|
EastUS2: '🇺🇸',
|
||||||
|
JapanEast: '🇯🇵',
|
||||||
|
KoreaCentral: '🇰🇷',
|
||||||
|
MexicoCentral: '🇲🇽',
|
||||||
|
NorthCentralUs: '🇺🇸',
|
||||||
|
SouthCentralUS: '🇺🇸',
|
||||||
|
UKSouth: '🇬🇧',
|
||||||
|
WestEurope: '🇪🇺',
|
||||||
|
WestUS: '🇺🇸',
|
||||||
|
WestUS2: '🇺🇸',
|
||||||
|
};
|
||||||
|
|
||||||
|
private static async handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
||||||
const bypassServer = getPref(PrefKey.SERVER_BYPASS_RESTRICTION);
|
const bypassServer = getPref(PrefKey.SERVER_BYPASS_RESTRICTION);
|
||||||
if (bypassServer !== 'off') {
|
if (bypassServer !== 'off') {
|
||||||
const ip = BypassServerIps[bypassServer as keyof typeof BypassServerIps];
|
const ip = BypassServerIps[bypassServer as keyof typeof BypassServerIps];
|
||||||
@ -35,24 +51,8 @@ class XcloudInterceptor {
|
|||||||
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
||||||
|
|
||||||
// Get server list
|
// Get server list
|
||||||
const serverEmojis = {
|
|
||||||
AustraliaEast: '🇦🇺',
|
|
||||||
AustraliaSouthEast: '🇦🇺',
|
|
||||||
BrazilSouth: '🇧🇷',
|
|
||||||
EastUS: '🇺🇸',
|
|
||||||
EastUS2: '🇺🇸',
|
|
||||||
JapanEast: '🇯🇵',
|
|
||||||
KoreaCentral: '🇰🇷',
|
|
||||||
MexicoCentral: '🇲🇽',
|
|
||||||
NorthCentralUs: '🇺🇸',
|
|
||||||
SouthCentralUS: '🇺🇸',
|
|
||||||
UKSouth: '🇬🇧',
|
|
||||||
WestEurope: '🇪🇺',
|
|
||||||
WestUS: '🇺🇸',
|
|
||||||
WestUS2: '🇺🇸',
|
|
||||||
};
|
|
||||||
|
|
||||||
const serverRegex = /\/\/(\w+)\./;
|
const serverRegex = /\/\/(\w+)\./;
|
||||||
|
const serverEmojis = XcloudInterceptor.SERVER_EMOJIS;
|
||||||
|
|
||||||
for (let region of obj.offeringSettings.regions) {
|
for (let region of obj.offeringSettings.regions) {
|
||||||
const regionName = region.name as keyof typeof serverEmojis;
|
const regionName = region.name as keyof typeof serverEmojis;
|
||||||
@ -91,7 +91,7 @@ class XcloudInterceptor {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
private static async handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
||||||
const PREF_STREAM_TARGET_RESOLUTION = getPref(PrefKey.STREAM_TARGET_RESOLUTION);
|
const PREF_STREAM_TARGET_RESOLUTION = getPref(PrefKey.STREAM_TARGET_RESOLUTION);
|
||||||
const PREF_STREAM_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE);
|
const PREF_STREAM_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE);
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ class XcloudInterceptor {
|
|||||||
return NATIVE_FETCH(newRequest);
|
return NATIVE_FETCH(newRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #handleWaitTime(request: RequestInfo | URL, init?: RequestInit) {
|
private static async handleWaitTime(request: RequestInfo | URL, init?: RequestInit) {
|
||||||
const response = await NATIVE_FETCH(request, init);
|
const response = await NATIVE_FETCH(request, init);
|
||||||
|
|
||||||
if (getPref(PrefKey.UI_LOADING_SCREEN_WAIT_TIME)) {
|
if (getPref(PrefKey.UI_LOADING_SCREEN_WAIT_TIME)) {
|
||||||
@ -143,7 +143,7 @@ class XcloudInterceptor {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #handleConfiguration(request: RequestInfo | URL, init?: RequestInit) {
|
private static async handleConfiguration(request: RequestInfo | URL, init?: RequestInit) {
|
||||||
if ((request as Request).method !== 'GET') {
|
if ((request as Request).method !== 'GET') {
|
||||||
return NATIVE_FETCH(request, init);
|
return NATIVE_FETCH(request, init);
|
||||||
}
|
}
|
||||||
@ -213,13 +213,13 @@ class XcloudInterceptor {
|
|||||||
|
|
||||||
// Server list
|
// Server list
|
||||||
if (url.endsWith('/v2/login/user')) {
|
if (url.endsWith('/v2/login/user')) {
|
||||||
return XcloudInterceptor.#handleLogin(request, init);
|
return XcloudInterceptor.handleLogin(request, init);
|
||||||
} else if (url.endsWith('/sessions/cloud/play')) { // Get session
|
} else if (url.endsWith('/sessions/cloud/play')) { // Get session
|
||||||
return XcloudInterceptor.#handlePlay(request, init);
|
return XcloudInterceptor.handlePlay(request, init);
|
||||||
} else if (url.includes('xboxlive.com') && url.includes('/waittime/')) {
|
} else if (url.includes('xboxlive.com') && url.includes('/waittime/')) {
|
||||||
return XcloudInterceptor.#handleWaitTime(request, init);
|
return XcloudInterceptor.handleWaitTime(request, init);
|
||||||
} else if (url.endsWith('/configuration')) {
|
} else if (url.endsWith('/configuration')) {
|
||||||
return XcloudInterceptor.#handleConfiguration(request, init);
|
return XcloudInterceptor.handleConfiguration(request, init);
|
||||||
} else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') {
|
} else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') {
|
||||||
return patchIceCandidates(request as Request);
|
return patchIceCandidates(request as Request);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import type { RemotePlayConsoleAddresses } from "@/types/network";
|
|||||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||||
|
|
||||||
export class XhomeInterceptor {
|
export class XhomeInterceptor {
|
||||||
static #consoleAddrs: RemotePlayConsoleAddresses = {};
|
private static consoleAddrs: RemotePlayConsoleAddresses = {};
|
||||||
|
|
||||||
private static readonly BASE_DEVICE_INFO = {
|
private static readonly BASE_DEVICE_INFO = {
|
||||||
appInfo: {
|
appInfo: {
|
||||||
@ -52,7 +52,7 @@ export class XhomeInterceptor {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static async #handleLogin(request: Request) {
|
private static async handleLogin(request: Request) {
|
||||||
try {
|
try {
|
||||||
const clone = (request as Request).clone();
|
const clone = (request as Request).clone();
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ export class XhomeInterceptor {
|
|||||||
return NATIVE_FETCH(request);
|
return NATIVE_FETCH(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #handleConfiguration(request: Request | URL) {
|
private static async handleConfiguration(request: Request | URL) {
|
||||||
const response = await NATIVE_FETCH(request);
|
const response = await NATIVE_FETCH(request);
|
||||||
|
|
||||||
const obj = await response.clone().json()
|
const obj = await response.clone().json()
|
||||||
@ -90,15 +90,15 @@ export class XhomeInterceptor {
|
|||||||
|
|
||||||
const serverDetails = obj.serverDetails;
|
const serverDetails = obj.serverDetails;
|
||||||
if (serverDetails.ipAddress) {
|
if (serverDetails.ipAddress) {
|
||||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
XhomeInterceptor.consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverDetails.ipV4Address) {
|
if (serverDetails.ipV4Address) {
|
||||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
XhomeInterceptor.consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverDetails.ipV6Address) {
|
if (serverDetails.ipV6Address) {
|
||||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
XhomeInterceptor.consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
||||||
}
|
}
|
||||||
|
|
||||||
response.json = () => Promise.resolve(obj);
|
response.json = () => Promise.resolve(obj);
|
||||||
@ -107,7 +107,7 @@ export class XhomeInterceptor {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) {
|
private static async handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) {
|
||||||
const response = await NATIVE_FETCH(request);
|
const response = await NATIVE_FETCH(request);
|
||||||
|
|
||||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.ALL) {
|
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.ALL) {
|
||||||
@ -144,7 +144,7 @@ export class XhomeInterceptor {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #handleTitles(request: Request) {
|
private static async handleTitles(request: Request) {
|
||||||
const clone = request.clone();
|
const clone = request.clone();
|
||||||
|
|
||||||
const headers: {[index: string]: any} = {};
|
const headers: {[index: string]: any} = {};
|
||||||
@ -163,7 +163,7 @@ export class XhomeInterceptor {
|
|||||||
return NATIVE_FETCH(request);
|
return NATIVE_FETCH(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #handlePlay(request: RequestInfo | URL) {
|
private static async handlePlay(request: RequestInfo | URL) {
|
||||||
const clone = (request as Request).clone();
|
const clone = (request as Request).clone();
|
||||||
const body = await clone.json();
|
const body = await clone.json();
|
||||||
|
|
||||||
@ -216,17 +216,17 @@ export class XhomeInterceptor {
|
|||||||
|
|
||||||
// Get console IP
|
// Get console IP
|
||||||
if (url.includes('/configuration')) {
|
if (url.includes('/configuration')) {
|
||||||
return XhomeInterceptor.#handleConfiguration(request);
|
return XhomeInterceptor.handleConfiguration(request);
|
||||||
} else if (url.endsWith('/sessions/home/play')) {
|
} else if (url.endsWith('/sessions/home/play')) {
|
||||||
return XhomeInterceptor.#handlePlay(request);
|
return XhomeInterceptor.handlePlay(request);
|
||||||
} else if (url.includes('inputconfigs')) {
|
} else if (url.includes('inputconfigs')) {
|
||||||
return XhomeInterceptor.#handleInputConfigs(request, opts);
|
return XhomeInterceptor.handleInputConfigs(request, opts);
|
||||||
} else if (url.includes('/login/user')) {
|
} else if (url.includes('/login/user')) {
|
||||||
return XhomeInterceptor.#handleLogin(request);
|
return XhomeInterceptor.handleLogin(request);
|
||||||
} else if (url.endsWith('/titles')) {
|
} else if (url.endsWith('/titles')) {
|
||||||
return XhomeInterceptor.#handleTitles(request);
|
return XhomeInterceptor.handleTitles(request);
|
||||||
} else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') {
|
} else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') {
|
||||||
return patchIceCandidates(request, XhomeInterceptor.#consoleAddrs);
|
return patchIceCandidates(request, XhomeInterceptor.consoleAddrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await NATIVE_FETCH(request);
|
return await NATIVE_FETCH(request);
|
||||||
|
Reference in New Issue
Block a user