mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-02 03:16:42 +02:00
Add "Suggest settings" feature
This commit is contained in:
@@ -5,7 +5,7 @@ import { BxLogger } from "./bx-logger";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { getPref, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export enum SupportedInputType {
|
||||
CONTROLLER = 'Controller',
|
||||
@@ -41,7 +41,7 @@ export const BxExposed = {
|
||||
let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
|
||||
|
||||
// Disable touch control when gamepad found
|
||||
if (touchControllerAvailability !== 'off' && getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) {
|
||||
if (touchControllerAvailability !== StreamTouchController.OFF && getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) {
|
||||
const gamepads = window.navigator.getGamepads();
|
||||
let gamepadFound = false;
|
||||
|
||||
@@ -52,10 +52,10 @@ export const BxExposed = {
|
||||
}
|
||||
}
|
||||
|
||||
gamepadFound && (touchControllerAvailability = 'off');
|
||||
gamepadFound && (touchControllerAvailability = StreamTouchController.OFF);
|
||||
}
|
||||
|
||||
if (touchControllerAvailability === 'off') {
|
||||
if (touchControllerAvailability === StreamTouchController.OFF) {
|
||||
// Disable touch on all games (not native touch)
|
||||
supportedInputTypes = supportedInputTypes.filter(i => i !== SupportedInputType.CUSTOM_TOUCH_OVERLAY && i !== SupportedInputType.GENERIC_TOUCH);
|
||||
// Empty TABs
|
||||
@@ -68,7 +68,7 @@ export const BxExposed = {
|
||||
supportedInputTypes.includes(SupportedInputType.CUSTOM_TOUCH_OVERLAY) ||
|
||||
supportedInputTypes.includes(SupportedInputType.GENERIC_TOUCH);
|
||||
|
||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === 'all') {
|
||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === StreamTouchController.ALL) {
|
||||
// Add generic touch support for non touch-supported games
|
||||
titleInfo.details.hasFakeTouchSupport = true;
|
||||
supportedInputTypes.push(SupportedInputType.GENERIC_TOUCH);
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { BxLogger } from "./bx-logger";
|
||||
|
||||
type BxFlags = {
|
||||
CheckForUpdate: boolean;
|
||||
EnableXcloudLogging: boolean;
|
||||
@@ -7,8 +9,12 @@ type BxFlags = {
|
||||
FeatureGates: {[key: string]: boolean} | null,
|
||||
|
||||
DeviceInfo: {
|
||||
deviceType: 'android' | 'android-tv' | 'webos' | 'unknown',
|
||||
deviceType: 'android' | 'android-tv' | 'android-handheld' | 'webos' | 'unknown',
|
||||
userAgent?: string,
|
||||
|
||||
androidInfo?: {
|
||||
board: string,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +41,6 @@ if (!BX_FLAGS.DeviceInfo.userAgent) {
|
||||
BX_FLAGS.DeviceInfo.userAgent = window.navigator.userAgent;
|
||||
}
|
||||
|
||||
BxLogger.info('BxFlags', BX_FLAGS);
|
||||
|
||||
export const NATIVE_FETCH = window.fetch;
|
||||
|
@@ -2,8 +2,36 @@ import type { BxIcon } from "@utils/bx-icon";
|
||||
import { setNearby } from "./navigation-utils";
|
||||
import type { NavigationNearbyElements } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
export enum ButtonStyle {
|
||||
PRIMARY = 1,
|
||||
DANGER = 2,
|
||||
GHOST = 4,
|
||||
FROSTED = 8,
|
||||
DROP_SHADOW = 16,
|
||||
FOCUSABLE = 32,
|
||||
FULL_WIDTH = 64,
|
||||
FULL_HEIGHT = 128,
|
||||
TALL = 256,
|
||||
CIRCULAR = 512,
|
||||
NORMAL_CASE = 1024,
|
||||
}
|
||||
|
||||
const ButtonStyleClass = {
|
||||
[ButtonStyle.PRIMARY]: 'bx-primary',
|
||||
[ButtonStyle.DANGER]: 'bx-danger',
|
||||
[ButtonStyle.GHOST]: 'bx-ghost',
|
||||
[ButtonStyle.FROSTED]: 'bx-frosted',
|
||||
[ButtonStyle.DROP_SHADOW]: 'bx-drop-shadow',
|
||||
[ButtonStyle.FOCUSABLE]: 'bx-focusable',
|
||||
[ButtonStyle.FULL_WIDTH]: 'bx-full-width',
|
||||
[ButtonStyle.FULL_HEIGHT]: 'bx-full-height',
|
||||
[ButtonStyle.TALL]: 'bx-tall',
|
||||
[ButtonStyle.CIRCULAR]: 'bx-circular',
|
||||
[ButtonStyle.NORMAL_CASE]: 'bx-normal-case',
|
||||
}
|
||||
|
||||
type BxButton = {
|
||||
style?: number | string | ButtonStyle;
|
||||
style?: ButtonStyle;
|
||||
url?: string;
|
||||
classes?: string[];
|
||||
icon?: typeof BxIcon;
|
||||
@@ -15,8 +43,6 @@ type BxButton = {
|
||||
attributes?: {[key: string]: any},
|
||||
}
|
||||
|
||||
type ButtonStyle = {[index: string]: number} & {[index: number]: string};
|
||||
|
||||
// Quickly create a tree of elements without having to use innerHTML
|
||||
type CreateElementOptions = {
|
||||
[index: string]: any;
|
||||
@@ -80,20 +106,7 @@ export const createSvgIcon = (icon: typeof BxIcon) => {
|
||||
return svgParser(icon.toString());
|
||||
}
|
||||
|
||||
export const ButtonStyle: DualEnum = {};
|
||||
ButtonStyle[ButtonStyle.PRIMARY = 1] = 'bx-primary';
|
||||
ButtonStyle[ButtonStyle.DANGER = 2] = 'bx-danger';
|
||||
ButtonStyle[ButtonStyle.GHOST = 4] = 'bx-ghost';
|
||||
ButtonStyle[ButtonStyle.FROSTED = 8] = 'bx-frosted';
|
||||
ButtonStyle[ButtonStyle.DROP_SHADOW = 16] = 'bx-drop-shadow';
|
||||
ButtonStyle[ButtonStyle.FOCUSABLE = 32] = 'bx-focusable';
|
||||
ButtonStyle[ButtonStyle.FULL_WIDTH = 64] = 'bx-full-width';
|
||||
ButtonStyle[ButtonStyle.FULL_HEIGHT = 128] = 'bx-full-height';
|
||||
ButtonStyle[ButtonStyle.TALL = 256] = 'bx-tall';
|
||||
ButtonStyle[ButtonStyle.CIRCULAR = 512] = 'bx-circular';
|
||||
ButtonStyle[ButtonStyle.NORMAL_CASE = 1024] = 'bx-normal-case';
|
||||
|
||||
const ButtonStyleIndices = Object.keys(ButtonStyle).splice(0, Object.keys(ButtonStyle).length / 2).map(i => parseInt(i));
|
||||
const ButtonStyleIndices = Object.keys(ButtonStyleClass).map(i => parseInt(i));
|
||||
|
||||
export const createButton = <T=HTMLButtonElement>(options: BxButton): T => {
|
||||
let $btn;
|
||||
@@ -106,8 +119,8 @@ export const createButton = <T=HTMLButtonElement>(options: BxButton): T => {
|
||||
}
|
||||
|
||||
const style = (options.style || 0) as number;
|
||||
style && ButtonStyleIndices.forEach(index => {
|
||||
(style & index) && $btn.classList.add(ButtonStyle[index] as string);
|
||||
style && ButtonStyleIndices.forEach((index: keyof typeof ButtonStyleClass) => {
|
||||
(style & index) && $btn.classList.add(ButtonStyleClass[index] as string);
|
||||
});
|
||||
|
||||
options.classes && $btn.classList.add(...options.classes);
|
||||
@@ -153,3 +166,9 @@ export function isElementVisible($elm: HTMLElement): boolean {
|
||||
|
||||
export const CTN = document.createTextNode.bind(document);
|
||||
window.BX_CE = createElement;
|
||||
|
||||
export function removeChildElements($parent: HTMLElement) {
|
||||
while ($parent.firstElementChild) {
|
||||
$parent.firstElementChild.remove();
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import { setNearby } from "./navigation-utils";
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { BaseSettingsStore } from "./settings-storages/base-settings-storage";
|
||||
import { type MultipleOptionsParams, type NumberStepperParams } from "@/types/setting-definition";
|
||||
import { BxEvent } from "./bx-event";
|
||||
|
||||
export enum SettingElementType {
|
||||
OPTIONS = 'options',
|
||||
@@ -13,16 +14,26 @@ export enum SettingElementType {
|
||||
CHECKBOX = 'checkbox',
|
||||
}
|
||||
|
||||
interface BxBaseSettingElement {
|
||||
setValue: (value: any) => void,
|
||||
}
|
||||
|
||||
export interface BxHtmlSettingElement extends HTMLElement, BxBaseSettingElement {};
|
||||
|
||||
export interface BxSelectSettingElement extends HTMLSelectElement, BxBaseSettingElement {}
|
||||
|
||||
export class SettingElement {
|
||||
static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
||||
const $control = CE<HTMLSelectElement>('select', {
|
||||
static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any): BxSelectSettingElement {
|
||||
const $control = CE<BxSelectSettingElement>('select', {
|
||||
// title: setting.label,
|
||||
tabindex: 0,
|
||||
}) as HTMLSelectElement;
|
||||
});
|
||||
|
||||
let $parent: HTMLElement;
|
||||
if (setting.optionsGroup) {
|
||||
$parent = CE('optgroup', {'label': setting.optionsGroup});
|
||||
$parent = CE('optgroup', {
|
||||
label: setting.optionsGroup,
|
||||
});
|
||||
$control.appendChild($parent);
|
||||
} else {
|
||||
$parent = $control;
|
||||
@@ -44,19 +55,20 @@ export class SettingElement {
|
||||
});
|
||||
|
||||
// Custom method
|
||||
($control as any).setValue = (value: any) => {
|
||||
$control.setValue = (value: any) => {
|
||||
$control.value = value;
|
||||
};
|
||||
|
||||
return $control;
|
||||
}
|
||||
|
||||
static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}) {
|
||||
const $control = CE<HTMLSelectElement>('select', {
|
||||
static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}): BxSelectSettingElement {
|
||||
const $control = CE<BxSelectSettingElement>('select', {
|
||||
// title: setting.label,
|
||||
multiple: true,
|
||||
tabindex: 0,
|
||||
});
|
||||
|
||||
if (params && params.size) {
|
||||
$control.setAttribute('size', params.size.toString());
|
||||
}
|
||||
@@ -75,7 +87,7 @@ export class SettingElement {
|
||||
|
||||
const $parent = target.parentElement!;
|
||||
$parent.focus();
|
||||
$parent.dispatchEvent(new Event('input'));
|
||||
BxEvent.dispatch($parent, 'input');
|
||||
});
|
||||
|
||||
$control.appendChild($option);
|
||||
@@ -100,9 +112,15 @@ export class SettingElement {
|
||||
}
|
||||
|
||||
static #renderNumber(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
||||
const $control = CE('input', {'tabindex': 0, 'type': 'number', 'min': setting.min, 'max': setting.max}) as HTMLInputElement;
|
||||
const $control = CE<HTMLInputElement>('input', {
|
||||
tabindex: 0,
|
||||
type: 'number',
|
||||
min: setting.min,
|
||||
max: setting.max,
|
||||
});
|
||||
|
||||
$control.value = currentValue;
|
||||
onChange && $control.addEventListener('change', (e: Event) => {
|
||||
onChange && $control.addEventListener('input', (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
const value = Math.max(setting.min!, Math.min(setting.max!, parseInt(target.value)));
|
||||
@@ -118,7 +136,7 @@ export class SettingElement {
|
||||
const $control = CE('input', {'type': 'checkbox', 'tabindex': 0}) as HTMLInputElement;
|
||||
$control.checked = currentValue;
|
||||
|
||||
onChange && $control.addEventListener('change', e => {
|
||||
onChange && $control.addEventListener('input', e => {
|
||||
!(e as any).ignoreOnChange && onChange(e, (e.target as HTMLInputElement).checked);
|
||||
});
|
||||
|
||||
@@ -162,77 +180,21 @@ export class SettingElement {
|
||||
$btnInc.classList.toggle('bx-inactive', controlValue === MAX);
|
||||
}
|
||||
|
||||
const $wrapper = CE('div', {'class': 'bx-number-stepper', id: `bx_setting_${key}`},
|
||||
$btnDec = CE('button', {
|
||||
'data-type': 'dec',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '-') as HTMLButtonElement,
|
||||
$text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement,
|
||||
$btnInc = CE('button', {
|
||||
'data-type': 'inc',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '+') as HTMLButtonElement,
|
||||
);
|
||||
|
||||
if (options.disabled) {
|
||||
($wrapper as any).disabled = true;
|
||||
}
|
||||
|
||||
if (!options.disabled && !options.hideSlider) {
|
||||
$range = CE('input', {
|
||||
id: `bx_setting_${key}`,
|
||||
type: 'range',
|
||||
min: MIN,
|
||||
max: MAX,
|
||||
value: value,
|
||||
step: STEPS,
|
||||
tabindex: 0,
|
||||
}) as HTMLInputElement;
|
||||
|
||||
$range.addEventListener('input', e => {
|
||||
value = parseInt((e.target as HTMLInputElement).value);
|
||||
const valueChanged = controlValue !== value;
|
||||
|
||||
if (!valueChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
controlValue = value;
|
||||
updateButtonsVisibility();
|
||||
$text.textContent = renderTextValue(value);
|
||||
|
||||
!(e as any).ignoreOnChange && onChange && onChange(e, value);
|
||||
});
|
||||
|
||||
$wrapper.appendChild($range);
|
||||
|
||||
if (options.ticks || options.exactTicks) {
|
||||
const markersId = `markers-${key}`;
|
||||
const $markers = CE('datalist', {'id': markersId});
|
||||
$range.setAttribute('list', markersId);
|
||||
|
||||
if (options.exactTicks) {
|
||||
let start = Math.max(Math.floor(MIN / options.exactTicks), 1) * options.exactTicks;
|
||||
|
||||
if (start === MIN) {
|
||||
start += options.exactTicks;
|
||||
}
|
||||
|
||||
for (let i = start; i < MAX; i += options.exactTicks) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
} else {
|
||||
for (let i = MIN + options.ticks!; i < MAX; i += options.ticks!) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
}
|
||||
$wrapper.appendChild($markers);
|
||||
}
|
||||
}
|
||||
const $wrapper = CE<BxHtmlSettingElement>('div', {'class': 'bx-number-stepper', id: `bx_setting_${key}`},
|
||||
$btnDec = CE('button', {
|
||||
'data-type': 'dec',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '-') as HTMLButtonElement,
|
||||
$text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement,
|
||||
$btnInc = CE('button', {
|
||||
'data-type': 'inc',
|
||||
type: 'button',
|
||||
class: options.hideSlider ? 'bx-focusable' : '',
|
||||
tabindex: options.hideSlider ? 0 : -1,
|
||||
}, '+') as HTMLButtonElement,
|
||||
);
|
||||
|
||||
if (options.disabled) {
|
||||
$btnInc.disabled = true;
|
||||
@@ -240,9 +202,66 @@ export class SettingElement {
|
||||
|
||||
$btnDec.disabled = true;
|
||||
$btnDec.classList.add('bx-inactive');
|
||||
|
||||
($wrapper as any).disabled = true;
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
$range = CE<HTMLInputElement>('input', {
|
||||
id: `bx_setting_${key}`,
|
||||
type: 'range',
|
||||
min: MIN,
|
||||
max: MAX,
|
||||
value: value,
|
||||
step: STEPS,
|
||||
tabindex: 0,
|
||||
});
|
||||
|
||||
options.hideSlider && $range.classList.add('bx-gone');
|
||||
|
||||
$range.addEventListener('input', e => {
|
||||
value = parseInt((e.target as HTMLInputElement).value);
|
||||
const valueChanged = controlValue !== value;
|
||||
|
||||
if (!valueChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
controlValue = value;
|
||||
updateButtonsVisibility();
|
||||
$text.textContent = renderTextValue(value);
|
||||
|
||||
!(e as any).ignoreOnChange && onChange && onChange(e, value);
|
||||
});
|
||||
|
||||
$wrapper.addEventListener('input', e => {
|
||||
BxEvent.dispatch($range, 'input');
|
||||
});
|
||||
$wrapper.appendChild($range);
|
||||
|
||||
if (options.ticks || options.exactTicks) {
|
||||
const markersId = `markers-${key}`;
|
||||
const $markers = CE('datalist', {'id': markersId});
|
||||
$range.setAttribute('list', markersId);
|
||||
|
||||
if (options.exactTicks) {
|
||||
let start = Math.max(Math.floor(MIN / options.exactTicks), 1) * options.exactTicks;
|
||||
|
||||
if (start === MIN) {
|
||||
start += options.exactTicks;
|
||||
}
|
||||
|
||||
for (let i = start; i < MAX; i += options.exactTicks) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
} else {
|
||||
for (let i = MIN + options.ticks!; i < MAX; i += options.ticks!) {
|
||||
$markers.appendChild(CE<HTMLOptionElement>('option', {'value': i}));
|
||||
}
|
||||
}
|
||||
$wrapper.appendChild($markers);
|
||||
}
|
||||
|
||||
updateButtonsVisibility();
|
||||
|
||||
let interval: number;
|
||||
@@ -278,16 +297,14 @@ export class SettingElement {
|
||||
|
||||
const onMouseDown = (e: PointerEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
isHolding = true;
|
||||
|
||||
const args = arguments;
|
||||
interval && clearInterval(interval);
|
||||
interval = window.setInterval(() => {
|
||||
const event = new Event('click');
|
||||
(event as any).arguments = args;
|
||||
|
||||
e.target?.dispatchEvent(event);
|
||||
e.target && BxEvent.dispatch(e.target as HTMLElement, 'click', {
|
||||
arguments: args,
|
||||
});
|
||||
}, 200);
|
||||
};
|
||||
|
||||
@@ -301,11 +318,9 @@ export class SettingElement {
|
||||
const onContextMenu = (e: Event) => e.preventDefault();
|
||||
|
||||
// Custom method
|
||||
($wrapper as any).setValue = (value: any) => {
|
||||
controlValue = parseInt(value);
|
||||
|
||||
$wrapper.setValue = (value: any) => {
|
||||
$text.textContent = renderTextValue(value);
|
||||
$range && ($range.value = value);
|
||||
$range.value = value;
|
||||
};
|
||||
|
||||
$btnDec.addEventListener('click', onClick);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { SettingDefinitions } from "@/types/setting-definition";
|
||||
import type { NumberStepperParams, SettingDefinitions } from "@/types/setting-definition";
|
||||
import { BxEvent } from "../bx-event";
|
||||
import { SettingElementType } from "../setting-element";
|
||||
|
||||
export class BaseSettingsStore {
|
||||
private storage: Storage;
|
||||
@@ -12,7 +13,8 @@ export class BaseSettingsStore {
|
||||
this.storage = window.localStorage;
|
||||
this.storageKey = storageKey;
|
||||
|
||||
for (const settingId in definitions) {
|
||||
let settingId: keyof typeof definitions
|
||||
for (settingId in definitions) {
|
||||
const setting = definitions[settingId];
|
||||
|
||||
/*
|
||||
@@ -49,14 +51,14 @@ export class BaseSettingsStore {
|
||||
return this.definitions[key];
|
||||
}
|
||||
|
||||
getSetting(key: PrefKey) {
|
||||
getSetting(key: PrefKey, checkUnsupported = true) {
|
||||
if (typeof key === 'undefined') {
|
||||
debugger;
|
||||
return;
|
||||
}
|
||||
|
||||
// Return default value if the feature is not supported
|
||||
if (this.definitions[key].unsupported) {
|
||||
if (checkUnsupported && this.definitions[key].unsupported) {
|
||||
return this.definitions[key].default;
|
||||
}
|
||||
|
||||
@@ -121,4 +123,30 @@ export class BaseSettingsStore {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
getLabel(key: PrefKey): string {
|
||||
return this.definitions[key].label || key;
|
||||
}
|
||||
|
||||
getValueText(key: PrefKey, value: any): string {
|
||||
const definition = this.definitions[key];
|
||||
if (definition.type === SettingElementType.NUMBER_STEPPER) {
|
||||
const params = (definition as any).params as NumberStepperParams;
|
||||
if (params.customTextValue) {
|
||||
const text = params.customTextValue(value);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
} else if ('options' in definition) {
|
||||
const options = (definition as any).options;
|
||||
if (value in options) {
|
||||
return options[value];
|
||||
}
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,7 @@ import { StreamPlayerType, StreamVideoProcessing } from "@/enums/stream-player";
|
||||
import { UiSection } from "@/enums/ui-sections";
|
||||
import { UserAgentProfile } from "@/enums/user-agent";
|
||||
import { StreamStat } from "@/modules/stream/stream-stats";
|
||||
import type { PreferenceSetting } from "@/types/preferences";
|
||||
import { type SettingDefinitions } from "@/types/setting-definition";
|
||||
import { type SettingDefinition, type SettingDefinitions } from "@/types/setting-definition";
|
||||
import { BX_FLAGS } from "../bx-flags";
|
||||
import { STATES, AppInterface, STORAGE } from "../global";
|
||||
import { CE } from "../html";
|
||||
@@ -15,8 +14,33 @@ import { BaseSettingsStore as BaseSettingsStorage } from "./base-settings-storag
|
||||
import { SettingElementType } from "../setting-element";
|
||||
|
||||
|
||||
export const enum StreamResolution {
|
||||
DIM_720P = '720p',
|
||||
DIM_1080P = '1080p',
|
||||
}
|
||||
|
||||
export const enum CodecProfile {
|
||||
DEFAULT = 'default',
|
||||
LOW = 'low',
|
||||
NORMAL = 'normal',
|
||||
HIGH = 'high',
|
||||
};
|
||||
|
||||
export const enum StreamTouchController {
|
||||
DEFAULT = 'default',
|
||||
ALL = 'all',
|
||||
OFF = 'off',
|
||||
}
|
||||
|
||||
export const enum ControllerDeviceVibration {
|
||||
ON = 'on',
|
||||
AUTO = 'auto',
|
||||
OFF = 'off',
|
||||
}
|
||||
|
||||
|
||||
function getSupportedCodecProfiles() {
|
||||
const options: {[index: string]: string} = {
|
||||
const options: PartialRecord<CodecProfile, string> = {
|
||||
default: t('default'),
|
||||
};
|
||||
|
||||
@@ -46,25 +70,25 @@ function getSupportedCodecProfiles() {
|
||||
|
||||
if (hasLowCodec) {
|
||||
if (!hasNormalCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-low')} (${t('default')})`;
|
||||
options[CodecProfile.DEFAULT] = `${t('visual-quality-low')} (${t('default')})`;
|
||||
} else {
|
||||
options.low = t('visual-quality-low');
|
||||
options[CodecProfile.LOW] = t('visual-quality-low');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNormalCodec) {
|
||||
if (!hasLowCodec && !hasHighCodec) {
|
||||
options.default = `${t('visual-quality-normal')} (${t('default')})`;
|
||||
options[CodecProfile.DEFAULT] = `${t('visual-quality-normal')} (${t('default')})`;
|
||||
} else {
|
||||
options.normal = t('visual-quality-normal');
|
||||
options[CodecProfile.NORMAL] = t('visual-quality-normal');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHighCodec) {
|
||||
if (!hasLowCodec && !hasNormalCodec) {
|
||||
options.default = `${t('visual-quality-high')} (${t('default')})`;
|
||||
options[CodecProfile.DEFAULT] = `${t('visual-quality-high')} (${t('default')})`;
|
||||
} else {
|
||||
options.high = t('visual-quality-high');
|
||||
options[CodecProfile.HIGH] = t('visual-quality-high');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,25 +164,31 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
default: 'auto',
|
||||
options: {
|
||||
auto: t('default'),
|
||||
'720p': '720p',
|
||||
'1080p': '1080p',
|
||||
[StreamResolution.DIM_720P]: '720p',
|
||||
[StreamResolution.DIM_1080P]: '1080p',
|
||||
},
|
||||
suggest: {
|
||||
lowest: StreamResolution.DIM_720P,
|
||||
highest: StreamResolution.DIM_1080P,
|
||||
},
|
||||
},
|
||||
[PrefKey.STREAM_CODEC_PROFILE]: {
|
||||
label: t('visual-quality'),
|
||||
default: 'default',
|
||||
options: getSupportedCodecProfiles(),
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
const options: any = setting.options;
|
||||
ready: (setting: SettingDefinition) => {
|
||||
const options = (setting as any).options;
|
||||
const keys = Object.keys(options);
|
||||
|
||||
if (keys.length <= 1) { // Unsupported
|
||||
setting.unsupported = true;
|
||||
setting.note = '⚠️ ' + t('browser-unsupported-feature');
|
||||
} else {
|
||||
// Set default value to the best codec profile
|
||||
// setting.default = keys[keys.length - 1];
|
||||
}
|
||||
|
||||
setting.suggest = {
|
||||
lowest: keys.length === 1 ? keys[0] : keys[1],
|
||||
highest: keys[keys.length - 1],
|
||||
};
|
||||
},
|
||||
},
|
||||
[PrefKey.PREFER_IPV6_SERVER]: {
|
||||
@@ -189,16 +219,16 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
|
||||
[PrefKey.STREAM_TOUCH_CONTROLLER]: {
|
||||
label: t('tc-availability'),
|
||||
default: 'all',
|
||||
default: StreamTouchController.ALL,
|
||||
options: {
|
||||
default: t('default'),
|
||||
all: t('tc-all-games'),
|
||||
off: t('off'),
|
||||
[StreamTouchController.DEFAULT]: t('default'),
|
||||
[StreamTouchController.ALL]: t('tc-all-games'),
|
||||
[StreamTouchController.OFF]: t('off'),
|
||||
},
|
||||
unsupported: !STATES.userAgent.capabilities.touch,
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
ready: (setting: SettingDefinition) => {
|
||||
if (setting.unsupported) {
|
||||
setting.default = 'default';
|
||||
setting.default = StreamTouchController.DEFAULT;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -274,6 +304,9 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
}
|
||||
},
|
||||
},
|
||||
suggest: {
|
||||
highest: 0,
|
||||
}
|
||||
},
|
||||
|
||||
[PrefKey.GAME_BAR_POSITION]: {
|
||||
@@ -318,11 +351,11 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
|
||||
[PrefKey.CONTROLLER_DEVICE_VIBRATION]: {
|
||||
label: t('device-vibration'),
|
||||
default: 'off',
|
||||
default: ControllerDeviceVibration.OFF,
|
||||
options: {
|
||||
on: t('on'),
|
||||
auto: t('device-vibration-not-using-gamepad'),
|
||||
off: t('off'),
|
||||
[ControllerDeviceVibration.ON]: t('on'),
|
||||
[ControllerDeviceVibration.AUTO]: t('device-vibration-not-using-gamepad'),
|
||||
[ControllerDeviceVibration.OFF]: t('off'),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -346,7 +379,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
const userAgent = ((window.navigator as any).orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||
return !AppInterface && userAgent.match(/(android|iphone|ipad)/) ? t('browser-unsupported-feature') : false;
|
||||
})(),
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
ready: (setting: SettingDefinition) => {
|
||||
let note;
|
||||
let url;
|
||||
if (setting.unsupported) {
|
||||
@@ -372,16 +405,15 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
on: t('on'),
|
||||
off: t('off'),
|
||||
},
|
||||
ready: (setting: PreferenceSetting) => {
|
||||
ready: (setting: SettingDefinition) => {
|
||||
if (AppInterface) {
|
||||
|
||||
} else if (UserAgent.isMobile()) {
|
||||
setting.unsupported = true;
|
||||
setting.default = 'off';
|
||||
delete setting.options!['default'];
|
||||
delete setting.options!['on'];
|
||||
delete (setting as any).options['default'];
|
||||
delete (setting as any).options['on'];
|
||||
} else {
|
||||
delete setting.options!['on'];
|
||||
delete (setting as any).options['on'];
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -530,6 +562,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
[StreamPlayerType.VIDEO]: t('default'),
|
||||
[StreamPlayerType.WEBGL2]: t('webgl2'),
|
||||
},
|
||||
suggest: {
|
||||
lowest: StreamPlayerType.VIDEO,
|
||||
highest: StreamPlayerType.WEBGL2,
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_PROCESSING]: {
|
||||
label: t('clarity-boost'),
|
||||
@@ -538,6 +574,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
[StreamVideoProcessing.USM]: t('unsharp-masking'),
|
||||
[StreamVideoProcessing.CAS]: t('amd-fidelity-cas'),
|
||||
},
|
||||
suggest: {
|
||||
lowest: StreamVideoProcessing.USM,
|
||||
highest: StreamVideoProcessing.CAS,
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_POWER_PREFERENCE]: {
|
||||
label: t('renderer-configuration'),
|
||||
@@ -547,6 +587,9 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
'low-power': t('low-power'),
|
||||
'high-performance': t('high-performance'),
|
||||
},
|
||||
suggest: {
|
||||
highest: 'low-power',
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_SHARPNESS]: {
|
||||
label: t('sharpness'),
|
||||
@@ -561,6 +604,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
return value === 0 ? t('off') : value.toString();
|
||||
},
|
||||
},
|
||||
suggest: {
|
||||
lowest: 0,
|
||||
highest: 4,
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_RATIO]: {
|
||||
label: t('aspect-ratio'),
|
||||
@@ -701,10 +748,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
},
|
||||
|
||||
[PrefKey.REMOTE_PLAY_RESOLUTION]: {
|
||||
default: '1080p',
|
||||
default: StreamResolution.DIM_1080P,
|
||||
options: {
|
||||
'1080p': '1080p',
|
||||
'720p': '720p',
|
||||
[StreamResolution.DIM_1080P]: '1080p',
|
||||
[StreamResolution.DIM_720P]: '720p',
|
||||
},
|
||||
},
|
||||
|
||||
|
@@ -121,8 +121,11 @@ const Texts = {
|
||||
"hide-system-menu-icon": "Hide System menu's icon",
|
||||
"hide-touch-controller": "Hide touch controller",
|
||||
"high-performance": "High performance",
|
||||
"highest-quality": "Highest quality",
|
||||
"highest-quality-note": "Your device may not be powerful enough to use these settings",
|
||||
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
|
||||
"horizontal-sensitivity": "Horizontal sensitivity",
|
||||
"how-to-improve-app-performance": "How to improve app's performance",
|
||||
"ignore": "Ignore",
|
||||
"import": "Import",
|
||||
"increase": "Increase",
|
||||
@@ -137,6 +140,7 @@ const Texts = {
|
||||
"loading-screen": "Loading screen",
|
||||
"local-co-op": "Local co-op",
|
||||
"low-power": "Low power",
|
||||
"lowest-quality": "Lowest quality",
|
||||
"map-mouse-to": "Map mouse to",
|
||||
"may-not-work-properly": "May not work properly!",
|
||||
"menu": "Menu",
|
||||
@@ -189,6 +193,28 @@ const Texts = {
|
||||
],
|
||||
"press-to-bind": "Press a key or do a mouse click to bind...",
|
||||
"prompt-preset-name": "Preset's name:",
|
||||
"recommended": "Recommended",
|
||||
"recommended-settings-for-device": [
|
||||
(e: any) => `Recommended settings for ${e.device}`,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
(e: any) => `Ajustes recomendados para ${e.device}`,
|
||||
,
|
||||
(e: any) => `Configurazioni consigliate per ${e.device}`,
|
||||
,
|
||||
(e: any) => `다음 기기에서 권장되는 설정: ${e.device}`,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
(e: any) => `Рекомендовані налаштування для ${e.device}`,
|
||||
(e: any) => `Cấu hình được đề xuất cho ${e.device}`,
|
||||
,
|
||||
,
|
||||
],
|
||||
"reduce-animations": "Reduce UI animations",
|
||||
"region": "Region",
|
||||
"reload-page": "Reload page",
|
||||
@@ -250,6 +276,8 @@ const Texts = {
|
||||
"stream-settings": "Stream settings",
|
||||
"stream-stats": "Stream stats",
|
||||
"stretch": "Stretch",
|
||||
"suggest-settings": "Suggest settings",
|
||||
"suggest-settings-link": "Suggest recommended settings for this device",
|
||||
"support-better-xcloud": "Support Better xCloud",
|
||||
"swap-buttons": "Swap buttons",
|
||||
"take-screenshot": "Take screenshot",
|
||||
@@ -314,6 +342,7 @@ const Texts = {
|
||||
"volume": "Volume",
|
||||
"wait-time-countdown": "Countdown",
|
||||
"wait-time-estimated": "Estimated finish time",
|
||||
"wallpaper": "Wallpaper",
|
||||
"webgl2": "WebGL2",
|
||||
};
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import { patchIceCandidates } from "./network";
|
||||
import { getPreferredServerRegion } from "./region";
|
||||
import { BypassServerIps } from "@/enums/bypass-servers";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export
|
||||
class XcloudInterceptor {
|
||||
@@ -111,7 +111,7 @@ class XcloudInterceptor {
|
||||
|
||||
// Force stream's resolution
|
||||
if (PREF_STREAM_TARGET_RESOLUTION !== 'auto') {
|
||||
const osName = (PREF_STREAM_TARGET_RESOLUTION === '720p') ? 'android' : 'windows';
|
||||
const osName = (PREF_STREAM_TARGET_RESOLUTION === StreamResolution.DIM_720P) ? 'android' : 'windows';
|
||||
body.settings.osName = osName;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ class XcloudInterceptor {
|
||||
}
|
||||
|
||||
// Touch controller for all games
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all') {
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL) {
|
||||
const titleInfo = STATES.currentStream.titleInfo;
|
||||
if (titleInfo?.details.hasTouchSupport) {
|
||||
TouchController.disable();
|
||||
|
@@ -6,7 +6,7 @@ import { NATIVE_FETCH } from "./bx-flags";
|
||||
import { STATES } from "./global";
|
||||
import { patchIceCandidates } from "./network";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||
|
||||
export class XhomeInterceptor {
|
||||
@@ -70,7 +70,7 @@ export class XhomeInterceptor {
|
||||
static async #handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) {
|
||||
const response = await NATIVE_FETCH(request);
|
||||
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'all') {
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.ALL) {
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ export class XhomeInterceptor {
|
||||
|
||||
// Patch resolution
|
||||
const deviceInfo = RemotePlay.BASE_DEVICE_INFO;
|
||||
if (getPref(PrefKey.REMOTE_PLAY_RESOLUTION) === '720p') {
|
||||
if (getPref(PrefKey.REMOTE_PLAY_RESOLUTION) === StreamResolution.DIM_720P) {
|
||||
deviceInfo.dev.os.name = 'android';
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user