mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-02 12:21:45 +02:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
fa19a5a68e | |||
3f834f74b6 | |||
749d5d720e | |||
b969d52a3c | |||
e5bd7e64a7 | |||
82ee00b4ae | |||
8e88af5f8c | |||
927eae3f2f | |||
9f440e9cf4 | |||
1acb30e3af | |||
34159fad22 |
9
build.ts
9
build.ts
@ -87,6 +87,15 @@ const postProcess = (str: string): string => {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
|
||||
// Replace " (e) =>" to " e =>"
|
||||
// str = str.replaceAll(/ \(([^\s,.$()]+)\) =>/g, ' $1 =>');
|
||||
|
||||
// Set indent to 1 space
|
||||
str = str.replaceAll(/\n(\s+)/g, (match, p1) => {
|
||||
const len = p1.length / 2;
|
||||
return '\n' + ' '.repeat(len);
|
||||
});
|
||||
|
||||
assert(str.includes('/* ADDITIONAL CODE */'));
|
||||
assert(str.includes('window.BX_EXPOSED = BxExposed'));
|
||||
assert(str.includes('window.BxEvent = BxEvent'));
|
||||
|
116
dist/better-xcloud.lite.user.js
vendored
116
dist/better-xcloud.lite.user.js
vendored
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud (Lite)
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.8.4-beta
|
||||
// @version 5.8.5
|
||||
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
||||
// @author redphx
|
||||
// @license MIT
|
||||
@ -111,7 +111,7 @@ function deepClone(obj) {
|
||||
if (!obj) return {};
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
var SCRIPT_VERSION = "5.8.4-beta", SCRIPT_VARIANT = "lite", AppInterface = window.AppInterface;
|
||||
var SCRIPT_VERSION = "5.8.5", SCRIPT_VARIANT = "lite", 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, supportMkb = AppInterface || !userAgent.match(/(android|iphone|ipad)/), STATES = {
|
||||
supportedRegion: !0,
|
||||
@ -936,10 +936,7 @@ class BaseSettingsStore {
|
||||
}
|
||||
class StreamStatsCollector {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!StreamStatsCollector.instance) StreamStatsCollector.instance = new StreamStatsCollector;
|
||||
return StreamStatsCollector.instance;
|
||||
}
|
||||
static getInstance = () => StreamStatsCollector.instance ?? (StreamStatsCollector.instance = new StreamStatsCollector);
|
||||
static INTERVAL_BACKGROUND = 60000;
|
||||
calculateGrade(value, grades) {
|
||||
return value > grades[2] ? "bad" : value > grades[1] ? "ok" : value > grades[0] ? "good" : "";
|
||||
@ -962,7 +959,8 @@ class StreamStatsCollector {
|
||||
fps: {
|
||||
current: 0,
|
||||
toString() {
|
||||
return this.current.toString();
|
||||
const maxFps = getPref("video_max_fps");
|
||||
return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString();
|
||||
}
|
||||
},
|
||||
btr: {
|
||||
@ -1807,10 +1805,7 @@ var MouseMapTo;
|
||||
})(MouseMapTo ||= {});
|
||||
class StreamStats {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!StreamStats.instance) StreamStats.instance = new StreamStats;
|
||||
return StreamStats.instance;
|
||||
}
|
||||
static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats);
|
||||
intervalId;
|
||||
REFRESH_INTERVAL = 1000;
|
||||
stats = {
|
||||
@ -2141,14 +2136,13 @@ function onChangeVideoPlayerType() {
|
||||
else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $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"), updateVideoPlayer();
|
||||
}
|
||||
function limitVideoPlayerFps() {
|
||||
const targetFps = getPref("video_max_fps");
|
||||
function limitVideoPlayerFps(targetFps) {
|
||||
STATES.currentStream.streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps);
|
||||
}
|
||||
function updateVideoPlayer() {
|
||||
const streamPlayer = STATES.currentStream.streamPlayer;
|
||||
if (!streamPlayer) return;
|
||||
limitVideoPlayerFps();
|
||||
limitVideoPlayerFps(getPref("video_max_fps"));
|
||||
const options = {
|
||||
processing: getPref("video_processing"),
|
||||
sharpness: getPref("video_sharpness"),
|
||||
@ -2381,10 +2375,7 @@ class KeyHelper {
|
||||
var LOG_TAG = "PointerClient";
|
||||
class PointerClient {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!PointerClient.instance) PointerClient.instance = new PointerClient;
|
||||
return PointerClient.instance;
|
||||
}
|
||||
static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient);
|
||||
socket;
|
||||
mkbHandler;
|
||||
start(port, mkbHandler) {
|
||||
@ -2516,11 +2507,8 @@ class PointerLockMouseDataProvider extends MouseDataProvider {
|
||||
#disableContextMenu = (e) => e.preventDefault();
|
||||
}
|
||||
class EmulatedMkbHandler extends MkbHandler {
|
||||
static #instance;
|
||||
static getInstance() {
|
||||
if (!EmulatedMkbHandler.#instance) EmulatedMkbHandler.#instance = new EmulatedMkbHandler;
|
||||
return EmulatedMkbHandler.#instance;
|
||||
}
|
||||
static instance;
|
||||
static getInstance = () => EmulatedMkbHandler.instance ?? (EmulatedMkbHandler.instance = new EmulatedMkbHandler);
|
||||
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
||||
static DEFAULT_PANNING_SENSITIVITY = 0.001;
|
||||
static DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
|
||||
@ -2798,10 +2786,7 @@ class NavigationDialog {
|
||||
}
|
||||
class NavigationDialogManager {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!NavigationDialogManager.instance) NavigationDialogManager.instance = new NavigationDialogManager;
|
||||
return NavigationDialogManager.instance;
|
||||
}
|
||||
static getInstance = () => NavigationDialogManager.instance ?? (NavigationDialogManager.instance = new NavigationDialogManager);
|
||||
static GAMEPAD_POLLING_INTERVAL = 50;
|
||||
static GAMEPAD_KEYS = [
|
||||
12,
|
||||
@ -3171,10 +3156,7 @@ if (getPref("block_social_features")) FeatureGates.EnableGuideChatTab = !1;
|
||||
if (BX_FLAGS.FeatureGates) FeatureGates = Object.assign(BX_FLAGS.FeatureGates, FeatureGates);
|
||||
class FullscreenText {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!FullscreenText.instance) FullscreenText.instance = new FullscreenText;
|
||||
return FullscreenText.instance;
|
||||
}
|
||||
static getInstance = () => FullscreenText.instance ?? (FullscreenText.instance = new FullscreenText);
|
||||
$text;
|
||||
constructor() {
|
||||
this.$text = CE("div", {
|
||||
@ -3190,10 +3172,7 @@ class FullscreenText {
|
||||
}
|
||||
class SettingsNavigationDialog extends NavigationDialog {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!SettingsNavigationDialog.instance) SettingsNavigationDialog.instance = new SettingsNavigationDialog;
|
||||
return SettingsNavigationDialog.instance;
|
||||
}
|
||||
static getInstance = () => SettingsNavigationDialog.instance ?? (SettingsNavigationDialog.instance = new SettingsNavigationDialog);
|
||||
$container;
|
||||
$tabs;
|
||||
$settings;
|
||||
@ -3458,7 +3437,9 @@ class SettingsNavigationDialog extends NavigationDialog {
|
||||
onChange: onChangeVideoPlayerType
|
||||
}, {
|
||||
pref: "video_max_fps",
|
||||
onChange: limitVideoPlayerFps
|
||||
onChange: (e) => {
|
||||
limitVideoPlayerFps(parseInt(e.target.value));
|
||||
}
|
||||
}, {
|
||||
pref: "video_power_preference",
|
||||
onChange: () => {
|
||||
@ -4185,10 +4166,7 @@ class HeaderSection {
|
||||
}
|
||||
class RemotePlayNavigationDialog extends NavigationDialog {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!RemotePlayNavigationDialog.instance) RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog;
|
||||
return RemotePlayNavigationDialog.instance;
|
||||
}
|
||||
static getInstance = () => RemotePlayNavigationDialog.instance ?? (RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog);
|
||||
STATE_LABELS = {
|
||||
On: t("powered-on"),
|
||||
Off: t("powered-off"),
|
||||
@ -4254,10 +4232,7 @@ class RemotePlayNavigationDialog extends NavigationDialog {
|
||||
var LOG_TAG2 = "RemotePlay";
|
||||
class RemotePlayManager {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!this.instance) this.instance = new RemotePlayManager;
|
||||
return this.instance;
|
||||
}
|
||||
static getInstance = () => RemotePlayManager.instance ?? (RemotePlayManager.instance = new RemotePlayManager);
|
||||
isInitialized = !1;
|
||||
XCLOUD_TOKEN;
|
||||
XHOME_TOKEN;
|
||||
@ -4544,10 +4519,7 @@ class GuideMenu {
|
||||
}
|
||||
class StreamBadges {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!StreamBadges.instance) StreamBadges.instance = new StreamBadges;
|
||||
return StreamBadges.instance;
|
||||
}
|
||||
static getInstance = () => StreamBadges.instance ?? (StreamBadges.instance = new StreamBadges);
|
||||
serverInfo = {};
|
||||
badges = {
|
||||
playtime: {
|
||||
@ -4718,17 +4690,7 @@ class StreamBadges {
|
||||
}
|
||||
}
|
||||
class XcloudInterceptor {
|
||||
static async#handleLogin(request, init) {
|
||||
const bypassServer = getPref("server_bypass_restriction");
|
||||
if (bypassServer !== "off") {
|
||||
const ip = BypassServerIps[bypassServer];
|
||||
ip && request.headers.set("X-Forwarded-For", ip);
|
||||
}
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response;
|
||||
const obj = await response.clone().json();
|
||||
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
||||
const serverEmojis = {
|
||||
static SERVER_EMOJIS = {
|
||||
AustraliaEast: "🇦🇺",
|
||||
AustraliaSouthEast: "🇦🇺",
|
||||
BrazilSouth: "🇧🇷",
|
||||
@ -4743,7 +4705,18 @@ class XcloudInterceptor {
|
||||
WestEurope: "🇪🇺",
|
||||
WestUS: "🇺🇸",
|
||||
WestUS2: "🇺🇸"
|
||||
}, serverRegex = /\/\/(\w+)\./;
|
||||
};
|
||||
static async handleLogin(request, init) {
|
||||
const bypassServer = getPref("server_bypass_restriction");
|
||||
if (bypassServer !== "off") {
|
||||
const ip = BypassServerIps[bypassServer];
|
||||
ip && request.headers.set("X-Forwarded-For", ip);
|
||||
}
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response;
|
||||
const obj = await response.clone().json();
|
||||
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
||||
const serverRegex = /\/\/(\w+)\./, serverEmojis = XcloudInterceptor.SERVER_EMOJIS;
|
||||
for (let region of obj.offeringSettings.regions) {
|
||||
const regionName = region.name;
|
||||
let shortName = region.name;
|
||||
@ -4762,7 +4735,7 @@ class XcloudInterceptor {
|
||||
}
|
||||
return STATES.gsToken = obj.gsToken, response.json = () => Promise.resolve(obj), response;
|
||||
}
|
||||
static async#handlePlay(request, init) {
|
||||
static async handlePlay(request, init) {
|
||||
const PREF_STREAM_TARGET_RESOLUTION = getPref("stream_target_resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream_preferred_locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url);
|
||||
let badgeRegion = parsedUrl.host.split(".", 1)[0];
|
||||
for (let regionName in STATES.serverRegions) {
|
||||
@ -4784,7 +4757,7 @@ class XcloudInterceptor {
|
||||
});
|
||||
return NATIVE_FETCH(newRequest);
|
||||
}
|
||||
static async#handleWaitTime(request, init) {
|
||||
static async handleWaitTime(request, init) {
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
if (getPref("ui_loading_screen_wait_time")) {
|
||||
const json = await response.clone().json();
|
||||
@ -4792,7 +4765,7 @@ class XcloudInterceptor {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
static async#handleConfiguration(request, init) {
|
||||
static async handleConfiguration(request, init) {
|
||||
if (request.method !== "GET") return NATIVE_FETCH(request, init);
|
||||
const response = await NATIVE_FETCH(request, init), text = await response.clone().text();
|
||||
if (!text.length) return response;
|
||||
@ -4811,10 +4784,10 @@ class XcloudInterceptor {
|
||||
}
|
||||
static async handle(request, init) {
|
||||
let url = typeof request === "string" ? request : request.url;
|
||||
if (url.endsWith("/v2/login/user")) return XcloudInterceptor.#handleLogin(request, init);
|
||||
else if (url.endsWith("/sessions/cloud/play")) return XcloudInterceptor.#handlePlay(request, init);
|
||||
else if (url.includes("xboxlive.com") && url.includes("/waittime/")) return XcloudInterceptor.#handleWaitTime(request, init);
|
||||
else if (url.endsWith("/configuration")) return XcloudInterceptor.#handleConfiguration(request, init);
|
||||
if (url.endsWith("/v2/login/user")) return XcloudInterceptor.handleLogin(request, init);
|
||||
else if (url.endsWith("/sessions/cloud/play")) return XcloudInterceptor.handlePlay(request, init);
|
||||
else if (url.includes("xboxlive.com") && url.includes("/waittime/")) return XcloudInterceptor.handleWaitTime(request, init);
|
||||
else if (url.endsWith("/configuration")) return XcloudInterceptor.handleConfiguration(request, init);
|
||||
else if (url && url.endsWith("/ice") && url.includes("/sessions/") && request.method === "GET") return patchIceCandidates(request);
|
||||
return NATIVE_FETCH(request, init);
|
||||
}
|
||||
@ -5083,7 +5056,7 @@ class WebGL2Player {
|
||||
saturation: 0
|
||||
};
|
||||
targetFps = 60;
|
||||
frameInterval = Math.ceil(1000 / this.targetFps);
|
||||
frameInterval = 0;
|
||||
lastFrameTime = 0;
|
||||
animFrameId = null;
|
||||
constructor($video) {
|
||||
@ -5107,7 +5080,7 @@ class WebGL2Player {
|
||||
this.options.saturation = 1 + (saturation - 100) / 100, update && this.updateCanvas();
|
||||
}
|
||||
setTargetFps(target) {
|
||||
this.targetFps = target, this.frameInterval = Math.ceil(1000 / target);
|
||||
this.targetFps = target, this.lastFrameTime = 0, this.frameInterval = target ? Math.floor(1000 / target) : 0;
|
||||
}
|
||||
getCanvas() {
|
||||
return this.$canvas;
|
||||
@ -5117,6 +5090,7 @@ class WebGL2Player {
|
||||
gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.$canvas.width, this.$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.options.saturation);
|
||||
}
|
||||
drawFrame() {
|
||||
if (this.targetFps === 0) return;
|
||||
if (this.targetFps < 60) {
|
||||
const currentTime = performance.now();
|
||||
if (currentTime - this.lastFrameTime < this.frameInterval) return;
|
||||
@ -5171,9 +5145,9 @@ class WebGL2Player {
|
||||
BxLogger.info(this.LOG_TAG, "Destroy"), this.stop();
|
||||
const gl = this.gl;
|
||||
if (gl) {
|
||||
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
||||
gl.getExtension("WEBGL_lose_context")?.loseContext(), gl.useProgram(null);
|
||||
for (let resource of this.resources)
|
||||
if (resource instanceof WebGLProgram) gl.useProgram(null), gl.deleteProgram(resource);
|
||||
if (resource instanceof WebGLProgram) gl.deleteProgram(resource);
|
||||
else if (resource instanceof WebGLShader) gl.deleteShader(resource);
|
||||
else if (resource instanceof WebGLTexture) gl.deleteTexture(resource);
|
||||
else if (resource instanceof WebGLBuffer) gl.deleteBuffer(resource);
|
||||
|
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.8.4
|
||||
// @version 5.8.5
|
||||
// ==/UserScript==
|
||||
|
216
dist/better-xcloud.user.js
vendored
216
dist/better-xcloud.user.js
vendored
@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.8.4
|
||||
// @version 5.8.5
|
||||
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
||||
// @author redphx
|
||||
// @license MIT
|
||||
@ -113,7 +113,7 @@ function deepClone(obj) {
|
||||
if (!obj) return {};
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
var SCRIPT_VERSION = "5.8.4", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface;
|
||||
var SCRIPT_VERSION = "5.8.5", 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, supportMkb = AppInterface || !userAgent.match(/(android|iphone|ipad)/), STATES = {
|
||||
supportedRegion: !0,
|
||||
@ -963,10 +963,7 @@ class BaseSettingsStore {
|
||||
}
|
||||
class StreamStatsCollector {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!StreamStatsCollector.instance) StreamStatsCollector.instance = new StreamStatsCollector;
|
||||
return StreamStatsCollector.instance;
|
||||
}
|
||||
static getInstance = () => StreamStatsCollector.instance ?? (StreamStatsCollector.instance = new StreamStatsCollector);
|
||||
static INTERVAL_BACKGROUND = 60000;
|
||||
calculateGrade(value, grades) {
|
||||
return value > grades[2] ? "bad" : value > grades[1] ? "ok" : value > grades[0] ? "good" : "";
|
||||
@ -989,7 +986,8 @@ class StreamStatsCollector {
|
||||
fps: {
|
||||
current: 0,
|
||||
toString() {
|
||||
return this.current.toString();
|
||||
const maxFps = getPref("video_max_fps");
|
||||
return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString();
|
||||
}
|
||||
},
|
||||
btr: {
|
||||
@ -1878,10 +1876,7 @@ var MouseMapTo;
|
||||
})(MouseMapTo ||= {});
|
||||
class StreamStats {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!StreamStats.instance) StreamStats.instance = new StreamStats;
|
||||
return StreamStats.instance;
|
||||
}
|
||||
static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats);
|
||||
intervalId;
|
||||
REFRESH_INTERVAL = 1000;
|
||||
stats = {
|
||||
@ -2258,14 +2253,13 @@ function onChangeVideoPlayerType() {
|
||||
else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $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"), updateVideoPlayer();
|
||||
}
|
||||
function limitVideoPlayerFps() {
|
||||
const targetFps = getPref("video_max_fps");
|
||||
function limitVideoPlayerFps(targetFps) {
|
||||
STATES.currentStream.streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps);
|
||||
}
|
||||
function updateVideoPlayer() {
|
||||
const streamPlayer = STATES.currentStream.streamPlayer;
|
||||
if (!streamPlayer) return;
|
||||
limitVideoPlayerFps();
|
||||
limitVideoPlayerFps(getPref("video_max_fps"));
|
||||
const options = {
|
||||
processing: getPref("video_processing"),
|
||||
sharpness: getPref("video_sharpness"),
|
||||
@ -2498,10 +2492,7 @@ class KeyHelper {
|
||||
var LOG_TAG = "PointerClient";
|
||||
class PointerClient {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!PointerClient.instance) PointerClient.instance = new PointerClient;
|
||||
return PointerClient.instance;
|
||||
}
|
||||
static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient);
|
||||
socket;
|
||||
mkbHandler;
|
||||
start(port, mkbHandler) {
|
||||
@ -2575,6 +2566,7 @@ class MouseDataProvider {
|
||||
class MkbHandler {}
|
||||
class NativeMkbHandler extends MkbHandler {
|
||||
static instance;
|
||||
static getInstance = () => NativeMkbHandler.instance ?? (NativeMkbHandler.instance = new NativeMkbHandler);
|
||||
#pointerClient;
|
||||
#enabled = !1;
|
||||
#mouseButtonsPressed = 0;
|
||||
@ -2584,10 +2576,6 @@ class NativeMkbHandler extends MkbHandler {
|
||||
#mouseHorizontalMultiply = 0;
|
||||
#inputSink;
|
||||
#$message;
|
||||
static getInstance() {
|
||||
if (!NativeMkbHandler.instance) NativeMkbHandler.instance = new NativeMkbHandler;
|
||||
return NativeMkbHandler.instance;
|
||||
}
|
||||
#onKeyboardEvent(e) {
|
||||
if (e.type === "keyup" && e.code === "F8") {
|
||||
e.preventDefault(), this.toggle();
|
||||
@ -2793,11 +2781,8 @@ class PointerLockMouseDataProvider extends MouseDataProvider {
|
||||
#disableContextMenu = (e) => e.preventDefault();
|
||||
}
|
||||
class EmulatedMkbHandler extends MkbHandler {
|
||||
static #instance;
|
||||
static getInstance() {
|
||||
if (!EmulatedMkbHandler.#instance) EmulatedMkbHandler.#instance = new EmulatedMkbHandler;
|
||||
return EmulatedMkbHandler.#instance;
|
||||
}
|
||||
static instance;
|
||||
static getInstance = () => EmulatedMkbHandler.instance ?? (EmulatedMkbHandler.instance = new EmulatedMkbHandler);
|
||||
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
||||
static DEFAULT_PANNING_SENSITIVITY = 0.001;
|
||||
static DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
|
||||
@ -3081,10 +3066,7 @@ class NavigationDialog {
|
||||
}
|
||||
class NavigationDialogManager {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!NavigationDialogManager.instance) NavigationDialogManager.instance = new NavigationDialogManager;
|
||||
return NavigationDialogManager.instance;
|
||||
}
|
||||
static getInstance = () => NavigationDialogManager.instance ?? (NavigationDialogManager.instance = new NavigationDialogManager);
|
||||
static GAMEPAD_POLLING_INTERVAL = 50;
|
||||
static GAMEPAD_KEYS = [
|
||||
12,
|
||||
@ -4257,9 +4239,9 @@ true` + text;
|
||||
return str = str.replace(text, newCode), str;
|
||||
},
|
||||
skipFeedbackDialog(str) {
|
||||
let text = "&&this.shouldTransitionToFeedback(";
|
||||
let text = "shouldTransitionToFeedback(e){";
|
||||
if (!str.includes(text)) return !1;
|
||||
return str = str.replace(text, "&& false " + text), str;
|
||||
return str = str.replace(text, text + "return !1;"), str;
|
||||
},
|
||||
enableNativeMkb(str) {
|
||||
let text = "e.mouseSupported&&e.keyboardSupported&&e.fullscreenSupported;";
|
||||
@ -4551,10 +4533,7 @@ class PatcherCache {
|
||||
}
|
||||
class FullscreenText {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!FullscreenText.instance) FullscreenText.instance = new FullscreenText;
|
||||
return FullscreenText.instance;
|
||||
}
|
||||
static getInstance = () => FullscreenText.instance ?? (FullscreenText.instance = new FullscreenText);
|
||||
$text;
|
||||
constructor() {
|
||||
this.$text = CE("div", {
|
||||
@ -4570,10 +4549,7 @@ class FullscreenText {
|
||||
}
|
||||
class SettingsNavigationDialog extends NavigationDialog {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!SettingsNavigationDialog.instance) SettingsNavigationDialog.instance = new SettingsNavigationDialog;
|
||||
return SettingsNavigationDialog.instance;
|
||||
}
|
||||
static getInstance = () => SettingsNavigationDialog.instance ?? (SettingsNavigationDialog.instance = new SettingsNavigationDialog);
|
||||
$container;
|
||||
$tabs;
|
||||
$settings;
|
||||
@ -4838,7 +4814,9 @@ class SettingsNavigationDialog extends NavigationDialog {
|
||||
onChange: onChangeVideoPlayerType
|
||||
}, {
|
||||
pref: "video_max_fps",
|
||||
onChange: limitVideoPlayerFps
|
||||
onChange: (e) => {
|
||||
limitVideoPlayerFps(parseInt(e.target.value));
|
||||
}
|
||||
}, {
|
||||
pref: "video_power_preference",
|
||||
onChange: () => {
|
||||
@ -5832,10 +5810,7 @@ class HeaderSection {
|
||||
}
|
||||
class RemotePlayNavigationDialog extends NavigationDialog {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!RemotePlayNavigationDialog.instance) RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog;
|
||||
return RemotePlayNavigationDialog.instance;
|
||||
}
|
||||
static getInstance = () => RemotePlayNavigationDialog.instance ?? (RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog);
|
||||
STATE_LABELS = {
|
||||
On: t("powered-on"),
|
||||
Off: t("powered-off"),
|
||||
@ -5901,10 +5876,7 @@ class RemotePlayNavigationDialog extends NavigationDialog {
|
||||
var LOG_TAG5 = "RemotePlay";
|
||||
class RemotePlayManager {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!this.instance) this.instance = new RemotePlayManager;
|
||||
return this.instance;
|
||||
}
|
||||
static getInstance = () => RemotePlayManager.instance ?? (RemotePlayManager.instance = new RemotePlayManager);
|
||||
isInitialized = !1;
|
||||
XCLOUD_TOKEN;
|
||||
XHOME_TOKEN;
|
||||
@ -6017,7 +5989,7 @@ class RemotePlayManager {
|
||||
}
|
||||
}
|
||||
class XhomeInterceptor {
|
||||
static #consoleAddrs = {};
|
||||
static consoleAddrs = {};
|
||||
static BASE_DEVICE_INFO = {
|
||||
appInfo: {
|
||||
env: {
|
||||
@ -6056,7 +6028,7 @@ class XhomeInterceptor {
|
||||
}
|
||||
}
|
||||
};
|
||||
static async#handleLogin(request) {
|
||||
static async handleLogin(request) {
|
||||
try {
|
||||
const obj = await request.clone().json();
|
||||
obj.offeringId = "xhome", request = new Request("https://xhome.gssv-play-prod.xboxlive.com/v2/login/user", {
|
||||
@ -6071,19 +6043,19 @@ class XhomeInterceptor {
|
||||
}
|
||||
return NATIVE_FETCH(request);
|
||||
}
|
||||
static async#handleConfiguration(request) {
|
||||
static async handleConfiguration(request) {
|
||||
const response = await NATIVE_FETCH(request), obj = await response.clone().json();
|
||||
console.log(obj);
|
||||
const processPorts = (port) => {
|
||||
const ports = new Set;
|
||||
return port && ports.add(port), ports.add(9002), Array.from(ports);
|
||||
}, serverDetails = obj.serverDetails;
|
||||
if (serverDetails.ipAddress) XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
||||
if (serverDetails.ipV4Address) XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
||||
if (serverDetails.ipV6Address) XhomeInterceptor.#consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
||||
if (serverDetails.ipAddress) XhomeInterceptor.consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
||||
if (serverDetails.ipV4Address) XhomeInterceptor.consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
||||
if (serverDetails.ipV6Address) XhomeInterceptor.consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
||||
return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;
|
||||
}
|
||||
static async#handleInputConfigs(request, opts) {
|
||||
static async handleInputConfigs(request, opts) {
|
||||
const response = await NATIVE_FETCH(request);
|
||||
if (getPref("stream_touch_controller") !== "all") return response;
|
||||
const obj = await response.clone().json(), xboxTitleId = JSON.parse(opts.body).titleIds[0];
|
||||
@ -6100,7 +6072,7 @@ class XhomeInterceptor {
|
||||
else TouchController.enable(), TouchController.requestCustomLayouts(xboxTitleId);
|
||||
return response.json = () => Promise.resolve(obj), response.text = () => Promise.resolve(JSON.stringify(obj)), response;
|
||||
}
|
||||
static async#handleTitles(request) {
|
||||
static async handleTitles(request) {
|
||||
const clone = request.clone(), headers = {};
|
||||
for (let pair of clone.headers.entries())
|
||||
headers[pair[0]] = pair[1];
|
||||
@ -6112,7 +6084,7 @@ class XhomeInterceptor {
|
||||
headers
|
||||
}), NATIVE_FETCH(request);
|
||||
}
|
||||
static async#handlePlay(request) {
|
||||
static async handlePlay(request) {
|
||||
const body = await request.clone().json(), newRequest = new Request(request, {
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
@ -6139,12 +6111,12 @@ class XhomeInterceptor {
|
||||
}
|
||||
request = new Request(newUrl, opts);
|
||||
let url = typeof request === "string" ? request : request.url;
|
||||
if (url.includes("/configuration")) return XhomeInterceptor.#handleConfiguration(request);
|
||||
else if (url.endsWith("/sessions/home/play")) return XhomeInterceptor.#handlePlay(request);
|
||||
else if (url.includes("inputconfigs")) return XhomeInterceptor.#handleInputConfigs(request, opts);
|
||||
else if (url.includes("/login/user")) return XhomeInterceptor.#handleLogin(request);
|
||||
else if (url.endsWith("/titles")) return XhomeInterceptor.#handleTitles(request);
|
||||
else if (url && url.endsWith("/ice") && url.includes("/sessions/") && request.method === "GET") return patchIceCandidates(request, XhomeInterceptor.#consoleAddrs);
|
||||
if (url.includes("/configuration")) return XhomeInterceptor.handleConfiguration(request);
|
||||
else if (url.endsWith("/sessions/home/play")) return XhomeInterceptor.handlePlay(request);
|
||||
else if (url.includes("inputconfigs")) return XhomeInterceptor.handleInputConfigs(request, opts);
|
||||
else if (url.includes("/login/user")) return XhomeInterceptor.handleLogin(request);
|
||||
else if (url.endsWith("/titles")) return XhomeInterceptor.handleTitles(request);
|
||||
else if (url && url.endsWith("/ice") && url.includes("/sessions/") && request.method === "GET") return patchIceCandidates(request, XhomeInterceptor.consoleAddrs);
|
||||
return await NATIVE_FETCH(request);
|
||||
}
|
||||
}
|
||||
@ -6413,10 +6385,7 @@ class GuideMenu {
|
||||
}
|
||||
class StreamBadges {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!StreamBadges.instance) StreamBadges.instance = new StreamBadges;
|
||||
return StreamBadges.instance;
|
||||
}
|
||||
static getInstance = () => StreamBadges.instance ?? (StreamBadges.instance = new StreamBadges);
|
||||
serverInfo = {};
|
||||
badges = {
|
||||
playtime: {
|
||||
@ -6581,17 +6550,7 @@ class StreamBadges {
|
||||
static setupEvents() {}
|
||||
}
|
||||
class XcloudInterceptor {
|
||||
static async#handleLogin(request, init) {
|
||||
const bypassServer = getPref("server_bypass_restriction");
|
||||
if (bypassServer !== "off") {
|
||||
const ip = BypassServerIps[bypassServer];
|
||||
ip && request.headers.set("X-Forwarded-For", ip);
|
||||
}
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response;
|
||||
const obj = await response.clone().json();
|
||||
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
||||
const serverEmojis = {
|
||||
static SERVER_EMOJIS = {
|
||||
AustraliaEast: "🇦🇺",
|
||||
AustraliaSouthEast: "🇦🇺",
|
||||
BrazilSouth: "🇧🇷",
|
||||
@ -6606,7 +6565,18 @@ class XcloudInterceptor {
|
||||
WestEurope: "🇪🇺",
|
||||
WestUS: "🇺🇸",
|
||||
WestUS2: "🇺🇸"
|
||||
}, serverRegex = /\/\/(\w+)\./;
|
||||
};
|
||||
static async handleLogin(request, init) {
|
||||
const bypassServer = getPref("server_bypass_restriction");
|
||||
if (bypassServer !== "off") {
|
||||
const ip = BypassServerIps[bypassServer];
|
||||
ip && request.headers.set("X-Forwarded-For", ip);
|
||||
}
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
if (response.status !== 200) return BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE), response;
|
||||
const obj = await response.clone().json();
|
||||
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
||||
const serverRegex = /\/\/(\w+)\./, serverEmojis = XcloudInterceptor.SERVER_EMOJIS;
|
||||
for (let region of obj.offeringSettings.regions) {
|
||||
const regionName = region.name;
|
||||
let shortName = region.name;
|
||||
@ -6625,7 +6595,7 @@ class XcloudInterceptor {
|
||||
}
|
||||
return STATES.gsToken = obj.gsToken, response.json = () => Promise.resolve(obj), response;
|
||||
}
|
||||
static async#handlePlay(request, init) {
|
||||
static async handlePlay(request, init) {
|
||||
const PREF_STREAM_TARGET_RESOLUTION = getPref("stream_target_resolution"), PREF_STREAM_PREFERRED_LOCALE = getPref("stream_preferred_locale"), url = typeof request === "string" ? request : request.url, parsedUrl = new URL(url);
|
||||
let badgeRegion = parsedUrl.host.split(".", 1)[0];
|
||||
for (let regionName in STATES.serverRegions) {
|
||||
@ -6647,7 +6617,7 @@ class XcloudInterceptor {
|
||||
});
|
||||
return NATIVE_FETCH(newRequest);
|
||||
}
|
||||
static async#handleWaitTime(request, init) {
|
||||
static async handleWaitTime(request, init) {
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
if (getPref("ui_loading_screen_wait_time")) {
|
||||
const json = await response.clone().json();
|
||||
@ -6655,7 +6625,7 @@ class XcloudInterceptor {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
static async#handleConfiguration(request, init) {
|
||||
static async handleConfiguration(request, init) {
|
||||
if (request.method !== "GET") return NATIVE_FETCH(request, init);
|
||||
if (getPref("stream_touch_controller") === "all") if (STATES.currentStream.titleInfo?.details.hasTouchSupport) TouchController.disable();
|
||||
else TouchController.enable();
|
||||
@ -6677,10 +6647,10 @@ class XcloudInterceptor {
|
||||
}
|
||||
static async handle(request, init) {
|
||||
let url = typeof request === "string" ? request : request.url;
|
||||
if (url.endsWith("/v2/login/user")) return XcloudInterceptor.#handleLogin(request, init);
|
||||
else if (url.endsWith("/sessions/cloud/play")) return XcloudInterceptor.#handlePlay(request, init);
|
||||
else if (url.includes("xboxlive.com") && url.includes("/waittime/")) return XcloudInterceptor.#handleWaitTime(request, init);
|
||||
else if (url.endsWith("/configuration")) return XcloudInterceptor.#handleConfiguration(request, init);
|
||||
if (url.endsWith("/v2/login/user")) return XcloudInterceptor.handleLogin(request, init);
|
||||
else if (url.endsWith("/sessions/cloud/play")) return XcloudInterceptor.handlePlay(request, init);
|
||||
else if (url.includes("xboxlive.com") && url.includes("/waittime/")) return XcloudInterceptor.handleWaitTime(request, init);
|
||||
else if (url.endsWith("/configuration")) return XcloudInterceptor.handleConfiguration(request, init);
|
||||
else if (url && url.endsWith("/ice") && url.includes("/sessions/") && request.method === "GET") return patchIceCandidates(request);
|
||||
return NATIVE_FETCH(request, init);
|
||||
}
|
||||
@ -6991,7 +6961,7 @@ class WebGL2Player {
|
||||
saturation: 0
|
||||
};
|
||||
targetFps = 60;
|
||||
frameInterval = Math.ceil(1000 / this.targetFps);
|
||||
frameInterval = 0;
|
||||
lastFrameTime = 0;
|
||||
animFrameId = null;
|
||||
constructor($video) {
|
||||
@ -7015,7 +6985,7 @@ class WebGL2Player {
|
||||
this.options.saturation = 1 + (saturation - 100) / 100, update && this.updateCanvas();
|
||||
}
|
||||
setTargetFps(target) {
|
||||
this.targetFps = target, this.frameInterval = Math.ceil(1000 / target);
|
||||
this.targetFps = target, this.lastFrameTime = 0, this.frameInterval = target ? Math.floor(1000 / target) : 0;
|
||||
}
|
||||
getCanvas() {
|
||||
return this.$canvas;
|
||||
@ -7025,6 +6995,7 @@ class WebGL2Player {
|
||||
gl.uniform2f(gl.getUniformLocation(program, "iResolution"), this.$canvas.width, this.$canvas.height), gl.uniform1i(gl.getUniformLocation(program, "filterId"), this.options.filterId), gl.uniform1f(gl.getUniformLocation(program, "sharpenFactor"), this.options.sharpenFactor), gl.uniform1f(gl.getUniformLocation(program, "brightness"), this.options.brightness), gl.uniform1f(gl.getUniformLocation(program, "contrast"), this.options.contrast), gl.uniform1f(gl.getUniformLocation(program, "saturation"), this.options.saturation);
|
||||
}
|
||||
drawFrame() {
|
||||
if (this.targetFps === 0) return;
|
||||
if (this.targetFps < 60) {
|
||||
const currentTime = performance.now();
|
||||
if (currentTime - this.lastFrameTime < this.frameInterval) return;
|
||||
@ -7079,9 +7050,9 @@ class WebGL2Player {
|
||||
BxLogger.info(this.LOG_TAG, "Destroy"), this.stop();
|
||||
const gl = this.gl;
|
||||
if (gl) {
|
||||
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
||||
gl.getExtension("WEBGL_lose_context")?.loseContext(), gl.useProgram(null);
|
||||
for (let resource of this.resources)
|
||||
if (resource instanceof WebGLProgram) gl.useProgram(null), gl.deleteProgram(resource);
|
||||
if (resource instanceof WebGLProgram) gl.deleteProgram(resource);
|
||||
else if (resource instanceof WebGLShader) gl.deleteShader(resource);
|
||||
else if (resource instanceof WebGLTexture) gl.deleteTexture(resource);
|
||||
else if (resource instanceof WebGLBuffer) gl.deleteBuffer(resource);
|
||||
@ -7343,6 +7314,9 @@ class BaseGameBarAction {
|
||||
onClick(e) {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
}
|
||||
render() {
|
||||
return this.$content;
|
||||
}
|
||||
}
|
||||
class ScreenshotAction extends BaseGameBarAction {
|
||||
$content;
|
||||
@ -7358,9 +7332,6 @@ class ScreenshotAction extends BaseGameBarAction {
|
||||
onClick(e) {
|
||||
super.onClick(e), Screenshot.takeScreenshot();
|
||||
}
|
||||
render() {
|
||||
return this.$content;
|
||||
}
|
||||
}
|
||||
class TouchControlAction extends BaseGameBarAction {
|
||||
$content;
|
||||
@ -7378,23 +7349,19 @@ class TouchControlAction extends BaseGameBarAction {
|
||||
onClick: this.onClick.bind(this),
|
||||
classes: ["bx-activated"]
|
||||
});
|
||||
this.$content = CE("div", {}, $btnEnable, $btnDisable), this.reset();
|
||||
this.$content = CE("div", {}, $btnEnable, $btnDisable);
|
||||
}
|
||||
onClick(e) {
|
||||
super.onClick(e);
|
||||
const isVisible = TouchController.toggleVisibility();
|
||||
this.$content.dataset.activated = (!isVisible).toString();
|
||||
}
|
||||
render() {
|
||||
return this.$content;
|
||||
}
|
||||
reset() {
|
||||
this.$content.dataset.activated = "false";
|
||||
}
|
||||
}
|
||||
class MicrophoneAction extends BaseGameBarAction {
|
||||
$content;
|
||||
visible = !1;
|
||||
constructor() {
|
||||
super();
|
||||
const $btnDefault = createButton({
|
||||
@ -7407,7 +7374,7 @@ class MicrophoneAction extends BaseGameBarAction {
|
||||
icon: BxIcon.MICROPHONE_MUTED,
|
||||
onClick: this.onClick.bind(this)
|
||||
});
|
||||
this.$content = CE("div", {}, $btnMuted, $btnDefault), this.reset(), window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, (e) => {
|
||||
this.$content = CE("div", {}, $btnMuted, $btnDefault), window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, (e) => {
|
||||
const enabled = e.microphoneState === "Enabled";
|
||||
this.$content.dataset.activated = enabled.toString(), this.$content.classList.remove("bx-gone");
|
||||
});
|
||||
@ -7417,11 +7384,8 @@ class MicrophoneAction extends BaseGameBarAction {
|
||||
const enabled = MicrophoneShortcut.toggle(!1);
|
||||
this.$content.dataset.activated = enabled.toString();
|
||||
}
|
||||
render() {
|
||||
return this.$content;
|
||||
}
|
||||
reset() {
|
||||
this.visible = !1, this.$content.classList.add("bx-gone"), this.$content.dataset.activated = "false";
|
||||
this.$content.classList.add("bx-gone"), this.$content.dataset.activated = "false";
|
||||
}
|
||||
}
|
||||
class TrueAchievementsAction extends BaseGameBarAction {
|
||||
@ -7431,16 +7395,12 @@ class TrueAchievementsAction extends BaseGameBarAction {
|
||||
this.$content = createButton({
|
||||
style: 4,
|
||||
icon: BxIcon.TRUE_ACHIEVEMENTS,
|
||||
title: t("true-achievements"),
|
||||
onClick: this.onClick.bind(this)
|
||||
});
|
||||
}
|
||||
onClick(e) {
|
||||
super.onClick(e), TrueAchievements.open(!1);
|
||||
}
|
||||
render() {
|
||||
return this.$content;
|
||||
}
|
||||
}
|
||||
class SpeakerAction extends BaseGameBarAction {
|
||||
$content;
|
||||
@ -7456,7 +7416,7 @@ class SpeakerAction extends BaseGameBarAction {
|
||||
onClick: this.onClick.bind(this),
|
||||
classes: ["bx-activated"]
|
||||
});
|
||||
this.$content = CE("div", {}, $btnEnable, $btnMuted), this.reset(), window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, (e) => {
|
||||
this.$content = CE("div", {}, $btnEnable, $btnMuted), window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, (e) => {
|
||||
const enabled = e.speakerState === 0;
|
||||
this.$content.dataset.activated = (!enabled).toString();
|
||||
});
|
||||
@ -7464,9 +7424,6 @@ class SpeakerAction extends BaseGameBarAction {
|
||||
onClick(e) {
|
||||
super.onClick(e), SoundShortcut.muteUnmute();
|
||||
}
|
||||
render() {
|
||||
return this.$content;
|
||||
}
|
||||
reset() {
|
||||
this.$content.dataset.activated = "false";
|
||||
}
|
||||
@ -7475,7 +7432,9 @@ class RendererShortcut {
|
||||
static toggleVisibility() {
|
||||
const $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]');
|
||||
if (!$mediaContainer) return !0;
|
||||
return $mediaContainer.classList.toggle("bx-gone"), !$mediaContainer.classList.contains("bx-gone");
|
||||
$mediaContainer.classList.toggle("bx-gone");
|
||||
const isShowing = !$mediaContainer.classList.contains("bx-gone");
|
||||
return limitVideoPlayerFps(isShowing ? getPref("video_max_fps") : 0), isShowing;
|
||||
}
|
||||
}
|
||||
class RendererAction extends BaseGameBarAction {
|
||||
@ -7492,26 +7451,20 @@ class RendererAction extends BaseGameBarAction {
|
||||
onClick: this.onClick.bind(this),
|
||||
classes: ["bx-activated"]
|
||||
});
|
||||
this.$content = CE("div", {}, $btnDefault, $btnActivated), this.reset();
|
||||
this.$content = CE("div", {}, $btnDefault, $btnActivated);
|
||||
}
|
||||
onClick(e) {
|
||||
super.onClick(e);
|
||||
const isVisible = RendererShortcut.toggleVisibility();
|
||||
this.$content.dataset.activated = (!isVisible).toString();
|
||||
}
|
||||
render() {
|
||||
return this.$content;
|
||||
}
|
||||
reset() {
|
||||
this.$content.dataset.activated = "false";
|
||||
}
|
||||
}
|
||||
class GameBar {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!GameBar.instance) GameBar.instance = new GameBar;
|
||||
return GameBar.instance;
|
||||
}
|
||||
static getInstance = () => GameBar.instance ?? (GameBar.instance = new GameBar);
|
||||
static VISIBLE_DURATION = 2000;
|
||||
$gameBar;
|
||||
$container;
|
||||
@ -7535,8 +7488,7 @@ class GameBar {
|
||||
if (e.target !== $gameBar) return;
|
||||
$container.classList.contains("bx-show") ? this.hideBar() : this.showBar();
|
||||
}), window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, this.hideBar.bind(this)), $container.addEventListener("pointerover", this.clearHideTimeout.bind(this)), $container.addEventListener("pointerout", this.beginHideTimeout.bind(this)), $container.addEventListener("transitionend", (e) => {
|
||||
const classList = $container.classList;
|
||||
if (classList.contains("bx-hide")) classList.remove("bx-hide"), classList.add("bx-offscreen");
|
||||
$container.classList.replace("bx-hide", "bx-offscreen");
|
||||
}), document.documentElement.appendChild($gameBar), this.$gameBar = $gameBar, this.$container = $container, getPref("game_bar_position") !== "off" && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e) => {
|
||||
if (!STATES.isPlaying) {
|
||||
this.disable();
|
||||
@ -7554,30 +7506,24 @@ class GameBar {
|
||||
this.timeoutId && clearTimeout(this.timeoutId), this.timeoutId = null;
|
||||
}
|
||||
enable() {
|
||||
this.$gameBar && this.$gameBar.classList.remove("bx-gone");
|
||||
this.$gameBar.classList.remove("bx-gone");
|
||||
}
|
||||
disable() {
|
||||
this.hideBar(), this.$gameBar && this.$gameBar.classList.add("bx-gone");
|
||||
this.hideBar(), this.$gameBar.classList.add("bx-gone");
|
||||
}
|
||||
showBar() {
|
||||
if (!this.$container) return;
|
||||
this.$container.classList.remove("bx-offscreen", "bx-hide", "bx-gone"), this.$container.classList.add("bx-show"), this.beginHideTimeout();
|
||||
}
|
||||
hideBar() {
|
||||
if (this.clearHideTimeout(), clearFocus(), !this.$container) return;
|
||||
this.$container.classList.remove("bx-show"), this.$container.classList.add("bx-hide");
|
||||
this.clearHideTimeout(), clearFocus(), this.$container.classList.replace("bx-show", "bx-hide");
|
||||
}
|
||||
reset() {
|
||||
for (let action of this.actions)
|
||||
action.reset();
|
||||
this.actions.forEach((action) => action.reset());
|
||||
}
|
||||
}
|
||||
class XcloudApi {
|
||||
static instance;
|
||||
static getInstance() {
|
||||
if (!XcloudApi.instance) XcloudApi.instance = new XcloudApi;
|
||||
return XcloudApi.instance;
|
||||
}
|
||||
static getInstance = () => XcloudApi.instance ?? (XcloudApi.instance = new XcloudApi);
|
||||
CACHE_TITLES = {};
|
||||
CACHE_WAIT_TIME = {};
|
||||
async getTitleInfo(id2) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
export abstract class BaseGameBarAction {
|
||||
abstract $content: HTMLElement;
|
||||
|
||||
constructor() {}
|
||||
reset() {}
|
||||
|
||||
@ -8,5 +10,7 @@ export abstract class BaseGameBarAction {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
};
|
||||
|
||||
abstract render(): HTMLElement;
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
};
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/shortcut-micro
|
||||
export class MicrophoneAction extends BaseGameBarAction {
|
||||
$content: HTMLElement;
|
||||
|
||||
visible: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -26,12 +24,7 @@ export class MicrophoneAction extends BaseGameBarAction {
|
||||
onClick: this.onClick.bind(this),
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnMuted,
|
||||
$btnDefault,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
||||
|
||||
window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, e => {
|
||||
const microphoneState = (e as any).microphoneState;
|
||||
@ -49,12 +42,7 @@ export class MicrophoneAction extends BaseGameBarAction {
|
||||
this.$content.dataset.activated = enabled.toString();
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.visible = false;
|
||||
this.$content.classList.add('bx-gone');
|
||||
this.$content.dataset.activated = 'false';
|
||||
}
|
||||
|
@ -23,12 +23,7 @@ export class RendererAction extends BaseGameBarAction {
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnDefault,
|
||||
$btnActivated,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnDefault, $btnActivated);
|
||||
}
|
||||
|
||||
onClick(e: Event) {
|
||||
@ -37,10 +32,6 @@ export class RendererAction extends BaseGameBarAction {
|
||||
this.$content.dataset.activated = (!isVisible).toString();
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.$content.dataset.activated = 'false';
|
||||
}
|
||||
|
@ -22,8 +22,4 @@ export class ScreenshotAction extends BaseGameBarAction {
|
||||
super.onClick(e);
|
||||
Screenshot.takeScreenshot();
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
}
|
||||
|
@ -24,12 +24,7 @@ export class SpeakerAction extends BaseGameBarAction {
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnEnable,
|
||||
$btnMuted,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnEnable, $btnMuted);
|
||||
|
||||
window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, e => {
|
||||
const speakerState = (e as any).speakerState;
|
||||
@ -44,10 +39,6 @@ export class SpeakerAction extends BaseGameBarAction {
|
||||
SoundShortcut.muteUnmute();
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.$content.dataset.activated = 'false';
|
||||
}
|
||||
|
@ -25,12 +25,7 @@ export class TouchControlAction extends BaseGameBarAction {
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnEnable,
|
||||
$btnDisable,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnEnable, $btnDisable);
|
||||
}
|
||||
|
||||
onClick(e: Event) {
|
||||
@ -39,10 +34,6 @@ export class TouchControlAction extends BaseGameBarAction {
|
||||
this.$content.dataset.activated = (!isVisible).toString();
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.$content.dataset.activated = 'false';
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { BxIcon } from "@/utils/bx-icon";
|
||||
import { createButton, ButtonStyle } from "@/utils/html";
|
||||
import { t } from "@/utils/translation";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { TrueAchievements } from "@/utils/true-achievements";
|
||||
|
||||
@ -13,7 +12,6 @@ export class TrueAchievementsAction extends BaseGameBarAction {
|
||||
this.$content = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TRUE_ACHIEVEMENTS,
|
||||
title: t('true-achievements'),
|
||||
onClick: this.onClick.bind(this),
|
||||
});
|
||||
}
|
||||
@ -22,8 +20,4 @@ export class TrueAchievementsAction extends BaseGameBarAction {
|
||||
super.onClick(e);
|
||||
TrueAchievements.open(false);
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import type { BaseGameBarAction } from "./action-base";
|
||||
import { STATES } from "@utils/global";
|
||||
import { MicrophoneAction } from "./action-microphone";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, StreamTouchController } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { getPref, StreamTouchController, type GameBarPosition } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { TrueAchievementsAction } from "./action-true-achievements";
|
||||
import { SpeakerAction } from "./action-speaker";
|
||||
import { RendererAction } from "./action-renderer";
|
||||
@ -15,13 +15,7 @@ import { RendererAction } from "./action-renderer";
|
||||
|
||||
export class GameBar {
|
||||
private static instance: GameBar;
|
||||
public static getInstance(): GameBar {
|
||||
if (!GameBar.instance) {
|
||||
GameBar.instance = new GameBar();
|
||||
}
|
||||
|
||||
return GameBar.instance;
|
||||
}
|
||||
public static getInstance = () => GameBar.instance ?? (GameBar.instance = new GameBar());
|
||||
|
||||
private static readonly VISIBLE_DURATION = 2000;
|
||||
|
||||
@ -35,7 +29,7 @@ export class GameBar {
|
||||
private constructor() {
|
||||
let $container;
|
||||
|
||||
const position = getPref(PrefKey.GAME_BAR_POSITION);
|
||||
const position = getPref(PrefKey.GAME_BAR_POSITION) as GameBarPosition;
|
||||
|
||||
const $gameBar = CE('div', {id: 'bx-game-bar', class: 'bx-gone', 'data-position': position},
|
||||
$container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
|
||||
@ -78,11 +72,7 @@ export class GameBar {
|
||||
|
||||
// Add animation when hiding game bar
|
||||
$container.addEventListener('transitionend', e => {
|
||||
const classList = $container.classList;
|
||||
if (classList.contains('bx-hide')) {
|
||||
classList.remove('bx-hide');
|
||||
classList.add('bx-offscreen');
|
||||
}
|
||||
$container.classList.replace('bx-hide', 'bx-offscreen');
|
||||
});
|
||||
|
||||
document.documentElement.appendChild($gameBar);
|
||||
@ -117,19 +107,15 @@ export class GameBar {
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.$gameBar && this.$gameBar.classList.remove('bx-gone');
|
||||
this.$gameBar.classList.remove('bx-gone');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.hideBar();
|
||||
this.$gameBar && this.$gameBar.classList.add('bx-gone');
|
||||
this.$gameBar.classList.add('bx-gone');
|
||||
}
|
||||
|
||||
showBar() {
|
||||
if (!this.$container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$container.classList.remove('bx-offscreen', 'bx-hide' , 'bx-gone');
|
||||
this.$container.classList.add('bx-show');
|
||||
|
||||
@ -142,18 +128,11 @@ export class GameBar {
|
||||
// Stop focusing Game Bar
|
||||
clearFocus();
|
||||
|
||||
if (!this.$container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$container.classList.remove('bx-show');
|
||||
this.$container.classList.add('bx-hide');
|
||||
this.$container.classList.replace('bx-show', 'bx-hide');
|
||||
}
|
||||
|
||||
// Reset all states
|
||||
reset() {
|
||||
for (const action of this.actions) {
|
||||
action.reset();
|
||||
}
|
||||
this.actions.forEach(action => action.reset());
|
||||
}
|
||||
}
|
||||
|
@ -124,14 +124,8 @@ This class uses some code from Yuzu emulator to handle mouse's movements
|
||||
Source: https://github.com/yuzu-emu/yuzu-mainline/blob/master/src/input_common/drivers/mouse.cpp
|
||||
*/
|
||||
export class EmulatedMkbHandler extends MkbHandler {
|
||||
static #instance: EmulatedMkbHandler;
|
||||
public static getInstance(): EmulatedMkbHandler {
|
||||
if (!EmulatedMkbHandler.#instance) {
|
||||
EmulatedMkbHandler.#instance = new EmulatedMkbHandler();
|
||||
}
|
||||
|
||||
return EmulatedMkbHandler.#instance;
|
||||
}
|
||||
private static instance: EmulatedMkbHandler;
|
||||
public static getInstance = () => EmulatedMkbHandler.instance ?? (EmulatedMkbHandler.instance = new EmulatedMkbHandler());
|
||||
|
||||
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
||||
|
||||
|
@ -23,6 +23,8 @@ type XcloudInputSink = {
|
||||
|
||||
export class NativeMkbHandler extends MkbHandler {
|
||||
private static instance: NativeMkbHandler;
|
||||
public static getInstance = () => NativeMkbHandler.instance ?? (NativeMkbHandler.instance = new NativeMkbHandler());
|
||||
|
||||
#pointerClient: PointerClient | undefined;
|
||||
#enabled: boolean = false;
|
||||
|
||||
@ -37,14 +39,6 @@ export class NativeMkbHandler extends MkbHandler {
|
||||
|
||||
#$message?: HTMLElement;
|
||||
|
||||
public static getInstance(): NativeMkbHandler {
|
||||
if (!NativeMkbHandler.instance) {
|
||||
NativeMkbHandler.instance = new NativeMkbHandler();
|
||||
}
|
||||
|
||||
return NativeMkbHandler.instance;
|
||||
}
|
||||
|
||||
#onKeyboardEvent(e: KeyboardEvent) {
|
||||
if (e.type === 'keyup' && e.code === 'F8') {
|
||||
e.preventDefault();
|
||||
|
@ -15,13 +15,7 @@ enum PointerAction {
|
||||
|
||||
export class PointerClient {
|
||||
private static instance: PointerClient;
|
||||
public static getInstance(): PointerClient {
|
||||
if (!PointerClient.instance) {
|
||||
PointerClient.instance = new PointerClient();
|
||||
}
|
||||
|
||||
return PointerClient.instance;
|
||||
}
|
||||
public static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient());
|
||||
|
||||
private socket: WebSocket | undefined | null;
|
||||
private mkbHandler: MkbHandler | undefined;
|
||||
|
@ -632,12 +632,12 @@ true` + text;
|
||||
},
|
||||
|
||||
skipFeedbackDialog(str: string) {
|
||||
let text = '&&this.shouldTransitionToFeedback(';
|
||||
let text = 'shouldTransitionToFeedback(e){';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.replace(text, '&& false ' + text);
|
||||
str = str.replace(text, text + 'return !1;');
|
||||
return str;
|
||||
},
|
||||
|
||||
|
@ -26,7 +26,7 @@ export class WebGL2Player {
|
||||
};
|
||||
|
||||
private targetFps = 60;
|
||||
private frameInterval = Math.ceil(1000 / this.targetFps);
|
||||
private frameInterval = 0;
|
||||
private lastFrameTime = 0;
|
||||
|
||||
private animFrameId: number | null = null;
|
||||
@ -73,7 +73,8 @@ export class WebGL2Player {
|
||||
|
||||
setTargetFps(target: number) {
|
||||
this.targetFps = target;
|
||||
this.frameInterval = Math.ceil(1000 / target);
|
||||
this.lastFrameTime = 0;
|
||||
this.frameInterval = target ? Math.floor(1000 / target) : 0;
|
||||
}
|
||||
|
||||
getCanvas() {
|
||||
@ -94,6 +95,11 @@ export class WebGL2Player {
|
||||
}
|
||||
|
||||
drawFrame() {
|
||||
// Don't draw when FPS is 0
|
||||
if (this.targetFps === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit FPS
|
||||
if (this.targetFps < 60) {
|
||||
const currentTime = performance.now();
|
||||
@ -233,10 +239,10 @@ export class WebGL2Player {
|
||||
const gl = this.gl;
|
||||
if (gl) {
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
gl.useProgram(null);
|
||||
|
||||
for (const resource of this.resources) {
|
||||
if (resource instanceof WebGLProgram) {
|
||||
gl.useProgram(null);
|
||||
gl.deleteProgram(resource);
|
||||
} else if (resource instanceof WebGLShader) {
|
||||
gl.deleteShader(resource);
|
||||
|
@ -37,13 +37,7 @@ type RemotePlayConsole = {
|
||||
|
||||
export class RemotePlayManager {
|
||||
private static instance: RemotePlayManager;
|
||||
public static getInstance(): RemotePlayManager {
|
||||
if (!this.instance) {
|
||||
this.instance = new RemotePlayManager();
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
public static getInstance = () => RemotePlayManager.instance ?? (RemotePlayManager.instance = new RemotePlayManager());
|
||||
|
||||
private isInitialized = false;
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { limitVideoPlayerFps } from "../stream/stream-settings-utils";
|
||||
|
||||
export class RendererShortcut {
|
||||
static toggleVisibility(): boolean {
|
||||
const $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]');
|
||||
@ -6,6 +10,9 @@ export class RendererShortcut {
|
||||
}
|
||||
|
||||
$mediaContainer.classList.toggle('bx-gone');
|
||||
return !$mediaContainer.classList.contains('bx-gone');
|
||||
const isShowing = !$mediaContainer.classList.contains('bx-gone');
|
||||
// Switch FPS
|
||||
limitVideoPlayerFps(isShowing ? getPref(PrefKey.VIDEO_MAX_FPS) : 0);
|
||||
return isShowing;
|
||||
}
|
||||
}
|
||||
|
@ -50,13 +50,7 @@ enum StreamBadge {
|
||||
|
||||
export class StreamBadges {
|
||||
private static instance: StreamBadges;
|
||||
public static getInstance(): StreamBadges {
|
||||
if (!StreamBadges.instance) {
|
||||
StreamBadges.instance = new StreamBadges();
|
||||
}
|
||||
|
||||
return StreamBadges.instance;
|
||||
}
|
||||
public static getInstance = () => StreamBadges.instance ?? (StreamBadges.instance = new StreamBadges());
|
||||
|
||||
private serverInfo: StreamServerInfo = {};
|
||||
|
||||
|
@ -45,8 +45,7 @@ export function onChangeVideoPlayerType() {
|
||||
}
|
||||
|
||||
|
||||
export function limitVideoPlayerFps() {
|
||||
const targetFps = getPref(PrefKey.VIDEO_MAX_FPS);
|
||||
export function limitVideoPlayerFps(targetFps: number) {
|
||||
const streamPlayer = STATES.currentStream.streamPlayer;
|
||||
streamPlayer?.getWebGL2Player()?.setTargetFps(targetFps);
|
||||
}
|
||||
@ -58,7 +57,7 @@ export function updateVideoPlayer() {
|
||||
return;
|
||||
}
|
||||
|
||||
limitVideoPlayerFps();
|
||||
limitVideoPlayerFps(getPref(PrefKey.VIDEO_MAX_FPS));
|
||||
|
||||
const options = {
|
||||
processing: getPref(PrefKey.VIDEO_PROCESSING),
|
||||
|
@ -9,13 +9,7 @@ import { StreamStat, StreamStatsCollector, type StreamStatGrade } from "@/utils/
|
||||
|
||||
export class StreamStats {
|
||||
private static instance: StreamStats;
|
||||
public static getInstance(): StreamStats {
|
||||
if (!StreamStats.instance) {
|
||||
StreamStats.instance = new StreamStats();
|
||||
}
|
||||
|
||||
return StreamStats.instance;
|
||||
}
|
||||
public static getInstance = () => StreamStats.instance ?? (StreamStats.instance = new StreamStats());
|
||||
|
||||
private intervalId?: number | null;
|
||||
private readonly REFRESH_INTERVAL = 1 * 1000;
|
||||
|
@ -88,12 +88,7 @@ export abstract class NavigationDialog {
|
||||
|
||||
export class NavigationDialogManager {
|
||||
private static instance: NavigationDialogManager;
|
||||
public static getInstance(): NavigationDialogManager {
|
||||
if (!NavigationDialogManager.instance) {
|
||||
NavigationDialogManager.instance = new NavigationDialogManager();
|
||||
}
|
||||
return NavigationDialogManager.instance;
|
||||
}
|
||||
public static getInstance = () => NavigationDialogManager.instance ?? (NavigationDialogManager.instance = new NavigationDialogManager());
|
||||
|
||||
private static readonly GAMEPAD_POLLING_INTERVAL = 50;
|
||||
private static readonly GAMEPAD_KEYS = [
|
||||
|
@ -11,12 +11,7 @@ import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
export class RemotePlayNavigationDialog extends NavigationDialog {
|
||||
private static instance: RemotePlayNavigationDialog;
|
||||
public static getInstance(): RemotePlayNavigationDialog {
|
||||
if (!RemotePlayNavigationDialog.instance) {
|
||||
RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog();
|
||||
}
|
||||
return RemotePlayNavigationDialog.instance;
|
||||
}
|
||||
public static getInstance = () => RemotePlayNavigationDialog.instance ?? (RemotePlayNavigationDialog.instance = new RemotePlayNavigationDialog());
|
||||
|
||||
private readonly STATE_LABELS: Record<RemotePlayConsoleState, string> = {
|
||||
[RemotePlayConsoleState.ON]: t('powered-on'),
|
||||
|
@ -64,12 +64,7 @@ type SettingTab = {
|
||||
|
||||
export class SettingsNavigationDialog extends NavigationDialog {
|
||||
private static instance: SettingsNavigationDialog;
|
||||
public static getInstance(): SettingsNavigationDialog {
|
||||
if (!SettingsNavigationDialog.instance) {
|
||||
SettingsNavigationDialog.instance = new SettingsNavigationDialog();
|
||||
}
|
||||
return SettingsNavigationDialog.instance;
|
||||
}
|
||||
public static getInstance = () => SettingsNavigationDialog.instance ?? (SettingsNavigationDialog.instance = new SettingsNavigationDialog());
|
||||
|
||||
$container!: HTMLElement;
|
||||
private $tabs!: HTMLElement;
|
||||
@ -409,7 +404,9 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
||||
onChange: onChangeVideoPlayerType,
|
||||
}, {
|
||||
pref: PrefKey.VIDEO_MAX_FPS,
|
||||
onChange: limitVideoPlayerFps,
|
||||
onChange: e => {
|
||||
limitVideoPlayerFps(parseInt(e.target.value));
|
||||
},
|
||||
}, {
|
||||
pref: PrefKey.VIDEO_POWER_PREFERENCE,
|
||||
onChange: () => {
|
||||
|
@ -2,12 +2,7 @@ import { CE } from "@/utils/html";
|
||||
|
||||
export class FullscreenText {
|
||||
private static instance: FullscreenText;
|
||||
public static getInstance(): FullscreenText {
|
||||
if (!FullscreenText.instance) {
|
||||
FullscreenText.instance = new FullscreenText();
|
||||
}
|
||||
return FullscreenText.instance;
|
||||
}
|
||||
public static getInstance = () => FullscreenText.instance ?? (FullscreenText.instance = new FullscreenText());
|
||||
|
||||
$text: HTMLElement;
|
||||
|
||||
|
@ -39,6 +39,10 @@ export const enum ControllerDeviceVibration {
|
||||
}
|
||||
|
||||
|
||||
export type GameBarPosition = 'bottom-left' | 'bottom-right' | 'off';
|
||||
export type GameBarPositionOptions = Record<GameBarPosition, string>;
|
||||
|
||||
|
||||
function getSupportedCodecProfiles() {
|
||||
const options: PartialRecord<CodecProfile, string> = {
|
||||
default: t('default'),
|
||||
@ -323,12 +327,12 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
[PrefKey.GAME_BAR_POSITION]: {
|
||||
requiredVariants: 'full',
|
||||
label: t('position'),
|
||||
default: 'bottom-left',
|
||||
default: 'bottom-left' satisfies GameBarPosition,
|
||||
options: {
|
||||
'bottom-left': t('bottom-left'),
|
||||
'bottom-right': t('bottom-right'),
|
||||
'off': t('off'),
|
||||
},
|
||||
} satisfies GameBarPositionOptions,
|
||||
},
|
||||
|
||||
[PrefKey.LOCAL_CO_OP_ENABLED]: {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { STATES } from "./global";
|
||||
import { humanFileSize, secondsToHm } from "./html";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export enum StreamStat {
|
||||
PING = 'ping',
|
||||
@ -92,13 +94,7 @@ type CurrentStats = {
|
||||
|
||||
export class StreamStatsCollector {
|
||||
private static instance: StreamStatsCollector;
|
||||
public static getInstance(): StreamStatsCollector {
|
||||
if (!StreamStatsCollector.instance) {
|
||||
StreamStatsCollector.instance = new StreamStatsCollector();
|
||||
}
|
||||
|
||||
return StreamStatsCollector.instance;
|
||||
}
|
||||
public static getInstance = () => StreamStatsCollector.instance ?? (StreamStatsCollector.instance = new StreamStatsCollector());
|
||||
|
||||
// Collect in background - 60 seconds
|
||||
static readonly INTERVAL_BACKGROUND = 60 * 1000;
|
||||
@ -127,7 +123,8 @@ export class StreamStatsCollector {
|
||||
[StreamStat.FPS]: {
|
||||
current: 0,
|
||||
toString() {
|
||||
return this.current.toString();
|
||||
const maxFps = getPref(PrefKey.VIDEO_MAX_FPS);
|
||||
return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString();
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -3,13 +3,7 @@ import { STATES } from "./global";
|
||||
|
||||
export class XcloudApi {
|
||||
private static instance: XcloudApi;
|
||||
public static getInstance(): XcloudApi {
|
||||
if (!XcloudApi.instance) {
|
||||
XcloudApi.instance = new XcloudApi();
|
||||
}
|
||||
|
||||
return XcloudApi.instance;
|
||||
}
|
||||
public static getInstance = () => XcloudApi.instance ?? (XcloudApi.instance = new XcloudApi());
|
||||
|
||||
private CACHE_TITLES: {[key: string]: XcloudTitleInfo} = {};
|
||||
private CACHE_WAIT_TIME: {[key: string]: XcloudWaitTimeInfo} = {};
|
||||
|
@ -13,9 +13,25 @@ import { BypassServerIps } from "@/enums/bypass-servers";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, StreamResolution, StreamTouchController } from "./settings-storages/global-settings-storage";
|
||||
|
||||
export
|
||||
class XcloudInterceptor {
|
||||
static async #handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
||||
export class XcloudInterceptor {
|
||||
private static readonly SERVER_EMOJIS = {
|
||||
AustraliaEast: '🇦🇺',
|
||||
AustraliaSouthEast: '🇦🇺',
|
||||
BrazilSouth: '🇧🇷',
|
||||
EastUS: '🇺🇸',
|
||||
EastUS2: '🇺🇸',
|
||||
JapanEast: '🇯🇵',
|
||||
KoreaCentral: '🇰🇷',
|
||||
MexicoCentral: '🇲🇽',
|
||||
NorthCentralUs: '🇺🇸',
|
||||
SouthCentralUS: '🇺🇸',
|
||||
UKSouth: '🇬🇧',
|
||||
WestEurope: '🇪🇺',
|
||||
WestUS: '🇺🇸',
|
||||
WestUS2: '🇺🇸',
|
||||
};
|
||||
|
||||
private static async handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
||||
const bypassServer = getPref(PrefKey.SERVER_BYPASS_RESTRICTION);
|
||||
if (bypassServer !== 'off') {
|
||||
const ip = BypassServerIps[bypassServer as keyof typeof BypassServerIps];
|
||||
@ -35,24 +51,8 @@ class XcloudInterceptor {
|
||||
RemotePlayManager.getInstance().xcloudToken = obj.gsToken;
|
||||
|
||||
// Get server list
|
||||
const serverEmojis = {
|
||||
AustraliaEast: '🇦🇺',
|
||||
AustraliaSouthEast: '🇦🇺',
|
||||
BrazilSouth: '🇧🇷',
|
||||
EastUS: '🇺🇸',
|
||||
EastUS2: '🇺🇸',
|
||||
JapanEast: '🇯🇵',
|
||||
KoreaCentral: '🇰🇷',
|
||||
MexicoCentral: '🇲🇽',
|
||||
NorthCentralUs: '🇺🇸',
|
||||
SouthCentralUS: '🇺🇸',
|
||||
UKSouth: '🇬🇧',
|
||||
WestEurope: '🇪🇺',
|
||||
WestUS: '🇺🇸',
|
||||
WestUS2: '🇺🇸',
|
||||
};
|
||||
|
||||
const serverRegex = /\/\/(\w+)\./;
|
||||
const serverEmojis = XcloudInterceptor.SERVER_EMOJIS;
|
||||
|
||||
for (let region of obj.offeringSettings.regions) {
|
||||
const regionName = region.name as keyof typeof serverEmojis;
|
||||
@ -91,7 +91,7 @@ class XcloudInterceptor {
|
||||
return response;
|
||||
}
|
||||
|
||||
static async #handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
||||
private static async handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
||||
const PREF_STREAM_TARGET_RESOLUTION = getPref(PrefKey.STREAM_TARGET_RESOLUTION);
|
||||
const PREF_STREAM_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE);
|
||||
|
||||
@ -129,7 +129,7 @@ class XcloudInterceptor {
|
||||
return NATIVE_FETCH(newRequest);
|
||||
}
|
||||
|
||||
static async #handleWaitTime(request: RequestInfo | URL, init?: RequestInit) {
|
||||
private static async handleWaitTime(request: RequestInfo | URL, init?: RequestInit) {
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
|
||||
if (getPref(PrefKey.UI_LOADING_SCREEN_WAIT_TIME)) {
|
||||
@ -143,7 +143,7 @@ class XcloudInterceptor {
|
||||
return response;
|
||||
}
|
||||
|
||||
static async #handleConfiguration(request: RequestInfo | URL, init?: RequestInit) {
|
||||
private static async handleConfiguration(request: RequestInfo | URL, init?: RequestInit) {
|
||||
if ((request as Request).method !== 'GET') {
|
||||
return NATIVE_FETCH(request, init);
|
||||
}
|
||||
@ -213,13 +213,13 @@ class XcloudInterceptor {
|
||||
|
||||
// Server list
|
||||
if (url.endsWith('/v2/login/user')) {
|
||||
return XcloudInterceptor.#handleLogin(request, init);
|
||||
return XcloudInterceptor.handleLogin(request, init);
|
||||
} else if (url.endsWith('/sessions/cloud/play')) { // Get session
|
||||
return XcloudInterceptor.#handlePlay(request, init);
|
||||
return XcloudInterceptor.handlePlay(request, init);
|
||||
} else if (url.includes('xboxlive.com') && url.includes('/waittime/')) {
|
||||
return XcloudInterceptor.#handleWaitTime(request, init);
|
||||
return XcloudInterceptor.handleWaitTime(request, init);
|
||||
} else if (url.endsWith('/configuration')) {
|
||||
return XcloudInterceptor.#handleConfiguration(request, init);
|
||||
return XcloudInterceptor.handleConfiguration(request, init);
|
||||
} else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') {
|
||||
return patchIceCandidates(request as Request);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
|
||||
export class XhomeInterceptor {
|
||||
static #consoleAddrs: RemotePlayConsoleAddresses = {};
|
||||
private static consoleAddrs: RemotePlayConsoleAddresses = {};
|
||||
|
||||
private static readonly BASE_DEVICE_INFO = {
|
||||
appInfo: {
|
||||
@ -52,7 +52,7 @@ export class XhomeInterceptor {
|
||||
},
|
||||
};
|
||||
|
||||
static async #handleLogin(request: Request) {
|
||||
private static async handleLogin(request: Request) {
|
||||
try {
|
||||
const clone = (request as Request).clone();
|
||||
|
||||
@ -74,7 +74,7 @@ export class XhomeInterceptor {
|
||||
return NATIVE_FETCH(request);
|
||||
}
|
||||
|
||||
static async #handleConfiguration(request: Request | URL) {
|
||||
private static async handleConfiguration(request: Request | URL) {
|
||||
const response = await NATIVE_FETCH(request);
|
||||
|
||||
const obj = await response.clone().json()
|
||||
@ -90,15 +90,15 @@ export class XhomeInterceptor {
|
||||
|
||||
const serverDetails = obj.serverDetails;
|
||||
if (serverDetails.ipAddress) {
|
||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
||||
XhomeInterceptor.consoleAddrs[serverDetails.ipAddress] = processPorts(serverDetails.port);
|
||||
}
|
||||
|
||||
if (serverDetails.ipV4Address) {
|
||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
||||
XhomeInterceptor.consoleAddrs[serverDetails.ipV4Address] = processPorts(serverDetails.ipV4Port);
|
||||
}
|
||||
|
||||
if (serverDetails.ipV6Address) {
|
||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
||||
XhomeInterceptor.consoleAddrs[serverDetails.ipV6Address] = processPorts(serverDetails.ipV6Port);
|
||||
}
|
||||
|
||||
response.json = () => Promise.resolve(obj);
|
||||
@ -107,7 +107,7 @@ export class XhomeInterceptor {
|
||||
return response;
|
||||
}
|
||||
|
||||
static async #handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) {
|
||||
private static async handleInputConfigs(request: Request | URL, opts: {[index: string]: any}) {
|
||||
const response = await NATIVE_FETCH(request);
|
||||
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.ALL) {
|
||||
@ -144,7 +144,7 @@ export class XhomeInterceptor {
|
||||
return response;
|
||||
}
|
||||
|
||||
static async #handleTitles(request: Request) {
|
||||
private static async handleTitles(request: Request) {
|
||||
const clone = request.clone();
|
||||
|
||||
const headers: {[index: string]: any} = {};
|
||||
@ -163,7 +163,7 @@ export class XhomeInterceptor {
|
||||
return NATIVE_FETCH(request);
|
||||
}
|
||||
|
||||
static async #handlePlay(request: RequestInfo | URL) {
|
||||
private static async handlePlay(request: RequestInfo | URL) {
|
||||
const clone = (request as Request).clone();
|
||||
const body = await clone.json();
|
||||
|
||||
@ -216,17 +216,17 @@ export class XhomeInterceptor {
|
||||
|
||||
// Get console IP
|
||||
if (url.includes('/configuration')) {
|
||||
return XhomeInterceptor.#handleConfiguration(request);
|
||||
return XhomeInterceptor.handleConfiguration(request);
|
||||
} else if (url.endsWith('/sessions/home/play')) {
|
||||
return XhomeInterceptor.#handlePlay(request);
|
||||
return XhomeInterceptor.handlePlay(request);
|
||||
} else if (url.includes('inputconfigs')) {
|
||||
return XhomeInterceptor.#handleInputConfigs(request, opts);
|
||||
return XhomeInterceptor.handleInputConfigs(request, opts);
|
||||
} else if (url.includes('/login/user')) {
|
||||
return XhomeInterceptor.#handleLogin(request);
|
||||
return XhomeInterceptor.handleLogin(request);
|
||||
} else if (url.endsWith('/titles')) {
|
||||
return XhomeInterceptor.#handleTitles(request);
|
||||
return XhomeInterceptor.handleTitles(request);
|
||||
} else if (url && url.endsWith('/ice') && url.includes('/sessions/') && (request as Request).method === 'GET') {
|
||||
return patchIceCandidates(request, XhomeInterceptor.#consoleAddrs);
|
||||
return patchIceCandidates(request, XhomeInterceptor.consoleAddrs);
|
||||
}
|
||||
|
||||
return await NATIVE_FETCH(request);
|
||||
|
Reference in New Issue
Block a user