Update better-xcloud.user.js

This commit is contained in:
redphx 2024-06-01 10:12:52 +07:00
parent 758501bcd3
commit 5d1a0a3428

View File

@ -2433,19 +2433,18 @@ var StreamBadge;
(function(StreamBadge2) { (function(StreamBadge2) {
StreamBadge2["PLAYTIME"] = "playtime"; StreamBadge2["PLAYTIME"] = "playtime";
StreamBadge2["BATTERY"] = "battery"; StreamBadge2["BATTERY"] = "battery";
StreamBadge2["IN"] = "in"; StreamBadge2["DOWNLOAD"] = "in";
StreamBadge2["OUT"] = "out"; StreamBadge2["UPLOAD"] = "out";
StreamBadge2["SERVER"] = "server"; StreamBadge2["SERVER"] = "server";
StreamBadge2["VIDEO"] = "video"; StreamBadge2["VIDEO"] = "video";
StreamBadge2["AUDIO"] = "audio"; StreamBadge2["AUDIO"] = "audio";
StreamBadge2["BREAK"] = "break";
})(StreamBadge || (StreamBadge = {})); })(StreamBadge || (StreamBadge = {}));
var StreamBadgeIcon = { var StreamBadgeIcon = {
[StreamBadge.PLAYTIME]: BxIcon.PLAYTIME, [StreamBadge.PLAYTIME]: BxIcon.PLAYTIME,
[StreamBadge.VIDEO]: BxIcon.DISPLAY, [StreamBadge.VIDEO]: BxIcon.DISPLAY,
[StreamBadge.BATTERY]: BxIcon.BATTERY, [StreamBadge.BATTERY]: BxIcon.BATTERY,
[StreamBadge.IN]: BxIcon.DOWNLOAD, [StreamBadge.DOWNLOAD]: BxIcon.DOWNLOAD,
[StreamBadge.OUT]: BxIcon.UPLOAD, [StreamBadge.UPLOAD]: BxIcon.UPLOAD,
[StreamBadge.SERVER]: BxIcon.SERVER, [StreamBadge.SERVER]: BxIcon.SERVER,
[StreamBadge.AUDIO]: BxIcon.AUDIO [StreamBadge.AUDIO]: BxIcon.AUDIO
}; };
@ -2465,6 +2464,7 @@ class StreamBadges {
#region = ""; #region = "";
startBatteryLevel = 100; startBatteryLevel = 100;
startTimestamp = 0; startTimestamp = 0;
#$container;
#cachedDoms = {}; #cachedDoms = {};
#interval; #interval;
#REFRESH_INTERVAL = 3000; #REFRESH_INTERVAL = 3000;
@ -2472,9 +2472,6 @@ class StreamBadges {
this.#region = region; this.#region = region;
} }
#renderBadge(name, value, color) { #renderBadge(name, value, color) {
if (name === StreamBadge.BREAK) {
return CE("div", { style: "display: block" });
}
let $badge; let $badge;
if (this.#cachedDoms[name]) { if (this.#cachedDoms[name]) {
$badge = this.#cachedDoms[name]; $badge = this.#cachedDoms[name];
@ -2488,8 +2485,8 @@ class StreamBadges {
this.#cachedDoms[name] = $badge; this.#cachedDoms[name] = $badge;
return $badge; return $badge;
} }
async#updateBadges(forceUpdate) { async#updateBadges(forceUpdate = false) {
if (!forceUpdate && !document.querySelector(".bx-badges")) { if (!this.#$container || !forceUpdate && !this.#$container.isConnected) {
this.#stop(); this.#stop();
return; return;
} }
@ -2523,8 +2520,8 @@ class StreamBadges {
} }
}); });
const badges = { const badges = {
[StreamBadge.IN]: totalIn ? this.#humanFileSize(totalIn) : null, [StreamBadge.DOWNLOAD]: totalIn ? this.#humanFileSize(totalIn) : null,
[StreamBadge.OUT]: totalOut ? this.#humanFileSize(totalOut) : null, [StreamBadge.UPLOAD]: totalOut ? this.#humanFileSize(totalOut) : null,
[StreamBadge.PLAYTIME]: playtime, [StreamBadge.PLAYTIME]: playtime,
[StreamBadge.BATTERY]: batteryLevel [StreamBadge.BATTERY]: batteryLevel
}; };
@ -2537,15 +2534,20 @@ class StreamBadges {
const $elm = this.#cachedDoms[name]; const $elm = this.#cachedDoms[name];
$elm && ($elm.lastElementChild.textContent = value); $elm && ($elm.lastElementChild.textContent = value);
if (name === StreamBadge.BATTERY) { if (name === StreamBadge.BATTERY) {
$elm.setAttribute("data-charging", isCharging.toString());
if (this.startBatteryLevel === 100 && batteryLevelInt === 100) { if (this.startBatteryLevel === 100 && batteryLevelInt === 100) {
$elm.style.display = "none"; $elm.classList.add("bx-gone");
} else { } else {
$elm.removeAttribute("style"); $elm.dataset.charging = isCharging.toString();
$elm.classList.remove("bx-gone");
} }
} }
} }
} }
async#start() {
await this.#updateBadges(true);
this.#stop();
this.#interval = window.setInterval(this.#updateBadges.bind(this), this.#REFRESH_INTERVAL);
}
#stop() { #stop() {
this.#interval && clearInterval(this.#interval); this.#interval && clearInterval(this.#interval);
this.#interval = null; this.#interval = null;
@ -2558,14 +2560,16 @@ class StreamBadges {
return hDisplay + mDisplay; return hDisplay + mDisplay;
} }
#humanFileSize(size) { #humanFileSize(size) {
const units = ["B", "kB", "MB", "GB", "TB"]; const units = ["B", "KB", "MB", "GB", "TB"];
let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
return (size / Math.pow(1024, i)).toFixed(2) + " " + units[i]; return (size / Math.pow(1024, i)).toFixed(2) + " " + units[i];
} }
async render() { async render() {
if (!this.#video) { if (this.#$container) {
await this.#getServerStats(); this.#start();
return this.#$container;
} }
await this.#getServerStats();
let video = ""; let video = "";
if (this.#resolution) { if (this.#resolution) {
video = `${this.#resolution.height}p`; video = `${this.#resolution.height}p`;
@ -2601,25 +2605,23 @@ class StreamBadges {
const BADGES = [ const BADGES = [
[StreamBadge.PLAYTIME, "1m", "#ff004d"], [StreamBadge.PLAYTIME, "1m", "#ff004d"],
[StreamBadge.BATTERY, batteryLevel, "#00b543"], [StreamBadge.BATTERY, batteryLevel, "#00b543"],
[StreamBadge.IN, this.#humanFileSize(0), "#29adff"], [StreamBadge.DOWNLOAD, this.#humanFileSize(0), "#29adff"],
[StreamBadge.OUT, this.#humanFileSize(0), "#ff77a8"], [StreamBadge.UPLOAD, this.#humanFileSize(0), "#ff77a8"],
[StreamBadge.BREAK],
[StreamBadge.SERVER, server, "#ff6c24"], [StreamBadge.SERVER, server, "#ff6c24"],
video ? [StreamBadge.VIDEO, video, "#742f29"] : null, video ? [StreamBadge.VIDEO, video, "#742f29"] : null,
audio ? [StreamBadge.AUDIO, audio, "#5f574f"] : null audio ? [StreamBadge.AUDIO, audio, "#5f574f"] : null
]; ];
const $wrapper = CE("div", { class: "bx-badges" }); const $container = CE("div", { class: "bx-badges" });
BADGES.forEach((item2) => { BADGES.forEach((item2) => {
if (!item2) { if (!item2) {
return; return;
} }
const $badge = this.#renderBadge(...item2); const $badge = this.#renderBadge(...item2);
$wrapper.appendChild($badge); $container.appendChild($badge);
}); });
await this.#updateBadges(true); this.#$container = $container;
this.#stop(); await this.#start();
this.#interval = window.setInterval(this.#updateBadges.bind(this), this.#REFRESH_INTERVAL); return $container;
return $wrapper;
} }
async#getServerStats() { async#getServerStats() {
const stats = await STATES.currentStream.peerConnection.getStats(); const stats = await STATES.currentStream.peerConnection.getStats();
@ -2631,10 +2633,10 @@ class StreamBadges {
let candidateId; let candidateId;
stats.forEach((stat) => { stats.forEach((stat) => {
if (stat.type === "codec") { if (stat.type === "codec") {
const mimeType = stat.mimeType.split("/"); const mimeType = stat.mimeType.split("/")[0];
if (mimeType[0] === "video") { if (mimeType === "video") {
allVideoCodecs[stat.id] = stat; allVideoCodecs[stat.id] = stat;
} else if (mimeType[0] === "audio") { } else if (mimeType === "audio") {
allAudioCodecs[stat.id] = stat; allAudioCodecs[stat.id] = stat;
} }
} else if (stat.type === "inbound-rtp" && stat.packetsReceived > 0) { } else if (stat.type === "inbound-rtp" && stat.packetsReceived > 0) {
@ -2688,6 +2690,16 @@ class StreamBadges {
} catch (e2) { } catch (e2) {
} }
}); });
window.addEventListener(BxEvent.XCLOUD_GUIDE_SHOWN, async (e) => {
const where = e.where;
if (where === XcloudGuideWhere.HOME && STATES.isPlaying) {
const $btnQuit = document.querySelector("#gamepass-dialog-root a[class*=QuitGameButton]");
if (!$btnQuit) {
return;
}
$btnQuit.insertAdjacentElement("beforebegin", await StreamBadges.getInstance().render());
}
});
} }
} }
@ -2761,14 +2773,6 @@ function injectStreamMenuButtons() {
if (item2.type !== "childList") { if (item2.type !== "childList") {
return; return;
} }
item2.removedNodes.forEach(($node) => {
if (!$node || $node.nodeType !== Node.ELEMENT_NODE) {
return;
}
if (!$node.className || !$node.className.startsWith) {
return;
}
});
item2.addedNodes.forEach(async ($node) => { item2.addedNodes.forEach(async ($node) => {
if (!$node || $node.nodeType !== Node.ELEMENT_NODE) { if (!$node || $node.nodeType !== Node.ELEMENT_NODE) {
return; return;
@ -5192,7 +5196,7 @@ var resizeVideoPlayer = function() {
height = Math.min(parentRect.height, Math.ceil(height)); height = Math.min(parentRect.height, Math.ceil(height));
$video.style.width = `${width}px`; $video.style.width = `${width}px`;
$video.style.height = `${height}px`; $video.style.height = `${height}px`;
$video.style.objectFit = "fill"; $video.style.objectFit = "contain";
} else { } else {
$video.style.width = "100%"; $video.style.width = "100%";
$video.style.height = "100%"; $video.style.height = "100%";
@ -6802,7 +6806,6 @@ div[data-testid=media-container].bx-taking-screenshot:before {
border-radius: 10px 0 0 10px; border-radius: 10px 0 0 10px;
} }
.bx-badges { .bx-badges {
position: absolute;
margin-left: 0px; margin-left: 0px;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
@ -6818,27 +6821,49 @@ div[data-testid=media-container].bx-taking-screenshot:before {
margin: 0 8px 8px 0; margin: 0 8px 8px 0;
box-shadow: 0px 0px 6px #000; box-shadow: 0px 0px 6px #000;
border-radius: 4px; border-radius: 4px;
} height: 30px;
.bx-badge svg {
width: 18px;
height: 18px;
} }
.bx-badge-name { .bx-badge-name {
background-color: #2d3036; background-color: #2d3036;
display: inline-block;
padding: 2px 8px;
border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px;
text-transform: uppercase; }
.bx-badge-name svg {
width: 16px;
height: 16px;
} }
.bx-badge-value { .bx-badge-value {
background-color: #808080; background-color: #808080;
display: inline-block;
padding: 2px 8px;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
} }
.bx-badge-name,
.bx-badge-value {
display: inline-block;
padding: 0 8px;
height: 30px;
line-height: 30px;
vertical-align: bottom;
}
.bx-badge-battery[data-charging=true] span:first-of-type::after { .bx-badge-battery[data-charging=true] span:first-of-type::after {
content: ' ⚡️'; content: ' ⚡️';
} }
div[class^=StreamMenu-module__container] .bx-badges {
position: absolute;
max-width: 500px;
}
#gamepass-dialog-root .bx-badges {
position: fixed;
top: 140px;
left: 460px;
max-width: 500px;
}
@media (min-width: 568px) and (max-height: 480px) {
#gamepass-dialog-root .bx-badges {
position: unset;
top: unset;
left: unset;
margin: 8px 0;
}
}
.bx-stats-bar { .bx-stats-bar {
display: block; display: block;
user-select: none; user-select: none;
@ -7374,7 +7399,7 @@ class MouseCursorHider {
} }
// src/modules/patches/controller-shortcuts.js // src/modules/patches/controller-shortcuts.js
var controller_shortcuts_default = "const currentGamepad = ${gamepadVar};\n\n// Share button on XS controller\nif (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {\n window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));\n}\n\nconst btnHome = currentGamepad.buttons[16];\nif (btnHome) {\n if (!this.bxHomeStates) {\n this.bxHomeStates = {};\n }\n\n if (btnHome.pressed) {\n this.gamepadIsIdle.set(currentGamepad.index, false);\n\n if (this.bxHomeStates[currentGamepad.index]) {\n const lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;\n\n if (currentGamepad.timestamp !== lastTimestamp) {\n this.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;\n\n const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);\n if (handled) {\n this.bxHomeStates[currentGamepad.index].shortcutPressed += 1;\n }\n }\n } else {\n // First time pressing > save current timestamp\n window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);\n this.bxHomeStates[currentGamepad.index] = {\n shortcutPressed: 0,\n timestamp: currentGamepad.timestamp,\n };\n }\n\n // Listen to next button press\n const intervalMs = 16;\n this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);\n\n // Hijack this button\n return;\n } else if (this.bxHomeStates[currentGamepad.index]) {\n const info = structuredClone(this.bxHomeStates[currentGamepad.index]);\n\n // Home button released\n this.bxHomeStates[currentGamepad.index] = null;\n\n if (info.shortcutPressed === 0) {\n const fakeGamepadMappings = [{\n GamepadIndex: currentGamepad.index,\n A: 0,\n B: 0,\n X: 0,\n Y: 0,\n LeftShoulder: 0,\n RightShoulder: 0,\n LeftTrigger: 0,\n RightTrigger: 0,\n View: 0,\n Menu: 0,\n LeftThumb: 0,\n RightThumb: 0,\n DPadUp: 0,\n DPadDown: 0,\n DPadLeft: 0,\n DPadRight: 0,\n Nexus: 1,\n LeftThumbXAxis: 0,\n LeftThumbYAxis: 0,\n RightThumbXAxis: 0,\n RightThumbYAxis: 0,\n PhysicalPhysicality: 0,\n VirtualPhysicality: 0,\n Dirty: true,\n Virtual: false,\n }];\n\n const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;\n const intervalMs = isLongPress ? 500 : 100;\n\n this.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);\n this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);\n return;\n }\n }\n}\n"; var controller_shortcuts_default = "const currentGamepad = ${gamepadVar};\n\n// Share button on XS controller\nif (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {\n window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));\n}\n\nconst btnHome = currentGamepad.buttons[16];\nif (btnHome) {\n if (!this.bxHomeStates) {\n this.bxHomeStates = {};\n }\n\n let intervalMs = 0;\n let hijack = false;\n\n if (btnHome.pressed) {\n hijack = true;\n intervalMs = 16;\n this.gamepadIsIdle.set(currentGamepad.index, false);\n\n if (this.bxHomeStates[currentGamepad.index]) {\n const lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;\n\n if (currentGamepad.timestamp !== lastTimestamp) {\n this.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;\n\n const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);\n if (handled) {\n this.bxHomeStates[currentGamepad.index].shortcutPressed += 1;\n }\n }\n } else {\n // First time pressing > save current timestamp\n window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);\n this.bxHomeStates[currentGamepad.index] = {\n shortcutPressed: 0,\n timestamp: currentGamepad.timestamp,\n };\n }\n } else if (this.bxHomeStates[currentGamepad.index]) {\n hijack = true;\n const info = structuredClone(this.bxHomeStates[currentGamepad.index]);\n\n // Home button released\n this.bxHomeStates[currentGamepad.index] = null;\n\n if (info.shortcutPressed === 0) {\n const fakeGamepadMappings = [{\n GamepadIndex: currentGamepad.index,\n A: 0,\n B: 0,\n X: 0,\n Y: 0,\n LeftShoulder: 0,\n RightShoulder: 0,\n LeftTrigger: 0,\n RightTrigger: 0,\n View: 0,\n Menu: 0,\n LeftThumb: 0,\n RightThumb: 0,\n DPadUp: 0,\n DPadDown: 0,\n DPadLeft: 0,\n DPadRight: 0,\n Nexus: 1,\n LeftThumbXAxis: 0,\n LeftThumbYAxis: 0,\n RightThumbXAxis: 0,\n RightThumbYAxis: 0,\n PhysicalPhysicality: 0,\n VirtualPhysicality: 0,\n Dirty: true,\n Virtual: false,\n }];\n\n const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;\n intervalMs = isLongPress ? 500 : 100;\n\n this.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);\n } else {\n intervalMs = 4;\n }\n }\n\n if (hijack && intervalMs) {\n // Listen to next button press\n this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);\n\n // Hijack this button\n return;\n }\n}\n";
// src/modules/patches/local-co-op-enable.js // src/modules/patches/local-co-op-enable.js
var local_co_op_enable_default = "let match;\nlet onGamepadChangedStr = this.onGamepadChanged.toString();\n\nonGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');\neval(`this.onGamepadChanged = function ${onGamepadChangedStr}`);\n\nlet onGamepadInputStr = this.onGamepadInput.toString();\n\nmatch = onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);\nif (match) {\n const gamepadIndexVar = match[0];\n onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`);\n eval(`this.onGamepadInput = function ${onGamepadInputStr}`);\n BxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');\n} else {\n BxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');\n}\n"; var local_co_op_enable_default = "let match;\nlet onGamepadChangedStr = this.onGamepadChanged.toString();\n\nonGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');\neval(`this.onGamepadChanged = function ${onGamepadChangedStr}`);\n\nlet onGamepadInputStr = this.onGamepadInput.toString();\n\nmatch = onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);\nif (match) {\n const gamepadIndexVar = match[0];\n onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`);\n eval(`this.onGamepadInput = function ${onGamepadInputStr}`);\n BxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');\n} else {\n BxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');\n}\n";
@ -8865,14 +8890,11 @@ var observeRootDialog = function($root) {
; ;
if (index === 0) { if (index === 0) {
BxEvent.dispatch(window, BxEvent.XCLOUD_GUIDE_SHOWN, { where: XcloudGuideWhere.HOME }); BxEvent.dispatch(window, BxEvent.XCLOUD_GUIDE_SHOWN, { where: XcloudGuideWhere.HOME });
console.log("quit", $addedElm.querySelector("a[class*=QuitGameButton]"));
} }
} }
} }
} }
} }
console.log("added", mutation.addedNodes);
console.log("removed", mutation.removedNodes);
const shown = $root.firstElementChild && $root.firstElementChild.childElementCount > 0 || false; const shown = $root.firstElementChild && $root.firstElementChild.childElementCount > 0 || false;
if (shown !== currentShown) { if (shown !== currentShown) {
currentShown = shown; currentShown = shown;
@ -9044,8 +9066,4 @@ window.addEventListener(BxEvent.STREAM_STOPPED, (e) => {
window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, (e) => { window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, (e) => {
Screenshot.takeScreenshot(); Screenshot.takeScreenshot();
}); });
window.addEventListener(BxEvent.XCLOUD_GUIDE_SHOWN, (e) => {
const where = e.where;
console.log("where", where);
});
main(); main();