mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-06 07:37:19 +02:00
Game bar (#392)
* Fix games with custom touch control sometimes not showing touch icon * Create game-bar with screenshot button * Disable Game bar when opening the Guide * Remove SCREENSHOT_BUTTON_POSITION pref * Make the touch control action functional * Show game bar when the game starts * Fix 720p/High not working (#387) * Update icons * Update game bar's animations * Reset states of Game bar actions before playing * Don't show Touch control action on non-touch-supported devices * Clean up * Update translations * Update actions' texts * Clean up
This commit is contained in:
parent
b66ca192b2
commit
b2e932cc4c
91
src/assets/css/game-bar.styl
Normal file
91
src/assets/css/game-bar.styl
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#bx-game-bar {
|
||||||
|
z-index: var(--bx-game-bar-z-index);
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 90px;
|
||||||
|
overflow: visible;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
height: 28px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
&:hover {
|
||||||
|
> svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bx-game-bar-container {
|
||||||
|
opacity: 0;
|
||||||
|
position absolute;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #1a1b1ee8;
|
||||||
|
border-radius: 0 10px 10px 0;
|
||||||
|
box-shadow: 0px 0px 6px #1c1c1c;
|
||||||
|
transition: opacity 0.1s ease-in;
|
||||||
|
|
||||||
|
&.bx-show {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
+ svg {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bx-hide {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
transition: transform 0.08s ease 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
svg {
|
||||||
|
transform: scale(0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch controller buttons */
|
||||||
|
div[data-enabled] {
|
||||||
|
button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show disable button */
|
||||||
|
div[data-enabled='true'] {
|
||||||
|
button:last-of-type {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show enable button */
|
||||||
|
div[data-enabled='false'] {
|
||||||
|
button:first-of-type {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,8 +27,7 @@
|
|||||||
--bx-stats-bar-z-index: 9001;
|
--bx-stats-bar-z-index: 9001;
|
||||||
--bx-stream-settings-z-index: 9000;
|
--bx-stream-settings-z-index: 9000;
|
||||||
--bx-mkb-pointer-lock-msg-z-index: 8999;
|
--bx-mkb-pointer-lock-msg-z-index: 8999;
|
||||||
--bx-screenshot-z-index: 8888;
|
--bx-game-bar-z-index: 8888;
|
||||||
--bx-touch-controller-bar-z-index: 5555;
|
|
||||||
--bx-wait-time-box-z-index: 100;
|
--bx-wait-time-box-z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
.bx-screenshot-button {
|
|
||||||
display: none;
|
|
||||||
opacity: 0;
|
|
||||||
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 #000000B0);
|
|
||||||
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=');
|
|
||||||
|
|
||||||
&[data-showing=true] {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[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);
|
|
||||||
|
|
||||||
&[data-showing=true] {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
@import 'stream.styl';
|
@import 'stream.styl';
|
||||||
@import 'number-stepper.styl';
|
@import 'number-stepper.styl';
|
||||||
@import 'stream-actions.styl';
|
@import 'game-bar.styl';
|
||||||
@import 'stream-stats.styl';
|
@import 'stream-stats.styl';
|
||||||
@import 'stream-settings.styl';
|
@import 'stream-settings.styl';
|
||||||
@import 'mkb.styl';
|
@import 'mkb.styl';
|
||||||
|
6
src/assets/svg/camera.svg
Normal file
6
src/assets/svg/camera.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||||
|
<g transform="matrix(.150985 0 0 .150985 -3.32603 -2.72209)" fill="none" stroke="#fff" stroke-width="16">
|
||||||
|
<path d="M208 208H48c-8.777 0-16-7.223-16-16V80c0-8.777 7.223-16 16-16h32l16-24h64l16 24h32c8.777 0 16 7.223 16 16v112c0 8.777-7.223 16-16 16z"/>
|
||||||
|
<circle cx="128" cy="132" r="36"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 494 B |
5
src/assets/svg/caret-right.svg
Normal file
5
src/assets/svg/caret-right.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100%" stroke="#fff" fill="#fff" height="100%" viewBox="0 0 13 23" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||||
|
<g transform="matrix(.399603 0 0 .709528 0 0)">
|
||||||
|
<path d="M1.601 2.727l23.255 13.097L1.601 28.922c-.977.55-.977 1.443 0 1.993s2.563.55 3.539 0l25.025-14.094c.977-.55.977-1.443 0-1.993L5.14.734c-.977-.55-2.563-.55-3.539 0s-.977 1.443 0 1.993z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 452 B |
9
src/assets/svg/touch-control-disable.svg
Normal file
9
src/assets/svg/touch-control-disable.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 32 32" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||||
|
<g fill="none" stroke="#fff">
|
||||||
|
<path d="M6.021 5.021l20 22" stroke-width="2"/>
|
||||||
|
<path d="M8.735 8.559H2.909a.89.89 0 0 0-.889.889v13.146a.89.89 0 0 0 .889.888h19.34m4.289 0h2.594a.89.89 0 0 0 .889-.888V9.448a.89.89 0 0 0-.889-.889H12.971" stroke-miterlimit="1.5" stroke-width="2.083"/>
|
||||||
|
</g>
|
||||||
|
<path d="M8.147 11.981l-.053-.001-.054.001c-.55.028-.988.483-.988 1.04v6c0 .575.467 1.042 1.042 1.042l.053-.001c.55-.028.988-.484.988-1.04v-6a1.04 1.04 0 0 0-.988-1.04z"/>
|
||||||
|
<path d="M11.147 14.981l-.054-.001h-6a1.04 1.04 0 1 0 0 2.083h6c.575 0 1.042-.467 1.042-1.042a1.04 1.04 0 0 0-.988-1.04z"/>
|
||||||
|
<circle cx="25.345" cy="18.582" r="2.561" fill="none" stroke="#fff" stroke-width="1.78" transform="matrix(1.17131 0 0 1.17131 -5.74235 -5.74456)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 915 B |
6
src/assets/svg/touch-control-enable.svg
Normal file
6
src/assets/svg/touch-control-enable.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 32 32" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2">
|
||||||
|
<path d="M30.021 9.448a.89.89 0 0 0-.889-.889H2.909a.89.89 0 0 0-.889.889v13.146a.89.89 0 0 0 .889.888h26.223a.89.89 0 0 0 .889-.888V9.448z" fill="none" stroke="#fff" stroke-width="2.083"/>
|
||||||
|
<path d="M8.147 11.981l-.053-.001-.054.001c-.55.028-.988.483-.988 1.04v6c0 .575.467 1.042 1.042 1.042l.053-.001c.55-.028.988-.484.988-1.04v-6a1.04 1.04 0 0 0-.988-1.04z"/>
|
||||||
|
<path d="M11.147 14.981l-.054-.001h-6a1.04 1.04 0 1 0 0 2.083h6c.575 0 1.042-.467 1.042-1.042a1.04 1.04 0 0 0-.988-1.04z"/>
|
||||||
|
<circle cx="25.345" cy="18.582" r="2.561" fill="none" stroke="#fff" stroke-width="1.78" transform="matrix(1.17131 0 0 1.17131 -5.74235 -5.74456)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 796 B |
41
src/index.ts
41
src/index.ts
@ -11,7 +11,7 @@ import { StreamBadges } from "@modules/stream/stream-badges";
|
|||||||
import { StreamStats } from "@modules/stream/stream-stats";
|
import { StreamStats } from "@modules/stream/stream-stats";
|
||||||
import { addCss } from "@utils/css";
|
import { addCss } from "@utils/css";
|
||||||
import { Toast } from "@utils/toast";
|
import { Toast } from "@utils/toast";
|
||||||
import { setupBxUi, updateVideoPlayerCss } from "@modules/ui/ui";
|
import { setupStreamUi, updateVideoPlayerCss } from "@modules/ui/ui";
|
||||||
import { PrefKey, getPref } from "@utils/preferences";
|
import { PrefKey, getPref } from "@utils/preferences";
|
||||||
import { LoadingScreen } from "@modules/loading-screen";
|
import { LoadingScreen } from "@modules/loading-screen";
|
||||||
import { MouseCursorHider } from "@modules/mkb/mouse-cursor-hider";
|
import { MouseCursorHider } from "@modules/mkb/mouse-cursor-hider";
|
||||||
@ -27,6 +27,7 @@ import { patchAudioContext, patchCanvasContext, patchMeControl, patchRtcCodecs,
|
|||||||
import { STATES } from "@utils/global";
|
import { STATES } from "@utils/global";
|
||||||
import { injectStreamMenuButtons } from "@modules/stream/stream-ui";
|
import { injectStreamMenuButtons } from "@modules/stream/stream-ui";
|
||||||
import { BxLogger } from "@utils/bx-logger";
|
import { BxLogger } from "@utils/bx-logger";
|
||||||
|
import { GameBar } from "./modules/game-bar/game-bar";
|
||||||
|
|
||||||
// Handle login page
|
// Handle login page
|
||||||
if (window.location.pathname.includes('/auth/msa')) {
|
if (window.location.pathname.includes('/auth/msa')) {
|
||||||
@ -123,9 +124,7 @@ window.addEventListener(BxEvent.STREAM_LOADING, e => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup UI
|
// Setup UI
|
||||||
setupBxUi();
|
setupStreamUi();
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup loading screen
|
// Setup loading screen
|
||||||
@ -148,32 +147,14 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
|||||||
|
|
||||||
STATES.isPlaying = true;
|
STATES.isPlaying = true;
|
||||||
injectStreamMenuButtons();
|
injectStreamMenuButtons();
|
||||||
/*
|
|
||||||
if (getPref(Preferences.CONTROLLER_ENABLE_SHORTCUTS)) {
|
|
||||||
GamepadHandler.startPolling();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
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!.width = $video.videoWidth;
|
||||||
STATES.currentStream.$screenshotCanvas!.height = $video.videoHeight;
|
STATES.currentStream.$screenshotCanvas!.height = $video.videoHeight;
|
||||||
updateVideoPlayerCss();
|
updateVideoPlayerCss();
|
||||||
|
|
||||||
// Setup screenshot button
|
|
||||||
if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
|
|
||||||
const $btn = document.querySelector('.bx-screenshot-button')! as HTMLElement;
|
|
||||||
$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 => {
|
window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
|
||||||
@ -199,13 +180,9 @@ window.addEventListener(BxEvent.STREAM_STOPPED, e => {
|
|||||||
STATES.currentStream.$video = null;
|
STATES.currentStream.$video = null;
|
||||||
StreamStats.onStoppedPlaying();
|
StreamStats.onStoppedPlaying();
|
||||||
|
|
||||||
const $screenshotBtn = document.querySelector('.bx-screenshot-button');
|
|
||||||
if ($screenshotBtn) {
|
|
||||||
$screenshotBtn.removeAttribute('style');
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseCursorHider.stop();
|
MouseCursorHider.stop();
|
||||||
TouchController.reset();
|
TouchController.reset();
|
||||||
|
GameBar.disable();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -231,7 +208,7 @@ function main() {
|
|||||||
// Setup UI
|
// Setup UI
|
||||||
addCss();
|
addCss();
|
||||||
Toast.setup();
|
Toast.setup();
|
||||||
BX_FLAGS.PreloadUi && setupBxUi();
|
BX_FLAGS.PreloadUi && setupStreamUi();
|
||||||
|
|
||||||
StreamBadges.setupEvents();
|
StreamBadges.setupEvents();
|
||||||
StreamStats.setupEvents();
|
StreamStats.setupEvents();
|
||||||
|
6
src/modules/game-bar/action-base.ts
Normal file
6
src/modules/game-bar/action-base.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export abstract class BaseGameBarAction {
|
||||||
|
constructor() {}
|
||||||
|
reset() {}
|
||||||
|
|
||||||
|
abstract render(): HTMLElement;
|
||||||
|
}
|
78
src/modules/game-bar/action-screenshot.ts
Normal file
78
src/modules/game-bar/action-screenshot.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { BxEvent } from "@utils/bx-event";
|
||||||
|
import { AppInterface, STATES } from "@utils/global";
|
||||||
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
|
import { BaseGameBarAction } from "./action-base";
|
||||||
|
import { t } from "@utils/translation";
|
||||||
|
|
||||||
|
export class ScreenshotAction extends BaseGameBarAction {
|
||||||
|
$content: HTMLElement;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const currentStream = STATES.currentStream;
|
||||||
|
currentStream.$screenshotCanvas = CE('canvas', {'class': 'bx-gone'});
|
||||||
|
document.documentElement.appendChild(currentStream.$screenshotCanvas!);
|
||||||
|
|
||||||
|
const onClick = (e: Event) => {
|
||||||
|
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||||
|
this.takeScreenshot();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$content = createButton({
|
||||||
|
style: ButtonStyle.GHOST,
|
||||||
|
icon: BxIcon.SCREENSHOT,
|
||||||
|
title: t('take-screenshot'),
|
||||||
|
onClick: onClick,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): HTMLElement {
|
||||||
|
return this.$content;
|
||||||
|
}
|
||||||
|
|
||||||
|
takeScreenshot(callback?: any) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Get data URL and pass to parent app
|
||||||
|
if (AppInterface) {
|
||||||
|
const data = $canvas.toDataURL('image/png').split(';base64,')[1];
|
||||||
|
AppInterface.saveScreenshot(currentStream.titleId, data);
|
||||||
|
|
||||||
|
// Free screenshot from memory
|
||||||
|
$canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
|
||||||
|
|
||||||
|
callback && callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$canvas && $canvas.toBlob(blob => {
|
||||||
|
// Download screenshot
|
||||||
|
const now = +new Date;
|
||||||
|
const $anchor = CE<HTMLAnchorElement>('a', {
|
||||||
|
'download': `${currentStream.titleId}-${now}.png`,
|
||||||
|
'href': URL.createObjectURL(blob!),
|
||||||
|
});
|
||||||
|
$anchor.click();
|
||||||
|
|
||||||
|
// Free screenshot from memory
|
||||||
|
URL.revokeObjectURL($anchor.href);
|
||||||
|
$canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
|
||||||
|
|
||||||
|
callback && callback();
|
||||||
|
}, 'image/png');
|
||||||
|
}
|
||||||
|
}
|
51
src/modules/game-bar/action-touch-control.ts
Normal file
51
src/modules/game-bar/action-touch-control.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { BxEvent } from "@utils/bx-event";
|
||||||
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
|
import { TouchController } from "@modules/touch-controller";
|
||||||
|
import { BaseGameBarAction } from "./action-base";
|
||||||
|
import { t } from "@utils/translation";
|
||||||
|
|
||||||
|
export class TouchControlAction extends BaseGameBarAction {
|
||||||
|
$content: HTMLElement;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const onClick = (e: Event) => {
|
||||||
|
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||||
|
|
||||||
|
const $parent = (e as any).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: onClick,
|
||||||
|
});
|
||||||
|
|
||||||
|
const $btnDisable = createButton({
|
||||||
|
style: ButtonStyle.GHOST,
|
||||||
|
icon: BxIcon.TOUCH_CONTROL_DISABLE,
|
||||||
|
title: t('hide-touch-controller'),
|
||||||
|
onClick: onClick,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$content = CE('div', {'data-enabled': 'true'},
|
||||||
|
$btnEnable,
|
||||||
|
$btnDisable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): HTMLElement {
|
||||||
|
return this.$content;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.$content.setAttribute('data-enabled', 'true');
|
||||||
|
}
|
||||||
|
}
|
116
src/modules/game-bar/game-bar.ts
Normal file
116
src/modules/game-bar/game-bar.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { CE, createSvgIcon } from "@utils/html";
|
||||||
|
import { ScreenshotAction } from "./action-screenshot";
|
||||||
|
import { TouchControlAction } from "./action-touch-control";
|
||||||
|
import { BxEvent } from "@utils/bx-event";
|
||||||
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
|
import type { BaseGameBarAction } from "./action-base";
|
||||||
|
import { STATES } from "@utils/global";
|
||||||
|
import { PrefKey, getPref } from "@utils/preferences";
|
||||||
|
|
||||||
|
|
||||||
|
export class GameBar {
|
||||||
|
static readonly #VISIBLE_DURATION = 2000;
|
||||||
|
static #timeout: number | null;
|
||||||
|
|
||||||
|
static #$gameBar: HTMLElement;
|
||||||
|
static #$container: HTMLElement;
|
||||||
|
|
||||||
|
static #$actions: BaseGameBarAction[] = [];
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all states
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle game bar when clicking on the game bar box
|
||||||
|
$gameBar.addEventListener('click', e => {
|
||||||
|
if (e.target === $gameBar) {
|
||||||
|
if ($container.classList.contains('bx-show')) {
|
||||||
|
GameBar.hideBar();
|
||||||
|
} else {
|
||||||
|
GameBar.showBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide game bar after clicking on an action
|
||||||
|
window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, GameBar.hideBar);
|
||||||
|
|
||||||
|
$container.addEventListener('pointerover', GameBar.#clearHideTimeout);
|
||||||
|
$container.addEventListener('pointerout', GameBar.#beginHideTimeout);
|
||||||
|
|
||||||
|
// Add animation when hiding game bar
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,100 +0,0 @@
|
|||||||
import { STATES, AppInterface } from "@utils/global";
|
|
||||||
import { CE } from "@utils/html";
|
|
||||||
|
|
||||||
export function takeScreenshot(callback: any) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Get data URL and pass to parent app
|
|
||||||
if (AppInterface) {
|
|
||||||
const data = $canvas.toDataURL('image/png').split(';base64,')[1];
|
|
||||||
AppInterface.saveScreenshot(currentStream.titleId, data);
|
|
||||||
|
|
||||||
// Free screenshot from memory
|
|
||||||
$canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
|
|
||||||
|
|
||||||
callback && callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$canvas && $canvas.toBlob(blob => {
|
|
||||||
// Download screenshot
|
|
||||||
const now = +new Date;
|
|
||||||
const $anchor = CE<HTMLAnchorElement>('a', {
|
|
||||||
'download': `${currentStream.titleId}-${now}.png`,
|
|
||||||
'href': URL.createObjectURL(blob!),
|
|
||||||
});
|
|
||||||
$anchor.click();
|
|
||||||
|
|
||||||
// Free screenshot from memory
|
|
||||||
URL.revokeObjectURL($anchor.href);
|
|
||||||
$canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
|
|
||||||
|
|
||||||
callback && callback();
|
|
||||||
}, 'image/png');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export 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: number | null;
|
|
||||||
const detectDbClick = (e: MouseEvent) => {
|
|
||||||
if (!currentStream.$video) {
|
|
||||||
timeout = null;
|
|
||||||
$btn.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = null;
|
|
||||||
$btn.setAttribute('data-capturing', 'true');
|
|
||||||
|
|
||||||
takeScreenshot(() => {
|
|
||||||
// Hide button
|
|
||||||
$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) {
|
|
||||||
// Show button
|
|
||||||
$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);
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { STATES } from "@utils/global";
|
import { STATES } from "@utils/global";
|
||||||
import { CE, escapeHtml } from "@utils/html";
|
import { escapeHtml } from "@utils/html";
|
||||||
import { Toast } from "@utils/toast";
|
import { Toast } from "@utils/toast";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
import { BX_FLAGS } from "@utils/bx-flags";
|
import { BX_FLAGS } from "@utils/bx-flags";
|
||||||
@ -12,7 +12,11 @@ const LOG_TAG = 'TouchController';
|
|||||||
|
|
||||||
export class TouchController {
|
export class TouchController {
|
||||||
static readonly #EVENT_SHOW_DEFAULT_CONTROLLER = new MessageEvent('message', {
|
static readonly #EVENT_SHOW_DEFAULT_CONTROLLER = new MessageEvent('message', {
|
||||||
data: '{"content":"{\\"layoutId\\":\\"\\"}","target":"/streaming/touchcontrols/showlayoutv2","type":"Message"}',
|
data: JSON.stringify({
|
||||||
|
content: '{"layoutId":""}',
|
||||||
|
target: '/streaming/touchcontrols/showlayoutv2',
|
||||||
|
type: 'Message',
|
||||||
|
}),
|
||||||
origin: 'better-xcloud',
|
origin: 'better-xcloud',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -23,17 +27,17 @@ export class TouchController {
|
|||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static #$bar: HTMLElement;
|
|
||||||
static #$style: HTMLStyleElement;
|
static #$style: HTMLStyleElement;
|
||||||
|
|
||||||
static #enable = false;
|
static #enable = false;
|
||||||
static #showing = false;
|
|
||||||
static #dataChannel: RTCDataChannel | null;
|
static #dataChannel: RTCDataChannel | null;
|
||||||
|
|
||||||
static #customLayouts: {[index: string]: any} = {};
|
static #customLayouts: {[index: string]: any} = {};
|
||||||
static #baseCustomLayouts: {[index: string]: any} = {};
|
static #baseCustomLayouts: {[index: string]: any} = {};
|
||||||
static #currentLayoutId: string;
|
static #currentLayoutId: string;
|
||||||
|
|
||||||
|
static #customList: string[];
|
||||||
|
|
||||||
static enable() {
|
static enable() {
|
||||||
TouchController.#enable = true;
|
TouchController.#enable = true;
|
||||||
}
|
}
|
||||||
@ -48,37 +52,28 @@ export class TouchController {
|
|||||||
|
|
||||||
static #showDefault() {
|
static #showDefault() {
|
||||||
TouchController.#dispatchMessage(TouchController.#EVENT_SHOW_DEFAULT_CONTROLLER);
|
TouchController.#dispatchMessage(TouchController.#EVENT_SHOW_DEFAULT_CONTROLLER);
|
||||||
TouchController.#showing = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static #show() {
|
static #show() {
|
||||||
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.remove('bx-offscreen');
|
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.remove('bx-offscreen');
|
||||||
TouchController.#showing = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static #hide() {
|
static #hide() {
|
||||||
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.add('bx-offscreen');
|
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.add('bx-offscreen');
|
||||||
TouchController.#showing = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static #toggleVisibility() {
|
static toggleVisibility(status: boolean) {
|
||||||
if (!TouchController.#dataChannel) {
|
if (!TouchController.#dataChannel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchController.#showing ? TouchController.#hide() : TouchController.#show();
|
status ? TouchController.#hide() : TouchController.#show();
|
||||||
}
|
|
||||||
|
|
||||||
static #toggleBar(value: boolean) {
|
|
||||||
TouchController.#$bar && TouchController.#$bar.setAttribute('data-showing', value.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static reset() {
|
static reset() {
|
||||||
TouchController.#enable = false;
|
TouchController.#enable = false;
|
||||||
TouchController.#showing = false;
|
|
||||||
TouchController.#dataChannel = null;
|
TouchController.#dataChannel = null;
|
||||||
|
|
||||||
TouchController.#$bar && TouchController.#$bar.removeAttribute('data-showing');
|
|
||||||
TouchController.#$style && (TouchController.#$style.textContent = '');
|
TouchController.#$style && (TouchController.#$style.textContent = '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,15 +190,19 @@ export class TouchController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static updateCustomList() {
|
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')
|
NATIVE_FETCH('https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts/ids.json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
window.localStorage.setItem('better_xcloud_custom_touch_layouts', JSON.stringify(json));
|
TouchController.#customList = json;
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(json));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCustomList(): string[] {
|
static getCustomList(): string[] {
|
||||||
return JSON.parse(window.localStorage.getItem('better_xcloud_custom_touch_layouts') || '[]');
|
return TouchController.#customList;
|
||||||
}
|
}
|
||||||
|
|
||||||
static setup() {
|
static setup() {
|
||||||
@ -223,32 +222,9 @@ export class TouchController {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const $fragment = document.createDocumentFragment();
|
|
||||||
const $style = document.createElement('style');
|
const $style = document.createElement('style');
|
||||||
$fragment.appendChild($style);
|
document.documentElement.appendChild($style);
|
||||||
|
|
||||||
const $bar = CE('div', {'id': 'bx-touch-controller-bar'});
|
|
||||||
$fragment.appendChild($bar);
|
|
||||||
|
|
||||||
document.documentElement.appendChild($fragment);
|
|
||||||
|
|
||||||
// Setup double-tap event
|
|
||||||
let clickTimeout: number | null;
|
|
||||||
$bar.addEventListener('mousedown', (e: MouseEvent) => {
|
|
||||||
clickTimeout && clearTimeout(clickTimeout);
|
|
||||||
if (clickTimeout) {
|
|
||||||
// Double-clicked
|
|
||||||
clickTimeout = null;
|
|
||||||
TouchController.#toggleVisibility();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clickTimeout = window.setTimeout(() => {
|
|
||||||
clickTimeout = null;
|
|
||||||
}, 400);
|
|
||||||
});
|
|
||||||
|
|
||||||
TouchController.#$bar = $bar;
|
|
||||||
TouchController.#$style = $style;
|
TouchController.#$style = $style;
|
||||||
|
|
||||||
const PREF_STYLE_STANDARD = getPref(PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD);
|
const PREF_STYLE_STANDARD = getPref(PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD);
|
||||||
@ -307,7 +283,6 @@ export class TouchController {
|
|||||||
try {
|
try {
|
||||||
if (msg.data.includes('/titleinfo')) {
|
if (msg.data.includes('/titleinfo')) {
|
||||||
const json = JSON.parse(JSON.parse(msg.data).content);
|
const json = JSON.parse(JSON.parse(msg.data).content);
|
||||||
TouchController.#toggleBar(json.focused);
|
|
||||||
|
|
||||||
focused = json.focused;
|
focused = json.focused;
|
||||||
if (!json.focused) {
|
if (!json.focused) {
|
||||||
|
@ -31,7 +31,6 @@ const SETTINGS_UI = {
|
|||||||
PrefKey.AUDIO_MIC_ON_PLAYING,
|
PrefKey.AUDIO_MIC_ON_PLAYING,
|
||||||
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
|
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
|
||||||
|
|
||||||
PrefKey.SCREENSHOT_BUTTON_POSITION,
|
|
||||||
PrefKey.SCREENSHOT_APPLY_FILTERS,
|
PrefKey.SCREENSHOT_APPLY_FILTERS,
|
||||||
|
|
||||||
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
|
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
|
||||||
|
@ -5,11 +5,11 @@ import { UserAgent } from "@utils/user-agent";
|
|||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
import { MkbRemapper } from "@modules/mkb/mkb-remapper";
|
import { MkbRemapper } from "@modules/mkb/mkb-remapper";
|
||||||
import { getPref, PrefKey, toPrefElement } from "@utils/preferences";
|
import { getPref, PrefKey, toPrefElement } from "@utils/preferences";
|
||||||
import { setupScreenshotButton } from "@modules/screenshot";
|
|
||||||
import { StreamStats } from "@modules/stream/stream-stats";
|
import { StreamStats } from "@modules/stream/stream-stats";
|
||||||
import { TouchController } from "@modules/touch-controller";
|
import { TouchController } from "@modules/touch-controller";
|
||||||
import { t } from "@utils/translation";
|
import { t } from "@utils/translation";
|
||||||
import { VibrationManager } from "@modules/vibration-manager";
|
import { VibrationManager } from "@modules/vibration-manager";
|
||||||
|
import { GameBar } from "../game-bar/game-bar";
|
||||||
|
|
||||||
|
|
||||||
export function localRedirect(path: string) {
|
export function localRedirect(path: string) {
|
||||||
@ -468,13 +468,14 @@ div[data-testid="media-container"] {
|
|||||||
$elm.textContent = css;
|
$elm.textContent = css;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupBxUi() {
|
export function setupStreamUi() {
|
||||||
// Prevent initializing multiple times
|
// Prevent initializing multiple times
|
||||||
if (!document.querySelector('.bx-quick-settings-bar')) {
|
if (!document.querySelector('.bx-quick-settings-bar')) {
|
||||||
window.addEventListener('resize', updateVideoPlayerCss);
|
window.addEventListener('resize', updateVideoPlayerCss);
|
||||||
setupQuickSettingsBar();
|
setupQuickSettingsBar();
|
||||||
setupScreenshotButton();
|
|
||||||
StreamStats.render();
|
StreamStats.render();
|
||||||
|
|
||||||
|
GameBar.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVideoPlayerCss();
|
updateVideoPlayerCss();
|
||||||
|
@ -27,6 +27,8 @@ export enum BxEvent {
|
|||||||
XCLOUD_SERVERS_READY = 'bx-servers-ready',
|
XCLOUD_SERVERS_READY = 'bx-servers-ready',
|
||||||
|
|
||||||
DATA_CHANNEL_CREATED = 'bx-data-channel-created',
|
DATA_CHANNEL_CREATED = 'bx-data-channel-created',
|
||||||
|
|
||||||
|
GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated',
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace BxEvent {
|
export namespace BxEvent {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { GameBar } from "@modules/game-bar/game-bar";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
import { STATES } from "@utils/global";
|
import { STATES } from "@utils/global";
|
||||||
import { getPref, PrefKey } from "@utils/preferences";
|
import { getPref, PrefKey } from "@utils/preferences";
|
||||||
@ -15,25 +16,12 @@ enum InputType {
|
|||||||
export const BxExposed = {
|
export const BxExposed = {
|
||||||
onPollingModeChanged: (mode: 'All' | 'None') => {
|
onPollingModeChanged: (mode: 'All' | 'None') => {
|
||||||
if (!STATES.isPlaying) {
|
if (!STATES.isPlaying) {
|
||||||
return false;
|
GameBar.disable();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $screenshotBtn = document.querySelector('.bx-screenshot-button');
|
// Toggle Game bar
|
||||||
const $touchControllerBar = document.getElementById('bx-touch-controller-bar');
|
mode !== 'None' ? GameBar.disable() : GameBar.enable();
|
||||||
|
|
||||||
if (mode !== 'None') {
|
|
||||||
// Hide screenshot button
|
|
||||||
$screenshotBtn && $screenshotBtn.classList.add('bx-gone');
|
|
||||||
|
|
||||||
// Hide touch controller bar
|
|
||||||
$touchControllerBar && $touchControllerBar.classList.add('bx-gone');
|
|
||||||
} else {
|
|
||||||
// Show screenshot button
|
|
||||||
$screenshotBtn && $screenshotBtn.classList.remove('bx-gone');
|
|
||||||
|
|
||||||
// Show touch controller bar
|
|
||||||
$touchControllerBar && $touchControllerBar.classList.remove('bx-gone');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getTitleInfo: () => STATES.currentStream.titleInfo,
|
getTitleInfo: () => STATES.currentStream.titleInfo,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import iconCadetRight from "@assets/svg/caret-right.svg" with { type: "text" };
|
||||||
|
import iconCamera from "@assets/svg/camera.svg" with { type: "text" };
|
||||||
import iconController from "@assets/svg/controller.svg" with { type: "text" };
|
import iconController from "@assets/svg/controller.svg" with { type: "text" };
|
||||||
import iconCopy from "@assets/svg/copy.svg" with { type: "text" };
|
import iconCopy from "@assets/svg/copy.svg" with { type: "text" };
|
||||||
import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
|
import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
|
||||||
@ -11,6 +13,8 @@ import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
|
|||||||
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
|
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
|
||||||
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
|
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
|
||||||
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
|
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
|
||||||
|
import iconTouchControlEnable from "@assets/svg/touch-control-enable.svg" with { type: "text" };
|
||||||
|
import iconTouchControlDisable from "@assets/svg/touch-control-disable.svg" with { type: "text" };
|
||||||
|
|
||||||
export const BxIcon = {
|
export const BxIcon = {
|
||||||
STREAM_SETTINGS: iconStreamSettings,
|
STREAM_SETTINGS: iconStreamSettings,
|
||||||
@ -28,5 +32,10 @@ export const BxIcon = {
|
|||||||
|
|
||||||
REMOTE_PLAY: iconRemotePlay,
|
REMOTE_PLAY: iconRemotePlay,
|
||||||
|
|
||||||
|
CARET_RIGHT: iconCadetRight,
|
||||||
|
SCREENSHOT: iconCamera,
|
||||||
|
TOUCH_CONTROL_ENABLE: iconTouchControlEnable,
|
||||||
|
TOUCH_CONTROL_DISABLE: iconTouchControlDisable,
|
||||||
|
|
||||||
// HAND_TAP = '<path d="M6.537 8.906c0-4.216 3.469-7.685 7.685-7.685s7.685 3.469 7.685 7.685M7.719 30.778l-4.333-7.389C3.133 22.944 3 22.44 3 21.928a2.97 2.97 0 0 1 2.956-2.956 2.96 2.96 0 0 1 2.55 1.461l2.761 4.433V8.906a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v8.276a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v2.365a2.97 2.97 0 0 1 2.956-2.956A2.97 2.97 0 0 1 29 19.547v5.32c0 3.547-1.182 5.911-1.182 5.911"/>',
|
// HAND_TAP = '<path d="M6.537 8.906c0-4.216 3.469-7.685 7.685-7.685s7.685 3.469 7.685 7.685M7.719 30.778l-4.333-7.389C3.133 22.944 3 22.44 3 21.928a2.97 2.97 0 0 1 2.956-2.956 2.96 2.96 0 0 1 2.55 1.461l2.761 4.433V8.906a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v8.276a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v2.365a2.97 2.97 0 0 1 2.956-2.956A2.97 2.97 0 0 1 29 19.547v5.32c0 3.547-1.182 5.911-1.182 5.911"/>',
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -438,6 +438,9 @@ class XcloudInterceptor {
|
|||||||
overrides.inputConfiguration = overrides.inputConfiguration || {};
|
overrides.inputConfiguration = overrides.inputConfiguration || {};
|
||||||
overrides.inputConfiguration.enableVibration = true;
|
overrides.inputConfiguration.enableVibration = true;
|
||||||
|
|
||||||
|
overrides.videoConfiguration = overrides.videoConfiguration || {};
|
||||||
|
overrides.videoConfiguration.setCodecPreferences = true;
|
||||||
|
|
||||||
// Enable touch controller
|
// Enable touch controller
|
||||||
if (TouchController.isEnabled()) {
|
if (TouchController.isEnabled()) {
|
||||||
overrides.inputConfiguration.enableTouchInput = true;
|
overrides.inputConfiguration.enableTouchInput = true;
|
||||||
@ -570,7 +573,9 @@ export function interceptHttpRequests() {
|
|||||||
|
|
||||||
const newCustomList = customList.map(item => ({ id: item }));
|
const newCustomList = customList.map(item => ({ id: item }));
|
||||||
obj.push(...newCustomList);
|
obj.push(...newCustomList);
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.json = () => Promise.resolve(obj);
|
response.json = () => Promise.resolve(obj);
|
||||||
|
@ -46,7 +46,6 @@ export enum PrefKey {
|
|||||||
MKB_ABSOLUTE_MOUSE = 'mkb_absolute_mouse',
|
MKB_ABSOLUTE_MOUSE = 'mkb_absolute_mouse',
|
||||||
MKB_DEFAULT_PRESET_ID = 'mkb_default_preset_id',
|
MKB_DEFAULT_PRESET_ID = 'mkb_default_preset_id',
|
||||||
|
|
||||||
SCREENSHOT_BUTTON_POSITION = 'screenshot_button_position',
|
|
||||||
SCREENSHOT_APPLY_FILTERS = 'screenshot_apply_filters',
|
SCREENSHOT_APPLY_FILTERS = 'screenshot_apply_filters',
|
||||||
|
|
||||||
BLOCK_TRACKING = 'block_tracking',
|
BLOCK_TRACKING = 'block_tracking',
|
||||||
@ -227,15 +226,6 @@ export class Preferences {
|
|||||||
default: false,
|
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]: {
|
[PrefKey.SCREENSHOT_APPLY_FILTERS]: {
|
||||||
label: t('screenshot-apply-filters'),
|
label: t('screenshot-apply-filters'),
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -23,7 +23,7 @@ const Texts = {
|
|||||||
"Activate",
|
"Activate",
|
||||||
"Activo",
|
"Activo",
|
||||||
,
|
,
|
||||||
,
|
"Abilita",
|
||||||
"設定する",
|
"設定する",
|
||||||
"활성화",
|
"활성화",
|
||||||
"Aktywuj",
|
"Aktywuj",
|
||||||
@ -40,7 +40,7 @@ const Texts = {
|
|||||||
"Activated",
|
"Activated",
|
||||||
"Activado",
|
"Activado",
|
||||||
,
|
,
|
||||||
,
|
"In uso",
|
||||||
"設定中",
|
"設定中",
|
||||||
"활성화 됨",
|
"활성화 됨",
|
||||||
"Aktywowane",
|
"Aktywowane",
|
||||||
@ -57,7 +57,7 @@ const Texts = {
|
|||||||
"Active",
|
"Active",
|
||||||
"Activo",
|
"Activo",
|
||||||
,
|
,
|
||||||
,
|
"Attivo",
|
||||||
"有効",
|
"有効",
|
||||||
"활성화",
|
"활성화",
|
||||||
"Aktywny",
|
"Aktywny",
|
||||||
@ -91,7 +91,7 @@ const Texts = {
|
|||||||
"Apply",
|
"Apply",
|
||||||
"Aplicar",
|
"Aplicar",
|
||||||
,
|
,
|
||||||
,
|
"Applica",
|
||||||
"適用",
|
"適用",
|
||||||
,
|
,
|
||||||
"Zastosuj",
|
"Zastosuj",
|
||||||
@ -303,7 +303,7 @@ const Texts = {
|
|||||||
"Яркость",
|
"Яркость",
|
||||||
"Aydınlık",
|
"Aydınlık",
|
||||||
"Яскравість",
|
"Яскравість",
|
||||||
"Độ sáng",
|
"Độ sáng",
|
||||||
"亮度",
|
"亮度",
|
||||||
],
|
],
|
||||||
"browser-unsupported-feature": [
|
"browser-unsupported-feature": [
|
||||||
@ -414,7 +414,7 @@ const Texts = {
|
|||||||
"Clear",
|
"Clear",
|
||||||
"Borrar",
|
"Borrar",
|
||||||
,
|
,
|
||||||
,
|
"Pulisci",
|
||||||
"消去",
|
"消去",
|
||||||
"비우기",
|
"비우기",
|
||||||
"Wyczyść",
|
"Wyczyść",
|
||||||
@ -448,7 +448,7 @@ const Texts = {
|
|||||||
"Combine audio & video streams",
|
"Combine audio & video streams",
|
||||||
"Combinar flujos de audio y vídeo",
|
"Combinar flujos de audio y vídeo",
|
||||||
,
|
,
|
||||||
,
|
"Combinare i flussi audio e video",
|
||||||
"音声を映像ストリーミングと統合",
|
"音声を映像ストリーミングと統合",
|
||||||
,
|
,
|
||||||
"Połącz strumienie audio i wideo",
|
"Połącz strumienie audio i wideo",
|
||||||
@ -465,7 +465,7 @@ const Texts = {
|
|||||||
"May fix the laggy audio problem",
|
"May fix the laggy audio problem",
|
||||||
"Puede arreglar el problema de audio con retraso",
|
"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",
|
"Może rozwiązać problem z zacinającym dźwiękiem",
|
||||||
@ -499,7 +499,7 @@ const Texts = {
|
|||||||
"Do you want to delete this preset?",
|
"Do you want to delete this preset?",
|
||||||
"¿Desea eliminar este preajuste?",
|
"¿Desea eliminar este preajuste?",
|
||||||
"Voulez-vous supprimer ce préréglage?",
|
"Voulez-vous supprimer ce préréglage?",
|
||||||
,
|
"Vuoi eliminare questo profilo?",
|
||||||
"このプリセットを削除しますか?",
|
"このプリセットを削除しますか?",
|
||||||
"이 프리셋을 삭제하시겠습니까?",
|
"이 프리셋을 삭제하시겠습니까?",
|
||||||
"Czy na pewno chcesz usunąć ten szablon?",
|
"Czy na pewno chcesz usunąć ten szablon?",
|
||||||
@ -533,7 +533,7 @@ const Texts = {
|
|||||||
"Connected",
|
"Connected",
|
||||||
"Conectado",
|
"Conectado",
|
||||||
,
|
,
|
||||||
,
|
"Connesso",
|
||||||
"接続済み",
|
"接続済み",
|
||||||
,
|
,
|
||||||
"Połączony",
|
"Połączony",
|
||||||
@ -618,7 +618,7 @@ const Texts = {
|
|||||||
"Controller vibration",
|
"Controller vibration",
|
||||||
"Vibración del mando",
|
"Vibración del mando",
|
||||||
,
|
,
|
||||||
,
|
"Vibrazione del controller",
|
||||||
"コントローラーの振動",
|
"コントローラーの振動",
|
||||||
"컨트롤러 진동",
|
"컨트롤러 진동",
|
||||||
"Wibracje kontrolera",
|
"Wibracje kontrolera",
|
||||||
@ -635,7 +635,7 @@ const Texts = {
|
|||||||
"Copy",
|
"Copy",
|
||||||
"Copiar",
|
"Copiar",
|
||||||
,
|
,
|
||||||
,
|
"Duplica",
|
||||||
"コピー",
|
"コピー",
|
||||||
"복사",
|
"복사",
|
||||||
"Kopiuj",
|
"Kopiuj",
|
||||||
@ -669,7 +669,7 @@ const Texts = {
|
|||||||
"Deadzone counterweight",
|
"Deadzone counterweight",
|
||||||
"Contrapeso de la zona muerta",
|
"Contrapeso de la zona muerta",
|
||||||
,
|
,
|
||||||
,
|
"Compensazione della zona morta",
|
||||||
"デッドゾーンのカウンターウエイト",
|
"デッドゾーンのカウンターウエイト",
|
||||||
,
|
,
|
||||||
"Przeciwwaga martwej strefy",
|
"Przeciwwaga martwej strefy",
|
||||||
@ -737,7 +737,7 @@ const Texts = {
|
|||||||
"Device vibration",
|
"Device vibration",
|
||||||
"Vibración del dispositivo",
|
"Vibración del dispositivo",
|
||||||
,
|
,
|
||||||
,
|
"Vibrazione del dispositivo",
|
||||||
"デバイスの振動",
|
"デバイスの振動",
|
||||||
"기기 진동",
|
"기기 진동",
|
||||||
"Wibracje urządzenia",
|
"Wibracje urządzenia",
|
||||||
@ -754,7 +754,7 @@ const Texts = {
|
|||||||
"On when not using gamepad",
|
"On when not using gamepad",
|
||||||
"Activado cuando no se utiliza el mando",
|
"Activado cuando no se utiliza el mando",
|
||||||
,
|
,
|
||||||
,
|
"Abilita quando non si usa un gamepad",
|
||||||
"ゲームパッド未使用時にオン",
|
"ゲームパッド未使用時にオン",
|
||||||
"게임패드를 사용하지 않을 때",
|
"게임패드를 사용하지 않을 때",
|
||||||
"Włączone, gdy nie używasz kontrolera",
|
"Włączone, gdy nie używasz kontrolera",
|
||||||
@ -839,7 +839,7 @@ const Texts = {
|
|||||||
"Disabled",
|
"Disabled",
|
||||||
"Desactivado",
|
"Desactivado",
|
||||||
,
|
,
|
||||||
,
|
"Disattivato",
|
||||||
"無効",
|
"無効",
|
||||||
"비활성화됨",
|
"비활성화됨",
|
||||||
"Wyłączony",
|
"Wyłączony",
|
||||||
@ -856,7 +856,7 @@ const Texts = {
|
|||||||
"Disconnected",
|
"Disconnected",
|
||||||
"Desconectado",
|
"Desconectado",
|
||||||
,
|
,
|
||||||
,
|
"Disconnesso",
|
||||||
"切断",
|
"切断",
|
||||||
,
|
,
|
||||||
"Rozłączony",
|
"Rozłączony",
|
||||||
@ -907,7 +907,7 @@ const Texts = {
|
|||||||
"Enable local co-op support",
|
"Enable local co-op support",
|
||||||
"Habilitar soporte co-op local",
|
"Habilitar soporte co-op local",
|
||||||
,
|
,
|
||||||
,
|
"Abilita supporto cooperativo locale",
|
||||||
"ローカルマルチプレイのサポートを有効化",
|
"ローカルマルチプレイのサポートを有効化",
|
||||||
,
|
,
|
||||||
"Włącz lokalny co-op",
|
"Włącz lokalny co-op",
|
||||||
@ -924,7 +924,7 @@ const Texts = {
|
|||||||
"Only works if the game doesn't require a different profile",
|
"Only works if the game doesn't require a different profile",
|
||||||
"Solo funciona si el juego no requiere un perfil diferente",
|
"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",
|
"Działa tylko wtedy, gdy gra nie wymaga innego profilu",
|
||||||
@ -1026,7 +1026,7 @@ const Texts = {
|
|||||||
"Enabled",
|
"Enabled",
|
||||||
"Activado",
|
"Activado",
|
||||||
,
|
,
|
||||||
,
|
"Attivato",
|
||||||
"有効",
|
"有効",
|
||||||
"활성화됨",
|
"활성화됨",
|
||||||
"Włączony",
|
"Włączony",
|
||||||
@ -1043,7 +1043,7 @@ const Texts = {
|
|||||||
"Experimental",
|
"Experimental",
|
||||||
"Experimental",
|
"Experimental",
|
||||||
,
|
,
|
||||||
,
|
"Sperimentale",
|
||||||
"実験的機能",
|
"実験的機能",
|
||||||
,
|
,
|
||||||
"Eksperymentalne",
|
"Eksperymentalne",
|
||||||
@ -1060,7 +1060,7 @@ const Texts = {
|
|||||||
"Export",
|
"Export",
|
||||||
"Exportar",
|
"Exportar",
|
||||||
,
|
,
|
||||||
,
|
"Esporta",
|
||||||
"エクスポート(書出し)",
|
"エクスポート(書出し)",
|
||||||
"내보내기",
|
"내보내기",
|
||||||
"Eksportuj",
|
"Eksportuj",
|
||||||
@ -1094,7 +1094,7 @@ const Texts = {
|
|||||||
"Allows playing STW mode on mobile",
|
"Allows playing STW mode on mobile",
|
||||||
"Permitir jugar al modo STW en el móvil",
|
"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",
|
"Zezwól na granie w tryb STW na urządzeniu mobilnym",
|
||||||
@ -1111,7 +1111,7 @@ const Texts = {
|
|||||||
"Fortnite: force console version",
|
"Fortnite: force console version",
|
||||||
"Fortnite: forzar versión de consola",
|
"Fortnite: forzar versión de consola",
|
||||||
,
|
,
|
||||||
"Fortnite: Foza la versione console",
|
"Fortnite: forza la versione console",
|
||||||
"Fortnite: 強制的にコンソール版を起動する",
|
"Fortnite: 強制的にコンソール版を起動する",
|
||||||
,
|
,
|
||||||
"Fortnite: wymuś wersję konsolową",
|
"Fortnite: wymuś wersję konsolową",
|
||||||
@ -1145,7 +1145,7 @@ const Texts = {
|
|||||||
"Help",
|
"Help",
|
||||||
"Ayuda",
|
"Ayuda",
|
||||||
,
|
,
|
||||||
,
|
"Guida",
|
||||||
"ヘルプ",
|
"ヘルプ",
|
||||||
,
|
,
|
||||||
"Pomoc",
|
"Pomoc",
|
||||||
@ -1179,7 +1179,7 @@ const Texts = {
|
|||||||
"Hide web page's scrollbar",
|
"Hide web page's scrollbar",
|
||||||
"Oculta la barra de desplazamiento de la página",
|
"Oculta la barra de desplazamiento de la página",
|
||||||
,
|
,
|
||||||
,
|
"Nascondi la barra di scorrimento della pagina web",
|
||||||
"Webページのスクロールバーを隠す",
|
"Webページのスクロールバーを隠す",
|
||||||
,
|
,
|
||||||
"Ukryj pasek przewijania strony",
|
"Ukryj pasek przewijania strony",
|
||||||
@ -1207,13 +1207,30 @@ const Texts = {
|
|||||||
"Ẩn biểu tượng của menu Hệ thống",
|
"Ẩ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": [
|
"horizontal-sensitivity": [
|
||||||
"Horizontale Empfindlichkeit",
|
"Horizontale Empfindlichkeit",
|
||||||
"Sensitifitas horizontal",
|
"Sensitifitas horizontal",
|
||||||
"Horizontal sensitivity",
|
"Horizontal sensitivity",
|
||||||
"Sensibilidad horizontal",
|
"Sensibilidad horizontal",
|
||||||
,
|
,
|
||||||
,
|
"Sensibilità orizzontale",
|
||||||
"左右方向の感度",
|
"左右方向の感度",
|
||||||
,
|
,
|
||||||
"Czułość pozioma",
|
"Czułość pozioma",
|
||||||
@ -1230,7 +1247,7 @@ const Texts = {
|
|||||||
"Import",
|
"Import",
|
||||||
"Importar",
|
"Importar",
|
||||||
,
|
,
|
||||||
,
|
"Importa",
|
||||||
"インポート(読込み)",
|
"インポート(読込み)",
|
||||||
"가져오기",
|
"가져오기",
|
||||||
"Importuj",
|
"Importuj",
|
||||||
@ -1247,7 +1264,7 @@ const Texts = {
|
|||||||
"Install Better xCloud app for Android",
|
"Install Better xCloud app for Android",
|
||||||
"Instale la aplicación Better xCloud para Android",
|
"Instale la aplicación Better xCloud para Android",
|
||||||
,
|
,
|
||||||
,
|
"Installa l'applicazione Better xCloud per Android",
|
||||||
"Android用のBetter xCloudをインストール",
|
"Android用のBetter xCloudをインストール",
|
||||||
,
|
,
|
||||||
"Zainstaluj aplikację Better xCloud na Androida",
|
"Zainstaluj aplikację Better xCloud na Androida",
|
||||||
@ -1264,7 +1281,7 @@ const Texts = {
|
|||||||
"Keyboard shortcuts",
|
"Keyboard shortcuts",
|
||||||
"Atajos del teclado",
|
"Atajos del teclado",
|
||||||
,
|
,
|
||||||
,
|
"Scorciatoie da tastiera",
|
||||||
"キーボードショートカット",
|
"キーボードショートカット",
|
||||||
,
|
,
|
||||||
"Skróty klawiszowe",
|
"Skróty klawiszowe",
|
||||||
@ -1332,7 +1349,7 @@ const Texts = {
|
|||||||
"Left stick",
|
"Left stick",
|
||||||
"Joystick izquierdo",
|
"Joystick izquierdo",
|
||||||
,
|
,
|
||||||
,
|
"Levetta sinistra",
|
||||||
"左スティック",
|
"左スティック",
|
||||||
"왼쪽 스틱",
|
"왼쪽 스틱",
|
||||||
"Lewy drążek analogowy",
|
"Lewy drążek analogowy",
|
||||||
@ -1366,7 +1383,7 @@ const Texts = {
|
|||||||
"Local co-op",
|
"Local co-op",
|
||||||
"Co-op local",
|
"Co-op local",
|
||||||
,
|
,
|
||||||
,
|
"Cooperativa locale",
|
||||||
"ローカルマルチプレイ",
|
"ローカルマルチプレイ",
|
||||||
,
|
,
|
||||||
"Lokalna kooperacja",
|
"Lokalna kooperacja",
|
||||||
@ -1383,7 +1400,7 @@ const Texts = {
|
|||||||
"Map mouse to",
|
"Map mouse to",
|
||||||
"Mapear ratón a",
|
"Mapear ratón a",
|
||||||
,
|
,
|
||||||
,
|
"Usa il mouse come",
|
||||||
"マウスの割り当て",
|
"マウスの割り当て",
|
||||||
,
|
,
|
||||||
"Przypisz myszkę do",
|
"Przypisz myszkę do",
|
||||||
@ -1553,7 +1570,7 @@ const Texts = {
|
|||||||
"Name",
|
"Name",
|
||||||
"Nombre",
|
"Nombre",
|
||||||
,
|
,
|
||||||
,
|
"Nome",
|
||||||
"名前",
|
"名前",
|
||||||
"이름",
|
"이름",
|
||||||
"Nazwa",
|
"Nazwa",
|
||||||
@ -1570,7 +1587,7 @@ const Texts = {
|
|||||||
"New",
|
"New",
|
||||||
"Nuevo",
|
"Nuevo",
|
||||||
,
|
,
|
||||||
,
|
"Nuovo",
|
||||||
"新しい",
|
"新しい",
|
||||||
"새로 만들기",
|
"새로 만들기",
|
||||||
"Nowy",
|
"Nowy",
|
||||||
@ -1808,7 +1825,7 @@ const Texts = {
|
|||||||
"Preset",
|
"Preset",
|
||||||
"Preajuste",
|
"Preajuste",
|
||||||
,
|
,
|
||||||
,
|
"Profilo",
|
||||||
"プリセット",
|
"プリセット",
|
||||||
"프리셋",
|
"프리셋",
|
||||||
"Szablon",
|
"Szablon",
|
||||||
@ -1825,7 +1842,7 @@ const Texts = {
|
|||||||
"Press Esc to cancel",
|
"Press Esc to cancel",
|
||||||
"Presione Esc para cancelar",
|
"Presione Esc para cancelar",
|
||||||
,
|
,
|
||||||
,
|
"Premi Esc per annullare",
|
||||||
"Escを押してキャンセル",
|
"Escを押してキャンセル",
|
||||||
"ESC를 눌러 취소",
|
"ESC를 눌러 취소",
|
||||||
"Naciśnij Esc, aby anulować",
|
"Naciśnij Esc, aby anulować",
|
||||||
@ -1842,7 +1859,7 @@ const Texts = {
|
|||||||
(e: any) => `Press ${e.key} to toggle the Mouse and Keyboard feature`,
|
(e: any) => `Press ${e.key} to toggle the Mouse and Keyboard feature`,
|
||||||
(e: any) => `Pulsa ${e.key} para activar la función de ratón y teclado`,
|
(e: any) => `Pulsa ${e.key} para activar la función de ratón y teclado`,
|
||||||
,
|
,
|
||||||
,
|
(e: any) => `Premi ${e.key} per attivare o disattivare la funzione Mouse e Tastiera`,
|
||||||
(e: any) => `${e.key} キーでマウスとキーボードの機能を切り替える`,
|
(e: any) => `${e.key} キーでマウスとキーボードの機能を切り替える`,
|
||||||
(e: any) => `${e.key} 키를 눌러 마우스와 키보드 기능을 활성화 하십시오`,
|
(e: any) => `${e.key} 키를 눌러 마우스와 키보드 기능을 활성화 하십시오`,
|
||||||
(e: any) => `Naciśnij ${e.key}, aby przełączyć funkcję myszy i klawiatury`,
|
(e: any) => `Naciśnij ${e.key}, aby przełączyć funkcję myszy i klawiatury`,
|
||||||
@ -1859,7 +1876,7 @@ const Texts = {
|
|||||||
"Press a key or do a mouse click to bind...",
|
"Press a key or do a mouse click to bind...",
|
||||||
"Presione una tecla o haga un clic del ratón para enlazar...",
|
"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ć...",
|
"Naciśnij klawisz lub kliknij myszą, aby przypisać...",
|
||||||
@ -1876,7 +1893,7 @@ const Texts = {
|
|||||||
"Preset's name:",
|
"Preset's name:",
|
||||||
"Nombre del preajuste:",
|
"Nombre del preajuste:",
|
||||||
,
|
,
|
||||||
,
|
"Nome del profilo:",
|
||||||
"プリセット名:",
|
"プリセット名:",
|
||||||
"프리셋 이름:",
|
"프리셋 이름:",
|
||||||
"Nazwa szablonu:",
|
"Nazwa szablonu:",
|
||||||
@ -1961,7 +1978,7 @@ const Texts = {
|
|||||||
"Rename",
|
"Rename",
|
||||||
"Renombrar",
|
"Renombrar",
|
||||||
,
|
,
|
||||||
,
|
"Rinomina",
|
||||||
"名前変更",
|
"名前変更",
|
||||||
"이름 바꾸기",
|
"이름 바꾸기",
|
||||||
"Zmień nazwę",
|
"Zmień nazwę",
|
||||||
@ -1978,7 +1995,7 @@ const Texts = {
|
|||||||
"Right-click on a key to unbind it",
|
"Right-click on a key to unbind it",
|
||||||
"Clic derecho en una tecla para desvincularla",
|
"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",
|
"Kliknij prawym przyciskiem myszy na klawisz, aby anulować przypisanie",
|
||||||
@ -1995,7 +2012,7 @@ const Texts = {
|
|||||||
"Right stick",
|
"Right stick",
|
||||||
"Joystick derecho",
|
"Joystick derecho",
|
||||||
,
|
,
|
||||||
,
|
"Levetta destra",
|
||||||
"右スティック",
|
"右スティック",
|
||||||
"오른쪽 스틱",
|
"오른쪽 스틱",
|
||||||
"Prawy drążek analogowy",
|
"Prawy drążek analogowy",
|
||||||
@ -2114,7 +2131,7 @@ const Texts = {
|
|||||||
"Save",
|
"Save",
|
||||||
"Guardar",
|
"Guardar",
|
||||||
,
|
,
|
||||||
,
|
"Conferma",
|
||||||
"保存",
|
"保存",
|
||||||
"저장",
|
"저장",
|
||||||
"Zapisz",
|
"Zapisz",
|
||||||
@ -2131,7 +2148,7 @@ const Texts = {
|
|||||||
"Applies video filters to screenshots",
|
"Applies video filters to screenshots",
|
||||||
"Aplica filtros de vídeo a las capturas de pantalla",
|
"Aplica filtros de vídeo a las capturas de pantalla",
|
||||||
,
|
,
|
||||||
,
|
"Applica filtri video agli screenshot",
|
||||||
"スクリーンショットにビデオフィルターを適用",
|
"スクリーンショットにビデオフィルターを適用",
|
||||||
,
|
,
|
||||||
"Stosuje filtry wideo do zrzutów ekranu",
|
"Stosuje filtry wideo do zrzutów ekranu",
|
||||||
@ -2165,7 +2182,7 @@ const Texts = {
|
|||||||
"Separate Touch controller & Controller #1",
|
"Separate Touch controller & Controller #1",
|
||||||
"Separar controlador táctil y controlador #1",
|
"Separar controlador táctil y controlador #1",
|
||||||
,
|
,
|
||||||
,
|
"Controller su schermo e Controller #1 separati",
|
||||||
"タッチコントローラーとコントローラー#1を分ける",
|
"タッチコントローラーとコントローラー#1を分ける",
|
||||||
,
|
,
|
||||||
"Oddziel Kontroler dotykowy i Kontroler #1",
|
"Oddziel Kontroler dotykowy i Kontroler #1",
|
||||||
@ -2182,7 +2199,7 @@ const Texts = {
|
|||||||
"Touch controller is Player 1, Controller #1 is Player 2",
|
"Touch controller is Player 1, Controller #1 is Player 2",
|
||||||
"El controlador táctil es Jugador 1, Controlador #1 es Jugador 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に割り当てられます",
|
"タッチコントローラーがプレイヤー1、コントローラー#1がプレイヤー2に割り当てられます",
|
||||||
,
|
,
|
||||||
"Kontroler dotykowy to Gracz 1, Kontroler #1 to Gracz 2",
|
"Kontroler dotykowy to Gracz 1, Kontroler #1 to Gracz 2",
|
||||||
@ -2250,7 +2267,7 @@ const Texts = {
|
|||||||
"Shortcut keys",
|
"Shortcut keys",
|
||||||
"Teclas de atajo",
|
"Teclas de atajo",
|
||||||
,
|
,
|
||||||
,
|
"Tasti di scelta rapida",
|
||||||
"ショートカットキー",
|
"ショートカットキー",
|
||||||
,
|
,
|
||||||
"Skróty klawiszowe",
|
"Skróty klawiszowe",
|
||||||
@ -2295,6 +2312,23 @@ const Texts = {
|
|||||||
"Hiển thị thông số khi vào game",
|
"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": [
|
"show-wait-time": [
|
||||||
"Geschätzte Wartezeit anzeigen",
|
"Geschätzte Wartezeit anzeigen",
|
||||||
"Tampilkan waktu antrian",
|
"Tampilkan waktu antrian",
|
||||||
@ -2556,7 +2590,7 @@ const Texts = {
|
|||||||
"Stick decay minimum",
|
"Stick decay minimum",
|
||||||
"Disminuir mínimamente el analógico",
|
"Disminuir mínimamente el analógico",
|
||||||
,
|
,
|
||||||
,
|
"Tempo minimo di rilascio dello stick",
|
||||||
"スティックの減衰の最小値",
|
"スティックの減衰の最小値",
|
||||||
,
|
,
|
||||||
"Minimalne opóźnienie drążka",
|
"Minimalne opóźnienie drążka",
|
||||||
@ -2573,7 +2607,7 @@ const Texts = {
|
|||||||
"Stick decay strength",
|
"Stick decay strength",
|
||||||
"Intensidad de decaimiento del analógico",
|
"Intensidad de decaimiento del analógico",
|
||||||
,
|
,
|
||||||
,
|
"Velocità di rilascio dello stick",
|
||||||
"スティックの減衰の強さ",
|
"スティックの減衰の強さ",
|
||||||
,
|
,
|
||||||
"Siła opóźnienia drążka",
|
"Siła opóźnienia drążka",
|
||||||
@ -2624,7 +2658,7 @@ const Texts = {
|
|||||||
"Support Better xCloud",
|
"Support Better xCloud",
|
||||||
"Apoyar a Better xCloud",
|
"Apoyar a Better xCloud",
|
||||||
,
|
,
|
||||||
,
|
"Sostieni Better xCloud",
|
||||||
"Better xCloudをサポート",
|
"Better xCloudをサポート",
|
||||||
,
|
,
|
||||||
"Wesprzyj Better xCloud",
|
"Wesprzyj Better xCloud",
|
||||||
@ -2652,6 +2686,23 @@ const Texts = {
|
|||||||
"Hoán đổi nút",
|
"Hoán đổi nút",
|
||||||
"交换按钮",
|
"交换按钮",
|
||||||
],
|
],
|
||||||
|
"take-screenshot": [
|
||||||
|
"Screenshot aufnehmen",
|
||||||
|
,
|
||||||
|
"Take screenshot",
|
||||||
|
"Capturar pantalla",
|
||||||
|
,
|
||||||
|
,
|
||||||
|
"スクリーンショットを撮影",
|
||||||
|
,
|
||||||
|
,
|
||||||
|
,
|
||||||
|
"Сделать снимок экрана",
|
||||||
|
,
|
||||||
|
"Зробити знімок екрану",
|
||||||
|
"Lưu ảnh màn hình",
|
||||||
|
,
|
||||||
|
],
|
||||||
"target-resolution": [
|
"target-resolution": [
|
||||||
"Festgelegte Auflösung",
|
"Festgelegte Auflösung",
|
||||||
"Resolusi",
|
"Resolusi",
|
||||||
@ -2709,7 +2760,7 @@ const Texts = {
|
|||||||
"Off when controller found",
|
"Off when controller found",
|
||||||
"Desactivar cuando se encuentra el controlador",
|
"Desactivar cuando se encuentra el controlador",
|
||||||
,
|
,
|
||||||
,
|
"Disabilitata quando un controllor viene rilevato",
|
||||||
"コントローラー接続時に無効化",
|
"コントローラー接続時に無効化",
|
||||||
,
|
,
|
||||||
"Wyłącz, gdy kontroler zostanie znaleziony",
|
"Wyłącz, gdy kontroler zostanie znaleziony",
|
||||||
@ -2756,11 +2807,11 @@ const Texts = {
|
|||||||
],
|
],
|
||||||
"tc-default-opacity": [
|
"tc-default-opacity": [
|
||||||
"Standard Deckkraft",
|
"Standard Deckkraft",
|
||||||
,
|
"Opasitas bawaan",
|
||||||
"Default opacity",
|
"Default opacity",
|
||||||
"Opacidad por defecto",
|
"Opacidad por defecto",
|
||||||
,
|
,
|
||||||
,
|
"Opacità predefinita",
|
||||||
"既定の透過度",
|
"既定の透過度",
|
||||||
,
|
,
|
||||||
"Domyślna przezroczystość",
|
"Domyślna przezroczystość",
|
||||||
@ -2894,14 +2945,14 @@ const Texts = {
|
|||||||
(e: any) => `Touch-Steuerungslayout von ${e.name}`,
|
(e: any) => `Touch-Steuerungslayout von ${e.name}`,
|
||||||
,
|
,
|
||||||
(e: any) => `Touch control layout by ${e.name}`,
|
(e: any) => `Touch control layout by ${e.name}`,
|
||||||
|
(e: any) => `Disposición del control táctil por ${e.nombre}`,
|
||||||
,
|
,
|
||||||
,
|
(e: any) => `Configurazione dei comandi su schermo creata da ${e.name}`,
|
||||||
,
|
|
||||||
(e: any) => `タッチ操作レイアウト作成者: ${e.name}`,
|
(e: any) => `タッチ操作レイアウト作成者: ${e.name}`,
|
||||||
,
|
,
|
||||||
(e: any) => `Układ sterowania dotykowego stworzony przez ${e.name}`,
|
(e: any) => `Układ sterowania dotykowego stworzony przez ${e.name}`,
|
||||||
,
|
,
|
||||||
,
|
(e: any) => `Сенсорная раскладка по ${e.name}`,
|
||||||
(e: any) => `${e.name} kişisinin dokunmatik kontrolcü tuş şeması`,
|
(e: any) => `${e.name} kişisinin dokunmatik kontrolcü tuş şeması`,
|
||||||
(e: any) => `Розташування сенсорного керування від ${e.name}`,
|
(e: any) => `Розташування сенсорного керування від ${e.name}`,
|
||||||
(e: any) => `Bố cục điều khiển cảm ứng tạo bởi ${e.name}`,
|
(e: any) => `Bố cục điều khiển cảm ứng tạo bởi ${e.name}`,
|
||||||
@ -3015,7 +3066,7 @@ const Texts = {
|
|||||||
"Use mouse's absolute position",
|
"Use mouse's absolute position",
|
||||||
"Usar la posición absoluta del ratón",
|
"Usar la posición absoluta del ratón",
|
||||||
,
|
,
|
||||||
,
|
"Usa la posizione assoluta del mouse",
|
||||||
"マウスの絶対座標を使用",
|
"マウスの絶対座標を使用",
|
||||||
"마우스 절대위치 사용",
|
"마우스 절대위치 사용",
|
||||||
"Użyj pozycji bezwzględnej myszy",
|
"Użyj pozycji bezwzględnej myszy",
|
||||||
@ -3066,7 +3117,7 @@ const Texts = {
|
|||||||
"Vibration intensity",
|
"Vibration intensity",
|
||||||
"Intensidad de la vibración",
|
"Intensidad de la vibración",
|
||||||
,
|
,
|
||||||
,
|
"Intensità della vibrazione",
|
||||||
"振動の強さ",
|
"振動の強さ",
|
||||||
"진동 세기",
|
"진동 세기",
|
||||||
"Siła wibracji",
|
"Siła wibracji",
|
||||||
@ -3083,7 +3134,7 @@ const Texts = {
|
|||||||
"Vibration",
|
"Vibration",
|
||||||
"Vibración",
|
"Vibración",
|
||||||
,
|
,
|
||||||
,
|
"Vibrazione",
|
||||||
"振動",
|
"振動",
|
||||||
,
|
,
|
||||||
"Wibracje",
|
"Wibracje",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user