mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-01 20:01:44 +02:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
e75fa397ee | |||
98a9f4fc37 | |||
dee8c9dbd0 | |||
d31a06be89 | |||
277c777121 | |||
385fd71e86 | |||
986d9fe088 | |||
6de235ce2f |
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 5.1.0
|
// @version 5.1.1
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
110
dist/better-xcloud.user.js
vendored
110
dist/better-xcloud.user.js
vendored
@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 5.1.0
|
// @version 5.1.1
|
||||||
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
// @description Improve Xbox Cloud Gaming (xCloud) experience
|
||||||
// @author redphx
|
// @author redphx
|
||||||
// @license MIT
|
// @license MIT
|
||||||
@ -111,7 +111,7 @@ function deepClone(obj) {
|
|||||||
return obj;
|
return obj;
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
var SCRIPT_VERSION = "5.1.0", AppInterface = window.AppInterface;
|
var SCRIPT_VERSION = "5.1.1", AppInterface = window.AppInterface;
|
||||||
UserAgent.init();
|
UserAgent.init();
|
||||||
var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {
|
var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {
|
||||||
isPlaying: !1,
|
isPlaying: !1,
|
||||||
@ -487,6 +487,7 @@ var SUPPORTED_LANGUAGES = {
|
|||||||
sharpness: "Sharpness",
|
sharpness: "Sharpness",
|
||||||
"shortcut-keys": "Shortcut keys",
|
"shortcut-keys": "Shortcut keys",
|
||||||
show: "Show",
|
show: "Show",
|
||||||
|
"show-controller-connection-status": "Show controller connection status",
|
||||||
"show-game-art": "Show game art",
|
"show-game-art": "Show game art",
|
||||||
"show-hide": "Show/hide",
|
"show-hide": "Show/hide",
|
||||||
"show-stats-on-startup": "Show stats when starting the game",
|
"show-stats-on-startup": "Show stats when starting the game",
|
||||||
@ -990,6 +991,7 @@ var PrefKey;
|
|||||||
PrefKey2["CONTROLLER_ENABLE_VIBRATION"] = "controller_enable_vibration";
|
PrefKey2["CONTROLLER_ENABLE_VIBRATION"] = "controller_enable_vibration";
|
||||||
PrefKey2["CONTROLLER_DEVICE_VIBRATION"] = "controller_device_vibration";
|
PrefKey2["CONTROLLER_DEVICE_VIBRATION"] = "controller_device_vibration";
|
||||||
PrefKey2["CONTROLLER_VIBRATION_INTENSITY"] = "controller_vibration_intensity";
|
PrefKey2["CONTROLLER_VIBRATION_INTENSITY"] = "controller_vibration_intensity";
|
||||||
|
PrefKey2["CONTROLLER_SHOW_CONNECTION_STATUS"] = "controller_show_connection_status";
|
||||||
PrefKey2["NATIVE_MKB_ENABLED"] = "native_mkb_enabled";
|
PrefKey2["NATIVE_MKB_ENABLED"] = "native_mkb_enabled";
|
||||||
PrefKey2["NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY"] = "native_mkb_scroll_x_sensitivity";
|
PrefKey2["NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY"] = "native_mkb_scroll_x_sensitivity";
|
||||||
PrefKey2["NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY"] = "native_mkb_scroll_y_sensitivity";
|
PrefKey2["NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY"] = "native_mkb_scroll_y_sensitivity";
|
||||||
@ -1269,6 +1271,10 @@ class Preferences {
|
|||||||
target: "_blank"
|
target: "_blank"
|
||||||
}, t("enable-local-co-op-support-note"))
|
}, t("enable-local-co-op-support-note"))
|
||||||
},
|
},
|
||||||
|
[PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS]: {
|
||||||
|
label: t("show-controller-connection-status"),
|
||||||
|
default: !0
|
||||||
|
},
|
||||||
[PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: {
|
[PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: {
|
||||||
default: !1
|
default: !1
|
||||||
},
|
},
|
||||||
@ -1733,7 +1739,7 @@ class Screenshot {
|
|||||||
$player = streamPlayer.getPlayerElement(StreamPlayerType.VIDEO);
|
$player = streamPlayer.getPlayerElement(StreamPlayerType.VIDEO);
|
||||||
if (!$player || !$player.isConnected)
|
if (!$player || !$player.isConnected)
|
||||||
return;
|
return;
|
||||||
$player.parentElement.addEventListener("animationend", this.#onAnimationEnd), $player.parentElement.classList.add("bx-taking-screenshot");
|
$player.parentElement.addEventListener("animationend", this.#onAnimationEnd, { once: !0 }), $player.parentElement.classList.add("bx-taking-screenshot");
|
||||||
const canvasContext = Screenshot.#canvasContext;
|
const canvasContext = Screenshot.#canvasContext;
|
||||||
if ($player instanceof HTMLCanvasElement)
|
if ($player instanceof HTMLCanvasElement)
|
||||||
streamPlayer.getWebGL2Player().drawFrame();
|
streamPlayer.getWebGL2Player().drawFrame();
|
||||||
@ -3533,10 +3539,10 @@ class StreamSettings {
|
|||||||
const $tab = $container.querySelector(`.bx-stream-settings-tabs svg[data-group=${tabId}]`);
|
const $tab = $container.querySelector(`.bx-stream-settings-tabs svg[data-group=${tabId}]`);
|
||||||
$tab && $tab.dispatchEvent(new Event("click"));
|
$tab && $tab.dispatchEvent(new Event("click"));
|
||||||
}
|
}
|
||||||
this.$overlay.classList.remove("bx-gone"), this.$overlay.dataset.isPlaying = STATES.isPlaying.toString(), $container.classList.remove("bx-gone"), document.body.classList.add("bx-no-scroll");
|
this.$overlay.classList.remove("bx-gone"), this.$overlay.dataset.isPlaying = STATES.isPlaying.toString(), $container.classList.remove("bx-gone"), document.body.classList.add("bx-no-scroll"), BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
||||||
}
|
}
|
||||||
hide() {
|
hide() {
|
||||||
this.$overlay.classList.add("bx-gone"), this.$container.classList.add("bx-gone"), document.body.classList.remove("bx-no-scroll");
|
this.$overlay.classList.add("bx-gone"), this.$container.classList.add("bx-gone"), document.body.classList.remove("bx-no-scroll"), BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||||
}
|
}
|
||||||
#setupDialog() {
|
#setupDialog() {
|
||||||
let $tabs, $settings;
|
let $tabs, $settings;
|
||||||
@ -4949,7 +4955,7 @@ function interceptHttpRequests() {
|
|||||||
if (url.startsWith("https://emerald.xboxservices.com/xboxcomfd/experimentation"))
|
if (url.startsWith("https://emerald.xboxservices.com/xboxcomfd/experimentation"))
|
||||||
try {
|
try {
|
||||||
const response = await NATIVE_FETCH(request, init), json = await response.json();
|
const response = await NATIVE_FETCH(request, init), json = await response.json();
|
||||||
if (json && json.exp && json.treatments)
|
if (json && json.exp && json.exp.treatments)
|
||||||
for (let key in FeatureGates)
|
for (let key in FeatureGates)
|
||||||
json.exp.treatments[key] = FeatureGates[key];
|
json.exp.treatments[key] = FeatureGates[key];
|
||||||
return response.json = () => Promise.resolve(json), response;
|
return response.json = () => Promise.resolve(json), response;
|
||||||
@ -5739,6 +5745,11 @@ true` + ",this._connectionType=";
|
|||||||
if (!str2.includes('if(!e)throw new Error("RequestInfo.origin is falsy");'))
|
if (!str2.includes('if(!e)throw new Error("RequestInfo.origin is falsy");'))
|
||||||
return !1;
|
return !1;
|
||||||
return str2 = str2.replace('if(!e)throw new Error("RequestInfo.origin is falsy");', 'if (!e) e = "https://www.xbox.com";'), str2;
|
return str2 = str2.replace('if(!e)throw new Error("RequestInfo.origin is falsy");', 'if (!e) e = "https://www.xbox.com";'), str2;
|
||||||
|
},
|
||||||
|
exposeDialogRoutes(str2) {
|
||||||
|
if (!str2.includes("return{goBack:function(){"))
|
||||||
|
return !1;
|
||||||
|
return str2 = str2.replace("return{goBack:function(){", "return window.BX_EXPOSED.dialogRoutes = {goBack:function(){"), str2;
|
||||||
}
|
}
|
||||||
}, PATCH_ORDERS = [
|
}, PATCH_ORDERS = [
|
||||||
...getPref(PrefKey.NATIVE_MKB_ENABLED) === "on" ? [
|
...getPref(PrefKey.NATIVE_MKB_ENABLED) === "on" ? [
|
||||||
@ -5752,6 +5763,7 @@ true` + ",this._connectionType=";
|
|||||||
"overrideSettings",
|
"overrideSettings",
|
||||||
"broadcastPollingMode",
|
"broadcastPollingMode",
|
||||||
"exposeStreamSession",
|
"exposeStreamSession",
|
||||||
|
"exposeDialogRoutes",
|
||||||
getPref(PrefKey.UI_LAYOUT) !== "default" && "websiteLayout",
|
getPref(PrefKey.UI_LAYOUT) !== "default" && "websiteLayout",
|
||||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && "supportLocalCoOp",
|
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && "supportLocalCoOp",
|
||||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && "forceFortniteConsole",
|
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && "forceFortniteConsole",
|
||||||
@ -6146,6 +6158,7 @@ var SETTINGS_UI = {
|
|||||||
items: [
|
items: [
|
||||||
PrefKey.UI_LAYOUT,
|
PrefKey.UI_LAYOUT,
|
||||||
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
|
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
|
||||||
|
PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS,
|
||||||
PrefKey.STREAM_SIMPLIFY_MENU,
|
PrefKey.STREAM_SIMPLIFY_MENU,
|
||||||
PrefKey.SKIP_SPLASH_VIDEO,
|
PrefKey.SKIP_SPLASH_VIDEO,
|
||||||
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
||||||
@ -6593,7 +6606,7 @@ class StreamPlayer {
|
|||||||
// src/utils/monkey-patches.ts
|
// src/utils/monkey-patches.ts
|
||||||
function patchVideoApi() {
|
function patchVideoApi() {
|
||||||
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.SKIP_SPLASH_VIDEO), showFunc = function() {
|
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.SKIP_SPLASH_VIDEO), showFunc = function() {
|
||||||
if (this.style.visibility = "visible", this.removeEventListener("playing", showFunc), !this.videoWidth)
|
if (this.style.visibility = "visible", !this.videoWidth)
|
||||||
return;
|
return;
|
||||||
const playerOptions = {
|
const playerOptions = {
|
||||||
processing: getPref(PrefKey.VIDEO_PROCESSING),
|
processing: getPref(PrefKey.VIDEO_PROCESSING),
|
||||||
@ -6615,7 +6628,7 @@ function patchVideoApi() {
|
|||||||
}
|
}
|
||||||
const $parent = this.parentElement;
|
const $parent = this.parentElement;
|
||||||
if (!this.src && $parent.dataset.testid === "media-container")
|
if (!this.src && $parent.dataset.testid === "media-container")
|
||||||
this.addEventListener("playing", showFunc);
|
this.addEventListener("loadedmetadata", showFunc, { once: !0 });
|
||||||
return nativePlay.apply(this);
|
return nativePlay.apply(this);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -7007,40 +7020,69 @@ var GuideMenuTab;
|
|||||||
})(GuideMenuTab || (GuideMenuTab = {}));
|
})(GuideMenuTab || (GuideMenuTab = {}));
|
||||||
|
|
||||||
class GuideMenu {
|
class GuideMenu {
|
||||||
static #injectHome($root) {
|
static #BUTTONS = {
|
||||||
const $dividers = $root.querySelectorAll("div[class*=Divider-module__divider]");
|
streamSetting: createButton({
|
||||||
if (!$dividers)
|
label: t("stream-settings"),
|
||||||
return;
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
const $lastDivider = $dividers[$dividers.length - 1];
|
onClick: (e) => {
|
||||||
if (AppInterface) {
|
window.addEventListener(BxEvent.XCLOUD_DIALOG_DISMISSED, (e2) => {
|
||||||
const $btnQuit = createButton({
|
setTimeout(() => StreamSettings.getInstance().show(), 50);
|
||||||
label: t("close-app"),
|
}, { once: !0 }), window.BX_EXPOSED.dialogRoutes.closeAll();
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
|
}
|
||||||
onClick: (e) => {
|
}),
|
||||||
AppInterface.closeApp();
|
appSettings: createButton({
|
||||||
}
|
label: t("android-app-settings"),
|
||||||
});
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
$lastDivider.insertAdjacentElement("afterend", $btnQuit);
|
onClick: (e) => {
|
||||||
}
|
window.BX_EXPOSED.dialogRoutes.closeAll(), AppInterface.openAppSettings && AppInterface.openAppSettings();
|
||||||
}
|
}
|
||||||
static #injectHomePlaying($root) {
|
}),
|
||||||
const $btnQuit = $root.querySelector("a[class*=QuitGameButton]");
|
closeApp: createButton({
|
||||||
if (!$btnQuit)
|
label: t("close-app"),
|
||||||
return;
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
|
||||||
const $btnReload = createButton({
|
onClick: (e) => {
|
||||||
|
AppInterface.closeApp();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
reloadStream: createButton({
|
||||||
label: t("reload-stream"),
|
label: t("reload-stream"),
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
confirm(t("confirm-reload-stream")) && window.location.reload();
|
confirm(t("confirm-reload-stream")) && window.location.reload();
|
||||||
}
|
}
|
||||||
}), $btnHome = createButton({
|
}),
|
||||||
|
backToHome: createButton({
|
||||||
label: t("back-to-home"),
|
label: t("back-to-home"),
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
confirm(t("back-to-home-confirm")) && (window.location.href = window.location.href.substring(0, 31));
|
confirm(t("back-to-home-confirm")) && (window.location.href = window.location.href.substring(0, 31));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
$btnQuit.insertAdjacentElement("afterend", $btnReload), $btnReload.insertAdjacentElement("afterend", $btnHome);
|
};
|
||||||
|
static #renderButtons(buttons) {
|
||||||
|
const $div = CE("div", {});
|
||||||
|
for (let $button of buttons)
|
||||||
|
$div.appendChild($button);
|
||||||
|
return $div;
|
||||||
|
}
|
||||||
|
static #injectHome($root) {
|
||||||
|
const $dividers = $root.querySelectorAll("div[class*=Divider-module__divider]");
|
||||||
|
if (!$dividers)
|
||||||
|
return;
|
||||||
|
const buttons = [];
|
||||||
|
if (buttons.push(GuideMenu.#BUTTONS.streamSetting), AppInterface)
|
||||||
|
buttons.push(GuideMenu.#BUTTONS.appSettings), buttons.push(GuideMenu.#BUTTONS.closeApp);
|
||||||
|
const $buttons = GuideMenu.#renderButtons(buttons);
|
||||||
|
$dividers[$dividers.length - 1].insertAdjacentElement("afterend", $buttons);
|
||||||
|
}
|
||||||
|
static #injectHomePlaying($root) {
|
||||||
|
const $btnQuit = $root.querySelector("a[class*=QuitGameButton]");
|
||||||
|
if (!$btnQuit)
|
||||||
|
return;
|
||||||
|
const buttons = [];
|
||||||
|
buttons.push(GuideMenu.#BUTTONS.streamSetting), AppInterface && buttons.push(GuideMenu.#BUTTONS.appSettings);
|
||||||
|
const $buttons = GuideMenu.#renderButtons(buttons);
|
||||||
|
$btnQuit.insertAdjacentElement("afterend", $buttons);
|
||||||
const $btnXcloudHome = $root.querySelector("div[class^=HomeButtonWithDivider]");
|
const $btnXcloudHome = $root.querySelector("div[class^=HomeButtonWithDivider]");
|
||||||
$btnXcloudHome && ($btnXcloudHome.style.display = "none");
|
$btnXcloudHome && ($btnXcloudHome.style.display = "none");
|
||||||
}
|
}
|
||||||
@ -7105,7 +7147,9 @@ var unload = function() {
|
|||||||
});
|
});
|
||||||
observer.observe(document.documentElement, { subtree: !0, childList: !0 });
|
observer.observe(document.documentElement, { subtree: !0, childList: !0 });
|
||||||
}, main = function() {
|
}, main = function() {
|
||||||
if (waitForRootDialog(), patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), AppInterface && patchPointerLockApi(), getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext(), getPref(PrefKey.BLOCK_TRACKING) && patchMeControl(), STATES.userAgentHasTouchSupport && TouchController.updateCustomList(), overridePreloadState(), VibrationManager.initialSetup(), BX_FLAGS.CheckForUpdate && checkForUpdate(), addCss(), preloadFonts(), Toast.setup(), getPref(PrefKey.GAME_BAR_POSITION) !== "off" && GameBar.getInstance(), BX_FLAGS.PreloadUi && setupStreamUi(), Screenshot.setup(), GuideMenu.observe(), StreamBadges.setupEvents(), StreamStats.setupEvents(), EmulatedMkbHandler.setupEvents(), Patcher.init(), disablePwa(), window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad)), getPref(PrefKey.REMOTE_PLAY_ENABLED))
|
if (waitForRootDialog(), patchRtcPeerConnection(), patchRtcCodecs(), interceptHttpRequests(), patchVideoApi(), patchCanvasContext(), AppInterface && patchPointerLockApi(), getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext(), getPref(PrefKey.BLOCK_TRACKING) && patchMeControl(), STATES.userAgentHasTouchSupport && TouchController.updateCustomList(), overridePreloadState(), VibrationManager.initialSetup(), BX_FLAGS.CheckForUpdate && checkForUpdate(), addCss(), preloadFonts(), Toast.setup(), getPref(PrefKey.GAME_BAR_POSITION) !== "off" && GameBar.getInstance(), BX_FLAGS.PreloadUi && setupStreamUi(), Screenshot.setup(), GuideMenu.observe(), StreamBadges.setupEvents(), StreamStats.setupEvents(), EmulatedMkbHandler.setupEvents(), Patcher.init(), disablePwa(), getPref(PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS))
|
||||||
|
window.addEventListener("gamepadconnected", (e) => showGamepadToast(e.gamepad)), window.addEventListener("gamepaddisconnected", (e) => showGamepadToast(e.gamepad));
|
||||||
|
if (getPref(PrefKey.REMOTE_PLAY_ENABLED))
|
||||||
RemotePlay.detect();
|
RemotePlay.detect();
|
||||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === "all")
|
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === "all")
|
||||||
TouchController.setup();
|
TouchController.setup();
|
||||||
|
@ -303,8 +303,10 @@ function main() {
|
|||||||
disablePwa();
|
disablePwa();
|
||||||
|
|
||||||
// Show a toast when connecting/disconecting controller
|
// Show a toast when connecting/disconecting controller
|
||||||
window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad));
|
if (getPref(PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS)) {
|
||||||
window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad));
|
window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad));
|
||||||
|
window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad));
|
||||||
|
}
|
||||||
|
|
||||||
// Preload Remote Play
|
// Preload Remote Play
|
||||||
if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) {
|
if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) {
|
||||||
|
@ -635,6 +635,16 @@ true` + text;
|
|||||||
str = str.replace(text, 'if (!e) e = "https://www.xbox.com";');
|
str = str.replace(text, 'if (!e) e = "https://www.xbox.com";');
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
exposeDialogRoutes(str: string) {
|
||||||
|
const text = 'return{goBack:function(){';
|
||||||
|
if (!str.includes(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace(text, 'return window.BX_EXPOSED.dialogRoutes = {goBack:function(){');
|
||||||
|
return str;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let PATCH_ORDERS: PatchArray = [
|
let PATCH_ORDERS: PatchArray = [
|
||||||
@ -652,6 +662,7 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
'broadcastPollingMode',
|
'broadcastPollingMode',
|
||||||
|
|
||||||
'exposeStreamSession',
|
'exposeStreamSession',
|
||||||
|
'exposeDialogRoutes',
|
||||||
|
|
||||||
getPref(PrefKey.UI_LAYOUT) !== 'default' && 'websiteLayout',
|
getPref(PrefKey.UI_LAYOUT) !== 'default' && 'websiteLayout',
|
||||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
||||||
|
@ -255,6 +255,8 @@ export class StreamSettings {
|
|||||||
|
|
||||||
$container.classList.remove('bx-gone');
|
$container.classList.remove('bx-gone');
|
||||||
document.body.classList.add('bx-no-scroll');
|
document.body.classList.add('bx-no-scroll');
|
||||||
|
|
||||||
|
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
@ -262,6 +264,8 @@ export class StreamSettings {
|
|||||||
this.$container!.classList.add('bx-gone');
|
this.$container!.classList.add('bx-gone');
|
||||||
|
|
||||||
document.body.classList.remove('bx-no-scroll');
|
document.body.classList.remove('bx-no-scroll');
|
||||||
|
|
||||||
|
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#setupDialog() {
|
#setupDialog() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { PrefKey } from "@utils/preferences"
|
import { PrefKey, getPref } from "@utils/preferences"
|
||||||
import { BxEvent } from "@utils/bx-event"
|
import { BxEvent } from "@utils/bx-event"
|
||||||
import { getPref } from "@utils/preferences"
|
|
||||||
import { CE } from "@utils/html"
|
import { CE } from "@utils/html"
|
||||||
import { t } from "@utils/translation"
|
import { t } from "@utils/translation"
|
||||||
import { STATES } from "@utils/global"
|
import { STATES } from "@utils/global"
|
||||||
|
@ -89,6 +89,7 @@ const SETTINGS_UI = {
|
|||||||
items: [
|
items: [
|
||||||
PrefKey.UI_LAYOUT,
|
PrefKey.UI_LAYOUT,
|
||||||
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
|
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
|
||||||
|
PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS,
|
||||||
PrefKey.STREAM_SIMPLIFY_MENU,
|
PrefKey.STREAM_SIMPLIFY_MENU,
|
||||||
PrefKey.SKIP_SPLASH_VIDEO,
|
PrefKey.SKIP_SPLASH_VIDEO,
|
||||||
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
||||||
|
@ -1,33 +1,97 @@
|
|||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
import { AppInterface, STATES } from "@/utils/global";
|
import { AppInterface, STATES } from "@/utils/global";
|
||||||
import { createButton, ButtonStyle } from "@/utils/html";
|
import { createButton, ButtonStyle, CE } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
|
import { StreamSettings } from "../stream/stream-settings";
|
||||||
|
|
||||||
export enum GuideMenuTab {
|
export enum GuideMenuTab {
|
||||||
HOME,
|
HOME,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GuideMenu {
|
export class GuideMenu {
|
||||||
|
static #BUTTONS = {
|
||||||
|
streamSetting: createButton({
|
||||||
|
label: t('stream-settings'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
// Wait until the Guide dialog is closed
|
||||||
|
window.addEventListener(BxEvent.XCLOUD_DIALOG_DISMISSED, e => {
|
||||||
|
setTimeout(() => StreamSettings.getInstance().show(), 50);
|
||||||
|
}, {once: true});
|
||||||
|
|
||||||
|
// Close all xCloud's dialogs
|
||||||
|
window.BX_EXPOSED.dialogRoutes.closeAll();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
appSettings: createButton({
|
||||||
|
label: t('android-app-settings'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
// Close all xCloud's dialogs
|
||||||
|
window.BX_EXPOSED.dialogRoutes.closeAll();
|
||||||
|
|
||||||
|
AppInterface.openAppSettings && AppInterface.openAppSettings();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
closeApp: createButton({
|
||||||
|
label: t('close-app'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
|
||||||
|
onClick: e => {
|
||||||
|
AppInterface.closeApp();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
reloadStream: createButton({
|
||||||
|
label: t('reload-stream'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
confirm(t('confirm-reload-stream')) && window.location.reload();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
backToHome: createButton({
|
||||||
|
label: t('back-to-home'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
confirm(t('back-to-home-confirm')) && (window.location.href = window.location.href.substring(0, 31));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
static #renderButtons(buttons: HTMLElement[]) {
|
||||||
|
const $div = CE('div', {});
|
||||||
|
|
||||||
|
for (const $button of buttons) {
|
||||||
|
$div.appendChild($button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $div;
|
||||||
|
}
|
||||||
|
|
||||||
static #injectHome($root: HTMLElement) {
|
static #injectHome($root: HTMLElement) {
|
||||||
// Find the last divider
|
// Find the last divider
|
||||||
const $dividers = $root.querySelectorAll('div[class*=Divider-module__divider]');
|
const $dividers = $root.querySelectorAll('div[class*=Divider-module__divider]');
|
||||||
if (!$dividers) {
|
if (!$dividers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const $lastDivider = $dividers[$dividers.length - 1];
|
|
||||||
|
|
||||||
// Add "Close app" button
|
const buttons: HTMLElement[] = [];
|
||||||
|
|
||||||
|
// "Stream settings" button
|
||||||
|
buttons.push(GuideMenu.#BUTTONS.streamSetting);
|
||||||
|
|
||||||
|
// "App settings" & "Close app" buttons
|
||||||
if (AppInterface) {
|
if (AppInterface) {
|
||||||
const $btnQuit = createButton({
|
buttons.push(GuideMenu.#BUTTONS.appSettings);
|
||||||
label: t('close-app'),
|
buttons.push(GuideMenu.#BUTTONS.closeApp);
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
|
|
||||||
onClick: e => {
|
|
||||||
AppInterface.closeApp();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$lastDivider.insertAdjacentElement('afterend', $btnQuit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const $buttons = GuideMenu.#renderButtons(buttons);
|
||||||
|
|
||||||
|
const $lastDivider = $dividers[$dividers.length - 1];
|
||||||
|
$lastDivider.insertAdjacentElement('afterend', $buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
static #injectHomePlaying($root: HTMLElement) {
|
static #injectHomePlaying($root: HTMLElement) {
|
||||||
@ -36,25 +100,13 @@ export class GuideMenu {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add buttons
|
const buttons: HTMLElement[] = [];
|
||||||
const $btnReload = createButton({
|
|
||||||
label: t('reload-stream'),
|
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
|
||||||
onClick: e => {
|
|
||||||
confirm(t('confirm-reload-stream')) && window.location.reload();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const $btnHome = createButton({
|
buttons.push(GuideMenu.#BUTTONS.streamSetting);
|
||||||
label: t('back-to-home'),
|
AppInterface && buttons.push(GuideMenu.#BUTTONS.appSettings);
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
|
||||||
onClick: e => {
|
|
||||||
confirm(t('back-to-home-confirm')) && (window.location.href = window.location.href.substring(0, 31));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$btnQuit.insertAdjacentElement('afterend', $btnReload);
|
const $buttons = GuideMenu.#renderButtons(buttons);
|
||||||
$btnReload.insertAdjacentElement('afterend', $btnHome);
|
$btnQuit.insertAdjacentElement('afterend', $buttons);
|
||||||
|
|
||||||
// Hide xCloud's Home button
|
// Hide xCloud's Home button
|
||||||
const $btnXcloudHome = $root.querySelector('div[class^=HomeButtonWithDivider]') as HTMLElement;
|
const $btnXcloudHome = $root.querySelector('div[class^=HomeButtonWithDivider]') as HTMLElement;
|
||||||
|
@ -11,8 +11,6 @@ export function patchVideoApi() {
|
|||||||
// Show video player when it's ready
|
// Show video player when it's ready
|
||||||
const showFunc = function(this: HTMLVideoElement) {
|
const showFunc = function(this: HTMLVideoElement) {
|
||||||
this.style.visibility = 'visible';
|
this.style.visibility = 'visible';
|
||||||
this.removeEventListener('playing', showFunc);
|
|
||||||
|
|
||||||
if (!this.videoWidth) {
|
if (!this.videoWidth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -49,7 +47,7 @@ export function patchVideoApi() {
|
|||||||
const $parent = this.parentElement!!;
|
const $parent = this.parentElement!!;
|
||||||
// Video tag is stream player
|
// Video tag is stream player
|
||||||
if (!this.src && $parent.dataset.testid === 'media-container') {
|
if (!this.src && $parent.dataset.testid === 'media-container') {
|
||||||
this.addEventListener('playing', showFunc);
|
this.addEventListener('loadedmetadata', showFunc, {once: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
return nativePlay.apply(this);
|
return nativePlay.apply(this);
|
||||||
|
@ -576,7 +576,7 @@ export function interceptHttpRequests() {
|
|||||||
const response = await NATIVE_FETCH(request, init);
|
const response = await NATIVE_FETCH(request, init);
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
if (json && json.exp && json.treatments) {
|
if (json && json.exp && json.exp.treatments) {
|
||||||
for (const key in FeatureGates) {
|
for (const key in FeatureGates) {
|
||||||
json.exp.treatments[key] = FeatureGates[key]
|
json.exp.treatments[key] = FeatureGates[key]
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ export enum PrefKey {
|
|||||||
CONTROLLER_ENABLE_VIBRATION = 'controller_enable_vibration',
|
CONTROLLER_ENABLE_VIBRATION = 'controller_enable_vibration',
|
||||||
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
|
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
|
||||||
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
|
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
|
||||||
|
CONTROLLER_SHOW_CONNECTION_STATUS = 'controller_show_connection_status',
|
||||||
|
|
||||||
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
|
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
|
||||||
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
|
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
|
||||||
@ -384,6 +385,11 @@ export class Preferences {
|
|||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
[PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS]: {
|
||||||
|
label: t('show-controller-connection-status'),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
|
||||||
[PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: {
|
[PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
@ -57,7 +57,7 @@ export class Screenshot {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$player.parentElement!.addEventListener('animationend', this.#onAnimationEnd);
|
$player.parentElement!.addEventListener('animationend', this.#onAnimationEnd, { once: true });
|
||||||
$player.parentElement!.classList.add('bx-taking-screenshot');
|
$player.parentElement!.classList.add('bx-taking-screenshot');
|
||||||
|
|
||||||
const canvasContext = Screenshot.#canvasContext;
|
const canvasContext = Screenshot.#canvasContext;
|
||||||
|
@ -200,6 +200,7 @@ const Texts = {
|
|||||||
"sharpness": "Sharpness",
|
"sharpness": "Sharpness",
|
||||||
"shortcut-keys": "Shortcut keys",
|
"shortcut-keys": "Shortcut keys",
|
||||||
"show": "Show",
|
"show": "Show",
|
||||||
|
"show-controller-connection-status": "Show controller connection status",
|
||||||
"show-game-art": "Show game art",
|
"show-game-art": "Show game art",
|
||||||
"show-hide": "Show/hide",
|
"show-hide": "Show/hide",
|
||||||
"show-stats-on-startup": "Show stats when starting the game",
|
"show-stats-on-startup": "Show stats when starting the game",
|
||||||
|
Reference in New Issue
Block a user