mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-29 19:01:43 +02:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
fa19a5a68e | |||
3f834f74b6 | |||
749d5d720e | |||
b969d52a3c | |||
e5bd7e64a7 | |||
82ee00b4ae | |||
8e88af5f8c | |||
927eae3f2f | |||
9f440e9cf4 | |||
1acb30e3af | |||
34159fad22 | |||
741538ebcf | |||
6d2e04aff1 | |||
f2bc98229f | |||
49fb8e2818 | |||
d012d96675 | |||
c129feaf2d | |||
4f7b23912d | |||
e4d73f9e36 | |||
2eea9ce8f5 | |||
27abab8473 | |||
0c34173815 | |||
0164423e45 | |||
71dcaf4b07 |
9
build.ts
9
build.ts
@ -87,6 +87,15 @@ const postProcess = (str: string): string => {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
|
||||
// Replace " (e) =>" to " e =>"
|
||||
// str = str.replaceAll(/ \(([^\s,.$()]+)\) =>/g, ' $1 =>');
|
||||
|
||||
// Set indent to 1 space
|
||||
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('window.BX_EXPOSED = BxExposed'));
|
||||
assert(str.includes('window.BxEvent = BxEvent'));
|
||||
|
10578
dist/better-xcloud.lite.user.js
vendored
10578
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==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.8.2
|
||||
// @version 5.8.5
|
||||
// ==/UserScript==
|
||||
|
14818
dist/better-xcloud.user.js
vendored
14818
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -76,21 +76,21 @@
|
||||
}
|
||||
|
||||
/* Touch controller buttons */
|
||||
div[data-enabled] {
|
||||
div[data-activated] {
|
||||
button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show enabled button */
|
||||
div[data-enabled='true'] {
|
||||
/* Show default button */
|
||||
div[data-activated='false'] {
|
||||
button:first-of-type {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show enable button */
|
||||
div[data-enabled='false'] {
|
||||
/* Show activated button */
|
||||
div[data-activated='true'] {
|
||||
button:last-of-type {
|
||||
display: block;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
display: inline-block;
|
||||
min-width: 40px;
|
||||
font-family: var(--bx-monospaced-font);
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
|
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 |
@ -75,6 +75,7 @@ export enum PrefKey {
|
||||
VIDEO_PLAYER_TYPE = 'video_player_type',
|
||||
VIDEO_PROCESSING = 'video_processing',
|
||||
VIDEO_POWER_PREFERENCE = 'video_power_preference',
|
||||
VIDEO_MAX_FPS = 'video_max_fps',
|
||||
VIDEO_SHARPNESS = 'video_sharpness',
|
||||
VIDEO_RATIO = 'video_ratio',
|
||||
VIDEO_BRIGHTNESS = 'video_brightness',
|
||||
|
@ -38,37 +38,37 @@ const enum ShortcutAction {
|
||||
}
|
||||
|
||||
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[]} = {};
|
||||
static #buttonsStatus: {[key: string]: boolean[]} = {};
|
||||
private static buttonsCache: {[key: string]: boolean[]} = {};
|
||||
private static buttonsStatus: {[key: string]: boolean[]} = {};
|
||||
|
||||
static #$selectProfile: HTMLSelectElement;
|
||||
static #$selectActions: Partial<{[key in GamepadKey]: HTMLSelectElement}> = {};
|
||||
static #$container: HTMLElement;
|
||||
private static $selectProfile: HTMLSelectElement;
|
||||
private static $selectActions: Partial<{[key in GamepadKey]: HTMLSelectElement}> = {};
|
||||
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) {
|
||||
ControllerShortcut.#buttonsCache[index] = [];
|
||||
ControllerShortcut.#buttonsStatus[index] = [];
|
||||
ControllerShortcut.buttonsCache[index] = [];
|
||||
ControllerShortcut.buttonsStatus[index] = [];
|
||||
}
|
||||
|
||||
static handle(gamepad: Gamepad): boolean {
|
||||
if (!ControllerShortcut.#ACTIONS) {
|
||||
ControllerShortcut.#ACTIONS = ControllerShortcut.#getActionsFromStorage();
|
||||
if (!ControllerShortcut.ACTIONS) {
|
||||
ControllerShortcut.ACTIONS = ControllerShortcut.getActionsFromStorage();
|
||||
}
|
||||
|
||||
const gamepadIndex = gamepad.index;
|
||||
const actions = ControllerShortcut.#ACTIONS![gamepad.id];
|
||||
const actions = ControllerShortcut.ACTIONS![gamepad.id];
|
||||
if (!actions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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
|
||||
ControllerShortcut.#buttonsStatus[gamepadIndex] = [];
|
||||
ControllerShortcut.buttonsStatus[gamepadIndex] = [];
|
||||
|
||||
const pressed: boolean[] = [];
|
||||
let otherButtonPressed = false;
|
||||
@ -80,17 +80,17 @@ export class ControllerShortcut {
|
||||
pressed[index] = true;
|
||||
|
||||
// If this is newly pressed button -> run action
|
||||
if (actions[index] && !ControllerShortcut.#buttonsCache[gamepadIndex][index]) {
|
||||
setTimeout(() => ControllerShortcut.#runAction(actions[index]!), 0);
|
||||
if (actions[index] && !ControllerShortcut.buttonsCache[gamepadIndex][index]) {
|
||||
setTimeout(() => ControllerShortcut.runAction(actions[index]!), 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ControllerShortcut.#buttonsStatus[gamepadIndex] = pressed;
|
||||
ControllerShortcut.buttonsStatus[gamepadIndex] = pressed;
|
||||
return otherButtonPressed;
|
||||
}
|
||||
|
||||
static #runAction(action: ShortcutAction) {
|
||||
private static runAction(action: ShortcutAction) {
|
||||
switch (action) {
|
||||
case ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW:
|
||||
SettingsNavigationDialog.getInstance().show();
|
||||
@ -134,8 +134,8 @@ export class ControllerShortcut {
|
||||
}
|
||||
}
|
||||
|
||||
static #updateAction(profile: string, button: GamepadKey, action: ShortcutAction | null) {
|
||||
const actions = ControllerShortcut.#ACTIONS!;
|
||||
private static updateAction(profile: string, button: GamepadKey, action: ShortcutAction | null) {
|
||||
const actions = ControllerShortcut.ACTIONS!;
|
||||
if (!(profile in actions)) {
|
||||
actions[profile] = [];
|
||||
}
|
||||
@ -147,9 +147,9 @@ export class ControllerShortcut {
|
||||
actions[profile][button] = action;
|
||||
|
||||
// Remove empty profiles
|
||||
for (const key in ControllerShortcut.#ACTIONS) {
|
||||
for (const key in ControllerShortcut.ACTIONS) {
|
||||
let empty = true;
|
||||
for (const value of ControllerShortcut.#ACTIONS[key]) {
|
||||
for (const value of ControllerShortcut.ACTIONS[key]) {
|
||||
if (!!value) {
|
||||
empty = false;
|
||||
break;
|
||||
@ -157,19 +157,19 @@ export class ControllerShortcut {
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
delete ControllerShortcut.#ACTIONS[key];
|
||||
delete ControllerShortcut.ACTIONS[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const $select = ControllerShortcut.#$selectProfile;
|
||||
const $container = ControllerShortcut.#$container;
|
||||
private static updateProfileList(e?: GamepadEvent) {
|
||||
const $select = ControllerShortcut.$selectProfile;
|
||||
const $container = ControllerShortcut.$container;
|
||||
|
||||
const $fragment = document.createDocumentFragment();
|
||||
|
||||
@ -205,16 +205,16 @@ export class ControllerShortcut {
|
||||
|
||||
}
|
||||
|
||||
static #switchProfile(profile: string) {
|
||||
let actions = ControllerShortcut.#ACTIONS![profile];
|
||||
private static switchProfile(profile: string) {
|
||||
let actions = ControllerShortcut.ACTIONS![profile];
|
||||
if (!actions) {
|
||||
actions = [];
|
||||
}
|
||||
|
||||
// Reset selects' values
|
||||
let button: any;
|
||||
for (button in ControllerShortcut.#$selectActions) {
|
||||
const $select = ControllerShortcut.#$selectActions[button as GamepadKey]!;
|
||||
for (button in ControllerShortcut.$selectActions) {
|
||||
const $select = ControllerShortcut.$selectActions[button as GamepadKey]!;
|
||||
$select.value = actions[button] || '';
|
||||
|
||||
BxEvent.dispatch($select, 'input', {
|
||||
@ -224,15 +224,15 @@ export class ControllerShortcut {
|
||||
}
|
||||
}
|
||||
|
||||
static #getActionsFromStorage() {
|
||||
return JSON.parse(window.localStorage.getItem(ControllerShortcut.#STORAGE_KEY) || '{}');
|
||||
private static getActionsFromStorage() {
|
||||
return JSON.parse(window.localStorage.getItem(ControllerShortcut.STORAGE_KEY) || '{}');
|
||||
}
|
||||
|
||||
static renderSettings() {
|
||||
const PREF_CONTROLLER_FRIENDLY_UI = getPref(PrefKey.UI_CONTROLLER_FRIENDLY);
|
||||
|
||||
// Read actions from localStorage
|
||||
ControllerShortcut.#ACTIONS = ControllerShortcut.#getActionsFromStorage();
|
||||
ControllerShortcut.ACTIONS = ControllerShortcut.getActionsFromStorage();
|
||||
|
||||
const buttons: Map<GamepadKey, PrompFont> = new Map();
|
||||
buttons.set(GamepadKey.Y, PrompFont.Y);
|
||||
@ -340,7 +340,7 @@ export class ControllerShortcut {
|
||||
);
|
||||
|
||||
$selectProfile.addEventListener('input', e => {
|
||||
ControllerShortcut.#switchProfile($selectProfile.value);
|
||||
ControllerShortcut.switchProfile($selectProfile.value);
|
||||
});
|
||||
|
||||
const onActionChanged = (e: Event) => {
|
||||
@ -361,7 +361,7 @@ export class ControllerShortcut {
|
||||
($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.addEventListener('input', onActionChanged);
|
||||
|
||||
ControllerShortcut.#$selectActions[button] = $select;
|
||||
ControllerShortcut.$selectActions[button] = $select;
|
||||
|
||||
if (PREF_CONTROLLER_FRIENDLY_UI) {
|
||||
const $bxSelect = BxSelectElement.wrap($select);
|
||||
@ -412,14 +412,14 @@ export class ControllerShortcut {
|
||||
|
||||
$container.appendChild($remap);
|
||||
|
||||
ControllerShortcut.#$selectProfile = $selectProfile;
|
||||
ControllerShortcut.#$container = $container;
|
||||
ControllerShortcut.$selectProfile = $selectProfile;
|
||||
ControllerShortcut.$container = $container;
|
||||
|
||||
// Detect when gamepad connected/disconnect
|
||||
window.addEventListener('gamepadconnected', ControllerShortcut.#updateProfileList);
|
||||
window.addEventListener('gamepaddisconnected', ControllerShortcut.#updateProfileList);
|
||||
window.addEventListener('gamepadconnected', ControllerShortcut.updateProfileList);
|
||||
window.addEventListener('gamepaddisconnected', ControllerShortcut.updateProfileList);
|
||||
|
||||
ControllerShortcut.#updateProfileList();
|
||||
ControllerShortcut.updateProfileList();
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
export abstract class BaseGameBarAction {
|
||||
abstract $content: HTMLElement;
|
||||
|
||||
constructor() {}
|
||||
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 {
|
||||
$content: HTMLElement;
|
||||
|
||||
visible: boolean = false;
|
||||
|
||||
constructor() {
|
||||
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({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE,
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
const $btnMuted = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE_MUTED,
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnDefault,
|
||||
$btnMuted,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
||||
|
||||
window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, e => {
|
||||
const microphoneState = (e as any).microphoneState;
|
||||
const enabled = microphoneState === MicrophoneState.ENABLED;
|
||||
|
||||
this.$content.setAttribute('data-enabled', enabled.toString());
|
||||
this.$content.dataset.activated = enabled.toString();
|
||||
|
||||
// Show the button in Game Bar if the mic is enabled
|
||||
this.$content.classList.remove('bx-gone');
|
||||
});
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
onClick(e: Event) {
|
||||
super.onClick(e);
|
||||
const enabled = MicrophoneShortcut.toggle(false);
|
||||
this.$content.dataset.activated = enabled.toString();
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.visible = false;
|
||||
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 { createButton, ButtonStyle } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
@ -11,20 +10,16 @@ export class ScreenshotAction extends BaseGameBarAction {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const onClick = (e: Event) => {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
Screenshot.takeScreenshot();
|
||||
};
|
||||
|
||||
this.$content = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.SCREENSHOT,
|
||||
title: t('take-screenshot'),
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
onClick(e: Event): void {
|
||||
super.onClick(e);
|
||||
Screenshot.takeScreenshot();
|
||||
}
|
||||
}
|
||||
|
@ -11,44 +11,35 @@ export class SpeakerAction extends BaseGameBarAction {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const onClick = (e: Event) => {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
SoundShortcut.muteUnmute();
|
||||
};
|
||||
|
||||
const $btnEnable = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.AUDIO,
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
});
|
||||
|
||||
const $btnMuted = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.SPEAKER_MUTED,
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnEnable,
|
||||
$btnMuted,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnEnable, $btnMuted);
|
||||
|
||||
window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, e => {
|
||||
const speakerState = (e as any).speakerState;
|
||||
const enabled = speakerState === SpeakerState.ENABLED;
|
||||
|
||||
this.$content.dataset.enabled = enabled.toString();
|
||||
this.$content.dataset.activated = (!enabled).toString();
|
||||
});
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
onClick(e: Event) {
|
||||
super.onClick(e);
|
||||
SoundShortcut.muteUnmute();
|
||||
}
|
||||
|
||||
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 { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { TouchController } from "@modules/touch-controller";
|
||||
@ -11,44 +10,31 @@ export class TouchControlAction extends BaseGameBarAction {
|
||||
constructor() {
|
||||
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({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TOUCH_CONTROL_ENABLE,
|
||||
title: t('show-touch-controller'),
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
});
|
||||
|
||||
const $btnDisable = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TOUCH_CONTROL_DISABLE,
|
||||
title: t('hide-touch-controller'),
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnEnable,
|
||||
$btnDisable,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnEnable, $btnDisable);
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
onClick(e: Event) {
|
||||
super.onClick(e);
|
||||
const isVisible = TouchController.toggleVisibility();
|
||||
this.$content.dataset.activated = (!isVisible).toString();
|
||||
}
|
||||
|
||||
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 { createButton, ButtonStyle } from "@/utils/html";
|
||||
import { t } from "@/utils/translation";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { TrueAchievements } from "@/utils/true-achievements";
|
||||
|
||||
@ -11,20 +9,15 @@ export class TrueAchievementsAction extends BaseGameBarAction {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const onClick = (e: Event) => {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
TrueAchievements.open(false);
|
||||
};
|
||||
|
||||
this.$content = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TRUE_ACHIEVEMENTS,
|
||||
title: t('true-achievements'),
|
||||
onClick: onClick,
|
||||
onClick: this.onClick.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
onClick(e: Event) {
|
||||
super.onClick(e);
|
||||
TrueAchievements.open(false);
|
||||
}
|
||||
}
|
||||
|
@ -7,44 +7,40 @@ import type { BaseGameBarAction } from "./action-base";
|
||||
import { STATES } from "@utils/global";
|
||||
import { MicrophoneAction } from "./action-microphone";
|
||||
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 { SpeakerAction } from "./action-speaker";
|
||||
import { RendererAction } from "./action-renderer";
|
||||
|
||||
|
||||
export class GameBar {
|
||||
private static instance: GameBar;
|
||||
public static getInstance(): GameBar {
|
||||
if (!GameBar.instance) {
|
||||
GameBar.instance = new GameBar();
|
||||
}
|
||||
|
||||
return GameBar.instance;
|
||||
}
|
||||
public static getInstance = () => GameBar.instance ?? (GameBar.instance = new GameBar());
|
||||
|
||||
private static readonly VISIBLE_DURATION = 2000;
|
||||
|
||||
private $gameBar: HTMLElement;
|
||||
private $container: HTMLElement;
|
||||
|
||||
private timeout: number | null = null;
|
||||
private timeoutId: number | null = null;
|
||||
|
||||
private actions: BaseGameBarAction[] = [];
|
||||
|
||||
private constructor() {
|
||||
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},
|
||||
$container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
|
||||
createSvgIcon(position === 'bottom-left' ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT),
|
||||
);
|
||||
$container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
|
||||
createSvgIcon(position === 'bottom-left' ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT),
|
||||
);
|
||||
|
||||
this.actions = [
|
||||
new ScreenshotAction(),
|
||||
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.OFF) ? [new TouchControlAction()] : []),
|
||||
new SpeakerAction(),
|
||||
new RendererAction(),
|
||||
new MicrophoneAction(),
|
||||
new TrueAchievementsAction(),
|
||||
];
|
||||
@ -76,11 +72,7 @@ export class GameBar {
|
||||
|
||||
// Add animation when hiding game bar
|
||||
$container.addEventListener('transitionend', e => {
|
||||
const classList = $container.classList;
|
||||
if (classList.contains('bx-hide')) {
|
||||
classList.remove('bx-hide');
|
||||
classList.add('bx-offscreen');
|
||||
}
|
||||
$container.classList.replace('bx-hide', 'bx-offscreen');
|
||||
});
|
||||
|
||||
document.documentElement.appendChild($gameBar);
|
||||
@ -103,31 +95,27 @@ export class GameBar {
|
||||
private beginHideTimeout() {
|
||||
this.clearHideTimeout();
|
||||
|
||||
this.timeout = window.setTimeout(() => {
|
||||
this.timeout = null;
|
||||
this.hideBar();
|
||||
}, GameBar.VISIBLE_DURATION);
|
||||
this.timeoutId = window.setTimeout(() => {
|
||||
this.timeoutId = null;
|
||||
this.hideBar();
|
||||
}, GameBar.VISIBLE_DURATION);
|
||||
}
|
||||
|
||||
private clearHideTimeout() {
|
||||
this.timeout && clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
this.timeoutId && clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.$gameBar && this.$gameBar.classList.remove('bx-gone');
|
||||
this.$gameBar.classList.remove('bx-gone');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.hideBar();
|
||||
this.$gameBar && this.$gameBar.classList.add('bx-gone');
|
||||
this.$gameBar.classList.add('bx-gone');
|
||||
}
|
||||
|
||||
showBar() {
|
||||
if (!this.$container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$container.classList.remove('bx-offscreen', 'bx-hide' , 'bx-gone');
|
||||
this.$container.classList.add('bx-show');
|
||||
|
||||
@ -140,18 +128,11 @@ export class GameBar {
|
||||
// Stop focusing Game Bar
|
||||
clearFocus();
|
||||
|
||||
if (!this.$container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$container.classList.remove('bx-show');
|
||||
this.$container.classList.add('bx-hide');
|
||||
this.$container.classList.replace('bx-show', 'bx-hide');
|
||||
}
|
||||
|
||||
// Reset all states
|
||||
reset() {
|
||||
for (const action of this.actions) {
|
||||
action.reset();
|
||||
}
|
||||
this.actions.forEach(action => action.reset());
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { compressCss } from "@macros/build" with {type: "macro"};
|
||||
|
||||
export class LoadingScreen {
|
||||
static #$bgStyle: HTMLElement;
|
||||
static #$waitTimeBox: HTMLElement;
|
||||
private static $bgStyle: HTMLElement;
|
||||
private static $waitTimeBox: HTMLElement;
|
||||
|
||||
static #waitTimeInterval?: number | null = null;
|
||||
static #orgWebTitle: string;
|
||||
private static waitTimeInterval?: number | null = null;
|
||||
private static orgWebTitle: string;
|
||||
|
||||
static #secondsToString(seconds: number) {
|
||||
private static secondsToString(seconds: number) {
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
|
||||
@ -28,21 +28,21 @@ export class LoadingScreen {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LoadingScreen.#$bgStyle) {
|
||||
if (!LoadingScreen.$bgStyle) {
|
||||
const $bgStyle = CE('style');
|
||||
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') {
|
||||
LoadingScreen.#hideRocket();
|
||||
LoadingScreen.hideRocket();
|
||||
}
|
||||
}
|
||||
|
||||
static #hideRocket() {
|
||||
let $bgStyle = LoadingScreen.#$bgStyle;
|
||||
private static hideRocket() {
|
||||
let $bgStyle = LoadingScreen.$bgStyle;
|
||||
|
||||
$bgStyle.textContent! += compressCss(`
|
||||
#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
|
||||
let $bgStyle = LoadingScreen.#$bgStyle;
|
||||
let $bgStyle = LoadingScreen.$bgStyle;
|
||||
|
||||
// Limit max width to reduce image size
|
||||
imageUrl = imageUrl + '?w=1920';
|
||||
@ -89,14 +89,14 @@ export class LoadingScreen {
|
||||
static setupWaitTime(waitTime: number) {
|
||||
// Hide rocket when queing
|
||||
if (getPref(PrefKey.UI_LOADING_SCREEN_ROCKET) === 'hide-queue') {
|
||||
LoadingScreen.#hideRocket();
|
||||
LoadingScreen.hideRocket();
|
||||
}
|
||||
|
||||
let secondsLeft = waitTime;
|
||||
let $countDown;
|
||||
let $estimated;
|
||||
|
||||
LoadingScreen.#orgWebTitle = document.title;
|
||||
LoadingScreen.orgWebTitle = document.title;
|
||||
|
||||
const endDate = new Date();
|
||||
const timeZoneOffsetSeconds = endDate.getTimezoneOffset() * 60;
|
||||
@ -104,9 +104,9 @@ export class LoadingScreen {
|
||||
|
||||
let endDateStr = endDate.toISOString().slice(0, 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) {
|
||||
$waitTimeBox = CE('div', {'class': 'bx-wait-time-box'},
|
||||
CE('label', {}, t('server')),
|
||||
@ -118,7 +118,7 @@ export class LoadingScreen {
|
||||
);
|
||||
|
||||
document.documentElement.appendChild($waitTimeBox);
|
||||
LoadingScreen.#$waitTimeBox = $waitTimeBox;
|
||||
LoadingScreen.$waitTimeBox = $waitTimeBox;
|
||||
} else {
|
||||
$waitTimeBox.classList.remove('bx-gone');
|
||||
$estimated = $waitTimeBox.querySelector('.bx-wait-time-estimated')!;
|
||||
@ -126,36 +126,36 @@ export class LoadingScreen {
|
||||
}
|
||||
|
||||
$estimated.textContent = endDateStr;
|
||||
$countDown.textContent = LoadingScreen.#secondsToString(secondsLeft);
|
||||
document.title = `[${$countDown.textContent}] ${LoadingScreen.#orgWebTitle}`;
|
||||
$countDown.textContent = LoadingScreen.secondsToString(secondsLeft);
|
||||
document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`;
|
||||
|
||||
LoadingScreen.#waitTimeInterval = window.setInterval(() => {
|
||||
LoadingScreen.waitTimeInterval = window.setInterval(() => {
|
||||
secondsLeft--;
|
||||
$countDown.textContent = LoadingScreen.#secondsToString(secondsLeft);
|
||||
document.title = `[${$countDown.textContent}] ${LoadingScreen.#orgWebTitle}`;
|
||||
$countDown.textContent = LoadingScreen.secondsToString(secondsLeft);
|
||||
document.title = `[${$countDown.textContent}] ${LoadingScreen.orgWebTitle}`;
|
||||
|
||||
if (secondsLeft <= 0) {
|
||||
LoadingScreen.#waitTimeInterval && clearInterval(LoadingScreen.#waitTimeInterval);
|
||||
LoadingScreen.#waitTimeInterval = null;
|
||||
LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval);
|
||||
LoadingScreen.waitTimeInterval = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
static hide() {
|
||||
LoadingScreen.#orgWebTitle && (document.title = LoadingScreen.#orgWebTitle);
|
||||
LoadingScreen.#$waitTimeBox && LoadingScreen.#$waitTimeBox.classList.add('bx-gone');
|
||||
LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle);
|
||||
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"]');
|
||||
$rocketBg && $rocketBg.addEventListener('transitionend', e => {
|
||||
LoadingScreen.#$bgStyle.textContent += compressCss(`
|
||||
LoadingScreen.$bgStyle.textContent += compressCss(`
|
||||
#game-stream {
|
||||
background: #000 !important;
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
LoadingScreen.#$bgStyle.textContent += compressCss(`
|
||||
LoadingScreen.$bgStyle.textContent += compressCss(`
|
||||
#game-stream rect[width="800"] {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
@ -166,10 +166,10 @@ export class LoadingScreen {
|
||||
}
|
||||
|
||||
static reset() {
|
||||
LoadingScreen.#$bgStyle && (LoadingScreen.#$bgStyle.textContent = '');
|
||||
LoadingScreen.$bgStyle && (LoadingScreen.$bgStyle.textContent = '');
|
||||
|
||||
LoadingScreen.#$waitTimeBox && LoadingScreen.#$waitTimeBox.classList.add('bx-gone');
|
||||
LoadingScreen.#waitTimeInterval && clearInterval(LoadingScreen.#waitTimeInterval);
|
||||
LoadingScreen.#waitTimeInterval = null;
|
||||
LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add('bx-gone');
|
||||
LoadingScreen.waitTimeInterval && clearInterval(LoadingScreen.waitTimeInterval);
|
||||
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
|
||||
*/
|
||||
export class EmulatedMkbHandler extends MkbHandler {
|
||||
static #instance: EmulatedMkbHandler;
|
||||
public static getInstance(): EmulatedMkbHandler {
|
||||
if (!EmulatedMkbHandler.#instance) {
|
||||
EmulatedMkbHandler.#instance = new EmulatedMkbHandler();
|
||||
}
|
||||
|
||||
return EmulatedMkbHandler.#instance;
|
||||
}
|
||||
private static instance: EmulatedMkbHandler;
|
||||
public static getInstance = () => EmulatedMkbHandler.instance ?? (EmulatedMkbHandler.instance = new EmulatedMkbHandler());
|
||||
|
||||
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
||||
|
||||
|
@ -23,6 +23,8 @@ type XcloudInputSink = {
|
||||
|
||||
export class NativeMkbHandler extends MkbHandler {
|
||||
private static instance: NativeMkbHandler;
|
||||
public static getInstance = () => NativeMkbHandler.instance ?? (NativeMkbHandler.instance = new NativeMkbHandler());
|
||||
|
||||
#pointerClient: PointerClient | undefined;
|
||||
#enabled: boolean = false;
|
||||
|
||||
@ -37,14 +39,6 @@ export class NativeMkbHandler extends MkbHandler {
|
||||
|
||||
#$message?: HTMLElement;
|
||||
|
||||
public static getInstance(): NativeMkbHandler {
|
||||
if (!NativeMkbHandler.instance) {
|
||||
NativeMkbHandler.instance = new NativeMkbHandler();
|
||||
}
|
||||
|
||||
return NativeMkbHandler.instance;
|
||||
}
|
||||
|
||||
#onKeyboardEvent(e: KeyboardEvent) {
|
||||
if (e.type === 'keyup' && e.code === 'F8') {
|
||||
e.preventDefault();
|
||||
|
@ -15,45 +15,39 @@ enum PointerAction {
|
||||
|
||||
export class PointerClient {
|
||||
private static instance: PointerClient;
|
||||
public static getInstance(): PointerClient {
|
||||
if (!PointerClient.instance) {
|
||||
PointerClient.instance = new PointerClient();
|
||||
}
|
||||
public static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient());
|
||||
|
||||
return PointerClient.instance;
|
||||
}
|
||||
|
||||
#socket: WebSocket | undefined | null;
|
||||
#mkbHandler: MkbHandler | undefined;
|
||||
private socket: WebSocket | undefined | null;
|
||||
private mkbHandler: MkbHandler | undefined;
|
||||
|
||||
start(port: number, mkbHandler: MkbHandler) {
|
||||
if (!port) {
|
||||
throw new Error('PointerServer port is 0');
|
||||
}
|
||||
|
||||
this.#mkbHandler = mkbHandler;
|
||||
this.mkbHandler = mkbHandler;
|
||||
|
||||
// Create WebSocket connection.
|
||||
this.#socket = new WebSocket(`ws://localhost:${port}`);
|
||||
this.#socket.binaryType = 'arraybuffer';
|
||||
this.socket = new WebSocket(`ws://localhost:${port}`);
|
||||
this.socket.binaryType = 'arraybuffer';
|
||||
|
||||
// Connection opened
|
||||
this.#socket.addEventListener('open', (event) => {
|
||||
this.socket.addEventListener('open', (event) => {
|
||||
BxLogger.info(LOG_TAG, 'connected')
|
||||
});
|
||||
|
||||
// Error
|
||||
this.#socket.addEventListener('error', (event) => {
|
||||
this.socket.addEventListener('error', (event) => {
|
||||
BxLogger.error(LOG_TAG, event);
|
||||
Toast.show('Cannot setup mouse: ' + event);
|
||||
});
|
||||
|
||||
this.#socket.addEventListener('close', (event) => {
|
||||
this.#socket = null;
|
||||
this.socket.addEventListener('close', (event) => {
|
||||
this.socket = null;
|
||||
});
|
||||
|
||||
// Listen for messages
|
||||
this.#socket.addEventListener('message', (event) => {
|
||||
this.socket.addEventListener('message', (event) => {
|
||||
const dataView = new DataView(event.data);
|
||||
|
||||
let messageType = dataView.getInt8(0);
|
||||
@ -84,7 +78,7 @@ export class PointerClient {
|
||||
offset += Int16Array.BYTES_PER_ELEMENT;
|
||||
const y = dataView.getInt16(offset);
|
||||
|
||||
this.#mkbHandler?.handleMouseMove({
|
||||
this.mkbHandler?.handleMouseMove({
|
||||
movementX: x,
|
||||
movementY: y,
|
||||
});
|
||||
@ -94,7 +88,7 @@ export class PointerClient {
|
||||
onPress(messageType: PointerAction, dataView: DataView, offset: number) {
|
||||
const button = dataView.getUint8(offset);
|
||||
|
||||
this.#mkbHandler?.handleMouseClick({
|
||||
this.mkbHandler?.handleMouseClick({
|
||||
pointerButton: button,
|
||||
pressed: messageType === PointerAction.BUTTON_PRESS,
|
||||
});
|
||||
@ -108,7 +102,7 @@ export class PointerClient {
|
||||
offset += Int16Array.BYTES_PER_ELEMENT;
|
||||
const hScroll = dataView.getInt16(offset);
|
||||
|
||||
this.#mkbHandler?.handleMouseWheel({
|
||||
this.mkbHandler?.handleMouseWheel({
|
||||
vertical: vScroll,
|
||||
horizontal: hScroll,
|
||||
});
|
||||
@ -118,13 +112,13 @@ export class PointerClient {
|
||||
|
||||
onPointerCaptureChanged(dataView: DataView, offset: number) {
|
||||
const hasCapture = dataView.getInt8(offset) === 1;
|
||||
!hasCapture && this.#mkbHandler?.stop();
|
||||
!hasCapture && this.mkbHandler?.stop();
|
||||
}
|
||||
|
||||
stop() {
|
||||
try {
|
||||
this.#socket?.close();
|
||||
this.socket?.close();
|
||||
} catch (e) {}
|
||||
this.#socket = null;
|
||||
this.socket = null;
|
||||
}
|
||||
}
|
||||
|
@ -632,12 +632,12 @@ true` + text;
|
||||
},
|
||||
|
||||
skipFeedbackDialog(str: string) {
|
||||
let text = '&&this.shouldTransitionToFeedback(';
|
||||
let text = 'shouldTransitionToFeedback(e){';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.replace(text, '&& false ' + text);
|
||||
str = str.replace(text, text + 'return !1;');
|
||||
return str;
|
||||
},
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
#version 300 es
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D data;
|
||||
uniform vec2 iResolution;
|
||||
|
||||
const int FILTER_UNSHARP_MASKING = 1;
|
||||
const int FILTER_CAS = 2;
|
||||
// const int FILTER_CAS = 2;
|
||||
|
||||
// constrast = 0.8
|
||||
const float CAS_CONTRAST_PEAK = (-3.0 * 0.8 + 8.0);
|
||||
const float CAS_CONTRAST_PEAK = 0.8 * -3.0 + 8.0;
|
||||
|
||||
// Luminosity factor
|
||||
const vec3 LUMINOSITY_FACTOR = vec3(0.2126, 0.7152, 0.0722);
|
||||
@ -17,96 +19,82 @@ uniform float brightness;
|
||||
uniform float contrast;
|
||||
uniform float saturation;
|
||||
|
||||
vec3 clarityBoost(sampler2D tex, vec2 coord) {
|
||||
out vec4 fragColor;
|
||||
|
||||
vec3 clarityBoost(sampler2D tex, vec2 coord, vec3 e) {
|
||||
vec2 texelSize = 1.0 / iResolution.xy;
|
||||
|
||||
// Load a collection of samples in a 3x3 neighorhood, where e is the current pixel.
|
||||
// a b c
|
||||
// d e f
|
||||
// g h i
|
||||
vec3 a = texture2D(tex, coord + texelSize * vec2(-1, 1)).rgb;
|
||||
vec3 b = texture2D(tex, coord + texelSize * vec2(0, 1)).rgb;
|
||||
vec3 c = texture2D(tex, coord + texelSize * vec2(1, 1)).rgb;
|
||||
vec3 a = texture(tex, coord + texelSize * vec2(-1, 1)).rgb;
|
||||
vec3 b = texture(tex, coord + texelSize * vec2(0, 1)).rgb;
|
||||
vec3 c = texture(tex, coord + texelSize * vec2(1, 1)).rgb;
|
||||
|
||||
vec3 d = texture2D(tex, coord + texelSize * vec2(-1, 0)).rgb;
|
||||
vec3 e = texture2D(tex, coord).rgb;
|
||||
vec3 f = texture2D(tex, coord + texelSize * vec2(1, 0)).rgb;
|
||||
vec3 d = texture(tex, coord + texelSize * vec2(-1, 0)).rgb;
|
||||
vec3 f = texture(tex, coord + texelSize * vec2(1, 0)).rgb;
|
||||
|
||||
vec3 g = texture2D(tex, coord + texelSize * vec2(-1, -1)).rgb;
|
||||
vec3 h = texture2D(tex, coord + texelSize * vec2(0, -1)).rgb;
|
||||
vec3 i = texture2D(tex, coord + texelSize * vec2(1, -1)).rgb;
|
||||
vec3 g = texture(tex, coord + texelSize * vec2(-1, -1)).rgb;
|
||||
vec3 h = texture(tex, coord + texelSize * vec2(0, -1)).rgb;
|
||||
vec3 i = texture(tex, coord + texelSize * vec2(1, -1)).rgb;
|
||||
|
||||
if (filterId == FILTER_CAS) {
|
||||
// Soft min and max.
|
||||
// a b c b
|
||||
// d e f * 0.5 + d e f * 0.5
|
||||
// g h i h
|
||||
// These are 2.0x bigger (factored out the extra multiply).
|
||||
vec3 minRgb = min(min(min(d, e), min(f, b)), h);
|
||||
vec3 minRgb2 = min(min(a, c), min(g, i));
|
||||
minRgb += min(minRgb, minRgb2);
|
||||
|
||||
vec3 maxRgb = max(max(max(d, e), max(f, b)), h);
|
||||
vec3 maxRgb2 = max(max(a, c), max(g, i));
|
||||
maxRgb += max(maxRgb, maxRgb2);
|
||||
|
||||
// Smooth minimum distance to signal limit divided by smooth max.
|
||||
vec3 reciprocalMaxRgb = 1.0 / maxRgb;
|
||||
vec3 amplifyRgb = clamp(min(minRgb, 2.0 - maxRgb) * reciprocalMaxRgb, 0.0, 1.0);
|
||||
|
||||
// Shaping amount of sharpening.
|
||||
amplifyRgb = inversesqrt(amplifyRgb);
|
||||
|
||||
vec3 weightRgb = -(1.0 / (amplifyRgb * CAS_CONTRAST_PEAK));
|
||||
vec3 reciprocalWeightRgb = 1.0 / (4.0 * weightRgb + 1.0);
|
||||
|
||||
// 0 w 0
|
||||
// Filter shape: w 1 w
|
||||
// 0 w 0
|
||||
vec3 window = (b + d) + (f + h);
|
||||
vec3 outColor = clamp((window * weightRgb + e) * reciprocalWeightRgb, 0.0, 1.0);
|
||||
|
||||
outColor = mix(e, outColor, sharpenFactor / 2.0);
|
||||
|
||||
return outColor;
|
||||
} else if (filterId == FILTER_UNSHARP_MASKING) {
|
||||
vec3 gaussianBlur = (a + c + g + i) * 1.0 +
|
||||
(b + d + f + h) * 2.0 +
|
||||
e * 4.0;
|
||||
// USM
|
||||
if (filterId == FILTER_UNSHARP_MASKING) {
|
||||
vec3 gaussianBlur = (a + c + g + i) * 1.0 + (b + d + f + h) * 2.0 + e * 4.0;
|
||||
gaussianBlur /= 16.0;
|
||||
|
||||
// Return edge detection
|
||||
return e + (e - gaussianBlur) * sharpenFactor / 3.0;
|
||||
}
|
||||
|
||||
return e;
|
||||
// CAS
|
||||
// Soft min and max.
|
||||
// a b c b
|
||||
// d e f * 0.5 + d e f * 0.5
|
||||
// g h i h
|
||||
// These are 2.0x bigger (factored out the extra multiply).
|
||||
vec3 minRgb = min(min(min(d, e), min(f, b)), h);
|
||||
minRgb += min(min(a, c), min(g, i));
|
||||
|
||||
vec3 maxRgb = max(max(max(d, e), max(f, b)), h);
|
||||
maxRgb += max(max(a, c), max(g, i));
|
||||
|
||||
// Smooth minimum distance to signal limit divided by smooth max.
|
||||
vec3 reciprocalMaxRgb = 1.0 / maxRgb;
|
||||
vec3 amplifyRgb = clamp(min(minRgb, 2.0 - maxRgb) * reciprocalMaxRgb, 0.0, 1.0);
|
||||
|
||||
// Shaping amount of sharpening.
|
||||
amplifyRgb = inversesqrt(amplifyRgb);
|
||||
|
||||
vec3 weightRgb = -(1.0 / (amplifyRgb * CAS_CONTRAST_PEAK));
|
||||
vec3 reciprocalWeightRgb = 1.0 / (4.0 * weightRgb + 1.0);
|
||||
|
||||
// 0 w 0
|
||||
// Filter shape: w 1 w
|
||||
// 0 w 0
|
||||
vec3 window = b + d + f + h;
|
||||
vec3 outColor = clamp((window * weightRgb + e) * reciprocalWeightRgb, 0.0, 1.0);
|
||||
|
||||
return mix(e, outColor, sharpenFactor / 2.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 color;
|
||||
vec2 uv = gl_FragCoord.xy / iResolution.xy;
|
||||
// Get current pixel
|
||||
vec3 color = texture(data, uv).rgb;
|
||||
|
||||
if (sharpenFactor > 0.0) {
|
||||
color = clarityBoost(data, uv);
|
||||
} else {
|
||||
color = texture2D(data, uv).rgb;
|
||||
}
|
||||
// Clarity boost
|
||||
color = sharpenFactor > 0.0 ? clarityBoost(data, uv, color) : color;
|
||||
|
||||
// Saturation
|
||||
if (saturation != 1.0) {
|
||||
vec3 grayscale = vec3(dot(color, LUMINOSITY_FACTOR));
|
||||
color = mix(grayscale, color, saturation);
|
||||
}
|
||||
color = saturation != 1.0 ? mix(vec3(dot(color, LUMINOSITY_FACTOR)), color, saturation) : color;
|
||||
|
||||
// Contrast
|
||||
if (contrast != 1.0) {
|
||||
color = 0.5 + contrast * (color - 0.5);
|
||||
}
|
||||
color = contrast * (color - 0.5) + 0.5;
|
||||
|
||||
// Brightness
|
||||
if (brightness != 1.0) {
|
||||
color = brightness * color;
|
||||
}
|
||||
color = brightness * color;
|
||||
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
fragColor = vec4(color, 1.0);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
attribute vec4 position;
|
||||
#version 300 es
|
||||
|
||||
in vec4 position;
|
||||
|
||||
void main() {
|
||||
gl_Position = position;
|
||||
|
@ -5,9 +5,9 @@ import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
|
||||
|
||||
const LOG_TAG = 'WebGL2Player';
|
||||
|
||||
export class WebGL2Player {
|
||||
private readonly LOG_TAG = 'WebGL2Player';
|
||||
|
||||
private $video: HTMLVideoElement;
|
||||
private $canvas: HTMLCanvasElement;
|
||||
|
||||
@ -25,10 +25,14 @@ export class WebGL2Player {
|
||||
saturation: 0.0,
|
||||
};
|
||||
|
||||
private targetFps = 60;
|
||||
private frameInterval = 0;
|
||||
private lastFrameTime = 0;
|
||||
|
||||
private animFrameId: number | null = null;
|
||||
|
||||
constructor($video: HTMLVideoElement) {
|
||||
BxLogger.info(LOG_TAG, 'Initialize');
|
||||
BxLogger.info(this.LOG_TAG, 'Initialize');
|
||||
this.$video = $video;
|
||||
|
||||
const $canvas = document.createElement('canvas');
|
||||
@ -67,6 +71,12 @@ export class WebGL2Player {
|
||||
update && this.updateCanvas();
|
||||
}
|
||||
|
||||
setTargetFps(target: number) {
|
||||
this.targetFps = target;
|
||||
this.lastFrameTime = 0;
|
||||
this.frameInterval = target ? Math.floor(1000 / target) : 0;
|
||||
}
|
||||
|
||||
getCanvas() {
|
||||
return this.$canvas;
|
||||
}
|
||||
@ -85,10 +95,23 @@ export class WebGL2Player {
|
||||
}
|
||||
|
||||
drawFrame() {
|
||||
const gl = this.gl!;
|
||||
const $video = this.$video;
|
||||
// Don't draw when FPS is 0
|
||||
if (this.targetFps === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, $video);
|
||||
// Limit FPS
|
||||
if (this.targetFps < 60) {
|
||||
const currentTime = performance.now();
|
||||
const timeSinceLastFrame = currentTime - this.lastFrameTime;
|
||||
if (timeSinceLastFrame < this.frameInterval) {
|
||||
return;
|
||||
}
|
||||
this.lastFrameTime = currentTime;
|
||||
}
|
||||
|
||||
const gl = this.gl!;
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.$video);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
@ -98,23 +121,19 @@ export class WebGL2Player {
|
||||
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
|
||||
const $video = this.$video;
|
||||
animate = () => {
|
||||
if (this.stopped) {
|
||||
return;
|
||||
if (!this.stopped) {
|
||||
this.drawFrame();
|
||||
this.animFrameId = $video.requestVideoFrameCallback(animate);
|
||||
}
|
||||
|
||||
this.drawFrame();
|
||||
this.animFrameId = $video.requestVideoFrameCallback(animate);
|
||||
}
|
||||
|
||||
this.animFrameId = $video.requestVideoFrameCallback(animate);
|
||||
} else {
|
||||
animate = () => {
|
||||
if (this.stopped) {
|
||||
return;
|
||||
if (!this.stopped) {
|
||||
this.drawFrame();
|
||||
this.animFrameId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
this.drawFrame();
|
||||
this.animFrameId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
this.animFrameId = requestAnimationFrame(animate);
|
||||
@ -122,9 +141,9 @@ export class WebGL2Player {
|
||||
}
|
||||
|
||||
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('webgl', {
|
||||
const gl = this.$canvas.getContext('webgl2', {
|
||||
isBx: true,
|
||||
antialias: true,
|
||||
alpha: false,
|
||||
@ -165,14 +184,7 @@ export class WebGL2Player {
|
||||
this.resources.push(buffer);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
-1, -1,
|
||||
1, -1,
|
||||
-1, 1,
|
||||
-1, 1,
|
||||
1, -1,
|
||||
1, 1,
|
||||
]), gl.STATIC_DRAW);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
|
||||
|
||||
gl.enableVertexAttribArray(0);
|
||||
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
||||
@ -198,14 +210,14 @@ export class WebGL2Player {
|
||||
resume() {
|
||||
this.stop();
|
||||
this.stopped = false;
|
||||
BxLogger.info(LOG_TAG, 'Resume');
|
||||
BxLogger.info(this.LOG_TAG, 'Resume');
|
||||
|
||||
this.$canvas.classList.remove('bx-gone');
|
||||
this.setupRendering();
|
||||
}
|
||||
|
||||
stop() {
|
||||
BxLogger.info(LOG_TAG, 'Stop');
|
||||
BxLogger.info(this.LOG_TAG, 'Stop');
|
||||
this.$canvas.classList.add('bx-gone');
|
||||
|
||||
this.stopped = true;
|
||||
@ -221,16 +233,16 @@ export class WebGL2Player {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
BxLogger.info(LOG_TAG, 'Destroy');
|
||||
BxLogger.info(this.LOG_TAG, 'Destroy');
|
||||
this.stop();
|
||||
|
||||
const gl = this.gl;
|
||||
if (gl) {
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
gl.useProgram(null);
|
||||
|
||||
for (const resource of this.resources) {
|
||||
if (resource instanceof WebGLProgram) {
|
||||
gl.useProgram(null);
|
||||
gl.deleteProgram(resource);
|
||||
} else if (resource instanceof WebGLShader) {
|
||||
gl.deleteShader(resource);
|
||||
|
@ -37,13 +37,7 @@ type RemotePlayConsole = {
|
||||
|
||||
export class RemotePlayManager {
|
||||
private static instance: RemotePlayManager;
|
||||
public static getInstance(): RemotePlayManager {
|
||||
if (!this.instance) {
|
||||
this.instance = new RemotePlayManager();
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
public static getInstance = () => RemotePlayManager.instance ?? (RemotePlayManager.instance = new RemotePlayManager());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -50,13 +50,7 @@ enum StreamBadge {
|
||||
|
||||
export class StreamBadges {
|
||||
private static instance: StreamBadges;
|
||||
public static getInstance(): StreamBadges {
|
||||
if (!StreamBadges.instance) {
|
||||
StreamBadges.instance = new StreamBadges();
|
||||
}
|
||||
|
||||
return StreamBadges.instance;
|
||||
}
|
||||
public static getInstance = () => StreamBadges.instance ?? (StreamBadges.instance = new StreamBadges());
|
||||
|
||||
private serverInfo: StreamServerInfo = {};
|
||||
|
||||
|
@ -7,9 +7,10 @@ import { getPref, setPref } from "@/utils/settings-storages/global-settings-stor
|
||||
|
||||
export function onChangeVideoPlayerType() {
|
||||
const playerType = getPref(PrefKey.VIDEO_PLAYER_TYPE);
|
||||
const $videoProcessing = document.getElementById('bx_setting_video_processing') as HTMLSelectElement;
|
||||
const $videoSharpness = document.getElementById('bx_setting_video_sharpness') as HTMLElement;
|
||||
const $videoPowerPreference = document.getElementById('bx_setting_video_power_preference') as HTMLElement;
|
||||
const $videoProcessing = document.getElementById(`bx_setting_${PrefKey.VIDEO_PROCESSING}`) as HTMLSelectElement;
|
||||
const $videoSharpness = document.getElementById(`bx_setting_${PrefKey.VIDEO_SHARPNESS}`) as HTMLElement;
|
||||
const $videoPowerPreference = document.getElementById(`bx_setting_${PrefKey.VIDEO_POWER_PREFERENCE}`) as HTMLElement;
|
||||
const $videoMaxFps = document.getElementById(`bx_setting_${PrefKey.VIDEO_MAX_FPS}`) as HTMLElement;
|
||||
|
||||
if (!$videoProcessing) {
|
||||
return;
|
||||
@ -38,17 +39,26 @@ export function onChangeVideoPlayerType() {
|
||||
|
||||
// Hide Power Preference setting if renderer isn't WebGL2
|
||||
$videoPowerPreference.closest('.bx-settings-row')!.classList.toggle('bx-gone', playerType !== StreamPlayerType.WEBGL2);
|
||||
$videoMaxFps.closest('.bx-settings-row')!.classList.toggle('bx-gone', playerType !== StreamPlayerType.WEBGL2);
|
||||
|
||||
updateVideoPlayer();
|
||||
}
|
||||
|
||||
|
||||
export function limitVideoPlayerFps(targetFps: number) {
|
||||
const streamPlayer = STATES.currentStream.streamPlayer;
|
||||
streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps);
|
||||
}
|
||||
|
||||
|
||||
export function updateVideoPlayer() {
|
||||
const streamPlayer = STATES.currentStream.streamPlayer;
|
||||
if (!streamPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
limitVideoPlayerFps(getPref(PrefKey.VIDEO_MAX_FPS));
|
||||
|
||||
const options = {
|
||||
processing: getPref(PrefKey.VIDEO_PROCESSING),
|
||||
sharpness: getPref(PrefKey.VIDEO_SHARPNESS),
|
||||
@ -60,6 +70,7 @@ export function updateVideoPlayer() {
|
||||
streamPlayer.setPlayerType(getPref(PrefKey.VIDEO_PLAYER_TYPE));
|
||||
streamPlayer.updateOptions(options);
|
||||
streamPlayer.refreshPlayer();
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener('resize', updateVideoPlayer);
|
||||
|
@ -9,13 +9,7 @@ import { StreamStat, StreamStatsCollector, type StreamStatGrade } from "@/utils/
|
||||
|
||||
export class StreamStats {
|
||||
private static instance: StreamStats;
|
||||
public static getInstance(): StreamStats {
|
||||
if (!StreamStats.instance) {
|
||||
StreamStats.instance = new StreamStats();
|
||||
}
|
||||
|
||||
return StreamStats.instance;
|
||||
}
|
||||
public static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats());
|
||||
|
||||
private intervalId?: number | null;
|
||||
private readonly REFRESH_INTERVAL = 1 * 1000;
|
||||
|
@ -85,16 +85,24 @@ export class TouchController {
|
||||
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.remove('bx-offscreen');
|
||||
}
|
||||
|
||||
/*
|
||||
static #hide() {
|
||||
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.add('bx-offscreen');
|
||||
}
|
||||
*/
|
||||
|
||||
static toggleVisibility(status: boolean) {
|
||||
static toggleVisibility(): boolean {
|
||||
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() {
|
||||
|
@ -88,12 +88,7 @@ export abstract class NavigationDialog {
|
||||
|
||||
export class NavigationDialogManager {
|
||||
private static instance: NavigationDialogManager;
|
||||
public static getInstance(): NavigationDialogManager {
|
||||
if (!NavigationDialogManager.instance) {
|
||||
NavigationDialogManager.instance = new NavigationDialogManager();
|
||||
}
|
||||
return NavigationDialogManager.instance;
|
||||
}
|
||||
public static getInstance = () => NavigationDialogManager.instance ?? (NavigationDialogManager.instance = new NavigationDialogManager());
|
||||
|
||||
private static readonly GAMEPAD_POLLING_INTERVAL = 50;
|
||||
private static readonly GAMEPAD_KEYS = [
|
||||
|
@ -11,12 +11,7 @@ import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
export class RemotePlayNavigationDialog extends NavigationDialog {
|
||||
private static instance: RemotePlayNavigationDialog;
|
||||
public static getInstance(): RemotePlayNavigationDialog {
|
||||
if (!RemotePlayNavigationDialog.instance) {
|
||||
RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog();
|
||||
}
|
||||
return RemotePlayNavigationDialog.instance;
|
||||
}
|
||||
public static getInstance = () => RemotePlayNavigationDialog.instance ?? (RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog());
|
||||
|
||||
private readonly STATE_LABELS: Record<RemotePlayConsoleState, string> = {
|
||||
[RemotePlayConsoleState.ON]: t('powered-on'),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
||||
|
||||
import { onChangeVideoPlayerType, updateVideoPlayer } from "@/modules/stream/stream-settings-utils";
|
||||
import { limitVideoPlayerFps, onChangeVideoPlayerType, updateVideoPlayer } from "@/modules/stream/stream-settings-utils";
|
||||
import { ButtonStyle, CE, createButton, createSvgIcon, removeChildElements, type BxButton } from "@/utils/html";
|
||||
import { NavigationDialog, NavigationDirection } from "./navigation-dialog";
|
||||
import { ControllerShortcut } from "@/modules/controller-shortcut";
|
||||
@ -64,12 +64,7 @@ type SettingTab = {
|
||||
|
||||
export class SettingsNavigationDialog extends NavigationDialog {
|
||||
private static instance: SettingsNavigationDialog;
|
||||
public static getInstance(): SettingsNavigationDialog {
|
||||
if (!SettingsNavigationDialog.instance) {
|
||||
SettingsNavigationDialog.instance = new SettingsNavigationDialog();
|
||||
}
|
||||
return SettingsNavigationDialog.instance;
|
||||
}
|
||||
public static getInstance = () => SettingsNavigationDialog.instance ?? (SettingsNavigationDialog.instance = new SettingsNavigationDialog());
|
||||
|
||||
$container!: HTMLElement;
|
||||
private $tabs!: HTMLElement;
|
||||
@ -407,6 +402,11 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
items: [{
|
||||
pref: PrefKey.VIDEO_PLAYER_TYPE,
|
||||
onChange: onChangeVideoPlayerType,
|
||||
}, {
|
||||
pref: PrefKey.VIDEO_MAX_FPS,
|
||||
onChange: e => {
|
||||
limitVideoPlayerFps(parseInt(e.target.value));
|
||||
},
|
||||
}, {
|
||||
pref: PrefKey.VIDEO_POWER_PREFERENCE,
|
||||
onChange: () => {
|
||||
|
@ -2,12 +2,7 @@ import { CE } from "@/utils/html";
|
||||
|
||||
export class FullscreenText {
|
||||
private static instance: FullscreenText;
|
||||
public static getInstance(): FullscreenText {
|
||||
if (!FullscreenText.instance) {
|
||||
FullscreenText.instance = new FullscreenText();
|
||||
}
|
||||
return FullscreenText.instance;
|
||||
}
|
||||
public static getInstance = () => FullscreenText.instance ?? (FullscreenText.instance = new FullscreenText());
|
||||
|
||||
$text: HTMLElement;
|
||||
|
||||
|
@ -7,6 +7,8 @@ import iconCopy from "@assets/svg/copy.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 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 iconNativeMkb from "@assets/svg/native-mkb.svg" with { type: "text" };
|
||||
import iconNew from "@assets/svg/new.svg" with { type: "text" };
|
||||
@ -48,6 +50,8 @@ export const BxIcon = {
|
||||
CONTROLLER: iconController,
|
||||
CREATE_SHORTCUT: iconCreateShortcut,
|
||||
DISPLAY: iconDisplay,
|
||||
EYE: iconEye,
|
||||
EYE_SLASH: iconEyeSlash,
|
||||
HOME: iconHome,
|
||||
NATIVE_MKB: iconNativeMkb,
|
||||
NEW: iconNew,
|
||||
|
@ -5,22 +5,12 @@ const enum TextColor {
|
||||
}
|
||||
|
||||
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[]) {
|
||||
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 #log(color: string, tag: string, ...args: any) {
|
||||
console.log(`%c${BxLogger.#PREFIX}`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||
private static log(color: string, tag: string, ...args: any) {
|
||||
console.log(`%c[BxC]`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,10 @@ export const enum ControllerDeviceVibration {
|
||||
}
|
||||
|
||||
|
||||
export type GameBarPosition = 'bottom-left' | 'bottom-right' | 'off';
|
||||
export type GameBarPositionOptions = Record<GameBarPosition, string>;
|
||||
|
||||
|
||||
function getSupportedCodecProfiles() {
|
||||
const options: PartialRecord<CodecProfile, string> = {
|
||||
default: t('default'),
|
||||
@ -323,12 +327,12 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
[PrefKey.GAME_BAR_POSITION]: {
|
||||
requiredVariants: 'full',
|
||||
label: t('position'),
|
||||
default: 'bottom-left',
|
||||
default: 'bottom-left' satisfies GameBarPosition,
|
||||
options: {
|
||||
'bottom-left': t('bottom-left'),
|
||||
'bottom-right': t('bottom-right'),
|
||||
'off': t('off'),
|
||||
},
|
||||
} satisfies GameBarPositionOptions,
|
||||
},
|
||||
|
||||
[PrefKey.LOCAL_CO_OP_ENABLED]: {
|
||||
@ -616,6 +620,21 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
highest: 'low-power',
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_MAX_FPS]: {
|
||||
label: t('max-fps'),
|
||||
type: SettingElementType.NUMBER_STEPPER,
|
||||
default: 60,
|
||||
min: 10,
|
||||
max: 60,
|
||||
steps: 10,
|
||||
params: {
|
||||
exactTicks: 10,
|
||||
customTextValue: (value: any) => {
|
||||
value = parseInt(value);
|
||||
return value === 60 ? t('unlimited') : value + 'fps';
|
||||
},
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_SHARPNESS]: {
|
||||
label: t('sharpness'),
|
||||
type: SettingElementType.NUMBER_STEPPER,
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { STATES } from "./global";
|
||||
import { humanFileSize, secondsToHm } from "./html";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export enum StreamStat {
|
||||
PING = 'ping',
|
||||
@ -92,13 +94,7 @@ type CurrentStats = {
|
||||
|
||||
export class StreamStatsCollector {
|
||||
private static instance: StreamStatsCollector;
|
||||
public static getInstance(): StreamStatsCollector {
|
||||
if (!StreamStatsCollector.instance) {
|
||||
StreamStatsCollector.instance = new StreamStatsCollector();
|
||||
}
|
||||
|
||||
return StreamStatsCollector.instance;
|
||||
}
|
||||
public static getInstance = () => StreamStatsCollector.instance ?? (StreamStatsCollector.instance = new StreamStatsCollector());
|
||||
|
||||
// Collect in background - 60 seconds
|
||||
static readonly INTERVAL_BACKGROUND = 60 * 1000;
|
||||
@ -127,7 +123,8 @@ export class StreamStatsCollector {
|
||||
[StreamStat.FPS]: {
|
||||
current: 0,
|
||||
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 {
|
||||
static #$wrapper: HTMLElement;
|
||||
static #$msg: HTMLElement;
|
||||
static #$status: HTMLElement;
|
||||
static #stack: Array<[string, string, ToastOptions]> = [];
|
||||
static #isShowing = false;
|
||||
private static $wrapper: HTMLElement;
|
||||
private static $msg: HTMLElement;
|
||||
private static $status: HTMLElement;
|
||||
private static stack: Array<[string, string, ToastOptions]> = [];
|
||||
private static isShowing = false;
|
||||
|
||||
static #timeout?: number | null;
|
||||
static #DURATION = 3000;
|
||||
private static timeout?: number | null;
|
||||
private static DURATION = 3000;
|
||||
|
||||
static show(msg: string, status?: string, options: Partial<ToastOptions> = {}) {
|
||||
options = options || {};
|
||||
@ -21,69 +21,70 @@ export class Toast {
|
||||
const args = Array.from(arguments) as [string, string, ToastOptions];
|
||||
if (options.instant) {
|
||||
// Clear stack
|
||||
Toast.#stack = [args];
|
||||
Toast.#showNext();
|
||||
Toast.stack = [args];
|
||||
Toast.showNext();
|
||||
} else {
|
||||
Toast.#stack.push(args);
|
||||
!Toast.#isShowing && Toast.#showNext();
|
||||
Toast.stack.push(args);
|
||||
!Toast.isShowing && Toast.showNext();
|
||||
}
|
||||
}
|
||||
|
||||
static #showNext() {
|
||||
if (!Toast.#stack.length) {
|
||||
Toast.#isShowing = false;
|
||||
private static showNext() {
|
||||
if (!Toast.stack.length) {
|
||||
Toast.isShowing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.#isShowing = true;
|
||||
Toast.isShowing = true;
|
||||
|
||||
Toast.#timeout && clearTimeout(Toast.#timeout);
|
||||
Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
|
||||
Toast.timeout && clearTimeout(Toast.timeout);
|
||||
Toast.timeout = window.setTimeout(Toast.hide, Toast.DURATION);
|
||||
|
||||
// Get values from item
|
||||
const [msg, status, options] = Toast.#stack.shift()!;
|
||||
const [msg, status, options] = Toast.stack.shift()!;
|
||||
|
||||
if (options && options.html) {
|
||||
Toast.#$msg.innerHTML = msg;
|
||||
Toast.$msg.innerHTML = msg;
|
||||
} else {
|
||||
Toast.#$msg.textContent = msg;
|
||||
Toast.$msg.textContent = msg;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
Toast.#$status.classList.remove('bx-gone');
|
||||
Toast.#$status.textContent = status;
|
||||
Toast.$status.classList.remove('bx-gone');
|
||||
Toast.$status.textContent = status;
|
||||
} 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.add('bx-show');
|
||||
}
|
||||
|
||||
static #hide() {
|
||||
Toast.#timeout = null;
|
||||
private static hide() {
|
||||
Toast.timeout = null;
|
||||
|
||||
const classList = Toast.#$wrapper.classList;
|
||||
const classList = Toast.$wrapper.classList;
|
||||
classList.remove('bx-show');
|
||||
classList.add('bx-hide');
|
||||
}
|
||||
|
||||
static setup() {
|
||||
Toast.#$wrapper = CE('div', {'class': 'bx-toast bx-offscreen'},
|
||||
Toast.#$msg = CE('span', {'class': 'bx-toast-msg'}),
|
||||
Toast.#$status = CE('span', {'class': 'bx-toast-status'}));
|
||||
Toast.$wrapper = CE('div', {'class': 'bx-toast bx-offscreen'},
|
||||
Toast.$msg = CE('span', {'class': 'bx-toast-msg'}),
|
||||
Toast.$status = CE('span', {'class': 'bx-toast-status'}),
|
||||
);
|
||||
|
||||
Toast.#$wrapper.addEventListener('transitionend', e => {
|
||||
const classList = Toast.#$wrapper.classList;
|
||||
Toast.$wrapper.addEventListener('transitionend', e => {
|
||||
const classList = Toast.$wrapper.classList;
|
||||
if (classList.contains('bx-hide')) {
|
||||
classList.remove('bx-offscreen', 'bx-hide');
|
||||
classList.add('bx-offscreen');
|
||||
|
||||
Toast.#showNext();
|
||||
Toast.showNext();
|
||||
}
|
||||
});
|
||||
|
||||
document.documentElement.appendChild(Toast.#$wrapper);
|
||||
document.documentElement.appendChild(Toast.$wrapper);
|
||||
}
|
||||
}
|
||||
|
@ -143,6 +143,7 @@ const Texts = {
|
||||
"local-co-op": "Local co-op",
|
||||
"lowest-quality": "Lowest quality",
|
||||
"map-mouse-to": "Map mouse to",
|
||||
"max-fps": "Max FPS",
|
||||
"may-not-work-properly": "May not work properly!",
|
||||
"menu": "Menu",
|
||||
"microphone": "Microphone",
|
||||
|
@ -3,21 +3,14 @@ import { STATES } from "./global";
|
||||
|
||||
export class XcloudApi {
|
||||
private static instance: XcloudApi;
|
||||
public static getInstance = () => XcloudApi.instance ?? (XcloudApi.instance = new XcloudApi());
|
||||
|
||||
public static getInstance(): XcloudApi {
|
||||
if (!XcloudApi.instance) {
|
||||
XcloudApi.instance = new XcloudApi();
|
||||
}
|
||||
|
||||
return XcloudApi.instance;
|
||||
}
|
||||
|
||||
#CACHE_TITLES: {[key: string]: XcloudTitleInfo} = {};
|
||||
#CACHE_WAIT_TIME: {[key: string]: XcloudWaitTimeInfo} = {};
|
||||
private CACHE_TITLES: {[key: string]: XcloudTitleInfo} = {};
|
||||
private CACHE_WAIT_TIME: {[key: string]: XcloudWaitTimeInfo} = {};
|
||||
|
||||
async getTitleInfo(id: string): Promise<XcloudTitleInfo | null> {
|
||||
if (id in this.#CACHE_TITLES) {
|
||||
return this.#CACHE_TITLES[id];
|
||||
if (id in this.CACHE_TITLES) {
|
||||
return this.CACHE_TITLES[id];
|
||||
}
|
||||
|
||||
const baseUri = STATES.selectedRegion.baseUri;
|
||||
@ -45,13 +38,13 @@ export class XcloudApi {
|
||||
} catch (e) {
|
||||
json = {}
|
||||
}
|
||||
this.#CACHE_TITLES[id] = json;
|
||||
this.CACHE_TITLES[id] = json;
|
||||
return json;
|
||||
}
|
||||
|
||||
async getWaitTime(id: string): Promise<XcloudWaitTimeInfo | null> {
|
||||
if (id in this.#CACHE_WAIT_TIME) {
|
||||
return this.#CACHE_WAIT_TIME[id];
|
||||
if (id in this.CACHE_WAIT_TIME) {
|
||||
return this.CACHE_WAIT_TIME[id];
|
||||
}
|
||||
|
||||
const baseUri = STATES.selectedRegion.baseUri;
|
||||
@ -73,7 +66,7 @@ export class XcloudApi {
|
||||
json = {};
|
||||
}
|
||||
|
||||
this.#CACHE_WAIT_TIME[id] = json;
|
||||
this.CACHE_WAIT_TIME[id] = json;
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,25 @@ import { BypassServerIps } from "@/enums/bypass-servers";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export
|
||||
class XcloudInterceptor {
|
||||
static async #handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
||||
export class XcloudInterceptor {
|
||||
private static readonly SERVER_EMOJIS = {
|
||||
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);
|
||||
if (bypassServer !== 'off') {
|
||||
const ip = BypassServerIps[bypassServer as keyof typeof BypassServerIps];
|
||||
@ -35,24 +51,8 @@ class XcloudInterceptor {
|
||||
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
||||
|
||||
// Get server list
|
||||
const serverEmojis = {
|
||||
AustraliaEast: '🇦🇺',
|
||||
AustraliaSouthEast: '🇦🇺',
|
||||
BrazilSouth: '🇧🇷',
|
||||
EastUS: '🇺🇸',
|
||||
EastUS2: '🇺🇸',
|
||||
JapanEast: '🇯🇵',
|
||||
KoreaCentral: '🇰🇷',
|
||||
MexicoCentral: '🇲🇽',
|
||||
NorthCentralUs: '🇺🇸',
|
||||
SouthCentralUS: '🇺🇸',
|
||||
UKSouth: '🇬🇧',
|
||||
WestEurope: '🇪🇺',
|
||||
WestUS: '🇺🇸',
|
||||
WestUS2: '🇺🇸',
|
||||
};
|
||||
|
||||
const serverRegex = /\/\/(\w+)\./;
|
||||
const serverEmojis = XcloudInterceptor.SERVER_EMOJIS;
|
||||
|
||||
for (let region of obj.offeringSettings.regions) {
|
||||
const regionName = region.name as keyof typeof serverEmojis;
|
||||
@ -91,7 +91,7 @@ class XcloudInterceptor {
|
||||
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_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE);
|
||||
|
||||
@ -129,7 +129,7 @@ class XcloudInterceptor {
|
||||
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);
|
||||
|
||||
if (getPref(PrefKey.UI_LOADING_SCREEN_WAIT_TIME)) {
|
||||
@ -143,7 +143,7 @@ class XcloudInterceptor {
|
||||
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') {
|
||||
return NATIVE_FETCH(request, init);
|
||||
}
|
||||
@ -213,13 +213,13 @@ class XcloudInterceptor {
|
||||
|
||||
// Server list
|
||||
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
|
||||
return XcloudInterceptor.#handlePlay(request, init);
|
||||
return XcloudInterceptor.handlePlay(request, init);
|
||||
} else if (url.includes('xboxlive.com') && url.includes('/waittime/')) {
|
||||
return XcloudInterceptor.#handleWaitTime(request, init);
|
||||
return XcloudInterceptor.handleWaitTime(request, init);
|
||||
} 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') {
|
||||
return patchIceCandidates(request as Request);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
|
||||
export class XhomeInterceptor {
|
||||
static #consoleAddrs: RemotePlayConsoleAddresses = {};
|
||||
private static consoleAddrs: RemotePlayConsoleAddresses = {};
|
||||
|
||||
private static readonly BASE_DEVICE_INFO = {
|
||||
appInfo: {
|
||||
@ -52,7 +52,7 @@ export class XhomeInterceptor {
|
||||
},
|
||||
};
|
||||
|
||||
static async #handleLogin(request: Request) {
|
||||
private static async handleLogin(request: Request) {
|
||||
try {
|
||||
const clone = (request as Request).clone();
|
||||
|
||||
@ -74,7 +74,7 @@ export class XhomeInterceptor {
|
||||
return NATIVE_FETCH(request);
|
||||
}
|
||||
|
||||
static async #handleConfiguration(request: Request | URL) {
|
||||
private static async handleConfiguration(request: Request | URL) {
|
||||
const response = await NATIVE_FETCH(request);
|
||||
|
||||
const obj = await response.clone().json()
|
||||
@ -90,15 +90,15 @@ export class XhomeInterceptor {
|
||||
|
||||
const serverDetails = obj.serverDetails;
|
||||
if (serverDetails.ipAddress) {
|
||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
||||
XhomeInterceptor.consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
||||
}
|
||||
|
||||
if (serverDetails.ipV4Address) {
|
||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
||||
XhomeInterceptor.consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
||||
}
|
||||
|
||||
if (serverDetails.ipV6Address) {
|
||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
||||
XhomeInterceptor.consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
||||
}
|
||||
|
||||
response.json = () => Promise.resolve(obj);
|
||||
@ -107,7 +107,7 @@ export class XhomeInterceptor {
|
||||
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);
|
||||
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.ALL) {
|
||||
@ -144,7 +144,7 @@ export class XhomeInterceptor {
|
||||
return response;
|
||||
}
|
||||
|
||||
static async #handleTitles(request: Request) {
|
||||
private static async handleTitles(request: Request) {
|
||||
const clone = request.clone();
|
||||
|
||||
const headers: {[index: string]: any} = {};
|
||||
@ -163,7 +163,7 @@ export class XhomeInterceptor {
|
||||
return NATIVE_FETCH(request);
|
||||
}
|
||||
|
||||
static async #handlePlay(request: RequestInfo | URL) {
|
||||
private static async handlePlay(request: RequestInfo | URL) {
|
||||
const clone = (request as Request).clone();
|
||||
const body = await clone.json();
|
||||
|
||||
@ -216,17 +216,17 @@ export class XhomeInterceptor {
|
||||
|
||||
// Get console IP
|
||||
if (url.includes('/configuration')) {
|
||||
return XhomeInterceptor.#handleConfiguration(request);
|
||||
return XhomeInterceptor.handleConfiguration(request);
|
||||
} else if (url.endsWith('/sessions/home/play')) {
|
||||
return XhomeInterceptor.#handlePlay(request);
|
||||
return XhomeInterceptor.handlePlay(request);
|
||||
} else if (url.includes('inputconfigs')) {
|
||||
return XhomeInterceptor.#handleInputConfigs(request, opts);
|
||||
return XhomeInterceptor.handleInputConfigs(request, opts);
|
||||
} else if (url.includes('/login/user')) {
|
||||
return XhomeInterceptor.#handleLogin(request);
|
||||
return XhomeInterceptor.handleLogin(request);
|
||||
} 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') {
|
||||
return patchIceCandidates(request, XhomeInterceptor.#consoleAddrs);
|
||||
return patchIceCandidates(request, XhomeInterceptor.consoleAddrs);
|
||||
}
|
||||
|
||||
return await NATIVE_FETCH(request);
|
||||
|
Reference in New Issue
Block a user