diff --git a/dist/better-xcloud.lite.user.js b/dist/better-xcloud.lite.user.js index a1a8b3f..e44e985 100755 --- a/dist/better-xcloud.lite.user.js +++ b/dist/better-xcloud.lite.user.js @@ -141,7 +141,7 @@ function deepClone(obj) { } var BxEvent; ((BxEvent) => { - BxEvent.JUMP_BACK_IN_READY = "bx-jump-back-in-ready", BxEvent.POPSTATE = "bx-popstate", BxEvent.TITLE_INFO_READY = "bx-title-info-ready", BxEvent.SETTINGS_CHANGED = "bx-settings-changed", BxEvent.STREAM_LOADING = "bx-stream-loading", BxEvent.STREAM_STARTING = "bx-stream-starting", BxEvent.STREAM_STARTED = "bx-stream-started", BxEvent.STREAM_PLAYING = "bx-stream-playing", BxEvent.STREAM_STOPPED = "bx-stream-stopped", BxEvent.STREAM_ERROR_PAGE = "bx-stream-error-page", BxEvent.STREAM_WEBRTC_CONNECTED = "bx-stream-webrtc-connected", BxEvent.STREAM_WEBRTC_DISCONNECTED = "bx-stream-webrtc-disconnected", BxEvent.MKB_UPDATED = "bx-mkb-updated", BxEvent.KEYBOARD_SHORTCUTS_UPDATED = "bx-keyboard-shortcuts-updated", BxEvent.STREAM_SESSION_READY = "bx-stream-session-ready", BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED = "bx-custom-touch-layouts-loaded", BxEvent.TOUCH_LAYOUT_MANAGER_READY = "bx-touch-layout-manager-ready", BxEvent.REMOTE_PLAY_READY = "bx-remote-play-ready", BxEvent.REMOTE_PLAY_FAILED = "bx-remote-play-failed", BxEvent.XCLOUD_SERVERS_READY = "bx-servers-ready", BxEvent.XCLOUD_SERVERS_UNAVAILABLE = "bx-servers-unavailable", BxEvent.DATA_CHANNEL_CREATED = "bx-data-channel-created", BxEvent.DEVICE_VIBRATION_CHANGED = "bx-device-vibration-changed", BxEvent.GAME_BAR_ACTION_ACTIVATED = "bx-game-bar-action-activated", BxEvent.MICROPHONE_STATE_CHANGED = "bx-microphone-state-changed", BxEvent.SPEAKER_STATE_CHANGED = "bx-speaker-state-changed", BxEvent.VIDEO_VISIBILITY_CHANGED = "bx-video-visibility-changed", BxEvent.CAPTURE_SCREENSHOT = "bx-capture-screenshot", BxEvent.POINTER_LOCK_REQUESTED = "bx-pointer-lock-requested", BxEvent.POINTER_LOCK_EXITED = "bx-pointer-lock-exited", BxEvent.NAVIGATION_FOCUS_CHANGED = "bx-nav-focus-changed", BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED = "bx-gh-pages-force-native-mkb-updated", BxEvent.XCLOUD_DIALOG_SHOWN = "bx-xcloud-dialog-shown", BxEvent.XCLOUD_DIALOG_DISMISSED = "bx-xcloud-dialog-dismissed", BxEvent.XCLOUD_GUIDE_MENU_SHOWN = "bx-xcloud-guide-menu-shown", BxEvent.XCLOUD_POLLING_MODE_CHANGED = "bx-xcloud-polling-mode-changed", BxEvent.XCLOUD_RENDERING_COMPONENT = "bx-xcloud-rendering-component", BxEvent.XCLOUD_ROUTER_HISTORY_READY = "bx-xcloud-router-history-ready"; + BxEvent.JUMP_BACK_IN_READY = "bx-jump-back-in-ready", BxEvent.POPSTATE = "bx-popstate", BxEvent.STREAM_SESSION_READY = "bx-stream-session-ready", BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED = "bx-custom-touch-layouts-loaded", BxEvent.TOUCH_LAYOUT_MANAGER_READY = "bx-touch-layout-manager-ready", BxEvent.REMOTE_PLAY_READY = "bx-remote-play-ready", BxEvent.REMOTE_PLAY_FAILED = "bx-remote-play-failed", BxEvent.DATA_CHANNEL_CREATED = "bx-data-channel-created", BxEvent.GAME_BAR_ACTION_ACTIVATED = "bx-game-bar-action-activated", BxEvent.MICROPHONE_STATE_CHANGED = "bx-microphone-state-changed", BxEvent.SPEAKER_STATE_CHANGED = "bx-speaker-state-changed", BxEvent.VIDEO_VISIBILITY_CHANGED = "bx-video-visibility-changed", BxEvent.CAPTURE_SCREENSHOT = "bx-capture-screenshot", BxEvent.POINTER_LOCK_REQUESTED = "bx-pointer-lock-requested", BxEvent.POINTER_LOCK_EXITED = "bx-pointer-lock-exited", BxEvent.NAVIGATION_FOCUS_CHANGED = "bx-nav-focus-changed", BxEvent.XCLOUD_DIALOG_SHOWN = "bx-xcloud-dialog-shown", BxEvent.XCLOUD_DIALOG_DISMISSED = "bx-xcloud-dialog-dismissed", BxEvent.XCLOUD_GUIDE_MENU_SHOWN = "bx-xcloud-guide-menu-shown", BxEvent.XCLOUD_POLLING_MODE_CHANGED = "bx-xcloud-polling-mode-changed", BxEvent.XCLOUD_RENDERING_COMPONENT = "bx-xcloud-rendering-component", BxEvent.XCLOUD_ROUTER_HISTORY_READY = "bx-xcloud-router-history-ready"; function dispatch(target, eventName, data) { if (!target) return; if (!eventName) { @@ -156,6 +156,33 @@ var BxEvent; BxEvent.dispatch = dispatch; })(BxEvent ||= {}); window.BxEvent = BxEvent; +class EventBus { + listeners = new Map; + static Script = new EventBus; + static Stream = new EventBus; + on(event, callback) { + if (!this.listeners.has(event)) this.listeners.set(event, new Set); + this.listeners.get(event).add(callback), BX_FLAGS.Debug && BxLogger.warning("EventBus", "on", event, callback); + } + off(event, callback) { + if (BX_FLAGS.Debug && BxLogger.warning("EventBus", "off", event, callback), !callback) { + this.listeners.delete(event); + return; + } + let callbacks = this.listeners.get(event); + if (!callbacks) return; + if (callbacks.delete(callback), callbacks.size === 0) this.listeners.delete(event); + } + offAll() { + this.listeners.clear(); + } + emit(event, payload) { + BX_FLAGS.Debug && BxLogger.warning("EventBus", "emit", event, payload); + let callbacks = this.listeners.get(event) || []; + for (let callback of callbacks) + callback(payload); + } +} class GhPagesUtils { static fetchLatestCommit() { NATIVE_FETCH("https://api.github.com/repos/redphx/better-xcloud/branches/gh-pages", { @@ -179,7 +206,7 @@ class GhPagesUtils { static getNativeMkbCustomList(update = !1) { let key = "BetterXcloud.GhPages.ForceNativeMkb"; update && NATIVE_FETCH(GhPagesUtils.getUrl("native-mkb/ids.json")).then((response) => response.json()).then((json) => { - if (json.$schemaVersion === 1) window.localStorage.setItem(key, JSON.stringify(json)), BxEvent.dispatch(window, BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED); + if (json.$schemaVersion === 1) window.localStorage.setItem(key, JSON.stringify(json)), EventBus.Script.emit("listForcedNativeMkbUpdated", {}); }); let info = JSON.parse(window.localStorage.getItem(key) || "{}"); if (info.$schemaVersion !== 1) return window.localStorage.removeItem(key), {}; @@ -878,7 +905,7 @@ class BaseSettingsStore { return this.settings[key]; } setSetting(key, value, emitEvent = !1) { - return value = this.validateValue("set", key, value), this.settings[key] = this.validateValue("get", key, value), this.saveSettings(), emitEvent && BxEvent.dispatch(window, BxEvent.SETTINGS_CHANGED, { + return value = this.validateValue("set", key, value), this.settings[key] = this.validateValue("get", key, value), this.saveSettings(), emitEvent && EventBus.Script.emit("settingChanged", { storageKey: this.storageKey, settingKey: key, settingValue: value @@ -1512,7 +1539,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { default: [], unsupported: !AppInterface && UserAgent.isMobile(), ready: (setting) => { - if (!setting.unsupported) setting.multipleOptions = GhPagesUtils.getNativeMkbCustomList(!0), window.addEventListener(BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED, (e) => { + if (!setting.unsupported) setting.multipleOptions = GhPagesUtils.getNativeMkbCustomList(!0), EventBus.Script.on("listForcedNativeMkbUpdated", () => { setting.multipleOptions = GhPagesUtils.getNativeMkbCustomList(); }); }, @@ -2141,7 +2168,7 @@ class StreamStatsCollector { } catch (e) {} } static setupEvents() { - window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { + EventBus.Stream.on("statePlaying", () => { StreamStatsCollector.getInstance().reset(); }); } @@ -2279,7 +2306,7 @@ class StreamStats { this.refreshStyles(), document.documentElement.appendChild(this.$container); } static setupEvents() { - window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { + EventBus.Stream.on("statePlaying", () => { let PREF_STATS_QUICK_GLANCE = getPref("stats.quickGlance.enabled"), PREF_STATS_SHOW_WHEN_PLAYING = getPref("stats.showWhenPlaying"), streamStats = StreamStats.getInstance(); if (PREF_STATS_SHOW_WHEN_PLAYING) streamStats.start(); else if (PREF_STATS_QUICK_GLANCE) streamStats.quickGlanceSetup(), !PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(!0); @@ -2573,7 +2600,7 @@ class StreamSettings { if (!STATES.browser.capabilities.deviceVibration) return; let mode = StreamSettings.getPref("deviceVibration.mode"), intensity = 0; if (mode === "on" || mode === "auto" && !hasGamepad()) intensity = StreamSettings.getPref("deviceVibration.intensity") / 100; - StreamSettings.settings.deviceVibrationIntensity = intensity, BxEvent.dispatch(window, BxEvent.DEVICE_VIBRATION_CHANGED); + StreamSettings.settings.deviceVibrationIntensity = intensity, EventBus.Script.emit("deviceVibrationUpdated", {}); } static async refreshMkbSettings() { let settings = StreamSettings.settings, presetId = StreamSettings.getPref("mkb.p1.preset.mappingId"), orgPreset = await MkbMappingPresetsTable.getInstance().getPreset(presetId), orgPresetData = orgPreset.data, converted = { @@ -2587,12 +2614,12 @@ class StreamSettings { if (typeof keyName === "string") converted.mapping[keyName] = buttonIndex; } let mouse = converted.mouse; - mouse["sensitivityX"] *= 0.001, mouse["sensitivityY"] *= 0.001, mouse["deadzoneCounterweight"] *= 0.01, settings.mkbPreset = converted, setPref("mkb.p1.preset.mappingId", orgPreset.id), BxEvent.dispatch(window, BxEvent.MKB_UPDATED); + mouse["sensitivityX"] *= 0.001, mouse["sensitivityY"] *= 0.001, mouse["deadzoneCounterweight"] *= 0.01, settings.mkbPreset = converted, setPref("mkb.p1.preset.mappingId", orgPreset.id), EventBus.Script.emit("mkbSettingUpdated", {}); } static async refreshKeyboardShortcuts() { let settings = StreamSettings.settings, presetId = StreamSettings.getPref("keyboardShortcuts.preset.inGameId"); if (presetId === 0) { - settings.keyboardShortcuts = null, setPref("keyboardShortcuts.preset.inGameId", presetId), BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED); + settings.keyboardShortcuts = null, setPref("keyboardShortcuts.preset.inGameId", presetId), EventBus.Script.emit("keyboardShortcutsUpdated", {}); return; } let orgPreset = await KeyboardShortcutsTable.getInstance().getPreset(presetId), orgPresetData = orgPreset.data.mapping, converted = {}, action; @@ -2600,7 +2627,7 @@ class StreamSettings { let info = orgPresetData[action], key = `${info.code}:${info.modifiers || 0}`; converted[key] = action; } - settings.keyboardShortcuts = converted, setPref("keyboardShortcuts.preset.inGameId", orgPreset.id), BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED); + settings.keyboardShortcuts = converted, setPref("keyboardShortcuts.preset.inGameId", orgPreset.id), EventBus.Script.emit("keyboardShortcutsUpdated", {}); } static async refreshAllSettings() { window.BX_STREAM_SETTINGS = StreamSettings.settings, await StreamSettings.refreshControllerSettings(), await StreamSettings.refreshMkbSettings(), await StreamSettings.refreshKeyboardShortcuts(); @@ -2627,7 +2654,7 @@ class MkbPopup { $btnActivate; mkbHandler; constructor() { - this.render(), window.addEventListener(BxEvent.KEYBOARD_SHORTCUTS_UPDATED, (e) => { + this.render(), EventBus.Script.on("keyboardShortcutsUpdated", () => { let $newButton = this.createActivateButton(); this.$btnActivate.replaceWith($newButton), this.$btnActivate = $newButton; }); @@ -3616,7 +3643,6 @@ class BxNumberStepper extends HTMLInputElement { $btnInc; $btnDec; $range; - onInput; onRangeInput; onClick; onPointerUp; @@ -3643,7 +3669,7 @@ class BxNumberStepper extends HTMLInputElement { class: options.hideSlider ? "bx-focusable" : "", tabindex: options.hideSlider ? 0 : -1 }, "+"))); - if (self.$text = $text, self.$btnInc = $btnInc, self.$btnDec = $btnDec, self.onChange = onChange, self.onInput = BxNumberStepper.onInput.bind(self), self.onRangeInput = BxNumberStepper.onRangeInput.bind(self), self.onClick = BxNumberStepper.onClick.bind(self), self.onPointerUp = BxNumberStepper.onPointerUp.bind(self), self.onPointerDown = BxNumberStepper.onPointerDown.bind(self), self.controlMin = min, self.controlMax = max, self.isHolding = !1, self.options = options, self.uiMin = options.reverse ? -max : min, self.uiMax = options.reverse ? -min : max, self.steps = Math.max(options.steps || 1, 1), BxNumberStepper.setValue.call(self, value), options.disabled) return $btnInc.disabled = !0, $btnInc.classList.add("bx-inactive"), $btnDec.disabled = !0, $btnDec.classList.add("bx-inactive"), self.disabled = !0, self; + if (self.$text = $text, self.$btnInc = $btnInc, self.$btnDec = $btnDec, self.onChange = onChange, self.onRangeInput = BxNumberStepper.onRangeInput.bind(self), self.onClick = BxNumberStepper.onClick.bind(self), self.onPointerUp = BxNumberStepper.onPointerUp.bind(self), self.onPointerDown = BxNumberStepper.onPointerDown.bind(self), self.controlMin = min, self.controlMax = max, self.isHolding = !1, self.options = options, self.uiMin = options.reverse ? -max : min, self.uiMax = options.reverse ? -min : max, self.steps = Math.max(options.steps || 1, 1), BxNumberStepper.setValue.call(self, value), options.disabled) return $btnInc.disabled = !0, $btnInc.classList.add("bx-inactive"), $btnDec.disabled = !0, $btnDec.classList.add("bx-inactive"), self.disabled = !0, self; if ($range = CE("input", { id: `bx_inp_setting_${key}`, type: "range", @@ -3652,7 +3678,7 @@ class BxNumberStepper extends HTMLInputElement { value: options.reverse ? -value : value, step: self.steps, tabindex: 0 - }), self.$range = $range, options.hideSlider && $range.classList.add("bx-gone"), $range.addEventListener("input", self.onRangeInput), self.addEventListener("input", self.onInput), self.appendChild($range), options.ticks || options.exactTicks) { + }), self.$range = $range, options.hideSlider && $range.classList.add("bx-gone"), self.addEventListener("input", self.onRangeInput), self.appendChild($range), options.ticks || options.exactTicks) { let markersId = `markers-${key}`, $markers = CE("datalist", { id: markersId }); if ($range.setAttribute("list", markersId), options.exactTicks) { let start = Math.max(Math.floor(min / options.exactTicks), 1) * options.exactTicks; @@ -3683,9 +3709,6 @@ class BxNumberStepper extends HTMLInputElement { static normalizeValue(value) { return value = parseInt(value), value = Math.max(this.controlMin, value), value = Math.min(this.controlMax, value), value; } - static onInput(e) { - BxEvent.dispatch(this.$range, "input"); - } static onRangeInput(e) { let value = parseInt(e.target.value); if (this.options.reverse) value *= -1; @@ -4255,12 +4278,9 @@ class SettingsDialog extends NavigationDialog { }, onCreated: (setting, $elm) => { let $range = $elm.querySelector("input[type=range"); - window.addEventListener(BxEvent.SETTINGS_CHANGED, (e) => { - let { storageKey, settingKey, settingValue } = e; - if (storageKey !== "BetterXcloud" || settingKey !== "audio.volume") return; - $range.value = settingValue, BxEvent.dispatch($range, "input", { - ignoreOnChange: !0 - }); + EventBus.Script.on("settingChanged", (payload) => { + let { storageKey, settingKey, settingValue } = payload; + if (storageKey === "BetterXcloud" && settingKey === "audio.volume") $range.value = settingValue, BxEvent.dispatch($range, "input", { ignoreOnChange: !0 }); }); } }] @@ -5435,7 +5455,7 @@ class XcloudInterceptor { ip && request.headers.set("X-Forwarded-For", ip); } let response = await NATIVE_FETCH(request, init); - if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response; + if (response.status !== 200) return EventBus.Script.emit("xcloudServerUnavailable", {}), response; let obj = await response.clone().json(); RemotePlayManager.getInstance()?.setXcloudToken(obj.gsToken); let serverRegex = /\/\/(\w+)\./, serverExtra = XcloudInterceptor.SERVER_EXTRA_INFO, region; @@ -5447,7 +5467,7 @@ class XcloudInterceptor { else region.contintent = "other"; region.shortName = shortName.toUpperCase(), STATES.serverRegions[region.name] = Object.assign({}, region); } - BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY); + EventBus.Script.emit("xcloudServerReady", {}); let preferredRegion = getPreferredServerRegion(); if (preferredRegion && preferredRegion in STATES.serverRegions) { let tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); @@ -5456,7 +5476,7 @@ class XcloudInterceptor { return STATES.gsToken = obj.gsToken, response.json = () => Promise.resolve(obj), response; } static async handlePlay(request, init) { - BxEvent.dispatch(window, BxEvent.STREAM_LOADING); + EventBus.Stream.emit("stateLoading", {}); let PREF_STREAM_TARGET_RESOLUTION = getPref("stream.video.resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream.locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url), badgeRegion = parsedUrl.host.split(".", 1)[0]; for (let regionName in STATES.serverRegions) { let region = STATES.serverRegions[regionName]; @@ -5492,7 +5512,7 @@ class XcloudInterceptor { if (request.method !== "GET") return NATIVE_FETCH(request, init); let response = await NATIVE_FETCH(request, init), text = await response.clone().text(); if (!text.length) return response; - BxEvent.dispatch(window, BxEvent.STREAM_STARTING); + EventBus.Stream.emit("stateStarting", {}); let obj = JSON.parse(text), overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {}; overrides.inputConfiguration = overrides.inputConfiguration || {}, overrides.inputConfiguration.enableVibration = !0; let overrideMkb = null; @@ -5725,7 +5745,7 @@ function onHistoryChanged(e) { window.setTimeout(RemotePlayManager.detect, 10); let $settings = document.querySelector(".bx-settings-container"); if ($settings) $settings.classList.add("bx-gone"); - NavigationDialogManager.getInstance().hide(), LoadingScreen.reset(), window.setTimeout(HeaderSection.watchHeader, 2000), BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); + NavigationDialogManager.getInstance().hide(), LoadingScreen.reset(), window.setTimeout(HeaderSection.watchHeader, 2000), EventBus.Stream.emit("stateStopped", {}); } function setCodecPreferences(sdp, preferredCodec) { let h264Pattern = /a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g, profilePrefix = preferredCodec === "high" ? "4d" : preferredCodec === "low" ? "420" : "42e", preferredCodecIds = [], matches = sdp.matchAll(h264Pattern) || []; @@ -6081,7 +6101,7 @@ function patchVideoApi() { contrast: getPref("video.contrast"), brightness: getPref("video.brightness") }; - STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref("video.player.type"), playerOptions), BxEvent.dispatch(window, BxEvent.STREAM_PLAYING, { + STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref("video.player.type"), playerOptions), EventBus.Stream.emit("statePlaying", { $video: this }); }, nativePlay = HTMLMediaElement.prototype.play; @@ -6280,7 +6300,7 @@ class StreamUiHandler { if (!($elm instanceof HTMLElement)) return; let className = $elm.className || ""; if (className.includes("PureErrorPage")) { - BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE); + EventBus.Stream.emit("stateError", {}); return; } if (className.startsWith("StreamMenu-module__container")) { @@ -6401,25 +6421,25 @@ window.addEventListener(BxEvent.POPSTATE, onHistoryChanged); window.addEventListener("popstate", onHistoryChanged); window.history.pushState = patchHistoryMethod("pushState"); window.history.replaceState = patchHistoryMethod("replaceState"); -window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, (e) => { - if (STATES.supportedRegion = !1, window.setTimeout(HeaderSection.watchHeader, 2000), document.querySelector("div[class^=UnsupportedMarketPage-module__container]")) SettingsDialog.getInstance().show(); -}, { once: !0 }); -window.addEventListener(BxEvent.XCLOUD_SERVERS_READY, (e) => { +EventBus.Script.on("xcloudServerUnavailable", () => { + if (EventBus.Script.off("xcloudServerUnavailable", null), STATES.supportedRegion = !1, window.setTimeout(HeaderSection.watchHeader, 2000), document.querySelector("div[class^=UnsupportedMarketPage-module__container]")) SettingsDialog.getInstance().show(); +}); +EventBus.Script.on("xcloudServerReady", () => { STATES.isSignedIn = !0, window.setTimeout(HeaderSection.watchHeader, 2000); }); -window.addEventListener(BxEvent.STREAM_LOADING, (e) => { +EventBus.Stream.on("stateLoading", () => { if (window.location.pathname.includes("/launch/") && STATES.currentStream.titleInfo) STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title); else STATES.currentStream.titleSlug = "remote-play"; }); -getPref("loadingScreen.gameArt.show") && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup); -window.addEventListener(BxEvent.STREAM_STARTING, (e) => { +getPref("loadingScreen.gameArt.show") && EventBus.Script.on("titleInfoReady", LoadingScreen.setup); +EventBus.Stream.on("stateStarting", () => { LoadingScreen.hide(); }); -window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { +EventBus.Stream.on("statePlaying", (payload) => { window.BX_STREAM_SETTINGS = StreamSettings.settings, StreamSettings.refreshAllSettings(), STATES.isPlaying = !0, StreamUiHandler.observe(), updateVideoPlayer(); }); -window.addEventListener(BxEvent.STREAM_ERROR_PAGE, (e) => { - BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); +EventBus.Stream.on("stateError", () => { + EventBus.Stream.emit("stateStopped", {}); }); window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => { let dataChannel = e.dataChannel; @@ -6441,9 +6461,9 @@ function unload() { if (!STATES.isPlaying) return; STATES.currentStream.streamPlayer?.destroy(), STATES.isPlaying = !1, STATES.currentStream = {}, window.BX_EXPOSED.shouldShowSensorControls = !1, window.BX_EXPOSED.stopTakRendering = !1, NavigationDialogManager.getInstance().hide(), StreamStats.getInstance().destroy(), StreamBadges.getInstance().destroy(); } -window.addEventListener(BxEvent.STREAM_STOPPED, unload); +EventBus.Stream.on("stateStopped", unload); window.addEventListener("pagehide", (e) => { - BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); + EventBus.Stream.emit("stateStopped", {}); }); function main() { if (GhPagesUtils.fetchLatestCommit(), getPref("nativeMkb.mode") !== "off") { diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js index 3fb16fd..3525e2f 100755 --- a/dist/better-xcloud.user.js +++ b/dist/better-xcloud.user.js @@ -143,7 +143,7 @@ function deepClone(obj) { } var BxEvent; ((BxEvent) => { - BxEvent.JUMP_BACK_IN_READY = "bx-jump-back-in-ready", BxEvent.POPSTATE = "bx-popstate", BxEvent.TITLE_INFO_READY = "bx-title-info-ready", BxEvent.SETTINGS_CHANGED = "bx-settings-changed", BxEvent.STREAM_LOADING = "bx-stream-loading", BxEvent.STREAM_STARTING = "bx-stream-starting", BxEvent.STREAM_STARTED = "bx-stream-started", BxEvent.STREAM_PLAYING = "bx-stream-playing", BxEvent.STREAM_STOPPED = "bx-stream-stopped", BxEvent.STREAM_ERROR_PAGE = "bx-stream-error-page", BxEvent.STREAM_WEBRTC_CONNECTED = "bx-stream-webrtc-connected", BxEvent.STREAM_WEBRTC_DISCONNECTED = "bx-stream-webrtc-disconnected", BxEvent.MKB_UPDATED = "bx-mkb-updated", BxEvent.KEYBOARD_SHORTCUTS_UPDATED = "bx-keyboard-shortcuts-updated", BxEvent.STREAM_SESSION_READY = "bx-stream-session-ready", BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED = "bx-custom-touch-layouts-loaded", BxEvent.TOUCH_LAYOUT_MANAGER_READY = "bx-touch-layout-manager-ready", BxEvent.REMOTE_PLAY_READY = "bx-remote-play-ready", BxEvent.REMOTE_PLAY_FAILED = "bx-remote-play-failed", BxEvent.XCLOUD_SERVERS_READY = "bx-servers-ready", BxEvent.XCLOUD_SERVERS_UNAVAILABLE = "bx-servers-unavailable", BxEvent.DATA_CHANNEL_CREATED = "bx-data-channel-created", BxEvent.DEVICE_VIBRATION_CHANGED = "bx-device-vibration-changed", BxEvent.GAME_BAR_ACTION_ACTIVATED = "bx-game-bar-action-activated", BxEvent.MICROPHONE_STATE_CHANGED = "bx-microphone-state-changed", BxEvent.SPEAKER_STATE_CHANGED = "bx-speaker-state-changed", BxEvent.VIDEO_VISIBILITY_CHANGED = "bx-video-visibility-changed", BxEvent.CAPTURE_SCREENSHOT = "bx-capture-screenshot", BxEvent.POINTER_LOCK_REQUESTED = "bx-pointer-lock-requested", BxEvent.POINTER_LOCK_EXITED = "bx-pointer-lock-exited", BxEvent.NAVIGATION_FOCUS_CHANGED = "bx-nav-focus-changed", BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED = "bx-gh-pages-force-native-mkb-updated", BxEvent.XCLOUD_DIALOG_SHOWN = "bx-xcloud-dialog-shown", BxEvent.XCLOUD_DIALOG_DISMISSED = "bx-xcloud-dialog-dismissed", BxEvent.XCLOUD_GUIDE_MENU_SHOWN = "bx-xcloud-guide-menu-shown", BxEvent.XCLOUD_POLLING_MODE_CHANGED = "bx-xcloud-polling-mode-changed", BxEvent.XCLOUD_RENDERING_COMPONENT = "bx-xcloud-rendering-component", BxEvent.XCLOUD_ROUTER_HISTORY_READY = "bx-xcloud-router-history-ready"; + BxEvent.JUMP_BACK_IN_READY = "bx-jump-back-in-ready", BxEvent.POPSTATE = "bx-popstate", BxEvent.STREAM_SESSION_READY = "bx-stream-session-ready", BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED = "bx-custom-touch-layouts-loaded", BxEvent.TOUCH_LAYOUT_MANAGER_READY = "bx-touch-layout-manager-ready", BxEvent.REMOTE_PLAY_READY = "bx-remote-play-ready", BxEvent.REMOTE_PLAY_FAILED = "bx-remote-play-failed", BxEvent.DATA_CHANNEL_CREATED = "bx-data-channel-created", BxEvent.GAME_BAR_ACTION_ACTIVATED = "bx-game-bar-action-activated", BxEvent.MICROPHONE_STATE_CHANGED = "bx-microphone-state-changed", BxEvent.SPEAKER_STATE_CHANGED = "bx-speaker-state-changed", BxEvent.VIDEO_VISIBILITY_CHANGED = "bx-video-visibility-changed", BxEvent.CAPTURE_SCREENSHOT = "bx-capture-screenshot", BxEvent.POINTER_LOCK_REQUESTED = "bx-pointer-lock-requested", BxEvent.POINTER_LOCK_EXITED = "bx-pointer-lock-exited", BxEvent.NAVIGATION_FOCUS_CHANGED = "bx-nav-focus-changed", BxEvent.XCLOUD_DIALOG_SHOWN = "bx-xcloud-dialog-shown", BxEvent.XCLOUD_DIALOG_DISMISSED = "bx-xcloud-dialog-dismissed", BxEvent.XCLOUD_GUIDE_MENU_SHOWN = "bx-xcloud-guide-menu-shown", BxEvent.XCLOUD_POLLING_MODE_CHANGED = "bx-xcloud-polling-mode-changed", BxEvent.XCLOUD_RENDERING_COMPONENT = "bx-xcloud-rendering-component", BxEvent.XCLOUD_ROUTER_HISTORY_READY = "bx-xcloud-router-history-ready"; function dispatch(target, eventName, data) { if (!target) return; if (!eventName) { @@ -185,6 +185,33 @@ var GamepadKeyName = { 202: ["Right Stick Left", "↽"], 203: ["Right Stick Right", "⇁"] }; +class EventBus { + listeners = new Map; + static Script = new EventBus; + static Stream = new EventBus; + on(event, callback) { + if (!this.listeners.has(event)) this.listeners.set(event, new Set); + this.listeners.get(event).add(callback), BX_FLAGS.Debug && BxLogger.warning("EventBus", "on", event, callback); + } + off(event, callback) { + if (BX_FLAGS.Debug && BxLogger.warning("EventBus", "off", event, callback), !callback) { + this.listeners.delete(event); + return; + } + let callbacks = this.listeners.get(event); + if (!callbacks) return; + if (callbacks.delete(callback), callbacks.size === 0) this.listeners.delete(event); + } + offAll() { + this.listeners.clear(); + } + emit(event, payload) { + BX_FLAGS.Debug && BxLogger.warning("EventBus", "emit", event, payload); + let callbacks = this.listeners.get(event) || []; + for (let callback of callbacks) + callback(payload); + } +} class GhPagesUtils { static fetchLatestCommit() { NATIVE_FETCH("https://api.github.com/repos/redphx/better-xcloud/branches/gh-pages", { @@ -208,7 +235,7 @@ class GhPagesUtils { static getNativeMkbCustomList(update = !1) { let key = "BetterXcloud.GhPages.ForceNativeMkb"; update && NATIVE_FETCH(GhPagesUtils.getUrl("native-mkb/ids.json")).then((response) => response.json()).then((json) => { - if (json.$schemaVersion === 1) window.localStorage.setItem(key, JSON.stringify(json)), BxEvent.dispatch(window, BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED); + if (json.$schemaVersion === 1) window.localStorage.setItem(key, JSON.stringify(json)), EventBus.Script.emit("listForcedNativeMkbUpdated", {}); }); let info = JSON.parse(window.localStorage.getItem(key) || "{}"); if (info.$schemaVersion !== 1) return window.localStorage.removeItem(key), {}; @@ -953,7 +980,7 @@ class BaseSettingsStore { return this.settings[key]; } setSetting(key, value, emitEvent = !1) { - return value = this.validateValue("set", key, value), this.settings[key] = this.validateValue("get", key, value), this.saveSettings(), emitEvent && BxEvent.dispatch(window, BxEvent.SETTINGS_CHANGED, { + return value = this.validateValue("set", key, value), this.settings[key] = this.validateValue("get", key, value), this.saveSettings(), emitEvent && EventBus.Script.emit("settingChanged", { storageKey: this.storageKey, settingKey: key, settingValue: value @@ -1587,7 +1614,7 @@ class GlobalSettingsStorage extends BaseSettingsStore { default: [], unsupported: !AppInterface && UserAgent.isMobile(), ready: (setting) => { - if (!setting.unsupported) setting.multipleOptions = GhPagesUtils.getNativeMkbCustomList(!0), window.addEventListener(BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED, (e) => { + if (!setting.unsupported) setting.multipleOptions = GhPagesUtils.getNativeMkbCustomList(!0), EventBus.Script.on("listForcedNativeMkbUpdated", () => { setting.multipleOptions = GhPagesUtils.getNativeMkbCustomList(); }); }, @@ -2250,7 +2277,7 @@ class StreamStatsCollector { } catch (e) {} } static setupEvents() { - window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { + EventBus.Stream.on("statePlaying", () => { StreamStatsCollector.getInstance().reset(); }); } @@ -2388,7 +2415,7 @@ class StreamStats { this.refreshStyles(), document.documentElement.appendChild(this.$container); } static setupEvents() { - window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { + EventBus.Stream.on("statePlaying", () => { let PREF_STATS_QUICK_GLANCE = getPref("stats.quickGlance.enabled"), PREF_STATS_SHOW_WHEN_PLAYING = getPref("stats.showWhenPlaying"), streamStats = StreamStats.getInstance(); if (PREF_STATS_SHOW_WHEN_PLAYING) streamStats.start(); else if (PREF_STATS_QUICK_GLANCE) streamStats.quickGlanceSetup(), !PREF_STATS_SHOW_WHEN_PLAYING && streamStats.start(!0); @@ -2688,7 +2715,7 @@ class StreamSettings { if (!STATES.browser.capabilities.deviceVibration) return; let mode = StreamSettings.getPref("deviceVibration.mode"), intensity = 0; if (mode === "on" || mode === "auto" && !hasGamepad()) intensity = StreamSettings.getPref("deviceVibration.intensity") / 100; - StreamSettings.settings.deviceVibrationIntensity = intensity, BxEvent.dispatch(window, BxEvent.DEVICE_VIBRATION_CHANGED); + StreamSettings.settings.deviceVibrationIntensity = intensity, EventBus.Script.emit("deviceVibrationUpdated", {}); } static async refreshMkbSettings() { let settings = StreamSettings.settings, presetId = StreamSettings.getPref("mkb.p1.preset.mappingId"), orgPreset = await MkbMappingPresetsTable.getInstance().getPreset(presetId), orgPresetData = orgPreset.data, converted = { @@ -2702,12 +2729,12 @@ class StreamSettings { if (typeof keyName === "string") converted.mapping[keyName] = buttonIndex; } let mouse = converted.mouse; - mouse["sensitivityX"] *= 0.001, mouse["sensitivityY"] *= 0.001, mouse["deadzoneCounterweight"] *= 0.01, settings.mkbPreset = converted, setPref("mkb.p1.preset.mappingId", orgPreset.id), BxEvent.dispatch(window, BxEvent.MKB_UPDATED); + mouse["sensitivityX"] *= 0.001, mouse["sensitivityY"] *= 0.001, mouse["deadzoneCounterweight"] *= 0.01, settings.mkbPreset = converted, setPref("mkb.p1.preset.mappingId", orgPreset.id), EventBus.Script.emit("mkbSettingUpdated", {}); } static async refreshKeyboardShortcuts() { let settings = StreamSettings.settings, presetId = StreamSettings.getPref("keyboardShortcuts.preset.inGameId"); if (presetId === 0) { - settings.keyboardShortcuts = null, setPref("keyboardShortcuts.preset.inGameId", presetId), BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED); + settings.keyboardShortcuts = null, setPref("keyboardShortcuts.preset.inGameId", presetId), EventBus.Script.emit("keyboardShortcutsUpdated", {}); return; } let orgPreset = await KeyboardShortcutsTable.getInstance().getPreset(presetId), orgPresetData = orgPreset.data.mapping, converted = {}, action; @@ -2715,7 +2742,7 @@ class StreamSettings { let info = orgPresetData[action], key = `${info.code}:${info.modifiers || 0}`; converted[key] = action; } - settings.keyboardShortcuts = converted, setPref("keyboardShortcuts.preset.inGameId", orgPreset.id), BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED); + settings.keyboardShortcuts = converted, setPref("keyboardShortcuts.preset.inGameId", orgPreset.id), EventBus.Script.emit("keyboardShortcutsUpdated", {}); } static async refreshAllSettings() { window.BX_STREAM_SETTINGS = StreamSettings.settings, await StreamSettings.refreshControllerSettings(), await StreamSettings.refreshMkbSettings(), await StreamSettings.refreshKeyboardShortcuts(); @@ -2742,7 +2769,7 @@ class MkbPopup { $btnActivate; mkbHandler; constructor() { - this.render(), window.addEventListener(BxEvent.KEYBOARD_SHORTCUTS_UPDATED, (e) => { + this.render(), EventBus.Script.on("keyboardShortcutsUpdated", () => { let $newButton = this.createActivateButton(); this.$btnActivate.replaceWith($newButton), this.$btnActivate = $newButton; }); @@ -3237,11 +3264,11 @@ class EmulatedMkbHandler extends MkbHandler { this.waitForMouseData(!0), this.mouseDataProvider?.stop(); } static setupEvents() { - if (window.addEventListener(BxEvent.STREAM_PLAYING, () => { + if (EventBus.Stream.on("statePlaying", () => { if (STATES.currentStream.titleInfo?.details.hasMkbSupport) NativeMkbHandler.getInstance()?.init(); else EmulatedMkbHandler.getInstance()?.init(); }), EmulatedMkbHandler.isAllowed()) - window.addEventListener(BxEvent.MKB_UPDATED, () => { + EventBus.Script.on("mkbSettingUpdated", () => { EmulatedMkbHandler.getInstance()?.refreshPresetData(); }); } @@ -4756,7 +4783,6 @@ class BxNumberStepper extends HTMLInputElement { $btnInc; $btnDec; $range; - onInput; onRangeInput; onClick; onPointerUp; @@ -4783,7 +4809,7 @@ class BxNumberStepper extends HTMLInputElement { class: options.hideSlider ? "bx-focusable" : "", tabindex: options.hideSlider ? 0 : -1 }, "+"))); - if (self.$text = $text, self.$btnInc = $btnInc, self.$btnDec = $btnDec, self.onChange = onChange, self.onInput = BxNumberStepper.onInput.bind(self), self.onRangeInput = BxNumberStepper.onRangeInput.bind(self), self.onClick = BxNumberStepper.onClick.bind(self), self.onPointerUp = BxNumberStepper.onPointerUp.bind(self), self.onPointerDown = BxNumberStepper.onPointerDown.bind(self), self.controlMin = min, self.controlMax = max, self.isHolding = !1, self.options = options, self.uiMin = options.reverse ? -max : min, self.uiMax = options.reverse ? -min : max, self.steps = Math.max(options.steps || 1, 1), BxNumberStepper.setValue.call(self, value), options.disabled) return $btnInc.disabled = !0, $btnInc.classList.add("bx-inactive"), $btnDec.disabled = !0, $btnDec.classList.add("bx-inactive"), self.disabled = !0, self; + if (self.$text = $text, self.$btnInc = $btnInc, self.$btnDec = $btnDec, self.onChange = onChange, self.onRangeInput = BxNumberStepper.onRangeInput.bind(self), self.onClick = BxNumberStepper.onClick.bind(self), self.onPointerUp = BxNumberStepper.onPointerUp.bind(self), self.onPointerDown = BxNumberStepper.onPointerDown.bind(self), self.controlMin = min, self.controlMax = max, self.isHolding = !1, self.options = options, self.uiMin = options.reverse ? -max : min, self.uiMax = options.reverse ? -min : max, self.steps = Math.max(options.steps || 1, 1), BxNumberStepper.setValue.call(self, value), options.disabled) return $btnInc.disabled = !0, $btnInc.classList.add("bx-inactive"), $btnDec.disabled = !0, $btnDec.classList.add("bx-inactive"), self.disabled = !0, self; if ($range = CE("input", { id: `bx_inp_setting_${key}`, type: "range", @@ -4792,7 +4818,7 @@ class BxNumberStepper extends HTMLInputElement { value: options.reverse ? -value : value, step: self.steps, tabindex: 0 - }), self.$range = $range, options.hideSlider && $range.classList.add("bx-gone"), $range.addEventListener("input", self.onRangeInput), self.addEventListener("input", self.onInput), self.appendChild($range), options.ticks || options.exactTicks) { + }), self.$range = $range, options.hideSlider && $range.classList.add("bx-gone"), self.addEventListener("input", self.onRangeInput), self.appendChild($range), options.ticks || options.exactTicks) { let markersId = `markers-${key}`, $markers = CE("datalist", { id: markersId }); if ($range.setAttribute("list", markersId), options.exactTicks) { let start = Math.max(Math.floor(min / options.exactTicks), 1) * options.exactTicks; @@ -4823,9 +4849,6 @@ class BxNumberStepper extends HTMLInputElement { static normalizeValue(value) { return value = parseInt(value), value = Math.max(this.controlMin, value), value = Math.min(this.controlMax, value), value; } - static onInput(e) { - BxEvent.dispatch(this.$range, "input"); - } static onRangeInput(e) { let value = parseInt(e.target.value); if (this.options.reverse) value *= -1; @@ -6116,12 +6139,9 @@ class SettingsDialog extends NavigationDialog { }, onCreated: (setting, $elm) => { let $range = $elm.querySelector("input[type=range"); - window.addEventListener(BxEvent.SETTINGS_CHANGED, (e) => { - let { storageKey, settingKey, settingValue } = e; - if (storageKey !== "BetterXcloud" || settingKey !== "audio.volume") return; - $range.value = settingValue, BxEvent.dispatch($range, "input", { - ignoreOnChange: !0 - }); + EventBus.Script.on("settingChanged", (payload) => { + let { storageKey, settingKey, settingValue } = payload; + if (storageKey === "BetterXcloud" && settingKey === "audio.volume") $range.value = settingValue, BxEvent.dispatch($range, "input", { ignoreOnChange: !0 }); }); } }] @@ -6968,7 +6988,7 @@ var BxExposed = { if (touchControllerAvailability === "off") supportedInputTypes = supportedInputTypes.filter((i) => i !== "CustomTouchOverlay" && i !== "GenericTouch"), titleInfo.details.supportedTabs = []; if (titleInfo.details.hasNativeTouchSupport = supportedInputTypes.includes("NativeTouch"), titleInfo.details.hasTouchSupport = titleInfo.details.hasNativeTouchSupport || supportedInputTypes.includes("CustomTouchOverlay") || supportedInputTypes.includes("GenericTouch"), !titleInfo.details.hasTouchSupport && touchControllerAvailability === "all") titleInfo.details.hasFakeTouchSupport = !0, supportedInputTypes.push("GenericTouch"); } - return titleInfo.details.supportedInputTypes = supportedInputTypes, STATES.currentStream.titleInfo = titleInfo, BxEvent.dispatch(window, BxEvent.TITLE_INFO_READY), titleInfo; + return titleInfo.details.supportedInputTypes = supportedInputTypes, STATES.currentStream.titleInfo = titleInfo, EventBus.Script.emit("titleInfoReady", {}), titleInfo; }, setupGainNode: ($media, audioStream) => { if ($media instanceof HTMLAudioElement) $media.muted = !0, $media.addEventListener("playing", (e) => { @@ -7290,7 +7310,7 @@ class XhomeInterceptor { return NATIVE_FETCH(request); } static async handleConfiguration(request) { - BxEvent.dispatch(window, BxEvent.STREAM_STARTING); + EventBus.Stream.emit("stateStarting", {}); let response = await NATIVE_FETCH(request), obj = await response.clone().json(), serverDetails = obj.serverDetails, pairs = [ ["ipAddress", "port"], ["ipV4Address", "ipV4Port"], @@ -7335,7 +7355,7 @@ class XhomeInterceptor { }), NATIVE_FETCH(request); } static async handlePlay(request) { - BxEvent.dispatch(window, BxEvent.STREAM_LOADING); + EventBus.Stream.emit("stateLoading", {}); let body = await request.clone().json(), newRequest = new Request(request, { body: JSON.stringify(body) }); @@ -7744,7 +7764,7 @@ class XcloudInterceptor { ip && request.headers.set("X-Forwarded-For", ip); } let response = await NATIVE_FETCH(request, init); - if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response; + if (response.status !== 200) return EventBus.Script.emit("xcloudServerUnavailable", {}), response; let obj = await response.clone().json(); RemotePlayManager.getInstance()?.setXcloudToken(obj.gsToken); let serverRegex = /\/\/(\w+)\./, serverExtra = XcloudInterceptor.SERVER_EXTRA_INFO, region; @@ -7756,7 +7776,7 @@ class XcloudInterceptor { else region.contintent = "other"; region.shortName = shortName.toUpperCase(), STATES.serverRegions[region.name] = Object.assign({}, region); } - BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY); + EventBus.Script.emit("xcloudServerReady", {}); let preferredRegion = getPreferredServerRegion(); if (preferredRegion && preferredRegion in STATES.serverRegions) { let tmp = Object.assign({}, STATES.serverRegions[preferredRegion]); @@ -7765,7 +7785,7 @@ class XcloudInterceptor { return STATES.gsToken = obj.gsToken, response.json = () => Promise.resolve(obj), response; } static async handlePlay(request, init) { - BxEvent.dispatch(window, BxEvent.STREAM_LOADING); + EventBus.Stream.emit("stateLoading", {}); let PREF_STREAM_TARGET_RESOLUTION = getPref("stream.video.resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream.locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url), badgeRegion = parsedUrl.host.split(".", 1)[0]; for (let regionName in STATES.serverRegions) { let region = STATES.serverRegions[regionName]; @@ -7803,7 +7823,7 @@ class XcloudInterceptor { else TouchController.enable(); let response = await NATIVE_FETCH(request, init), text = await response.clone().text(); if (!text.length) return response; - BxEvent.dispatch(window, BxEvent.STREAM_STARTING); + EventBus.Stream.emit("stateStarting", {}); let obj = JSON.parse(text), overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {}; overrides.inputConfiguration = overrides.inputConfiguration || {}, overrides.inputConfiguration.enableVibration = !0; let overrideMkb = null; @@ -8070,7 +8090,7 @@ function onHistoryChanged(e) { window.setTimeout(RemotePlayManager.detect, 10); let $settings = document.querySelector(".bx-settings-container"); if ($settings) $settings.classList.add("bx-gone"); - NavigationDialogManager.getInstance().hide(), LoadingScreen.reset(), window.setTimeout(HeaderSection.watchHeader, 2000), BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); + NavigationDialogManager.getInstance().hide(), LoadingScreen.reset(), window.setTimeout(HeaderSection.watchHeader, 2000), EventBus.Stream.emit("stateStopped", {}); } function setCodecPreferences(sdp, preferredCodec) { let h264Pattern = /a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g, profilePrefix = preferredCodec === "high" ? "4d" : preferredCodec === "low" ? "420" : "42e", preferredCodecIds = [], matches = sdp.matchAll(h264Pattern) || []; @@ -8427,7 +8447,7 @@ function patchVideoApi() { contrast: getPref("video.contrast"), brightness: getPref("video.brightness") }; - STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref("video.player.type"), playerOptions), BxEvent.dispatch(window, BxEvent.STREAM_PLAYING, { + STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref("video.player.type"), playerOptions), EventBus.Stream.emit("statePlaying", { $video: this }); }, nativePlay = HTMLMediaElement.prototype.play; @@ -8983,7 +9003,7 @@ class StreamUiHandler { if (!($elm instanceof HTMLElement)) return; let className = $elm.className || ""; if (className.includes("PureErrorPage")) { - BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE); + EventBus.Stream.emit("stateError", {}); return; } if (className.startsWith("StreamMenu-module__container")) { @@ -9111,9 +9131,7 @@ class DeviceVibrationManager { this.boundOnMessage = this.onMessage.bind(this), window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => { let dataChannel = e.dataChannel; if (dataChannel?.label === "input") this.reset(), this.dataChannel = dataChannel, this.setupDataChannel(); - }), window.addEventListener(BxEvent.DEVICE_VIBRATION_CHANGED, (e) => { - this.setupDataChannel(); - }); + }), EventBus.Script.on("deviceVibrationUpdated", () => this.setupDataChannel()); } setupDataChannel() { if (!this.dataChannel) return; @@ -9203,37 +9221,37 @@ window.addEventListener(BxEvent.POPSTATE, onHistoryChanged); window.addEventListener("popstate", onHistoryChanged); window.history.pushState = patchHistoryMethod("pushState"); window.history.replaceState = patchHistoryMethod("replaceState"); -window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, (e) => { - if (STATES.supportedRegion = !1, window.setTimeout(HeaderSection.watchHeader, 2000), document.querySelector("div[class^=UnsupportedMarketPage-module__container]")) SettingsDialog.getInstance().show(); -}, { once: !0 }); -window.addEventListener(BxEvent.XCLOUD_SERVERS_READY, (e) => { +EventBus.Script.on("xcloudServerUnavailable", () => { + if (EventBus.Script.off("xcloudServerUnavailable", null), STATES.supportedRegion = !1, window.setTimeout(HeaderSection.watchHeader, 2000), document.querySelector("div[class^=UnsupportedMarketPage-module__container]")) SettingsDialog.getInstance().show(); +}); +EventBus.Script.on("xcloudServerReady", () => { STATES.isSignedIn = !0, window.setTimeout(HeaderSection.watchHeader, 2000); }); -window.addEventListener(BxEvent.STREAM_LOADING, (e) => { +EventBus.Stream.on("stateLoading", () => { if (window.location.pathname.includes("/launch/") && STATES.currentStream.titleInfo) STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title); else STATES.currentStream.titleSlug = "remote-play"; }); -getPref("loadingScreen.gameArt.show") && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup); -window.addEventListener(BxEvent.STREAM_STARTING, (e) => { +getPref("loadingScreen.gameArt.show") && EventBus.Script.on("titleInfoReady", LoadingScreen.setup); +EventBus.Stream.on("stateStarting", () => { LoadingScreen.hide(); { let cursorHider = MouseCursorHider.getInstance(); if (cursorHider) cursorHider.start(), cursorHider.hide(); } }); -window.addEventListener(BxEvent.STREAM_PLAYING, (e) => { +EventBus.Stream.on("statePlaying", (payload) => { window.BX_STREAM_SETTINGS = StreamSettings.settings, StreamSettings.refreshAllSettings(), STATES.isPlaying = !0, StreamUiHandler.observe(); { let gameBar = GameBar.getInstance(); if (gameBar) gameBar.reset(), gameBar.enable(), gameBar.showBar(); KeyboardShortcutHandler.getInstance().start(); - let $video = e.$video; + let $video = payload.$video; ScreenshotManager.getInstance().updateCanvasSize($video.videoWidth, $video.videoHeight), getPref("localCoOp.enabled") && BxExposed.toggleLocalCoOp(getPref("localCoOp.enabled")); } updateVideoPlayer(); }); -window.addEventListener(BxEvent.STREAM_ERROR_PAGE, (e) => { - BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); +EventBus.Stream.on("stateError", () => { + EventBus.Stream.emit("stateStopped", {}); }); window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, (e) => { if (e.component === "product-detail") ProductDetailsPage.injectButtons(); @@ -9258,9 +9276,9 @@ function unload() { if (!STATES.isPlaying) return; KeyboardShortcutHandler.getInstance().stop(), EmulatedMkbHandler.getInstance()?.destroy(), NativeMkbHandler.getInstance()?.destroy(), DeviceVibrationManager.getInstance()?.reset(), STATES.currentStream.streamPlayer?.destroy(), STATES.isPlaying = !1, STATES.currentStream = {}, window.BX_EXPOSED.shouldShowSensorControls = !1, window.BX_EXPOSED.stopTakRendering = !1, NavigationDialogManager.getInstance().hide(), StreamStats.getInstance().destroy(), StreamBadges.getInstance().destroy(), MouseCursorHider.getInstance()?.stop(), TouchController.reset(), GameBar.getInstance()?.disable(); } -window.addEventListener(BxEvent.STREAM_STOPPED, unload); +EventBus.Stream.on("stateStopped", unload); window.addEventListener("pagehide", (e) => { - BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); + EventBus.Stream.emit("stateStopped", {}); }); window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, (e) => { ScreenshotManager.getInstance().takeScreenshot(); diff --git a/src/index.ts b/src/index.ts index cecd906..9065e9b 100755 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,7 @@ import { StreamSettings } from "./utils/stream-settings"; import { KeyboardShortcutHandler } from "./modules/mkb/keyboard-shortcut-handler"; import { GhPagesUtils } from "./utils/gh-pages"; import { DeviceVibrationManager } from "./modules/device-vibration-manager"; +import { EventBus } from "./utils/event-bus"; // Handle login page if (window.location.pathname.includes('/auth/msa')) { @@ -190,7 +191,9 @@ window.addEventListener('popstate', onHistoryChanged); window.history.pushState = patchHistoryMethod('pushState'); window.history.replaceState = patchHistoryMethod('replaceState'); -window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => { +EventBus.Script.on('xcloudServerUnavailable', () => { + EventBus.Script.off('xcloudServerUnavailable', null); + STATES.supportedRegion = false; window.setTimeout(HeaderSection.watchHeader, 2000); @@ -199,14 +202,14 @@ window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => { if ($unsupportedPage) { SettingsDialog.getInstance().show(); } -}, { once: true }); +}); -window.addEventListener(BxEvent.XCLOUD_SERVERS_READY, e => { +EventBus.Script.on('xcloudServerReady', () => { STATES.isSignedIn = true; window.setTimeout(HeaderSection.watchHeader, 2000); }); -window.addEventListener(BxEvent.STREAM_LOADING, e => { +EventBus.Stream.on('stateLoading', () => { // Get title ID for screenshot's name if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) { STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title); @@ -216,9 +219,9 @@ window.addEventListener(BxEvent.STREAM_LOADING, e => { }); // Setup loading screen -getPref(PrefKey.LOADING_SCREEN_GAME_ART) && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup); +getPref(PrefKey.LOADING_SCREEN_GAME_ART) && EventBus.Script.on('titleInfoReady', LoadingScreen.setup); -window.addEventListener(BxEvent.STREAM_STARTING, e => { +EventBus.Stream.on('stateStarting', () => { // Hide loading screen LoadingScreen.hide(); @@ -232,7 +235,7 @@ window.addEventListener(BxEvent.STREAM_STARTING, e => { } }); -window.addEventListener(BxEvent.STREAM_PLAYING, e => { +EventBus.Stream.on('statePlaying', payload => { window.BX_STREAM_SETTINGS = StreamSettings.settings; StreamSettings.refreshAllSettings(); @@ -251,7 +254,7 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => { KeyboardShortcutHandler.getInstance().start(); // Setup screenshot - const $video = (e as any).$video as HTMLVideoElement; + const $video = payload.$video as HTMLVideoElement; ScreenshotManager.getInstance().updateCanvasSize($video.videoWidth, $video.videoHeight); // Setup local co-op @@ -262,8 +265,8 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => { updateVideoPlayer(); }); -window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => { - BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); +EventBus.Stream.on('stateError', () => { + EventBus.Stream.emit('stateStopped', {}); }); isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => { @@ -340,9 +343,9 @@ function unload() { } } -window.addEventListener(BxEvent.STREAM_STOPPED, unload); +EventBus.Stream.on('stateStopped', unload); window.addEventListener('pagehide', e => { - BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); + EventBus.Stream.emit('stateStopped', {}); }); isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => { diff --git a/src/modules/device-vibration-manager.ts b/src/modules/device-vibration-manager.ts index fd33984..b86da56 100755 --- a/src/modules/device-vibration-manager.ts +++ b/src/modules/device-vibration-manager.ts @@ -1,6 +1,7 @@ import { AppInterface, STATES } from "@utils/global"; import { BxEvent } from "@utils/bx-event"; import { StreamSettings } from "@/utils/stream-settings"; +import { EventBus } from "@/utils/event-bus"; const VIBRATION_DATA_MAP = { gamepadIndex: 8, @@ -47,9 +48,7 @@ export class DeviceVibrationManager { } }); - window.addEventListener(BxEvent.DEVICE_VIBRATION_CHANGED, e => { - this.setupDataChannel(); - }); + EventBus.Script.on('deviceVibrationUpdated', () => this.setupDataChannel()); } private setupDataChannel() { diff --git a/src/modules/mkb/mkb-handler.ts b/src/modules/mkb/mkb-handler.ts index a2ef204..c6644e5 100755 --- a/src/modules/mkb/mkb-handler.ts +++ b/src/modules/mkb/mkb-handler.ts @@ -18,6 +18,7 @@ import { MkbPopup } from "./mkb-popup"; import type { MkbConvertedPresetData } from "@/types/presets"; import { StreamSettings } from "@/utils/stream-settings"; import { ShortcutAction } from "@/enums/shortcut-actions"; +import { EventBus } from "@/utils/event-bus"; const PointerToMouseButton = { 1: 0, @@ -639,7 +640,7 @@ export class EmulatedMkbHandler extends MkbHandler { static setupEvents() { if (isFullVersion()) { - window.addEventListener(BxEvent.STREAM_PLAYING, () => { + EventBus.Stream.on('statePlaying', () => { if (STATES.currentStream.titleInfo?.details.hasMkbSupport) { // Enable native MKB in Android app NativeMkbHandler.getInstance()?.init(); @@ -649,7 +650,7 @@ export class EmulatedMkbHandler extends MkbHandler { }); if (EmulatedMkbHandler.isAllowed()) { - window.addEventListener(BxEvent.MKB_UPDATED, () => { + EventBus.Script.on('mkbSettingUpdated', () => { EmulatedMkbHandler.getInstance()?.refreshPresetData(); }); } diff --git a/src/modules/mkb/mkb-popup.ts b/src/modules/mkb/mkb-popup.ts index 9808f08..3d9616d 100755 --- a/src/modules/mkb/mkb-popup.ts +++ b/src/modules/mkb/mkb-popup.ts @@ -1,12 +1,12 @@ import { CE, createButton, ButtonStyle, type BxButtonOptions } from "@/utils/html"; import { t } from "@/utils/translation"; -import { BxEvent } from "@/utils/bx-event"; import { ShortcutAction } from "@/enums/shortcut-actions"; import { SettingsDialog } from "../ui/dialog/settings-dialog"; import type { MkbHandler } from "./base-mkb-handler"; import { NativeMkbHandler } from "./native-mkb-handler"; import { StreamSettings } from "@/utils/stream-settings"; import { KeyHelper } from "./key-helper"; +import { EventBus } from "@/utils/event-bus"; type MkbPopupType = 'virtual' | 'native'; @@ -24,7 +24,7 @@ export class MkbPopup { constructor() { this.render(); - window.addEventListener(BxEvent.KEYBOARD_SHORTCUTS_UPDATED, e => { + EventBus.Script.on('keyboardShortcutsUpdated', () => { const $newButton = this.createActivateButton(); this.$btnActivate.replaceWith($newButton); this.$btnActivate = $newButton; diff --git a/src/modules/stream/stream-stats.ts b/src/modules/stream/stream-stats.ts index 4ce2c84..13840d6 100755 --- a/src/modules/stream/stream-stats.ts +++ b/src/modules/stream/stream-stats.ts @@ -1,4 +1,3 @@ -import { BxEvent } from "@utils/bx-event" import { CE } from "@utils/html" import { t } from "@utils/translation" import { STATES } from "@utils/global" @@ -7,6 +6,7 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage" import { StreamStatsCollector, type StreamStatGrade } from "@/utils/stream-stats-collector" import { BxLogger } from "@/utils/bx-logger" import { StreamStat } from "@/enums/pref-values" +import { EventBus } from "@/utils/event-bus" export class StreamStats { @@ -230,7 +230,7 @@ export class StreamStats { } static setupEvents() { - window.addEventListener(BxEvent.STREAM_PLAYING, e => { + EventBus.Stream.on('statePlaying', () => { const PREF_STATS_QUICK_GLANCE = getPref(PrefKey.STATS_QUICK_GLANCE_ENABLED); const PREF_STATS_SHOW_WHEN_PLAYING = getPref(PrefKey.STATS_SHOW_WHEN_PLAYING); diff --git a/src/modules/stream/stream-ui.ts b/src/modules/stream/stream-ui.ts index ad64b4c..ee4ea1e 100755 --- a/src/modules/stream/stream-ui.ts +++ b/src/modules/stream/stream-ui.ts @@ -1,11 +1,11 @@ import { STATES } from "@utils/global.ts"; import { createSvgIcon } from "@utils/html.ts"; import { BxIcon } from "@utils/bx-icon"; -import { BxEvent } from "@utils/bx-event.ts"; import { t } from "@utils/translation.ts"; import { StreamBadges } from "./stream-badges.ts"; import { StreamStats } from "./stream-stats.ts"; import { SettingsDialog } from "../ui/dialog/settings-dialog.ts"; +import { EventBus } from "@/utils/event-bus.ts"; export class StreamUiHandler { @@ -243,7 +243,7 @@ export class StreamUiHandler { // Error Page: .PureErrorPage.ErrorScreen if (className.includes('PureErrorPage')) { - BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE); + EventBus.Stream.emit('stateError', {}); return; } diff --git a/src/modules/ui/dialog/settings-dialog.ts b/src/modules/ui/dialog/settings-dialog.ts index 047b7a6..536f911 100755 --- a/src/modules/ui/dialog/settings-dialog.ts +++ b/src/modules/ui/dialog/settings-dialog.ts @@ -30,6 +30,7 @@ import { SuggestionsSetting } from "./settings/suggestions"; import { StreamSettings } from "@/utils/stream-settings"; import { MkbExtraSettings } from "./settings/mkb-extra"; import { BxExposed } from "@/utils/bx-exposed"; +import { EventBus } from "@/utils/event-bus"; type SettingTabSectionItem = Partial<{ @@ -434,16 +435,13 @@ export class SettingsDialog extends NavigationDialog { }, onCreated: (setting: SettingTabSectionItem, $elm: HTMLElement) => { const $range = $elm.querySelector('input[type=range')!; - window.addEventListener(BxEvent.SETTINGS_CHANGED, e => { - const { storageKey, settingKey, settingValue } = e as any; - if (storageKey !== StorageKey.GLOBAL || settingKey !== PrefKey.AUDIO_VOLUME) { - return; - } - $range.value = settingValue; - BxEvent.dispatch($range, 'input', { - ignoreOnChange: true, - }); + EventBus.Script.on('settingChanged', payload => { + const { storageKey, settingKey, settingValue } = payload; + if (storageKey === StorageKey.GLOBAL && settingKey === PrefKey.AUDIO_VOLUME) { + $range.value = settingValue; + BxEvent.dispatch($range, 'input', { ignoreOnChange: true }); + } }); }, }], diff --git a/src/utils/bx-event.ts b/src/utils/bx-event.ts index a53e381..e240402 100755 --- a/src/utils/bx-event.ts +++ b/src/utils/bx-event.ts @@ -7,23 +7,6 @@ export namespace BxEvent { export const JUMP_BACK_IN_READY = 'bx-jump-back-in-ready'; export const POPSTATE = 'bx-popstate'; - export const TITLE_INFO_READY = 'bx-title-info-ready'; - - export const SETTINGS_CHANGED = 'bx-settings-changed'; - - export const STREAM_LOADING = 'bx-stream-loading'; - export const STREAM_STARTING = 'bx-stream-starting'; - export const STREAM_STARTED = 'bx-stream-started'; - export const STREAM_PLAYING = 'bx-stream-playing'; - export const STREAM_STOPPED = 'bx-stream-stopped'; - export const STREAM_ERROR_PAGE = 'bx-stream-error-page'; - - export const STREAM_WEBRTC_CONNECTED = 'bx-stream-webrtc-connected'; - export const STREAM_WEBRTC_DISCONNECTED = 'bx-stream-webrtc-disconnected'; - - export const MKB_UPDATED = 'bx-mkb-updated'; - export const KEYBOARD_SHORTCUTS_UPDATED = 'bx-keyboard-shortcuts-updated'; - // export const STREAM_EVENT_TARGET_READY = 'bx-stream-event-target-ready'; export const STREAM_SESSION_READY = 'bx-stream-session-ready'; @@ -33,11 +16,7 @@ export namespace BxEvent { export const REMOTE_PLAY_READY = 'bx-remote-play-ready'; export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed'; - export const XCLOUD_SERVERS_READY = 'bx-servers-ready'; - export const XCLOUD_SERVERS_UNAVAILABLE = 'bx-servers-unavailable'; - export const DATA_CHANNEL_CREATED = 'bx-data-channel-created'; - export const DEVICE_VIBRATION_CHANGED = 'bx-device-vibration-changed'; export const GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated'; export const MICROPHONE_STATE_CHANGED = 'bx-microphone-state-changed'; @@ -51,8 +30,6 @@ export namespace BxEvent { export const NAVIGATION_FOCUS_CHANGED = 'bx-nav-focus-changed'; - export const GH_PAGES_FORCE_NATIVE_MKB_UPDATED = 'bx-gh-pages-force-native-mkb-updated'; - // xCloud Dialog events export const XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown'; export const XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed'; @@ -86,7 +63,7 @@ export namespace BxEvent { target.dispatchEvent(event); AppInterface && AppInterface.onEvent(eventName); - BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', eventName, data) + BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', eventName, data); } } diff --git a/src/utils/bx-exposed.ts b/src/utils/bx-exposed.ts index 4fabb2c..128cd77 100755 --- a/src/utils/bx-exposed.ts +++ b/src/utils/bx-exposed.ts @@ -1,7 +1,6 @@ import { isFullVersion } from "@macros/build" with { type: "macro" }; import { ControllerShortcut } from "@/modules/controller-shortcut"; -import { BxEvent } from "@utils/bx-event"; import { deepClone, STATES } from "@utils/global"; import { BxLogger } from "./bx-logger"; import { BX_FLAGS } from "./bx-flags"; @@ -12,6 +11,7 @@ import { GamePassCloudGallery } from "@/enums/game-pass-gallery"; import { TouchController } from "@/modules/touch-controller"; import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values"; import { Patcher, type PatchPage } from "@/modules/patcher/patcher"; +import { EventBus } from "./event-bus"; export enum SupportedInputType { CONTROLLER = 'Controller', @@ -139,7 +139,7 @@ export const BxExposed = { // Save this info in STATES STATES.currentStream.titleInfo = titleInfo; - BxEvent.dispatch(window, BxEvent.TITLE_INFO_READY); + EventBus.Script.emit('titleInfoReady', {}); return titleInfo; }, diff --git a/src/utils/event-bus.ts b/src/utils/event-bus.ts new file mode 100644 index 0000000..f1611fe --- /dev/null +++ b/src/utils/event-bus.ts @@ -0,0 +1,81 @@ +import type { PrefKey, StorageKey } from "@/enums/pref-keys"; +import { BX_FLAGS } from "./bx-flags"; +import { BxLogger } from "./bx-logger"; + +type EventCallback = (payload: T) => void; + +type ScriptEvents = { + xcloudServerReady: {}; + xcloudServerUnavailable: {}; + + titleInfoReady: {}; + settingChanged: { + storageKey: StorageKey; + settingKey: PrefKey; + settingValue: any; + }; + + mkbSettingUpdated: {}; + keyboardShortcutsUpdated: {}; + deviceVibrationUpdated: {}; + + // GH pages + listForcedNativeMkbUpdated: {}; +}; + +type StreamEvents = { + stateLoading: {}; + stateStarting: {}; + statePlaying: { $video?: HTMLVideoElement }; + stateStopped: {}; + stateError: {}; +}; + +export class EventBus> { + private listeners: Map>> = new Map(); + + static readonly Script = new EventBus(); + static readonly Stream = new EventBus(); + + on(event: K, callback: EventCallback): void { + if (!this.listeners.has(event)) { + this.listeners.set(event, new Set()); + } + this.listeners.get(event)!.add(callback); + + BX_FLAGS.Debug && BxLogger.warning('EventBus', 'on', event, callback); + } + + off(event: K, callback: EventCallback | null): void { + BX_FLAGS.Debug && BxLogger.warning('EventBus', 'off', event, callback); + + if (!callback) { + // Remove all listener callbacks + this.listeners.delete(event); + return; + } + + const callbacks = this.listeners.get(event); + if (!callbacks) { + return; + } + + callbacks.delete(callback); + if (callbacks.size === 0) { + this.listeners.delete(event); + } + } + + offAll(): void { + this.listeners.clear(); + } + + emit(event: K, payload: TEvents[K]): void { + BX_FLAGS.Debug && BxLogger.warning('EventBus', 'emit', event, payload); + + const callbacks = this.listeners.get(event) || []; + for (const callback of callbacks) { + callback(payload); + } + } +} diff --git a/src/utils/gh-pages.ts b/src/utils/gh-pages.ts index 83599a4..d7cf386 100755 --- a/src/utils/gh-pages.ts +++ b/src/utils/gh-pages.ts @@ -1,7 +1,7 @@ import { StorageKey } from "@/enums/pref-keys"; import { NATIVE_FETCH } from "./bx-flags"; import { BxLogger } from "./bx-logger"; -import { BxEvent } from "./bx-event"; +import { EventBus } from "./event-bus"; export type ForceNativeMkbResponse = { @@ -53,7 +53,7 @@ export class GhPagesUtils { if (json.$schemaVersion === supportedSchema) { // Save to storage window.localStorage.setItem(key, JSON.stringify(json)); - BxEvent.dispatch(window, BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED); + EventBus.Script.emit('listForcedNativeMkbUpdated', {}); } }); diff --git a/src/utils/history.ts b/src/utils/history.ts index 4dfb104..0b77093 100755 --- a/src/utils/history.ts +++ b/src/utils/history.ts @@ -3,14 +3,15 @@ import { LoadingScreen } from "@modules/loading-screen"; import { RemotePlayManager } from "@/modules/remote-play-manager"; import { HeaderSection } from "@/modules/ui/header"; import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog"; +import { EventBus } from "./event-bus"; export function patchHistoryMethod(type: 'pushState' | 'replaceState') { const orig = window.history[type]; return function(...args: any[]) { BxEvent.dispatch(window, BxEvent.POPSTATE, { - arguments: args, - }); + arguments: args, + }); // @ts-ignore return orig.apply(this, arguments); @@ -38,5 +39,5 @@ export function onHistoryChanged(e: PopStateEvent) { LoadingScreen.reset(); window.setTimeout(HeaderSection.watchHeader, 2000); - BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); + EventBus.Stream.emit('stateStopped', {}); } diff --git a/src/utils/monkey-patches.ts b/src/utils/monkey-patches.ts index 4069221..12c62e0 100755 --- a/src/utils/monkey-patches.ts +++ b/src/utils/monkey-patches.ts @@ -7,6 +7,7 @@ import { PrefKey } from "@/enums/pref-keys"; import { getPref, getPrefDefinition } from "./settings-storages/global-settings-storage"; import { CodecProfile } from "@/enums/pref-values"; import type { SettingDefinition } from "@/types/setting-definition"; +import { EventBus } from "./event-bus"; export function patchVideoApi() { const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.UI_SKIP_SPLASH_VIDEO); @@ -27,9 +28,9 @@ export function patchVideoApi() { } satisfies StreamPlayerOptions; STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref(PrefKey.VIDEO_PLAYER_TYPE), playerOptions); - BxEvent.dispatch(window, BxEvent.STREAM_PLAYING, { - $video: this, - }); + EventBus.Stream.emit('statePlaying', { + $video: this, + }) } const nativePlay = HTMLMediaElement.prototype.play; diff --git a/src/utils/settings-storages/base-settings-storage.ts b/src/utils/settings-storages/base-settings-storage.ts index 5889157..b10a9b8 100755 --- a/src/utils/settings-storages/base-settings-storage.ts +++ b/src/utils/settings-storages/base-settings-storage.ts @@ -1,16 +1,16 @@ -import type { PrefKey } from "@/enums/pref-keys"; +import type { PrefKey, StorageKey } from "@/enums/pref-keys"; import type { NumberStepperParams, SettingAction, SettingDefinitions } from "@/types/setting-definition"; -import { BxEvent } from "../bx-event"; import { t } from "../translation"; import { SCRIPT_VARIANT } from "../global"; +import { EventBus } from "../event-bus"; export class BaseSettingsStore { private storage: Storage; - private storageKey: string; + private storageKey: StorageKey; private _settings: object | null; private definitions: SettingDefinitions; - constructor(storageKey: string, definitions: SettingDefinitions) { + constructor(storageKey: StorageKey, definitions: SettingDefinitions) { this.storage = window.localStorage; this.storageKey = storageKey; @@ -93,7 +93,7 @@ export class BaseSettingsStore { this.settings[key] = this.validateValue('get', key, value); this.saveSettings(); - emitEvent && BxEvent.dispatch(window, BxEvent.SETTINGS_CHANGED, { + emitEvent && EventBus.Script.emit('settingChanged', { storageKey: this.storageKey, settingKey: key, settingValue: value, diff --git a/src/utils/settings-storages/global-settings-storage.ts b/src/utils/settings-storages/global-settings-storage.ts index 636ab85..24622fe 100755 --- a/src/utils/settings-storages/global-settings-storage.ts +++ b/src/utils/settings-storages/global-settings-storage.ts @@ -12,7 +12,7 @@ import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerSty import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table"; import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table"; import { GhPagesUtils } from "../gh-pages"; -import { BxEvent } from "../bx-event"; +import { EventBus } from "../event-bus"; function getSupportedCodecProfiles() { @@ -432,7 +432,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage { if (!setting.unsupported) { (setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList(true); - window.addEventListener(BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED, e => { + EventBus.Script.on('listForcedNativeMkbUpdated', () => { (setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList(); }); } diff --git a/src/utils/stream-settings.ts b/src/utils/stream-settings.ts index 4d129de..8bc2145 100755 --- a/src/utils/stream-settings.ts +++ b/src/utils/stream-settings.ts @@ -10,10 +10,10 @@ import { hasGamepad } from "./gamepad"; import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table"; import type { GamepadKey } from "@/enums/gamepad"; import { MkbPresetKey, MouseConstant } from "@/enums/mkb"; -import { BxEvent } from "./bx-event"; import { KeyboardShortcutDefaultId, KeyboardShortcutsTable } from "./local-db/keyboard-shortcuts-table"; import { ShortcutAction } from "@/enums/shortcut-actions"; import { KeyHelper } from "@/modules/mkb/key-helper"; +import { EventBus } from "./event-bus"; export type StreamSettingsData = { @@ -110,7 +110,7 @@ export class StreamSettings { } StreamSettings.settings.deviceVibrationIntensity = intensity; - BxEvent.dispatch(window, BxEvent.DEVICE_VIBRATION_CHANGED); + EventBus.Script.emit('deviceVibrationUpdated', {}); } static async refreshMkbSettings() { @@ -148,7 +148,7 @@ export class StreamSettings { settings.mkbPreset = converted; setPref(PrefKey.MKB_P1_MAPPING_PRESET_ID, orgPreset.id); - BxEvent.dispatch(window, BxEvent.MKB_UPDATED); + EventBus.Script.emit('mkbSettingUpdated', {}); } static async refreshKeyboardShortcuts() { @@ -159,7 +159,7 @@ export class StreamSettings { settings.keyboardShortcuts = null; setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, presetId); - BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED); + EventBus.Script.emit('keyboardShortcutsUpdated', {}); return; } @@ -179,7 +179,7 @@ export class StreamSettings { settings.keyboardShortcuts = converted; setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, orgPreset.id); - BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED); + EventBus.Script.emit('keyboardShortcutsUpdated', {}); } static async refreshAllSettings() { diff --git a/src/utils/stream-stats-collector.ts b/src/utils/stream-stats-collector.ts index 2015a90..357440c 100755 --- a/src/utils/stream-stats-collector.ts +++ b/src/utils/stream-stats-collector.ts @@ -1,10 +1,10 @@ import { PrefKey } from "@/enums/pref-keys"; -import { BxEvent } from "./bx-event"; import { STATES } from "./global"; import { humanFileSize, secondsToHm } from "./html"; import { getPref } from "./settings-storages/global-settings-storage"; import { BxLogger } from "./bx-logger"; import { StreamStat } from "@/enums/pref-values"; +import { EventBus } from "./event-bus"; export type StreamStatGrade = '' | 'bad' | 'ok' | 'good'; @@ -310,9 +310,8 @@ export class StreamStatsCollector { } static setupEvents() { - window.addEventListener(BxEvent.STREAM_PLAYING, e => { - const statsCollector = StreamStatsCollector.getInstance(); - statsCollector.reset(); + EventBus.Stream.on('statePlaying', () => { + StreamStatsCollector.getInstance().reset(); }); } } diff --git a/src/utils/xcloud-interceptor.ts b/src/utils/xcloud-interceptor.ts index b4b7041..0b5811c 100755 --- a/src/utils/xcloud-interceptor.ts +++ b/src/utils/xcloud-interceptor.ts @@ -4,7 +4,6 @@ import { LoadingScreen } from "@modules/loading-screen"; import { RemotePlayManager } from "@/modules/remote-play-manager"; import { StreamBadges } from "@modules/stream/stream-badges"; import { TouchController } from "@modules/touch-controller"; -import { BxEvent } from "./bx-event"; import { NATIVE_FETCH, BX_FLAGS } from "./bx-flags"; import { STATES } from "./global"; import { generateMsDeviceInfo, getOsNameFromResolution, patchIceCandidates } from "./network"; @@ -13,6 +12,7 @@ import { BypassServerIps } from "@/enums/bypass-servers"; import { PrefKey } from "@/enums/pref-keys"; import { getPref } from "./settings-storages/global-settings-storage"; import { NativeMkbMode, StreamResolution, TouchControllerMode } from "@/enums/pref-values"; +import { EventBus } from "./event-bus"; export class XcloudInterceptor { private static readonly SERVER_EXTRA_INFO: Record = { @@ -52,7 +52,7 @@ export class XcloudInterceptor { const response = await NATIVE_FETCH(request, init); if (response.status !== 200) { // Unsupported region - BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE); + EventBus.Script.emit('xcloudServerUnavailable', {}); return response; } @@ -89,7 +89,7 @@ export class XcloudInterceptor { STATES.serverRegions[region.name] = Object.assign({}, region); } - BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY); + EventBus.Script.emit('xcloudServerReady', {}); const preferredRegion = getPreferredServerRegion(); if (preferredRegion && preferredRegion in STATES.serverRegions) { @@ -107,7 +107,7 @@ export class XcloudInterceptor { } private static async handlePlay(request: RequestInfo | URL, init?: RequestInit) { - BxEvent.dispatch(window, BxEvent.STREAM_LOADING); + EventBus.Stream.emit('stateLoading', {}); const PREF_STREAM_TARGET_RESOLUTION = getPref(PrefKey.STREAM_RESOLUTION); const PREF_STREAM_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE); @@ -189,7 +189,7 @@ export class XcloudInterceptor { return response; } - BxEvent.dispatch(window, BxEvent.STREAM_STARTING); + EventBus.Stream.emit('stateStarting', {}); const obj = JSON.parse(text); let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {}; diff --git a/src/utils/xhome-interceptor.ts b/src/utils/xhome-interceptor.ts index a5bb3a5..b5246f4 100755 --- a/src/utils/xhome-interceptor.ts +++ b/src/utils/xhome-interceptor.ts @@ -9,6 +9,7 @@ import { getPref } from "./settings-storages/global-settings-storage"; import type { RemotePlayConsoleAddresses } from "@/types/network"; import { RemotePlayManager } from "@/modules/remote-play-manager"; import { StreamResolution, TouchControllerMode } from "@/enums/pref-values"; +import { EventBus } from "./event-bus"; export class XhomeInterceptor { private static consoleAddrs: RemotePlayConsoleAddresses = {}; @@ -35,7 +36,7 @@ export class XhomeInterceptor { } private static async handleConfiguration(request: Request | URL) { - BxEvent.dispatch(window, BxEvent.STREAM_STARTING); + EventBus.Stream.emit('stateStarting', {}); const response = await NATIVE_FETCH(request); const obj = await response.clone().json(); @@ -124,7 +125,7 @@ export class XhomeInterceptor { } private static async handlePlay(request: RequestInfo | URL) { - BxEvent.dispatch(window, BxEvent.STREAM_LOADING); + EventBus.Stream.emit('stateLoading', {}); const clone = (request as Request).clone(); const body = await clone.json(); diff --git a/src/web-components/bx-number-stepper.ts b/src/web-components/bx-number-stepper.ts index cd3be5f..284c9b7 100755 --- a/src/web-components/bx-number-stepper.ts +++ b/src/web-components/bx-number-stepper.ts @@ -1,5 +1,4 @@ import type { NumberStepperParams } from "@/types/setting-definition"; -import { BxEvent } from "@/utils/bx-event"; import { CE, escapeCssSelector } from "@/utils/html"; import { setNearby } from "@/utils/navigation-utils"; import type { BxHtmlSettingElement } from "@/utils/setting-element"; @@ -26,7 +25,6 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl private $btnDec!: HTMLButtonElement; private $range!: HTMLInputElement | null; - onInput!: typeof BxNumberStepper['onInput']; onRangeInput!: typeof BxNumberStepper['onRangeInput']; onClick!: typeof BxNumberStepper['onClick']; onPointerUp!: typeof BxNumberStepper['onPointerUp']; @@ -77,7 +75,6 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl self.$btnDec = $btnDec; self.onChange = onChange; - self.onInput = BxNumberStepper.onInput.bind(self); self.onRangeInput = BxNumberStepper.onRangeInput.bind(self); self.onClick = BxNumberStepper.onClick.bind(self); self.onPointerUp = BxNumberStepper.onPointerUp.bind(self); @@ -117,8 +114,7 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl self.$range = $range; options.hideSlider && $range.classList.add('bx-gone'); - $range.addEventListener('input', self.onRangeInput); - self.addEventListener('input', self.onInput); + self.addEventListener('input', self.onRangeInput); self.appendChild($range); if (options.ticks || options.exactTicks) { @@ -183,10 +179,6 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl return value; } - private static onInput(this: BxNumberStepper, e: Event) { - BxEvent.dispatch(this.$range, 'input'); - } - private static onRangeInput(this: BxNumberStepper, e: Event) { let value = parseInt((e.target as HTMLInputElement).value); if (this.options.reverse) {