From 49af04a3e04eb38c2dd3d5aa92c3f0f97e04b50f Mon Sep 17 00:00:00 2001
From: redphx <96280+redphx@users.noreply.github.com>
Date: Fri, 10 May 2024 18:38:35 +0700
Subject: [PATCH] Update better-xcloud.user.js
---
dist/better-xcloud.user.js | 1445 ++++++++++++++++++++----------------
1 file changed, 797 insertions(+), 648 deletions(-)
diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js
index 4fe1e6d..3bb0de4 100644
--- a/dist/better-xcloud.user.js
+++ b/dist/better-xcloud.user.js
@@ -47,6 +47,7 @@ var BxEvent;
BxEvent2["REMOTE_PLAY_FAILED"] = "bx-remote-play-failed";
BxEvent2["XCLOUD_SERVERS_READY"] = "bx-servers-ready";
BxEvent2["DATA_CHANNEL_CREATED"] = "bx-data-channel-created";
+ BxEvent2["GAME_BAR_ACTION_ACTIVATED"] = "bx-game-bar-action-activated";
})(BxEvent || (BxEvent = {}));
(function(BxEvent) {
function dispatch(target, eventName, data) {
@@ -158,6 +159,86 @@ var createButton = (options) => {
var CTN = document.createTextNode.bind(document);
window.BX_CE = createElement;
+// src/assets/svg/caret-right.svg
+var caret_right_default = "\n";
+
+// src/assets/svg/camera.svg
+var camera_default = "\n";
+
+// src/assets/svg/controller.svg
+var controller_default = "\n";
+
+// src/assets/svg/copy.svg
+var copy_default = "\n";
+
+// src/assets/svg/cursor-text.svg
+var cursor_text_default = "\n";
+
+// src/assets/svg/display.svg
+var display_default = "\n";
+
+// src/assets/svg/mouse-settings.svg
+var mouse_settings_default = "\n";
+
+// src/assets/svg/mouse.svg
+var mouse_default = "\n";
+
+// src/assets/svg/new.svg
+var new_default = "\n";
+
+// src/assets/svg/question.svg
+var question_default = "\n";
+
+// src/assets/svg/refresh.svg
+var refresh_default = "\n";
+
+// src/assets/svg/remote-play.svg
+var remote_play_default = "\n";
+
+// src/assets/svg/stream-settings.svg
+var stream_settings_default = "\n";
+
+// src/assets/svg/stream-stats.svg
+var stream_stats_default = "\n";
+
+// src/assets/svg/trash.svg
+var trash_default = "\n";
+
+// src/assets/svg/touch-control-enable.svg
+var touch_control_enable_default = "\n";
+
+// src/assets/svg/touch-control-disable.svg
+var touch_control_disable_default = "\n";
+
+// src/utils/bx-icon.ts
+var BxIcon = {
+ STREAM_SETTINGS: stream_settings_default,
+ STREAM_STATS: stream_stats_default,
+ CONTROLLER: controller_default,
+ DISPLAY: display_default,
+ MOUSE: mouse_default,
+ MOUSE_SETTINGS: mouse_settings_default,
+ NEW: new_default,
+ COPY: copy_default,
+ TRASH: trash_default,
+ CURSOR_TEXT: cursor_text_default,
+ QUESTION: question_default,
+ REFRESH: refresh_default,
+ REMOTE_PLAY: remote_play_default,
+ CARET_RIGHT: caret_right_default,
+ SCREENSHOT: camera_default,
+ TOUCH_CONTROL_ENABLE: touch_control_enable_default,
+ TOUCH_CONTROL_DISABLE: touch_control_disable_default
+};
+
+// src/modules/game-bar/action-base.ts
+class BaseGameBarAction {
+ constructor() {
+ }
+ reset() {
+ }
+}
+
// src/utils/translation.ts
var SUPPORTED_LANGUAGES = {
"en-ID": "Bahasa Indonesia",
@@ -183,7 +264,7 @@ var Texts = {
"Activate",
"Activo",
,
- ,
+ "Abilita",
"設定する",
"활성화",
"Aktywuj",
@@ -200,7 +281,7 @@ var Texts = {
"Activated",
"Activado",
,
- ,
+ "In uso",
"設定中",
"활성화 됨",
"Aktywowane",
@@ -217,7 +298,7 @@ var Texts = {
"Active",
"Activo",
,
- ,
+ "Attivo",
"有効",
"활성화",
"Aktywny",
@@ -251,7 +332,7 @@ var Texts = {
"Apply",
"Aplicar",
,
- ,
+ "Applica",
"適用",
,
"Zastosuj",
@@ -463,7 +544,7 @@ var Texts = {
"Яркость",
"Aydınlık",
"Яскравість",
- "Độ sáng",
+ "Độ sáng",
"亮度"
],
"browser-unsupported-feature": [
@@ -574,7 +655,7 @@ var Texts = {
"Clear",
"Borrar",
,
- ,
+ "Pulisci",
"消去",
"비우기",
"Wyczyść",
@@ -608,7 +689,7 @@ var Texts = {
"Combine audio & video streams",
"Combinar flujos de audio y vídeo",
,
- ,
+ "Combinare i flussi audio e video",
"音声を映像ストリーミングと統合",
,
"Połącz strumienie audio i wideo",
@@ -625,7 +706,7 @@ var Texts = {
"May fix the laggy audio problem",
"Puede arreglar el problema de audio con retraso",
,
- ,
+ "Potrebbe risolvere il problema dell'audio irregolare",
"音声の遅延を改善できる可能性があります",
,
"Może rozwiązać problem z zacinającym dźwiękiem",
@@ -659,7 +740,7 @@ var Texts = {
"Do you want to delete this preset?",
"¿Desea eliminar este preajuste?",
"Voulez-vous supprimer ce préréglage?",
- ,
+ "Vuoi eliminare questo profilo?",
"このプリセットを削除しますか?",
"이 프리셋을 삭제하시겠습니까?",
"Czy na pewno chcesz usunąć ten szablon?",
@@ -694,7 +775,7 @@ var Texts = {
"Connected",
"Conectado",
,
- ,
+ "Connesso",
"接続済み",
,
"Połączony",
@@ -779,7 +860,7 @@ var Texts = {
"Controller vibration",
"Vibración del mando",
,
- ,
+ "Vibrazione del controller",
"コントローラーの振動",
"컨트롤러 진동",
"Wibracje kontrolera",
@@ -796,7 +877,7 @@ var Texts = {
"Copy",
"Copiar",
,
- ,
+ "Duplica",
"コピー",
"복사",
"Kopiuj",
@@ -830,7 +911,7 @@ var Texts = {
"Deadzone counterweight",
"Contrapeso de la zona muerta",
,
- ,
+ "Compensazione della zona morta",
"デッドゾーンのカウンターウエイト",
,
"Przeciwwaga martwej strefy",
@@ -898,7 +979,7 @@ var Texts = {
"Device vibration",
"Vibración del dispositivo",
,
- ,
+ "Vibrazione del dispositivo",
"デバイスの振動",
"기기 진동",
"Wibracje urządzenia",
@@ -915,7 +996,7 @@ var Texts = {
"On when not using gamepad",
"Activado cuando no se utiliza el mando",
,
- ,
+ "Abilita quando non si usa un gamepad",
"ゲームパッド未使用時にオン",
"게임패드를 사용하지 않을 때",
"Włączone, gdy nie używasz kontrolera",
@@ -1000,7 +1081,7 @@ var Texts = {
"Disabled",
"Desactivado",
,
- ,
+ "Disattivato",
"無効",
"비활성화됨",
"Wyłączony",
@@ -1017,7 +1098,7 @@ var Texts = {
"Disconnected",
"Desconectado",
,
- ,
+ "Disconnesso",
"切断",
,
"Rozłączony",
@@ -1068,7 +1149,7 @@ var Texts = {
"Enable local co-op support",
"Habilitar soporte co-op local",
,
- ,
+ "Abilita supporto cooperativo locale",
"ローカルマルチプレイのサポートを有効化",
,
"Włącz lokalny co-op",
@@ -1085,7 +1166,7 @@ var Texts = {
"Only works if the game doesn't require a different profile",
"Solo funciona si el juego no requiere un perfil diferente",
,
- ,
+ "Funziona quando il gioco non richiede un profilo differente",
"別アカウントでのサインインを必要としないゲームのみ動作します",
,
"Działa tylko wtedy, gdy gra nie wymaga innego profilu",
@@ -1187,7 +1268,7 @@ var Texts = {
"Enabled",
"Activado",
,
- ,
+ "Attivato",
"有効",
"활성화됨",
"Włączony",
@@ -1204,7 +1285,7 @@ var Texts = {
"Experimental",
"Experimental",
,
- ,
+ "Sperimentale",
"実験的機能",
,
"Eksperymentalne",
@@ -1221,7 +1302,7 @@ var Texts = {
"Export",
"Exportar",
,
- ,
+ "Esporta",
"エクスポート(書出し)",
"내보내기",
"Eksportuj",
@@ -1255,7 +1336,7 @@ var Texts = {
"Allows playing STW mode on mobile",
"Permitir jugar al modo STW en el móvil",
,
- ,
+ "Consente di riprodurre la modalità Salva il Mondo sul cellulare",
"モバイル版で「世界を救え」をプレイできるようになります",
,
"Zezwól na granie w tryb STW na urządzeniu mobilnym",
@@ -1272,7 +1353,7 @@ var Texts = {
"Fortnite: force console version",
"Fortnite: forzar versión de consola",
,
- "Fortnite: Foza la versione console",
+ "Fortnite: forza la versione console",
"Fortnite: 強制的にコンソール版を起動する",
,
"Fortnite: wymuś wersję konsolową",
@@ -1306,7 +1387,7 @@ var Texts = {
"Help",
"Ayuda",
,
- ,
+ "Guida",
"ヘルプ",
,
"Pomoc",
@@ -1340,7 +1421,7 @@ var Texts = {
"Hide web page's scrollbar",
"Oculta la barra de desplazamiento de la página",
,
- ,
+ "Nascondi la barra di scorrimento della pagina web",
"Webページのスクロールバーを隠す",
,
"Ukryj pasek przewijania strony",
@@ -1368,13 +1449,30 @@ var Texts = {
"Ẩn biểu tượng của menu Hệ thống",
"隐藏系统菜单图标"
],
+ "hide-touch-controller": [
+ "Touch-Controller ausblenden",
+ ,
+ "Hide touch controller",
+ "Ocultar controles táctiles",
+ ,
+ ,
+ "タッチコントローラーを隠す",
+ ,
+ ,
+ ,
+ "Скрыть сенсорный контроллер",
+ ,
+ "Приховати сенсорний контролер",
+ "Ẩn bộ điều khiển cảm ứng",
+ ,
+ ],
"horizontal-sensitivity": [
"Horizontale Empfindlichkeit",
"Sensitifitas horizontal",
"Horizontal sensitivity",
"Sensibilidad horizontal",
,
- ,
+ "Sensibilità orizzontale",
"左右方向の感度",
,
"Czułość pozioma",
@@ -1391,7 +1489,7 @@ var Texts = {
"Import",
"Importar",
,
- ,
+ "Importa",
"インポート(読込み)",
"가져오기",
"Importuj",
@@ -1408,7 +1506,7 @@ var Texts = {
"Install Better xCloud app for Android",
"Instale la aplicación Better xCloud para Android",
,
- ,
+ "Installa l'applicazione Better xCloud per Android",
"Android用のBetter xCloudをインストール",
,
"Zainstaluj aplikację Better xCloud na Androida",
@@ -1425,7 +1523,7 @@ var Texts = {
"Keyboard shortcuts",
"Atajos del teclado",
,
- ,
+ "Scorciatoie da tastiera",
"キーボードショートカット",
,
"Skróty klawiszowe",
@@ -1493,7 +1591,7 @@ var Texts = {
"Left stick",
"Joystick izquierdo",
,
- ,
+ "Levetta sinistra",
"左スティック",
"왼쪽 스틱",
"Lewy drążek analogowy",
@@ -1527,7 +1625,7 @@ var Texts = {
"Local co-op",
"Co-op local",
,
- ,
+ "Cooperativa locale",
"ローカルマルチプレイ",
,
"Lokalna kooperacja",
@@ -1544,7 +1642,7 @@ var Texts = {
"Map mouse to",
"Mapear ratón a",
,
- ,
+ "Usa il mouse come",
"マウスの割り当て",
,
"Przypisz myszkę do",
@@ -1714,7 +1812,7 @@ var Texts = {
"Name",
"Nombre",
,
- ,
+ "Nome",
"名前",
"이름",
"Nazwa",
@@ -1731,7 +1829,7 @@ var Texts = {
"New",
"Nuevo",
,
- ,
+ "Nuovo",
"新しい",
"새로 만들기",
"Nowy",
@@ -1969,7 +2067,7 @@ var Texts = {
"Preset",
"Preajuste",
,
- ,
+ "Profilo",
"プリセット",
"프리셋",
"Szablon",
@@ -1986,7 +2084,7 @@ var Texts = {
"Press Esc to cancel",
"Presione Esc para cancelar",
,
- ,
+ "Premi Esc per annullare",
"Escを押してキャンセル",
"ESC를 눌러 취소",
"Naciśnij Esc, aby anulować",
@@ -2003,7 +2101,7 @@ var Texts = {
(e) => `Press ${e.key} to toggle the Mouse and Keyboard feature`,
(e) => `Pulsa ${e.key} para activar la función de ratón y teclado`,
,
- ,
+ (e) => `Premi ${e.key} per attivare o disattivare la funzione Mouse e Tastiera`,
(e) => `${e.key} キーでマウスとキーボードの機能を切り替える`,
(e) => `${e.key} 키를 눌러 마우스와 키보드 기능을 활성화 하십시오`,
(e) => `Naciśnij ${e.key}, aby przełączyć funkcję myszy i klawiatury`,
@@ -2020,7 +2118,7 @@ var Texts = {
"Press a key or do a mouse click to bind...",
"Presione una tecla o haga un clic del ratón para enlazar...",
,
- ,
+ "Premi un tasto o fai un clic del mouse per associare...",
"キーを押すかマウスをクリックして割り当て...",
"정지하려면 아무키나 마우스를 클릭해주세요...",
"Naciśnij klawisz lub kliknij myszą, aby przypisać...",
@@ -2037,7 +2135,7 @@ var Texts = {
"Preset's name:",
"Nombre del preajuste:",
,
- ,
+ "Nome del profilo:",
"プリセット名:",
"프리셋 이름:",
"Nazwa szablonu:",
@@ -2122,7 +2220,7 @@ var Texts = {
"Rename",
"Renombrar",
,
- ,
+ "Rinomina",
"名前変更",
"이름 바꾸기",
"Zmień nazwę",
@@ -2139,7 +2237,7 @@ var Texts = {
"Right-click on a key to unbind it",
"Clic derecho en una tecla para desvincularla",
,
- ,
+ "Clic col tasto destro su una assegnazione per dissociarla",
"右クリックで割り当て解除",
"할당 해제하려면 키를 오른쪽 클릭하세요",
"Kliknij prawym przyciskiem myszy na klawisz, aby anulować przypisanie",
@@ -2156,7 +2254,7 @@ var Texts = {
"Right stick",
"Joystick derecho",
,
- ,
+ "Levetta destra",
"右スティック",
"오른쪽 스틱",
"Prawy drążek analogowy",
@@ -2275,7 +2373,7 @@ var Texts = {
"Save",
"Guardar",
,
- ,
+ "Conferma",
"保存",
"저장",
"Zapisz",
@@ -2292,7 +2390,7 @@ var Texts = {
"Applies video filters to screenshots",
"Aplica filtros de vídeo a las capturas de pantalla",
,
- ,
+ "Applica filtri video agli screenshot",
"スクリーンショットにビデオフィルターを適用",
,
"Stosuje filtry wideo do zrzutów ekranu",
@@ -2326,7 +2424,7 @@ var Texts = {
"Separate Touch controller & Controller #1",
"Separar controlador táctil y controlador #1",
,
- ,
+ "Controller su schermo e Controller #1 separati",
"タッチコントローラーとコントローラー#1を分ける",
,
"Oddziel Kontroler dotykowy i Kontroler #1",
@@ -2343,7 +2441,7 @@ var Texts = {
"Touch controller is Player 1, Controller #1 is Player 2",
"El controlador táctil es Jugador 1, Controlador #1 es Jugador 2",
,
- ,
+ "Il Giocatore 1 userà il Controller su schermo, il Giocatore 2 userà il Controller #1",
"タッチコントローラーがプレイヤー1、コントローラー#1がプレイヤー2に割り当てられます",
,
"Kontroler dotykowy to Gracz 1, Kontroler #1 to Gracz 2",
@@ -2411,7 +2509,7 @@ var Texts = {
"Shortcut keys",
"Teclas de atajo",
,
- ,
+ "Tasti di scelta rapida",
"ショートカットキー",
,
"Skróty klawiszowe",
@@ -2456,6 +2554,23 @@ var Texts = {
"Hiển thị thông số khi vào game",
"开始游戏时显示统计信息"
],
+ "show-touch-controller": [
+ "Touch-Controller anzeigen",
+ ,
+ "Show touch controller",
+ "Mostrar controles táctiles",
+ ,
+ ,
+ "タッチコントローラーを表示",
+ ,
+ ,
+ ,
+ "Показать сенсорный контроллер",
+ ,
+ "Показати сенсорний контролер",
+ "Hiện bộ điều khiển cảm ứng",
+ ,
+ ],
"show-wait-time": [
"Geschätzte Wartezeit anzeigen",
"Tampilkan waktu antrian",
@@ -2717,7 +2832,7 @@ var Texts = {
"Stick decay minimum",
"Disminuir mínimamente el analógico",
,
- ,
+ "Tempo minimo di rilascio dello stick",
"スティックの減衰の最小値",
,
"Minimalne opóźnienie drążka",
@@ -2734,7 +2849,7 @@ var Texts = {
"Stick decay strength",
"Intensidad de decaimiento del analógico",
,
- ,
+ "Velocità di rilascio dello stick",
"スティックの減衰の強さ",
,
"Siła opóźnienia drążka",
@@ -2785,7 +2900,7 @@ var Texts = {
"Support Better xCloud",
"Apoyar a Better xCloud",
,
- ,
+ "Sostieni Better xCloud",
"Better xCloudをサポート",
,
"Wesprzyj Better xCloud",
@@ -2813,6 +2928,23 @@ var Texts = {
"Hoán đổi nút",
"交换按钮"
],
+ "take-screenshot": [
+ "Screenshot aufnehmen",
+ ,
+ "Take screenshot",
+ "Capturar pantalla",
+ ,
+ ,
+ "スクリーンショットを撮影",
+ ,
+ ,
+ ,
+ "Сделать снимок экрана",
+ ,
+ "Зробити знімок екрану",
+ "Lưu ảnh màn hình",
+ ,
+ ],
"target-resolution": [
"Festgelegte Auflösung",
"Resolusi",
@@ -2870,7 +3002,7 @@ var Texts = {
"Off when controller found",
"Desactivar cuando se encuentra el controlador",
,
- ,
+ "Disabilitata quando un controllor viene rilevato",
"コントローラー接続時に無効化",
,
"Wyłącz, gdy kontroler zostanie znaleziony",
@@ -2917,11 +3049,11 @@ var Texts = {
],
"tc-default-opacity": [
"Standard Deckkraft",
- ,
+ "Opasitas bawaan",
"Default opacity",
"Opacidad por defecto",
,
- ,
+ "Opacità predefinita",
"既定の透過度",
,
"Domyślna przezroczystość",
@@ -3055,14 +3187,14 @@ var Texts = {
(e) => `Touch-Steuerungslayout von ${e.name}`,
,
(e) => `Touch control layout by ${e.name}`,
+ (e) => `Disposición del control táctil por ${e.nombre}`,
,
- ,
- ,
+ (e) => `Configurazione dei comandi su schermo creata da ${e.name}`,
(e) => `タッチ操作レイアウト作成者: ${e.name}`,
,
(e) => `Układ sterowania dotykowego stworzony przez ${e.name}`,
,
- ,
+ (e) => `Сенсорная раскладка по ${e.name}`,
(e) => `${e.name} kişisinin dokunmatik kontrolcü tuş şeması`,
(e) => `Розташування сенсорного керування від ${e.name}`,
(e) => `Bố cục điều khiển cảm ứng tạo bởi ${e.name}`,
@@ -3176,7 +3308,7 @@ var Texts = {
"Use mouse's absolute position",
"Usar la posición absoluta del ratón",
,
- ,
+ "Usa la posizione assoluta del mouse",
"マウスの絶対座標を使用",
"마우스 절대위치 사용",
"Użyj pozycji bezwzględnej myszy",
@@ -3227,7 +3359,7 @@ var Texts = {
"Vibration intensity",
"Intensidad de la vibración",
,
- ,
+ "Intensità della vibrazione",
"振動の強さ",
"진동 세기",
"Siła wibracji",
@@ -3244,7 +3376,7 @@ var Texts = {
"Vibration",
"Vibración",
,
- ,
+ "Vibrazione",
"振動",
,
"Wibracje",
@@ -3420,6 +3552,125 @@ var t = Translations.get;
var refreshCurrentLocale = Translations.refreshCurrentLocale;
refreshCurrentLocale();
+// src/modules/game-bar/action-screenshot.ts
+class ScreenshotAction extends BaseGameBarAction {
+ $content;
+ constructor() {
+ super();
+ const currentStream = STATES.currentStream;
+ currentStream.$screenshotCanvas = CE("canvas", { class: "bx-gone" });
+ document.documentElement.appendChild(currentStream.$screenshotCanvas);
+ const onClick = (e) => {
+ BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
+ this.takeScreenshot();
+ };
+ this.$content = createButton({
+ style: ButtonStyle.GHOST,
+ icon: BxIcon.SCREENSHOT,
+ title: t("take-screenshot"),
+ onClick
+ });
+ }
+ render() {
+ return this.$content;
+ }
+ takeScreenshot(callback) {
+ const currentStream = STATES.currentStream;
+ const $video = currentStream.$video;
+ const $canvas = currentStream.$screenshotCanvas;
+ if (!$video || !$canvas) {
+ return;
+ }
+ const $canvasContext = $canvas.getContext("2d", {
+ alpha: false,
+ willReadFrequently: false
+ });
+ $canvasContext.drawImage($video, 0, 0, $canvas.width, $canvas.height);
+ if (AppInterface) {
+ const data = $canvas.toDataURL("image/png").split(";base64,")[1];
+ AppInterface.saveScreenshot(currentStream.titleId, data);
+ $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
+ callback && callback();
+ return;
+ }
+ $canvas && $canvas.toBlob((blob) => {
+ const now = +new Date;
+ const $anchor = CE("a", {
+ download: `${currentStream.titleId}-${now}.png`,
+ href: URL.createObjectURL(blob)
+ });
+ $anchor.click();
+ URL.revokeObjectURL($anchor.href);
+ $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
+ callback && callback();
+ }, "image/png");
+ }
+}
+
+// src/utils/toast.ts
+class Toast {
+ static #$wrapper;
+ static #$msg;
+ static #$status;
+ static #stack = [];
+ static #isShowing = false;
+ static #timeout;
+ static #DURATION = 3000;
+ static show(msg, status, options = {}) {
+ options = options || {};
+ const args = Array.from(arguments);
+ if (options.instant) {
+ Toast.#stack = [args];
+ Toast.#showNext();
+ } else {
+ Toast.#stack.push(args);
+ !Toast.#isShowing && Toast.#showNext();
+ }
+ }
+ static #showNext() {
+ if (!Toast.#stack.length) {
+ Toast.#isShowing = false;
+ return;
+ }
+ Toast.#isShowing = true;
+ Toast.#timeout && clearTimeout(Toast.#timeout);
+ Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
+ const [msg, status, options] = Toast.#stack.shift();
+ if (options.html) {
+ Toast.#$msg.innerHTML = msg;
+ } else {
+ Toast.#$msg.textContent = msg;
+ }
+ if (status) {
+ Toast.#$status.classList.remove("bx-gone");
+ Toast.#$status.textContent = status;
+ } else {
+ Toast.#$status.classList.add("bx-gone");
+ }
+ const classList = Toast.#$wrapper.classList;
+ classList.remove("bx-offscreen", "bx-hide");
+ classList.add("bx-show");
+ }
+ static #hide() {
+ Toast.#timeout = null;
+ const classList = Toast.#$wrapper.classList;
+ classList.remove("bx-show");
+ classList.add("bx-hide");
+ }
+ static setup() {
+ Toast.#$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.#$msg = CE("span", { class: "bx-toast-msg" }), Toast.#$status = CE("span", { class: "bx-toast-status" }));
+ Toast.#$wrapper.addEventListener("transitionend", (e) => {
+ const classList = Toast.#$wrapper.classList;
+ if (classList.contains("bx-hide")) {
+ classList.remove("bx-offscreen", "bx-hide");
+ classList.add("bx-offscreen");
+ Toast.#showNext();
+ }
+ });
+ document.documentElement.appendChild(Toast.#$wrapper);
+ }
+}
+
// src/utils/settings.ts
var SettingElementType;
(function(SettingElementType2) {
@@ -4175,7 +4426,6 @@ var PrefKey;
PrefKey2["MKB_HIDE_IDLE_CURSOR"] = "mkb_hide_idle_cursor";
PrefKey2["MKB_ABSOLUTE_MOUSE"] = "mkb_absolute_mouse";
PrefKey2["MKB_DEFAULT_PRESET_ID"] = "mkb_default_preset_id";
- PrefKey2["SCREENSHOT_BUTTON_POSITION"] = "screenshot_button_position";
PrefKey2["SCREENSHOT_APPLY_FILTERS"] = "screenshot_apply_filters";
PrefKey2["BLOCK_TRACKING"] = "block_tracking";
PrefKey2["BLOCK_SOCIAL_FEATURES"] = "block_social_features";
@@ -4336,15 +4586,6 @@ class Preferences {
label: t("prefer-ipv6-server"),
default: false
},
- [PrefKey.SCREENSHOT_BUTTON_POSITION]: {
- label: t("screenshot-button-position"),
- default: "bottom-left",
- options: {
- "bottom-left": t("bottom-left"),
- "bottom-right": t("bottom-right"),
- none: t("disable")
- }
- },
[PrefKey.SCREENSHOT_APPLY_FILTERS]: {
label: t("screenshot-apply-filters"),
default: false
@@ -4810,86 +5051,6 @@ var getPref = prefs.get.bind(prefs);
var setPref = prefs.set.bind(prefs);
var toPrefElement = prefs.toElement.bind(prefs);
-// src/utils/bx-exposed.ts
-var InputType;
-(function(InputType2) {
- InputType2["CONTROLLER"] = "Controller";
- InputType2["MKB"] = "MKB";
- InputType2["CUSTOM_TOUCH_OVERLAY"] = "CustomTouchOverlay";
- InputType2["GENERIC_TOUCH"] = "GenericTouch";
- InputType2["NATIVE_TOUCH"] = "NativeTouch";
- InputType2["BATIVE_SENSOR"] = "NativeSensor";
-})(InputType || (InputType = {}));
-var BxExposed = {
- onPollingModeChanged: (mode) => {
- if (!STATES.isPlaying) {
- return false;
- }
- const $screenshotBtn = document.querySelector(".bx-screenshot-button");
- const $touchControllerBar = document.getElementById("bx-touch-controller-bar");
- if (mode !== "None") {
- $screenshotBtn && $screenshotBtn.classList.add("bx-gone");
- $touchControllerBar && $touchControllerBar.classList.add("bx-gone");
- } else {
- $screenshotBtn && $screenshotBtn.classList.remove("bx-gone");
- $touchControllerBar && $touchControllerBar.classList.remove("bx-gone");
- }
- },
- getTitleInfo: () => STATES.currentStream.titleInfo,
- modifyTitleInfo: (titleInfo) => {
- titleInfo = structuredClone(titleInfo);
- if (STATES.hasTouchSupport) {
- let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
- let supportedInputTypes = titleInfo.details.supportedInputTypes;
- if (touchControllerAvailability !== "off" && getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) {
- const gamepads = window.navigator.getGamepads();
- let gamepadFound = false;
- for (let gamepad of gamepads) {
- if (gamepad && gamepad.connected) {
- gamepadFound = true;
- break;
- }
- }
- gamepadFound && (touchControllerAvailability = "off");
- }
- if (UserAgent.isMobile()) {
- supportedInputTypes = supportedInputTypes.filter((i) => i !== InputType.MKB);
- }
- if (touchControllerAvailability === "off") {
- supportedInputTypes = supportedInputTypes.filter((i) => i !== InputType.CUSTOM_TOUCH_OVERLAY && i !== InputType.GENERIC_TOUCH);
- }
- titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
- titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) || supportedInputTypes.includes(InputType.GENERIC_TOUCH);
- if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === "all") {
- titleInfo.details.hasFakeTouchSupport = true;
- supportedInputTypes.push(InputType.GENERIC_TOUCH);
- }
- titleInfo.details.supportedInputTypes = supportedInputTypes;
- }
- STATES.currentStream.titleInfo = titleInfo;
- BxEvent.dispatch(window, BxEvent.TITLE_INFO_READY);
- return titleInfo;
- },
- setupGainNode: ($media, audioStream) => {
- if ($media instanceof HTMLAudioElement) {
- $media.muted = true;
- $media.addEventListener("playing", (e) => {
- $media.muted = true;
- $media.pause();
- });
- } else {
- $media.muted = true;
- $media.addEventListener("playing", (e) => {
- $media.muted = true;
- });
- }
- const audioCtx = STATES.currentStream.audioContext;
- const source = audioCtx.createMediaStreamSource(audioStream);
- const gainNode = audioCtx.createGain();
- source.connect(gainNode).connect(audioCtx.destination);
- }
-};
-
// src/utils/region.ts
function getPreferredServerRegion(shortName = false) {
let preferredRegion = getPref(PrefKey.SERVER_REGION);
@@ -5043,126 +5204,6 @@ class LoadingScreen {
}
}
-// src/assets/svg/controller.svg
-var controller_default = "\n";
-
-// src/assets/svg/copy.svg
-var copy_default = "\n";
-
-// src/assets/svg/cursor-text.svg
-var cursor_text_default = "\n";
-
-// src/assets/svg/display.svg
-var display_default = "\n";
-
-// src/assets/svg/mouse-settings.svg
-var mouse_settings_default = "\n";
-
-// src/assets/svg/mouse.svg
-var mouse_default = "\n";
-
-// src/assets/svg/new.svg
-var new_default = "\n";
-
-// src/assets/svg/question.svg
-var question_default = "\n";
-
-// src/assets/svg/refresh.svg
-var refresh_default = "\n";
-
-// src/assets/svg/remote-play.svg
-var remote_play_default = "\n";
-
-// src/assets/svg/stream-settings.svg
-var stream_settings_default = "\n";
-
-// src/assets/svg/stream-stats.svg
-var stream_stats_default = "\n";
-
-// src/assets/svg/trash.svg
-var trash_default = "\n";
-
-// src/utils/bx-icon.ts
-var BxIcon = {
- STREAM_SETTINGS: stream_settings_default,
- STREAM_STATS: stream_stats_default,
- CONTROLLER: controller_default,
- DISPLAY: display_default,
- MOUSE: mouse_default,
- MOUSE_SETTINGS: mouse_settings_default,
- NEW: new_default,
- COPY: copy_default,
- TRASH: trash_default,
- CURSOR_TEXT: cursor_text_default,
- QUESTION: question_default,
- REFRESH: refresh_default,
- REMOTE_PLAY: remote_play_default
-};
-
-// src/utils/toast.ts
-class Toast {
- static #$wrapper;
- static #$msg;
- static #$status;
- static #stack = [];
- static #isShowing = false;
- static #timeout;
- static #DURATION = 3000;
- static show(msg, status, options = {}) {
- options = options || {};
- const args = Array.from(arguments);
- if (options.instant) {
- Toast.#stack = [args];
- Toast.#showNext();
- } else {
- Toast.#stack.push(args);
- !Toast.#isShowing && Toast.#showNext();
- }
- }
- static #showNext() {
- if (!Toast.#stack.length) {
- Toast.#isShowing = false;
- return;
- }
- Toast.#isShowing = true;
- Toast.#timeout && clearTimeout(Toast.#timeout);
- Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
- const [msg, status, options] = Toast.#stack.shift();
- if (options.html) {
- Toast.#$msg.innerHTML = msg;
- } else {
- Toast.#$msg.textContent = msg;
- }
- if (status) {
- Toast.#$status.classList.remove("bx-gone");
- Toast.#$status.textContent = status;
- } else {
- Toast.#$status.classList.add("bx-gone");
- }
- const classList = Toast.#$wrapper.classList;
- classList.remove("bx-offscreen", "bx-hide");
- classList.add("bx-show");
- }
- static #hide() {
- Toast.#timeout = null;
- const classList = Toast.#$wrapper.classList;
- classList.remove("bx-show");
- classList.add("bx-hide");
- }
- static setup() {
- Toast.#$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.#$msg = CE("span", { class: "bx-toast-msg" }), Toast.#$status = CE("span", { class: "bx-toast-status" }));
- Toast.#$wrapper.addEventListener("transitionend", (e) => {
- const classList = Toast.#$wrapper.classList;
- if (classList.contains("bx-hide")) {
- classList.remove("bx-offscreen", "bx-hide");
- classList.add("bx-offscreen");
- Toast.#showNext();
- }
- });
- document.documentElement.appendChild(Toast.#$wrapper);
- }
-}
-
// src/modules/mkb/definitions.ts
var GamepadKey;
(function(GamepadKey2) {
@@ -6505,323 +6546,6 @@ class MkbRemapper {
}
}
-// src/modules/screenshot.ts
-function takeScreenshot(callback) {
- const currentStream = STATES.currentStream;
- const $video = currentStream.$video;
- const $canvas = currentStream.$screenshotCanvas;
- if (!$video || !$canvas) {
- return;
- }
- const $canvasContext = $canvas.getContext("2d", {
- alpha: false,
- willReadFrequently: false
- });
- $canvasContext.drawImage($video, 0, 0, $canvas.width, $canvas.height);
- if (AppInterface) {
- const data = $canvas.toDataURL("image/png").split(";base64,")[1];
- AppInterface.saveScreenshot(currentStream.titleId, data);
- $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
- callback && callback();
- return;
- }
- $canvas && $canvas.toBlob((blob) => {
- const now = +new Date;
- const $anchor = CE("a", {
- download: `${currentStream.titleId}-${now}.png`,
- href: URL.createObjectURL(blob)
- });
- $anchor.click();
- URL.revokeObjectURL($anchor.href);
- $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
- callback && callback();
- }, "image/png");
-}
-function setupScreenshotButton() {
- const currentStream = STATES.currentStream;
- currentStream.$screenshotCanvas = CE("canvas", { class: "bx-screenshot-canvas" });
- document.documentElement.appendChild(currentStream.$screenshotCanvas);
- const delay = 2000;
- const $btn = CE("div", { class: "bx-screenshot-button", "data-showing": false });
- let timeout;
- const detectDbClick = (e) => {
- if (!currentStream.$video) {
- timeout = null;
- $btn.style.display = "none";
- return;
- }
- if (timeout) {
- clearTimeout(timeout);
- timeout = null;
- $btn.setAttribute("data-capturing", "true");
- takeScreenshot(() => {
- $btn.setAttribute("data-showing", "false");
- window.setTimeout(() => {
- if (!timeout) {
- $btn.setAttribute("data-capturing", "false");
- }
- }, 100);
- });
- return;
- }
- const isShowing = $btn.getAttribute("data-showing") === "true";
- if (!isShowing) {
- $btn.setAttribute("data-showing", "true");
- $btn.setAttribute("data-capturing", "false");
- timeout && clearTimeout(timeout);
- timeout = window.setTimeout(() => {
- timeout = null;
- $btn.setAttribute("data-showing", "false");
- $btn.setAttribute("data-capturing", "false");
- }, delay);
- }
- };
- $btn.addEventListener("mousedown", detectDbClick);
- document.documentElement.appendChild($btn);
-}
-
-// src/modules/touch-controller.ts
-var LOG_TAG2 = "TouchController";
-
-class TouchController {
- static #EVENT_SHOW_DEFAULT_CONTROLLER = new MessageEvent("message", {
- data: '{"content":"{\\"layoutId\\":\\"\\"}","target":"/streaming/touchcontrols/showlayoutv2","type":"Message"}',
- origin: "better-xcloud"
- });
- static #$bar;
- static #$style;
- static #enable = false;
- static #showing = false;
- static #dataChannel;
- static #customLayouts = {};
- static #baseCustomLayouts = {};
- static #currentLayoutId;
- static enable() {
- TouchController.#enable = true;
- }
- static disable() {
- TouchController.#enable = false;
- }
- static isEnabled() {
- return TouchController.#enable;
- }
- static #showDefault() {
- TouchController.#dispatchMessage(TouchController.#EVENT_SHOW_DEFAULT_CONTROLLER);
- TouchController.#showing = true;
- }
- static #show() {
- document.querySelector("#BabylonCanvasContainer-main")?.parentElement?.classList.remove("bx-offscreen");
- TouchController.#showing = true;
- }
- static #hide() {
- document.querySelector("#BabylonCanvasContainer-main")?.parentElement?.classList.add("bx-offscreen");
- TouchController.#showing = false;
- }
- static #toggleVisibility() {
- if (!TouchController.#dataChannel) {
- return;
- }
- TouchController.#showing ? TouchController.#hide() : TouchController.#show();
- }
- static #toggleBar(value) {
- TouchController.#$bar && TouchController.#$bar.setAttribute("data-showing", value.toString());
- }
- static reset() {
- TouchController.#enable = false;
- TouchController.#showing = false;
- TouchController.#dataChannel = null;
- TouchController.#$bar && TouchController.#$bar.removeAttribute("data-showing");
- TouchController.#$style && (TouchController.#$style.textContent = "");
- }
- static #dispatchMessage(msg) {
- TouchController.#dataChannel && window.setTimeout(() => {
- TouchController.#dataChannel.dispatchEvent(msg);
- }, 10);
- }
- static #dispatchLayouts(data) {
- BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
- data
- });
- }
- static async getCustomLayouts(xboxTitleId, retries = 1) {
- if (xboxTitleId in TouchController.#customLayouts) {
- TouchController.#dispatchLayouts(TouchController.#customLayouts[xboxTitleId]);
- return;
- }
- retries = retries || 1;
- if (retries > 2) {
- TouchController.#customLayouts[xboxTitleId] = null;
- window.setTimeout(() => TouchController.#dispatchLayouts(null), 1000);
- return;
- }
- const baseUrl = `https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts${BX_FLAGS.UseDevTouchLayout ? "/dev" : ""}`;
- const url = `${baseUrl}/${xboxTitleId}.json`;
- try {
- const resp = await NATIVE_FETCH(url);
- const json = await resp.json();
- const layouts = {};
- json.layouts.forEach(async (layoutName) => {
- let baseLayouts = {};
- if (layoutName in TouchController.#baseCustomLayouts) {
- baseLayouts = TouchController.#baseCustomLayouts[layoutName];
- } else {
- try {
- const layoutUrl = `${baseUrl}/layouts/${layoutName}.json`;
- const resp2 = await NATIVE_FETCH(layoutUrl);
- const json2 = await resp2.json();
- baseLayouts = json2.layouts;
- TouchController.#baseCustomLayouts[layoutName] = baseLayouts;
- } catch (e) {
- }
- }
- Object.assign(layouts, baseLayouts);
- });
- json.layouts = layouts;
- TouchController.#customLayouts[xboxTitleId] = json;
- window.setTimeout(() => TouchController.#dispatchLayouts(json), 1000);
- } catch (e) {
- TouchController.getCustomLayouts(xboxTitleId, retries + 1);
- }
- }
- static loadCustomLayout(xboxTitleId, layoutId, delay = 0) {
- if (!window.BX_EXPOSED.touchLayoutManager) {
- return;
- }
- const layoutChanged = TouchController.#currentLayoutId !== layoutId;
- TouchController.#currentLayoutId = layoutId;
- const layoutData = TouchController.#customLayouts[xboxTitleId];
- if (!xboxTitleId || !layoutId || !layoutData) {
- TouchController.#enable && TouchController.#showDefault();
- return;
- }
- const layout = layoutData.layouts[layoutId] || layoutData.layouts[layoutData.default_layout];
- if (!layout) {
- return;
- }
- let msg;
- let html13 = false;
- if (layout.author) {
- const author = `${escapeHtml(layout.author)}`;
- msg = t("touch-control-layout-by", { name: author });
- html13 = true;
- } else {
- msg = t("touch-control-layout");
- }
- layoutChanged && Toast.show(msg, layout.name, { html: html13 });
- window.setTimeout(() => {
- window.BX_EXPOSED.touchLayoutManager.changeLayoutForScope({
- type: "showLayout",
- scope: xboxTitleId,
- subscope: "base",
- layout: {
- id: "System.Standard",
- displayName: "System",
- layoutFile: layout
- }
- });
- }, delay);
- }
- static updateCustomList() {
- NATIVE_FETCH("https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts/ids.json").then((response) => response.json()).then((json) => {
- window.localStorage.setItem("better_xcloud_custom_touch_layouts", JSON.stringify(json));
- });
- }
- static getCustomList() {
- return JSON.parse(window.localStorage.getItem("better_xcloud_custom_touch_layouts") || "[]");
- }
- static setup() {
- window.testTouchLayout = (layout) => {
- const { touchLayoutManager } = window.BX_EXPOSED;
- touchLayoutManager && touchLayoutManager.changeLayoutForScope({
- type: "showLayout",
- scope: "" + STATES.currentStream?.xboxTitleId,
- subscope: "base",
- layout: {
- id: "System.Standard",
- displayName: "Custom",
- layoutFile: layout
- }
- });
- };
- const $fragment = document.createDocumentFragment();
- const $style = document.createElement("style");
- $fragment.appendChild($style);
- const $bar = CE("div", { id: "bx-touch-controller-bar" });
- $fragment.appendChild($bar);
- document.documentElement.appendChild($fragment);
- let clickTimeout;
- $bar.addEventListener("mousedown", (e) => {
- clickTimeout && clearTimeout(clickTimeout);
- if (clickTimeout) {
- clickTimeout = null;
- TouchController.#toggleVisibility();
- return;
- }
- clickTimeout = window.setTimeout(() => {
- clickTimeout = null;
- }, 400);
- });
- TouchController.#$bar = $bar;
- TouchController.#$style = $style;
- const PREF_STYLE_STANDARD = getPref(PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD);
- const PREF_STYLE_CUSTOM = getPref(PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM);
- window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => {
- const dataChannel = e.dataChannel;
- if (!dataChannel || dataChannel.label !== "message") {
- return;
- }
- let filter = "";
- if (TouchController.#enable) {
- if (PREF_STYLE_STANDARD === "white") {
- filter = "grayscale(1) brightness(2)";
- } else if (PREF_STYLE_STANDARD === "muted") {
- filter = "sepia(0.5)";
- }
- } else if (PREF_STYLE_CUSTOM === "muted") {
- filter = "sepia(0.5)";
- }
- if (filter) {
- $style.textContent = `#babylon-canvas { filter: ${filter} !important; }`;
- } else {
- $style.textContent = "";
- }
- TouchController.#dataChannel = dataChannel;
- dataChannel.addEventListener("open", () => {
- window.setTimeout(TouchController.#show, 1000);
- });
- let focused = false;
- dataChannel.addEventListener("message", (msg) => {
- if (msg.origin === "better-xcloud" || typeof msg.data !== "string") {
- return;
- }
- if (msg.data.includes("touchcontrols/showtitledefault")) {
- if (TouchController.#enable) {
- if (focused) {
- TouchController.getCustomLayouts(STATES.currentStream?.xboxTitleId);
- } else {
- TouchController.#showDefault();
- }
- }
- return;
- }
- try {
- if (msg.data.includes("/titleinfo")) {
- const json = JSON.parse(JSON.parse(msg.data).content);
- TouchController.#toggleBar(json.focused);
- focused = json.focused;
- if (!json.focused) {
- TouchController.#show();
- }
- STATES.currentStream.xboxTitleId = parseInt(json.titleid, 16).toString();
- }
- } catch (e2) {
- BxLogger.error(LOG_TAG2, "Load custom layout", e2);
- }
- });
- });
- }
-}
-
// src/modules/vibration-manager.ts
var VIBRATION_DATA_MAP = {
gamepadIndex: 8,
@@ -7303,18 +7027,18 @@ div[data-testid="media-container"] {
}
$elm.textContent = css;
}
-function setupBxUi() {
+function setupStreamUi() {
if (!document.querySelector(".bx-quick-settings-bar")) {
window.addEventListener("resize", updateVideoPlayerCss);
setupQuickSettingsBar();
- setupScreenshotButton();
StreamStats.render();
+ GameBar.setup();
}
updateVideoPlayerCss();
}
// src/modules/remote-play.ts
-var LOG_TAG3 = "RemotePlay";
+var LOG_TAG2 = "RemotePlay";
var RemotePlayConsoleState;
(function(RemotePlayConsoleState2) {
RemotePlayConsoleState2["ON"] = "On";
@@ -7380,7 +7104,7 @@ class RemotePlay {
RemotePlay.#$content = CE("div", {}, t("getting-consoles-list"));
RemotePlay.#getXhomeToken(() => {
RemotePlay.#getConsolesList(() => {
- BxLogger.info(LOG_TAG3, "Consoles", RemotePlay.#CONSOLES);
+ BxLogger.info(LOG_TAG2, "Consoles", RemotePlay.#CONSOLES);
RemotePlay.#renderConsoles();
BxEvent.dispatch(window, BxEvent.REMOTE_PLAY_READY);
});
@@ -7733,6 +7457,7 @@ function interceptHttpRequests() {
const newCustomList = customList.map((item2) => ({ id: item2 }));
obj.push(...newCustomList);
} catch (e) {
+ console.log(e);
}
}
response.json = () => Promise.resolve(obj);
@@ -7990,6 +7715,8 @@ class XcloudInterceptor {
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || "{}") || {};
overrides.inputConfiguration = overrides.inputConfiguration || {};
overrides.inputConfiguration.enableVibration = true;
+ overrides.videoConfiguration = overrides.videoConfiguration || {};
+ overrides.videoConfiguration.setCodecPreferences = true;
if (TouchController.isEnabled()) {
overrides.inputConfiguration.enableTouchInput = true;
overrides.inputConfiguration.maxTouchPoints = 10;
@@ -8020,6 +7747,415 @@ class XcloudInterceptor {
}
}
+// src/modules/touch-controller.ts
+var LOG_TAG3 = "TouchController";
+
+class TouchController {
+ static #EVENT_SHOW_DEFAULT_CONTROLLER = new MessageEvent("message", {
+ data: JSON.stringify({
+ content: '{"layoutId":""}',
+ target: "/streaming/touchcontrols/showlayoutv2",
+ type: "Message"
+ }),
+ origin: "better-xcloud"
+ });
+ static #$style;
+ static #enable = false;
+ static #dataChannel;
+ static #customLayouts = {};
+ static #baseCustomLayouts = {};
+ static #currentLayoutId;
+ static #customList;
+ static enable() {
+ TouchController.#enable = true;
+ }
+ static disable() {
+ TouchController.#enable = false;
+ }
+ static isEnabled() {
+ return TouchController.#enable;
+ }
+ static #showDefault() {
+ TouchController.#dispatchMessage(TouchController.#EVENT_SHOW_DEFAULT_CONTROLLER);
+ }
+ static #show() {
+ document.querySelector("#BabylonCanvasContainer-main")?.parentElement?.classList.remove("bx-offscreen");
+ }
+ static #hide() {
+ document.querySelector("#BabylonCanvasContainer-main")?.parentElement?.classList.add("bx-offscreen");
+ }
+ static toggleVisibility(status) {
+ if (!TouchController.#dataChannel) {
+ return;
+ }
+ status ? TouchController.#hide() : TouchController.#show();
+ }
+ static reset() {
+ TouchController.#enable = false;
+ TouchController.#dataChannel = null;
+ TouchController.#$style && (TouchController.#$style.textContent = "");
+ }
+ static #dispatchMessage(msg) {
+ TouchController.#dataChannel && window.setTimeout(() => {
+ TouchController.#dataChannel.dispatchEvent(msg);
+ }, 10);
+ }
+ static #dispatchLayouts(data) {
+ BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
+ data
+ });
+ }
+ static async getCustomLayouts(xboxTitleId, retries = 1) {
+ if (xboxTitleId in TouchController.#customLayouts) {
+ TouchController.#dispatchLayouts(TouchController.#customLayouts[xboxTitleId]);
+ return;
+ }
+ retries = retries || 1;
+ if (retries > 2) {
+ TouchController.#customLayouts[xboxTitleId] = null;
+ window.setTimeout(() => TouchController.#dispatchLayouts(null), 1000);
+ return;
+ }
+ const baseUrl = `https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts${BX_FLAGS.UseDevTouchLayout ? "/dev" : ""}`;
+ const url = `${baseUrl}/${xboxTitleId}.json`;
+ try {
+ const resp = await NATIVE_FETCH(url);
+ const json = await resp.json();
+ const layouts = {};
+ json.layouts.forEach(async (layoutName) => {
+ let baseLayouts = {};
+ if (layoutName in TouchController.#baseCustomLayouts) {
+ baseLayouts = TouchController.#baseCustomLayouts[layoutName];
+ } else {
+ try {
+ const layoutUrl = `${baseUrl}/layouts/${layoutName}.json`;
+ const resp2 = await NATIVE_FETCH(layoutUrl);
+ const json2 = await resp2.json();
+ baseLayouts = json2.layouts;
+ TouchController.#baseCustomLayouts[layoutName] = baseLayouts;
+ } catch (e) {
+ }
+ }
+ Object.assign(layouts, baseLayouts);
+ });
+ json.layouts = layouts;
+ TouchController.#customLayouts[xboxTitleId] = json;
+ window.setTimeout(() => TouchController.#dispatchLayouts(json), 1000);
+ } catch (e) {
+ TouchController.getCustomLayouts(xboxTitleId, retries + 1);
+ }
+ }
+ static loadCustomLayout(xboxTitleId, layoutId, delay = 0) {
+ if (!window.BX_EXPOSED.touchLayoutManager) {
+ return;
+ }
+ const layoutChanged = TouchController.#currentLayoutId !== layoutId;
+ TouchController.#currentLayoutId = layoutId;
+ const layoutData = TouchController.#customLayouts[xboxTitleId];
+ if (!xboxTitleId || !layoutId || !layoutData) {
+ TouchController.#enable && TouchController.#showDefault();
+ return;
+ }
+ const layout = layoutData.layouts[layoutId] || layoutData.layouts[layoutData.default_layout];
+ if (!layout) {
+ return;
+ }
+ let msg;
+ let html15 = false;
+ if (layout.author) {
+ const author = `${escapeHtml(layout.author)}`;
+ msg = t("touch-control-layout-by", { name: author });
+ html15 = true;
+ } else {
+ msg = t("touch-control-layout");
+ }
+ layoutChanged && Toast.show(msg, layout.name, { html: html15 });
+ window.setTimeout(() => {
+ window.BX_EXPOSED.touchLayoutManager.changeLayoutForScope({
+ type: "showLayout",
+ scope: xboxTitleId,
+ subscope: "base",
+ layout: {
+ id: "System.Standard",
+ displayName: "System",
+ layoutFile: layout
+ }
+ });
+ }, delay);
+ }
+ static updateCustomList() {
+ const key = "better_xcloud_custom_touch_layouts";
+ TouchController.#customList = JSON.parse(window.localStorage.getItem(key) || "[]");
+ NATIVE_FETCH("https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts/ids.json").then((response) => response.json()).then((json) => {
+ TouchController.#customList = json;
+ window.localStorage.setItem(key, JSON.stringify(json));
+ });
+ }
+ static getCustomList() {
+ return TouchController.#customList;
+ }
+ static setup() {
+ window.testTouchLayout = (layout) => {
+ const { touchLayoutManager } = window.BX_EXPOSED;
+ touchLayoutManager && touchLayoutManager.changeLayoutForScope({
+ type: "showLayout",
+ scope: "" + STATES.currentStream?.xboxTitleId,
+ subscope: "base",
+ layout: {
+ id: "System.Standard",
+ displayName: "Custom",
+ layoutFile: layout
+ }
+ });
+ };
+ const $style = document.createElement("style");
+ document.documentElement.appendChild($style);
+ TouchController.#$style = $style;
+ const PREF_STYLE_STANDARD = getPref(PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD);
+ const PREF_STYLE_CUSTOM = getPref(PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM);
+ window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, (e) => {
+ const dataChannel = e.dataChannel;
+ if (!dataChannel || dataChannel.label !== "message") {
+ return;
+ }
+ let filter = "";
+ if (TouchController.#enable) {
+ if (PREF_STYLE_STANDARD === "white") {
+ filter = "grayscale(1) brightness(2)";
+ } else if (PREF_STYLE_STANDARD === "muted") {
+ filter = "sepia(0.5)";
+ }
+ } else if (PREF_STYLE_CUSTOM === "muted") {
+ filter = "sepia(0.5)";
+ }
+ if (filter) {
+ $style.textContent = `#babylon-canvas { filter: ${filter} !important; }`;
+ } else {
+ $style.textContent = "";
+ }
+ TouchController.#dataChannel = dataChannel;
+ dataChannel.addEventListener("open", () => {
+ window.setTimeout(TouchController.#show, 1000);
+ });
+ let focused = false;
+ dataChannel.addEventListener("message", (msg) => {
+ if (msg.origin === "better-xcloud" || typeof msg.data !== "string") {
+ return;
+ }
+ if (msg.data.includes("touchcontrols/showtitledefault")) {
+ if (TouchController.#enable) {
+ if (focused) {
+ TouchController.getCustomLayouts(STATES.currentStream?.xboxTitleId);
+ } else {
+ TouchController.#showDefault();
+ }
+ }
+ return;
+ }
+ try {
+ if (msg.data.includes("/titleinfo")) {
+ const json = JSON.parse(JSON.parse(msg.data).content);
+ focused = json.focused;
+ if (!json.focused) {
+ TouchController.#show();
+ }
+ STATES.currentStream.xboxTitleId = parseInt(json.titleid, 16).toString();
+ }
+ } catch (e2) {
+ BxLogger.error(LOG_TAG3, "Load custom layout", e2);
+ }
+ });
+ });
+ }
+}
+
+// src/modules/game-bar/action-touch-control.ts
+class TouchControlAction extends BaseGameBarAction {
+ $content;
+ constructor() {
+ super();
+ const onClick = (e) => {
+ BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
+ const $parent = e.target.closest("div[data-enabled]");
+ let enabled = $parent.getAttribute("data-enabled", "true") === "true";
+ $parent.setAttribute("data-enabled", (!enabled).toString());
+ TouchController.toggleVisibility(enabled);
+ };
+ const $btnEnable = createButton({
+ style: ButtonStyle.GHOST,
+ icon: BxIcon.TOUCH_CONTROL_ENABLE,
+ title: t("show-touch-controller"),
+ onClick
+ });
+ const $btnDisable = createButton({
+ style: ButtonStyle.GHOST,
+ icon: BxIcon.TOUCH_CONTROL_DISABLE,
+ title: t("hide-touch-controller"),
+ onClick
+ });
+ this.$content = CE("div", { "data-enabled": "true" }, $btnEnable, $btnDisable);
+ }
+ render() {
+ return this.$content;
+ }
+ reset() {
+ this.$content.setAttribute("data-enabled", "true");
+ }
+}
+
+// src/modules/game-bar/game-bar.ts
+class GameBar {
+ static #VISIBLE_DURATION = 2000;
+ static #timeout;
+ static #$gameBar;
+ static #$container;
+ static #$actions = [];
+ static #beginHideTimeout() {
+ GameBar.#clearHideTimeout();
+ GameBar.#timeout = window.setTimeout(() => {
+ GameBar.#timeout = null;
+ GameBar.hideBar();
+ }, GameBar.#VISIBLE_DURATION);
+ }
+ static #clearHideTimeout() {
+ GameBar.#timeout && clearTimeout(GameBar.#timeout);
+ GameBar.#timeout = null;
+ }
+ static enable() {
+ GameBar.#$gameBar && GameBar.#$gameBar.classList.remove("bx-gone");
+ }
+ static disable() {
+ GameBar.#$gameBar && GameBar.#$gameBar.classList.add("bx-gone");
+ GameBar.hideBar();
+ }
+ static showBar() {
+ if (!GameBar.#$container) {
+ return;
+ }
+ GameBar.#$container.classList.remove("bx-offscreen", "bx-hide");
+ GameBar.#$container.classList.add("bx-show");
+ GameBar.#beginHideTimeout();
+ }
+ static hideBar() {
+ if (!GameBar.#$container) {
+ return;
+ }
+ GameBar.#$container.classList.remove("bx-show");
+ GameBar.#$container.classList.add("bx-hide");
+ }
+ static reset() {
+ for (const action of GameBar.#$actions) {
+ action.reset();
+ }
+ }
+ static setup() {
+ let $container;
+ const $gameBar = CE("div", { id: "bx-game-bar", class: "bx-gone" }, $container = CE("div", { class: "bx-game-bar-container bx-offscreen" }), createSvgIcon(BxIcon.CARET_RIGHT));
+ GameBar.#$actions = [
+ new ScreenshotAction,
+ ...STATES.hasTouchSupport && getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== "off" ? [new TouchControlAction] : []
+ ];
+ for (const action of GameBar.#$actions) {
+ $container.appendChild(action.render());
+ }
+ $gameBar.addEventListener("click", (e) => {
+ if (e.target === $gameBar) {
+ if ($container.classList.contains("bx-show")) {
+ GameBar.hideBar();
+ } else {
+ GameBar.showBar();
+ }
+ }
+ });
+ window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, GameBar.hideBar);
+ $container.addEventListener("pointerover", GameBar.#clearHideTimeout);
+ $container.addEventListener("pointerout", GameBar.#beginHideTimeout);
+ $container.addEventListener("transitionend", (e) => {
+ const classList = $container.classList;
+ if (classList.contains("bx-hide")) {
+ classList.remove("bx-offscreen", "bx-hide");
+ classList.add("bx-offscreen");
+ }
+ });
+ document.documentElement.appendChild($gameBar);
+ GameBar.#$gameBar = $gameBar;
+ GameBar.#$container = $container;
+ }
+}
+
+// src/utils/bx-exposed.ts
+var InputType;
+(function(InputType2) {
+ InputType2["CONTROLLER"] = "Controller";
+ InputType2["MKB"] = "MKB";
+ InputType2["CUSTOM_TOUCH_OVERLAY"] = "CustomTouchOverlay";
+ InputType2["GENERIC_TOUCH"] = "GenericTouch";
+ InputType2["NATIVE_TOUCH"] = "NativeTouch";
+ InputType2["BATIVE_SENSOR"] = "NativeSensor";
+})(InputType || (InputType = {}));
+var BxExposed = {
+ onPollingModeChanged: (mode) => {
+ if (!STATES.isPlaying) {
+ GameBar.disable();
+ return;
+ }
+ mode !== "None" ? GameBar.disable() : GameBar.enable();
+ },
+ getTitleInfo: () => STATES.currentStream.titleInfo,
+ modifyTitleInfo: (titleInfo) => {
+ titleInfo = structuredClone(titleInfo);
+ if (STATES.hasTouchSupport) {
+ let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
+ let supportedInputTypes = titleInfo.details.supportedInputTypes;
+ if (touchControllerAvailability !== "off" && getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) {
+ const gamepads = window.navigator.getGamepads();
+ let gamepadFound = false;
+ for (let gamepad of gamepads) {
+ if (gamepad && gamepad.connected) {
+ gamepadFound = true;
+ break;
+ }
+ }
+ gamepadFound && (touchControllerAvailability = "off");
+ }
+ if (UserAgent.isMobile()) {
+ supportedInputTypes = supportedInputTypes.filter((i) => i !== InputType.MKB);
+ }
+ if (touchControllerAvailability === "off") {
+ supportedInputTypes = supportedInputTypes.filter((i) => i !== InputType.CUSTOM_TOUCH_OVERLAY && i !== InputType.GENERIC_TOUCH);
+ }
+ titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
+ titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) || supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) || supportedInputTypes.includes(InputType.GENERIC_TOUCH);
+ if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === "all") {
+ titleInfo.details.hasFakeTouchSupport = true;
+ supportedInputTypes.push(InputType.GENERIC_TOUCH);
+ }
+ titleInfo.details.supportedInputTypes = supportedInputTypes;
+ }
+ STATES.currentStream.titleInfo = titleInfo;
+ BxEvent.dispatch(window, BxEvent.TITLE_INFO_READY);
+ return titleInfo;
+ },
+ setupGainNode: ($media, audioStream) => {
+ if ($media instanceof HTMLAudioElement) {
+ $media.muted = true;
+ $media.addEventListener("playing", (e) => {
+ $media.muted = true;
+ $media.pause();
+ });
+ } else {
+ $media.muted = true;
+ $media.addEventListener("playing", (e) => {
+ $media.muted = true;
+ });
+ }
+ const audioCtx = STATES.currentStream.audioContext;
+ const source = audioCtx.createMediaStreamSource(audioStream);
+ const gainNode = audioCtx.createGain();
+ source.connect(gainNode).connect(audioCtx.destination);
+ }
+};
+
// src/utils/gamepad.ts
function showGamepadToast(gamepad) {
if (gamepad.id === MkbHandler.VIRTUAL_GAMEPAD_ID) {
@@ -8068,8 +8204,7 @@ function addCss() {
--bx-stats-bar-z-index: 9001;
--bx-stream-settings-z-index: 9000;
--bx-mkb-pointer-lock-msg-z-index: 8999;
- --bx-screenshot-z-index: 8888;
- --bx-touch-controller-bar-z-index: 5555;
+ --bx-game-bar-z-index: 8888;
--bx-wait-time-box-z-index: 100;
}
@font-face {
@@ -8707,44 +8842,72 @@ body[data-media-type=tv] .bx-stream-refresh-button {
.bx-number-stepper button:disabled {
display: none;
}
-.bx-screenshot-button {
- display: none;
- opacity: 0;
+#bx-game-bar {
+ z-index: var(--bx-game-bar-z-index);
position: fixed;
- bottom: 0;
- box-sizing: border-box;
- width: 60px;
- height: 90px;
- padding: 16px 16px 46px 16px;
- background-size: cover;
- background-repeat: no-repeat;
- background-origin: content-box;
- filter: drop-shadow(0 0 2px rgba(0,0,0,0.69));
- transition: opacity 0.1s ease-in-out 0s, padding 0.1s ease-in 0s;
- z-index: var(--bx-screenshot-z-index);
-/* Credit: https://phosphoricons.com */
- background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDMyIDMyIiBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMjguMzA4IDUuMDM4aC00LjI2NWwtMi4wOTctMy4xNDVhMS4yMyAxLjIzIDAgMCAwLTEuMDIzLS41NDhoLTkuODQ2YTEuMjMgMS4yMyAwIDAgMC0xLjAyMy41NDhMNy45NTYgNS4wMzhIMy42OTJBMy43MSAzLjcxIDAgMCAwIDAgOC43MzF2MTcuMjMxYTMuNzEgMy43MSAwIDAgMCAzLjY5MiAzLjY5MmgyNC42MTVBMy43MSAzLjcxIDAgMCAwIDMyIDI1Ljk2MlY4LjczMWEzLjcxIDMuNzEgMCAwIDAtMy42OTItMy42OTJ6bS02Ljc2OSAxMS42OTJjMCAzLjAzOS0yLjUgNS41MzgtNS41MzggNS41MzhzLTUuNTM4LTIuNS01LjUzOC01LjUzOCAyLjUtNS41MzggNS41MzgtNS41MzggNS41MzggMi41IDUuNTM4IDUuNTM4eiIvPjwvc3ZnPgo=");
-}
-.bx-screenshot-button[data-showing=true] {
- opacity: 0.9;
-}
-.bx-screenshot-button[data-capturing=true] {
- padding: 8px 8px 38px 8px;
-}
-.bx-screenshot-canvas {
- display: none;
-}
-#bx-touch-controller-bar {
- display: none;
- opacity: 0;
- position: fixed;
- bottom: 0;
left: 0;
- right: 0;
- height: 6vh;
- z-index: var(--bx-touch-controller-bar-z-index);
+ bottom: 0;
+ width: 40px;
+ height: 90px;
+ overflow: visible;
+ cursor: pointer;
}
-#bx-touch-controller-bar[data-showing=true] {
+#bx-game-bar > svg {
+ display: none;
+ pointer-events: none;
+ position: absolute;
+ height: 28px;
+ margin-top: 16px;
+}
+@media (hover: hover) {
+ #bx-game-bar:hover > svg {
+ display: block;
+ }
+}
+#bx-game-bar .bx-game-bar-container {
+ opacity: 0;
+ position: absolute;
+ display: flex;
+ overflow: hidden;
+ background: rgba(26,27,30,0.91);
+ border-radius: 0 10px 10px 0;
+ box-shadow: 0px 0px 6px #1c1c1c;
+ transition: opacity 0.1s ease-in;
+/* Touch controller buttons */
+/* Show disable button */
+/* Show enable button */
+}
+#bx-game-bar .bx-game-bar-container.bx-show {
+ opacity: 1;
+}
+#bx-game-bar .bx-game-bar-container.bx-show + svg {
+ display: none !important;
+}
+#bx-game-bar .bx-game-bar-container.bx-hide {
+ opacity: 0;
+}
+#bx-game-bar .bx-game-bar-container button {
+ width: 60px;
+ height: 60px;
+}
+#bx-game-bar .bx-game-bar-container button svg {
+ width: 28px;
+ height: 28px;
+ transition: transform 0.08s ease 0s;
+}
+#bx-game-bar .bx-game-bar-container button:hover {
+ border-radius: 0;
+}
+#bx-game-bar .bx-game-bar-container button:active svg {
+ transform: scale(0.75);
+}
+#bx-game-bar .bx-game-bar-container div[data-enabled] button {
+ display: none;
+}
+#bx-game-bar .bx-game-bar-container div[data-enabled='true'] button:last-of-type {
+ display: block;
+}
+#bx-game-bar .bx-game-bar-container div[data-enabled='false'] button:first-of-type {
display: block;
}
.bx-badges {
@@ -10089,7 +10252,6 @@ var SETTINGS_UI = {
PrefKey.GAME_FORTNITE_FORCE_CONSOLE,
PrefKey.AUDIO_MIC_ON_PLAYING,
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
- PrefKey.SCREENSHOT_BUTTON_POSITION,
PrefKey.SCREENSHOT_APPLY_FILTERS,
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
PrefKey.STREAM_COMBINE_SOURCES
@@ -10431,7 +10593,7 @@ var main = function() {
BX_FLAGS.CheckForUpdate && checkForUpdate();
addCss();
Toast.setup();
- BX_FLAGS.PreloadUi && setupBxUi();
+ BX_FLAGS.PreloadUi && setupStreamUi();
StreamBadges.setupEvents();
StreamStats.setupEvents();
MkbHandler.setupEvents();
@@ -10511,7 +10673,7 @@ window.addEventListener(BxEvent.STREAM_LOADING, (e) => {
STATES.currentStream.titleId = "remote-play";
STATES.currentStream.productId = "";
}
- setupBxUi();
+ setupStreamUi();
});
getPref(PrefKey.UI_LOADING_SCREEN_GAME_ART) && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup);
window.addEventListener(BxEvent.STREAM_STARTING, (e) => {
@@ -10526,22 +10688,12 @@ window.addEventListener(BxEvent.STREAM_PLAYING, (e) => {
STATES.currentStream.$video = $video;
STATES.isPlaying = true;
injectStreamMenuButtons();
- const PREF_SCREENSHOT_BUTTON_POSITION = getPref(PrefKey.SCREENSHOT_BUTTON_POSITION);
+ GameBar.reset();
+ GameBar.enable();
+ GameBar.showBar();
STATES.currentStream.$screenshotCanvas.width = $video.videoWidth;
STATES.currentStream.$screenshotCanvas.height = $video.videoHeight;
updateVideoPlayerCss();
- if (PREF_SCREENSHOT_BUTTON_POSITION !== "none") {
- const $btn = document.querySelector(".bx-screenshot-button");
- $btn.classList.remove("bx-gone");
- $btn.style.display = "block";
- if (PREF_SCREENSHOT_BUTTON_POSITION === "bottom-right") {
- $btn.style.right = "0";
- } else {
- $btn.style.left = "0";
- }
- }
- const $touchControllerBar = document.getElementById("bx-touch-controller-bar");
- $touchControllerBar && $touchControllerBar.classList.remove("bx-gone");
});
window.addEventListener(BxEvent.STREAM_ERROR_PAGE, (e) => {
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
@@ -10559,11 +10711,8 @@ window.addEventListener(BxEvent.STREAM_STOPPED, (e) => {
STATES.currentStream.audioGainNode = null;
STATES.currentStream.$video = null;
StreamStats.onStoppedPlaying();
- const $screenshotBtn = document.querySelector(".bx-screenshot-button");
- if ($screenshotBtn) {
- $screenshotBtn.removeAttribute("style");
- }
MouseCursorHider.stop();
TouchController.reset();
+ GameBar.disable();
});
main();