diff --git a/dist/better-xcloud.pretty.user.js b/dist/better-xcloud.pretty.user.js index 387e6cf..0fe0f37 100644 --- a/dist/better-xcloud.pretty.user.js +++ b/dist/better-xcloud.pretty.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Better xCloud // @namespace https://github.com/redphx -// @version 6.3.0 +// @version 6.3.1-beta // @description Improve Xbox Cloud Gaming (xCloud) experience // @author redphx // @license MIT @@ -190,7 +190,7 @@ class UserAgent { }); } } -var SCRIPT_VERSION = "6.3.0", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; +var SCRIPT_VERSION = "6.3.1-beta", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; UserAgent.init(); var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = { supportedRegion: !0, @@ -1944,6 +1944,9 @@ class GameSettingsStorage extends BaseSettingsStorage { constructor(id) { super(`${"BetterXcloud.Stream"}.${id}`, StreamSettingsStorage.DEFINITIONS); } + isEmpty() { + return Object.keys(this.settings).length === 0; + } deleteSetting(pref) { if (this.hasSetting(pref)) return delete this.settings[pref], this.saveSettings(), !0; return !1; @@ -4304,7 +4307,9 @@ class SettingsManager { class: "bx-stream-settings-selection bx-gone", _nearby: { orientation: "vertical" } }, CE("div", !1, $select), this.$tips), BxEventBus.Stream.on("xboxTitleId.changed", async ({ id }) => { - this.playingGameId = id, setGameIdPref(id); + this.playingGameId = id; + let gameSettings = STORAGE.Stream.getGameSettings(id), selectedId = gameSettings && !gameSettings.isEmpty() ? id : -1; + setGameIdPref(selectedId); let $optGroup = $select.querySelector("optgroup"); while ($optGroup.childElementCount > 1) $optGroup.lastElementChild?.remove(); @@ -4312,9 +4317,9 @@ class SettingsManager { let title = id === 0 ? "Xbox" : await XboxApi.getProductTitle(id); $optGroup.appendChild(CE("option", { value: id - }, title)), $select.value = id.toString(); - } else $select.value = "-1"; - BxEventBus.Stream.emit("gameSettings.switched", { id }); + }, title)); + } + $select.value = selectedId.toString(), BxEventBus.Stream.emit("gameSettings.switched", { id: selectedId }); }); } getStreamSettingsSelection() { diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js index 8e5cb1b..b9f979e 100755 --- a/dist/better-xcloud.user.js +++ b/dist/better-xcloud.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Better xCloud // @namespace https://github.com/redphx -// @version 6.3.0 +// @version 6.3.1-beta // @description Improve Xbox Cloud Gaming (xCloud) experience // @author redphx // @license MIT @@ -25,7 +25,7 @@ var ALL_PREFS = {global: ["audio.mic.onPlaying","audio.volume.booster.enabled"," var SMART_TV_UNIQUE_ID = "FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA", CHROMIUM_VERSION = "125.0.0.0"; if (!!window.chrome || window.navigator.userAgent.includes("Chrome")) {let match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/);if (match) CHROMIUM_VERSION = match[1];} class UserAgent {static STORAGE_KEY = "BetterXcloud.UserAgent";static #config;static #isMobile = null;static #isSafari = null;static #isSafariMobile = null;static #USER_AGENTS = {"windows-edge": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROMIUM_VERSION} Safari/537.36 Edg/${CHROMIUM_VERSION}`,"macos-safari": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1","smarttv-generic": `${window.navigator.userAgent} Smart-TV`,"smarttv-tizen": `Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) ${CHROMIUM_VERSION}/7.0 TV Safari/537.36 ${SMART_TV_UNIQUE_ID}`,"vr-oculus": window.navigator.userAgent + " OculusBrowser VR"};static init() {if (UserAgent.#config = JSON.parse(window.localStorage.getItem(UserAgent.STORAGE_KEY) || "{}"), !UserAgent.#config.profile) UserAgent.#config.profile = BX_FLAGS.DeviceInfo.deviceType === "android-tv" || BX_FLAGS.DeviceInfo.deviceType === "webos" ? "vr-oculus" : "default";if (!UserAgent.#config.custom) UserAgent.#config.custom = "";UserAgent.spoof();}static updateStorage(profile, custom) {let config = UserAgent.#config;if (config.profile = profile, profile === "custom" && typeof custom !== "undefined") config.custom = custom;window.localStorage.setItem(UserAgent.STORAGE_KEY, JSON.stringify(config));}static getDefault() {return window.navigator.orgUserAgent || window.navigator.userAgent;}static get(profile) {let defaultUserAgent = window.navigator.userAgent;switch (profile) {case "default":return defaultUserAgent;case "custom":return UserAgent.#config.custom || defaultUserAgent;default:return UserAgent.#USER_AGENTS[profile] || defaultUserAgent;}}static isSafari() {if (this.#isSafari !== null) return this.#isSafari;let userAgent = UserAgent.getDefault().toLowerCase(), result = userAgent.includes("safari") && !userAgent.includes("chrom");return this.#isSafari = result, result;}static isSafariMobile() {if (this.#isSafariMobile !== null) return this.#isSafariMobile;let userAgent = UserAgent.getDefault().toLowerCase(), result = this.isSafari() && userAgent.includes("mobile");return this.#isSafariMobile = result, result;}static isMobile() {if (this.#isMobile !== null) return this.#isMobile;let userAgent = UserAgent.getDefault().toLowerCase(), result = /iphone|ipad|android/.test(userAgent);return this.#isMobile = result, result;}static spoof() {let profile = UserAgent.#config.profile;if (profile === "default") return;let newUserAgent = UserAgent.get(profile);if ("userAgentData" in window.navigator) window.navigator.orgUserAgentData = window.navigator.userAgentData, Object.defineProperty(window.navigator, "userAgentData", {});window.navigator.orgUserAgent = window.navigator.userAgent, Object.defineProperty(window.navigator, "userAgent", {value: newUserAgent});}} -var SCRIPT_VERSION = "6.3.0", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; +var SCRIPT_VERSION = "6.3.1-beta", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; UserAgent.init(); var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {supportedRegion: !0,serverRegions: {},selectedRegion: {},gsToken: "",isSignedIn: !1,isPlaying: !1,browser: {capabilities: {touch: browserHasTouchSupport,batteryApi: "getBattery" in window.navigator,deviceVibration: !!window.navigator.vibrate,mkb: AppInterface || !UserAgent.getDefault().toLowerCase().match(/(android|iphone|ipad)/),emulatedNativeMkb: !!AppInterface}},userAgent: {isTv,capabilities: {touch: userAgentHasTouchSupport,mkb: AppInterface || !userAgent.match(/(android|iphone|ipad)/)}},currentStream: {},remotePlay: {},pointerServerPort: 9269}; function deepClone(obj) {if (!obj) return {};if ("structuredClone" in window) return structuredClone(obj);return JSON.parse(JSON.stringify(obj));} @@ -74,7 +74,7 @@ class BaseLocalTable {tableName;constructor(tableName) {this.tableName = tableNa class BasePresetsTable extends BaseLocalTable {async newPreset(name, data) {let newRecord = { name, data };return await this.add(newRecord);}async updatePreset(preset) {return await this.put(preset);}async deletePreset(id) {return this.delete(id);}async getPreset(id) {if (id === 0) return null;if (id < 0) return this.DEFAULT_PRESETS[id];let preset = await this.get(id);if (!preset) preset = this.DEFAULT_PRESETS[this.DEFAULT_PRESET_ID];return preset;}async getPresets() {let all = deepClone(this.DEFAULT_PRESETS), presets = {default: Object.keys(this.DEFAULT_PRESETS).map((key) => parseInt(key)),custom: [],data: {}};if (await this.count() > 0) {let items = await this.getAll(), id;for (id in items) {let item2 = items[id];presets.custom.push(item2.id), all[item2.id] = item2;}}return presets.data = all, presets;}async getPresetsData() {let presetsData = {};for (let id in this.DEFAULT_PRESETS) {let preset = this.DEFAULT_PRESETS[id];presetsData[id] = deepClone(preset.data);}if (await this.count() > 0) {let items = await this.getAll(), id;for (id in items) {let item2 = items[id];presetsData[item2.id] = item2.data;}}return presetsData;}} class KeyboardShortcutsTable extends BasePresetsTable {static instance;static getInstance = () => KeyboardShortcutsTable.instance ?? (KeyboardShortcutsTable.instance = new KeyboardShortcutsTable);LOG_TAG = "KeyboardShortcutsTable";TABLE_PRESETS = LocalDb.TABLE_KEYBOARD_SHORTCUTS;DEFAULT_PRESETS = {[-1]: {id: -1,name: t("standard"),data: {mapping: {"mkb.toggle": {code: "F8"},"stream.screenshot.capture": {code: "Slash"}}}}};BLANK_PRESET_DATA = {mapping: {}};DEFAULT_PRESET_ID = -1;constructor() {super(LocalDb.TABLE_KEYBOARD_SHORTCUTS);BxLogger.info(this.LOG_TAG, "constructor()");}} class MkbMappingPresetsTable extends BasePresetsTable {static instance;static getInstance = () => MkbMappingPresetsTable.instance ?? (MkbMappingPresetsTable.instance = new MkbMappingPresetsTable);LOG_TAG = "MkbMappingPresetsTable";TABLE_PRESETS = LocalDb.TABLE_VIRTUAL_CONTROLLERS;DEFAULT_PRESETS = {[-1]: {id: -1,name: t("standard"),data: {mapping: {16: ["Backquote"],12: ["ArrowUp", "Digit1"],13: ["ArrowDown", "Digit2"],14: ["ArrowLeft", "Digit3"],15: ["ArrowRight", "Digit4"],100: ["KeyW"],101: ["KeyS"],102: ["KeyA"],103: ["KeyD"],200: ["KeyU"],201: ["KeyJ"],202: ["KeyH"],203: ["KeyK"],0: ["Space", "KeyE"],2: ["KeyR"],1: ["KeyC", "Backspace"],3: ["KeyV"],9: ["Enter"],8: ["Tab"],4: ["KeyQ"],5: ["KeyF"],7: ["Mouse0"],6: ["Mouse2"],10: ["KeyX"],11: ["KeyZ"]},mouse: {mapTo: 2,sensitivityX: 100,sensitivityY: 100,deadzoneCounterweight: 20}}},[-2]: {id: -2,name: "Shooter",data: {mapping: {16: ["Backquote"],12: ["ArrowUp"],13: ["ArrowDown"],14: ["ArrowLeft"],15: ["ArrowRight"],100: ["KeyW"],101: ["KeyS"],102: ["KeyA"],103: ["KeyD"],200: ["KeyI"],201: ["KeyK"],202: ["KeyJ"],203: ["KeyL"],0: ["Space", "KeyE"],2: ["KeyR"],1: ["ControlLeft", "Backspace"],3: ["KeyV"],9: ["Enter"],8: ["Tab"],4: ["KeyC", "KeyG"],5: ["KeyQ"],7: ["Mouse0"],6: ["Mouse2"],10: ["ShiftLeft"],11: ["KeyF"]},mouse: {mapTo: 2,sensitivityX: 100,sensitivityY: 100,deadzoneCounterweight: 20}}}};BLANK_PRESET_DATA = {mapping: {},mouse: {mapTo: 2,sensitivityX: 100,sensitivityY: 100,deadzoneCounterweight: 20}};DEFAULT_PRESET_ID = -1;constructor() {super(LocalDb.TABLE_VIRTUAL_CONTROLLERS);BxLogger.info(this.LOG_TAG, "constructor()");}} -class GameSettingsStorage extends BaseSettingsStorage {constructor(id) {super(`${"BetterXcloud.Stream"}.${id}`, StreamSettingsStorage.DEFINITIONS);}deleteSetting(pref) {if (this.hasSetting(pref)) return delete this.settings[pref], this.saveSettings(), !0;return !1;}} +class GameSettingsStorage extends BaseSettingsStorage {constructor(id) {super(`${"BetterXcloud.Stream"}.${id}`, StreamSettingsStorage.DEFINITIONS);}isEmpty() {return Object.keys(this.settings).length === 0;}deleteSetting(pref) {if (this.hasSetting(pref)) return delete this.settings[pref], this.saveSettings(), !0;return !1;}} class ControllerCustomizationsTable extends BasePresetsTable {static instance;static getInstance = () => ControllerCustomizationsTable.instance ?? (ControllerCustomizationsTable.instance = new ControllerCustomizationsTable(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS));TABLE_PRESETS = LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS;DEFAULT_PRESETS = {[-1]: {id: -1,name: "ABXY ⇄ BAYX",data: {mapping: {0: 1,1: 0,2: 3,3: 2},settings: {leftStickDeadzone: [0, 100],rightStickDeadzone: [0, 100],leftTriggerRange: [0, 100],rightTriggerRange: [0, 100],vibrationIntensity: 100}}}};BLANK_PRESET_DATA = {mapping: {},settings: {leftTriggerRange: [0, 100],rightTriggerRange: [0, 100],leftStickDeadzone: [0, 100],rightStickDeadzone: [0, 100],vibrationIntensity: 100}};DEFAULT_PRESET_ID = 0;} class ControllerShortcutsTable extends BasePresetsTable {static instance;static getInstance = () => ControllerShortcutsTable.instance ?? (ControllerShortcutsTable.instance = new ControllerShortcutsTable);LOG_TAG = "ControllerShortcutsTable";TABLE_PRESETS = LocalDb.TABLE_CONTROLLER_SHORTCUTS;DEFAULT_PRESETS = {[-1]: {id: -1,name: "Type A",data: {mapping: {3: AppInterface ? "device.volume.inc" : "stream.volume.inc",0: AppInterface ? "device.volume.dec" : "stream.volume.dec",2: "stream.stats.toggle",1: AppInterface ? "device.sound.toggle" : "stream.sound.toggle",5: "stream.screenshot.capture",9: "stream.menu.show"}}},[-2]: {id: -2,name: "Type B",data: {mapping: {12: AppInterface ? "device.volume.inc" : "stream.volume.inc",13: AppInterface ? "device.volume.dec" : "stream.volume.dec",15: "stream.stats.toggle",14: AppInterface ? "device.sound.toggle" : "stream.sound.toggle",4: "stream.screenshot.capture",8: "stream.menu.show"}}}};BLANK_PRESET_DATA = {mapping: {}};DEFAULT_PRESET_ID = -1;constructor() {super(LocalDb.TABLE_CONTROLLER_SHORTCUTS);BxLogger.info(this.LOG_TAG, "constructor()");}} class StreamSettingsStorage extends BaseSettingsStorage {static DEFINITIONS = {"deviceVibration.mode": {requiredVariants: "full",label: t("device-vibration"),default: "off",options: {off: t("off"),on: t("on"),auto: t("device-vibration-not-using-gamepad")}},"deviceVibration.intensity": {requiredVariants: "full",label: t("vibration-intensity"),default: 50,min: 10,max: 100,params: {steps: 10,suffix: "%",exactTicks: 20}},"controller.pollingRate": {requiredVariants: "full",label: t("polling-rate"),default: 4,min: 4,max: 60,params: {steps: 4,exactTicks: 20,reverse: !0,customTextValue(value) {value = parseInt(value);let text = +(1000 / value).toFixed(2) + " Hz";if (value === 4) text = `${text} (${t("default")})`;return text;}}},"controller.settings": {default: {}},"nativeMkb.scroll.sensitivityX": {requiredVariants: "full",label: t("horizontal-scroll-sensitivity"),default: 0,min: 0,max: 1e4,params: {steps: 10,exactTicks: 2000,customTextValue: (value) => {if (!value) return t("default");return (value / 100).toFixed(1) + "x";}}},"nativeMkb.scroll.sensitivityY": {requiredVariants: "full",label: t("vertical-scroll-sensitivity"),default: 0,min: 0,max: 1e4,params: {steps: 10,exactTicks: 2000,customTextValue: (value) => {if (!value) return t("default");return (value / 100).toFixed(1) + "x";}}},"mkb.p1.preset.mappingId": {requiredVariants: "full",default: -1},"mkb.p1.slot": {requiredVariants: "full",default: 1,min: 1,max: 4,params: {hideSlider: !0}},"mkb.p2.preset.mappingId": {requiredVariants: "full",default: 0},"mkb.p2.slot": {requiredVariants: "full",default: 0,min: 0,max: 4,params: {hideSlider: !0,customTextValue(value) {return value = parseInt(value), value === 0 ? t("off") : value.toString();}}},"keyboardShortcuts.preset.inGameId": {requiredVariants: "full",default: -1},"video.player.type": {label: t("renderer"),default: "default",options: {default: t("default"),webgl2: t("webgl2")},suggest: {lowest: "default",highest: "webgl2"}},"video.processing": {label: t("clarity-boost"),default: "usm",options: {usm: t("unsharp-masking"),cas: t("amd-fidelity-cas")},suggest: {lowest: "usm",highest: "cas"}},"video.player.powerPreference": {label: t("renderer-configuration"),default: "default",options: {default: t("default"),"low-power": t("battery-saving"),"high-performance": t("high-performance")},suggest: {highest: "low-power"}},"video.maxFps": {label: t("limit-fps"),default: 60,min: 10,max: 60,params: {steps: 10,exactTicks: 10,customTextValue: (value) => {return value = parseInt(value), value === 60 ? t("unlimited") : value + "fps";}}},"video.processing.sharpness": {label: t("sharpness"),default: 0,min: 0,max: 10,params: {exactTicks: 2,customTextValue: (value) => {return value = parseInt(value), value === 0 ? t("off") : value.toString();}},suggest: {lowest: 0,highest: 2}},"video.ratio": {label: t("aspect-ratio"),note: STATES.browser.capabilities.touch ? t("aspect-ratio-note") : void 0,default: "16:9",options: {"16:9": `16:9 (${t("default")})`,"18:9": "18:9","21:9": "21:9","16:10": "16:10","4:3": "4:3",fill: t("stretch")}},"video.position": {label: t("position"),note: STATES.browser.capabilities.touch ? t("aspect-ratio-note") : void 0,default: "center",options: {top: t("top"),"top-half": t("top-half"),center: `${t("center")} (${t("default")})`,"bottom-half": t("bottom-half"),bottom: t("bottom")}},"video.saturation": {label: t("saturation"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"video.contrast": {label: t("contrast"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"video.brightness": {label: t("brightness"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"audio.volume": {label: t("volume"),default: 100,min: 0,max: 600,params: {steps: 10,suffix: "%",ticks: 100}},"stats.items": {label: t("stats"),default: ["ping", "fps", "btr", "dt", "pl", "fl"],multipleOptions: {time: t("clock"),play: t("playtime"),batt: t("battery"),ping: t("stat-ping"),jit: t("jitter"),fps: t("stat-fps"),btr: t("stat-bitrate"),dt: t("stat-decode-time"),pl: t("stat-packets-lost"),fl: t("stat-frames-lost"),dl: t("downloaded"),ul: t("uploaded")},params: {size: 0},ready: (setting) => {let multipleOptions = setting.multipleOptions;if (!STATES.browser.capabilities.batteryApi) delete multipleOptions["batt"];for (let key in multipleOptions)multipleOptions[key] = key.toUpperCase() + ": " + multipleOptions[key];}},"stats.showWhenPlaying": {label: t("show-stats-on-startup"),default: !1},"stats.quickGlance.enabled": {label: "👀 " + t("enable-quick-glance-mode"),default: !0},"stats.position": {label: t("position"),default: "top-right",options: {"top-left": t("top-left"),"top-center": t("top-center"),"top-right": t("top-right")}},"stats.textSize": {label: t("text-size"),default: "0.9rem",options: {"0.9rem": t("small"),"1.0rem": t("normal"),"1.1rem": t("large")}},"stats.opacity.all": {label: t("opacity"),default: 80,min: 50,max: 100,params: {steps: 10,suffix: "%",ticks: 10}},"stats.opacity.background": {label: t("background-opacity"),default: 100,min: 0,max: 100,params: {steps: 10,suffix: "%",ticks: 10}},"stats.colors": {label: t("conditional-formatting"),default: !1},"localCoOp.enabled": {requiredVariants: "full",label: t("enable-local-co-op-support"),labelIcon: BxIcon.LOCAL_CO_OP,default: !1,note: () => CE("div", !1, CE("a", {href: "https://github.com/redphx/better-xcloud/discussions/275",target: "_blank"}, t("enable-local-co-op-support-note")), CE("br"), "⚠️ " + t("unexpected-behavior"))}};gameSettings = {};xboxTitleId = -1;constructor() {super("BetterXcloud.Stream", StreamSettingsStorage.DEFINITIONS);}setGameId(id) {this.xboxTitleId = id;}getGameSettings(id) {if (id > -1) {if (!this.gameSettings[id]) this.gameSettings[id] = new GameSettingsStorage(id);return this.gameSettings[id];}return null;}getSetting(key, checkUnsupported) {return this.getSettingByGame(this.xboxTitleId, key, !0, checkUnsupported);}getSettingByGame(id, key, returnBaseValue = !0, checkUnsupported) {let gameSettings = this.getGameSettings(id);if (gameSettings?.hasSetting(key)) return gameSettings.getSetting(key, checkUnsupported);if (returnBaseValue) return super.getSetting(key, checkUnsupported);return;}setSetting(key, value, origin) {return this.setSettingByGame(this.xboxTitleId, key, value, origin);}setSettingByGame(id, key, value, origin) {let gameSettings = this.getGameSettings(id);if (gameSettings) return BxLogger.info("setSettingByGame", id, key, value), gameSettings.setSetting(key, value, origin);return BxLogger.info("setSettingByGame", id, key, value), super.setSetting(key, value, origin);}hasGameSetting(id, key) {let gameSettings = this.getGameSettings(id);return !!(gameSettings && gameSettings.hasSetting(key));}getControllerSetting(gamepadId) {let controllerSetting = this.getSetting("controller.settings")[gamepadId];if (!controllerSetting) controllerSetting = {};if (!controllerSetting.hasOwnProperty("shortcutPresetId")) controllerSetting.shortcutPresetId = -1;if (!controllerSetting.hasOwnProperty("customizationPresetId")) controllerSetting.customizationPresetId = 0;return controllerSetting;}} @@ -127,7 +127,7 @@ class BxNumberStepper extends HTMLInputElement {intervalId = null;isHolding;cont class SettingElement {static renderOptions(key, setting, currentValue, onChange) {let $control = CE("select", {tabindex: 0}), $parent;if (setting.optionsGroup) $parent = CE("optgroup", {label: setting.optionsGroup}), $control.appendChild($parent);else $parent = $control;for (let value in setting.options) {let label = setting.options[value], $option = CE("option", { value }, label);$parent.appendChild($option);}return $control.value = currentValue, onChange && $control.addEventListener("input", (e) => {let target = e.target, value = setting.type && setting.type === "number" ? parseInt(target.value) : target.value;!e.ignoreOnChange && onChange(e, value);}), $control.setValue = (value) => {$control.value = value;}, $control;}static renderMultipleOptions(key, setting, currentValue, onChange, params = {}) {let $control = CE("select", {multiple: !0,tabindex: 0}), totalOptions = Object.keys(setting.multipleOptions).length, size = params.size ? Math.min(params.size, totalOptions) : totalOptions;$control.setAttribute("size", size.toString());for (let value in setting.multipleOptions) {let label = setting.multipleOptions[value], $option = CE("option", { value }, label);$option.selected = currentValue.indexOf(value) > -1, $option.addEventListener("mousedown", function(e) {e.preventDefault();let target = e.target;target.selected = !target.selected;let $parent = target.parentElement;$parent.focus(), BxEvent.dispatch($parent, "input");}), $control.appendChild($option);}return $control.addEventListener("mousedown", function(e) {let self = this, orgScrollTop = self.scrollTop;window.setTimeout(() => self.scrollTop = orgScrollTop, 0);}), $control.addEventListener("mousemove", (e) => e.preventDefault()), onChange && $control.addEventListener("input", (e) => {let target = e.target, values = Array.from(target.selectedOptions).map((i) => i.value);!e.ignoreOnChange && onChange(e, values);}), Object.defineProperty($control, "value", {get() {return Array.from($control.options).filter((option) => option.selected).map((option) => option.value);},set(value) {let values = value.split(",");Array.from($control.options).forEach((option) => {option.selected = values.includes(option.value);});}}), $control;}static renderCheckbox(key, setting, currentValue, onChange) {let $control = CE("input", { type: "checkbox", tabindex: 0 });return $control.checked = currentValue, onChange && $control.addEventListener("input", (e) => {!e.ignoreOnChange && onChange(e, e.target.checked);}), $control.setValue = (value) => {$control.checked = !!value;}, $control;}static renderNumberStepper(key, setting, value, onChange, options = {}) {return BxNumberStepper.create(key, value, setting.min, setting.max, options, onChange);}static METHOD_MAP = {options: SettingElement.renderOptions,"multiple-options": SettingElement.renderMultipleOptions,"number-stepper": SettingElement.renderNumberStepper,checkbox: SettingElement.renderCheckbox};static render(type, key, setting, currentValue, onChange, options) {let method = SettingElement.METHOD_MAP[type], $control = method(...Array.from(arguments).slice(1));if (type !== "number-stepper") $control.id = `bx_setting_${escapeCssSelector(key)}`;if (type === "options" || type === "multiple-options") $control.name = $control.id;return $control;}static fromPref(key, onChange, overrideParams = {}) {let { definition, storage } = getPrefInfo(key);if (!definition) return null;let currentValue = storage.getSetting(key), type;if ("options" in definition) type = "options";else if ("multipleOptions" in definition) type = "multiple-options";else if (typeof definition.default === "number") type = "number-stepper";else type = "checkbox";let params = {};if ("params" in definition) params = Object.assign(overrideParams, definition.params || {});if (params.disabled) currentValue = definition.default;return SettingElement.render(type, key, definition, currentValue, (e, value) => {if (isGlobalPref(key)) setGlobalPref(key, value, "ui");else {let id = SettingsManager.getInstance().getTargetGameId();setGamePref(id, key, value, "ui");}onChange && onChange(e, value);}, params);}} class BxSelectElement extends HTMLSelectElement {isControllerFriendly;optionsList;indicatorsList;$indicators;visibleIndex;isMultiple;$select;$btnNext;$btnPrev;$label;$checkBox;static create($select, forceFriendly = !1) {let isControllerFriendly = forceFriendly || getGlobalPref("ui.controllerFriendly");if ($select.multiple && !isControllerFriendly) return $select.classList.add("bx-select"), $select;$select.removeAttribute("tabindex");let $wrapper = CE("div", {class: "bx-select",_dataset: {controllerFriendly: isControllerFriendly}});if ($select.classList.contains("bx-full-width")) $wrapper.classList.add("bx-full-width");let $content, self = $wrapper;self.isControllerFriendly = isControllerFriendly, self.isMultiple = $select.multiple, self.visibleIndex = $select.selectedIndex, self.$select = $select, self.optionsList = Array.from($select.querySelectorAll("option")), self.$indicators = CE("div", { class: "bx-select-indicators" }), self.indicatorsList = [];let $btnPrev, $btnNext;if (isControllerFriendly) {$btnPrev = createButton({label: "<",style: 64}), $btnNext = createButton({label: ">",style: 64}), setNearby($wrapper, {orientation: "horizontal",focus: $btnNext}), self.$btnNext = $btnNext, self.$btnPrev = $btnPrev;let boundOnPrevNext = BxSelectElement.onPrevNext.bind(self);$btnPrev.addEventListener("click", boundOnPrevNext), $btnNext.addEventListener("click", boundOnPrevNext);} else $select.addEventListener("change", (e) => {self.visibleIndex = $select.selectedIndex, BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self);});if (self.isMultiple) $content = CE("button", {class: "bx-select-value bx-focusable",tabindex: 0}, CE("div", !1, self.$checkBox = CE("input", { type: "checkbox" }), self.$label = CE("span", !1, "")), self.$indicators), $content.addEventListener("click", (e) => {self.$checkBox.click();}), self.$checkBox.addEventListener("input", (e) => {let $option = BxSelectElement.getOptionAtIndex.call(self, self.visibleIndex);$option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input");});else $content = CE("div", !1, self.$label = CE("label", { for: $select.id + "_checkbox" }, ""), self.$indicators);return $select.addEventListener("input", BxSelectElement.render.bind(self)), new MutationObserver((mutationList, observer2) => {mutationList.forEach((mutation) => {if (mutation.type === "childList" || mutation.type === "attributes") self.visibleIndex = $select.selectedIndex, self.optionsList = Array.from($select.querySelectorAll("option")), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self);});}).observe($select, {subtree: !0,childList: !0,attributes: !0}), self.append($select, $btnPrev || "", $content, $btnNext || ""), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self), Object.defineProperty(self, "value", {get() {return $select.value;},set(value) {self.optionsList = Array.from($select.querySelectorAll("option")), $select.value = value, self.visibleIndex = $select.selectedIndex, BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self);}}), Object.defineProperty(self, "disabled", {get() {return $select.disabled;},set(value) {$select.disabled = value;}}), self.addEventListener = function() {$select.addEventListener.apply($select, arguments);}, self.removeEventListener = function() {$select.removeEventListener.apply($select, arguments);}, self.dispatchEvent = function() {return $select.dispatchEvent.apply($select, arguments);}, self.appendChild = function(node) {return $select.appendChild(node), node;}, self;}static resetIndicators() {let {optionsList,indicatorsList,$indicators} = this, targetSize = optionsList.length;if (indicatorsList.length > targetSize) while (indicatorsList.length > targetSize)indicatorsList.pop()?.remove();else if (indicatorsList.length < targetSize) while (indicatorsList.length < targetSize) {let $indicator = CE("span", {});indicatorsList.push($indicator), $indicators.appendChild($indicator);}for (let $indicator of indicatorsList)clearDataSet($indicator);$indicators.classList.toggle("bx-invisible", targetSize <= 1);}static getOptionAtIndex(index) {return this.optionsList[index];}static render(e) {let {$label,$btnNext,$btnPrev,$checkBox,optionsList,indicatorsList} = this;if (e && e.manualTrigger) this.visibleIndex = this.$select.selectedIndex;this.visibleIndex = BxSelectElement.normalizeIndex.call(this, this.visibleIndex);let $option = BxSelectElement.getOptionAtIndex.call(this, this.visibleIndex), content = "";if ($option) {let $parent = $option.parentElement, hasLabel = $parent instanceof HTMLOptGroupElement || this.$select.querySelector("optgroup");if (content = $option.dataset.label || $option.textContent || "", content && hasLabel) {let groupLabel = $parent instanceof HTMLOptGroupElement ? $parent.label : " ";$label.innerHTML = "";let fragment = document.createDocumentFragment();fragment.appendChild(CE("span", !1, groupLabel)), fragment.appendChild(document.createTextNode(content)), $label.appendChild(fragment);} else $label.textContent = content;} else $label.textContent = content;if ($label.classList.toggle("bx-line-through", $option && $option.disabled), this.isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content);let disableButtons = optionsList.length <= 1;$btnPrev?.classList.toggle("bx-gone", disableButtons), $btnNext?.classList.toggle("bx-gone", disableButtons);for (let i = 0;i < optionsList.length; i++) {let $option2 = optionsList[i], $indicator = indicatorsList[i];if (!$option2 || !$indicator) continue;if (clearDataSet($indicator), $option2.selected) $indicator.dataset.selected = "true";if ($option2.index === this.visibleIndex) $indicator.dataset.highlighted = "true";}}static normalizeIndex(index) {return Math.min(Math.max(index, 0), this.optionsList.length - 1);}static onPrevNext(e) {if (!e.target) return;let {$btnNext,$select,isMultiple,visibleIndex: currentIndex} = this, newIndex = e.target.closest("button") === $btnNext ? currentIndex + 1 : currentIndex - 1;if (newIndex > this.optionsList.length - 1) newIndex = 0;else if (newIndex < 0) newIndex = this.optionsList.length - 1;if (newIndex = BxSelectElement.normalizeIndex.call(this, newIndex), this.visibleIndex = newIndex, !isMultiple && newIndex !== currentIndex) $select.selectedIndex = newIndex;if (isMultiple) BxSelectElement.render.call(this);else BxEvent.dispatch($select, "input");}} class XboxApi {static CACHED_TITLES = {};static async getProductTitle(xboxTitleId) {if (xboxTitleId = xboxTitleId.toString(), XboxApi.CACHED_TITLES[xboxTitleId]) return XboxApi.CACHED_TITLES[xboxTitleId];try {let url = `https://displaycatalog.mp.microsoft.com/v7.0/products/lookup?market=US&languages=en&value=${xboxTitleId}&alternateId=XboxTitleId&fieldsTemplate=browse`, productTitle = (await (await NATIVE_FETCH(url)).json()).Products[0].LocalizedProperties[0].ProductTitle;return XboxApi.CACHED_TITLES[xboxTitleId] = productTitle, productTitle;} catch (e) {}return;}} -class SettingsManager {static instance;static getInstance = () => SettingsManager.instance ?? (SettingsManager.instance = new SettingsManager);$streamSettingsSelection;$tips;playingGameId = -1;targetGameId = -1;SETTINGS = {"localCoOp.enabled": {onChange: () => {BxExposed.toggleLocalCoOp(getStreamPref("localCoOp.enabled"));}},"deviceVibration.mode": {onChange: StreamSettings.refreshControllerSettings},"deviceVibration.intensity": {onChange: StreamSettings.refreshControllerSettings},"controller.pollingRate": {onChange: StreamSettings.refreshControllerSettings},"controller.settings": {onChange: StreamSettings.refreshControllerSettings},"nativeMkb.scroll.sensitivityX": {onChange: () => {let value = getStreamPref("nativeMkb.scroll.sensitivityX");NativeMkbHandler.getInstance()?.setHorizontalScrollMultiplier(value / 100);}},"nativeMkb.scroll.sensitivityY": {onChange: () => {let value = getStreamPref("nativeMkb.scroll.sensitivityY");NativeMkbHandler.getInstance()?.setVerticalScrollMultiplier(value / 100);}},"video.player.type": {onChange: () => {if (onChangeVideoPlayerType(), STATES.isPlaying) updateVideoPlayer();},alwaysTriggerOnChange: !0},"video.player.powerPreference": {onChange: () => {let streamPlayer = STATES.currentStream.streamPlayer;if (!streamPlayer) return;streamPlayer.reloadPlayer(), updateVideoPlayer();}},"video.processing": {onChange: updateVideoPlayer},"video.processing.sharpness": {onChange: updateVideoPlayer},"video.maxFps": {onChange: () => {let value = getStreamPref("video.maxFps");limitVideoPlayerFps(value);}},"video.ratio": {onChange: updateVideoPlayer},"video.brightness": {onChange: updateVideoPlayer},"video.contrast": {onChange: updateVideoPlayer},"video.saturation": {onChange: updateVideoPlayer},"video.position": {onChange: updateVideoPlayer},"audio.volume": {onChange: () => {let value = getStreamPref("audio.volume");SoundShortcut.setGainNodeVolume(value);}},"stats.items": {onChange: StreamStats.refreshStyles},"stats.quickGlance.enabled": {onChange: () => {let value = getStreamPref("stats.quickGlance.enabled"), streamStats = StreamStats.getInstance();value ? streamStats.quickGlanceSetup() : streamStats.quickGlanceStop();}},"stats.position": {onChange: StreamStats.refreshStyles},"stats.textSize": {onChange: StreamStats.refreshStyles},"stats.opacity.all": {onChange: StreamStats.refreshStyles},"stats.opacity.background": {onChange: StreamStats.refreshStyles},"stats.colors": {onChange: StreamStats.refreshStyles},"mkb.p1.preset.mappingId": {onChange: StreamSettings.refreshMkbSettings},"mkb.p1.slot": {onChange: () => {EmulatedMkbHandler.getInstance()?.resetXcloudGamepads();}},"keyboardShortcuts.preset.inGameId": {onChange: StreamSettings.refreshKeyboardShortcuts}};constructor() {BxEventBus.Stream.on("setting.changed", (data) => {if (isStreamPref(data.settingKey)) this.updateStreamElement(data.settingKey);}), BxEventBus.Stream.on("gameSettings.switched", ({ id }) => {this.switchGameSettings(id);}), this.renderStreamSettingsSelection();}updateStreamElement(key, onChanges) {let info = this.SETTINGS[key];if (info.onChange && (STATES.isPlaying || info.alwaysTriggerOnChange)) if (onChanges) onChanges.add(info.onChange);else info.onChange();let $elm = info.$element;if (!$elm) return;let value = getGamePref(this.targetGameId, key, !0);if ("setValue" in $elm) $elm.setValue(value);else $elm.value = value.toString();this.updateDataset($elm, key);}switchGameSettings(id) {if (setGameIdPref(id), this.targetGameId === id) return;let onChanges = new Set, oldGameId = this.targetGameId;this.targetGameId = id;let key;for (key in this.SETTINGS) {if (!isStreamPref(key)) continue;let oldValue = getGamePref(oldGameId, key, !0, !0), newValue = getGamePref(this.targetGameId, key, !0, !0);if (oldValue === newValue) continue;this.updateStreamElement(key, onChanges);}onChanges.forEach((onChange) => {onChange && onChange();}), this.$tips.classList.toggle("bx-gone", id < 0);}setElement(pref, $elm) {if (!this.SETTINGS[pref]) this.SETTINGS[pref] = {};this.updateDataset($elm, pref), this.SETTINGS[pref].$element = $elm;}getElement(pref, params) {if (!this.SETTINGS[pref]) this.SETTINGS[pref] = {};let $elm = this.SETTINGS[pref].$element;if (!$elm) $elm = SettingElement.fromPref(pref, null, params), this.SETTINGS[pref].$element = $elm;return this.updateDataset($elm, pref), $elm;}hasElement(pref) {return !!this.SETTINGS[pref]?.$element;}updateDataset($elm, pref) {if (this.targetGameId === this.playingGameId && hasGamePref(this.playingGameId, pref)) $elm.dataset.override = "true";else delete $elm.dataset.override;}renderStreamSettingsSelection() {this.$tips = CE("p", { class: "bx-gone" }, `⇐ Q ⟶: ${t("reset-highlighted-setting")}`);let $select = BxSelectElement.create(CE("select", !1, CE("optgroup", { label: t("settings-for") }, CE("option", { value: -1 }, t("all-games")))), !0);$select.addEventListener("input", (e) => {let id = parseInt($select.value);BxEventBus.Stream.emit("gameSettings.switched", { id });}), this.$streamSettingsSelection = CE("div", {class: "bx-stream-settings-selection bx-gone",_nearby: { orientation: "vertical" }}, CE("div", !1, $select), this.$tips), BxEventBus.Stream.on("xboxTitleId.changed", async ({ id }) => {this.playingGameId = id, setGameIdPref(id);let $optGroup = $select.querySelector("optgroup");while ($optGroup.childElementCount > 1)$optGroup.lastElementChild?.remove();if (id >= 0) {let title = id === 0 ? "Xbox" : await XboxApi.getProductTitle(id);$optGroup.appendChild(CE("option", {value: id}, title)), $select.value = id.toString();} else $select.value = "-1";BxEventBus.Stream.emit("gameSettings.switched", { id });});}getStreamSettingsSelection() {return this.$streamSettingsSelection;}getTargetGameId() {return this.targetGameId;}} +class SettingsManager {static instance;static getInstance = () => SettingsManager.instance ?? (SettingsManager.instance = new SettingsManager);$streamSettingsSelection;$tips;playingGameId = -1;targetGameId = -1;SETTINGS = {"localCoOp.enabled": {onChange: () => {BxExposed.toggleLocalCoOp(getStreamPref("localCoOp.enabled"));}},"deviceVibration.mode": {onChange: StreamSettings.refreshControllerSettings},"deviceVibration.intensity": {onChange: StreamSettings.refreshControllerSettings},"controller.pollingRate": {onChange: StreamSettings.refreshControllerSettings},"controller.settings": {onChange: StreamSettings.refreshControllerSettings},"nativeMkb.scroll.sensitivityX": {onChange: () => {let value = getStreamPref("nativeMkb.scroll.sensitivityX");NativeMkbHandler.getInstance()?.setHorizontalScrollMultiplier(value / 100);}},"nativeMkb.scroll.sensitivityY": {onChange: () => {let value = getStreamPref("nativeMkb.scroll.sensitivityY");NativeMkbHandler.getInstance()?.setVerticalScrollMultiplier(value / 100);}},"video.player.type": {onChange: () => {if (onChangeVideoPlayerType(), STATES.isPlaying) updateVideoPlayer();},alwaysTriggerOnChange: !0},"video.player.powerPreference": {onChange: () => {let streamPlayer = STATES.currentStream.streamPlayer;if (!streamPlayer) return;streamPlayer.reloadPlayer(), updateVideoPlayer();}},"video.processing": {onChange: updateVideoPlayer},"video.processing.sharpness": {onChange: updateVideoPlayer},"video.maxFps": {onChange: () => {let value = getStreamPref("video.maxFps");limitVideoPlayerFps(value);}},"video.ratio": {onChange: updateVideoPlayer},"video.brightness": {onChange: updateVideoPlayer},"video.contrast": {onChange: updateVideoPlayer},"video.saturation": {onChange: updateVideoPlayer},"video.position": {onChange: updateVideoPlayer},"audio.volume": {onChange: () => {let value = getStreamPref("audio.volume");SoundShortcut.setGainNodeVolume(value);}},"stats.items": {onChange: StreamStats.refreshStyles},"stats.quickGlance.enabled": {onChange: () => {let value = getStreamPref("stats.quickGlance.enabled"), streamStats = StreamStats.getInstance();value ? streamStats.quickGlanceSetup() : streamStats.quickGlanceStop();}},"stats.position": {onChange: StreamStats.refreshStyles},"stats.textSize": {onChange: StreamStats.refreshStyles},"stats.opacity.all": {onChange: StreamStats.refreshStyles},"stats.opacity.background": {onChange: StreamStats.refreshStyles},"stats.colors": {onChange: StreamStats.refreshStyles},"mkb.p1.preset.mappingId": {onChange: StreamSettings.refreshMkbSettings},"mkb.p1.slot": {onChange: () => {EmulatedMkbHandler.getInstance()?.resetXcloudGamepads();}},"keyboardShortcuts.preset.inGameId": {onChange: StreamSettings.refreshKeyboardShortcuts}};constructor() {BxEventBus.Stream.on("setting.changed", (data) => {if (isStreamPref(data.settingKey)) this.updateStreamElement(data.settingKey);}), BxEventBus.Stream.on("gameSettings.switched", ({ id }) => {this.switchGameSettings(id);}), this.renderStreamSettingsSelection();}updateStreamElement(key, onChanges) {let info = this.SETTINGS[key];if (info.onChange && (STATES.isPlaying || info.alwaysTriggerOnChange)) if (onChanges) onChanges.add(info.onChange);else info.onChange();let $elm = info.$element;if (!$elm) return;let value = getGamePref(this.targetGameId, key, !0);if ("setValue" in $elm) $elm.setValue(value);else $elm.value = value.toString();this.updateDataset($elm, key);}switchGameSettings(id) {if (setGameIdPref(id), this.targetGameId === id) return;let onChanges = new Set, oldGameId = this.targetGameId;this.targetGameId = id;let key;for (key in this.SETTINGS) {if (!isStreamPref(key)) continue;let oldValue = getGamePref(oldGameId, key, !0, !0), newValue = getGamePref(this.targetGameId, key, !0, !0);if (oldValue === newValue) continue;this.updateStreamElement(key, onChanges);}onChanges.forEach((onChange) => {onChange && onChange();}), this.$tips.classList.toggle("bx-gone", id < 0);}setElement(pref, $elm) {if (!this.SETTINGS[pref]) this.SETTINGS[pref] = {};this.updateDataset($elm, pref), this.SETTINGS[pref].$element = $elm;}getElement(pref, params) {if (!this.SETTINGS[pref]) this.SETTINGS[pref] = {};let $elm = this.SETTINGS[pref].$element;if (!$elm) $elm = SettingElement.fromPref(pref, null, params), this.SETTINGS[pref].$element = $elm;return this.updateDataset($elm, pref), $elm;}hasElement(pref) {return !!this.SETTINGS[pref]?.$element;}updateDataset($elm, pref) {if (this.targetGameId === this.playingGameId && hasGamePref(this.playingGameId, pref)) $elm.dataset.override = "true";else delete $elm.dataset.override;}renderStreamSettingsSelection() {this.$tips = CE("p", { class: "bx-gone" }, `⇐ Q ⟶: ${t("reset-highlighted-setting")}`);let $select = BxSelectElement.create(CE("select", !1, CE("optgroup", { label: t("settings-for") }, CE("option", { value: -1 }, t("all-games")))), !0);$select.addEventListener("input", (e) => {let id = parseInt($select.value);BxEventBus.Stream.emit("gameSettings.switched", { id });}), this.$streamSettingsSelection = CE("div", {class: "bx-stream-settings-selection bx-gone",_nearby: { orientation: "vertical" }}, CE("div", !1, $select), this.$tips), BxEventBus.Stream.on("xboxTitleId.changed", async ({ id }) => {this.playingGameId = id;let gameSettings = STORAGE.Stream.getGameSettings(id), selectedId = gameSettings && !gameSettings.isEmpty() ? id : -1;setGameIdPref(selectedId);let $optGroup = $select.querySelector("optgroup");while ($optGroup.childElementCount > 1)$optGroup.lastElementChild?.remove();if (id >= 0) {let title = id === 0 ? "Xbox" : await XboxApi.getProductTitle(id);$optGroup.appendChild(CE("option", {value: id}, title));}$select.value = selectedId.toString(), BxEventBus.Stream.emit("gameSettings.switched", { id: selectedId });});}getStreamSettingsSelection() {return this.$streamSettingsSelection;}getTargetGameId() {return this.targetGameId;}} function onChangeVideoPlayerType() {let playerType = getStreamPref("video.player.type"), settingsManager = SettingsManager.getInstance();if (!settingsManager.hasElement("video.processing")) return;let isDisabled = !1, $videoProcessing = settingsManager.getElement("video.processing"), $videoSharpness = settingsManager.getElement("video.processing.sharpness"), $videoPowerPreference = settingsManager.getElement("video.player.powerPreference"), $videoMaxFps = settingsManager.getElement("video.maxFps"), $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`);if (playerType === "webgl2") $optCas && ($optCas.disabled = !1);else if ($videoProcessing.value = "usm", setStreamPref("video.processing", "usm", "direct"), $optCas && ($optCas.disabled = !0), UserAgent.isSafari()) isDisabled = !0;$videoProcessing.disabled = isDisabled, $videoSharpness.dataset.disabled = isDisabled.toString(), $videoPowerPreference.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), $videoMaxFps.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2");} function limitVideoPlayerFps(targetFps) {STATES.currentStream.streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps);} function updateVideoPlayer() {let streamPlayer = STATES.currentStream.streamPlayer;if (!streamPlayer) return;limitVideoPlayerFps(getStreamPref("video.maxFps"));let options = {processing: getStreamPref("video.processing"),sharpness: getStreamPref("video.processing.sharpness"),saturation: getStreamPref("video.saturation"),contrast: getStreamPref("video.contrast"),brightness: getStreamPref("video.brightness")};streamPlayer.setPlayerType(getStreamPref("video.player.type")), streamPlayer.updateOptions(options), streamPlayer.refreshPlayer();} diff --git a/src/index.ts b/src/index.ts index f3288c0..6db4cc0 100755 --- a/src/index.ts +++ b/src/index.ts @@ -270,7 +270,6 @@ BxEventBus.Stream.on('state.playing', payload => { } } - updateVideoPlayer(); }); diff --git a/src/modules/settings-manager.ts b/src/modules/settings-manager.ts index db4504d..9397f3a 100644 --- a/src/modules/settings-manager.ts +++ b/src/modules/settings-manager.ts @@ -3,7 +3,7 @@ import { limitVideoPlayerFps, onChangeVideoPlayerType, updateVideoPlayer } from import { StreamStats } from "./stream/stream-stats"; import { SoundShortcut } from "./shortcuts/sound-shortcut"; import { STATES } from "@/utils/global"; -import { getGamePref, getStreamPref, hasGamePref, isStreamPref, setGameIdPref } from "@/utils/pref-utils"; +import { getGamePref, getStreamPref, hasGamePref, isStreamPref, setGameIdPref, STORAGE } from "@/utils/pref-utils"; import { BxExposed } from "@/utils/bx-exposed"; import { StreamSettings } from "@/utils/stream-settings"; import { NativeMkbHandler } from "./mkb/native-mkb-handler"; @@ -311,10 +311,16 @@ export class SettingsManager { BxEventBus.Stream.on('xboxTitleId.changed', async ({ id }) => { this.playingGameId = id; - setGameIdPref(id); - const $optGroup = $select.querySelector('optgroup')!; + + // Only switch to game settings if it's not empty + const gameSettings = STORAGE.Stream.getGameSettings(id); + const customSettings = gameSettings && !gameSettings.isEmpty(); + const selectedId = customSettings ? id : -1; + + setGameIdPref(selectedId); // Remove every options except the first one (All games) + const $optGroup = $select.querySelector('optgroup')!; while ($optGroup.childElementCount > 1) { $optGroup.lastElementChild?.remove(); } @@ -325,13 +331,12 @@ export class SettingsManager { $optGroup.appendChild(CE('option', { value: id, }, title)); - - $select.value = id.toString(); - } else { - $select.value = '-1'; } - BxEventBus.Stream.emit('gameSettings.switched', { id }); + // Activate custom settings + + $select.value = selectedId.toString(); + BxEventBus.Stream.emit('gameSettings.switched', { id: selectedId }); }); } diff --git a/src/utils/settings-storages/game-settings-storage.ts b/src/utils/settings-storages/game-settings-storage.ts index 6863c8a..e3fc223 100644 --- a/src/utils/settings-storages/game-settings-storage.ts +++ b/src/utils/settings-storages/game-settings-storage.ts @@ -7,6 +7,10 @@ export class GameSettingsStorage extends BaseSettingsStorage { super(`${StorageKey.STREAM}.${id}`, StreamSettingsStorage.DEFINITIONS); } + isEmpty() { + return Object.keys(this.settings).length === 0; + } + deleteSetting(pref: StreamPref) { if (this.hasSetting(pref)) { delete this.settings[pref];