Add "Suggest settings" feature

This commit is contained in:
redphx
2024-08-26 17:27:34 +07:00
parent 070113b764
commit 4f7e0a4f7f
23 changed files with 880 additions and 229 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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',
},
},

View File

@@ -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",
};

View File

@@ -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();

View File

@@ -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';
}