mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-08 15:21:43 +02:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f5f7b9d2e | |||
d04742bc25 | |||
ed871bbe83 | |||
dca8ab9cf6 | |||
1bf2f41813 | |||
0fb3b7b7f7 | |||
7709cceff0 | |||
f8b8012f5c | |||
1d8517a997 | |||
c893bb2a5d | |||
46469e3949 | |||
d8a085d43f | |||
b84c464066 | |||
f0549b388a | |||
9c3b1bd908 | |||
d671be21ee | |||
11aefb34d1 | |||
597cc9782d | |||
61cfd3f8db | |||
a3d5d6a819 | |||
ca64b592c5 | |||
d0a8b894b9 | |||
3230b99a05 | |||
f0e4d4b8d0 | |||
d0b84d4591 | |||
d292bef5e7 | |||
5381575048 | |||
7206c9e8bc | |||
5fb0dec9f2 | |||
4ffc034076 | |||
b11d465804 | |||
e1ba2344b7 | |||
8c446ceec3 | |||
7438375356 | |||
741bc9a4e5 | |||
de7bf3edc7 | |||
79ebb1a817 | |||
160044c958 | |||
78c70b5d90 | |||
9044a07c0b | |||
e40d258c79 | |||
3864457a07 | |||
da362325f2 | |||
4062852904 | |||
c426f64ea9 | |||
f7266d6361 | |||
4bd96de89e | |||
4011eb402a | |||
557a38214d | |||
4648126f03 | |||
07b2e47757 | |||
cf4609d87b | |||
1ca2b771e7 | |||
fe98a1165f | |||
4777f90a53 | |||
1ea1afe4d4 |
588
dist/better-xcloud.lite.user.js
vendored
588
dist/better-xcloud.lite.user.js
vendored
File diff suppressed because one or more lines are too long
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 6.0.0
|
// @version 6.0.4
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
935
dist/better-xcloud.user.js
vendored
935
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -7,11 +7,11 @@
|
|||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-xds-platform=tv] & {
|
body[data-media-type=tv] & {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
html:not([data-xds-platform=tv]) & {
|
body:not([data-media-type=tv]) & {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
> button:first-of-type {
|
> button:first-of-type {
|
||||||
@ -34,7 +34,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
||||||
html[data-xds-platform=tv] & {
|
body[data-media-type=tv] & {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@ -42,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html:not([data-xds-platform=tv]) & {
|
body:not([data-media-type=tv]) & {
|
||||||
button {
|
button {
|
||||||
span {
|
span {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
margin-right: -50%;
|
margin-right: -50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
min-width: 420px;
|
min-width: 420px;
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
z-index: var(--bx-key-binding-dialog-z-index);
|
z-index: var(--bx-key-binding-dialog-z-index);
|
||||||
background: #1a1b1e;
|
background: #1a1b1e;
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
width: 450px;
|
width: 450px;
|
||||||
max-width: calc(100vw - 20px);
|
max-width: calc(100vw - 20px);
|
||||||
margin: 0 0 0 auto;
|
margin: 0 0 0 auto;
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
|
|
||||||
max-height: 95vh;
|
max-height: 95vh;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -90,6 +90,13 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bx-default-preset-note {
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bx-centered-dialog,
|
.bx-centered-dialog,
|
||||||
@ -214,5 +221,6 @@
|
|||||||
|
|
||||||
.bx-settings-row {
|
.bx-settings-row {
|
||||||
background: none;
|
background: none;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
width: 420px;
|
width: 420px;
|
||||||
max-width: calc(100vw - 20px);
|
max-width: calc(100vw - 20px);
|
||||||
margin: 0 0 0 auto;
|
margin: 0 0 0 auto;
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
|
|
||||||
> .bx-button {
|
> .bx-button {
|
||||||
display: table;
|
display: table;
|
||||||
|
@ -140,8 +140,28 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
|
|||||||
font-family: var(--bx-normal-font) !important;
|
font-family: var(--bx-normal-font) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
select[multiple] {
|
select[multiple], select[multiple]:focus {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
option {
|
||||||
|
padding: 4px 6px;
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
color = #1a7bc0;
|
||||||
|
background: color linear-gradient(0deg, color 0%, color 100%);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '☑️';
|
||||||
|
font-size: 12px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6px;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide UI elements */
|
/* Hide UI elements */
|
||||||
|
@ -324,18 +324,22 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #003861;
|
background: #003861;
|
||||||
|
height: 45px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
label {
|
label {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 10px;
|
align-content: center;
|
||||||
|
padding: 0 10px;
|
||||||
background: #004f87;
|
background: #004f87;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 40px;
|
width: 45px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
|||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-transparent=true] {
|
&[data-shadow=true] {
|
||||||
background: none;
|
background: none;
|
||||||
filter: drop-shadow(1px 0 0 #000000f0) drop-shadow(-1px 0 0 #000000f0) drop-shadow(0 1px 0 #000000f0) drop-shadow(0 -1px 0 #000000f0);
|
filter: drop-shadow(1px 0 0 #000000f0) drop-shadow(-1px 0 0 #000000f0) drop-shadow(0 1px 0 #000000f0) drop-shadow(0 -1px 0 #000000f0);
|
||||||
}
|
}
|
||||||
@ -165,10 +165,10 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
min-width: 60px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
white-space: pre;
|
||||||
|
|
||||||
&[data-grade=good] {
|
&[data-grade=good] {
|
||||||
color: #6bffff;
|
color: #6bffff;
|
||||||
@ -181,9 +181,5 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
|||||||
&[data-grade=bad] {
|
&[data-grade=bad] {
|
||||||
color: #ff5f5f;
|
color: #ff5f5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
min-width: 22px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,41 @@ body[data-media-type=tv] .bx-stream-home-button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div[data-testid=media-container] {
|
div[data-testid=media-container] {
|
||||||
display: flex;
|
&[data-position=center] {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-position=top] {
|
||||||
|
video, canvas {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-position=bottom] {
|
||||||
|
video, canvas {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#game-stream {
|
||||||
|
video {
|
||||||
|
margin: auto;
|
||||||
|
align-self: center;
|
||||||
|
background: #000;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
align-self: center;
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.bx-taking-screenshot:before {
|
&.bx-taking-screenshot:before {
|
||||||
animation: bx-anim-taking-screenshot 0.5s ease;
|
animation: bx-anim-taking-screenshot 0.5s ease;
|
||||||
@ -59,21 +93,6 @@ div[data-testid=media-container] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#game-stream video {
|
|
||||||
margin: auto;
|
|
||||||
align-self: center;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#game-stream canvas {
|
|
||||||
position: absolute;
|
|
||||||
align-self: center;
|
|
||||||
margin: auto;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#gamepass-dialog-root div[class^=Guide-module__guide] {
|
#gamepass-dialog-root div[class^=Guide-module__guide] {
|
||||||
.bx-button {
|
.bx-button {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
@ -62,6 +62,7 @@ div.bx-select {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
min-height: 15px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: block;
|
display: block;
|
||||||
@ -70,6 +71,8 @@ div.bx-select {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
min-height: 15px;
|
||||||
|
align-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ export const enum PrefKey {
|
|||||||
CONTROLLER_POLLING_RATE = 'controller.pollingRate',
|
CONTROLLER_POLLING_RATE = 'controller.pollingRate',
|
||||||
|
|
||||||
NATIVE_MKB_MODE = 'nativeMkb.mode',
|
NATIVE_MKB_MODE = 'nativeMkb.mode',
|
||||||
FORCE_NATIVE_MKB_GAMES = 'nativeMkb.forcedGames',
|
NATIVE_MKB_FORCED_GAMES = 'nativeMkb.forcedGames',
|
||||||
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityX',
|
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityX',
|
||||||
NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityY',
|
NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityY',
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ export const enum PrefKey {
|
|||||||
SCREENSHOT_APPLY_FILTERS = 'screenshot.applyFilters',
|
SCREENSHOT_APPLY_FILTERS = 'screenshot.applyFilters',
|
||||||
|
|
||||||
BLOCK_TRACKING = 'block.tracking',
|
BLOCK_TRACKING = 'block.tracking',
|
||||||
BLOCK_SOCIAL_FEATURES = 'block.social',
|
BLOCK_FEATURES = 'block.features',
|
||||||
|
|
||||||
LOADING_SCREEN_GAME_ART = 'loadingScreen.gameArt.show',
|
LOADING_SCREEN_GAME_ART = 'loadingScreen.gameArt.show',
|
||||||
LOADING_SCREEN_SHOW_WAIT_TIME = 'loadingScreen.waitTime.show',
|
LOADING_SCREEN_SHOW_WAIT_TIME = 'loadingScreen.waitTime.show',
|
||||||
@ -73,7 +73,6 @@ export const enum PrefKey {
|
|||||||
UI_LAYOUT = 'ui.layout',
|
UI_LAYOUT = 'ui.layout',
|
||||||
UI_SCROLLBAR_HIDE = 'ui.hideScrollbar',
|
UI_SCROLLBAR_HIDE = 'ui.hideScrollbar',
|
||||||
UI_HIDE_SECTIONS = 'ui.hideSections',
|
UI_HIDE_SECTIONS = 'ui.hideSections',
|
||||||
BYOG_DISABLED = 'feature.byog.disabled',
|
|
||||||
|
|
||||||
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui.gameCard.waitTime.show',
|
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui.gameCard.waitTime.show',
|
||||||
UI_SIMPLIFY_STREAM_MENU = 'ui.streamMenu.simplify',
|
UI_SIMPLIFY_STREAM_MENU = 'ui.streamMenu.simplify',
|
||||||
@ -93,6 +92,7 @@ export const enum PrefKey {
|
|||||||
VIDEO_BRIGHTNESS = 'video.brightness',
|
VIDEO_BRIGHTNESS = 'video.brightness',
|
||||||
VIDEO_CONTRAST = 'video.contrast',
|
VIDEO_CONTRAST = 'video.contrast',
|
||||||
VIDEO_SATURATION = 'video.saturation',
|
VIDEO_SATURATION = 'video.saturation',
|
||||||
|
VIDEO_POSITION = 'video.position',
|
||||||
|
|
||||||
AUDIO_MIC_ON_PLAYING = 'audio.mic.onPlaying',
|
AUDIO_MIC_ON_PLAYING = 'audio.mic.onPlaying',
|
||||||
AUDIO_VOLUME_CONTROL_ENABLED = 'audio.volume.booster.enabled',
|
AUDIO_VOLUME_CONTROL_ENABLED = 'audio.volume.booster.enabled',
|
||||||
@ -103,8 +103,8 @@ export const enum PrefKey {
|
|||||||
STATS_QUICK_GLANCE_ENABLED = 'stats.quickGlance.enabled',
|
STATS_QUICK_GLANCE_ENABLED = 'stats.quickGlance.enabled',
|
||||||
STATS_POSITION = 'stats.position',
|
STATS_POSITION = 'stats.position',
|
||||||
STATS_TEXT_SIZE = 'stats.textSize',
|
STATS_TEXT_SIZE = 'stats.textSize',
|
||||||
STATS_TRANSPARENT = 'stats.transparent',
|
STATS_OPACITY_ALL = 'stats.opacity.all',
|
||||||
STATS_OPACITY = 'stats.opacity',
|
STATS_OPACITY_BACKGROUND = 'stats.opacity.background',
|
||||||
STATS_CONDITIONAL_FORMATTING = 'stats.colors',
|
STATS_CONDITIONAL_FORMATTING = 'stats.colors',
|
||||||
|
|
||||||
REMOTE_PLAY_ENABLED = 'xhome.enabled',
|
REMOTE_PLAY_ENABLED = 'xhome.enabled',
|
||||||
|
@ -93,6 +93,14 @@ export const enum VideoRatio {
|
|||||||
FILL = 'fill',
|
FILL = 'fill',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum VideoPosition {
|
||||||
|
CENTER = 'center',
|
||||||
|
TOP = 'top',
|
||||||
|
TOP_HALF = 'top-half',
|
||||||
|
BOTTOM = 'bottom',
|
||||||
|
BOTTOM_HALF = 'bottom-half',
|
||||||
|
}
|
||||||
|
|
||||||
export const enum StreamPlayerType {
|
export const enum StreamPlayerType {
|
||||||
VIDEO = 'default',
|
VIDEO = 'default',
|
||||||
WEBGL2 = 'webgl2',
|
WEBGL2 = 'webgl2',
|
||||||
@ -102,3 +110,11 @@ export const enum StreamVideoProcessing {
|
|||||||
USM = 'usm',
|
USM = 'usm',
|
||||||
CAS = 'cas',
|
CAS = 'cas',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum BlockFeature {
|
||||||
|
CHAT = 'chat',
|
||||||
|
FRIENDS = 'friends',
|
||||||
|
BYOG = 'byog',
|
||||||
|
NOTIFICATIONS_INVITES = 'notifications-invites',
|
||||||
|
NOTIFICATIONS_ACHIEVEMENTS = 'notifications-achievements',
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export const enum ShortcutAction {
|
export const enum ShortcutAction {
|
||||||
BETTER_XCLOUD_SETTINGS_SHOW = 'bx.settings.show',
|
BETTER_XCLOUD_SETTINGS_SHOW = 'bx.settings.show',
|
||||||
|
|
||||||
|
CONTROLLER_XBOX_BUTTON_PRESS = 'controller.xbox.press',
|
||||||
|
|
||||||
STREAM_VIDEO_TOGGLE = 'stream.video.toggle',
|
STREAM_VIDEO_TOGGLE = 'stream.video.toggle',
|
||||||
STREAM_SCREENSHOT_CAPTURE = 'stream.screenshot.capture',
|
STREAM_SCREENSHOT_CAPTURE = 'stream.screenshot.capture',
|
||||||
|
|
||||||
|
80
src/index.ts
80
src/index.ts
@ -1,4 +1,4 @@
|
|||||||
import { compressCss, isFullVersion } from "@macros/build" with {type: "macro"};
|
import { compressCss, isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import "@utils/global";
|
import "@utils/global";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
@ -16,7 +16,7 @@ import { LoadingScreen } from "@modules/loading-screen";
|
|||||||
import { MouseCursorHider } from "@modules/mkb/mouse-cursor-hider";
|
import { MouseCursorHider } from "@modules/mkb/mouse-cursor-hider";
|
||||||
import { TouchController } from "@modules/touch-controller";
|
import { TouchController } from "@modules/touch-controller";
|
||||||
import { checkForUpdate, disablePwa, productTitleToSlug } from "@utils/utils";
|
import { checkForUpdate, disablePwa, productTitleToSlug } from "@utils/utils";
|
||||||
import { Patcher } from "@modules/patcher";
|
import { Patcher } from "@/modules/patcher/patcher";
|
||||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||||
import { onHistoryChanged, patchHistoryMethod } from "@utils/history";
|
import { onHistoryChanged, patchHistoryMethod } from "@utils/history";
|
||||||
import { disableAdobeAudienceManager, patchAudioContext, patchCanvasContext, patchMeControl, patchPointerLockApi, patchRtcCodecs, patchRtcPeerConnection, patchVideoApi } from "@utils/monkey-patches";
|
import { disableAdobeAudienceManager, patchAudioContext, patchCanvasContext, patchMeControl, patchPointerLockApi, patchRtcCodecs, patchRtcPeerConnection, patchVideoApi } from "@utils/monkey-patches";
|
||||||
@ -27,7 +27,7 @@ import { ScreenshotManager } from "./utils/screenshot-manager";
|
|||||||
import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
|
import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
|
||||||
import { GuideMenu } from "./modules/ui/guide-menu";
|
import { GuideMenu } from "./modules/ui/guide-menu";
|
||||||
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
||||||
import { NativeMkbMode, TouchControllerMode, UiSection } from "./enums/pref-values";
|
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiSection } from "./enums/pref-values";
|
||||||
import { HeaderSection } from "./modules/ui/header";
|
import { HeaderSection } from "./modules/ui/header";
|
||||||
import { GameTile } from "./modules/ui/game-tile";
|
import { GameTile } from "./modules/ui/game-tile";
|
||||||
import { ProductDetailsPage } from "./modules/ui/product-details";
|
import { ProductDetailsPage } from "./modules/ui/product-details";
|
||||||
@ -44,6 +44,7 @@ import { StreamSettings } from "./utils/stream-settings";
|
|||||||
import { KeyboardShortcutHandler } from "./modules/mkb/keyboard-shortcut-handler";
|
import { KeyboardShortcutHandler } from "./modules/mkb/keyboard-shortcut-handler";
|
||||||
import { GhPagesUtils } from "./utils/gh-pages";
|
import { GhPagesUtils } from "./utils/gh-pages";
|
||||||
import { DeviceVibrationManager } from "./modules/device-vibration-manager";
|
import { DeviceVibrationManager } from "./modules/device-vibration-manager";
|
||||||
|
import { BxEventBus } from "./utils/bx-event-bus";
|
||||||
|
|
||||||
// Handle login page
|
// Handle login page
|
||||||
if (window.location.pathname.includes('/auth/msa')) {
|
if (window.location.pathname.includes('/auth/msa')) {
|
||||||
@ -159,7 +160,7 @@ document.addEventListener('readystatechange', e => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATES.isSignedIn = !!((window as any).xbcUser?.isSignedIn);
|
STATES.isSignedIn = !!window.xbcUser?.isSignedIn;
|
||||||
|
|
||||||
if (STATES.isSignedIn) {
|
if (STATES.isSignedIn) {
|
||||||
// Preload Remote Play
|
// Preload Remote Play
|
||||||
@ -170,7 +171,7 @@ document.addEventListener('readystatechange', e => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide "Play with Friends" skeleton section
|
// Hide "Play with Friends" skeleton section
|
||||||
if (getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS)) {
|
if (getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) || getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
|
||||||
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest<HTMLElement>('div[class*=HomePage-module]');
|
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest<HTMLElement>('div[class*=HomePage-module]');
|
||||||
$parent && ($parent.style.display = 'none');
|
$parent && ($parent.style.display = 'none');
|
||||||
}
|
}
|
||||||
@ -190,7 +191,7 @@ window.addEventListener('popstate', onHistoryChanged);
|
|||||||
window.history.pushState = patchHistoryMethod('pushState');
|
window.history.pushState = patchHistoryMethod('pushState');
|
||||||
window.history.replaceState = patchHistoryMethod('replaceState');
|
window.history.replaceState = patchHistoryMethod('replaceState');
|
||||||
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => {
|
BxEventBus.Script.once('xcloudServerUnavailable', () => {
|
||||||
STATES.supportedRegion = false;
|
STATES.supportedRegion = false;
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||||
|
|
||||||
@ -199,14 +200,14 @@ window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => {
|
|||||||
if ($unsupportedPage) {
|
if ($unsupportedPage) {
|
||||||
SettingsDialog.getInstance().show();
|
SettingsDialog.getInstance().show();
|
||||||
}
|
}
|
||||||
}, {once: true});
|
});
|
||||||
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_SERVERS_READY, e => {
|
BxEventBus.Script.on('xcloud.server.ready', () => {
|
||||||
STATES.isSignedIn = true;
|
STATES.isSignedIn = true;
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(BxEvent.STREAM_LOADING, e => {
|
BxEventBus.Stream.on('state.loading', () => {
|
||||||
// Get title ID for screenshot's name
|
// Get title ID for screenshot's name
|
||||||
if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) {
|
if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) {
|
||||||
STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title);
|
STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title);
|
||||||
@ -216,9 +217,9 @@ window.addEventListener(BxEvent.STREAM_LOADING, e => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Setup loading screen
|
// Setup loading screen
|
||||||
getPref(PrefKey.LOADING_SCREEN_GAME_ART) && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup);
|
getPref(PrefKey.LOADING_SCREEN_GAME_ART) && BxEventBus.Script.on('titleInfo.ready', LoadingScreen.setup);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.STREAM_STARTING, e => {
|
BxEventBus.Stream.on('state.starting', () => {
|
||||||
// Hide loading screen
|
// Hide loading screen
|
||||||
LoadingScreen.hide();
|
LoadingScreen.hide();
|
||||||
|
|
||||||
@ -232,7 +233,7 @@ window.addEventListener(BxEvent.STREAM_STARTING, e => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
BxEventBus.Stream.on('state.playing', payload => {
|
||||||
window.BX_STREAM_SETTINGS = StreamSettings.settings;
|
window.BX_STREAM_SETTINGS = StreamSettings.settings;
|
||||||
StreamSettings.refreshAllSettings();
|
StreamSettings.refreshAllSettings();
|
||||||
|
|
||||||
@ -251,7 +252,7 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
|||||||
KeyboardShortcutHandler.getInstance().start();
|
KeyboardShortcutHandler.getInstance().start();
|
||||||
|
|
||||||
// Setup screenshot
|
// Setup screenshot
|
||||||
const $video = (e as any).$video as HTMLVideoElement;
|
const $video = payload.$video as HTMLVideoElement;
|
||||||
ScreenshotManager.getInstance().updateCanvasSize($video.videoWidth, $video.videoHeight);
|
ScreenshotManager.getInstance().updateCanvasSize($video.videoWidth, $video.videoHeight);
|
||||||
|
|
||||||
// Setup local co-op
|
// Setup local co-op
|
||||||
@ -262,21 +263,21 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
|||||||
updateVideoPlayer();
|
updateVideoPlayer();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
|
BxEventBus.Stream.on('state.error', () => {
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
BxEventBus.Stream.emit('state.stopped', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
||||||
const component = (e as any).component;
|
const component = (e as any).component;
|
||||||
if (component === 'product-details') {
|
if (component === 'product-detail') {
|
||||||
ProductDetailsPage.injectButtons();
|
ProductDetailsPage.injectButtons();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Detect game change
|
// Detect game change
|
||||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||||
const dataChannel = (e as any).dataChannel;
|
const { dataChannel } = payload;
|
||||||
if (!dataChannel || dataChannel.label !== 'message') {
|
if (dataChannel?.label !== 'message') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,26 +286,29 @@ window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get xboxTitleId from message
|
if (!msg.data.includes('/titleinfo')) {
|
||||||
if (msg.data.includes('/titleinfo')) {
|
return;
|
||||||
const json = JSON.parse(JSON.parse(msg.data).content);
|
}
|
||||||
const xboxTitleId = parseInt(json.titleid, 16);
|
|
||||||
STATES.currentStream.xboxTitleId = xboxTitleId;
|
|
||||||
|
|
||||||
// Get titleSlug for Remote Play
|
// Get xboxTitleId from message
|
||||||
if (STATES.remotePlay.isPlaying) {
|
const json = JSON.parse(JSON.parse(msg.data).content);
|
||||||
STATES.currentStream.titleSlug = 'remote-play';
|
const xboxTitleId = parseInt(json.titleid, 16);
|
||||||
if (json.focused) {
|
STATES.currentStream.xboxTitleId = xboxTitleId;
|
||||||
const productTitle = await XboxApi.getProductTitle(xboxTitleId);
|
|
||||||
if (productTitle) {
|
// Get titleSlug for Remote Play
|
||||||
STATES.currentStream.titleSlug = productTitleToSlug(productTitle);
|
if (STATES.remotePlay.isPlaying) {
|
||||||
}
|
STATES.currentStream.titleSlug = 'remote-play';
|
||||||
|
if (json.focused) {
|
||||||
|
const productTitle = await XboxApi.getProductTitle(xboxTitleId);
|
||||||
|
if (productTitle) {
|
||||||
|
STATES.currentStream.titleSlug = productTitleToSlug(productTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function unload() {
|
function unload() {
|
||||||
if (!STATES.isPlaying) {
|
if (!STATES.isPlaying) {
|
||||||
return;
|
return;
|
||||||
@ -340,9 +344,9 @@ function unload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener(BxEvent.STREAM_STOPPED, unload);
|
BxEventBus.Stream.on('state.stopped', unload);
|
||||||
window.addEventListener('pagehide', e => {
|
window.addEventListener('pagehide', e => {
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
BxEventBus.Stream.emit('state.stopped', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
||||||
@ -353,8 +357,8 @@ isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
|||||||
function main() {
|
function main() {
|
||||||
GhPagesUtils.fetchLatestCommit();
|
GhPagesUtils.fetchLatestCommit();
|
||||||
|
|
||||||
if (getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON) {
|
if (getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) !== NativeMkbMode.OFF) {
|
||||||
const customList = getPref<string[]>(PrefKey.FORCE_NATIVE_MKB_GAMES);
|
const customList = getPref<string[]>(PrefKey.NATIVE_MKB_FORCED_GAMES);
|
||||||
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,7 +410,7 @@ function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start PointerProviderServer
|
// Start PointerProviderServer
|
||||||
if (getPref(PrefKey.MKB_ENABLED) && AppInterface) {
|
if (AppInterface && (getPref(PrefKey.MKB_ENABLED) || getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) {
|
||||||
STATES.pointerServerPort = AppInterface.startPointerServer() || 9269;
|
STATES.pointerServerPort = AppInterface.startPointerServer() || 9269;
|
||||||
BxLogger.info('startPointerServer', 'Port', STATES.pointerServerPort.toString());
|
BxLogger.info('startPointerServer', 'Port', STATES.pointerServerPort.toString());
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import { ShortcutHandler } from "@/utils/shortcut-handler";
|
|||||||
|
|
||||||
|
|
||||||
export class ControllerShortcut {
|
export class ControllerShortcut {
|
||||||
private static buttonsCache: {[key: string]: boolean[]} = {};
|
private static buttonsCache: { [key: string]: boolean[] } = {};
|
||||||
private static buttonsStatus: {[key: string]: boolean[]} = {};
|
private static buttonsStatus: { [key: string]: boolean[] } = {};
|
||||||
|
|
||||||
static reset(index: number) {
|
static reset(index: number) {
|
||||||
ControllerShortcut.buttonsCache[index] = [];
|
ControllerShortcut.buttonsCache[index] = [];
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AppInterface, STATES } from "@utils/global";
|
import { AppInterface, STATES } from "@utils/global";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
|
||||||
import { StreamSettings } from "@/utils/stream-settings";
|
import { StreamSettings } from "@/utils/stream-settings";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
const VIBRATION_DATA_MAP = {
|
const VIBRATION_DATA_MAP = {
|
||||||
gamepadIndex: 8,
|
gamepadIndex: 8,
|
||||||
@ -37,8 +37,8 @@ export class DeviceVibrationManager {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.boundOnMessage = this.onMessage.bind(this);
|
this.boundOnMessage = this.onMessage.bind(this);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||||
const dataChannel = (e as any).dataChannel as RTCDataChannel;
|
const { dataChannel } = payload;
|
||||||
if (dataChannel?.label === 'input') {
|
if (dataChannel?.label === 'input') {
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|
||||||
@ -47,9 +47,7 @@ export class DeviceVibrationManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener(BxEvent.DEVICE_VIBRATION_CHANGED, e => {
|
BxEventBus.Script.on('deviceVibration.updated', () => this.setupDataChannel());
|
||||||
this.setupDataChannel();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupDataChannel() {
|
private setupDataChannel() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
export abstract class BaseGameBarAction {
|
export abstract class BaseGameBarAction {
|
||||||
abstract $content: HTMLElement;
|
abstract $content: HTMLElement;
|
||||||
@ -7,7 +7,7 @@ export abstract class BaseGameBarAction {
|
|||||||
reset() {}
|
reset() {}
|
||||||
|
|
||||||
onClick(e: Event) {
|
onClick(e: Event) {
|
||||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
BxEventBus.Stream.emit('gameBar.activated', {});
|
||||||
};
|
};
|
||||||
|
|
||||||
render(): HTMLElement {
|
render(): HTMLElement {
|
||||||
|
@ -13,6 +13,7 @@ import { SpeakerAction } from "./speaker-action";
|
|||||||
import { RendererAction } from "./renderer-action";
|
import { RendererAction } from "./renderer-action";
|
||||||
import { BxLogger } from "@/utils/bx-logger";
|
import { BxLogger } from "@/utils/bx-logger";
|
||||||
import { GameBarPosition, TouchControllerMode } from "@/enums/pref-values";
|
import { GameBarPosition, TouchControllerMode } from "@/enums/pref-values";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
export class GameBar {
|
export class GameBar {
|
||||||
@ -47,8 +48,8 @@ export class GameBar {
|
|||||||
|
|
||||||
const position = getPref<GameBarPosition>(PrefKey.GAME_BAR_POSITION);
|
const position = getPref<GameBarPosition>(PrefKey.GAME_BAR_POSITION);
|
||||||
|
|
||||||
const $gameBar = CE('div', {id: 'bx-game-bar', class: 'bx-gone', 'data-position': position},
|
const $gameBar = CE('div', { id: 'bx-game-bar', class: 'bx-gone', 'data-position': position },
|
||||||
$container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
|
$container = CE('div', { class: 'bx-game-bar-container bx-offscreen' }),
|
||||||
createSvgIcon(position === 'bottom-left' ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT),
|
createSvgIcon(position === 'bottom-left' ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ export class GameBar {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Hide game bar after clicking on an action
|
// Hide game bar after clicking on an action
|
||||||
window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, this.hideBar);
|
BxEventBus.Stream.on('gameBar.activated', this.hideBar);
|
||||||
|
|
||||||
$container.addEventListener('pointerover', this.clearHideTimeout);
|
$container.addEventListener('pointerover', this.clearHideTimeout);
|
||||||
$container.addEventListener('pointerout', this.beginHideTimeout);
|
$container.addEventListener('pointerout', this.beginHideTimeout);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { BxEvent } from "@utils/bx-event";
|
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
import { BaseGameBarAction } from "./base-action";
|
import { BaseGameBarAction } from "./base-action";
|
||||||
import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/microphone-shortcut";
|
import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/microphone-shortcut";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
export class MicrophoneAction extends BaseGameBarAction {
|
export class MicrophoneAction extends BaseGameBarAction {
|
||||||
@ -26,9 +26,8 @@ export class MicrophoneAction extends BaseGameBarAction {
|
|||||||
|
|
||||||
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, e => {
|
BxEventBus.Stream.on('microphone.state.changed', payload => {
|
||||||
const microphoneState = (e as any).microphoneState;
|
const enabled = payload.state === MicrophoneState.ENABLED;
|
||||||
const enabled = microphoneState === MicrophoneState.ENABLED;
|
|
||||||
this.$content.dataset.activated = enabled.toString();
|
this.$content.dataset.activated = enabled.toString();
|
||||||
|
|
||||||
// Show the button in Game Bar if the mic is enabled
|
// Show the button in Game Bar if the mic is enabled
|
||||||
|
@ -2,7 +2,7 @@ import { BxIcon } from "@utils/bx-icon";
|
|||||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
import { BaseGameBarAction } from "./base-action";
|
import { BaseGameBarAction } from "./base-action";
|
||||||
import { RendererShortcut } from "../shortcuts/renderer-shortcut";
|
import { RendererShortcut } from "../shortcuts/renderer-shortcut";
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
export class RendererAction extends BaseGameBarAction {
|
export class RendererAction extends BaseGameBarAction {
|
||||||
@ -26,9 +26,8 @@ export class RendererAction extends BaseGameBarAction {
|
|||||||
|
|
||||||
this.$content = CE('div', {}, $btnDefault, $btnActivated);
|
this.$content = CE('div', {}, $btnDefault, $btnActivated);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.VIDEO_VISIBILITY_CHANGED, e => {
|
BxEventBus.Stream.on('video.visibility.changed', payload => {
|
||||||
const isShowing = (e as any).isShowing;
|
this.$content.dataset.activated = (!payload.isVisible).toString();
|
||||||
this.$content.dataset.activated = (!isShowing).toString();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { BxEvent } from "@utils/bx-event";
|
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||||
import { BaseGameBarAction } from "./base-action";
|
import { BaseGameBarAction } from "./base-action";
|
||||||
import { SoundShortcut, SpeakerState } from "../shortcuts/sound-shortcut";
|
import { SoundShortcut, SpeakerState } from "../shortcuts/sound-shortcut";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
export class SpeakerAction extends BaseGameBarAction {
|
export class SpeakerAction extends BaseGameBarAction {
|
||||||
@ -26,10 +26,8 @@ export class SpeakerAction extends BaseGameBarAction {
|
|||||||
|
|
||||||
this.$content = CE('div', {}, $btnEnable, $btnMuted);
|
this.$content = CE('div', {}, $btnEnable, $btnMuted);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, e => {
|
BxEventBus.Stream.on('speaker.state.changed', payload => {
|
||||||
const speakerState = (e as any).speakerState;
|
const enabled = payload.state === SpeakerState.ENABLED;
|
||||||
const enabled = speakerState === SpeakerState.ENABLED;
|
|
||||||
|
|
||||||
this.$content.dataset.activated = (!enabled).toString();
|
this.$content.dataset.activated = (!enabled).toString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { t } from "@utils/translation";
|
|||||||
import { STATES } from "@utils/global";
|
import { STATES } from "@utils/global";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
import { compressCss } from "@macros/build" with {type: "macro"};
|
import { compressCss } from "@macros/build" with { type: "macro" };
|
||||||
import { LoadingScreenRocket } from "@/enums/pref-values";
|
import { LoadingScreenRocket } from "@/enums/pref-values";
|
||||||
|
|
||||||
export class LoadingScreen {
|
export class LoadingScreen {
|
||||||
|
@ -95,7 +95,7 @@ export class KeyHelper {
|
|||||||
const tmp = str.split(':');
|
const tmp = str.split(':');
|
||||||
|
|
||||||
const code = tmp[0] as KeyEventInfo['code'];
|
const code = tmp[0] as KeyEventInfo['code'];
|
||||||
const modifiers = parseInt(tmp[1]);
|
const modifiers = parseInt(tmp[1] as string);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { MkbPresetKey, MouseConstant, MouseMapTo, WheelCode } from "@/enums/mkb";
|
import { MkbPresetKey, MouseConstant, MouseMapTo, WheelCode } from "@/enums/mkb";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
@ -16,6 +16,9 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
|||||||
import { GamepadKey, GamepadStick } from "@/enums/gamepad";
|
import { GamepadKey, GamepadStick } from "@/enums/gamepad";
|
||||||
import { MkbPopup } from "./mkb-popup";
|
import { MkbPopup } from "./mkb-popup";
|
||||||
import type { MkbConvertedPresetData } from "@/types/presets";
|
import type { MkbConvertedPresetData } from "@/types/presets";
|
||||||
|
import { StreamSettings } from "@/utils/stream-settings";
|
||||||
|
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
const PointerToMouseButton = {
|
const PointerToMouseButton = {
|
||||||
1: 0,
|
1: 0,
|
||||||
@ -168,7 +171,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
private popup: MkbPopup;
|
private popup: MkbPopup;
|
||||||
|
|
||||||
private STICK_MAP: {[key in GamepadKey]?: [GamepadKey[], number, number]} = {
|
private STICK_MAP: { [key in GamepadKey]?: [GamepadKey[], number, number] } = {
|
||||||
[GamepadKey.LS_LEFT]: [this.LEFT_STICK_X, 0, -1],
|
[GamepadKey.LS_LEFT]: [this.LEFT_STICK_X, 0, -1],
|
||||||
[GamepadKey.LS_RIGHT]: [this.LEFT_STICK_X, 0, 1],
|
[GamepadKey.LS_RIGHT]: [this.LEFT_STICK_X, 0, 1],
|
||||||
[GamepadKey.LS_UP]: [this.LEFT_STICK_Y, 1, -1],
|
[GamepadKey.LS_UP]: [this.LEFT_STICK_Y, 1, -1],
|
||||||
@ -515,7 +518,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
window.addEventListener('keyup', this.onKeyboardEvent);
|
window.addEventListener('keyup', this.onKeyboardEvent);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
||||||
window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.onDialogShown);
|
BxEventBus.Script.on('dialog.shown', this.onDialogShown);
|
||||||
|
|
||||||
if (AppInterface) {
|
if (AppInterface) {
|
||||||
// Android app doesn't support PointerLock API so we need to use a different method
|
// Android app doesn't support PointerLock API so we need to use a different method
|
||||||
@ -529,7 +532,12 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
MkbPopup.getInstance().reset();
|
MkbPopup.getInstance().reset();
|
||||||
|
|
||||||
if (AppInterface) {
|
if (AppInterface) {
|
||||||
Toast.show(t('press-key-to-toggle-mkb', {key: `<b>F8</b>`}), t('virtual-controller'), {html: true});
|
const shortcutKey = StreamSettings.findKeyboardShortcut(ShortcutAction.MKB_TOGGLE);
|
||||||
|
if (shortcutKey) {
|
||||||
|
const msg = t('press-key-to-toggle-mkb', { key: `<b>${KeyHelper.codeToKeyName(shortcutKey)}</b>` });
|
||||||
|
Toast.show(msg, t('native-mkb'), { html: true });
|
||||||
|
}
|
||||||
|
|
||||||
this.waitForMouseData(false);
|
this.waitForMouseData(false);
|
||||||
} else {
|
} else {
|
||||||
this.waitForMouseData(true);
|
this.waitForMouseData(true);
|
||||||
@ -561,7 +569,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
||||||
window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.onDialogShown);
|
BxEventBus.Script.off('dialog.shown', this.onDialogShown);
|
||||||
|
|
||||||
this.mouseDataProvider?.destroy();
|
this.mouseDataProvider?.destroy();
|
||||||
|
|
||||||
@ -627,12 +635,12 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
this.waitForMouseData(true);
|
this.waitForMouseData(true);
|
||||||
this.mouseDataProvider?.stop();
|
this.mouseDataProvider?.stop();
|
||||||
|
|
||||||
// Toast.show(t('virtual-controller'), t('disabled'), {instant: true});
|
// Toast.show(t('virtual-controller'), t('disabled'), { instant: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
static setupEvents() {
|
static setupEvents() {
|
||||||
if (isFullVersion()) {
|
if (isFullVersion()) {
|
||||||
window.addEventListener(BxEvent.STREAM_PLAYING, () => {
|
BxEventBus.Stream.on('state.playing', () => {
|
||||||
if (STATES.currentStream.titleInfo?.details.hasMkbSupport) {
|
if (STATES.currentStream.titleInfo?.details.hasMkbSupport) {
|
||||||
// Enable native MKB in Android app
|
// Enable native MKB in Android app
|
||||||
NativeMkbHandler.getInstance()?.init();
|
NativeMkbHandler.getInstance()?.init();
|
||||||
@ -642,7 +650,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (EmulatedMkbHandler.isAllowed()) {
|
if (EmulatedMkbHandler.isAllowed()) {
|
||||||
window.addEventListener(BxEvent.MKB_UPDATED, () => {
|
BxEventBus.Script.on('mkb.setting.updated', () => {
|
||||||
EmulatedMkbHandler.getInstance()?.refreshPresetData();
|
EmulatedMkbHandler.getInstance()?.refreshPresetData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { CE, createButton, ButtonStyle, type BxButtonOptions } from "@/utils/html";
|
import { CE, createButton, ButtonStyle, type BxButtonOptions } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
|
||||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||||
import { SettingsDialog } from "../ui/dialog/settings-dialog";
|
import { SettingsDialog } from "../ui/dialog/settings-dialog";
|
||||||
import type { MkbHandler } from "./base-mkb-handler";
|
import type { MkbHandler } from "./base-mkb-handler";
|
||||||
import { NativeMkbHandler } from "./native-mkb-handler";
|
import { NativeMkbHandler } from "./native-mkb-handler";
|
||||||
import { StreamSettings } from "@/utils/stream-settings";
|
import { StreamSettings } from "@/utils/stream-settings";
|
||||||
import { KeyHelper } from "./key-helper";
|
import { KeyHelper } from "./key-helper";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
type MkbPopupType = 'virtual' | 'native';
|
type MkbPopupType = 'virtual' | 'native';
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export class MkbPopup {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
window.addEventListener(BxEvent.KEYBOARD_SHORTCUTS_UPDATED, e => {
|
BxEventBus.Script.on('keyboardShortcuts.updated', () => {
|
||||||
const $newButton = this.createActivateButton();
|
const $newButton = this.createActivateButton();
|
||||||
this.$btnActivate.replaceWith($newButton);
|
this.$btnActivate.replaceWith($newButton);
|
||||||
this.$btnActivate = $newButton;
|
this.$btnActivate = $newButton;
|
||||||
|
@ -12,6 +12,7 @@ import { KeyHelper } from "./key-helper";
|
|||||||
import { StreamSettings } from "@/utils/stream-settings";
|
import { StreamSettings } from "@/utils/stream-settings";
|
||||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||||
import { NativeMkbMode } from "@/enums/pref-values";
|
import { NativeMkbMode } from "@/enums/pref-values";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
type NativeMouseData = {
|
type NativeMouseData = {
|
||||||
X: number,
|
X: number,
|
||||||
@ -100,10 +101,6 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
this.onKeyboardEvent(event as KeyboardEvent);
|
this.onKeyboardEvent(event as KeyboardEvent);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BxEvent.XCLOUD_DIALOG_SHOWN:
|
|
||||||
this.onDialogShown();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BxEvent.POINTER_LOCK_REQUESTED:
|
case BxEvent.POINTER_LOCK_REQUESTED:
|
||||||
this.onPointerLockRequested(event);
|
this.onPointerLockRequested(event);
|
||||||
break;
|
break;
|
||||||
@ -135,10 +132,10 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
window.addEventListener('keyup', this);
|
window.addEventListener('keyup', this);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this);
|
|
||||||
window.addEventListener(BxEvent.POINTER_LOCK_REQUESTED, this);
|
window.addEventListener(BxEvent.POINTER_LOCK_REQUESTED, this);
|
||||||
window.addEventListener(BxEvent.POINTER_LOCK_EXITED, this);
|
window.addEventListener(BxEvent.POINTER_LOCK_EXITED, this);
|
||||||
window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this);
|
window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this);
|
||||||
|
BxEventBus.Script.on('dialog.shown', this.onDialogShown);
|
||||||
|
|
||||||
const shortcutKey = StreamSettings.findKeyboardShortcut(ShortcutAction.MKB_TOGGLE);
|
const shortcutKey = StreamSettings.findKeyboardShortcut(ShortcutAction.MKB_TOGGLE);
|
||||||
if (shortcutKey) {
|
if (shortcutKey) {
|
||||||
@ -182,7 +179,7 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
window.BX_EXPOSED.stopTakRendering = true;
|
window.BX_EXPOSED.stopTakRendering = true;
|
||||||
this.waitForMouseData(false);
|
this.waitForMouseData(false);
|
||||||
|
|
||||||
Toast.show(t('native-mkb'), t('enabled'), {instant: true});
|
Toast.show(t('native-mkb'), t('enabled'), { instant: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
@ -195,14 +192,17 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.pointerClient?.stop();
|
this.pointerClient?.stop();
|
||||||
|
this.stop();
|
||||||
|
|
||||||
window.removeEventListener('keyup', this);
|
window.removeEventListener('keyup', this);
|
||||||
|
|
||||||
window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this);
|
|
||||||
window.removeEventListener(BxEvent.POINTER_LOCK_REQUESTED, this);
|
window.removeEventListener(BxEvent.POINTER_LOCK_REQUESTED, this);
|
||||||
window.removeEventListener(BxEvent.POINTER_LOCK_EXITED, this);
|
window.removeEventListener(BxEvent.POINTER_LOCK_EXITED, this);
|
||||||
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this);
|
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this);
|
||||||
|
BxEventBus.Script.off('dialog.shown', this.onDialogShown);
|
||||||
|
|
||||||
this.waitForMouseData(false);
|
this.waitForMouseData(false);
|
||||||
|
document.pointerLockElement && document.exitPointerLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove(data: MkbMouseMove): void {
|
handleMouseMove(data: MkbMouseMove): void {
|
||||||
|
45
src/modules/patcher/patcher-utils.ts
Normal file
45
src/modules/patcher/patcher-utils.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import type { PatchArray, PatchName, PatchPage } from "./patcher";
|
||||||
|
|
||||||
|
export class PatcherUtils {
|
||||||
|
static indexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
|
||||||
|
const index = txt.indexOf(searchString, startIndex);
|
||||||
|
if (index < 0 || (maxRange && index - startIndex > maxRange)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
|
||||||
|
const index = txt.lastIndexOf(searchString, startIndex);
|
||||||
|
if (index < 0 || (maxRange && startIndex - index > maxRange)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static insertAt(txt: string, index: number, insertString: string): string {
|
||||||
|
return txt.substring(0, index) + insertString + txt.substring(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static replaceWith(txt: string, index: number, fromString: string, toString: string): string {
|
||||||
|
return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static filterPatches(patches: Array<string | false>): PatchArray {
|
||||||
|
return patches.filter((item): item is PatchName => !!item);
|
||||||
|
}
|
||||||
|
|
||||||
|
static patchBeforePageLoad(str: string, page: PatchPage): string | false {
|
||||||
|
let text = `chunkName:()=>"${page}-page",`;
|
||||||
|
if (!str.includes(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace('requireAsync(e){', `requireAsync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
|
||||||
|
str = str.replace('requireSync(e){', `requireSync(e){window.BX_EXPOSED.beforePageLoad("${page}");`);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
@ -1,55 +1,26 @@
|
|||||||
import { SCRIPT_VERSION, STATES } from "@utils/global";
|
import { AppInterface, SCRIPT_VERSION, STATES } from "@utils/global";
|
||||||
import { BX_FLAGS } from "@utils/bx-flags";
|
import { BX_FLAGS } from "@utils/bx-flags";
|
||||||
import { BxLogger } from "@utils/bx-logger";
|
import { BxLogger } from "@utils/bx-logger";
|
||||||
import { hashCode, renderString } from "@utils/utils";
|
import { blockSomeNotifications, hashCode, renderString } from "@utils/utils";
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
|
|
||||||
import codeControllerShortcuts from "./patches/controller-shortcuts.js" with { type: "text" };
|
import codeControllerShortcuts from "./patches/controller-shortcuts.js" with { type: "text" };
|
||||||
import codeExposeStreamSession from "./patches/expose-stream-session.js" with { type: "text" };
|
import codeExposeStreamSession from "./patches/expose-stream-session.js" with { type: "text" };
|
||||||
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
|
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
|
||||||
import codeSetCurrentlyFocusedInteractable from "./patches/set-currently-focused-interactable.js" with { type: "text" };
|
|
||||||
import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type: "text" };
|
import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type: "text" };
|
||||||
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
||||||
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
||||||
import { FeatureGates } from "@/utils/feature-gates.js";
|
|
||||||
import { PrefKey, StorageKey } from "@/enums/pref-keys.js";
|
import { PrefKey, StorageKey } from "@/enums/pref-keys.js";
|
||||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
|
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
import { NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values";
|
import { BlockFeature, NativeMkbMode, TouchControllerMode, UiLayout, UiSection } from "@/enums/pref-values";
|
||||||
|
import { PatcherUtils } from "./patcher-utils.js";
|
||||||
|
|
||||||
type PathName = keyof typeof PATCHES;
|
export type PatchName = keyof typeof PATCHES;
|
||||||
type PatchArray = PathName[];
|
export type PatchArray = PatchName[];
|
||||||
|
export type PatchPage = 'home' | 'stream' | 'product-detail';
|
||||||
|
|
||||||
class PatcherUtils {
|
|
||||||
static indexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
|
|
||||||
const index = txt.indexOf(searchString, startIndex);
|
|
||||||
if (index < 0 || (maxRange && index - startIndex > maxRange)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
|
|
||||||
const index = txt.lastIndexOf(searchString, startIndex);
|
|
||||||
if (index < 0 || (maxRange && startIndex - index > maxRange)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
static insertAt(txt: string, index: number, insertString: string): string {
|
|
||||||
return txt.substring(0, index) + insertString + txt.substring(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
static replaceWith(txt: string, index: number, fromString: string, toString: string): string {
|
|
||||||
return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ENDING_CHUNKS_PATCH_NAME = 'loadingEndingChunks';
|
|
||||||
const LOG_TAG = 'Patcher';
|
const LOG_TAG = 'Patcher';
|
||||||
|
|
||||||
const PATCHES = {
|
const PATCHES = {
|
||||||
@ -272,25 +243,6 @@ logFunc(logTag, '//', logMessage);
|
|||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Override website's settings
|
|
||||||
overrideSettings(str: string) {
|
|
||||||
const index = str.indexOf(',EnableStreamGate:');
|
|
||||||
if (index < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the next "},"
|
|
||||||
const endIndex = str.indexOf('},', index);
|
|
||||||
|
|
||||||
let newSettings = JSON.stringify(FeatureGates);
|
|
||||||
newSettings = newSettings.substring(1, newSettings.length - 1);
|
|
||||||
|
|
||||||
const newCode = newSettings;
|
|
||||||
|
|
||||||
str = str.substring(0, endIndex) + ',' + newCode + str.substring(endIndex);
|
|
||||||
return str;
|
|
||||||
},
|
|
||||||
|
|
||||||
disableGamepadDisconnectedScreen(str: string) {
|
disableGamepadDisconnectedScreen(str: string) {
|
||||||
const index = str.indexOf('"GamepadDisconnected_Title",');
|
const index = str.indexOf('"GamepadDisconnected_Title",');
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
@ -314,19 +266,6 @@ logFunc(logTag, '//', logMessage);
|
|||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add patches that are only needed when start playing
|
|
||||||
loadingEndingChunks(str: string) {
|
|
||||||
let text = '"FamilySagaManager"';
|
|
||||||
if (!str.includes(text)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
BxLogger.info(LOG_TAG, 'Remaining patches:', PATCH_ORDERS);
|
|
||||||
PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS);
|
|
||||||
|
|
||||||
return str;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Disable StreamGate
|
// Disable StreamGate
|
||||||
disableStreamGate(str: string) {
|
disableStreamGate(str: string) {
|
||||||
const index = str.indexOf('case"partially-ready":');
|
const index = str.indexOf('case"partially-ready":');
|
||||||
@ -520,6 +459,10 @@ BxEvent.dispatch(window, BxEvent.XCLOUD_POLLING_MODE_CHANGED);
|
|||||||
|
|
||||||
// Get param name
|
// Get param name
|
||||||
const params = str.substring(index, backetIndex).match(/\(([^)]+)\)/)![1];
|
const params = str.substring(index, backetIndex).match(/\(([^)]+)\)/)![1];
|
||||||
|
if (!params) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const titleInfoVar = params.split(',')[0];
|
const titleInfoVar = params.split(',')[0];
|
||||||
|
|
||||||
const newCode = `
|
const newCode = `
|
||||||
@ -542,6 +485,10 @@ BxLogger.info('patchXcloudTitleInfo', ${titleInfoVar});
|
|||||||
|
|
||||||
// Get param name
|
// Get param name
|
||||||
const params = str.substring(index, backetIndex).match(/\(([^)]+)\)/)![1];
|
const params = str.substring(index, backetIndex).match(/\(([^)]+)\)/)![1];
|
||||||
|
if (!params) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const configsVar = params.split(',')[1];
|
const configsVar = params.split(',')[1];
|
||||||
|
|
||||||
const newCode = `
|
const newCode = `
|
||||||
@ -719,30 +666,6 @@ true` + text;
|
|||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
|
||||||
(x.AW, {
|
|
||||||
path: V.LoginDeviceCode.path,
|
|
||||||
exact: !0,
|
|
||||||
render: () => (0, n.jsx)(qe, {
|
|
||||||
children: (0, n.jsx)(Et.R, {})
|
|
||||||
})
|
|
||||||
}, V.LoginDeviceCode.name),
|
|
||||||
|
|
||||||
const qe = e => {
|
|
||||||
let {
|
|
||||||
children: t
|
|
||||||
} = e;
|
|
||||||
const {
|
|
||||||
isTV: a,
|
|
||||||
isSupportedTVBrowser: r
|
|
||||||
} = (0, T.d)();
|
|
||||||
return a && r ? (0, n.jsx)(n.Fragment, {
|
|
||||||
children: t
|
|
||||||
}) : (0, n.jsx)(x.l_, {
|
|
||||||
to: V.Home.getLink()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
enableTvRoutes(str: string) {
|
enableTvRoutes(str: string) {
|
||||||
let index = str.indexOf('.LoginDeviceCode.path,');
|
let index = str.indexOf('.LoginDeviceCode.path,');
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
@ -771,6 +694,18 @@ true` + text;
|
|||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Don't render News section
|
||||||
|
ignoreNewsSection(str: string) {
|
||||||
|
let index = str.indexOf('Logger("CarouselRow")');
|
||||||
|
index > -1 && (index = PatcherUtils.lastIndexOf(str, 'const ', index, 200));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = PatcherUtils.insertAt(str, index, 'return null;');
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
// Don't render "Play With Friends" sections
|
// Don't render "Play With Friends" sections
|
||||||
ignorePlayWithFriendsSection(str: string) {
|
ignorePlayWithFriendsSection(str: string) {
|
||||||
let index = str.indexOf('location:"PlayWithFriendsRow",');
|
let index = str.indexOf('location:"PlayWithFriendsRow",');
|
||||||
@ -908,20 +843,19 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
index = str.indexOf('{', index) + 1;
|
index = str.indexOf('{', index) + 1;
|
||||||
str = str.substring(0, index) + codeSetCurrentlyFocusedInteractable + str.substring(index);
|
str = PatcherUtils.insertAt(str, index, 'e && BxEvent.dispatch(window, BxEvent.NAVIGATION_FOCUS_CHANGED, { element: e });');
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
// product-details-page.js#2388, 24.17.20
|
detectProductDetailPage(str: string) {
|
||||||
detectProductDetailsPage(str: string) {
|
|
||||||
let index = str.indexOf('{location:"ProductDetailPage",');
|
let index = str.indexOf('{location:"ProductDetailPage",');
|
||||||
index >= 0 && (index = PatcherUtils.lastIndexOf('return', str, index, 200));
|
index >= 0 && (index = PatcherUtils.lastIndexOf(str, 'return', index, 200));
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, { component: "product-details" });' + str.substring(index);
|
str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, { component: "product-detail" });' + str.substring(index);
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -992,12 +926,71 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
|||||||
str = str.replace(text, '=window.BX_EXPOSED.modifyPreloadedState(window.__PRELOADED_STATE__);');
|
str = str.replace(text, '=window.BX_EXPOSED.modifyPreloadedState(window.__PRELOADED_STATE__);');
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
homePageBeforeLoad(str: string) {
|
||||||
|
return PatcherUtils.patchBeforePageLoad(str, 'home');
|
||||||
|
},
|
||||||
|
|
||||||
|
productDetailPageBeforeLoad(str: string) {
|
||||||
|
return PatcherUtils.patchBeforePageLoad(str, 'product-detail');
|
||||||
|
},
|
||||||
|
|
||||||
|
streamPageBeforeLoad(str: string) {
|
||||||
|
return PatcherUtils.patchBeforePageLoad(str, 'stream');
|
||||||
|
},
|
||||||
|
|
||||||
|
disableAbsoluteMouse(str: string) {
|
||||||
|
let text = 'sendAbsoluteMouseCapableMessage(e){';
|
||||||
|
if (!str.includes(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace(text, text + 'return;');
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
changeNotificationsSubscription(str: string) {
|
||||||
|
let text = ';buildSubscriptionQueryParamsForNotifications(';
|
||||||
|
let index = str.indexOf(text);
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += text.length;
|
||||||
|
// Get parameter name
|
||||||
|
const subsVar = str[index];
|
||||||
|
|
||||||
|
// Find index after {
|
||||||
|
index = str.indexOf('{', index) + 1;
|
||||||
|
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||||
|
const filters = [];
|
||||||
|
if (blockFeatures.includes(BlockFeature.NOTIFICATIONS_INVITES)) {
|
||||||
|
filters.push('GameInvite', 'PartyInvite');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockFeatures.includes(BlockFeature.FRIENDS)) {
|
||||||
|
filters.push('Follower');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockFeatures.includes(BlockFeature.NOTIFICATIONS_ACHIEVEMENTS)) {
|
||||||
|
filters.push('AchievementUnlock');
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCode = `
|
||||||
|
let subs = ${subsVar};
|
||||||
|
subs = subs.filter(val => !${JSON.stringify(filters)}.includes(val));
|
||||||
|
${subsVar} = subs;
|
||||||
|
`;
|
||||||
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let PATCH_ORDERS: PatchArray = [
|
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
...(getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
...(AppInterface && getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
||||||
'enableNativeMkb',
|
'enableNativeMkb',
|
||||||
'exposeInputSink',
|
'exposeInputSink',
|
||||||
|
'disableAbsoluteMouse',
|
||||||
] : []),
|
] : []),
|
||||||
|
|
||||||
'modifyPreloadedState',
|
'modifyPreloadedState',
|
||||||
@ -1008,17 +1001,19 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
'patchRequestInfoCrash',
|
'patchRequestInfoCrash',
|
||||||
|
|
||||||
'disableStreamGate',
|
'disableStreamGate',
|
||||||
'overrideSettings',
|
|
||||||
'broadcastPollingMode',
|
'broadcastPollingMode',
|
||||||
'patchGamepadPolling',
|
'patchGamepadPolling',
|
||||||
|
|
||||||
'exposeStreamSession',
|
'exposeStreamSession',
|
||||||
'exposeDialogRoutes',
|
'exposeDialogRoutes',
|
||||||
|
|
||||||
|
'homePageBeforeLoad',
|
||||||
|
'productDetailPageBeforeLoad',
|
||||||
|
'streamPageBeforeLoad',
|
||||||
|
|
||||||
'guideAchievementsDefaultLocked',
|
'guideAchievementsDefaultLocked',
|
||||||
|
|
||||||
'enableTvRoutes',
|
'enableTvRoutes',
|
||||||
// AppInterface && 'detectProductDetailsPage',
|
|
||||||
|
|
||||||
'supportLocalCoOp',
|
'supportLocalCoOp',
|
||||||
'overrideStorageGetSettings',
|
'overrideStorageGetSettings',
|
||||||
@ -1027,11 +1022,6 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
getPref<UiLayout>(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
|
getPref<UiLayout>(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
|
||||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
||||||
|
|
||||||
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
|
|
||||||
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
|
||||||
getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
|
||||||
(getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.NATIVE_MKB) || getPref<UiSection>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.MOST_POPULAR)) && 'ignoreSiglSections',
|
|
||||||
|
|
||||||
...(STATES.userAgent.capabilities.touch ? [
|
...(STATES.userAgent.capabilities.touch ? [
|
||||||
'disableTouchContextMenu',
|
'disableTouchContextMenu',
|
||||||
] : []),
|
] : []),
|
||||||
@ -1059,12 +1049,25 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
'enableConsoleLogging',
|
'enableConsoleLogging',
|
||||||
'enableXcloudLogger',
|
'enableXcloudLogger',
|
||||||
] : []),
|
] : []),
|
||||||
].filter((item): item is string => !!item) as PatchArray;
|
|
||||||
|
...(blockSomeNotifications() ? [
|
||||||
|
'changeNotificationsSubscription',
|
||||||
|
] : []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hideSections = getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS);
|
||||||
|
let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
|
hideSections.includes(UiSection.NEWS) && 'ignoreNewsSection',
|
||||||
|
hideSections.includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
|
||||||
|
hideSections.includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
||||||
|
STATES.browser.capabilities.touch && hideSections.includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
||||||
|
hideSections.some(value => [UiSection.NATIVE_MKB, UiSection.MOST_POPULAR].includes(value)) && 'ignoreSiglSections',
|
||||||
|
]);
|
||||||
|
|
||||||
// Only when playing
|
// Only when playing
|
||||||
// TODO: check this
|
// TODO: check this
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let PLAYING_PATCH_ORDERS: PatchArray = [
|
let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
'patchXcloudTitleInfo',
|
'patchXcloudTitleInfo',
|
||||||
'disableGamepadDisconnectedScreen',
|
'disableGamepadDisconnectedScreen',
|
||||||
'patchStreamHud',
|
'patchStreamHud',
|
||||||
@ -1085,7 +1088,7 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
|
|||||||
...(STATES.userAgent.capabilities.touch ? [
|
...(STATES.userAgent.capabilities.touch ? [
|
||||||
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'patchShowSensorControls',
|
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'patchShowSensorControls',
|
||||||
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'exposeTouchLayoutManager',
|
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'exposeTouchLayoutManager',
|
||||||
(getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF) || !STATES.userAgent.capabilities.touch) && 'disableTakRenderer',
|
(getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
|
||||||
getPref<TouchControllerDefaultOpacity>(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
|
getPref<TouchControllerDefaultOpacity>(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
|
||||||
'patchBabylonRendererClass',
|
'patchBabylonRendererClass',
|
||||||
] : []),
|
] : []),
|
||||||
@ -1102,16 +1105,36 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
|
|||||||
] : []),
|
] : []),
|
||||||
|
|
||||||
// Native MKB
|
// Native MKB
|
||||||
...(getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
...(AppInterface && getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
||||||
'patchMouseAndKeyboardEnabled',
|
'patchMouseAndKeyboardEnabled',
|
||||||
'disableNativeRequestPointerLock',
|
'disableNativeRequestPointerLock',
|
||||||
] : []),
|
] : []),
|
||||||
].filter((item): item is string => !!item);
|
]);
|
||||||
|
|
||||||
const ALL_PATCHES = [...PATCH_ORDERS, ...PLAYING_PATCH_ORDERS];
|
let PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
|
AppInterface && 'detectProductDetailPage',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ALL_PATCHES = [...PATCH_ORDERS, ...HOME_PAGE_PATCH_ORDERS, ...STREAM_PAGE_PATCH_ORDERS, ...PRODUCT_DETAIL_PAGE_PATCH_ORDERS];
|
||||||
|
|
||||||
export class Patcher {
|
export class Patcher {
|
||||||
static #patchFunctionBind() {
|
private static remainingPatches: { [key in PatchPage]: PatchArray } = {
|
||||||
|
home: HOME_PAGE_PATCH_ORDERS,
|
||||||
|
stream: STREAM_PAGE_PATCH_ORDERS,
|
||||||
|
'product-detail': PRODUCT_DETAIL_PAGE_PATCH_ORDERS,
|
||||||
|
};
|
||||||
|
|
||||||
|
static patchPage(page: PatchPage) {
|
||||||
|
const remaining = Patcher.remainingPatches[page];
|
||||||
|
if (!remaining) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PATCH_ORDERS = PATCH_ORDERS.concat(remaining);
|
||||||
|
delete Patcher.remainingPatches[page];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static patchNativeBind() {
|
||||||
const nativeBind = Function.prototype.bind;
|
const nativeBind = Function.prototype.bind;
|
||||||
Function.prototype.bind = function() {
|
Function.prototype.bind = function() {
|
||||||
let valid = false;
|
let valid = false;
|
||||||
@ -1132,8 +1155,6 @@ export class Patcher {
|
|||||||
return nativeBind.apply(this, arguments);
|
return nativeBind.apply(this, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
PatcherCache.getInstance().init();
|
|
||||||
|
|
||||||
if (typeof arguments[1] === 'function') {
|
if (typeof arguments[1] === 'function') {
|
||||||
BxLogger.info(LOG_TAG, 'Restored Function.prototype.bind()');
|
BxLogger.info(LOG_TAG, 'Restored Function.prototype.bind()');
|
||||||
Function.prototype.bind = nativeBind;
|
Function.prototype.bind = nativeBind;
|
||||||
@ -1211,6 +1232,7 @@ export class Patcher {
|
|||||||
patchesToCheck.splice(patchIndex, 1);
|
patchesToCheck.splice(patchIndex, 1);
|
||||||
patchIndex--;
|
patchIndex--;
|
||||||
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
||||||
|
BxLogger.info(LOG_TAG, 'Remaining patches', PATCH_ORDERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply patched functions
|
// Apply patched functions
|
||||||
@ -1236,7 +1258,7 @@ export class Patcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
Patcher.#patchFunctionBind();
|
Patcher.patchNativeBind();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1249,7 +1271,29 @@ export class PatcherCache {
|
|||||||
|
|
||||||
private CACHE!: { [key: string]: PatchArray };
|
private CACHE!: { [key: string]: PatchArray };
|
||||||
|
|
||||||
private isInitialized = false;
|
private constructor() {
|
||||||
|
this.checkSignature();
|
||||||
|
|
||||||
|
// Read cache from storage
|
||||||
|
this.CACHE = JSON.parse(window.localStorage.getItem(this.KEY_CACHE) || '{}');
|
||||||
|
BxLogger.info(LOG_TAG, 'Cache', this.CACHE);
|
||||||
|
|
||||||
|
const pathName = window.location.pathname;
|
||||||
|
if (pathName.includes('/play/launch/')) {
|
||||||
|
Patcher.patchPage('stream');
|
||||||
|
} else if (pathName.includes('/play/games/')) {
|
||||||
|
Patcher.patchPage('product-detail');
|
||||||
|
} else if (pathName.endsWith('/play') || pathName.endsWith('/play/')) {
|
||||||
|
Patcher.patchPage('home');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove cached patches from PATCH_ORDERS & PLAYING_PATCH_ORDERS
|
||||||
|
PATCH_ORDERS = this.cleanupPatches(PATCH_ORDERS);
|
||||||
|
STREAM_PAGE_PATCH_ORDERS = this.cleanupPatches(STREAM_PAGE_PATCH_ORDERS);
|
||||||
|
PRODUCT_DETAIL_PAGE_PATCH_ORDERS = this.cleanupPatches(PRODUCT_DETAIL_PAGE_PATCH_ORDERS);
|
||||||
|
|
||||||
|
BxLogger.info(LOG_TAG, 'PATCH_ORDERS', PATCH_ORDERS.slice(0));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get patch's signature
|
* Get patch's signature
|
||||||
@ -1333,30 +1377,4 @@ export class PatcherCache {
|
|||||||
// Save to storage
|
// Save to storage
|
||||||
window.localStorage.setItem(this.KEY_CACHE, JSON.stringify(this.CACHE));
|
window.localStorage.setItem(this.KEY_CACHE, JSON.stringify(this.CACHE));
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
|
||||||
if (this.isInitialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.isInitialized = true;
|
|
||||||
|
|
||||||
this.checkSignature();
|
|
||||||
|
|
||||||
// Read cache from storage
|
|
||||||
this.CACHE = JSON.parse(window.localStorage.getItem(this.KEY_CACHE) || '{}');
|
|
||||||
BxLogger.info(LOG_TAG, this.CACHE);
|
|
||||||
|
|
||||||
if (window.location.pathname.includes('/play/')) {
|
|
||||||
PATCH_ORDERS.push(...PLAYING_PATCH_ORDERS);
|
|
||||||
} else {
|
|
||||||
PATCH_ORDERS.push(ENDING_CHUNKS_PATCH_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove cached patches from PATCH_ORDERS & PLAYING_PATCH_ORDERS
|
|
||||||
PATCH_ORDERS = this.cleanupPatches(PATCH_ORDERS);
|
|
||||||
PLAYING_PATCH_ORDERS = this.cleanupPatches(PLAYING_PATCH_ORDERS);
|
|
||||||
|
|
||||||
BxLogger.info(LOG_TAG, PATCH_ORDERS.slice(0));
|
|
||||||
BxLogger.info(LOG_TAG, PLAYING_PATCH_ORDERS.slice(0));
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -3,11 +3,7 @@ window.BX_EXPOSED.streamSession = this;
|
|||||||
const orgSetMicrophoneState = this.setMicrophoneState.bind(this);
|
const orgSetMicrophoneState = this.setMicrophoneState.bind(this);
|
||||||
this.setMicrophoneState = state => {
|
this.setMicrophoneState = state => {
|
||||||
orgSetMicrophoneState(state);
|
orgSetMicrophoneState(state);
|
||||||
|
window.BxEventBus.Stream.emit('microphone.state.changed', { state });
|
||||||
const evt = new Event(BxEvent.MICROPHONE_STATE_CHANGED);
|
|
||||||
evt.microphoneState = state;
|
|
||||||
|
|
||||||
window.dispatchEvent(evt);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));
|
window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));
|
@ -1 +0,0 @@
|
|||||||
e && BxEvent.dispatch(window, BxEvent.NAVIGATION_FOCUS_CHANGED, {element: e});
|
|
@ -204,7 +204,7 @@ export class RemotePlayManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.consoles.length === 0) {
|
if (this.consoles.length === 0) {
|
||||||
Toast.show(t('no-consoles-found'), '', {instant: true});
|
Toast.show(t('no-consoles-found'), '', { instant: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ export class MicrophoneShortcut {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
window.BX_EXPOSED.streamSession.tryEnableChatAsync(enableMic);
|
window.BX_EXPOSED.streamSession.tryEnableChatAsync(enableMic);
|
||||||
showToast && Toast.show(t('microphone'), t(enableMic ? 'unmuted': 'muted'), {instant: true});
|
showToast && Toast.show(t('microphone'), t(enableMic ? 'unmuted': 'muted'), { instant: true });
|
||||||
|
|
||||||
return enableMic;
|
return enableMic;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
import { limitVideoPlayerFps } from "../stream/stream-settings-utils";
|
import { limitVideoPlayerFps } from "../stream/stream-settings-utils";
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
export class RendererShortcut {
|
export class RendererShortcut {
|
||||||
static toggleVisibility() {
|
static toggleVisibility() {
|
||||||
const $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]');
|
const $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]');
|
||||||
if (!$mediaContainer) {
|
if (!$mediaContainer) {
|
||||||
BxEvent.dispatch(window, BxEvent.VIDEO_VISIBILITY_CHANGED, { isShowing: true });
|
BxEventBus.Stream.emit('video.visibility.changed', { isVisible: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mediaContainer.classList.toggle('bx-gone');
|
$mediaContainer.classList.toggle('bx-gone');
|
||||||
const isShowing = !$mediaContainer.classList.contains('bx-gone');
|
const isVisible = !$mediaContainer.classList.contains('bx-gone');
|
||||||
|
|
||||||
// Switch FPS
|
// Switch FPS
|
||||||
limitVideoPlayerFps(isShowing ? getPref(PrefKey.VIDEO_MAX_FPS) : 0);
|
limitVideoPlayerFps(isVisible ? getPref(PrefKey.VIDEO_MAX_FPS) : 0);
|
||||||
BxEvent.dispatch(window, BxEvent.VIDEO_VISIBILITY_CHANGED, { isShowing });
|
BxEventBus.Stream.emit('video.visibility.changed', { isVisible });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,17 @@ export const SHORTCUT_ACTIONS: ShortcutActions = {
|
|||||||
[ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW]: [t('settings'), t('show')],
|
[ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW]: [t('settings'), t('show')],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// MKB
|
||||||
|
...(STATES.browser.capabilities.mkb ? {
|
||||||
|
[t('mouse-and-keyboard')]: {
|
||||||
|
[ShortcutAction.MKB_TOGGLE]: [t('toggle')],
|
||||||
|
},
|
||||||
|
} : {}),
|
||||||
|
|
||||||
|
[t('controller')]: {
|
||||||
|
[ShortcutAction.CONTROLLER_XBOX_BUTTON_PRESS]: [t('button-xbox'), t('press')],
|
||||||
|
},
|
||||||
|
|
||||||
// Device
|
// Device
|
||||||
...(!!AppInterface ? {
|
...(!!AppInterface ? {
|
||||||
[t('device')]: {
|
[t('device')]: {
|
||||||
@ -45,13 +56,6 @@ export const SHORTCUT_ACTIONS: ShortcutActions = {
|
|||||||
[ShortcutAction.STREAM_MICROPHONE_TOGGLE]: [t('microphone'), t('toggle')],
|
[ShortcutAction.STREAM_MICROPHONE_TOGGLE]: [t('microphone'), t('toggle')],
|
||||||
},
|
},
|
||||||
|
|
||||||
// MKB
|
|
||||||
...(STATES.browser.capabilities.mkb ? {
|
|
||||||
[t('mouse-and-keyboard')]: {
|
|
||||||
[ShortcutAction.MKB_TOGGLE]: [t('toggle')],
|
|
||||||
},
|
|
||||||
} : {}),
|
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
[t('other')]: {
|
[t('other')]: {
|
||||||
[ShortcutAction.TRUE_ACHIEVEMENTS_OPEN]: [t('true-achievements'), t('show')],
|
[ShortcutAction.TRUE_ACHIEVEMENTS_OPEN]: [t('true-achievements'), t('show')],
|
||||||
|
@ -4,7 +4,7 @@ import { Toast } from "@utils/toast";
|
|||||||
import { ceilToNearest, floorToNearest } from "@/utils/utils";
|
import { ceilToNearest, floorToNearest } from "@/utils/utils";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref, setPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref, setPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
export enum SpeakerState {
|
export enum SpeakerState {
|
||||||
ENABLED,
|
ENABLED,
|
||||||
@ -37,7 +37,7 @@ export class SoundShortcut {
|
|||||||
SoundShortcut.setGainNodeVolume(newValue);
|
SoundShortcut.setGainNodeVolume(newValue);
|
||||||
|
|
||||||
// Show toast
|
// Show toast
|
||||||
Toast.show(`${t('stream')} ❯ ${t('volume')}`, newValue + '%', {instant: true});
|
Toast.show(`${t('stream')} ❯ ${t('volume')}`, newValue + '%', { instant: true });
|
||||||
|
|
||||||
return newValue;
|
return newValue;
|
||||||
}
|
}
|
||||||
@ -69,10 +69,10 @@ export class SoundShortcut {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SoundShortcut.setGainNodeVolume(targetValue);
|
SoundShortcut.setGainNodeVolume(targetValue);
|
||||||
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, {instant: true});
|
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, { instant: true });
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
|
BxEventBus.Stream.emit('speaker.state.changed', {
|
||||||
speakerState: targetValue === 0 ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
state: targetValue === 0 ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -82,11 +82,11 @@ export class SoundShortcut {
|
|||||||
$media.muted = !$media.muted;
|
$media.muted = !$media.muted;
|
||||||
|
|
||||||
const status = $media.muted ? t('muted') : t('unmuted');
|
const status = $media.muted ? t('muted') : t('unmuted');
|
||||||
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, {instant: true});
|
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, { instant: true });
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
|
BxEventBus.Stream.emit('speaker.state.changed', {
|
||||||
speakerState: $media.muted ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
state: $media.muted ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
src/modules/shortcuts/virtual-controller-shortcut.ts
Normal file
21
src/modules/shortcuts/virtual-controller-shortcut.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { generateVirtualControllerMapping } from "@/utils/gamepad";
|
||||||
|
|
||||||
|
export class VirtualControllerShortcut {
|
||||||
|
static pressXboxButton(): void {
|
||||||
|
const streamSession = window.BX_EXPOSED.streamSession;
|
||||||
|
if (!streamSession) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const released = generateVirtualControllerMapping();
|
||||||
|
const pressed = generateVirtualControllerMapping({
|
||||||
|
Nexus: 1,
|
||||||
|
VirtualPhysicality: 1024, // Home
|
||||||
|
});
|
||||||
|
|
||||||
|
streamSession.onVirtualGamepadInput('systemMenu', performance.now(), [pressed]);
|
||||||
|
setTimeout(() => {
|
||||||
|
streamSession.onVirtualGamepadInput('systemMenu', performance.now(), [released]);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { CE } from "@/utils/html";
|
import { CE } from "@/utils/html";
|
||||||
import { WebGL2Player } from "./player/webgl2-player";
|
import { WebGL2Player } from "./player/webgl2-player";
|
||||||
@ -7,7 +7,7 @@ import { STATES } from "@/utils/global";
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
import { BX_FLAGS } from "@/utils/bx-flags";
|
import { BX_FLAGS } from "@/utils/bx-flags";
|
||||||
import { StreamPlayerType, StreamVideoProcessing, VideoRatio } from "@/enums/pref-values";
|
import { StreamPlayerType, StreamVideoProcessing, VideoPosition, VideoRatio } from "@/enums/pref-values";
|
||||||
|
|
||||||
export type StreamPlayerOptions = Partial<{
|
export type StreamPlayerOptions = Partial<{
|
||||||
processing: string,
|
processing: string,
|
||||||
@ -39,13 +39,12 @@ export class StreamPlayer {
|
|||||||
private setupVideoElements() {
|
private setupVideoElements() {
|
||||||
this.$videoCss = document.getElementById('bx-video-css') as HTMLStyleElement;
|
this.$videoCss = document.getElementById('bx-video-css') as HTMLStyleElement;
|
||||||
if (this.$videoCss) {
|
if (this.$videoCss) {
|
||||||
this.$usmMatrix = this.$videoCss.querySelector('#bx-filter-usm-matrix') as any;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $fragment = document.createDocumentFragment();
|
const $fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
this.$videoCss = CE<HTMLStyleElement>('style', {id: 'bx-video-css'});
|
this.$videoCss = CE<HTMLStyleElement>('style', { id: 'bx-video-css' });
|
||||||
$fragment.appendChild(this.$videoCss);
|
$fragment.appendChild(this.$videoCss);
|
||||||
|
|
||||||
// Setup SVG filters
|
// Setup SVG filters
|
||||||
@ -53,7 +52,7 @@ export class StreamPlayer {
|
|||||||
id: 'bx-video-filters',
|
id: 'bx-video-filters',
|
||||||
xmlns: 'http://www.w3.org/2000/svg',
|
xmlns: 'http://www.w3.org/2000/svg',
|
||||||
class: 'bx-gone',
|
class: 'bx-gone',
|
||||||
}, CE('defs', {xmlns: 'http://www.w3.org/2000/svg'},
|
}, CE('defs', { xmlns: 'http://www.w3.org/2000/svg' },
|
||||||
CE('filter', {
|
CE('filter', {
|
||||||
id: 'bx-filter-usm',
|
id: 'bx-filter-usm',
|
||||||
xmlns: 'http://www.w3.org/2000/svg',
|
xmlns: 'http://www.w3.org/2000/svg',
|
||||||
@ -141,6 +140,23 @@ export class StreamPlayer {
|
|||||||
$video.dataset.width = width.toString();
|
$video.dataset.width = width.toString();
|
||||||
$video.dataset.height = height.toString();
|
$video.dataset.height = height.toString();
|
||||||
|
|
||||||
|
// Set position
|
||||||
|
const $parent = $video.parentElement!;
|
||||||
|
const position = getPref<VideoPosition>(PrefKey.VIDEO_POSITION);
|
||||||
|
$parent.style.removeProperty('padding-top');
|
||||||
|
|
||||||
|
$parent.dataset.position = position;
|
||||||
|
if (position === VideoPosition.TOP_HALF || position === VideoPosition.BOTTOM_HALF) {
|
||||||
|
let padding = Math.floor((window.innerHeight - height) / 4);
|
||||||
|
if (padding > 0) {
|
||||||
|
if (position === VideoPosition.BOTTOM_HALF) {
|
||||||
|
padding *= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parent.style.paddingTop = padding + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update size
|
// Update size
|
||||||
targetWidth = `${width}px`;
|
targetWidth = `${width}px`;
|
||||||
targetHeight = `${height}px`;
|
targetHeight = `${height}px`;
|
||||||
@ -164,6 +180,8 @@ export class StreamPlayer {
|
|||||||
$webGL2Canvas.style.width = targetWidth;
|
$webGL2Canvas.style.width = targetWidth;
|
||||||
$webGL2Canvas.style.height = targetHeight;
|
$webGL2Canvas.style.height = targetHeight;
|
||||||
$webGL2Canvas.style.objectFit = targetObjectFit;
|
$webGL2Canvas.style.objectFit = targetObjectFit;
|
||||||
|
|
||||||
|
$video.dispatchEvent(new Event('resize'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update video dimensions
|
// Update video dimensions
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isLiteVersion } from "@macros/build" with {type: "macro"};
|
import { isLiteVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { t } from "@utils/translation";
|
import { t } from "@utils/translation";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
@ -118,9 +118,9 @@ export class StreamBadges {
|
|||||||
return $badge;
|
return $badge;
|
||||||
}
|
}
|
||||||
|
|
||||||
$badge = CE('div', {class: 'bx-badge', title: badgeInfo.name},
|
$badge = CE('div', { class: 'bx-badge', title: badgeInfo.name },
|
||||||
CE('span', {class: 'bx-badge-name'}, createSvgIcon(badgeInfo.icon)),
|
CE('span', { class: 'bx-badge-name' }, createSvgIcon(badgeInfo.icon)),
|
||||||
CE('span', {class: 'bx-badge-value', style: `background-color: ${badgeInfo.color}`}, value),
|
CE('span', { class: 'bx-badge-value', style: `background-color: ${badgeInfo.color}` }, value),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (name === StreamBadge.BATTERY) {
|
if (name === StreamBadge.BATTERY) {
|
||||||
@ -219,7 +219,7 @@ export class StreamBadges {
|
|||||||
this.serverInfo.audio ? this.badges.audio.$element : [StreamBadge.AUDIO, '?'],
|
this.serverInfo.audio ? this.badges.audio.$element : [StreamBadge.AUDIO, '?'],
|
||||||
];
|
];
|
||||||
|
|
||||||
const $container = CE('div', {class: 'bx-badges'});
|
const $container = CE('div', { class: 'bx-badges' });
|
||||||
|
|
||||||
for (const item of BADGES) {
|
for (const item of BADGES) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
@ -253,8 +253,9 @@ export class StreamBadges {
|
|||||||
const allAudioCodecs: Record<string, RTCBasicStat> = {};
|
const allAudioCodecs: Record<string, RTCBasicStat> = {};
|
||||||
let audioCodecId;
|
let audioCodecId;
|
||||||
|
|
||||||
const allCandidates: Record<string, string> = {};
|
const allCandidatePairs: Record<string, string> = {};
|
||||||
let candidateId;
|
const allRemoteCandidates: Record<string, string> = {};
|
||||||
|
let candidatePairId;
|
||||||
|
|
||||||
stats.forEach((stat: RTCBasicStat) => {
|
stats.forEach((stat: RTCBasicStat) => {
|
||||||
if (stat.type === 'codec') {
|
if (stat.type === 'codec') {
|
||||||
@ -275,10 +276,12 @@ export class StreamBadges {
|
|||||||
} else if (stat.kind === 'audio') {
|
} else if (stat.kind === 'audio') {
|
||||||
audioCodecId = stat.codecId;
|
audioCodecId = stat.codecId;
|
||||||
}
|
}
|
||||||
} else if (stat.type === 'candidate-pair' && stat.packetsReceived > 0 && stat.state === 'succeeded') {
|
} else if (stat.type === 'transport' && (stat as unknown as RTCTransportStats).selectedCandidatePairId) {
|
||||||
candidateId = stat.remoteCandidateId;
|
candidatePairId = (stat as unknown as RTCTransportStats).selectedCandidatePairId;
|
||||||
|
} else if (stat.type === 'candidate-pair') {
|
||||||
|
allCandidatePairs[stat.id] = (stat as unknown as RTCIceCandidatePairStats).remoteCandidateId;
|
||||||
} else if (stat.type === 'remote-candidate') {
|
} else if (stat.type === 'remote-candidate') {
|
||||||
allCandidates[stat.id] = stat.address;
|
allRemoteCandidates[stat.id] = stat.address;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -336,12 +339,12 @@ export class StreamBadges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get server type
|
// Get server type
|
||||||
if (candidateId) {
|
if (candidatePairId) {
|
||||||
BxLogger.info('candidate', candidateId, allCandidates);
|
BxLogger.info('candidate', candidatePairId, allCandidatePairs);
|
||||||
|
|
||||||
// Server + Region
|
// Server + Region
|
||||||
let text = '';
|
let text = '';
|
||||||
const isIpv6 = allCandidates[candidateId].includes(':');
|
const isIpv6 = allRemoteCandidates[allCandidatePairs[candidatePairId]].includes(':');
|
||||||
|
|
||||||
const server = this.serverInfo.server;
|
const server = this.serverInfo.server;
|
||||||
if (server && server.region) {
|
if (server && server.region) {
|
||||||
|
@ -71,7 +71,6 @@ export function updateVideoPlayer() {
|
|||||||
streamPlayer.setPlayerType(getPref(PrefKey.VIDEO_PLAYER_TYPE));
|
streamPlayer.setPlayerType(getPref(PrefKey.VIDEO_PLAYER_TYPE));
|
||||||
streamPlayer.updateOptions(options);
|
streamPlayer.updateOptions(options);
|
||||||
streamPlayer.refreshPlayer();
|
streamPlayer.refreshPlayer();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', updateVideoPlayer);
|
window.addEventListener('resize', updateVideoPlayer);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { BxEvent } from "@utils/bx-event"
|
|
||||||
import { CE } from "@utils/html"
|
import { CE } from "@utils/html"
|
||||||
import { t } from "@utils/translation"
|
import { t } from "@utils/translation"
|
||||||
import { STATES } from "@utils/global"
|
import { STATES } from "@utils/global"
|
||||||
@ -7,6 +6,7 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage"
|
|||||||
import { StreamStatsCollector, type StreamStatGrade } from "@/utils/stream-stats-collector"
|
import { StreamStatsCollector, type StreamStatGrade } from "@/utils/stream-stats-collector"
|
||||||
import { BxLogger } from "@/utils/bx-logger"
|
import { BxLogger } from "@/utils/bx-logger"
|
||||||
import { StreamStat } from "@/enums/pref-values"
|
import { StreamStat } from "@/enums/pref-values"
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus"
|
||||||
|
|
||||||
|
|
||||||
export class StreamStats {
|
export class StreamStats {
|
||||||
@ -193,12 +193,21 @@ export class StreamStats {
|
|||||||
|
|
||||||
refreshStyles() {
|
refreshStyles() {
|
||||||
const PREF_ITEMS = getPref<StreamStat[]>(PrefKey.STATS_ITEMS);
|
const PREF_ITEMS = getPref<StreamStat[]>(PrefKey.STATS_ITEMS);
|
||||||
|
const PREF_OPACITY_BG = getPref<number>(PrefKey.STATS_OPACITY_BACKGROUND);
|
||||||
|
|
||||||
const $container = this.$container;
|
const $container = this.$container;
|
||||||
$container.dataset.stats = '[' + PREF_ITEMS.join('][') + ']';
|
$container.dataset.stats = '[' + PREF_ITEMS.join('][') + ']';
|
||||||
$container.dataset.position = getPref(PrefKey.STATS_POSITION);
|
$container.dataset.position = getPref(PrefKey.STATS_POSITION);
|
||||||
$container.dataset.transparent = getPref(PrefKey.STATS_TRANSPARENT);
|
|
||||||
$container.style.opacity = getPref(PrefKey.STATS_OPACITY) + '%';
|
if (PREF_OPACITY_BG === 0) {
|
||||||
|
$container.style.removeProperty('background-color');
|
||||||
|
$container.dataset.shadow = 'true';
|
||||||
|
} else {
|
||||||
|
delete $container.dataset.shadow;
|
||||||
|
$container.style.backgroundColor = `rgba(0, 0, 0, ${PREF_OPACITY_BG}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$container.style.opacity = getPref(PrefKey.STATS_OPACITY_ALL) + '%';
|
||||||
$container.style.fontSize = getPref(PrefKey.STATS_TEXT_SIZE);
|
$container.style.fontSize = getPref(PrefKey.STATS_TEXT_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +218,7 @@ export class StreamStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async render() {
|
private async render() {
|
||||||
this.$container = CE('div', {class: 'bx-stats-bar bx-gone'});
|
this.$container = CE('div', { class: 'bx-stats-bar bx-gone' });
|
||||||
|
|
||||||
let statKey: keyof typeof this.stats;
|
let statKey: keyof typeof this.stats;
|
||||||
for (statKey in this.stats) {
|
for (statKey in this.stats) {
|
||||||
@ -230,7 +239,7 @@ export class StreamStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static setupEvents() {
|
static setupEvents() {
|
||||||
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
BxEventBus.Stream.on('state.playing', () => {
|
||||||
const PREF_STATS_QUICK_GLANCE = getPref(PrefKey.STATS_QUICK_GLANCE_ENABLED);
|
const PREF_STATS_QUICK_GLANCE = getPref(PrefKey.STATS_QUICK_GLANCE_ENABLED);
|
||||||
const PREF_STATS_SHOW_WHEN_PLAYING = getPref(PrefKey.STATS_SHOW_WHEN_PLAYING);
|
const PREF_STATS_SHOW_WHEN_PLAYING = getPref(PrefKey.STATS_SHOW_WHEN_PLAYING);
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { STATES } from "@utils/global.ts";
|
import { STATES } from "@utils/global.ts";
|
||||||
import { createSvgIcon } from "@utils/html.ts";
|
import { createSvgIcon } from "@utils/html.ts";
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
import { BxEvent } from "@utils/bx-event.ts";
|
|
||||||
import { t } from "@utils/translation.ts";
|
import { t } from "@utils/translation.ts";
|
||||||
import { StreamBadges } from "./stream-badges.ts";
|
import { StreamBadges } from "./stream-badges.ts";
|
||||||
import { StreamStats } from "./stream-stats.ts";
|
import { StreamStats } from "./stream-stats.ts";
|
||||||
import { SettingsDialog } from "../ui/dialog/settings-dialog.ts";
|
import { SettingsDialog } from "../ui/dialog/settings-dialog.ts";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus.ts";
|
||||||
|
|
||||||
|
|
||||||
export class StreamUiHandler {
|
export class StreamUiHandler {
|
||||||
@ -243,7 +243,7 @@ export class StreamUiHandler {
|
|||||||
|
|
||||||
// Error Page: .PureErrorPage.ErrorScreen
|
// Error Page: .PureErrorPage.ErrorScreen
|
||||||
if (className.includes('PureErrorPage')) {
|
if (className.includes('PureErrorPage')) {
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE);
|
BxEventBus.Stream.emit('state.error', {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ export class StreamUiHandler {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe($screen, {subtree: true, childList: true});
|
observer.observe($screen, { subtree: true, childList: true });
|
||||||
StreamUiHandler.observer = observer;
|
StreamUiHandler.observer = observer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { PrefKey } from "@/enums/pref-keys";
|
|||||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||||
import { TouchControllerStyleCustom, TouchControllerStyleStandard } from "@/enums/pref-values";
|
import { TouchControllerStyleCustom, TouchControllerStyleStandard } from "@/enums/pref-values";
|
||||||
import { GhPagesUtils } from "@/utils/gh-pages";
|
import { GhPagesUtils } from "@/utils/gh-pages";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
const LOG_TAG = 'TouchController';
|
const LOG_TAG = 'TouchController';
|
||||||
|
|
||||||
@ -233,13 +234,13 @@ export class TouchController {
|
|||||||
let html = false;
|
let html = false;
|
||||||
if (layout.author) {
|
if (layout.author) {
|
||||||
const author = `<b>${escapeHtml(layout.author)}</b>`;
|
const author = `<b>${escapeHtml(layout.author)}</b>`;
|
||||||
msg = t('touch-control-layout-by', {name: author});
|
msg = t('touch-control-layout-by', { name: author });
|
||||||
html = true;
|
html = true;
|
||||||
} else {
|
} else {
|
||||||
msg = t('touch-control-layout');
|
msg = t('touch-control-layout');
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutChanged && Toast.show(msg, layout.name, {html: html});
|
layoutChanged && Toast.show(msg, layout.name, { html });
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
// Show gyroscope control in the "More options" dialog if this layout has gyroscope
|
// Show gyroscope control in the "More options" dialog if this layout has gyroscope
|
||||||
@ -268,7 +269,7 @@ export class TouchController {
|
|||||||
|
|
||||||
static setup() {
|
static setup() {
|
||||||
// Function for testing touch control
|
// Function for testing touch control
|
||||||
(window as any).testTouchLayout = (layout: any) => {
|
window.testTouchLayout = (layout: any) => {
|
||||||
const { touchLayoutManager } = window.BX_EXPOSED;
|
const { touchLayoutManager } = window.BX_EXPOSED;
|
||||||
|
|
||||||
touchLayoutManager && touchLayoutManager.changeLayoutForScope({
|
touchLayoutManager && touchLayoutManager.changeLayoutForScope({
|
||||||
@ -291,9 +292,9 @@ export class TouchController {
|
|||||||
const PREF_STYLE_STANDARD = getPref<TouchControllerStyleStandard>(PrefKey.TOUCH_CONTROLLER_STYLE_STANDARD);
|
const PREF_STYLE_STANDARD = getPref<TouchControllerStyleStandard>(PrefKey.TOUCH_CONTROLLER_STYLE_STANDARD);
|
||||||
const PREF_STYLE_CUSTOM = getPref<TouchControllerStyleCustom>(PrefKey.TOUCH_CONTROLLER_STYLE_CUSTOM);
|
const PREF_STYLE_CUSTOM = getPref<TouchControllerStyleCustom>(PrefKey.TOUCH_CONTROLLER_STYLE_CUSTOM);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||||
const dataChannel = (e as any).dataChannel;
|
const { dataChannel } = payload;
|
||||||
if (!dataChannel || dataChannel.label !== 'message') {
|
if (dataChannel?.label !== 'message') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { GamepadKey } from "@/enums/gamepad";
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
import { BxLogger } from "@/utils/bx-logger";
|
import { BxLogger } from "@/utils/bx-logger";
|
||||||
import { CE, isElementVisible } from "@/utils/html";
|
import { CE, isElementVisible } from "@/utils/html";
|
||||||
import { setNearby } from "@/utils/navigation-utils";
|
import { setNearby } from "@/utils/navigation-utils";
|
||||||
@ -154,7 +155,7 @@ export class NavigationDialogManager {
|
|||||||
private constructor() {
|
private constructor() {
|
||||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||||
|
|
||||||
this.$overlay = CE('div', {class: 'bx-navigation-dialog-overlay bx-gone'});
|
this.$overlay = CE('div', { class: 'bx-navigation-dialog-overlay bx-gone' });
|
||||||
this.$overlay.addEventListener('click', e => {
|
this.$overlay.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -164,7 +165,7 @@ export class NavigationDialogManager {
|
|||||||
|
|
||||||
document.documentElement.appendChild(this.$overlay);
|
document.documentElement.appendChild(this.$overlay);
|
||||||
|
|
||||||
this.$container = CE('div', {class: 'bx-navigation-dialog bx-gone'});
|
this.$container = CE('div', { class: 'bx-navigation-dialog bx-gone' });
|
||||||
document.documentElement.appendChild(this.$container);
|
document.documentElement.appendChild(this.$container);
|
||||||
|
|
||||||
// Hide dialog when the Guide menu is shown
|
// Hide dialog when the Guide menu is shown
|
||||||
@ -186,7 +187,7 @@ export class NavigationDialogManager {
|
|||||||
// Find un-calculated <select> elements
|
// Find un-calculated <select> elements
|
||||||
this.calculateSelectBoxes($dialog);
|
this.calculateSelectBoxes($dialog);
|
||||||
});
|
});
|
||||||
observer.observe(this.$container, {childList: true});
|
observer.observe(this.$container, { childList: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +261,7 @@ export class NavigationDialogManager {
|
|||||||
} else if (keyCode === 'Enter' || keyCode === 'NumpadEnter' || keyCode === 'Space') {
|
} else if (keyCode === 'Enter' || keyCode === 'NumpadEnter' || keyCode === 'Space') {
|
||||||
if (!($target instanceof HTMLInputElement && $target.type === 'text')) {
|
if (!($target instanceof HTMLInputElement && $target.type === 'text')) {
|
||||||
handled = true;
|
handled = true;
|
||||||
$target.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
$target.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||||
}
|
}
|
||||||
} else if (keyCode === 'Escape') {
|
} else if (keyCode === 'Escape') {
|
||||||
handled = true;
|
handled = true;
|
||||||
@ -393,7 +394,7 @@ export class NavigationDialogManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (releasedButton === GamepadKey.A) {
|
if (releasedButton === GamepadKey.A) {
|
||||||
document.activeElement && document.activeElement.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
document.activeElement?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||||
return;
|
return;
|
||||||
} else if (releasedButton === GamepadKey.B) {
|
} else if (releasedButton === GamepadKey.B) {
|
||||||
this.hide();
|
this.hide();
|
||||||
@ -439,10 +440,10 @@ export class NavigationDialogManager {
|
|||||||
show(dialog: NavigationDialog, configs={}, clearStack=false) {
|
show(dialog: NavigationDialog, configs={}, clearStack=false) {
|
||||||
this.clearGamepadHoldingInterval();
|
this.clearGamepadHoldingInterval();
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
BxEventBus.Script.emit('dialog.shown', {});
|
||||||
|
|
||||||
// Stop xCloud's navigation polling
|
// Stop xCloud's navigation polling
|
||||||
(window as any).BX_EXPOSED.disableGamepadPolling = true;
|
window.BX_EXPOSED.disableGamepadPolling = true;
|
||||||
|
|
||||||
// Lock scroll bar
|
// Lock scroll bar
|
||||||
document.body.classList.add('bx-no-scroll');
|
document.body.classList.add('bx-no-scroll');
|
||||||
@ -475,11 +476,14 @@ export class NavigationDialogManager {
|
|||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.clearGamepadHoldingInterval();
|
this.clearGamepadHoldingInterval();
|
||||||
|
if (!this.isShowing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Unlock scroll bar
|
// Unlock scroll bar
|
||||||
document.body.classList.remove('bx-no-scroll');
|
document.body.classList.remove('bx-no-scroll');
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
|
BxEventBus.Script.emit('dialog.dismissed', {});
|
||||||
|
|
||||||
// Hide content
|
// Hide content
|
||||||
this.$overlay.classList.add('bx-gone');
|
this.$overlay.classList.add('bx-gone');
|
||||||
@ -504,7 +508,7 @@ export class NavigationDialogManager {
|
|||||||
this.unmountCurrentDialog();
|
this.unmountCurrentDialog();
|
||||||
|
|
||||||
// Enable xCloud's navigation polling
|
// Enable xCloud's navigation polling
|
||||||
(window as any).BX_EXPOSED.disableGamepadPolling = false;
|
window.BX_EXPOSED.disableGamepadPolling = false;
|
||||||
|
|
||||||
// Show the last dialog in dialogs stack
|
// Show the last dialog in dialogs stack
|
||||||
if (this.dialogsStack.length) {
|
if (this.dialogsStack.length) {
|
||||||
|
@ -16,6 +16,7 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
|||||||
|
|
||||||
private $presets!: HTMLSelectElement;
|
private $presets!: HTMLSelectElement;
|
||||||
private $header!: HTMLElement;
|
private $header!: HTMLElement;
|
||||||
|
private $defaultNote!: HTMLElement;
|
||||||
protected $content!: HTMLElement;
|
protected $content!: HTMLElement;
|
||||||
|
|
||||||
private $btnRename!: HTMLButtonElement;
|
private $btnRename!: HTMLButtonElement;
|
||||||
@ -36,15 +37,12 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
|||||||
const isDefaultPreset = this.currentPresetId <= 0;
|
const isDefaultPreset = this.currentPresetId <= 0;
|
||||||
this.$btnRename.disabled = isDefaultPreset;
|
this.$btnRename.disabled = isDefaultPreset;
|
||||||
this.$btnDelete.disabled = isDefaultPreset;
|
this.$btnDelete.disabled = isDefaultPreset;
|
||||||
|
this.$defaultNote.classList.toggle('bx-gone', !isDefaultPreset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async renderPresetsList() {
|
private async renderPresetsList() {
|
||||||
this.allPresets = await this.presetsDb.getPresets();
|
this.allPresets = await this.presetsDb.getPresets();
|
||||||
if (!this.currentPresetId) {
|
renderPresetsList<T>(this.$presets, this.allPresets, this.currentPresetId, { selectedIndicator: true });
|
||||||
this.currentPresetId = this.allPresets.default[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPresetsList<T>(this.$presets, this.allPresets, this.currentPresetId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private promptNewName(action: string,value='') {
|
private promptNewName(action: string,value='') {
|
||||||
@ -121,7 +119,7 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
|||||||
createButton({
|
createButton({
|
||||||
icon: BxIcon.NEW,
|
icon: BxIcon.NEW,
|
||||||
title: t('new'),
|
title: t('new'),
|
||||||
style: ButtonStyle.FOCUSABLE,
|
style: ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
||||||
onClick: async (e) => {
|
onClick: async (e) => {
|
||||||
const newName = this.promptNewName(t('new'));
|
const newName = this.promptNewName(t('new'));
|
||||||
if (!newName) {
|
if (!newName) {
|
||||||
@ -140,7 +138,7 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
|||||||
createButton({
|
createButton({
|
||||||
icon: BxIcon.COPY,
|
icon: BxIcon.COPY,
|
||||||
title: t('copy'),
|
title: t('copy'),
|
||||||
style: ButtonStyle.FOCUSABLE,
|
style: ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
||||||
onClick: async (e) => {
|
onClick: async (e) => {
|
||||||
const preset = this.allPresets.data[this.currentPresetId];
|
const preset = this.allPresets.data[this.currentPresetId];
|
||||||
|
|
||||||
@ -168,8 +166,11 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
|||||||
onClick: e => this.hide(),
|
onClick: e => this.hide(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
$header,
|
CE('div', {},
|
||||||
CE('div', { class: 'bx-dialog-content bx-hide-scroll-bar' }, this.$content),
|
$header,
|
||||||
|
this.$defaultNote = CE('div', { class: 'bx-default-preset-note bx-gone' }, t('default-preset-note')),
|
||||||
|
),
|
||||||
|
CE('div', { class: 'bx-dialog-content' }, this.$content),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,8 +95,8 @@ export class ControllerShortcutsManagerDialog extends BaseProfileManagerDialog<C
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
fragment.appendChild(CE('p', {class: 'bx-shortcut-note'},
|
fragment.appendChild(CE('p', { class: 'bx-shortcut-note' },
|
||||||
CE('span', {class: 'bx-prompt'}, PrompFont.HOME),
|
CE('span', { class: 'bx-prompt' }, PrompFont.HOME),
|
||||||
': ' + t('controller-shortcuts-xbox-note'),
|
': ' + t('controller-shortcuts-xbox-note'),
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -109,8 +109,8 @@ export class ControllerShortcutsManagerDialog extends BaseProfileManagerDialog<C
|
|||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const $label = CE('label', {class: 'bx-prompt'}, `${PrompFont.HOME}${prompt}`);
|
const $label = CE('label', { class: 'bx-prompt' }, `${PrompFont.HOME}${prompt}`);
|
||||||
const $div = CE('div', {class: 'bx-shortcut-actions'});
|
const $div = CE('div', { class: 'bx-shortcut-actions' });
|
||||||
|
|
||||||
let $fakeSelect: HTMLSelectElement | null = null;
|
let $fakeSelect: HTMLSelectElement | null = null;
|
||||||
if (!PREF_CONTROLLER_FRIENDLY_UI) {
|
if (!PREF_CONTROLLER_FRIENDLY_UI) {
|
||||||
|
@ -19,6 +19,7 @@ export class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog<Key
|
|||||||
// private readonly LOG_TAG = 'KeyboardShortcutsManagerDialog';
|
// private readonly LOG_TAG = 'KeyboardShortcutsManagerDialog';
|
||||||
|
|
||||||
protected $content: HTMLElement;
|
protected $content: HTMLElement;
|
||||||
|
private $unbindNote: HTMLElement;
|
||||||
private readonly allKeyElements: BxKeyBindingButton[] = [];
|
private readonly allKeyElements: BxKeyBindingButton[] = [];
|
||||||
|
|
||||||
protected readonly BLANK_PRESET_DATA: KeyboardShortcutPresetData = {
|
protected readonly BLANK_PRESET_DATA: KeyboardShortcutPresetData = {
|
||||||
@ -65,7 +66,10 @@ export class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog<Key
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$content = CE('div', {}, $rows);
|
this.$content = CE('div', {},
|
||||||
|
this.$unbindNote = CE('i', { class: 'bx-mkb-note' }, t('right-click-to-unbind')),
|
||||||
|
$rows,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onKeyChanged = (e: Event) => {
|
private onKeyChanged = (e: Event) => {
|
||||||
@ -109,6 +113,9 @@ export class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog<Key
|
|||||||
const isDefaultPreset = id <= 0;
|
const isDefaultPreset = id <= 0;
|
||||||
this.updateButtonStates();
|
this.updateButtonStates();
|
||||||
|
|
||||||
|
// Toggle unbind note
|
||||||
|
this.$unbindNote.classList.toggle('bx-gone', isDefaultPreset);
|
||||||
|
|
||||||
// Update buttons
|
// Update buttons
|
||||||
for (const $elm of this.allKeyElements) {
|
for (const $elm of this.allKeyElements) {
|
||||||
const { action } = this.parseDataset($elm);
|
const { action } = this.parseDataset($elm);
|
||||||
|
@ -48,6 +48,7 @@ export class MkbMappingManagerDialog extends BaseProfileManagerDialog<MkbPresetR
|
|||||||
private $mouseSensitivityX!: BxNumberStepper;
|
private $mouseSensitivityX!: BxNumberStepper;
|
||||||
private $mouseSensitivityY!: BxNumberStepper;
|
private $mouseSensitivityY!: BxNumberStepper;
|
||||||
private $mouseDeadzone!: BxNumberStepper;
|
private $mouseDeadzone!: BxNumberStepper;
|
||||||
|
private $unbindNote!: HTMLElement;
|
||||||
|
|
||||||
constructor(title: string) {
|
constructor(title: string) {
|
||||||
super(title, MkbMappingPresetsTable.getInstance());
|
super(title, MkbMappingPresetsTable.getInstance());
|
||||||
@ -93,7 +94,7 @@ export class MkbMappingManagerDialog extends BaseProfileManagerDialog<MkbPresetR
|
|||||||
|
|
||||||
private render() {
|
private render() {
|
||||||
const $rows = CE('div', {},
|
const $rows = CE('div', {},
|
||||||
CE('i', { class: 'bx-mkb-note' }, t('right-click-to-unbind')),
|
this.$unbindNote = CE('i', { class: 'bx-mkb-note' }, t('right-click-to-unbind')),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const buttonIndex of this.BUTTONS_ORDER) {
|
for (const buttonIndex of this.BUTTONS_ORDER) {
|
||||||
@ -185,6 +186,9 @@ export class MkbMappingManagerDialog extends BaseProfileManagerDialog<MkbPresetR
|
|||||||
const isDefaultPreset = id <= 0;
|
const isDefaultPreset = id <= 0;
|
||||||
this.updateButtonStates();
|
this.updateButtonStates();
|
||||||
|
|
||||||
|
// Toggle unbind note
|
||||||
|
this.$unbindNote.classList.toggle('bx-gone', isDefaultPreset);
|
||||||
|
|
||||||
// Update buttons
|
// Update buttons
|
||||||
for (const $elm of this.allKeyElements) {
|
for (const $elm of this.allKeyElements) {
|
||||||
const { buttonIndex, keySlot } = this.parseDataset($elm);
|
const { buttonIndex, keySlot } = this.parseDataset($elm);
|
||||||
|
@ -32,7 +32,7 @@ export class RemotePlayDialog extends NavigationDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupDialog() {
|
private setupDialog() {
|
||||||
const $fragment = CE('div', {'class': 'bx-remote-play-container'});
|
const $fragment = CE('div', { class: 'bx-remote-play-container' });
|
||||||
|
|
||||||
const $settingNote = CE('p', {});
|
const $settingNote = CE('p', {});
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ export class RemotePlayDialog extends NavigationDialog {
|
|||||||
let $resolutions : HTMLSelectElement | NavigationElement = CE<HTMLSelectElement>('select', {},
|
let $resolutions : HTMLSelectElement | NavigationElement = CE<HTMLSelectElement>('select', {},
|
||||||
CE('option', { value: StreamResolution.DIM_720P }, '720p'),
|
CE('option', { value: StreamResolution.DIM_720P }, '720p'),
|
||||||
CE('option', { value: StreamResolution.DIM_1080P }, '1080p'),
|
CE('option', { value: StreamResolution.DIM_1080P }, '1080p'),
|
||||||
|
// CE('option', { value: StreamResolution.DIM_1080P_HQ }, `1080p (HQ) ${t('experimental')}`),
|
||||||
);
|
);
|
||||||
|
|
||||||
$resolutions = BxSelectElement.create($resolutions as HTMLSelectElement);
|
$resolutions = BxSelectElement.create($resolutions as HTMLSelectElement);
|
||||||
@ -69,13 +70,13 @@ export class RemotePlayDialog extends NavigationDialog {
|
|||||||
const consoles = manager.getConsoles();
|
const consoles = manager.getConsoles();
|
||||||
|
|
||||||
for (let con of consoles) {
|
for (let con of consoles) {
|
||||||
const $child = CE('div', {class: 'bx-remote-play-device-wrapper'},
|
const $child = CE('div', { class: 'bx-remote-play-device-wrapper' },
|
||||||
CE('div', {class: 'bx-remote-play-device-info'},
|
CE('div', { class: 'bx-remote-play-device-info' },
|
||||||
CE('div', {},
|
CE('div', {},
|
||||||
CE('span', {class: 'bx-remote-play-device-name'}, con.deviceName),
|
CE('span', { class: 'bx-remote-play-device-name' }, con.deviceName),
|
||||||
CE('span', {class: 'bx-remote-play-console-type'}, con.consoleType.replace('Xbox', ''))
|
CE('span', { class: 'bx-remote-play-console-type' }, con.consoleType.replace('Xbox', ''))
|
||||||
),
|
),
|
||||||
CE('div', {class: 'bx-remote-play-power-state'}, this.STATE_LABELS[con.powerState]),
|
CE('div', { class: 'bx-remote-play-power-state' }, this.STATE_LABELS[con.powerState]),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Connect button
|
// Connect button
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { limitVideoPlayerFps, onChangeVideoPlayerType, updateVideoPlayer } from "@/modules/stream/stream-settings-utils";
|
import { limitVideoPlayerFps, onChangeVideoPlayerType, updateVideoPlayer } from "@/modules/stream/stream-settings-utils";
|
||||||
import { ButtonStyle, CE, createButton, createSettingRow, createSvgIcon, escapeCssSelector, type BxButtonOptions } from "@/utils/html";
|
import { ButtonStyle, CE, createButton, createSettingRow, createSvgIcon, escapeCssSelector, type BxButtonOptions } from "@/utils/html";
|
||||||
@ -12,7 +12,7 @@ import { STATES, AppInterface, deepClone, SCRIPT_VERSION, STORAGE, SCRIPT_VARIAN
|
|||||||
import { t, Translations } from "@/utils/translation";
|
import { t, Translations } from "@/utils/translation";
|
||||||
import { BxSelectElement } from "@/web-components/bx-select";
|
import { BxSelectElement } from "@/web-components/bx-select";
|
||||||
import { setNearby } from "@/utils/navigation-utils";
|
import { setNearby } from "@/utils/navigation-utils";
|
||||||
import { PatcherCache } from "@/modules/patcher";
|
import { PatcherCache } from "@/modules/patcher/patcher";
|
||||||
import { UserAgentProfile } from "@/enums/user-agent";
|
import { UserAgentProfile } from "@/enums/user-agent";
|
||||||
import { UserAgent } from "@/utils/user-agent";
|
import { UserAgent } from "@/utils/user-agent";
|
||||||
import { BX_FLAGS } from "@/utils/bx-flags";
|
import { BX_FLAGS } from "@/utils/bx-flags";
|
||||||
@ -30,6 +30,7 @@ import { SuggestionsSetting } from "./settings/suggestions";
|
|||||||
import { StreamSettings } from "@/utils/stream-settings";
|
import { StreamSettings } from "@/utils/stream-settings";
|
||||||
import { MkbExtraSettings } from "./settings/mkb-extra";
|
import { MkbExtraSettings } from "./settings/mkb-extra";
|
||||||
import { BxExposed } from "@/utils/bx-exposed";
|
import { BxExposed } from "@/utils/bx-exposed";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
type SettingTabSectionItem = Partial<{
|
type SettingTabSectionItem = Partial<{
|
||||||
@ -39,7 +40,7 @@ type SettingTabSectionItem = Partial<{
|
|||||||
note: string | (() => HTMLElement);
|
note: string | (() => HTMLElement);
|
||||||
experimental: string;
|
experimental: string;
|
||||||
content: HTMLElement | (() => HTMLElement);
|
content: HTMLElement | (() => HTMLElement);
|
||||||
options: {[key: string]: string};
|
options: { [key: string]: string };
|
||||||
unsupported: boolean;
|
unsupported: boolean;
|
||||||
unsupportedNote: string;
|
unsupportedNote: string;
|
||||||
onChange: (e: any, value: number) => void;
|
onChange: (e: any, value: number) => void;
|
||||||
@ -112,7 +113,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
if (!SCRIPT_VERSION.includes('beta') && PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) {
|
if (!SCRIPT_VERSION.includes('beta') && PREF_LATEST_VERSION && PREF_LATEST_VERSION != SCRIPT_VERSION) {
|
||||||
// Show new version button
|
// Show new version button
|
||||||
const opts = {
|
const opts = {
|
||||||
label: '🌟 ' + t('new-version-available', {version: PREF_LATEST_VERSION}),
|
label: '🌟 ' + t('new-version-available', { version: PREF_LATEST_VERSION }),
|
||||||
style: ButtonStyle.PRIMARY | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_WIDTH,
|
style: ButtonStyle.PRIMARY | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_WIDTH,
|
||||||
} as BxButtonOptions;
|
} as BxButtonOptions;
|
||||||
|
|
||||||
@ -231,8 +232,9 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
items: [
|
items: [
|
||||||
PrefKey.NATIVE_MKB_MODE,
|
PrefKey.NATIVE_MKB_MODE,
|
||||||
{
|
{
|
||||||
pref: PrefKey.FORCE_NATIVE_MKB_GAMES,
|
pref: PrefKey.NATIVE_MKB_FORCED_GAMES,
|
||||||
multiLines: true,
|
multiLines: true,
|
||||||
|
note: CE('a', { href: 'https://github.com/redphx/better-xcloud/discussions/574', target: '_blank' }, t('unofficial-game-list')),
|
||||||
},
|
},
|
||||||
|
|
||||||
PrefKey.MKB_ENABLED,
|
PrefKey.MKB_ENABLED,
|
||||||
@ -280,12 +282,14 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
PrefKey.UI_HIDE_SYSTEM_MENU_ICON,
|
PrefKey.UI_HIDE_SYSTEM_MENU_ICON,
|
||||||
PrefKey.UI_DISABLE_FEEDBACK_DIALOG,
|
PrefKey.UI_DISABLE_FEEDBACK_DIALOG,
|
||||||
PrefKey.UI_REDUCE_ANIMATIONS,
|
PrefKey.UI_REDUCE_ANIMATIONS,
|
||||||
PrefKey.BLOCK_SOCIAL_FEATURES,
|
|
||||||
PrefKey.BYOG_DISABLED,
|
|
||||||
{
|
{
|
||||||
pref: PrefKey.UI_HIDE_SECTIONS,
|
pref: PrefKey.UI_HIDE_SECTIONS,
|
||||||
multiLines: true,
|
multiLines: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pref: PrefKey.BLOCK_FEATURES,
|
||||||
|
multiLines: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
@ -316,7 +320,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
pref: PrefKey.USER_AGENT_PROFILE,
|
pref: PrefKey.USER_AGENT_PROFILE,
|
||||||
multiLines: true,
|
multiLines: true,
|
||||||
onCreated: (setting, $control) => {
|
onCreated: (setting, $control) => {
|
||||||
const defaultUserAgent = (window.navigator as any).orgUserAgent || window.navigator.userAgent;
|
const defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent;
|
||||||
|
|
||||||
const $inpCustomUserAgent = CE<HTMLInputElement>('input', {
|
const $inpCustomUserAgent = CE<HTMLInputElement>('input', {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -433,16 +437,13 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
},
|
},
|
||||||
onCreated: (setting: SettingTabSectionItem, $elm: HTMLElement) => {
|
onCreated: (setting: SettingTabSectionItem, $elm: HTMLElement) => {
|
||||||
const $range = $elm.querySelector<HTMLInputElement>('input[type=range')!;
|
const $range = $elm.querySelector<HTMLInputElement>('input[type=range')!;
|
||||||
window.addEventListener(BxEvent.SETTINGS_CHANGED, e => {
|
|
||||||
const { storageKey, settingKey, settingValue } = e as any;
|
|
||||||
if (storageKey !== StorageKey.GLOBAL || settingKey !== PrefKey.AUDIO_VOLUME) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$range.value = settingValue;
|
BxEventBus.Script.on('setting.changed', payload => {
|
||||||
BxEvent.dispatch($range, 'input', {
|
const { storageKey, settingKey, settingValue } = payload;
|
||||||
ignoreOnChange: true,
|
if (storageKey === StorageKey.GLOBAL && settingKey === PrefKey.AUDIO_VOLUME) {
|
||||||
});
|
$range.value = settingValue;
|
||||||
|
BxEvent.dispatch($range, 'input', { ignoreOnChange: true });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
@ -475,6 +476,9 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
}, {
|
}, {
|
||||||
pref: PrefKey.VIDEO_RATIO,
|
pref: PrefKey.VIDEO_RATIO,
|
||||||
onChange: updateVideoPlayer,
|
onChange: updateVideoPlayer,
|
||||||
|
}, {
|
||||||
|
pref: PrefKey.VIDEO_POSITION,
|
||||||
|
onChange: updateVideoPlayer,
|
||||||
}, {
|
}, {
|
||||||
pref: PrefKey.VIDEO_SHARPNESS,
|
pref: PrefKey.VIDEO_SHARPNESS,
|
||||||
onChange: updateVideoPlayer,
|
onChange: updateVideoPlayer,
|
||||||
@ -545,7 +549,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
|
|
||||||
// If there is no custom layouts -> show only Default option
|
// If there is no custom layouts -> show only Default option
|
||||||
if (!customLayouts) {
|
if (!customLayouts) {
|
||||||
$elm.appendChild(CE('option', {value: ''}, t('default')));
|
$elm.appendChild(CE('option', { value: '' }, t('default')));
|
||||||
$elm.value = '';
|
$elm.value = '';
|
||||||
$elm.dispatchEvent(new Event('input'));
|
$elm.dispatchEvent(new Event('input'));
|
||||||
return;
|
return;
|
||||||
@ -563,7 +567,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
name = layout.name;
|
name = layout.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $option = CE('option', {value: key}, name);
|
const $option = CE('option', { value: key }, name);
|
||||||
$fragment.appendChild($option);
|
$fragment.appendChild($option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,10 +630,10 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
pref: PrefKey.STATS_TEXT_SIZE,
|
pref: PrefKey.STATS_TEXT_SIZE,
|
||||||
onChange: StreamStats.refreshStyles,
|
onChange: StreamStats.refreshStyles,
|
||||||
}, {
|
}, {
|
||||||
pref: PrefKey.STATS_OPACITY,
|
pref: PrefKey.STATS_OPACITY_ALL,
|
||||||
onChange: StreamStats.refreshStyles,
|
onChange: StreamStats.refreshStyles,
|
||||||
}, {
|
}, {
|
||||||
pref: PrefKey.STATS_TRANSPARENT,
|
pref: PrefKey.STATS_OPACITY_BACKGROUND,
|
||||||
onChange: StreamStats.refreshStyles,
|
onChange: StreamStats.refreshStyles,
|
||||||
}, {
|
}, {
|
||||||
pref: PrefKey.STATS_CONDITIONAL_FORMATTING,
|
pref: PrefKey.STATS_CONDITIONAL_FORMATTING,
|
||||||
@ -855,7 +859,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
|
|
||||||
setting.options[value] = label;
|
setting.options[value] = label;
|
||||||
|
|
||||||
const $option = CE<HTMLOptionElement>('option', {value: value}, label);
|
const $option = CE<HTMLOptionElement>('option', { value }, label);
|
||||||
const continent = continents[region.contintent];
|
const continent = continents[region.contintent];
|
||||||
if (!continent.children) {
|
if (!continent.children) {
|
||||||
continent.children = [];
|
continent.children = [];
|
||||||
@ -998,9 +1002,9 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
|
|
||||||
let $note;
|
let $note;
|
||||||
if (unsupportedNote) {
|
if (unsupportedNote) {
|
||||||
$note = CE('div', {class: 'bx-settings-dialog-note'}, unsupportedNote);
|
$note = CE('div', { class: 'bx-settings-dialog-note' }, unsupportedNote);
|
||||||
} else if (note) {
|
} else if (note) {
|
||||||
$note = CE('div', {class: 'bx-settings-dialog-note'}, note);
|
$note = CE('div', { class: 'bx-settings-dialog-note' }, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $row = createSettingRow(label, !prefDefinition?.unsupported && $control, {
|
const $row = createSettingRow(label, !prefDefinition?.unsupported && $control, {
|
||||||
@ -1079,7 +1083,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
|
|
||||||
// Add note
|
// Add note
|
||||||
if (section.unsupportedNote) {
|
if (section.unsupportedNote) {
|
||||||
const $note = CE('b', {class: 'bx-note-unsupported'}, section.unsupportedNote);
|
const $note = CE('b', { class: 'bx-note-unsupported' }, section.unsupportedNote);
|
||||||
|
|
||||||
$tabContent.appendChild($note);
|
$tabContent.appendChild($note);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ export class ControllerExtraSettings extends HTMLElement {
|
|||||||
CE('div', { class: 'bx-controller-extra-wrapper' },
|
CE('div', { class: 'bx-controller-extra-wrapper' },
|
||||||
$selectControllers,
|
$selectControllers,
|
||||||
|
|
||||||
CE('div', {class: 'bx-sub-content-box'},
|
CE('div', { class: 'bx-sub-content-box' },
|
||||||
createSettingRow(
|
createSettingRow(
|
||||||
t('controller-shortcuts-in-game'),
|
t('controller-shortcuts-in-game'),
|
||||||
CE('div', {
|
CE('div', {
|
||||||
@ -136,7 +136,7 @@ export class ControllerExtraSettings extends HTMLElement {
|
|||||||
|
|
||||||
// Render shortcut presets
|
// Render shortcut presets
|
||||||
const allShortcutPresets = await ControllerShortcutsTable.getInstance().getPresets();
|
const allShortcutPresets = await ControllerShortcutsTable.getInstance().getPresets();
|
||||||
renderPresetsList(this.$selectShortcuts, allShortcutPresets, null, true);
|
renderPresetsList(this.$selectShortcuts, allShortcutPresets, null, { addOffValue: true });
|
||||||
|
|
||||||
for (const name of this.controllerIds) {
|
for (const name of this.controllerIds) {
|
||||||
const $option = CE<HTMLOptionElement>('option', { value: name }, name);
|
const $option = CE<HTMLOptionElement>('option', { value: name }, name);
|
||||||
|
@ -108,11 +108,11 @@ export class MkbExtraSettings extends HTMLElement {
|
|||||||
private static async updateLayout(this: MkbExtraSettings) {
|
private static async updateLayout(this: MkbExtraSettings) {
|
||||||
// Render shortcut presets
|
// Render shortcut presets
|
||||||
const mappingPresets = await MkbMappingPresetsTable.getInstance().getPresets();
|
const mappingPresets = await MkbMappingPresetsTable.getInstance().getPresets();
|
||||||
renderPresetsList(this.$mappingPresets, mappingPresets, getPref<MkbPresetId>(PrefKey.MKB_P1_MAPPING_PRESET_ID), false);
|
renderPresetsList(this.$mappingPresets, mappingPresets, getPref<MkbPresetId>(PrefKey.MKB_P1_MAPPING_PRESET_ID));
|
||||||
|
|
||||||
// Render shortcut presets
|
// Render shortcut presets
|
||||||
const shortcutsPresets = await KeyboardShortcutsTable.getInstance().getPresets();
|
const shortcutsPresets = await KeyboardShortcutsTable.getInstance().getPresets();
|
||||||
renderPresetsList(this.$shortcutsPresets, shortcutsPresets, getPref<MkbPresetId>(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID), true);
|
renderPresetsList(this.$shortcutsPresets, shortcutsPresets, getPref<MkbPresetId>(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID), { addOffValue: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async saveMkbSettings(this: MkbExtraSettings) {
|
private static async saveMkbSettings(this: MkbExtraSettings) {
|
||||||
|
@ -91,12 +91,12 @@ export class SuggestionsSetting {
|
|||||||
SuggestionsSetting.generateDefaultSuggestedSettings.call(this);
|
SuggestionsSetting.generateDefaultSuggestedSettings.call(this);
|
||||||
|
|
||||||
// Start rendering
|
// Start rendering
|
||||||
const $suggestedSettings = CE('div', {class: 'bx-suggest-wrapper'});
|
const $suggestedSettings = CE('div', { class: 'bx-suggest-wrapper' });
|
||||||
const $select = CE<HTMLSelectElement>('select', {},
|
const $select = CE<HTMLSelectElement>('select', {},
|
||||||
hasRecommendedSettings && CE('option', {value: 'recommended'}, t('recommended')),
|
hasRecommendedSettings && CE('option', { value: 'recommended' }, t('recommended')),
|
||||||
!hasRecommendedSettings && CE('option', {value: 'highest'}, t('highest-quality')),
|
!hasRecommendedSettings && CE('option', { value: 'highest' }, t('highest-quality')),
|
||||||
CE('option', {value: 'default'}, t('default')),
|
CE('option', { value: 'default' }, t('default')),
|
||||||
CE('option', {value: 'lowest'}, t('lowest-quality')),
|
CE('option', { value: 'lowest' }, t('lowest-quality')),
|
||||||
);
|
);
|
||||||
$select.addEventListener('input', e => {
|
$select.addEventListener('input', e => {
|
||||||
const profile = $select.value as SuggestedSettingProfile;
|
const profile = $select.value as SuggestedSettingProfile;
|
||||||
@ -107,13 +107,13 @@ export class SuggestionsSetting {
|
|||||||
|
|
||||||
let note: HTMLElement | string | undefined;
|
let note: HTMLElement | string | undefined;
|
||||||
if (profile === 'recommended') {
|
if (profile === 'recommended') {
|
||||||
note = t('recommended-settings-for-device', {device: recommendedDevice});
|
note = t('recommended-settings-for-device', { device: recommendedDevice });
|
||||||
} else if (profile === 'highest') {
|
} else if (profile === 'highest') {
|
||||||
// Add note for "Highest quality" profile
|
// Add note for "Highest quality" profile
|
||||||
note = '⚠️ ' + t('highest-quality-note');
|
note = '⚠️ ' + t('highest-quality-note');
|
||||||
}
|
}
|
||||||
|
|
||||||
note && fragment.appendChild(CE('div', {class: 'bx-suggest-note'}, note));
|
note && fragment.appendChild(CE('div', { class: 'bx-suggest-note' }, note));
|
||||||
|
|
||||||
const settings = this.suggestedSettings[profile];
|
const settings = this.suggestedSettings[profile];
|
||||||
let prefKey: PrefKey;
|
let prefKey: PrefKey;
|
||||||
@ -265,7 +265,7 @@ export class SuggestionsSetting {
|
|||||||
|
|
||||||
// Get recommended settings from GitHub
|
// Get recommended settings from GitHub
|
||||||
try {
|
try {
|
||||||
let {brand, board, model} = androidInfo!;
|
let { brand, board, model } = androidInfo!;
|
||||||
brand = normalize(brand);
|
brand = normalize(brand);
|
||||||
board = normalize(board);
|
board = normalize(board);
|
||||||
model = normalize(model);
|
model = normalize(model);
|
||||||
|
@ -28,7 +28,7 @@ export class GameTile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof totalWaitTime === 'number' && isElementVisible($elm)) {
|
if (typeof totalWaitTime === 'number' && isElementVisible($elm)) {
|
||||||
const $div = CE('div', {'class': 'bx-game-tile-wait-time'},
|
const $div = CE('div', { class: 'bx-game-tile-wait-time' },
|
||||||
createSvgIcon(BxIcon.PLAYTIME),
|
createSvgIcon(BxIcon.PLAYTIME),
|
||||||
CE('span', {}, secondsToHms(totalWaitTime)),
|
CE('span', {}, secondsToHms(totalWaitTime)),
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
import { AppInterface, STATES } from "@/utils/global";
|
import { AppInterface, STATES } from "@/utils/global";
|
||||||
@ -7,6 +7,7 @@ import { t } from "@/utils/translation";
|
|||||||
import { SettingsDialog } from "./dialog/settings-dialog";
|
import { SettingsDialog } from "./dialog/settings-dialog";
|
||||||
import { TrueAchievements } from "@/utils/true-achievements";
|
import { TrueAchievements } from "@/utils/true-achievements";
|
||||||
import { BxIcon } from "@/utils/bx-icon";
|
import { BxIcon } from "@/utils/bx-icon";
|
||||||
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
|
||||||
export enum GuideMenuTab {
|
export enum GuideMenuTab {
|
||||||
HOME = 'home',
|
HOME = 'home',
|
||||||
@ -40,9 +41,9 @@ export class GuideMenu {
|
|||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// Wait until the Guide dialog is closed
|
// Wait until the Guide dialog is closed
|
||||||
window.addEventListener(BxEvent.XCLOUD_DIALOG_DISMISSED, e => {
|
BxEventBus.Script.once('xcloudDialogDismissed', () => {
|
||||||
setTimeout(() => SettingsDialog.getInstance().show(), 50);
|
setTimeout(() => SettingsDialog.getInstance().show(), 50);
|
||||||
}, {once: true});
|
});
|
||||||
|
|
||||||
// Close all xCloud's dialogs
|
// Close all xCloud's dialogs
|
||||||
this.closeGuideMenu();
|
this.closeGuideMenu();
|
||||||
@ -218,7 +219,7 @@ export class GuideMenu {
|
|||||||
for (index = 0; ($elm = $elm?.previousElementSibling); index++);
|
for (index = 0; ($elm = $elm?.previousElementSibling); index++);
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_GUIDE_MENU_SHOWN, {where: GuideMenuTab.HOME});
|
BxEvent.dispatch(window, BxEvent.XCLOUD_GUIDE_MENU_SHOWN, { where: GuideMenuTab.HOME });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ export class HeaderSection {
|
|||||||
this.timeoutId && clearTimeout(this.timeoutId);
|
this.timeoutId && clearTimeout(this.timeoutId);
|
||||||
this.timeoutId = window.setTimeout(this.checkHeader, 2000);
|
this.timeoutId = window.setTimeout(this.checkHeader, 2000);
|
||||||
});
|
});
|
||||||
this.observer.observe($root, {subtree: true, childList: true});
|
this.observer.observe($root, { subtree: true, childList: true });
|
||||||
|
|
||||||
this.checkHeader();
|
this.checkHeader();
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ export class ProductDetailsPage {
|
|||||||
$container.parentElement.appendChild(CE('div', {
|
$container.parentElement.appendChild(CE('div', {
|
||||||
class: 'bx-product-details-buttons',
|
class: 'bx-product-details-buttons',
|
||||||
},
|
},
|
||||||
BX_FLAGS.DeviceInfo.deviceType === 'android' && ProductDetailsPage.$btnShortcut,
|
['android-handheld', 'android'].includes(BX_FLAGS.DeviceInfo.deviceType) && ProductDetailsPage.$btnShortcut,
|
||||||
ProductDetailsPage.$btnWallpaper,
|
ProductDetailsPage.$btnWallpaper,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -24,4 +24,4 @@ export function localRedirect(path: string) {
|
|||||||
$anchor.click();
|
$anchor.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).localRedirect = localRedirect;
|
window.localRedirect = localRedirect;
|
||||||
|
27
src/types/global.d.ts
vendored
27
src/types/global.d.ts
vendored
@ -2,6 +2,8 @@ import type { BxExposed } from "@/utils/bx-exposed";
|
|||||||
import type { AllPresets, ControllerShortcutPresetRecord } from "./presets";
|
import type { AllPresets, ControllerShortcutPresetRecord } from "./presets";
|
||||||
import type { PrefKey } from "@/enums/pref-keys";
|
import type { PrefKey } from "@/enums/pref-keys";
|
||||||
import type { StreamSettings, type StreamSettingsData } from "@/utils/stream-settings";
|
import type { StreamSettings, type StreamSettingsData } from "@/utils/stream-settings";
|
||||||
|
import type { BxEvent } from "@/utils/bx-event";
|
||||||
|
import type { BxLogger } from "@/utils/bx-logger";
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
@ -9,7 +11,7 @@ declare global {
|
|||||||
interface Window {
|
interface Window {
|
||||||
AppInterface: any;
|
AppInterface: any;
|
||||||
BX_FLAGS?: BxFlags;
|
BX_FLAGS?: BxFlags;
|
||||||
BX_CE: (elmName: string, props: {[index: string]: any}={}) => HTMLElement;
|
BX_CE: (elmName: string, props: { [index: string]: any }={}) => HTMLElement;
|
||||||
BX_EXPOSED: typeof BxExposed & Partial<{
|
BX_EXPOSED: typeof BxExposed & Partial<{
|
||||||
shouldShowSensorControls: boolean;
|
shouldShowSensorControls: boolean;
|
||||||
stopTakRendering: boolean;
|
stopTakRendering: boolean;
|
||||||
@ -24,5 +26,28 @@ declare global {
|
|||||||
|
|
||||||
BX_REMOTE_PLAY_CONFIG: BxStates.remotePlay.config;
|
BX_REMOTE_PLAY_CONFIG: BxStates.remotePlay.config;
|
||||||
BX_STREAM_SETTINGS: StreamSettingsData;
|
BX_STREAM_SETTINGS: StreamSettingsData;
|
||||||
|
|
||||||
|
BX_FETCH: typeof window['fetch'];
|
||||||
|
|
||||||
|
BxEvent: typeof BxEvent;
|
||||||
|
BxEventBus: typeof BxEventBus;
|
||||||
|
BxLogger: typeof BxLogger;
|
||||||
|
localRedirect: (path: stringn) => void;
|
||||||
|
testTouchLayout: (layout: any) => void;
|
||||||
|
|
||||||
|
chrome?: any;
|
||||||
|
|
||||||
|
// xCloud properties
|
||||||
|
xbcUser?: {
|
||||||
|
isSignedIn: boolean;
|
||||||
|
};
|
||||||
|
MSA: any;
|
||||||
|
MeControl: any;
|
||||||
|
adobe: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Navigator {
|
||||||
|
orgUserAgent?: string;
|
||||||
|
orgUserAgentData?: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
src/types/index.d.ts
vendored
2
src/types/index.d.ts
vendored
@ -162,3 +162,5 @@ type XboxAchievement = {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type OsName = 'windows' | 'tizen' | 'android';
|
||||||
|
6
src/types/preferences.d.ts
vendored
6
src/types/preferences.d.ts
vendored
@ -1,8 +1,8 @@
|
|||||||
export type PreferenceSetting = {
|
export type PreferenceSetting = {
|
||||||
default: any;
|
default: any;
|
||||||
optionsGroup?: string;
|
optionsGroup?: string;
|
||||||
options?: {[index: string]: string};
|
options?: { [index: string]: string };
|
||||||
multipleOptions?: {[index: string]: string};
|
multipleOptions?: { [index: string]: string };
|
||||||
unsupported?: boolean;
|
unsupported?: boolean;
|
||||||
unsupportedNote?: string | (() => HTMLElement);
|
unsupportedNote?: string | (() => HTMLElement);
|
||||||
note?: string | (() => HTMLElement);
|
note?: string | (() => HTMLElement);
|
||||||
@ -17,4 +17,4 @@ export type PreferenceSetting = {
|
|||||||
label?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PreferenceSettings = {[index in PrefKey]: PreferenceSetting};
|
export type PreferenceSettings = { [index in PrefKey]: PreferenceSetting };
|
||||||
|
2
src/types/presets.d.ts
vendored
2
src/types/presets.d.ts
vendored
@ -65,7 +65,7 @@ type KeyboardShortcutConvertedPresetData = KeyboardShortcutPresetData & {
|
|||||||
interface AllPresets<T extends PresetRecord> {
|
interface AllPresets<T extends PresetRecord> {
|
||||||
default: Array<number>,
|
default: Array<number>,
|
||||||
custom: Array<number>,
|
custom: Array<number>,
|
||||||
data: {[key: string]: T},
|
data: { [key: string]: T },
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AllPresetsData<T extends PresetRecord> {
|
interface AllPresetsData<T extends PresetRecord> {
|
||||||
|
139
src/utils/bx-event-bus.ts
Normal file
139
src/utils/bx-event-bus.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import type { PrefKey, StorageKey } from "@/enums/pref-keys";
|
||||||
|
import { BX_FLAGS } from "./bx-flags";
|
||||||
|
import { BxLogger } from "./bx-logger";
|
||||||
|
import { AppInterface } from "./global";
|
||||||
|
import type { MicrophoneState } from "@/modules/shortcuts/microphone-shortcut";
|
||||||
|
import type { SpeakerState } from "@/modules/shortcuts/sound-shortcut";
|
||||||
|
|
||||||
|
type EventCallback<T = any> = (payload: T) => void;
|
||||||
|
|
||||||
|
type ScriptEvents = {
|
||||||
|
'xcloud.server.ready': {};
|
||||||
|
'xcloud.server.unavailable': {};
|
||||||
|
|
||||||
|
'dialog.shown': {},
|
||||||
|
'dialog.dismissed': {},
|
||||||
|
|
||||||
|
'titleInfo.ready': {};
|
||||||
|
'setting.changed': {
|
||||||
|
storageKey: StorageKey;
|
||||||
|
settingKey: PrefKey;
|
||||||
|
settingValue: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
'mkb.setting.updated': {};
|
||||||
|
'keyboardShortcuts.updated': {};
|
||||||
|
'deviceVibration.updated': {};
|
||||||
|
|
||||||
|
// GH pages
|
||||||
|
'list.forcedNativeMkb.updated': {
|
||||||
|
data: {
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type StreamEvents = {
|
||||||
|
'state.loading': {};
|
||||||
|
'state.starting': {};
|
||||||
|
'state.playing': { $video?: HTMLVideoElement };
|
||||||
|
'state.stopped': {};
|
||||||
|
'state.error': {};
|
||||||
|
|
||||||
|
'gameBar.activated': {},
|
||||||
|
'speaker.state.changed': { state: SpeakerState },
|
||||||
|
'video.visibility.changed': { isVisible: boolean },
|
||||||
|
// Inside patch
|
||||||
|
'microphone.state.changed': { state: MicrophoneState },
|
||||||
|
|
||||||
|
dataChannelCreated: { dataChannel: RTCDataChannel };
|
||||||
|
};
|
||||||
|
|
||||||
|
export class BxEventBus<TEvents extends Record<string, any>> {
|
||||||
|
private listeners: Map<keyof TEvents, Set<EventCallback<any>>> = new Map();
|
||||||
|
private group: string;
|
||||||
|
private appJsInterfaces: { [key in keyof TEvents]?: string };
|
||||||
|
|
||||||
|
static readonly Script = new BxEventBus<ScriptEvents>('script', {
|
||||||
|
'dialog.shown': 'onDialogShown',
|
||||||
|
'dialog.dismissed': 'onDialogDismissed',
|
||||||
|
});
|
||||||
|
static readonly Stream = new BxEventBus<StreamEvents>('stream', {
|
||||||
|
'state.loading': 'onStreamPlaying',
|
||||||
|
'state.playing': 'onStreamPlaying',
|
||||||
|
'state.stopped': 'onStreamStopped',
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(group: string, appJsInterfaces: { [key in keyof TEvents]?: string }) {
|
||||||
|
this.group = group;
|
||||||
|
this.appJsInterfaces = appJsInterfaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
on<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): void {
|
||||||
|
if (!this.listeners.has(event)) {
|
||||||
|
this.listeners.set(event, new Set());
|
||||||
|
}
|
||||||
|
this.listeners.get(event)!.add(callback);
|
||||||
|
|
||||||
|
BX_FLAGS.Debug && BxLogger.warning('EventBus', 'on', event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
once<K extends keyof TEvents>(event: string, callback: EventCallback<TEvents[K]>): void {
|
||||||
|
const wrapper = (...args: any[]) => {
|
||||||
|
// @ts-ignore
|
||||||
|
callback(...args);
|
||||||
|
this.off(event, wrapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.on(event, wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
off<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]> | null): void {
|
||||||
|
BX_FLAGS.Debug && BxLogger.warning('EventBus', 'off', event, callback);
|
||||||
|
|
||||||
|
if (!callback) {
|
||||||
|
// Remove all listener callbacks
|
||||||
|
this.listeners.delete(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callbacks = this.listeners.get(event);
|
||||||
|
if (!callbacks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.delete(callback);
|
||||||
|
if (callbacks.size === 0) {
|
||||||
|
this.listeners.delete(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offAll(): void {
|
||||||
|
this.listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit<K extends keyof TEvents>(event: K, payload: TEvents[K]): void {
|
||||||
|
const callbacks = this.listeners.get(event) || [];
|
||||||
|
for (const callback of callbacks) {
|
||||||
|
callback(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call method inside Android app
|
||||||
|
if (AppInterface) {
|
||||||
|
try {
|
||||||
|
if (event in this.appJsInterfaces) {
|
||||||
|
const method = this.appJsInterfaces[event];
|
||||||
|
AppInterface[method] && AppInterface[method]();
|
||||||
|
} else {
|
||||||
|
AppInterface.onEventBus(this.group + '.' + (event as string));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BX_FLAGS.Debug && BxLogger.warning('EventBus', 'emit', event, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.BxEventBus = BxEventBus;
|
@ -4,59 +4,28 @@ import { BX_FLAGS } from "./bx-flags";
|
|||||||
|
|
||||||
|
|
||||||
export namespace BxEvent {
|
export namespace BxEvent {
|
||||||
export const JUMP_BACK_IN_READY = 'bx-jump-back-in-ready';
|
|
||||||
export const POPSTATE = 'bx-popstate';
|
export const POPSTATE = 'bx-popstate';
|
||||||
|
|
||||||
export const TITLE_INFO_READY = 'bx-title-info-ready';
|
|
||||||
|
|
||||||
export const SETTINGS_CHANGED = 'bx-settings-changed';
|
|
||||||
|
|
||||||
export const STREAM_LOADING = 'bx-stream-loading';
|
|
||||||
export const STREAM_STARTING = 'bx-stream-starting';
|
|
||||||
export const STREAM_STARTED = 'bx-stream-started';
|
|
||||||
export const STREAM_PLAYING = 'bx-stream-playing';
|
|
||||||
export const STREAM_STOPPED = 'bx-stream-stopped';
|
|
||||||
export const STREAM_ERROR_PAGE = 'bx-stream-error-page';
|
|
||||||
|
|
||||||
export const STREAM_WEBRTC_CONNECTED = 'bx-stream-webrtc-connected';
|
|
||||||
export const STREAM_WEBRTC_DISCONNECTED = 'bx-stream-webrtc-disconnected';
|
|
||||||
|
|
||||||
export const MKB_UPDATED = 'bx-mkb-updated';
|
|
||||||
export const KEYBOARD_SHORTCUTS_UPDATED = 'bx-keyboard-shortcuts-updated';
|
|
||||||
|
|
||||||
// export const STREAM_EVENT_TARGET_READY = 'bx-stream-event-target-ready';
|
// export const STREAM_EVENT_TARGET_READY = 'bx-stream-event-target-ready';
|
||||||
|
// Inside patch
|
||||||
export const STREAM_SESSION_READY = 'bx-stream-session-ready';
|
export const STREAM_SESSION_READY = 'bx-stream-session-ready';
|
||||||
|
|
||||||
export const CUSTOM_TOUCH_LAYOUTS_LOADED = 'bx-custom-touch-layouts-loaded';
|
export const CUSTOM_TOUCH_LAYOUTS_LOADED = 'bx-custom-touch-layouts-loaded';
|
||||||
export const TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready';
|
export const TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready';
|
||||||
|
|
||||||
|
// Inside app
|
||||||
export const REMOTE_PLAY_READY = 'bx-remote-play-ready';
|
export const REMOTE_PLAY_READY = 'bx-remote-play-ready';
|
||||||
export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed';
|
export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed';
|
||||||
|
|
||||||
export const XCLOUD_SERVERS_READY = 'bx-servers-ready';
|
// Inside patch
|
||||||
export const XCLOUD_SERVERS_UNAVAILABLE = 'bx-servers-unavailable';
|
|
||||||
|
|
||||||
export const DATA_CHANNEL_CREATED = 'bx-data-channel-created';
|
|
||||||
export const DEVICE_VIBRATION_CHANGED = 'bx-device-vibration-changed';
|
|
||||||
|
|
||||||
export const GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated';
|
|
||||||
export const MICROPHONE_STATE_CHANGED = 'bx-microphone-state-changed';
|
|
||||||
export const SPEAKER_STATE_CHANGED = 'bx-speaker-state-changed';
|
|
||||||
export const VIDEO_VISIBILITY_CHANGED = 'bx-video-visibility-changed';
|
|
||||||
|
|
||||||
export const CAPTURE_SCREENSHOT = 'bx-capture-screenshot';
|
export const CAPTURE_SCREENSHOT = 'bx-capture-screenshot';
|
||||||
|
|
||||||
export const POINTER_LOCK_REQUESTED = 'bx-pointer-lock-requested';
|
export const POINTER_LOCK_REQUESTED = 'bx-pointer-lock-requested';
|
||||||
export const POINTER_LOCK_EXITED = 'bx-pointer-lock-exited';
|
export const POINTER_LOCK_EXITED = 'bx-pointer-lock-exited';
|
||||||
|
|
||||||
|
// Inside patch
|
||||||
export const NAVIGATION_FOCUS_CHANGED = 'bx-nav-focus-changed';
|
export const NAVIGATION_FOCUS_CHANGED = 'bx-nav-focus-changed';
|
||||||
|
|
||||||
export const GH_PAGES_FORCE_NATIVE_MKB_UPDATED = 'bx-gh-pages-force-native-mkb-updated';
|
|
||||||
|
|
||||||
// xCloud Dialog events
|
|
||||||
export const XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown';
|
|
||||||
export const XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed';
|
|
||||||
|
|
||||||
export const XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown';
|
export const XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown';
|
||||||
|
|
||||||
export const XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed';
|
export const XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed';
|
||||||
@ -76,7 +45,6 @@ export namespace BxEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const event = new Event(eventName);
|
const event = new Event(eventName);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
(event as any)[key] = data[key];
|
(event as any)[key] = data[key];
|
||||||
@ -86,8 +54,8 @@ export namespace BxEvent {
|
|||||||
target.dispatchEvent(event);
|
target.dispatchEvent(event);
|
||||||
AppInterface && AppInterface.onEvent(eventName);
|
AppInterface && AppInterface.onEvent(eventName);
|
||||||
|
|
||||||
BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', eventName, data)
|
BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', eventName, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).BxEvent = BxEvent;
|
window.BxEvent = BxEvent;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { ControllerShortcut } from "@/modules/controller-shortcut";
|
import { ControllerShortcut } from "@/modules/controller-shortcut";
|
||||||
import { BxEvent } from "@utils/bx-event";
|
|
||||||
import { deepClone, STATES } from "@utils/global";
|
import { deepClone, STATES } from "@utils/global";
|
||||||
import { BxLogger } from "./bx-logger";
|
import { BxLogger } from "./bx-logger";
|
||||||
import { BX_FLAGS } from "./bx-flags";
|
import { BX_FLAGS } from "./bx-flags";
|
||||||
@ -11,6 +10,9 @@ import { getPref } from "./settings-storages/global-settings-storage";
|
|||||||
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
|
import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
|
||||||
import { TouchController } from "@/modules/touch-controller";
|
import { TouchController } from "@/modules/touch-controller";
|
||||||
import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values";
|
import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values";
|
||||||
|
import { Patcher, type PatchPage } from "@/modules/patcher/patcher";
|
||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
import { FeatureGates } from "./feature-gates";
|
||||||
|
|
||||||
export enum SupportedInputType {
|
export enum SupportedInputType {
|
||||||
CONTROLLER = 'Controller',
|
CONTROLLER = 'Controller',
|
||||||
@ -35,6 +37,15 @@ export const BxExposed = {
|
|||||||
BxLogger.error(LOG_TAG, e);
|
BxLogger.error(LOG_TAG, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override feature gates
|
||||||
|
try {
|
||||||
|
for (const exp in FeatureGates) {
|
||||||
|
state.experiments.overrideFeatureGates[exp.toLocaleLowerCase()] = FeatureGates[exp];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
BxLogger.error(LOG_TAG, e);
|
||||||
|
}
|
||||||
|
|
||||||
// Add list of games with custom layouts to the official list
|
// Add list of games with custom layouts to the official list
|
||||||
try {
|
try {
|
||||||
const sigls = state.xcloud.sigls;
|
const sigls = state.xcloud.sigls;
|
||||||
@ -138,7 +149,7 @@ export const BxExposed = {
|
|||||||
|
|
||||||
// Save this info in STATES
|
// Save this info in STATES
|
||||||
STATES.currentStream.titleInfo = titleInfo;
|
STATES.currentStream.titleInfo = titleInfo;
|
||||||
BxEvent.dispatch(window, BxEvent.TITLE_INFO_READY);
|
BxEventBus.Script.emit('titleInfo.ready', {});
|
||||||
|
|
||||||
return titleInfo;
|
return titleInfo;
|
||||||
},
|
},
|
||||||
@ -208,5 +219,10 @@ export const BxExposed = {
|
|||||||
/ /g,
|
/ /g,
|
||||||
],
|
],
|
||||||
|
|
||||||
toggleLocalCoOp: (enable: boolean) => {},
|
toggleLocalCoOp(enable: boolean) {},
|
||||||
|
|
||||||
|
beforePageLoad: isFullVersion() ? (page: PatchPage) => {
|
||||||
|
BxLogger.info('beforePageLoad', page);
|
||||||
|
Patcher.patchPage(page);
|
||||||
|
} : () => {},
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ export type BxFlags = {
|
|||||||
SafariWorkaround: boolean;
|
SafariWorkaround: boolean;
|
||||||
|
|
||||||
ForceNativeMkbTitles: string[];
|
ForceNativeMkbTitles: string[];
|
||||||
FeatureGates: {[key: string]: boolean} | null,
|
FeatureGates: { [key: string]: boolean } | null,
|
||||||
|
|
||||||
DeviceInfo: {
|
DeviceInfo: {
|
||||||
deviceType: 'android' | 'android-tv' | 'android-handheld' | 'webos' | 'unknown',
|
deviceType: 'android' | 'android-tv' | 'android-handheld' | 'webos' | 'unknown',
|
||||||
|
@ -7,13 +7,13 @@ const enum TextColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BxLogger {
|
export class BxLogger {
|
||||||
static info = (tag: string, ...args: any[]) => BxLogger.log(TextColor.INFO, tag, ...args);
|
static info = (tag: string, ...args: any[]) => BX_FLAGS.Debug && BxLogger.log(TextColor.INFO, tag, ...args);
|
||||||
static warning = (tag: string, ...args: any[]) => BxLogger.log(TextColor.WARNING, tag, ...args);
|
static warning = (tag: string, ...args: any[]) => BX_FLAGS.Debug && BxLogger.log(TextColor.WARNING, tag, ...args);
|
||||||
static error = (tag: string, ...args: any[]) => BxLogger.log(TextColor.ERROR, tag, ...args);
|
static error = (tag: string, ...args: any[]) => BxLogger.log(TextColor.ERROR, tag, ...args);
|
||||||
|
|
||||||
private static log(color: string, tag: string, ...args: any) {
|
private static log(color: string, tag: string, ...args: any) {
|
||||||
BX_FLAGS.Debug && console.log(`%c[BxC]`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
console.log(`%c[BxC]`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).BxLogger = BxLogger;
|
window.BxLogger = BxLogger;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CE } from "@utils/html";
|
import { CE } from "@utils/html";
|
||||||
import { compressCss, renderStylus } from "@macros/build" with {type: "macro"};
|
import { compressCss, renderStylus } from "@macros/build" with { type: "macro" };
|
||||||
import { UiSection } from "@/enums/pref-values";
|
import { BlockFeature, UiSection } from "@/enums/pref-values";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ export function addCss() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide BYOG section
|
// Hide BYOG section
|
||||||
if (getPref(PrefKey.BYOG_DISABLED)) {
|
if (getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.BYOG)) {
|
||||||
selectorToHide.push('#BodyContent > div[class*=ByogRow-module__container___]');
|
selectorToHide.push('#BodyContent > div[class*=ByogRow-module__container___]');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ export function addCss() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide "Start a party" button in the Guide menu
|
// Hide "Start a party" button in the Guide menu
|
||||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
if (getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
|
||||||
selectorToHide.push('#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]');
|
selectorToHide.push('#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { BX_FLAGS } from "./bx-flags";
|
import { BX_FLAGS } from "./bx-flags";
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
import { NativeMkbMode } from "@/enums/pref-values";
|
import { BlockFeature, NativeMkbMode } from "@/enums/pref-values";
|
||||||
|
|
||||||
export let FeatureGates: {[key: string]: boolean} = {
|
export let FeatureGates: { [key: string]: boolean } = {
|
||||||
PwaPrompt: false,
|
PwaPrompt: false,
|
||||||
EnableWifiWarnings: false,
|
EnableWifiWarnings: false,
|
||||||
EnableUpdateRequiredPage: false,
|
EnableUpdateRequiredPage: false,
|
||||||
@ -17,12 +17,17 @@ if (nativeMkbMode !== NativeMkbMode.DEFAULT) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Disable chat feature
|
// Disable chat feature
|
||||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||||
|
if (blockFeatures.includes(BlockFeature.CHAT)) {
|
||||||
FeatureGates.EnableGuideChatTab = false;
|
FeatureGates.EnableGuideChatTab = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (blockFeatures.includes(BlockFeature.FRIENDS)) {
|
||||||
|
FeatureGates.EnableFriendsAndFollowers = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Disable BYOG feature
|
// Disable BYOG feature
|
||||||
if (getPref(PrefKey.BYOG_DISABLED)) {
|
if (blockFeatures.includes(BlockFeature.BYOG)) {
|
||||||
FeatureGates.EnableBYOG = false;
|
FeatureGates.EnableBYOG = false;
|
||||||
FeatureGates.EnableBYOGPurchase = false;
|
FeatureGates.EnableBYOGPurchase = false;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ export function showGamepadToast(gamepad: Gamepad) {
|
|||||||
status = t('disconnected');
|
status = t('disconnected');
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast.show(text, status, {instant: false});
|
Toast.show(text, status, { instant: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUniqueGamepadNames() {
|
export function getUniqueGamepadNames() {
|
||||||
@ -57,3 +57,36 @@ export function hasGamepad() {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateVirtualControllerMapping(override: {}={}) {
|
||||||
|
const mapping = {
|
||||||
|
GamepadIndex: 0,
|
||||||
|
A: 0,
|
||||||
|
B: 0,
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
LeftShoulder: 0,
|
||||||
|
RightShoulder: 0,
|
||||||
|
LeftTrigger: 0,
|
||||||
|
RightTrigger: 0,
|
||||||
|
View: 0,
|
||||||
|
Menu: 0,
|
||||||
|
LeftThumb: 0,
|
||||||
|
RightThumb: 0,
|
||||||
|
DPadUp: 0,
|
||||||
|
DPadDown: 0,
|
||||||
|
DPadLeft: 0,
|
||||||
|
DPadRight: 0,
|
||||||
|
Nexus: 0,
|
||||||
|
LeftThumbXAxis: 0,
|
||||||
|
LeftThumbYAxis: 0,
|
||||||
|
RightThumbXAxis: 0,
|
||||||
|
RightThumbYAxis: 0,
|
||||||
|
PhysicalPhysicality: 0,
|
||||||
|
VirtualPhysicality: 0,
|
||||||
|
Dirty: false,
|
||||||
|
Virtual: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.assign({}, mapping, override);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { StorageKey } from "@/enums/pref-keys";
|
import { StorageKey } from "@/enums/pref-keys";
|
||||||
import { NATIVE_FETCH } from "./bx-flags";
|
import { NATIVE_FETCH } from "./bx-flags";
|
||||||
import { BxLogger } from "./bx-logger";
|
import { BxLogger } from "./bx-logger";
|
||||||
import { BxEvent } from "./bx-event";
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
export type ForceNativeMkbResponse = {
|
export type ForceNativeMkbResponse = {
|
||||||
@ -53,7 +53,9 @@ export class GhPagesUtils {
|
|||||||
if (json.$schemaVersion === supportedSchema) {
|
if (json.$schemaVersion === supportedSchema) {
|
||||||
// Save to storage
|
// Save to storage
|
||||||
window.localStorage.setItem(key, JSON.stringify(json));
|
window.localStorage.setItem(key, JSON.stringify(json));
|
||||||
BxEvent.dispatch(window, BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED);
|
BxEventBus.Script.emit('list.forcedNativeMkb.updated', {
|
||||||
|
data: json,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ export const STATES: BxStates = {
|
|||||||
pointerServerPort: 9269,
|
pointerServerPort: 9269,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const STORAGE: {[key: string]: BaseSettingsStore} = {};
|
export const STORAGE: { [key: string]: BaseSettingsStore } = {};
|
||||||
|
|
||||||
export function deepClone(obj: any): typeof obj | {} {
|
export function deepClone(obj: any): typeof obj | {} {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
|
@ -2,6 +2,7 @@ import { BxEvent } from "@utils/bx-event";
|
|||||||
import { LoadingScreen } from "@modules/loading-screen";
|
import { LoadingScreen } from "@modules/loading-screen";
|
||||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||||
import { HeaderSection } from "@/modules/ui/header";
|
import { HeaderSection } from "@/modules/ui/header";
|
||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||||
|
|
||||||
export function patchHistoryMethod(type: 'pushState' | 'replaceState') {
|
export function patchHistoryMethod(type: 'pushState' | 'replaceState') {
|
||||||
@ -9,8 +10,8 @@ export function patchHistoryMethod(type: 'pushState' | 'replaceState') {
|
|||||||
|
|
||||||
return function(...args: any[]) {
|
return function(...args: any[]) {
|
||||||
BxEvent.dispatch(window, BxEvent.POPSTATE, {
|
BxEvent.dispatch(window, BxEvent.POPSTATE, {
|
||||||
arguments: args,
|
arguments: args,
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return orig.apply(this, arguments);
|
return orig.apply(this, arguments);
|
||||||
@ -26,17 +27,11 @@ export function onHistoryChanged(e: PopStateEvent) {
|
|||||||
|
|
||||||
window.setTimeout(RemotePlayManager.detect, 10);
|
window.setTimeout(RemotePlayManager.detect, 10);
|
||||||
|
|
||||||
// Hide Global settings
|
|
||||||
const $settings = document.querySelector('.bx-settings-container');
|
|
||||||
if ($settings) {
|
|
||||||
$settings.classList.add('bx-gone');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide Navigation dialog
|
// Hide Navigation dialog
|
||||||
NavigationDialogManager.getInstance().hide();
|
NavigationDialogManager.getInstance().hide();
|
||||||
|
|
||||||
LoadingScreen.reset();
|
LoadingScreen.reset();
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
BxEventBus.Stream.emit('state.stopped', {});
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ export type BxButtonOptions = Partial<{
|
|||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
onClick: EventListener;
|
onClick: EventListener;
|
||||||
tabIndex: number;
|
tabIndex: number;
|
||||||
attributes: {[key: string]: any},
|
attributes: { [key: string]: any },
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type SettingsRowOptions = Partial<{
|
export type SettingsRowOptions = Partial<{
|
||||||
@ -186,7 +186,7 @@ export function createSettingRow(label: string, $control: HTMLElement | false |
|
|||||||
let $label: HTMLElement;
|
let $label: HTMLElement;
|
||||||
|
|
||||||
const $row = CE<HTMLLabelElement>('label', { class: 'bx-settings-row' },
|
const $row = CE<HTMLLabelElement>('label', { class: 'bx-settings-row' },
|
||||||
$label = CE('span', {class: 'bx-settings-label'},
|
$label = CE('span', { class: 'bx-settings-label' },
|
||||||
label,
|
label,
|
||||||
options.$note,
|
options.$note,
|
||||||
),
|
),
|
||||||
@ -263,10 +263,10 @@ export function clearDataSet($elm: HTMLElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectElement, allPresets: AllPresets<T>, selectedValue: number | null, addOffValue=false) {
|
export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectElement, allPresets: AllPresets<T>, selectedValue: number | null, options: { addOffValue?: boolean, selectedIndicator?: boolean }={}) {
|
||||||
removeChildElements($select);
|
removeChildElements($select);
|
||||||
|
|
||||||
if (addOffValue) {
|
if (options.addOffValue) {
|
||||||
const $option = CE<HTMLOptionElement>('option', { value: 0 }, t('off'));
|
const $option = CE<HTMLOptionElement>('option', { value: 0 }, t('off'));
|
||||||
$option.selected = selectedValue === 0;
|
$option.selected = selectedValue === 0;
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
|||||||
|
|
||||||
// Render options
|
// Render options
|
||||||
const groups = {
|
const groups = {
|
||||||
default: t('default'),
|
default: t('default') + ' 🔒',
|
||||||
custom: t('custom'),
|
custom: t('custom'),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -284,8 +284,13 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
|||||||
const $optGroup = CE('optgroup', { label: groups[key] });
|
const $optGroup = CE('optgroup', { label: groups[key] });
|
||||||
for (const id of allPresets[key]) {
|
for (const id of allPresets[key]) {
|
||||||
const record = allPresets.data[id];
|
const record = allPresets.data[id];
|
||||||
const $option = CE<HTMLOptionElement>('option', { value: record.id }, record.name);
|
const selected = selectedValue === record.id;
|
||||||
$option.selected = selectedValue === record.id;
|
const name = options.selectedIndicator && selected ? '✅ ' + record.name : record.name;
|
||||||
|
|
||||||
|
const $option = CE<HTMLOptionElement>('option', { value: record.id }, name);
|
||||||
|
if (selected) {
|
||||||
|
$option.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
$optGroup.appendChild($option);
|
$optGroup.appendChild($option);
|
||||||
}
|
}
|
||||||
@ -300,7 +305,7 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
|||||||
const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
export function humanFileSize(size: number) {
|
export function humanFileSize(size: number) {
|
||||||
const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
||||||
return (size / Math.pow(1024, i)).toFixed(2) + ' ' + FILE_SIZE_UNITS[i];
|
return (size / Math.pow(1024, i)).toFixed(1) + ' ' + FILE_SIZE_UNITS[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function secondsToHm(seconds: number) {
|
export function secondsToHm(seconds: number) {
|
||||||
|
@ -55,11 +55,11 @@ export class BaseLocalTable<T extends BaseRecord> {
|
|||||||
return this.call(table.get.bind(table), ...arguments);
|
return this.call(table.get.bind(table), ...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(): Promise<{[key: string]: T}> {
|
async getAll(): Promise<{ [key: string]: T }> {
|
||||||
const table = await this.prepareTable();
|
const table = await this.prepareTable();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const all = await (this.call(table.getAll.bind(table), ...arguments) as Promise<T[]>);
|
const all = await (this.call(table.getAll.bind(table), ...arguments) as Promise<T[]>);
|
||||||
const results: {[key: string]: T} = {};
|
const results: { [key: string]: T } = {};
|
||||||
|
|
||||||
all.forEach(item => {
|
all.forEach(item => {
|
||||||
results[item.id as T['id']] = item;
|
results[item.id as T['id']] = item;
|
||||||
|
@ -23,9 +23,13 @@ export class ControllerSettingsTable extends BaseLocalTable<ControllerSettingsRe
|
|||||||
|
|
||||||
async getControllersData() {
|
async getControllersData() {
|
||||||
const all = await this.getAll();
|
const all = await this.getAll();
|
||||||
const results: {[key: string]: ControllerSettingsRecord['data']} = {};
|
const results: { [key: string]: ControllerSettingsRecord['data'] } = {};
|
||||||
|
|
||||||
for (const key in all) {
|
for (const key in all) {
|
||||||
|
if (!all[key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const settings = all[key].data;
|
const settings = all[key].data;
|
||||||
// Pre-calculate virabtionIntensity
|
// Pre-calculate virabtionIntensity
|
||||||
settings.vibrationIntensity /= 100;
|
settings.vibrationIntensity /= 100;
|
||||||
|
@ -7,6 +7,7 @@ import { PrefKey } from "@/enums/pref-keys";
|
|||||||
import { getPref, getPrefDefinition } from "./settings-storages/global-settings-storage";
|
import { getPref, getPrefDefinition } from "./settings-storages/global-settings-storage";
|
||||||
import { CodecProfile } from "@/enums/pref-values";
|
import { CodecProfile } from "@/enums/pref-values";
|
||||||
import type { SettingDefinition } from "@/types/setting-definition";
|
import type { SettingDefinition } from "@/types/setting-definition";
|
||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
|
||||||
export function patchVideoApi() {
|
export function patchVideoApi() {
|
||||||
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.UI_SKIP_SPLASH_VIDEO);
|
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.UI_SKIP_SPLASH_VIDEO);
|
||||||
@ -27,9 +28,9 @@ export function patchVideoApi() {
|
|||||||
} satisfies StreamPlayerOptions;
|
} satisfies StreamPlayerOptions;
|
||||||
STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref(PrefKey.VIDEO_PLAYER_TYPE), playerOptions);
|
STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref(PrefKey.VIDEO_PLAYER_TYPE), playerOptions);
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_PLAYING, {
|
BxEventBus.Stream.emit('state.playing', {
|
||||||
$video: this,
|
$video: this,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const nativePlay = HTMLMediaElement.prototype.play;
|
const nativePlay = HTMLMediaElement.prototype.play;
|
||||||
@ -50,7 +51,7 @@ export function patchVideoApi() {
|
|||||||
const $parent = this.parentElement!!;
|
const $parent = this.parentElement!!;
|
||||||
// Video tag is stream player
|
// Video tag is stream player
|
||||||
if (!this.src && $parent.dataset.testid === 'media-container') {
|
if (!this.src && $parent.dataset.testid === 'media-container') {
|
||||||
this.addEventListener('loadedmetadata', showFunc, {once: true});
|
this.addEventListener('loadedmetadata', showFunc, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return nativePlay.apply(this);
|
return nativePlay.apply(this);
|
||||||
@ -75,10 +76,7 @@ export function patchRtcPeerConnection() {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const dataChannel = nativeCreateDataChannel.apply(this, arguments);
|
const dataChannel = nativeCreateDataChannel.apply(this, arguments);
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.DATA_CHANNEL_CREATED, {
|
BxEventBus.Stream.emit('dataChannelCreated', { dataChannel });
|
||||||
dataChannel: dataChannel,
|
|
||||||
});
|
|
||||||
|
|
||||||
return dataChannel;
|
return dataChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,8 +198,8 @@ export function patchMeControl() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
(window as any).MSA = new Proxy(MSA, MsaHandler);
|
window.MSA = new Proxy(MSA, MsaHandler);
|
||||||
(window as any).MeControl = new Proxy(MeControl, MeControlHandler);
|
window.MeControl = new Proxy(MeControl, MeControlHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -209,7 +207,9 @@ export function patchMeControl() {
|
|||||||
* Disable Adobe Audience Manager (AAM)
|
* Disable Adobe Audience Manager (AAM)
|
||||||
*/
|
*/
|
||||||
export function disableAdobeAudienceManager() {
|
export function disableAdobeAudienceManager() {
|
||||||
(window as any).adobe = Object.freeze({});
|
Object.defineProperty(window, 'adobe', {
|
||||||
|
get() { return Object.freeze({}); }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { BX_FLAGS, NATIVE_FETCH } from "@utils/bx-flags";
|
import { BX_FLAGS, NATIVE_FETCH } from "@utils/bx-flags";
|
||||||
import { TouchController } from "@modules/touch-controller";
|
import { TouchController } from "@modules/touch-controller";
|
||||||
@ -11,6 +11,8 @@ import { XcloudInterceptor } from "./xcloud-interceptor";
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
import type { RemotePlayConsoleAddresses } from "@/types/network";
|
import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||||
|
import { BlockFeature, StreamResolution } from "@/enums/pref-values";
|
||||||
|
import { blockAllNotifications } from "./utils";
|
||||||
|
|
||||||
type RequestType = 'xcloud' | 'xhome';
|
type RequestType = 'xcloud' | 'xhome';
|
||||||
|
|
||||||
@ -39,7 +41,7 @@ function clearAllLogs() {
|
|||||||
clearDbLogs('XCloudAppLogs', 'logs');
|
clearDbLogs('XCloudAppLogs', 'logs');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateIceCandidates(candidates: any, options: {preferIpv6Server: boolean, consoleAddrs?: RemotePlayConsoleAddresses}) {
|
function updateIceCandidates(candidates: any, options: { preferIpv6Server: boolean, consoleAddrs?: RemotePlayConsoleAddresses }) {
|
||||||
const pattern = new RegExp(/a=candidate:(?<foundation>\d+) (?<component>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<port>\d+) (?<the_rest>.*)/);
|
const pattern = new RegExp(/a=candidate:(?<foundation>\d+) (?<component>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<port>\d+) (?<the_rest>.*)/);
|
||||||
|
|
||||||
const lst = [];
|
const lst = [];
|
||||||
@ -48,7 +50,7 @@ function updateIceCandidates(candidates: any, options: {preferIpv6Server: boolea
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groups: {[index: string]: string | number} = pattern.exec(item.candidate)!.groups!;
|
const groups: { [index: string]: string | number } = pattern.exec(item.candidate)!.groups!;
|
||||||
lst.push(groups);
|
lst.push(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,29 +122,44 @@ export async function patchIceCandidates(request: Request, consoleAddrs?: Remote
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function interceptHttpRequests() {
|
export function interceptHttpRequests() {
|
||||||
let BLOCKED_URLS: string[] = [];
|
let BLOCKED_URLS: string[] = [];
|
||||||
if (getPref(PrefKey.BLOCK_TRACKING)) {
|
if (getPref(PrefKey.BLOCK_TRACKING)) {
|
||||||
// Clear Applications Insight buffers
|
// Clear Applications Insight buffers
|
||||||
clearAllLogs();
|
clearAllLogs();
|
||||||
|
|
||||||
BLOCKED_URLS = BLOCKED_URLS.concat([
|
BLOCKED_URLS.push(
|
||||||
'https://arc.msn.com',
|
'https://arc.msn.com',
|
||||||
'https://browser.events.data.microsoft.com',
|
'https://browser.events.data.microsoft.com',
|
||||||
'https://dc.services.visualstudio.com',
|
'https://dc.services.visualstudio.com',
|
||||||
'https://2c06dea3f26c40c69b8456d319791fd0@o427368.ingest.sentry.io',
|
'https://2c06dea3f26c40c69b8456d319791fd0@o427368.ingest.sentry.io',
|
||||||
'https://mscom.demdex.net',
|
'https://mscom.demdex.net',
|
||||||
]);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
|
||||||
BLOCKED_URLS = BLOCKED_URLS.concat([
|
// 'https://notificationinbox.xboxlive.com',
|
||||||
|
// 'https://accounts.xboxlive.com/family/memberXuid',
|
||||||
|
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||||
|
if (blockFeatures.includes(BlockFeature.CHAT)) {
|
||||||
|
BLOCKED_URLS.push(
|
||||||
|
'https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockFeatures.includes(BlockFeature.FRIENDS)) {
|
||||||
|
BLOCKED_URLS.push(
|
||||||
'https://peoplehub.xboxlive.com/users/me/people/social',
|
'https://peoplehub.xboxlive.com/users/me/people/social',
|
||||||
'https://peoplehub.xboxlive.com/users/me/people/recommendations',
|
'https://peoplehub.xboxlive.com/users/me/people/recommendations',
|
||||||
'https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox',
|
);
|
||||||
// 'https://notificationinbox.xboxlive.com',
|
}
|
||||||
// 'https://accounts.xboxlive.com/family/memberXuid',
|
|
||||||
]);
|
// Block all notifications
|
||||||
|
if (blockAllNotifications()) {
|
||||||
|
BLOCKED_URLS.push(
|
||||||
|
'https://notificationinbox.xboxlive.com/',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const xhrPrototype = XMLHttpRequest.prototype;
|
const xhrPrototype = XMLHttpRequest.prototype;
|
||||||
@ -157,11 +174,13 @@ export function interceptHttpRequests() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
xhrPrototype.send = function(...arg) {
|
xhrPrototype.send = function(...arg) {
|
||||||
for (const blocked of BLOCKED_URLS) {
|
for (const url of BLOCKED_URLS) {
|
||||||
if ((this as any)._url.startsWith(blocked)) {
|
if ((this as any)._url.startsWith(url)) {
|
||||||
if (blocked === 'https://dc.services.visualstudio.com') {
|
if (url === 'https://dc.services.visualstudio.com') {
|
||||||
window.setTimeout(clearAllLogs, 1000);
|
window.setTimeout(clearAllLogs, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BxLogger.warning('Blocked URL', url);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,6 +194,7 @@ export function interceptHttpRequests() {
|
|||||||
'chat.xboxlive.com',
|
'chat.xboxlive.com',
|
||||||
'notificationinbox.xboxlive.com',
|
'notificationinbox.xboxlive.com',
|
||||||
'peoplehub.xboxlive.com',
|
'peoplehub.xboxlive.com',
|
||||||
|
'peoplehub-public.xboxlive.com',
|
||||||
'rta.xboxlive.com',
|
'rta.xboxlive.com',
|
||||||
'userpresence.xboxlive.com',
|
'userpresence.xboxlive.com',
|
||||||
'xblmessaging.xboxlive.com',
|
'xblmessaging.xboxlive.com',
|
||||||
@ -186,12 +206,13 @@ export function interceptHttpRequests() {
|
|||||||
'2c06dea3f26c40c69b8456d319791fd0@o427368.ingest.sentry.io',
|
'2c06dea3f26c40c69b8456d319791fd0@o427368.ingest.sentry.io',
|
||||||
];
|
];
|
||||||
|
|
||||||
(window as any).BX_FETCH = window.fetch = async (request: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
window.BX_FETCH = window.fetch = async (request: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||||
let url = (typeof request === 'string') ? request : (request as Request).url;
|
let url = (typeof request === 'string') ? request : (request as Request).url;
|
||||||
|
|
||||||
// Check blocked URLs
|
// Check blocked URLs
|
||||||
for (let blocked of BLOCKED_URLS) {
|
for (const blocked of BLOCKED_URLS) {
|
||||||
if (url.startsWith(blocked)) {
|
if (url.startsWith(blocked)) {
|
||||||
|
BxLogger.warning('Blocked URL', url);
|
||||||
return new Response('{"acc":1,"webResult":{}}', {
|
return new Response('{"acc":1,"webResult":{}}', {
|
||||||
status: 200,
|
status: 200,
|
||||||
statusText: '200 OK',
|
statusText: '200 OK',
|
||||||
@ -199,7 +220,7 @@ export function interceptHttpRequests() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore URLs
|
// Ignore domains
|
||||||
const domain = (new URL(url)).hostname;
|
const domain = (new URL(url)).hostname;
|
||||||
if (IGNORED_DOMAINS.includes(domain)) {
|
if (IGNORED_DOMAINS.includes(domain)) {
|
||||||
return NATIVE_FETCH(request, init);
|
return NATIVE_FETCH(request, init);
|
||||||
@ -283,3 +304,45 @@ export function interceptHttpRequests() {
|
|||||||
return XcloudInterceptor.handle(request, init);
|
return XcloudInterceptor.handle(request, init);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function generateMsDeviceInfo(osName: OsName) {
|
||||||
|
return {
|
||||||
|
appInfo: {
|
||||||
|
env: {
|
||||||
|
clientAppId: window.location.host,
|
||||||
|
clientAppType: 'browser',
|
||||||
|
clientAppVersion: '26.1.97',
|
||||||
|
clientSdkVersion: '10.3.7',
|
||||||
|
httpEnvironment: 'prod',
|
||||||
|
sdkInstallId: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
os: { name: osName, ver: '22631.2715', platform: 'desktop' },
|
||||||
|
hw: { make: 'Microsoft', model: 'unknown', sdktype: 'web' },
|
||||||
|
browser: { browserName: 'chrome', browserVersion: '130.0' },
|
||||||
|
displayInfo: {
|
||||||
|
dimensions: { widthInPixels: 1920, heightInPixels: 1080 },
|
||||||
|
pixelDensity: { dpiX: 1, dpiY: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOsNameFromResolution(resolution: StreamResolution): OsName {
|
||||||
|
let osName: OsName;
|
||||||
|
switch (resolution) {
|
||||||
|
case StreamResolution.DIM_1080P_HQ:
|
||||||
|
osName = 'tizen';
|
||||||
|
break;
|
||||||
|
case StreamResolution.DIM_1080P:
|
||||||
|
osName = 'windows';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
osName = 'android';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return osName;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { GuideMenu } from "@/modules/ui/guide-menu";
|
import { GuideMenu } from "@/modules/ui/guide-menu";
|
||||||
import { BxEvent } from "./bx-event";
|
|
||||||
import { BX_FLAGS } from "./bx-flags";
|
import { BX_FLAGS } from "./bx-flags";
|
||||||
import { BxLogger } from "./bx-logger";
|
import { BxLogger } from "./bx-logger";
|
||||||
import { BxIcon } from "./bx-icon";
|
import { BxIcon } from "./bx-icon";
|
||||||
@ -7,6 +6,7 @@ import { AppInterface } from "./global";
|
|||||||
import { createButton, ButtonStyle } from "./html";
|
import { createButton, ButtonStyle } from "./html";
|
||||||
import { t } from "./translation";
|
import { t } from "./translation";
|
||||||
import { parseDetailsPath } from "./utils";
|
import { parseDetailsPath } from "./utils";
|
||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
export class RootDialogObserver {
|
export class RootDialogObserver {
|
||||||
@ -85,11 +85,11 @@ export class RootDialogObserver {
|
|||||||
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
||||||
if (shown !== beingShown) {
|
if (shown !== beingShown) {
|
||||||
beingShown = shown;
|
beingShown = shown;
|
||||||
BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED);
|
BxEventBus.Script.emit(shown ? 'dialog.shown' : 'dialog.dismissed', {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
observer.observe($root, {subtree: true, childList: true});
|
observer.observe($root, { subtree: true, childList: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static waitForRootDialog() {
|
public static waitForRootDialog() {
|
||||||
@ -107,6 +107,6 @@ export class RootDialogObserver {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
observer.observe(document.documentElement, {subtree: true, childList: true});
|
observer.observe(document.documentElement, { subtree: true, childList: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export class ScreenshotManager {
|
|||||||
|
|
||||||
this.$download = CE<HTMLAnchorElement>('a');
|
this.$download = CE<HTMLAnchorElement>('a');
|
||||||
|
|
||||||
this.$canvas = CE<HTMLCanvasElement>('canvas', {'class': 'bx-gone'});
|
this.$canvas = CE<HTMLCanvasElement>('canvas', { class: 'bx-gone' });
|
||||||
this.canvasContext = this.$canvas.getContext('2d', {
|
this.canvasContext = this.$canvas.getContext('2d', {
|
||||||
alpha: false,
|
alpha: false,
|
||||||
willReadFrequently: false,
|
willReadFrequently: false,
|
||||||
@ -59,8 +59,11 @@ export class ScreenshotManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$player.parentElement!.addEventListener('animationend', this.onAnimationEnd, { once: true });
|
const $gameStream = $player.closest('#game-stream');
|
||||||
$player.parentElement!.classList.add('bx-taking-screenshot');
|
if ($gameStream) {
|
||||||
|
$gameStream.addEventListener('animationend', this.onAnimationEnd, { once: true });
|
||||||
|
$gameStream.classList.add('bx-taking-screenshot');
|
||||||
|
}
|
||||||
|
|
||||||
const canvasContext = this.canvasContext;
|
const canvasContext = this.canvasContext;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export class SettingElement {
|
|||||||
for (let value in setting.options) {
|
for (let value in setting.options) {
|
||||||
const label = setting.options[value];
|
const label = setting.options[value];
|
||||||
|
|
||||||
const $option = CE<HTMLOptionElement>('option', {value: value}, label);
|
const $option = CE<HTMLOptionElement>('option', { value }, label);
|
||||||
$parent.appendChild($option);
|
$parent.appendChild($option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,13 +68,14 @@ export class SettingElement {
|
|||||||
tabindex: 0,
|
tabindex: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const size = params.size ? params.size : Object.keys(setting.multipleOptions!).length;
|
const totalOptions = Object.keys(setting.multipleOptions!).length;
|
||||||
|
const size = params.size ? Math.min(params.size, totalOptions) : totalOptions;
|
||||||
$control.setAttribute('size', size.toString());
|
$control.setAttribute('size', size.toString());
|
||||||
|
|
||||||
for (let value in setting.multipleOptions) {
|
for (const value in setting.multipleOptions) {
|
||||||
const label = setting.multipleOptions[value];
|
const label = setting.multipleOptions[value];
|
||||||
|
|
||||||
const $option = CE<HTMLOptionElement>('option', {value: value}, label) as HTMLOptionElement;
|
const $option = CE<HTMLOptionElement>('option', { value }, label) as HTMLOptionElement;
|
||||||
$option.selected = currentValue.indexOf(value) > -1;
|
$option.selected = currentValue.indexOf(value) > -1;
|
||||||
|
|
||||||
$option.addEventListener('mousedown', function(e) {
|
$option.addEventListener('mousedown', function(e) {
|
||||||
@ -110,7 +111,7 @@ export class SettingElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static renderCheckbox(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
private static renderCheckbox(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
||||||
const $control = CE('input', {type: 'checkbox', tabindex: 0}) as HTMLInputElement;
|
const $control = CE('input', { type: 'checkbox', tabindex: 0 }) as HTMLInputElement;
|
||||||
$control.checked = currentValue;
|
$control.checked = currentValue;
|
||||||
|
|
||||||
onChange && $control.addEventListener('input', e => {
|
onChange && $control.addEventListener('input', e => {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import type { PrefKey } from "@/enums/pref-keys";
|
import type { PrefKey, StorageKey } from "@/enums/pref-keys";
|
||||||
import type { NumberStepperParams, SettingAction, SettingDefinitions } from "@/types/setting-definition";
|
import type { NumberStepperParams, SettingAction, SettingDefinitions } from "@/types/setting-definition";
|
||||||
import { BxEvent } from "../bx-event";
|
|
||||||
import { t } from "../translation";
|
import { t } from "../translation";
|
||||||
import { SCRIPT_VARIANT } from "../global";
|
import { SCRIPT_VARIANT } from "../global";
|
||||||
|
import { BxEventBus } from "../bx-event-bus";
|
||||||
|
|
||||||
export class BaseSettingsStore {
|
export class BaseSettingsStore {
|
||||||
private storage: Storage;
|
private storage: Storage;
|
||||||
private storageKey: string;
|
private storageKey: StorageKey;
|
||||||
private _settings: object | null;
|
private _settings: object | null;
|
||||||
private definitions: SettingDefinitions;
|
private definitions: SettingDefinitions;
|
||||||
|
|
||||||
constructor(storageKey: string, definitions: SettingDefinitions) {
|
constructor(storageKey: StorageKey, definitions: SettingDefinitions) {
|
||||||
this.storage = window.localStorage;
|
this.storage = window.localStorage;
|
||||||
this.storageKey = storageKey;
|
this.storageKey = storageKey;
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ export class BaseSettingsStore {
|
|||||||
this.settings[key] = this.validateValue('get', key, value);
|
this.settings[key] = this.validateValue('get', key, value);
|
||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
|
|
||||||
emitEvent && BxEvent.dispatch(window, BxEvent.SETTINGS_CHANGED, {
|
emitEvent && BxEventBus.Script.emit('setting.changed', {
|
||||||
storageKey: this.storageKey,
|
storageKey: this.storageKey,
|
||||||
settingKey: key,
|
settingKey: key,
|
||||||
settingValue: value,
|
settingValue: value,
|
||||||
|
@ -8,11 +8,11 @@ import { CE } from "../html";
|
|||||||
import { t, SUPPORTED_LANGUAGES } from "../translation";
|
import { t, SUPPORTED_LANGUAGES } from "../translation";
|
||||||
import { UserAgent } from "../user-agent";
|
import { UserAgent } from "../user-agent";
|
||||||
import { BaseSettingsStore as BaseSettingsStorage } from "./base-settings-storage";
|
import { BaseSettingsStore as BaseSettingsStorage } from "./base-settings-storage";
|
||||||
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, DeviceVibrationMode, NativeMkbMode, UiLayout, UiSection, StreamPlayerType, StreamVideoProcessing, VideoRatio, StreamStat } from "@/enums/pref-values";
|
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, DeviceVibrationMode, NativeMkbMode, UiLayout, UiSection, StreamPlayerType, StreamVideoProcessing, VideoRatio, StreamStat, VideoPosition, BlockFeature } from "@/enums/pref-values";
|
||||||
import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table";
|
import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table";
|
||||||
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
||||||
import { GhPagesUtils } from "../gh-pages";
|
import { GhPagesUtils } from "../gh-pages";
|
||||||
import { BxEvent } from "../bx-event";
|
import { BxEventBus } from "../bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
function getSupportedCodecProfiles() {
|
function getSupportedCodecProfiles() {
|
||||||
@ -89,7 +89,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
},
|
},
|
||||||
[PrefKey.SERVER_REGION]: {
|
[PrefKey.SERVER_REGION]: {
|
||||||
label: t('region'),
|
label: t('region'),
|
||||||
note: CE('a', {target: '_blank', href: 'https://umap.openstreetmap.fr/en/map/xbox-cloud-gaming-servers_1135022'}, t('server-locations')),
|
note: CE('a', { target: '_blank', href: 'https://umap.openstreetmap.fr/en/map/xbox-cloud-gaming-servers_1135022' }, t('server-locations')),
|
||||||
default: 'default',
|
default: 'default',
|
||||||
},
|
},
|
||||||
[PrefKey.SERVER_BYPASS_RESTRICTION]: {
|
[PrefKey.SERVER_BYPASS_RESTRICTION]: {
|
||||||
@ -321,10 +321,14 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
label: t('enable-local-co-op-support'),
|
label: t('enable-local-co-op-support'),
|
||||||
default: false,
|
default: false,
|
||||||
note: () => CE<HTMLAnchorElement>('a', {
|
note: () => CE('div', {},
|
||||||
href: 'https://github.com/redphx/better-xcloud/discussions/275',
|
CE<HTMLAnchorElement>('a', {
|
||||||
target: '_blank',
|
href: 'https://github.com/redphx/better-xcloud/discussions/275',
|
||||||
}, t('enable-local-co-op-support-note')),
|
target: '_blank',
|
||||||
|
}, t('enable-local-co-op-support-note')),
|
||||||
|
CE('br'),
|
||||||
|
'⚠️ ' + t('unexpected-behavior'),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.UI_CONTROLLER_SHOW_STATUS]: {
|
[PrefKey.UI_CONTROLLER_SHOW_STATUS]: {
|
||||||
@ -424,7 +428,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.FORCE_NATIVE_MKB_GAMES]: {
|
[PrefKey.NATIVE_MKB_FORCED_GAMES]: {
|
||||||
label: t('force-native-mkb-games'),
|
label: t('force-native-mkb-games'),
|
||||||
default: [],
|
default: [],
|
||||||
unsupported: !AppInterface && UserAgent.isMobile(),
|
unsupported: !AppInterface && UserAgent.isMobile(),
|
||||||
@ -432,11 +436,14 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
if (!setting.unsupported) {
|
if (!setting.unsupported) {
|
||||||
(setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList(true);
|
(setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList(true);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED, e => {
|
BxEventBus.Script.on('list.forcedNativeMkb.updated', payload => {
|
||||||
(setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList();
|
(setting as any).multipleOptions = payload.data.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
params: {
|
||||||
|
size: 6,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY]: {
|
[PrefKey.NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY]: {
|
||||||
@ -579,25 +586,29 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.BYOG_DISABLED]: {
|
|
||||||
label: t('disable-byog-feature'),
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
[PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME]: {
|
[PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME]: {
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
label: t('show-wait-time-in-game-card'),
|
label: t('show-wait-time-in-game-card'),
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.BLOCK_SOCIAL_FEATURES]: {
|
|
||||||
label: t('disable-social-features'),
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
[PrefKey.BLOCK_TRACKING]: {
|
[PrefKey.BLOCK_TRACKING]: {
|
||||||
label: t('disable-xcloud-analytics'),
|
label: t('disable-xcloud-analytics'),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
[PrefKey.BLOCK_FEATURES]: {
|
||||||
|
label: t('disable-features'),
|
||||||
|
default: [],
|
||||||
|
multipleOptions: {
|
||||||
|
[BlockFeature.CHAT]: t('chat'),
|
||||||
|
[BlockFeature.FRIENDS]: t('friends-followers'),
|
||||||
|
[BlockFeature.BYOG]: t('stream-your-own-game'),
|
||||||
|
[BlockFeature.NOTIFICATIONS_INVITES]: t('notifications') + ': ' + t('invites'),
|
||||||
|
[BlockFeature.NOTIFICATIONS_ACHIEVEMENTS]: t('notifications') + ': ' + t('achievements'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
[PrefKey.USER_AGENT_PROFILE]: {
|
[PrefKey.USER_AGENT_PROFILE]: {
|
||||||
label: t('user-agent-profile'),
|
label: t('user-agent-profile'),
|
||||||
note: '⚠️ ' + t('unexpected-behavior'),
|
note: '⚠️ ' + t('unexpected-behavior'),
|
||||||
@ -649,7 +660,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
[PrefKey.VIDEO_MAX_FPS]: {
|
[PrefKey.VIDEO_MAX_FPS]: {
|
||||||
label: t('max-fps'),
|
label: t('limit-fps'),
|
||||||
default: 60,
|
default: 60,
|
||||||
min: 10,
|
min: 10,
|
||||||
max: 60,
|
max: 60,
|
||||||
@ -681,10 +692,10 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
},
|
},
|
||||||
[PrefKey.VIDEO_RATIO]: {
|
[PrefKey.VIDEO_RATIO]: {
|
||||||
label: t('aspect-ratio'),
|
label: t('aspect-ratio'),
|
||||||
note: t('aspect-ratio-note'),
|
note: STATES.browser.capabilities.touch ? t('aspect-ratio-note') : undefined,
|
||||||
default: VideoRatio['16:9'],
|
default: VideoRatio['16:9'],
|
||||||
options: {
|
options: {
|
||||||
[VideoRatio['16:9']]: '16:9',
|
[VideoRatio['16:9']]: `16:9 (${t('default')})`,
|
||||||
[VideoRatio['18:9']]: '18:9',
|
[VideoRatio['18:9']]: '18:9',
|
||||||
[VideoRatio['21:9']]: '21:9',
|
[VideoRatio['21:9']]: '21:9',
|
||||||
[VideoRatio['16:10']]: '16:10',
|
[VideoRatio['16:10']]: '16:10',
|
||||||
@ -694,6 +705,19 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
//'cover': 'Cover',
|
//'cover': 'Cover',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[PrefKey.VIDEO_POSITION]: {
|
||||||
|
label: t('position'),
|
||||||
|
note: STATES.browser.capabilities.touch ? t('aspect-ratio-note') : undefined,
|
||||||
|
default: VideoPosition.CENTER,
|
||||||
|
options: {
|
||||||
|
[VideoPosition.TOP]: t('top'),
|
||||||
|
[VideoPosition.TOP_HALF]: t('top-half'),
|
||||||
|
[VideoPosition.CENTER]: `${t('center')} (${t('default')})`,
|
||||||
|
[VideoPosition.BOTTOM_HALF]: t('bottom-half'),
|
||||||
|
[VideoPosition.BOTTOM]: t('bottom'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
[PrefKey.VIDEO_SATURATION]: {
|
[PrefKey.VIDEO_SATURATION]: {
|
||||||
label: t('saturation'),
|
label: t('saturation'),
|
||||||
default: 100,
|
default: 100,
|
||||||
@ -746,7 +770,6 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
[PrefKey.STATS_ITEMS]: {
|
[PrefKey.STATS_ITEMS]: {
|
||||||
label: t('stats'),
|
label: t('stats'),
|
||||||
default: [StreamStat.PING, StreamStat.FPS, StreamStat.BITRATE, StreamStat.DECODE_TIME, StreamStat.PACKETS_LOST, StreamStat.FRAMES_LOST],
|
default: [StreamStat.PING, StreamStat.FPS, StreamStat.BITRATE, StreamStat.DECODE_TIME, StreamStat.PACKETS_LOST, StreamStat.FRAMES_LOST],
|
||||||
@ -806,11 +829,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
'1.1rem': t('large'),
|
'1.1rem': t('large'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[PrefKey.STATS_TRANSPARENT]: {
|
[PrefKey.STATS_OPACITY_ALL]: {
|
||||||
label: t('transparent-background'),
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
[PrefKey.STATS_OPACITY]: {
|
|
||||||
label: t('opacity'),
|
label: t('opacity'),
|
||||||
default: 80,
|
default: 80,
|
||||||
min: 50,
|
min: 50,
|
||||||
@ -821,6 +840,17 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
ticks: 10,
|
ticks: 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[PrefKey.STATS_OPACITY_BACKGROUND]: {
|
||||||
|
label: t('background-opacity'),
|
||||||
|
default: 100,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
params: {
|
||||||
|
steps: 10,
|
||||||
|
suffix: '%',
|
||||||
|
ticks: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
[PrefKey.STATS_CONDITIONAL_FORMATTING]: {
|
[PrefKey.STATS_CONDITIONAL_FORMATTING]: {
|
||||||
label: t('conditional-formatting'),
|
label: t('conditional-formatting'),
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -10,6 +10,7 @@ import { EmulatedMkbHandler } from "@/modules/mkb/mkb-handler";
|
|||||||
import { RendererShortcut } from "@/modules/shortcuts/renderer-shortcut";
|
import { RendererShortcut } from "@/modules/shortcuts/renderer-shortcut";
|
||||||
import { TrueAchievements } from "./true-achievements";
|
import { TrueAchievements } from "./true-achievements";
|
||||||
import { NativeMkbHandler } from "@/modules/mkb/native-mkb-handler";
|
import { NativeMkbHandler } from "@/modules/mkb/native-mkb-handler";
|
||||||
|
import { VirtualControllerShortcut } from "@/modules/shortcuts/virtual-controller-shortcut";
|
||||||
|
|
||||||
export class ShortcutHandler {
|
export class ShortcutHandler {
|
||||||
static runAction(action: ShortcutAction) {
|
static runAction(action: ShortcutAction) {
|
||||||
@ -69,6 +70,10 @@ export class ShortcutHandler {
|
|||||||
case ShortcutAction.TRUE_ACHIEVEMENTS_OPEN:
|
case ShortcutAction.TRUE_ACHIEVEMENTS_OPEN:
|
||||||
TrueAchievements.getInstance().open(false);
|
TrueAchievements.getInstance().open(false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ShortcutAction.CONTROLLER_XBOX_BUTTON_PRESS:
|
||||||
|
VirtualControllerShortcut.pressXboxButton();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import { hasGamepad } from "./gamepad";
|
|||||||
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
|
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
|
||||||
import type { GamepadKey } from "@/enums/gamepad";
|
import type { GamepadKey } from "@/enums/gamepad";
|
||||||
import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
|
import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
|
||||||
import { BxEvent } from "./bx-event";
|
|
||||||
import { KeyboardShortcutDefaultId, KeyboardShortcutsTable } from "./local-db/keyboard-shortcuts-table";
|
import { KeyboardShortcutDefaultId, KeyboardShortcutsTable } from "./local-db/keyboard-shortcuts-table";
|
||||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||||
import { KeyHelper } from "@/modules/mkb/key-helper";
|
import { KeyHelper } from "@/modules/mkb/key-helper";
|
||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
|
||||||
|
|
||||||
export type StreamSettingsData = {
|
export type StreamSettingsData = {
|
||||||
@ -110,7 +110,7 @@ export class StreamSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StreamSettings.settings.deviceVibrationIntensity = intensity;
|
StreamSettings.settings.deviceVibrationIntensity = intensity;
|
||||||
BxEvent.dispatch(window, BxEvent.DEVICE_VIBRATION_CHANGED);
|
BxEventBus.Script.emit('deviceVibration.updated', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async refreshMkbSettings() {
|
static async refreshMkbSettings() {
|
||||||
@ -148,7 +148,7 @@ export class StreamSettings {
|
|||||||
settings.mkbPreset = converted;
|
settings.mkbPreset = converted;
|
||||||
|
|
||||||
setPref(PrefKey.MKB_P1_MAPPING_PRESET_ID, orgPreset.id);
|
setPref(PrefKey.MKB_P1_MAPPING_PRESET_ID, orgPreset.id);
|
||||||
BxEvent.dispatch(window, BxEvent.MKB_UPDATED);
|
BxEventBus.Script.emit('mkb.setting.updated', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async refreshKeyboardShortcuts() {
|
static async refreshKeyboardShortcuts() {
|
||||||
@ -159,7 +159,7 @@ export class StreamSettings {
|
|||||||
settings.keyboardShortcuts = null;
|
settings.keyboardShortcuts = null;
|
||||||
|
|
||||||
setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, presetId);
|
setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, presetId);
|
||||||
BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED);
|
BxEventBus.Script.emit('keyboardShortcuts.updated', {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ export class StreamSettings {
|
|||||||
settings.keyboardShortcuts = converted;
|
settings.keyboardShortcuts = converted;
|
||||||
|
|
||||||
setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, orgPreset.id);
|
setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, orgPreset.id);
|
||||||
BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED);
|
BxEventBus.Script.emit('keyboardShortcuts.updated', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async refreshAllSettings() {
|
static async refreshAllSettings() {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { BxEvent } from "./bx-event";
|
|
||||||
import { STATES } from "./global";
|
import { STATES } from "./global";
|
||||||
import { humanFileSize, secondsToHm } from "./html";
|
import { humanFileSize, secondsToHm } from "./html";
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
import { BxLogger } from "./bx-logger";
|
import { BxLogger } from "./bx-logger";
|
||||||
import { StreamStat } from "@/enums/pref-values";
|
import { StreamStat } from "@/enums/pref-values";
|
||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
|
||||||
export type StreamStatGrade = '' | 'bad' | 'ok' | 'good';
|
export type StreamStatGrade = '' | 'bad' | 'ok' | 'good';
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ export class StreamStatsCollector {
|
|||||||
current: -1,
|
current: -1,
|
||||||
grades: [40, 75, 100],
|
grades: [40, 75, 100],
|
||||||
toString() {
|
toString() {
|
||||||
return this.current === -1 ? '???' : this.current.toString();
|
return this.current === -1 ? '???' : this.current.toString().padStart(3, ' ');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ export class StreamStatsCollector {
|
|||||||
current: 0,
|
current: 0,
|
||||||
grades: [30, 40, 60],
|
grades: [30, 40, 60],
|
||||||
toString() {
|
toString() {
|
||||||
return `${this.current.toFixed(2)}ms`;
|
return `${this.current.toFixed(1)}ms`.padStart(6, ' ');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -112,14 +112,14 @@ export class StreamStatsCollector {
|
|||||||
current: 0,
|
current: 0,
|
||||||
toString() {
|
toString() {
|
||||||
const maxFps = getPref<VideoMaxFps>(PrefKey.VIDEO_MAX_FPS);
|
const maxFps = getPref<VideoMaxFps>(PrefKey.VIDEO_MAX_FPS);
|
||||||
return maxFps < 60 ? `${maxFps}/${this.current}` : this.current.toString();
|
return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5, ' ') : this.current.toString();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[StreamStat.BITRATE]: {
|
[StreamStat.BITRATE]: {
|
||||||
current: 0,
|
current: 0,
|
||||||
toString() {
|
toString() {
|
||||||
return `${this.current.toFixed(2)} Mbps`;
|
return `${this.current.toFixed(1)} Mbps`.padStart(9, ' ');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -127,8 +127,8 @@ export class StreamStatsCollector {
|
|||||||
received: 0,
|
received: 0,
|
||||||
dropped: 0,
|
dropped: 0,
|
||||||
toString() {
|
toString() {
|
||||||
const framesDroppedPercentage = (this.dropped * 100 / ((this.dropped + this.received) || 1)).toFixed(2);
|
const framesDroppedPercentage = (this.dropped * 100 / ((this.dropped + this.received) || 1)).toFixed(1);
|
||||||
return framesDroppedPercentage === '0.00' ? this.dropped.toString() : `${this.dropped} (${framesDroppedPercentage}%)`;
|
return framesDroppedPercentage === '0.0' ? this.dropped.toString() : `${this.dropped} (${framesDroppedPercentage}%)`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -136,8 +136,8 @@ export class StreamStatsCollector {
|
|||||||
received: 0,
|
received: 0,
|
||||||
dropped: 0,
|
dropped: 0,
|
||||||
toString() {
|
toString() {
|
||||||
const packetsLostPercentage = (this.dropped * 100 / ((this.dropped + this.received) || 1)).toFixed(2);
|
const packetsLostPercentage = (this.dropped * 100 / ((this.dropped + this.received) || 1)).toFixed(1);
|
||||||
return packetsLostPercentage === '0.00' ? this.dropped.toString() : `${this.dropped} (${packetsLostPercentage}%)`;
|
return packetsLostPercentage === '0.0' ? this.dropped.toString() : `${this.dropped} (${packetsLostPercentage}%)`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -146,14 +146,14 @@ export class StreamStatsCollector {
|
|||||||
total: 0,
|
total: 0,
|
||||||
grades: [6, 9, 12],
|
grades: [6, 9, 12],
|
||||||
toString() {
|
toString() {
|
||||||
return isNaN(this.current) ? '??ms' : `${this.current.toFixed(2)}ms`;
|
return isNaN(this.current) ? '??ms' : `${this.current.toFixed(1)}ms`.padStart(6, ' ');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[StreamStat.DOWNLOAD]: {
|
[StreamStat.DOWNLOAD]: {
|
||||||
total: 0,
|
total: 0,
|
||||||
toString() {
|
toString() {
|
||||||
return humanFileSize(this.total);
|
return humanFileSize(this.total).padStart(8, ' ');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -201,6 +201,7 @@ export class StreamStatsCollector {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private lastVideoStat?: RTCInboundRtpStreamStats | null;
|
private lastVideoStat?: RTCInboundRtpStreamStats | null;
|
||||||
|
private selectedCandidatePairId: string | null | undefined = null;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||||
@ -212,6 +213,22 @@ export class StreamStatsCollector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find selected candidate
|
||||||
|
if (!this.selectedCandidatePairId) {
|
||||||
|
let found = false;
|
||||||
|
stats.forEach(stat => {
|
||||||
|
if (found || stat.type !== 'transport') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stat = (stat as unknown as RTCTransportStats);
|
||||||
|
if (stat.iceState === 'connected' && stat.selectedCandidatePairId) {
|
||||||
|
this.selectedCandidatePairId = (stat as unknown as RTCTransportStats).selectedCandidatePairId;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
stats.forEach(stat => {
|
stats.forEach(stat => {
|
||||||
if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
|
if (stat.type === 'inbound-rtp' && stat.kind === 'video') {
|
||||||
// FPS
|
// FPS
|
||||||
@ -256,7 +273,7 @@ export class StreamStatsCollector {
|
|||||||
dt.current = dt.total / framesDecodedDiff * 1000;
|
dt.current = dt.total / framesDecodedDiff * 1000;
|
||||||
|
|
||||||
this.lastVideoStat = stat;
|
this.lastVideoStat = stat;
|
||||||
} else if (stat.type === 'candidate-pair' && stat.packetsReceived > 0 && stat.state === 'succeeded') {
|
} else if (this.selectedCandidatePairId && stat.type === 'candidate-pair' && stat.id === this.selectedCandidatePairId) {
|
||||||
// Round Trip Time
|
// Round Trip Time
|
||||||
const ping = this.currentStats[StreamStat.PING];
|
const ping = this.currentStats[StreamStat.PING];
|
||||||
ping.current = stat.currentRoundTripTime ? stat.currentRoundTripTime * 1000 : -1;
|
ping.current = stat.currentRoundTripTime ? stat.currentRoundTripTime * 1000 : -1;
|
||||||
@ -310,9 +327,8 @@ export class StreamStatsCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static setupEvents() {
|
static setupEvents() {
|
||||||
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
BxEventBus.Stream.on('state.playing', () => {
|
||||||
const statsCollector = StreamStatsCollector.getInstance();
|
StreamStatsCollector.getInstance().reset();
|
||||||
statsCollector.reset();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ export class Toast {
|
|||||||
private constructor() {
|
private constructor() {
|
||||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||||
|
|
||||||
this.$wrapper = CE('div', {class: 'bx-toast bx-offscreen'},
|
this.$wrapper = CE('div', { class: 'bx-toast bx-offscreen' },
|
||||||
this.$msg = CE('span', {class: 'bx-toast-msg'}),
|
this.$msg = CE('span', { class: 'bx-toast-msg' }),
|
||||||
this.$status = CE('span', {class: 'bx-toast-status'}),
|
this.$status = CE('span', { class: 'bx-toast-status' }),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.$wrapper.addEventListener('transitionend', e => {
|
this.$wrapper.addEventListener('transitionend', e => {
|
||||||
|
@ -27,6 +27,7 @@ export const SUPPORTED_LANGUAGES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Texts = {
|
const Texts = {
|
||||||
|
"achievements": "Achievements",
|
||||||
"activate": "Activate",
|
"activate": "Activate",
|
||||||
"activated": "Activated",
|
"activated": "Activated",
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
@ -42,20 +43,26 @@ const Texts = {
|
|||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"back-to-home": "Back to home",
|
"back-to-home": "Back to home",
|
||||||
"back-to-home-confirm": "Do you want to go back to the home page (without disconnecting)?",
|
"back-to-home-confirm": "Do you want to go back to the home page (without disconnecting)?",
|
||||||
|
"background-opacity": "Background opacity",
|
||||||
"battery": "Battery",
|
"battery": "Battery",
|
||||||
"battery-saving": "Battery saving",
|
"battery-saving": "Battery saving",
|
||||||
"better-xcloud": "Better xCloud",
|
"better-xcloud": "Better xCloud",
|
||||||
"bitrate-audio-maximum": "Maximum audio bitrate",
|
"bitrate-audio-maximum": "Maximum audio bitrate",
|
||||||
"bitrate-video-maximum": "Maximum video bitrate",
|
"bitrate-video-maximum": "Maximum video bitrate",
|
||||||
|
"bottom": "Bottom",
|
||||||
|
"bottom-half": "Bottom half",
|
||||||
"bottom-left": "Bottom-left",
|
"bottom-left": "Bottom-left",
|
||||||
"bottom-right": "Bottom-right",
|
"bottom-right": "Bottom-right",
|
||||||
"brazil": "Brazil",
|
"brazil": "Brazil",
|
||||||
"brightness": "Brightness",
|
"brightness": "Brightness",
|
||||||
"browser-unsupported-feature": "Your browser doesn't support this feature",
|
"browser-unsupported-feature": "Your browser doesn't support this feature",
|
||||||
|
"button-xbox": "Xbox button",
|
||||||
"bypass-region-restriction": "Bypass region restriction",
|
"bypass-region-restriction": "Bypass region restriction",
|
||||||
"can-stream-xbox-360-games": "Can stream Xbox 360 games",
|
"can-stream-xbox-360-games": "Can stream Xbox 360 games",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"cant-stream-xbox-360-games": "Can't stream Xbox 360 games",
|
"cant-stream-xbox-360-games": "Can't stream Xbox 360 games",
|
||||||
|
"center": "Center",
|
||||||
|
"chat": "Chat",
|
||||||
"clarity-boost": "Clarity boost",
|
"clarity-boost": "Clarity boost",
|
||||||
"clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON",
|
"clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
@ -80,6 +87,8 @@ const Texts = {
|
|||||||
"contrast": "Contrast",
|
"contrast": "Contrast",
|
||||||
"controller": "Controller",
|
"controller": "Controller",
|
||||||
"controller-friendly-ui": "Controller-friendly UI",
|
"controller-friendly-ui": "Controller-friendly UI",
|
||||||
|
"controller-mapping": "Controller mapping",
|
||||||
|
"controller-mapping-in-game": "In-game controller mapping",
|
||||||
"controller-shortcuts": "Controller shortcuts",
|
"controller-shortcuts": "Controller shortcuts",
|
||||||
"controller-shortcuts-connect-note": "Connect a controller to use this feature",
|
"controller-shortcuts-connect-note": "Connect a controller to use this feature",
|
||||||
"controller-shortcuts-in-game": "In-game controller shortcuts",
|
"controller-shortcuts-in-game": "In-game controller shortcuts",
|
||||||
@ -91,6 +100,7 @@ const Texts = {
|
|||||||
"deadzone-counterweight": "Deadzone counterweight",
|
"deadzone-counterweight": "Deadzone counterweight",
|
||||||
"decrease": "Decrease",
|
"decrease": "Decrease",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
|
"default-preset-note": "You can't modify default presets. Create a new one to customize it.",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"device": "Device",
|
"device": "Device",
|
||||||
"device-unsupported-touch": "Your device doesn't have touch support",
|
"device-unsupported-touch": "Your device doesn't have touch support",
|
||||||
@ -98,6 +108,7 @@ const Texts = {
|
|||||||
"device-vibration-not-using-gamepad": "On when not using gamepad",
|
"device-vibration-not-using-gamepad": "On when not using gamepad",
|
||||||
"disable": "Disable",
|
"disable": "Disable",
|
||||||
"disable-byog-feature": "Disable \"Stream your own game\" feature",
|
"disable-byog-feature": "Disable \"Stream your own game\" feature",
|
||||||
|
"disable-features": "Disable features",
|
||||||
"disable-home-context-menu": "Disable context menu in Home page",
|
"disable-home-context-menu": "Disable context menu in Home page",
|
||||||
"disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog",
|
"disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog",
|
||||||
"disable-social-features": "Disable social features",
|
"disable-social-features": "Disable social features",
|
||||||
@ -122,6 +133,7 @@ const Texts = {
|
|||||||
"force-native-mkb-games": "Force native Mouse & Keyboard for these games",
|
"force-native-mkb-games": "Force native Mouse & Keyboard for these games",
|
||||||
"fortnite-allow-stw-mode": "Allows playing \"Save the World\" mode on mobile",
|
"fortnite-allow-stw-mode": "Allows playing \"Save the World\" mode on mobile",
|
||||||
"fortnite-force-console-version": "Fortnite: force console version",
|
"fortnite-force-console-version": "Fortnite: force console version",
|
||||||
|
"friends-followers": "Friends and followers",
|
||||||
"game-bar": "Game Bar",
|
"game-bar": "Game Bar",
|
||||||
"getting-consoles-list": "Getting the list of consoles...",
|
"getting-consoles-list": "Getting the list of consoles...",
|
||||||
"guide": "Guide",
|
"guide": "Guide",
|
||||||
@ -143,6 +155,7 @@ const Texts = {
|
|||||||
"import": "Import",
|
"import": "Import",
|
||||||
"increase": "Increase",
|
"increase": "Increase",
|
||||||
"install-android": "Better xCloud app for Android",
|
"install-android": "Better xCloud app for Android",
|
||||||
|
"invites": "Invites",
|
||||||
"japan": "Japan",
|
"japan": "Japan",
|
||||||
"jitter": "Jitter",
|
"jitter": "Jitter",
|
||||||
"keyboard-key": "Keyboard key",
|
"keyboard-key": "Keyboard key",
|
||||||
@ -153,13 +166,14 @@ const Texts = {
|
|||||||
"large": "Large",
|
"large": "Large",
|
||||||
"layout": "Layout",
|
"layout": "Layout",
|
||||||
"left-stick": "Left stick",
|
"left-stick": "Left stick",
|
||||||
|
"limit-fps": "Limit FPS",
|
||||||
"load-failed-message": "Failed to run Better xCloud",
|
"load-failed-message": "Failed to run Better xCloud",
|
||||||
"loading-screen": "Loading screen",
|
"loading-screen": "Loading screen",
|
||||||
"local-co-op": "Local co-op",
|
"local-co-op": "Local co-op",
|
||||||
"lowest-quality": "Lowest quality",
|
"lowest-quality": "Lowest quality",
|
||||||
"manage": "Manage",
|
"manage": "Manage",
|
||||||
"map-mouse-to": "Map mouse to",
|
"map-mouse-to": "Map mouse to",
|
||||||
"max-fps": "Max FPS",
|
"mapping": "Mapping",
|
||||||
"may-not-work-properly": "May not work properly!",
|
"may-not-work-properly": "May not work properly!",
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"microphone": "Microphone",
|
"microphone": "Microphone",
|
||||||
@ -198,6 +212,7 @@ const Texts = {
|
|||||||
"no-consoles-found": "No consoles found",
|
"no-consoles-found": "No consoles found",
|
||||||
"no-controllers-connected": "No controllers connected",
|
"no-controllers-connected": "No controllers connected",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
|
"notifications": "Notifications",
|
||||||
"off": "Off",
|
"off": "Off",
|
||||||
"official": "Official",
|
"official": "Official",
|
||||||
"on": "On",
|
"on": "On",
|
||||||
@ -214,6 +229,7 @@ const Texts = {
|
|||||||
"prefer-ipv6-server": "Prefer IPv6 server",
|
"prefer-ipv6-server": "Prefer IPv6 server",
|
||||||
"preferred-game-language": "Preferred game's language",
|
"preferred-game-language": "Preferred game's language",
|
||||||
"preset": "Preset",
|
"preset": "Preset",
|
||||||
|
"press": "Press",
|
||||||
"press-esc-to-cancel": "Press Esc to cancel",
|
"press-esc-to-cancel": "Press Esc to cancel",
|
||||||
"press-key-to-toggle-mkb": [
|
"press-key-to-toggle-mkb": [
|
||||||
(e: any) => `Press ${e.key} to toggle this feature`,
|
(e: any) => `Press ${e.key} to toggle this feature`,
|
||||||
@ -322,6 +338,7 @@ const Texts = {
|
|||||||
"stream": "Stream",
|
"stream": "Stream",
|
||||||
"stream-settings": "Stream settings",
|
"stream-settings": "Stream settings",
|
||||||
"stream-stats": "Stream stats",
|
"stream-stats": "Stream stats",
|
||||||
|
"stream-your-own-game": "Stream your own game",
|
||||||
"stretch": "Stretch",
|
"stretch": "Stretch",
|
||||||
"suggest-settings": "Suggest settings",
|
"suggest-settings": "Suggest settings",
|
||||||
"suggest-settings-link": "Suggest recommended settings for this device",
|
"suggest-settings-link": "Suggest recommended settings for this device",
|
||||||
@ -339,7 +356,9 @@ const Texts = {
|
|||||||
"tc-standard-layout-style": "Standard layout's button style",
|
"tc-standard-layout-style": "Standard layout's button style",
|
||||||
"text-size": "Text size",
|
"text-size": "Text size",
|
||||||
"toggle": "Toggle",
|
"toggle": "Toggle",
|
||||||
|
"top": "Top",
|
||||||
"top-center": "Top-center",
|
"top-center": "Top-center",
|
||||||
|
"top-half": "Top half",
|
||||||
"top-left": "Top-left",
|
"top-left": "Top-left",
|
||||||
"top-right": "Top-right",
|
"top-right": "Top-right",
|
||||||
"touch-control-layout": "Touch control layout",
|
"touch-control-layout": "Touch control layout",
|
||||||
|
@ -97,7 +97,7 @@ export class TrueAchievements {
|
|||||||
}
|
}
|
||||||
this.updateIds(xboxTitleId);
|
this.updateIds(xboxTitleId);
|
||||||
|
|
||||||
if (document.documentElement.dataset.xdsPlatform === 'tv') {
|
if (document.body.dataset.mediaType === 'tv') {
|
||||||
$div.appendChild(this.$link);
|
$div.appendChild(this.$link);
|
||||||
} else {
|
} else {
|
||||||
$div.appendChild(this.$button);
|
$div.appendChild(this.$button);
|
||||||
|
@ -9,12 +9,12 @@ type UserAgentConfig = {
|
|||||||
|
|
||||||
const SMART_TV_UNIQUE_ID = 'FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA';
|
const SMART_TV_UNIQUE_ID = 'FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA';
|
||||||
|
|
||||||
let CHROMIUM_VERSION = '123.0.0.0';
|
let CHROMIUM_VERSION = '125.0.0.0';
|
||||||
if (!!(window as any).chrome || window.navigator.userAgent.includes('Chrome')) {
|
if (!!window.chrome || window.navigator.userAgent.includes('Chrome')) {
|
||||||
// Get Chromium version in the original User-Agent value
|
// Get Chromium version in the original User-Agent value
|
||||||
const match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/);
|
const match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/);
|
||||||
if (match) {
|
if (match) {
|
||||||
CHROMIUM_VERSION = match[1];
|
CHROMIUM_VERSION = match[1] as string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ export class UserAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getDefault(): string {
|
static getDefault(): string {
|
||||||
return (window.navigator as any).orgUserAgent || window.navigator.userAgent;
|
return window.navigator.orgUserAgent || window.navigator.userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get(profile: UserAgentProfile): string {
|
static get(profile: UserAgentProfile): string {
|
||||||
@ -123,12 +123,12 @@ export class UserAgent {
|
|||||||
|
|
||||||
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
|
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
|
||||||
if ('userAgentData' in window.navigator) {
|
if ('userAgentData' in window.navigator) {
|
||||||
(window.navigator as any).orgUserAgentData = (window.navigator as any).userAgentData;
|
window.navigator.orgUserAgentData = window.navigator.userAgentData;
|
||||||
Object.defineProperty(window.navigator, 'userAgentData', {});
|
Object.defineProperty(window.navigator, 'userAgentData', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override navigator.userAgent
|
// Override navigator.userAgent
|
||||||
(window.navigator as any).orgUserAgent = window.navigator.userAgent;
|
window.navigator.orgUserAgent = window.navigator.userAgent;
|
||||||
Object.defineProperty(window.navigator, 'userAgent', {
|
Object.defineProperty(window.navigator, 'userAgent', {
|
||||||
value: newUserAgent,
|
value: newUserAgent,
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import { Toast } from "./toast";
|
|||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref, setPref } from "./settings-storages/global-settings-storage";
|
import { getPref, setPref } from "./settings-storages/global-settings-storage";
|
||||||
import { LocalDb } from "./local-db/local-db";
|
import { LocalDb } from "./local-db/local-db";
|
||||||
|
import { BlockFeature } from "@/enums/pref-values";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for update
|
* Check for update
|
||||||
@ -44,7 +45,7 @@ export function checkForUpdate() {
|
|||||||
* Disable PWA requirement on Safari
|
* Disable PWA requirement on Safari
|
||||||
*/
|
*/
|
||||||
export function disablePwa() {
|
export function disablePwa() {
|
||||||
const userAgent = ((window.navigator as any).orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
const userAgent = (window.navigator.orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||||
if (!userAgent) {
|
if (!userAgent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -102,11 +103,11 @@ export function roundToNearest(value: number, interval: number): number {
|
|||||||
export async function copyToClipboard(text: string, showToast=true): Promise<boolean> {
|
export async function copyToClipboard(text: string, showToast=true): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
showToast && Toast.show('Copied to clipboard', '', {instant: true});
|
showToast && Toast.show('Copied to clipboard', '', { instant: true });
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to copy: ', err);
|
console.error('Failed to copy: ', err);
|
||||||
showToast && Toast.show('Failed to copy', '', {instant: true});
|
showToast && Toast.show('Failed to copy', '', { instant: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -128,10 +129,10 @@ export function parseDetailsPath(path: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const titleSlug = matches.groups.titleSlug.replaceAll('\%' + '7C', '-');
|
const titleSlug = matches.groups.titleSlug!.replaceAll('\%' + '7C', '-');
|
||||||
const productId = matches.groups.productId;
|
const productId = matches.groups.productId;
|
||||||
|
|
||||||
return {titleSlug, productId};
|
return { titleSlug, productId };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearAllData() {
|
export function clearAllData() {
|
||||||
@ -155,3 +156,19 @@ export function clearAllData() {
|
|||||||
|
|
||||||
alert(t('clear-data-success'));
|
alert(t('clear-data-success'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function blockAllNotifications() {
|
||||||
|
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||||
|
const blockAll = [BlockFeature.FRIENDS, BlockFeature.NOTIFICATIONS_ACHIEVEMENTS, BlockFeature.NOTIFICATIONS_INVITES].every(value => blockFeatures.includes(value));
|
||||||
|
return blockAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function blockSomeNotifications() {
|
||||||
|
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||||
|
if (blockAllNotifications()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockSome = [BlockFeature.FRIENDS, BlockFeature.NOTIFICATIONS_ACHIEVEMENTS, BlockFeature.NOTIFICATIONS_INVITES].some(value => blockFeatures.includes(value));
|
||||||
|
return blockSome;
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { NATIVE_FETCH } from "./bx-flags"
|
|||||||
export class XboxApi {
|
export class XboxApi {
|
||||||
private static CACHED_TITLES: Record<string, string> = {};
|
private static CACHED_TITLES: Record<string, string> = {};
|
||||||
|
|
||||||
static async getProductTitle(xboxTitleId: number | string): Promise<string | null> {
|
static async getProductTitle(xboxTitleId: number | string): Promise<string | undefined> {
|
||||||
xboxTitleId = xboxTitleId.toString();
|
xboxTitleId = xboxTitleId.toString();
|
||||||
if (XboxApi.CACHED_TITLES[xboxTitleId]) {
|
if (XboxApi.CACHED_TITLES[xboxTitleId]) {
|
||||||
return XboxApi.CACHED_TITLES[xboxTitleId];
|
return XboxApi.CACHED_TITLES[xboxTitleId];
|
||||||
@ -20,6 +20,6 @@ export class XboxApi {
|
|||||||
return productTitle;
|
return productTitle;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,21 +7,21 @@ export class XcloudApi {
|
|||||||
public static getInstance = () => XcloudApi.instance ?? (XcloudApi.instance = new XcloudApi());
|
public static getInstance = () => XcloudApi.instance ?? (XcloudApi.instance = new XcloudApi());
|
||||||
private readonly LOG_TAG = 'XcloudApi';
|
private readonly LOG_TAG = 'XcloudApi';
|
||||||
|
|
||||||
private CACHE_TITLES: {[key: string]: XcloudTitleInfo} = {};
|
private CACHE_TITLES: { [key: string]: XcloudTitleInfo } = {};
|
||||||
private CACHE_WAIT_TIME: {[key: string]: XcloudWaitTimeInfo} = {};
|
private CACHE_WAIT_TIME: { [key: string]: XcloudWaitTimeInfo } = {};
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTitleInfo(id: string): Promise<XcloudTitleInfo | null> {
|
async getTitleInfo(id: string): Promise<XcloudTitleInfo | undefined> {
|
||||||
if (id in this.CACHE_TITLES) {
|
if (id in this.CACHE_TITLES) {
|
||||||
return this.CACHE_TITLES[id];
|
return this.CACHE_TITLES[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUri = STATES.selectedRegion.baseUri;
|
const baseUri = STATES.selectedRegion.baseUri;
|
||||||
if (!baseUri || !STATES.gsToken) {
|
if (!baseUri || !STATES.gsToken) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let json;
|
let json;
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { isFullVersion } from "@macros/build" with {type: "macro"};
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { LoadingScreen } from "@modules/loading-screen";
|
import { LoadingScreen } from "@modules/loading-screen";
|
||||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||||
import { StreamBadges } from "@modules/stream/stream-badges";
|
import { StreamBadges } from "@modules/stream/stream-badges";
|
||||||
import { TouchController } from "@modules/touch-controller";
|
import { TouchController } from "@modules/touch-controller";
|
||||||
import { BxEvent } from "./bx-event";
|
|
||||||
import { NATIVE_FETCH, BX_FLAGS } from "./bx-flags";
|
import { NATIVE_FETCH, BX_FLAGS } from "./bx-flags";
|
||||||
import { STATES } from "./global";
|
import { STATES } from "./global";
|
||||||
import { patchIceCandidates } from "./network";
|
import { generateMsDeviceInfo, getOsNameFromResolution, patchIceCandidates } from "./network";
|
||||||
import { getPreferredServerRegion } from "./region";
|
import { getPreferredServerRegion } from "./region";
|
||||||
import { BypassServerIps } from "@/enums/bypass-servers";
|
import { BypassServerIps } from "@/enums/bypass-servers";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
import { NativeMkbMode, StreamResolution, TouchControllerMode } from "@/enums/pref-values";
|
import { NativeMkbMode, StreamResolution, TouchControllerMode } from "@/enums/pref-values";
|
||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
|
||||||
export class XcloudInterceptor {
|
export class XcloudInterceptor {
|
||||||
private static readonly SERVER_EXTRA_INFO: Record<string, [string, ServerContinent]> = {
|
private static readonly SERVER_EXTRA_INFO: Record<string, [string, ServerContinent]> = {
|
||||||
@ -42,46 +42,6 @@ export class XcloudInterceptor {
|
|||||||
WestEurope: ['🇪🇺', 'europe'],
|
WestEurope: ['🇪🇺', 'europe'],
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly BASE_DEVICE_INFO = {
|
|
||||||
appInfo: {
|
|
||||||
env: {
|
|
||||||
clientAppId: window.location.host,
|
|
||||||
clientAppType: 'browser',
|
|
||||||
clientAppVersion: '24.17.36',
|
|
||||||
clientSdkVersion: '10.1.14',
|
|
||||||
httpEnvironment: 'prod',
|
|
||||||
sdkInstallId: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
dev: {
|
|
||||||
displayInfo: {
|
|
||||||
dimensions: {
|
|
||||||
widthInPixels: 1920,
|
|
||||||
heightInPixels: 1080,
|
|
||||||
},
|
|
||||||
pixelDensity: {
|
|
||||||
dpiX: 1,
|
|
||||||
dpiY: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hw: {
|
|
||||||
make: 'Microsoft',
|
|
||||||
model: 'unknown',
|
|
||||||
sdktype: 'web',
|
|
||||||
},
|
|
||||||
os: {
|
|
||||||
name: 'windows',
|
|
||||||
ver: '22631.2715',
|
|
||||||
platform: 'desktop',
|
|
||||||
},
|
|
||||||
browser: {
|
|
||||||
browserName: 'chrome',
|
|
||||||
browserVersion: '125.0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
private static async handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
private static async handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
||||||
const bypassServer = getPref<string>(PrefKey.SERVER_BYPASS_RESTRICTION);
|
const bypassServer = getPref<string>(PrefKey.SERVER_BYPASS_RESTRICTION);
|
||||||
if (bypassServer !== 'off') {
|
if (bypassServer !== 'off') {
|
||||||
@ -92,7 +52,7 @@ export class XcloudInterceptor {
|
|||||||
const response = await NATIVE_FETCH(request, init);
|
const response = await NATIVE_FETCH(request, init);
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
// Unsupported region
|
// Unsupported region
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE);
|
BxEventBus.Script.emit('xcloud.server.unavailable', {});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,12 +76,13 @@ export class XcloudInterceptor {
|
|||||||
|
|
||||||
let match = serverRegex.exec(region.baseUri);
|
let match = serverRegex.exec(region.baseUri);
|
||||||
if (match) {
|
if (match) {
|
||||||
shortName = match[1];
|
shortName = match[1] as string;
|
||||||
if (serverExtra[regionName]) {
|
if (serverExtra[regionName]) {
|
||||||
shortName = serverExtra[regionName][0] + ' ' + shortName;
|
shortName = serverExtra[regionName][0] + ' ' + shortName;
|
||||||
region.contintent = serverExtra[regionName][1];
|
region.contintent = serverExtra[regionName][1];
|
||||||
} else {
|
} else {
|
||||||
region.contintent = 'other';
|
region.contintent = 'other';
|
||||||
|
BX_FLAGS.Debug && alert('New server: ' + shortName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +90,7 @@ export class XcloudInterceptor {
|
|||||||
STATES.serverRegions[region.name] = Object.assign({}, region);
|
STATES.serverRegions[region.name] = Object.assign({}, region);
|
||||||
}
|
}
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY);
|
BxEventBus.Script.emit('xcloud.server.ready', {});
|
||||||
|
|
||||||
const preferredRegion = getPreferredServerRegion();
|
const preferredRegion = getPreferredServerRegion();
|
||||||
if (preferredRegion && preferredRegion in STATES.serverRegions) {
|
if (preferredRegion && preferredRegion in STATES.serverRegions) {
|
||||||
@ -147,7 +108,7 @@ export class XcloudInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
private static async handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_LOADING);
|
BxEventBus.Stream.emit('state.loading', {});
|
||||||
|
|
||||||
const PREF_STREAM_TARGET_RESOLUTION = getPref<StreamResolution>(PrefKey.STREAM_RESOLUTION);
|
const PREF_STREAM_TARGET_RESOLUTION = getPref<StreamResolution>(PrefKey.STREAM_RESOLUTION);
|
||||||
const PREF_STREAM_PREFERRED_LOCALE = getPref<StreamPreferredLocale>(PrefKey.STREAM_PREFERRED_LOCALE);
|
const PREF_STREAM_PREFERRED_LOCALE = getPref<StreamPreferredLocale>(PrefKey.STREAM_PREFERRED_LOCALE);
|
||||||
@ -155,10 +116,10 @@ export class XcloudInterceptor {
|
|||||||
const url = (typeof request === 'string') ? request : (request as Request).url;
|
const url = (typeof request === 'string') ? request : (request as Request).url;
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
let badgeRegion: string = parsedUrl.host.split('.', 1)[0];
|
let badgeRegion: string = parsedUrl.host.split('.', 1)[0] as string;
|
||||||
for (let regionName in STATES.serverRegions) {
|
for (let regionName in STATES.serverRegions) {
|
||||||
const region = STATES.serverRegions[regionName];
|
const region = STATES.serverRegions[regionName];
|
||||||
if (parsedUrl.origin == region.baseUri) {
|
if (region && parsedUrl.origin === region.baseUri) {
|
||||||
badgeRegion = regionName;
|
badgeRegion = regionName;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -168,31 +129,15 @@ export class XcloudInterceptor {
|
|||||||
const clone = (request as Request).clone();
|
const clone = (request as Request).clone();
|
||||||
const body = await clone.json();
|
const body = await clone.json();
|
||||||
|
|
||||||
const headers: {[index: string]: string} = {};
|
const headers: { [index: string]: string } = {};
|
||||||
for (const pair of (clone.headers as any).entries()) {
|
for (const pair of (clone.headers as any).entries()) {
|
||||||
headers[pair[0]] = pair[1];
|
headers[pair[0]] = pair[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force stream's resolution
|
// Force stream's resolution
|
||||||
if (PREF_STREAM_TARGET_RESOLUTION !== 'auto') {
|
if (PREF_STREAM_TARGET_RESOLUTION !== 'auto') {
|
||||||
let osName;
|
const osName = getOsNameFromResolution(PREF_STREAM_TARGET_RESOLUTION);
|
||||||
switch (PREF_STREAM_TARGET_RESOLUTION) {
|
headers['x-ms-device-info'] = JSON.stringify(generateMsDeviceInfo(osName));
|
||||||
case StreamResolution.DIM_1080P_HQ:
|
|
||||||
osName = 'tizen';
|
|
||||||
|
|
||||||
const deviceInfo = XcloudInterceptor.BASE_DEVICE_INFO;
|
|
||||||
deviceInfo.dev.os.name = 'tizen';
|
|
||||||
headers['x-ms-device-info'] = JSON.stringify(deviceInfo);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case StreamResolution.DIM_1080P:
|
|
||||||
osName = 'windows';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
osName = 'android';
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
body.settings.osName = osName;
|
body.settings.osName = osName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +190,7 @@ export class XcloudInterceptor {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_STARTING);
|
BxEventBus.Stream.emit('state.starting', {});
|
||||||
|
|
||||||
const obj = JSON.parse(text);
|
const obj = JSON.parse(text);
|
||||||
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {};
|
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user