mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-02 04:11:44 +02:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
d1b99705e6 | |||
52896c94ae | |||
cadc7987b7 | |||
8fb1787222 | |||
4231d7e9c6 | |||
ba05eab47b | |||
e852b246d3 | |||
23fb50cb6f | |||
443bf93c9a | |||
df2af43c64 | |||
fca3bee6dd | |||
9bf8a2ef66 | |||
b1df189c7d | |||
d91fdb798e | |||
a291443d43 | |||
8a7be5d523 | |||
7588f37472 | |||
a597d52585 | |||
f945a3adde | |||
438afe086a | |||
f6ee79770c | |||
f36c77e727 |
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 4.1.0
|
||||
// @version 4.1.2
|
||||
// ==/UserScript==
|
||||
|
912
dist/better-xcloud.user.js
vendored
912
dist/better-xcloud.user.js
vendored
File diff suppressed because it is too large
Load Diff
@ -7,3 +7,15 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
|
||||
background-color: #2d2d2d !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.bx-stream-refresh-button {
|
||||
top: calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important;
|
||||
}
|
||||
|
||||
body[data-media-type=default] .bx-stream-refresh-button {
|
||||
left: calc(env(safe-area-inset-left, 0px) + 11px) !important;
|
||||
}
|
||||
|
||||
body[data-media-type=tv] .bx-stream-refresh-button {
|
||||
top: calc(var(--gds-focus-borderSize) + 80px) !important;
|
||||
}
|
||||
|
3
src/assets/svg/refresh.svg
Normal file
3
src/assets/svg/refresh.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||
<path d="M23.247 12.377h7.247V5.13"/><path d="M23.911 25.663a13.29 13.29 0 0 1-9.119 3.623C7.504 29.286 1.506 23.289 1.506 16S7.504 2.713 14.792 2.713a13.29 13.29 0 0 1 9.395 3.891l6.307 5.772"/>
|
||||
</svg>
|
After Width: | Height: | Size: 378 B |
@ -23,7 +23,7 @@ import { RemotePlay } from "@modules/remote-play";
|
||||
import { onHistoryChanged, patchHistoryMethod } from "@utils/history";
|
||||
import { VibrationManager } from "@modules/vibration-manager";
|
||||
import { PreloadedState } from "@utils/titles-info";
|
||||
import { patchAudioContext, patchRtcCodecs, patchRtcPeerConnection, patchVideoApi } from "@utils/monkey-patches";
|
||||
import { patchAudioContext, patchMeControl, patchRtcCodecs, patchRtcPeerConnection, patchVideoApi } from "@utils/monkey-patches";
|
||||
import { STATES } from "@utils/global";
|
||||
import { injectStreamMenuButtons } from "@modules/stream/stream-ui";
|
||||
import { BxLogger } from "@utils/bx-logger";
|
||||
@ -216,9 +216,8 @@ function main() {
|
||||
interceptHttpRequests();
|
||||
patchVideoApi();
|
||||
|
||||
if (getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL)) {
|
||||
patchAudioContext();
|
||||
}
|
||||
getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext();
|
||||
getPref(PrefKey.BLOCK_TRACKING) && patchMeControl();
|
||||
|
||||
PreloadedState.override();
|
||||
|
||||
|
@ -59,22 +59,25 @@ const PATCHES = {
|
||||
|
||||
// Disable IndexDB logging
|
||||
disableIndexDbLogging(str: string) {
|
||||
const text = 'async addLog(e,t=1e4){';
|
||||
const text = ',this.logsDb=new';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return str.replace(text, text + 'return;');
|
||||
// Replace log() with an empty function
|
||||
let newCode = ',this.log=()=>{}';
|
||||
return str.replace(text, newCode + text);
|
||||
},
|
||||
|
||||
// Set TV layout
|
||||
tvLayout(str: string) {
|
||||
// Set custom website layout
|
||||
websiteLayout(str: string) {
|
||||
const text = '?"tv":"default"';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return str.replace(text, '?"tv":"tv"');
|
||||
const layout = getPref(PrefKey.UI_LAYOUT) === 'tv' ? 'tv' : 'default';
|
||||
return str.replace(text, `?"${layout}":"${layout}"`);
|
||||
},
|
||||
|
||||
// Replace "/direct-connect" with "/play"
|
||||
@ -458,6 +461,29 @@ BxLogger.info('patchRemotePlayMkb', ${configsVar});
|
||||
return str;
|
||||
|
||||
},
|
||||
|
||||
patchAudioMediaStream(str: string) {
|
||||
const text = '.srcObject=this.audioMediaStream,';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newCode = `window.BX_EXPOSED.setupGainNode(arguments[1], this.audioMediaStream),`;
|
||||
|
||||
str = str.replace(text, text + newCode);
|
||||
return str;
|
||||
},
|
||||
|
||||
patchCombinedAudioVideoMediaStream(str: string) {
|
||||
const text = '.srcObject=this.combinedAudioVideoStream';
|
||||
if (!str.includes(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newCode = `,window.BX_EXPOSED.setupGainNode(arguments[0], this.combinedAudioVideoStream)`;
|
||||
str = str.replace(text, text + newCode);
|
||||
return str;
|
||||
},
|
||||
};
|
||||
|
||||
let PATCH_ORDERS: PatchArray = [
|
||||
@ -465,7 +491,7 @@ let PATCH_ORDERS: PatchArray = [
|
||||
'overrideSettings',
|
||||
'broadcastPollingMode',
|
||||
|
||||
getPref(PrefKey.UI_LAYOUT) === 'tv' && 'tvLayout',
|
||||
getPref(PrefKey.UI_LAYOUT) !== 'default' && 'websiteLayout',
|
||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
||||
|
||||
@ -500,6 +526,12 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
|
||||
'patchStreamHud',
|
||||
'playVibration',
|
||||
|
||||
// Patch volume control for normal stream
|
||||
getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && !getPref(PrefKey.STREAM_COMBINE_SOURCES) && 'patchAudioMediaStream',
|
||||
// Patch volume control for combined audio+video stream
|
||||
getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && getPref(PrefKey.STREAM_COMBINE_SOURCES) && 'patchCombinedAudioVideoMediaStream',
|
||||
|
||||
|
||||
STATES.hasTouchSupport && getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'exposeTouchLayoutManager',
|
||||
STATES.hasTouchSupport && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
|
||||
|
||||
@ -520,8 +552,14 @@ const ALL_PATCHES = [...PATCH_ORDERS, ...PLAYING_PATCH_ORDERS];
|
||||
export class Patcher {
|
||||
static #patchFunctionBind() {
|
||||
const nativeBind = Function.prototype.bind;
|
||||
Function.prototype.bind = function () {
|
||||
Function.prototype.bind = function() {
|
||||
let valid = false;
|
||||
|
||||
// Looking for these criteria:
|
||||
// - Variable name <= 2 characters
|
||||
// - Has 2 params:
|
||||
// - The first one is null
|
||||
// - The second one is either 0 or a function
|
||||
if (this.name.length <= 2 && arguments.length === 2 && arguments[0] === null) {
|
||||
if (arguments[1] === 0 || (typeof arguments[1] === 'function')) {
|
||||
valid = true;
|
||||
@ -533,6 +571,8 @@ export class Patcher {
|
||||
return nativeBind.apply(this, arguments);
|
||||
}
|
||||
|
||||
PatcherCache.init();
|
||||
|
||||
if (typeof arguments[1] === 'function') {
|
||||
BxLogger.info(LOG_TAG, 'Restored Function.prototype.bind()');
|
||||
Function.prototype.bind = nativeBind;
|
||||
@ -549,23 +589,23 @@ export class Patcher {
|
||||
};
|
||||
}
|
||||
|
||||
static length() { return PATCH_ORDERS.length; };
|
||||
|
||||
static patch(item: [[number], { [key: string]: () => {} }]) {
|
||||
// !!! Use "caches" as variable name will break touch controller???
|
||||
// console.log('patch', '-----');
|
||||
let patchesToCheck: PatchArray;
|
||||
let appliedPatches;
|
||||
const caches: { [key: string]: string[] } = {};
|
||||
let appliedPatches: PatchArray;
|
||||
|
||||
const patchesMap: Record<string, PatchArray> = {};
|
||||
|
||||
for (let id in item[1]) {
|
||||
appliedPatches = [];
|
||||
|
||||
const cachedPatches = PatcherCache.getPatches(id);
|
||||
if (cachedPatches) {
|
||||
patchesToCheck = cachedPatches;
|
||||
patchesToCheck = cachedPatches.slice(0);
|
||||
patchesToCheck.push(...PATCH_ORDERS);
|
||||
} else {
|
||||
patchesToCheck = PATCH_ORDERS;
|
||||
patchesToCheck = PATCH_ORDERS.slice(0);
|
||||
}
|
||||
|
||||
// Empty patch list
|
||||
@ -573,20 +613,21 @@ export class Patcher {
|
||||
continue;
|
||||
}
|
||||
|
||||
// console.log(patchesToCheck);
|
||||
const func = item[1][id];
|
||||
let str = func.toString();
|
||||
|
||||
// console.log(id, str);
|
||||
|
||||
for (let groupIndex = 0; groupIndex < patchesToCheck.length; groupIndex++) {
|
||||
const patchName = patchesToCheck[groupIndex];
|
||||
let modified = false;
|
||||
let modified = false;
|
||||
|
||||
for (let patchIndex = 0; patchIndex < patchesToCheck.length; patchIndex++) {
|
||||
const patchName = patchesToCheck[patchIndex];
|
||||
if (appliedPatches.indexOf(patchName) > -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PATCHES[patchName]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check function against patch
|
||||
const patchedStr = PATCHES[patchName].call(null, str);
|
||||
|
||||
@ -598,28 +639,28 @@ export class Patcher {
|
||||
modified = true;
|
||||
str = patchedStr;
|
||||
|
||||
BxLogger.info(LOG_TAG, `Applied "${patchName}" patch`);
|
||||
BxLogger.info(LOG_TAG, `✅ ${patchName}`);
|
||||
appliedPatches.push(patchName);
|
||||
|
||||
// Remove patch
|
||||
patchesToCheck.splice(groupIndex, 1);
|
||||
groupIndex--;
|
||||
patchesToCheck.splice(patchIndex, 1);
|
||||
patchIndex--;
|
||||
PATCH_ORDERS = PATCH_ORDERS.filter(item => item != patchName);
|
||||
}
|
||||
|
||||
// Apply patched functions
|
||||
if (modified) {
|
||||
item[1][id] = eval(str);
|
||||
}
|
||||
// Apply patched functions
|
||||
if (modified) {
|
||||
item[1][id] = eval(str);
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
if (appliedPatches.length) {
|
||||
caches[id] = appliedPatches;
|
||||
patchesMap[id] = appliedPatches;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(caches).length) {
|
||||
PatcherCache.saveToCache(caches);
|
||||
if (Object.keys(patchesMap).length) {
|
||||
PatcherCache.saveToCache(patchesMap);
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,12 +669,14 @@ export class Patcher {
|
||||
}
|
||||
}
|
||||
|
||||
class PatcherCache {
|
||||
export class PatcherCache {
|
||||
static #KEY_CACHE = 'better_xcloud_patches_cache';
|
||||
static #KEY_SIGNATURE = 'better_xcloud_patches_cache_signature';
|
||||
|
||||
static #CACHE: any;
|
||||
|
||||
static #isInitialized = false;
|
||||
|
||||
/**
|
||||
* Get patch's signature
|
||||
*/
|
||||
@ -647,18 +690,22 @@ class PatcherCache {
|
||||
return sig;
|
||||
}
|
||||
|
||||
static clear() {
|
||||
// Clear cache
|
||||
window.localStorage.removeItem(PatcherCache.#KEY_CACHE);
|
||||
PatcherCache.#CACHE = {};
|
||||
}
|
||||
|
||||
static checkSignature() {
|
||||
const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0;
|
||||
const currentSig = PatcherCache.#getSignature();
|
||||
|
||||
if (currentSig !== parseInt(storedSig as string)) {
|
||||
BxLogger.warning(LOG_TAG, 'Signature changed');
|
||||
|
||||
// Clear cache
|
||||
window.localStorage.setItem(PatcherCache.#KEY_CACHE, '{}');
|
||||
|
||||
// Save new signature
|
||||
BxLogger.warning(LOG_TAG, 'Signature changed');
|
||||
window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString());
|
||||
|
||||
PatcherCache.clear();
|
||||
} else {
|
||||
BxLogger.info(LOG_TAG, 'Signature unchanged');
|
||||
}
|
||||
@ -682,7 +729,7 @@ class PatcherCache {
|
||||
return PatcherCache.#CACHE[id];
|
||||
}
|
||||
|
||||
static saveToCache(subCache: { [key: string]: string[] }) {
|
||||
static saveToCache(subCache: Record<string, PatchArray>) {
|
||||
for (const id in subCache) {
|
||||
const patchNames = subCache[id];
|
||||
|
||||
@ -703,6 +750,13 @@ class PatcherCache {
|
||||
}
|
||||
|
||||
static init() {
|
||||
if (PatcherCache.#isInitialized) {
|
||||
return;
|
||||
}
|
||||
PatcherCache.#isInitialized = true;
|
||||
|
||||
PatcherCache.checkSignature();
|
||||
|
||||
// Read cache from storage
|
||||
PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || '{}');
|
||||
BxLogger.info(LOG_TAG, PatcherCache.#CACHE);
|
||||
@ -721,11 +775,3 @@ class PatcherCache {
|
||||
BxLogger.info(LOG_TAG, PLAYING_PATCH_ORDERS.slice(0));
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('readystatechange', e => {
|
||||
if (document.readyState === 'interactive') {
|
||||
PatcherCache.checkSignature();
|
||||
}
|
||||
});
|
||||
|
||||
PatcherCache.init();
|
||||
|
@ -8,65 +8,6 @@ import { StreamBadges } from "./stream-badges.ts";
|
||||
import { StreamStats } from "./stream-stats.ts";
|
||||
|
||||
|
||||
class MouseHoldEvent {
|
||||
#isHolding = false;
|
||||
#timeout?: number | null;
|
||||
|
||||
#$elm;
|
||||
#callback;
|
||||
#duration;
|
||||
|
||||
#onMouseDown(e: MouseEvent | TouchEvent) {
|
||||
const _this = this;
|
||||
this.#isHolding = false;
|
||||
|
||||
this.#timeout && clearTimeout(this.#timeout);
|
||||
this.#timeout = window.setTimeout(() => {
|
||||
_this.#isHolding = true;
|
||||
_this.#callback();
|
||||
}, this.#duration);
|
||||
};
|
||||
|
||||
#onMouseUp(e: MouseEvent | TouchEvent) {
|
||||
this.#timeout && clearTimeout(this.#timeout);
|
||||
this.#timeout = null;
|
||||
|
||||
if (this.#isHolding) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
this.#isHolding = false;
|
||||
};
|
||||
|
||||
#addEventListeners = () => {
|
||||
this.#$elm.addEventListener('mousedown', this.#onMouseDown.bind(this));
|
||||
this.#$elm.addEventListener('click', this.#onMouseUp.bind(this));
|
||||
|
||||
this.#$elm.addEventListener('touchstart', this.#onMouseDown.bind(this));
|
||||
this.#$elm.addEventListener('touchend', this.#onMouseUp.bind(this));
|
||||
}
|
||||
|
||||
/*
|
||||
#clearEventLiseners = () => {
|
||||
this.#$elm.removeEventListener('mousedown', this.#onMouseDown);
|
||||
this.#$elm.removeEventListener('click', this.#onMouseUp);
|
||||
|
||||
this.#$elm.removeEventListener('touchstart', this.#onMouseDown);
|
||||
this.#$elm.removeEventListener('touchend', this.#onMouseUp);
|
||||
}
|
||||
*/
|
||||
|
||||
constructor($elm: HTMLElement, callback: any, duration=1000) {
|
||||
this.#$elm = $elm;
|
||||
this.#callback = callback;
|
||||
this.#duration = duration;
|
||||
|
||||
this.#addEventListeners();
|
||||
// $elm.clearMouseHoldEventListeners = this.#clearEventLiseners;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cloneStreamHudButton($orgButton: HTMLElement, label: string, svgIcon: typeof BxIcon) {
|
||||
const $container = $orgButton.cloneNode(true) as HTMLElement;
|
||||
let timeout: number | null;
|
||||
@ -192,25 +133,39 @@ export function injectStreamMenuButtons() {
|
||||
}
|
||||
|
||||
// Render badges
|
||||
if ($elm.className.startsWith('StreamMenu')) {
|
||||
if ($elm.className.startsWith('StreamMenu-module__container')) {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_MENU_SHOWN);
|
||||
|
||||
// Hide Quick bar when closing HUD
|
||||
const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]');
|
||||
if (!$btnCloseHud) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide Quick bar when closing HUD
|
||||
$btnCloseHud && $btnCloseHud.addEventListener('click', e => {
|
||||
$quickBar.classList.add('bx-gone');
|
||||
});
|
||||
|
||||
// Get "Quit game" button
|
||||
const $btnQuit = $elm.querySelector('div[class^=StreamMenu] > div > button:last-child') as HTMLElement;
|
||||
// Hold "Quit game" button to refresh the stream
|
||||
new MouseHoldEvent($btnQuit, () => {
|
||||
// Create Refresh button from the Close button
|
||||
const $btnRefresh = $btnCloseHud.cloneNode(true) as HTMLElement;
|
||||
|
||||
// Refresh SVG
|
||||
const $svgRefresh = createSvgIcon(BxIcon.REFRESH);
|
||||
// Copy classes
|
||||
$svgRefresh.setAttribute('class', $btnRefresh.firstElementChild!.getAttribute('class') || '');
|
||||
$svgRefresh.style.fill = 'none';
|
||||
|
||||
$btnRefresh.classList.add('bx-stream-refresh-button');
|
||||
// Remove icon
|
||||
$btnRefresh.removeChild($btnRefresh.firstElementChild!);
|
||||
// Add Refresh icon
|
||||
$btnRefresh.appendChild($svgRefresh);
|
||||
// Add "click" event listener
|
||||
$btnRefresh.addEventListener('click', e => {
|
||||
confirm(t('confirm-reload-stream')) && window.location.reload();
|
||||
}, 1000);
|
||||
});
|
||||
// Add to website
|
||||
$btnCloseHud.insertAdjacentElement('afterend', $btnRefresh);
|
||||
|
||||
// Render stream badges
|
||||
const $menu = document.querySelector('div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]');
|
||||
|
@ -178,9 +178,7 @@ export class TouchController {
|
||||
layout: {
|
||||
id: 'System.Standard',
|
||||
displayName: 'System',
|
||||
layoutFile: {
|
||||
content: layout.content,
|
||||
},
|
||||
layoutFile: layout,
|
||||
}
|
||||
});
|
||||
}, delay);
|
||||
@ -188,7 +186,7 @@ export class TouchController {
|
||||
|
||||
static setup() {
|
||||
// Function for testing touch control
|
||||
window.BX_EXPOSED.test_touch_control = (content: any) => {
|
||||
window.BX_EXPOSED.test_touch_control = (layout: any) => {
|
||||
const { touch_layout_manager } = window.BX_EXPOSED;
|
||||
|
||||
touch_layout_manager && touch_layout_manager.changeLayoutForScope({
|
||||
@ -198,9 +196,7 @@ export class TouchController {
|
||||
layout: {
|
||||
id: 'System.Standard',
|
||||
displayName: 'Custom',
|
||||
layoutFile: {
|
||||
content: content,
|
||||
},
|
||||
layoutFile: layout,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { getPreferredServerRegion } from "@utils/region";
|
||||
import { UserAgent, UserAgentProfile } from "@utils/user-agent";
|
||||
import { getPref, Preferences, PrefKey, setPref, toPrefElement } from "@utils/preferences";
|
||||
import { t, refreshCurrentLocale } from "@utils/translation";
|
||||
import { PatcherCache } from "../patcher";
|
||||
|
||||
const SETTINGS_UI = {
|
||||
'Better xCloud': {
|
||||
@ -159,6 +160,9 @@ export function setupSettingsUi() {
|
||||
|
||||
$reloadBtnWrapper.classList.remove('bx-gone');
|
||||
|
||||
// Clear PatcherCache;
|
||||
PatcherCache.clear();
|
||||
|
||||
if ((e.target as HTMLElement).id === 'bx_setting_' + PrefKey.BETTER_XCLOUD_LOCALE) {
|
||||
// Update locale
|
||||
refreshCurrentLocale();
|
||||
|
@ -73,9 +73,9 @@ export const BxExposed = {
|
||||
|
||||
// Pre-check supported input types
|
||||
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
||||
titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) &&
|
||||
!supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) &&
|
||||
!supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
||||
titleInfo.details.hasTouchSupport = supportedInputTypes.includes(InputType.NATIVE_TOUCH) ||
|
||||
supportedInputTypes.includes(InputType.CUSTOM_TOUCH_OVERLAY) ||
|
||||
supportedInputTypes.includes(InputType.GENERIC_TOUCH);
|
||||
|
||||
if (!titleInfo.details.hasTouchSupport && touchControllerAvailability === 'all') {
|
||||
// Add generic touch support for non touch-supported games
|
||||
@ -91,5 +91,26 @@ export const BxExposed = {
|
||||
BxEvent.dispatch(window, BxEvent.TITLE_INFO_READY);
|
||||
|
||||
return titleInfo;
|
||||
},
|
||||
|
||||
setupGainNode: ($media: HTMLMediaElement, audioStream: MediaStream) => {
|
||||
if ($media instanceof HTMLAudioElement) {
|
||||
$media.muted = true;
|
||||
$media.addEventListener('playing', e => {
|
||||
$media.muted = true;
|
||||
$media.pause();
|
||||
});
|
||||
} else {
|
||||
$media.muted = true;
|
||||
$media.addEventListener('playing', e => {
|
||||
$media.muted = true;
|
||||
});
|
||||
}
|
||||
|
||||
const audioCtx = STATES.currentStream.audioContext!;
|
||||
const source = audioCtx.createMediaStreamSource(audioStream);
|
||||
|
||||
const gainNode = audioCtx.createGain(); // call monkey-patched createGain() in BxAudioContext
|
||||
source.connect(gainNode).connect(audioCtx.destination);
|
||||
}
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import iconMouseSettings from "@assets/svg/mouse-settings.svg" with { type: "tex
|
||||
import iconMouse from "@assets/svg/mouse.svg" with { type: "text" };
|
||||
import iconNew from "@assets/svg/new.svg" with { type: "text" };
|
||||
import iconQuestion from "@assets/svg/question.svg" with { type: "text" };
|
||||
import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
|
||||
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
|
||||
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
|
||||
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
|
||||
@ -23,6 +24,7 @@ export const BxIcon = {
|
||||
TRASH: iconTrash,
|
||||
CURSOR_TEXT: iconCursorText,
|
||||
QUESTION: iconQuestion,
|
||||
REFRESH: iconRefresh,
|
||||
|
||||
REMOTE_PLAY: iconRemotePlay,
|
||||
|
||||
|
@ -20,7 +20,7 @@ export class BxLogger {
|
||||
}
|
||||
|
||||
static #log(color: TextColor, tag: string, ...args: any) {
|
||||
console.log('%c' + BxLogger.#PREFIX, 'color:' + color + ';font-weight:bold;', tag, '-', ...args);
|
||||
console.log(`%c${BxLogger.#PREFIX}`, `color:${color};font-weight:bold;`, tag, '//', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { getPref, PrefKey } from "@utils/preferences";
|
||||
import { STATES } from "@utils/global";
|
||||
import { UserAgent } from "@utils/user-agent";
|
||||
import { BxLogger } from "@utils/bx-logger";
|
||||
|
||||
export function patchVideoApi() {
|
||||
@ -104,10 +103,6 @@ export function patchRtcPeerConnection() {
|
||||
STATES.currentStream.peerConnection = conn;
|
||||
|
||||
conn.addEventListener('connectionstatechange', e => {
|
||||
if (conn.connectionState === 'connecting') {
|
||||
STATES.currentStream.audioGainNode = null;
|
||||
}
|
||||
|
||||
BxLogger.info('connectionstatechange', conn.connectionState);
|
||||
});
|
||||
return conn;
|
||||
@ -115,46 +110,73 @@ export function patchRtcPeerConnection() {
|
||||
}
|
||||
|
||||
export function patchAudioContext() {
|
||||
if (UserAgent.isSafari(true)) {
|
||||
const nativeCreateGain = window.AudioContext.prototype.createGain;
|
||||
window.AudioContext.prototype.createGain = function() {
|
||||
const OrgAudioContext = window.AudioContext;
|
||||
const nativeCreateGain = OrgAudioContext.prototype.createGain;
|
||||
|
||||
// @ts-ignore
|
||||
window.AudioContext = function(options?: AudioContextOptions | undefined): AudioContext {
|
||||
const ctx = new OrgAudioContext(options);
|
||||
BxLogger.info('patchAudioContext', ctx, options);
|
||||
|
||||
ctx.createGain = function() {
|
||||
const gainNode = nativeCreateGain.apply(this);
|
||||
gainNode.gain.value = getPref(PrefKey.AUDIO_VOLUME) / 100;
|
||||
|
||||
STATES.currentStream.audioGainNode = gainNode;
|
||||
return gainNode;
|
||||
}
|
||||
}
|
||||
|
||||
const OrgAudioContext = window.AudioContext;
|
||||
// @ts-ignore
|
||||
window.AudioContext = function() {
|
||||
const ctx = new OrgAudioContext();
|
||||
STATES.currentStream.audioContext = ctx;
|
||||
STATES.currentStream.audioGainNode = null;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
const nativePlay = HTMLAudioElement.prototype.play;
|
||||
HTMLAudioElement.prototype.play = function() {
|
||||
this.muted = true;
|
||||
|
||||
const promise = nativePlay.apply(this);
|
||||
if (STATES.currentStream.audioGainNode) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
this.addEventListener('playing', e => (e.target as HTMLAudioElement).pause());
|
||||
|
||||
const audioCtx = STATES.currentStream.audioContext!;
|
||||
// TOOD: check srcObject
|
||||
const audioStream = audioCtx.createMediaStreamSource(this.srcObject as any);
|
||||
const gainNode = audioCtx.createGain();
|
||||
|
||||
audioStream.connect(gainNode);
|
||||
gainNode.connect(audioCtx.destination);
|
||||
gainNode.gain.value = getPref(PrefKey.AUDIO_VOLUME) / 100;
|
||||
STATES.currentStream.audioGainNode = gainNode;
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable telemetry flags in meversion.js
|
||||
*/
|
||||
export function patchMeControl() {
|
||||
const overrideConfigs = {
|
||||
enableAADTelemetry: false,
|
||||
enableTelemetry: false,
|
||||
telEvs: '',
|
||||
oneDSUrl: '',
|
||||
};
|
||||
|
||||
const MSA = {
|
||||
MeControl: {},
|
||||
};
|
||||
const MeControl = {};
|
||||
|
||||
const MsaHandler: ProxyHandler<any> = {
|
||||
get(target, prop, receiver) {
|
||||
return target[prop];
|
||||
},
|
||||
|
||||
set(obj, prop, value) {
|
||||
if (prop === 'MeControl' && value.Config) {
|
||||
value.Config = Object.assign(value.Config, overrideConfigs);
|
||||
}
|
||||
|
||||
obj[prop] = value;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
const MeControlHandler: ProxyHandler<any> = {
|
||||
get(target, prop, receiver) {
|
||||
return target[prop];
|
||||
},
|
||||
|
||||
set(obj, prop, value) {
|
||||
if (prop === 'Config') {
|
||||
value = Object.assign(value, overrideConfigs);
|
||||
}
|
||||
|
||||
obj[prop] = value;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
(window as any).MSA = new Proxy(MSA, MsaHandler);
|
||||
(window as any).MeControl = new Proxy(MeControl, MeControlHandler);
|
||||
}
|
||||
|
@ -417,6 +417,7 @@ export class Preferences {
|
||||
default: 'default',
|
||||
options: {
|
||||
default: t('default'),
|
||||
normal: t('normal'),
|
||||
tv: t('smart-tv'),
|
||||
},
|
||||
},
|
||||
@ -441,7 +442,9 @@ export class Preferences {
|
||||
[UserAgentProfile.DEFAULT]: t('default'),
|
||||
[UserAgentProfile.EDGE_WINDOWS]: 'Edge + Windows',
|
||||
[UserAgentProfile.SAFARI_MACOS]: 'Safari + macOS',
|
||||
[UserAgentProfile.SMARTTV]: 'Smart TV',
|
||||
[UserAgentProfile.SMARTTV_TIZEN]: 'Samsung Smart TV',
|
||||
[UserAgentProfile.VR_OCULUS]: 'Meta Quest VR',
|
||||
[UserAgentProfile.KIWI_V123]: 'Kiwi Browser v123',
|
||||
[UserAgentProfile.CUSTOM]: t('custom'),
|
||||
},
|
||||
|
@ -3,7 +3,9 @@ import { PrefKey, getPref } from "@utils/preferences";
|
||||
export enum UserAgentProfile {
|
||||
EDGE_WINDOWS = 'edge-windows',
|
||||
SAFARI_MACOS = 'safari-macos',
|
||||
SMARTTV = 'smarttv',
|
||||
SMARTTV_TIZEN = 'smarttv-tizen',
|
||||
VR_OCULUS = 'vr-oculus',
|
||||
KIWI_V123 = 'kiwi-v123',
|
||||
DEFAULT = 'default',
|
||||
CUSTOM = 'custom',
|
||||
@ -26,7 +28,9 @@ export class UserAgent {
|
||||
static #USER_AGENTS = {
|
||||
[UserAgentProfile.EDGE_WINDOWS]: EDGE_USER_AGENT,
|
||||
[UserAgentProfile.SAFARI_MACOS]: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1',
|
||||
[UserAgentProfile.SMARTTV]: window.navigator.userAgent + ' SmartTV',
|
||||
[UserAgentProfile.SMARTTV_TIZEN]: 'Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) 94.0.4606.31/7.0 TV Safari/537.36',
|
||||
[UserAgentProfile.VR_OCULUS]: window.navigator.userAgent + ' OculusBrowser VR',
|
||||
[UserAgentProfile.KIWI_V123]: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.118 Mobile Safari/537.36',
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user