Compare commits

...

8 Commits

Author SHA1 Message Date
redphx
033ac31333 Bump version to 3.1.3 2024-02-03 07:16:46 +07:00
redphx
1f754d4a1d Add STREAM_STOPPED and STREAM_ERROR_PAGE events 2024-02-02 10:31:40 +07:00
redphx
0d39ccf8bf Typo 2024-02-02 10:04:59 +07:00
redphx
966d7f2f6c Hide touchscreen using bx-offscreen instead of bx-gone 2024-02-02 10:02:57 +07:00
redphx
fdbf618253 Add STREAM_MENU_SHOWN and STREAM_MENU_HIDDEN events 2024-02-02 09:59:42 +07:00
redphx
aaa7612293 Fix default touch control not showing sometimes 2024-02-01 20:22:18 +07:00
redphx
d578718958 Bump version to 3.1.2 2024-02-01 09:21:26 +07:00
redphx
46647dbffd Refactor + bug fixes (#245)
* Create DATA_CHANNEL_CREATED event

* Add BxEvent.dispatch()

* Dispatch STREAM_WEBRTC_CONNECTED event

* Fix not being able to hide touch control in Remote Play

* Fix touch bar again

* Listen to STREAM_LOADING & STREAM_STARTING events

* Add setupEvents() functions

* Show/hide touch controller using CSS instead

* Fix exception in LoadingScreen class

* Fix Remote Play stopped working
2024-02-01 09:20:17 +07:00
2 changed files with 359 additions and 239 deletions

View File

@@ -1,5 +1,5 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.1.1
// @version 3.1.3
// ==/UserScript==

View File

@@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.1.1
// @version 3.1.3
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@@ -13,7 +13,7 @@
// ==/UserScript==
'use strict';
const SCRIPT_VERSION = '3.1.1';
const SCRIPT_VERSION = '3.1.3';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
const ENABLE_XCLOUD_LOGGER = false;
@@ -44,11 +44,39 @@ const BxEvent = {
JUMP_BACK_IN_READY: 'bx-jump-back-in-ready',
POPSTATE: 'bx-popstate',
STREAM_LOADING: 'bx-stream-loading',
STREAM_STARTING: 'bx-stream-starting',
STREAM_STARTED: 'bx-stream-started',
STREAM_PLAYING: 'bx-stream-playing',
STREAM_STOPPED: 'bx-stream-stopped',
STREAM_ERROR_PAGE: 'bx-stream-error-page',
STREAM_MENU_SHOWN: 'bx-stream-menu-shown',
STREAM_MENU_HIDDEN: 'bx-stream-menu-hidden',
STREAM_WEBRTC_CONNECTED: 'bx-stream-webrtc-connected',
STREAM_WEBRTC_DISCONNECTED: 'bx-stream-webrtc-disconnected',
CUSTOM_TOUCH_LAYOUTS_LOADED: 'bx-custom-touch-layouts-loaded',
DATA_CHANNEL_CREATED: 'bx-data-channel-created',
dispatch: (target, eventName, data) => {
if (!eventName) {
alert('BxEvent.dispatch(): eventName is null');
return;
}
const event = new Event(eventName);
if (data) {
for (const key in data) {
event[key] = data[key];
}
}
target.dispatchEvent(event);
},
};
// Quickly create a tree of elements without having to use innerHTML
@@ -3068,7 +3096,29 @@ class RemotePlay {
return;
}
const GSSV_TOKEN = JSON.parse(localStorage.getItem('xboxcom_xbl_user_info')).tokens['http://gssv.xboxlive.com/'].token;
let GSSV_TOKEN;
try {
GSSV_TOKEN = JSON.parse(localStorage.getItem('xboxcom_xbl_user_info')).tokens['http://gssv.xboxlive.com/'].token;
} catch (e) {
for (let i = 0; i < localStorage.length; i++){
const key = localStorage.key(i);
if (!key.startsWith('Auth.User.')) {
continue;
}
const json = JSON.parse(localStorage.getItem(key));
for (const token of json.tokens) {
if (!token.relyingParty.includes('gssv.xboxlive.com')) {
continue;
}
GSSV_TOKEN = token.tokenData.token;
break;
}
break;
}
}
fetch('https://xhome.gssv-play-prod.xboxlive.com/v2/login/user', {
method: 'POST',
@@ -3317,20 +3367,24 @@ class LoadingScreen {
LoadingScreen.#orgWebTitle && (document.title = LoadingScreen.#orgWebTitle);
LoadingScreen.#$waitTimeBox && LoadingScreen.#$waitTimeBox.classList.add('bx-gone');
const $rocketBg = document.querySelector('#game-stream rect[width="800"]');
$rocketBg && $rocketBg.addEventListener('transitionend', e => {
LoadingScreen.#$bgStyle.textContent += `
if (LoadingScreen.#$bgStyle) {
const $rocketBg = document.querySelector('#game-stream rect[width="800"]');
$rocketBg && $rocketBg.addEventListener('transitionend', e => {
LoadingScreen.#$bgStyle.textContent += `
#game-stream {
background: #000 !important;
}
`;
});
});
LoadingScreen.#$bgStyle.textContent += `
LoadingScreen.#$bgStyle.textContent += `
#game-stream rect[width="800"] {
opacity: 1 !important;
}
`;
}
LoadingScreen.reset();
}
static reset() {
@@ -3386,12 +3440,12 @@ class TouchController {
}
static #show() {
TouchController.loadCustomLayout(GAME_XBOX_TITLE_ID, TouchController.#currentLayoutId, 0);
document.querySelector('#BabylonCanvasContainer-main').parentElement.classList.remove('bx-offscreen');
TouchController.#showing = true;
}
static #hide() {
TouchController.#dispatchMessage(TouchController.#EVENT_HIDE_CONTROLLER);
document.querySelector('#BabylonCanvasContainer-main').parentElement.classList.add('bx-offscreen');
TouchController.#showing = false;
}
@@ -3403,8 +3457,8 @@ class TouchController {
TouchController.#showing ? TouchController.#hide() : TouchController.#show();
}
static enableBar() {
TouchController.#$bar && TouchController.#$bar.setAttribute('data-showing', true);
static #toggleBar(value) {
TouchController.#$bar && TouchController.#$bar.setAttribute('data-showing', value);
}
static reset() {
@@ -3424,9 +3478,9 @@ class TouchController {
static getCustomLayouts(xboxTitleId) {
const dispatchLayouts = data => {
const event = new Event(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED);
event.data = data;
window.dispatchEvent(event);
BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
data: data,
});
};
xboxTitleId = '' + xboxTitleId;
@@ -3442,11 +3496,7 @@ class TouchController {
url += `${xboxTitleId}.json`;
}
NATIVE_FETCH(url)
.then(resp => resp.json(), () => {
TouchController.#customLayouts[xboxTitleId] = null;
// Wait for BX_EXPOSED.touch_layout_manager
setTimeout(() => dispatchLayouts(null), 1000);
})
.then(resp => resp.json())
.then(json => {
// Normalize data
const schema_version = json.schema_version || 1;
@@ -3468,6 +3518,11 @@ class TouchController {
// Wait for BX_EXPOSED.touch_layout_manager
setTimeout(() => dispatchLayouts(json), 1000);
})
.catch(() => {
TouchController.#customLayouts[xboxTitleId] = null;
// Wait for BX_EXPOSED.touch_layout_manager
setTimeout(() => dispatchLayouts(null), 1000);
});
}
@@ -3503,11 +3558,14 @@ class TouchController {
}
static setup() {
const $fragment = document.createDocumentFragment();
const $style = document.createElement('style');
document.documentElement.appendChild($style);
$fragment.appendChild($style);
const $bar = createElement('div', {'id': 'bx-touch-controller-bar'});
document.documentElement.appendChild($bar);
$fragment.appendChild($bar);
document.documentElement.appendChild($fragment);
// Setup double-tap event
let clickTimeout;
@@ -3531,11 +3589,10 @@ class TouchController {
const PREF_STYLE_STANDARD = getPref(Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD);
const PREF_STYLE_CUSTOM = getPref(Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM);
const nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel;
RTCPeerConnection.prototype.createDataChannel = function() {
const dataChannel = nativeCreateDataChannel.apply(this, arguments);
if (dataChannel.label !== 'message') {
return dataChannel;
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
const dataChannel = e.dataChannel;
if (!dataChannel || dataChannel.label !== 'message') {
return;
}
// Apply touch controller's style
@@ -3552,6 +3609,8 @@ class TouchController {
if (filter) {
$style.textContent = `#babylon-canvas { filter: ${filter} !important; }`;
} else {
$style.textContent = '';
}
TouchController.#dataChannel = dataChannel;
@@ -3576,14 +3635,19 @@ class TouchController {
try {
if (msg.data.includes('/titleinfo')) {
const json = JSON.parse(JSON.parse(msg.data).content);
const xboxTitleId = parseInt(json.titleid, 16);
GAME_XBOX_TITLE_ID = xboxTitleId;
}
} catch (e) { console.log(e) }
});
TouchController.#toggleBar(json.focused);
return dataChannel;
};
if (!json.focused) {
TouchController.#show();
}
GAME_XBOX_TITLE_ID = parseInt(json.titleid, 16);
}
} catch (e) {
console.log(e);
}
});
});
}
}
@@ -4725,8 +4789,8 @@ class MkbHandler {
this.#$message.addEventListener('click', this.#onActivatePointerLock);
document.documentElement.appendChild(this.#$message);
window.addEventListener('bx-stream-menu-shown', this.#onStreamMenuShown);
window.addEventListener('bx-stream-menu-hidden', this.#onStreamMenuHidden);
window.addEventListener(BxEvent.STREAM_MENU_SHOWN, this.#onStreamMenuShown);
window.addEventListener(BxEvent.STREAM_MENU_HIDDEN, this.#onStreamMenuHidden);
this.#waitForPointerLock(true);
}
@@ -4743,8 +4807,8 @@ class MkbHandler {
document.removeEventListener('pointerlockchange', this.#onPointerLockChange);
document.removeEventListener('pointerlockerror', this.#onPointerLockError);
window.removeEventListener('bx-stream-menu-shown', this.#onStreamMenuShown);
window.removeEventListener('bx-stream-menu-hidden', this.#onStreamMenuHidden);
window.removeEventListener(BxEvent.STREAM_MENU_SHOWN, this.#onStreamMenuShown);
window.removeEventListener(BxEvent.STREAM_MENU_HIDDEN, this.#onStreamMenuHidden);
}
start = () => {
@@ -4765,9 +4829,9 @@ class MkbHandler {
virtualGamepad.connected = true;
virtualGamepad.timestamp = performance.now();
const event = new Event('gamepadconnected');
event.gamepad = virtualGamepad;
window.dispatchEvent(event);
BxEvent.dispatch(window, 'gamepadconnected', {
gamepad: virtualGamepad,
});
}
stop = () => {
@@ -4777,9 +4841,9 @@ class MkbHandler {
virtualGamepad.connected = false;
virtualGamepad.timestamp = performance.now();
const event = new Event('gamepaddisconnected');
event.gamepad = virtualGamepad;
window.dispatchEvent(event);
BxEvent.dispatch(window, 'gamepaddisconnected', {
gamepad: virtualGamepad,
});
window.navigator.getGamepads = this.#nativeGetGamepads;
@@ -4793,6 +4857,16 @@ class MkbHandler {
window.removeEventListener('wheel', this.#onWheelEvent);
window.removeEventListener('contextmenu', this.#disableContextMenu);
}
static setupEvents() {
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
// Enable MKB
if (getPref(Preferences.MKB_ENABLED) && (!ENABLE_NATIVE_MKB_BETA || !window.NATIVE_MKB_TITLES.includes(GAME_PRODUCT_ID))) {
console.log('Emulate MKB');
MkbHandler.INSTANCE.init();
}
});
}
}
@@ -5518,11 +5592,10 @@ class VibrationManager {
VibrationManager.updateGlobalVars();
const orgCreateDataChannel = RTCPeerConnection.prototype.createDataChannel;
RTCPeerConnection.prototype.createDataChannel = function() {
const dataChannel = orgCreateDataChannel.apply(this, arguments);
if (dataChannel.label !== 'input') {
return dataChannel;
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
const dataChannel = e.dataChannel;
if (!dataChannel || dataChannel.label !== 'input') {
return;
}
const VIBRATION_DATA_MAP = {
@@ -5581,9 +5654,7 @@ class VibrationManager {
VibrationManager.#playDeviceVibration(data);
});
return dataChannel;
};
});
}
}
@@ -5828,6 +5899,22 @@ class StreamBadges {
return $wrapper;
}
static setupEvents() {
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
const $video = e.$video;
StreamBadges.resolution = {width: $video.videoWidth, height: $video.videoHeight};
StreamBadges.startTimestamp = +new Date;
// Get battery level
try {
navigator.getBattery && navigator.getBattery().then(bm => {
StreamBadges.startBatteryLevel = Math.round(bm.level * 100);
});
} catch(e) {}
});
}
}
@@ -6034,6 +6121,92 @@ class StreamStats {
StreamStats.refreshStyles();
}
static getServerStats() {
STREAM_WEBRTC && STREAM_WEBRTC.getStats().then(stats => {
const allVideoCodecs = {};
let videoCodecId;
const allAudioCodecs = {};
let audioCodecId;
const allCandidates = {};
let candidateId;
stats.forEach(stat => {
if (stat.type == 'codec') {
const mimeType = stat.mimeType.split('/');
if (mimeType[0] === 'video') {
// Store all video stats
allVideoCodecs[stat.id] = stat;
} else if (mimeType[0] === 'audio') {
// Store all audio stats
allAudioCodecs[stat.id] = stat;
}
} else if (stat.type === 'inbound-rtp' && stat.packetsReceived > 0) {
// Get the codecId of the video/audio track currently being used
if (stat.kind === 'video') {
videoCodecId = stat.codecId;
} else if (stat.kind === 'audio') {
audioCodecId = stat.codecId;
}
} else if (stat.type === 'candidate-pair' && stat.packetsReceived > 0 && stat.state === 'succeeded') {
candidateId = stat.remoteCandidateId;
} else if (stat.type === 'remote-candidate') {
allCandidates[stat.id] = stat.address;
}
});
// Get video codec from codecId
if (videoCodecId) {
const videoStat = allVideoCodecs[videoCodecId];
const video = {
codec: videoStat.mimeType.substring(6),
};
if (video.codec === 'H264') {
const match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine);
video.profile = match ? match[1] : null;
}
StreamBadges.video = video;
}
// Get audio codec from codecId
if (audioCodecId) {
const audioStat = allAudioCodecs[audioCodecId];
StreamBadges.audio = {
codec: audioStat.mimeType.substring(6),
bitrate: audioStat.clockRate,
}
}
// Get server type
if (candidateId) {
console.log('candidate', candidateId, allCandidates);
StreamBadges.ipv6 = allCandidates[candidateId].includes(':');
}
if (getPref(Preferences.STATS_SHOW_WHEN_PLAYING)) {
StreamStats.start();
}
});
}
static setupEvents() {
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
const PREF_STATS_QUICK_GLANCE = getPref(Preferences.STATS_QUICK_GLANCE);
const PREF_STATS_SHOW_WHEN_PLAYING = getPref(Preferences.STATS_SHOW_WHEN_PLAYING);
StreamStats.getServerStats();
// Setup Stat's Quick Glance mode
if (PREF_STATS_QUICK_GLANCE) {
StreamStats.quickGlanceSetup();
// Show stats bar
!PREF_STATS_SHOW_WHEN_PLAYING && StreamStats.start(true);
}
});
}
}
class UserAgent {
@@ -8785,8 +8958,11 @@ function interceptHttpRequests() {
let url = (typeof request === 'string') ? request : request.url;
if (url.endsWith('/play')) {
// Setup UI
setupBxUi();
BxEvent.dispatch(window, BxEvent.STREAM_LOADING);
}
if (url.endsWith('/configuration')) {
BxEvent.dispatch(window, BxEvent.STREAM_STARTING);
}
if (IS_REMOTE_PLAYING && (url.includes('/sessions/home') || url.includes('inputconfigs'))) {
@@ -8855,9 +9031,9 @@ function interceptHttpRequests() {
if (obj[0].supportedTabs.length > 0) {
TouchController.disable();
const event = new Event(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED);
event.data = null;
window.dispatchEvent(event);
BxEvent.dispatch(window, BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, {
data: null,
});
} else {
TouchController.enable();
@@ -8966,9 +9142,6 @@ function interceptHttpRequests() {
// Get region
if (url.endsWith('/sessions/cloud/play')) {
// Setup loading screen
PREF_UI_LOADING_SCREEN_GAME_ART && LoadingScreen.setup();
// Start hiding cursor
if (!getPref(Preferences.MKB_ENABLED) && getPref(Preferences.MKB_HIDE_IDLE_CURSOR)) {
MouseCursorHider.start();
@@ -9023,8 +9196,6 @@ function interceptHttpRequests() {
}
if (url.endsWith('/configuration') && url.includes('/sessions/cloud/') && request.method === 'GET') {
PREF_UI_LOADING_SCREEN_GAME_ART && LoadingScreen.hide();
const promise = NATIVE_FETCH(...arg);
// Touch controller for all games
@@ -9438,8 +9609,9 @@ function getVideoPlayerFilterStyle() {
function updateVideoPlayerCss() {
let $elm = document.getElementById('bx-video-css');
if (!$elm) {
const $fragment = document.createDocumentFragment();
$elm = CE('style', {id: 'bx-video-css'});
document.documentElement.appendChild($elm);
$fragment.appendChild($elm);
// Setup SVG filters
const $svg = CE('svg', {
@@ -9451,7 +9623,8 @@ function updateVideoPlayerCss() {
CE('feConvolveMatrix', {'id': 'bx-filter-clarity-matrix', 'order': '3', 'xmlns': 'http://www.w3.org/2000/svg'}))
)
);
document.documentElement.appendChild($svg);
$fragment.appendChild($svg);
document.documentElement.appendChild($fragment);
}
let filters = getVideoPlayerFilterStyle();
@@ -9625,13 +9798,13 @@ function injectStreamMenuButtons() {
}
item.removedNodes.forEach($node => {
if (!$node.className || !$node.className.startsWith) {
if (!$node || !$node.className || !$node.className.startsWith) {
return;
}
if ($node.className.startsWith('StreamMenu')) {
if (!document.querySelector('div[class^=PureInStreamConfirmationModal]')) {
window.dispatchEvent(new Event('bx-stream-menu-hidden'));
BxEvent.dispatch(window, BxEvent.STREAM_MENU_HIDDEN);
}
}
});
@@ -9641,6 +9814,12 @@ function injectStreamMenuButtons() {
return;
}
// Error Page: .PureErrorPage.ErrorScreen
if ($node.className.includes('PureErrorPage')) {
BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE);
return;
}
if (PREF_DISABLE_FEEDBACK_DIALOG && $node.className.startsWith('PostStreamFeedbackScreen')) {
const $btnClose = $node.querySelector('button');
$btnClose && $btnClose.click();
@@ -9649,7 +9828,7 @@ function injectStreamMenuButtons() {
// Render badges
if ($node.className.startsWith('StreamMenu')) {
window.dispatchEvent(new Event('bx-stream-menu-shown'));
BxEvent.dispatch(window, BxEvent.STREAM_MENU_SHOWN);
// Hide Quick bar when closing HUD
const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]');
@@ -9795,13 +9974,13 @@ function patchVideoApi() {
return;
}
onStreamStarted(this);
BxEvent.dispatch(window, BxEvent.STREAM_PLAYING, {
$video: this,
});
}
const nativePlay = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function() {
LoadingScreen.reset();
if (this.className && this.className.startsWith('XboxSplashVideo')) {
if (PREF_SKIP_SPLASH_VIDEO) {
this.volume = 0;
@@ -10282,24 +10461,19 @@ function patchHistoryMethod(type) {
const orig = window.history[type];
return function(...args) {
const event = new Event(BxEvent.POPSTATE);
event.arguments = args;
window.dispatchEvent(event);
BxEvent.dispatch(window, BxEvent.POPSTATE, {
arguments: args,
});
return orig.apply(this, arguments);
};
};
function onHistoryChanged(e) {
if (e.arguments && e.arguments[0] && e.arguments[0].origin === 'better-xcloud') {
if (e && e.arguments && e.arguments[0] && e.arguments[0].origin === 'better-xcloud') {
return;
}
// Stop MKB listeners
MkbHandler.INSTANCE.destroy();
IS_PLAYING = false;
setTimeout(RemotePlay.detect, 10);
const $settings = document.querySelector('.better_xcloud_settings');
@@ -10307,165 +10481,10 @@ function onHistoryChanged(e) {
$settings.classList.add('bx-gone');
}
const $quickBar = document.querySelector('.bx-quick-settings-bar');
if ($quickBar) {
$quickBar.classList.add('bx-gone');
}
STREAM_AUDIO_GAIN_NODE = null;
$STREAM_VIDEO = null;
StreamStats.onStoppedPlaying();
const $screenshotBtn = document.querySelector('.bx-screenshot-button');
if ($screenshotBtn) {
$screenshotBtn.style = '';
}
MouseCursorHider.stop();
TouchController.reset();
LoadingScreen.reset();
GamepadHandler.stopPolling();
setTimeout(checkHeader, 2000);
}
function onStreamStarted($video) {
IS_PLAYING = true;
// Get title ID for screenshot's name
if (window.location.pathname.includes('/launch/')) {
const matches = /\/launch\/(?<title_id>[^\/]+)\/(?<product_id>\w+)/.exec(window.location.pathname);
GAME_TITLE_ID = matches.groups.title_id;
GAME_PRODUCT_ID = matches.groups.product_id;
} else {
GAME_TITLE_ID = 'remote-play';
GAME_PRODUCT_ID = null;
}
// Enable MKB
if (getPref(Preferences.MKB_ENABLED) && (!ENABLE_NATIVE_MKB_BETA || !window.NATIVE_MKB_TITLES.includes(GAME_PRODUCT_ID))) {
console.log('Emulate MKB');
MkbHandler.INSTANCE.init();
}
if (TouchController.isEnabled()) {
TouchController.enableBar();
}
/*
if (getPref(Preferences.CONTROLLER_ENABLE_SHORTCUTS)) {
GamepadHandler.startPolling();
}
*/
const PREF_SCREENSHOT_BUTTON_POSITION = getPref(Preferences.SCREENSHOT_BUTTON_POSITION);
const PREF_STATS_QUICK_GLANCE = getPref(Preferences.STATS_QUICK_GLANCE);
const PREF_STATS_SHOW_WHEN_PLAYING = getPref(Preferences.STATS_SHOW_WHEN_PLAYING);
// Setup Stat's Quick Glance mode
if (PREF_STATS_QUICK_GLANCE) {
StreamStats.quickGlanceSetup();
// Show stats bar
!PREF_STATS_SHOW_WHEN_PLAYING && StreamStats.start(true);
}
$STREAM_VIDEO = $video;
$SCREENSHOT_CANVAS.width = $video.videoWidth;
$SCREENSHOT_CANVAS.height = $video.videoHeight;
StreamBadges.resolution = {width: $video.videoWidth, height: $video.videoHeight};
StreamBadges.startTimestamp = +new Date;
// Get battery level
try {
navigator.getBattery && navigator.getBattery().then(bm => {
StreamBadges.startBatteryLevel = Math.round(bm.level * 100);
});
} catch(e) {}
STREAM_WEBRTC.getStats().then(stats => {
const allVideoCodecs = {};
let videoCodecId;
const allAudioCodecs = {};
let audioCodecId;
const allCandidates = {};
let candidateId;
stats.forEach(stat => {
if (stat.type == 'codec') {
const mimeType = stat.mimeType.split('/');
if (mimeType[0] === 'video') {
// Store all video stats
allVideoCodecs[stat.id] = stat;
} else if (mimeType[0] === 'audio') {
// Store all audio stats
allAudioCodecs[stat.id] = stat;
}
} else if (stat.type === 'inbound-rtp' && stat.packetsReceived > 0) {
// Get the codecId of the video/audio track currently being used
if (stat.kind === 'video') {
videoCodecId = stat.codecId;
} else if (stat.kind === 'audio') {
audioCodecId = stat.codecId;
}
} else if (stat.type === 'candidate-pair' && stat.packetsReceived > 0 && stat.state === 'succeeded') {
candidateId = stat.remoteCandidateId;
} else if (stat.type === 'remote-candidate') {
allCandidates[stat.id] = stat.address;
}
});
// Get video codec from codecId
if (videoCodecId) {
const videoStat = allVideoCodecs[videoCodecId];
const video = {
codec: videoStat.mimeType.substring(6),
};
if (video.codec === 'H264') {
const match = /profile-level-id=([0-9a-f]{6})/.exec(videoStat.sdpFmtpLine);
video.profile = match ? match[1] : null;
}
StreamBadges.video = video;
}
// Get audio codec from codecId
if (audioCodecId) {
const audioStat = allAudioCodecs[audioCodecId];
StreamBadges.audio = {
codec: audioStat.mimeType.substring(6),
bitrate: audioStat.clockRate,
}
}
// Get server type
if (candidateId) {
console.log(candidateId, allCandidates);
StreamBadges.ipv6 = allCandidates[candidateId].includes(':');
}
if (PREF_STATS_SHOW_WHEN_PLAYING) {
StreamStats.start();
}
});
// Setup screenshot button
if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
const $btn = document.querySelector('.bx-screenshot-button');
$btn.style.display = 'block';
if (PREF_SCREENSHOT_BUTTON_POSITION === 'bottom-right') {
$btn.style.right = '0';
} else {
$btn.style.left = '0';
}
}
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
}
@@ -10508,6 +10527,93 @@ window.addEventListener('popstate', onHistoryChanged);
window.history.pushState = patchHistoryMethod('pushState');
window.history.replaceState = patchHistoryMethod('replaceState');
window.addEventListener(BxEvent.STREAM_LOADING, e => {
// Get title ID for screenshot's name
if (window.location.pathname.includes('/launch/')) {
const matches = /\/launch\/(?<title_id>[^\/]+)\/(?<product_id>\w+)/.exec(window.location.pathname);
GAME_TITLE_ID = matches.groups.title_id;
GAME_PRODUCT_ID = matches.groups.product_id;
} else {
GAME_TITLE_ID = 'remote-play';
GAME_PRODUCT_ID = null;
}
// Setup UI
setupBxUi();
// Setup loading screen
getPref(Preferences.UI_LOADING_SCREEN_GAME_ART) && LoadingScreen.setup();
});
window.addEventListener(BxEvent.STREAM_STARTING, e => {
// Hide loading screen
getPref(Preferences.UI_LOADING_SCREEN_GAME_ART) && LoadingScreen.hide();
});
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
const $video = e.$video;
$STREAM_VIDEO = $video;
IS_PLAYING = true;
/*
if (getPref(Preferences.CONTROLLER_ENABLE_SHORTCUTS)) {
GamepadHandler.startPolling();
}
*/
const PREF_SCREENSHOT_BUTTON_POSITION = getPref(Preferences.SCREENSHOT_BUTTON_POSITION);
$SCREENSHOT_CANVAS.width = $video.videoWidth;
$SCREENSHOT_CANVAS.height = $video.videoHeight;
// Setup screenshot button
if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
const $btn = document.querySelector('.bx-screenshot-button');
$btn.style.display = 'block';
if (PREF_SCREENSHOT_BUTTON_POSITION === 'bottom-right') {
$btn.style.right = '0';
} else {
$btn.style.left = '0';
}
}
});
window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
});
window.addEventListener(BxEvent.STREAM_STOPPED, e => {
if (!IS_PLAYING) {
return;
}
IS_PLAYING = false;
// Stop MKB listeners
MkbHandler.INSTANCE.destroy();
const $quickBar = document.querySelector('.bx-quick-settings-bar');
if ($quickBar) {
$quickBar.classList.add('bx-gone');
}
STREAM_AUDIO_GAIN_NODE = null;
$STREAM_VIDEO = null;
StreamStats.onStoppedPlaying();
const $screenshotBtn = document.querySelector('.bx-screenshot-button');
if ($screenshotBtn) {
$screenshotBtn.style = '';
}
MouseCursorHider.stop();
TouchController.reset();
GamepadHandler.stopPolling();
});
PreloadedState.override();
// Check for Update
@@ -10562,11 +10668,21 @@ if (getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'all') {
VibrationManager.initialSetup();
const nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel;
RTCPeerConnection.prototype.createDataChannel = function() {
const dataChannel = nativeCreateDataChannel.apply(this, arguments);
BxEvent.dispatch(window, BxEvent.DATA_CHANNEL_CREATED, {
dataChannel: dataChannel,
});
return dataChannel;
}
const OrgRTCPeerConnection = window.RTCPeerConnection;
window.RTCPeerConnection = function() {
const peer = new OrgRTCPeerConnection();
STREAM_WEBRTC = peer;
return peer;
STREAM_WEBRTC = new OrgRTCPeerConnection();
return STREAM_WEBRTC;
}
patchRtcCodecs();
@@ -10589,3 +10705,7 @@ if (getPref(Preferences.CONTROLLER_ENABLE_SHORTCUTS)) {
Patcher.initialize();
RemotePlay.detect();
StreamBadges.setupEvents();
StreamStats.setupEvents();
MkbHandler.setupEvents();