mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-07 16:17:20 +02:00
Stream badges & 1080p stream (#10)
* Get stream statuses * Render badges * Add "Force 1080p stream" feature (#9)
This commit is contained in:
parent
f8134c3e5a
commit
a5caafa93a
@ -19,9 +19,41 @@ const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
|
|||||||
const SERVER_REGIONS = {};
|
const SERVER_REGIONS = {};
|
||||||
|
|
||||||
|
|
||||||
|
class StreamStatus {
|
||||||
|
static ipv6 = false;
|
||||||
|
static dimension = {width: 0, height: 0};
|
||||||
|
static hqCodec = false;
|
||||||
|
static region = '';
|
||||||
|
|
||||||
|
static #renderBadge(name, value, color) {
|
||||||
|
const CE = createElement;
|
||||||
|
const $badge = CE('div', {'class': 'better_xcloud_badge'},
|
||||||
|
CE('span', {'class': 'better_xcloud_badge_name'}, name),
|
||||||
|
CE('span', {'class': 'better_xcloud_badge_value', 'style': `background-color: ${color}`}, value));
|
||||||
|
|
||||||
|
return $badge;
|
||||||
|
}
|
||||||
|
|
||||||
|
static render() {
|
||||||
|
const BADGES = [
|
||||||
|
['region', StreamStatus.region, '#d7450b'],
|
||||||
|
['server', StreamStatus.ipv6 ? 'IPv6' : 'IPv4', '#008746'],
|
||||||
|
['quality', StreamStatus.hqCodec ? 'High' : 'Normal', '#007c8f'],
|
||||||
|
['dimension', `${StreamStatus.dimension.width}x${StreamStatus.dimension.height}`, '#ff3977'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const $wrapper = createElement('div', {'class': 'better_xcloud_badges'});
|
||||||
|
BADGES.forEach(item => $wrapper.appendChild(StreamStatus.#renderBadge(...item)));
|
||||||
|
|
||||||
|
return $wrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Preferences {
|
class Preferences {
|
||||||
static get SERVER_REGION() { return 'server_region'; }
|
static get SERVER_REGION() { return 'server_region'; }
|
||||||
static get PREFER_IPV6_SERVER() { return 'prefer_ipv6_server'; }
|
static get PREFER_IPV6_SERVER() { return 'prefer_ipv6_server'; }
|
||||||
|
static get FORCE_1080P_STREAM() { return 'force_1080p_stream'; }
|
||||||
static get USE_DESKTOP_CODEC() { return 'use_desktop_codec'; }
|
static get USE_DESKTOP_CODEC() { return 'use_desktop_codec'; }
|
||||||
|
|
||||||
static get BLOCK_TRACKING() { return 'block_tracking'; }
|
static get BLOCK_TRACKING() { return 'block_tracking'; }
|
||||||
@ -44,14 +76,20 @@ class Preferences {
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
'id': Preferences.PREFER_IPV6_SERVER,
|
'id': Preferences.FORCE_1080P_STREAM,
|
||||||
'label': 'Prefer IPv6 streaming server',
|
'label': 'Force 1080p stream',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
'id': Preferences.USE_DESKTOP_CODEC,
|
'id': Preferences.USE_DESKTOP_CODEC,
|
||||||
'label': 'Force high quality stream',
|
'label': 'Force high quality codec',
|
||||||
|
'default': false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
'id': Preferences.PREFER_IPV6_SERVER,
|
||||||
|
'label': 'Prefer IPv6 streaming server',
|
||||||
'default': false,
|
'default': false,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -289,6 +327,42 @@ function addCss() {
|
|||||||
background-color: #00753c;
|
background-color: #00753c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.better_xcloud_badges {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -35px;
|
||||||
|
margin-left: 0px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.better_xcloud_badge {
|
||||||
|
border: none;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 24px;
|
||||||
|
color: #fff;
|
||||||
|
font-family: Bahnschrift Semibold, Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.better_xcloud_badge .better_xcloud_badge_name {
|
||||||
|
background-color: #2d3036;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.better_xcloud_badge .better_xcloud_badge_value {
|
||||||
|
background-color: grey;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide UI elements */
|
/* Hide UI elements */
|
||||||
#headerArea, #uhfSkipToMain, .uhf-footer {
|
#headerArea, #uhfSkipToMain, .uhf-footer {
|
||||||
display: none;
|
display: none;
|
||||||
@ -362,7 +436,7 @@ function getPreferredServerRegion() {
|
|||||||
|
|
||||||
|
|
||||||
function updateIceCandidates(candidates) {
|
function updateIceCandidates(candidates) {
|
||||||
const pattern = new RegExp(/a=candidate:(?<order>\d+) (?<num>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<the_rest>.*)/);
|
const pattern = new RegExp(/a=candidate:(?<foundation>\d+) (?<component>\d+) UDP (?<priority>\d+) (?<ip>[^\s]+) (?<the_rest>.*)/);
|
||||||
|
|
||||||
const lst = [];
|
const lst = [];
|
||||||
for (let item of candidates) {
|
for (let item of candidates) {
|
||||||
@ -377,19 +451,19 @@ function updateIceCandidates(candidates) {
|
|||||||
lst.sort((a, b) => (a.ip.includes(':') || a.ip > b.ip) ? -1 : 1);
|
lst.sort((a, b) => (a.ip.includes(':') || a.ip > b.ip) ? -1 : 1);
|
||||||
|
|
||||||
const newCandidates = [];
|
const newCandidates = [];
|
||||||
let order = 1;
|
let foundation = 1;
|
||||||
lst.forEach(item => {
|
lst.forEach(item => {
|
||||||
item.order = order;
|
item.foundation = foundation;
|
||||||
item.priority = (order == 1) ? 100 : 1;
|
item.priority = (foundation == 1) ? 100 : 1;
|
||||||
|
|
||||||
newCandidates.push({
|
newCandidates.push({
|
||||||
'candidate': `a=candidate:${item.order} 1 UDP ${item.priority} ${item.ip} ${item.the_rest}`,
|
'candidate': `a=candidate:${item.foundation} 1 UDP ${item.priority} ${item.ip} ${item.the_rest}`,
|
||||||
'messageType': 'iceCandidate',
|
'messageType': 'iceCandidate',
|
||||||
'sdpMLineIndex': '0',
|
'sdpMLineIndex': '0',
|
||||||
'sdpMid': '0',
|
'sdpMid': '0',
|
||||||
});
|
});
|
||||||
|
|
||||||
++order;
|
++foundation;
|
||||||
});
|
});
|
||||||
|
|
||||||
newCandidates.push({
|
newCandidates.push({
|
||||||
@ -448,6 +522,7 @@ function interceptHttpRequests() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PREF_PREFER_IPV6_SERVER = PREFS.get(Preferences.PREFER_IPV6_SERVER);
|
const PREF_PREFER_IPV6_SERVER = PREFS.get(Preferences.PREFER_IPV6_SERVER);
|
||||||
|
const PREF_FORCE_1080P_STREAM = PREFS.get(Preferences.FORCE_1080P_STREAM);
|
||||||
const PREF_USE_DESKTOP_CODEC = PREFS.get(Preferences.USE_DESKTOP_CODEC);
|
const PREF_USE_DESKTOP_CODEC = PREFS.get(Preferences.USE_DESKTOP_CODEC);
|
||||||
const HAS_CODECS_API_SUPPORT = hasRtcSetCodecPreferencesSupport();
|
const HAS_CODECS_API_SUPPORT = hasRtcSetCodecPreferencesSupport();
|
||||||
|
|
||||||
@ -490,6 +565,36 @@ function interceptHttpRequests() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get region
|
||||||
|
if (url.endsWith('/sessions/cloud/play')) {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
|
StreamStatus.region = parsedUrl.host.split('.', 1)[0];
|
||||||
|
for (let regionName in SERVER_REGIONS) {
|
||||||
|
const region = SERVER_REGIONS[regionName];
|
||||||
|
if (parsedUrl.origin == region.baseUri) {
|
||||||
|
StreamStatus.region = regionName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force 1080p stream
|
||||||
|
if (PREF_FORCE_1080P_STREAM) {
|
||||||
|
// Intercept "osName" value
|
||||||
|
const clone = request.clone();
|
||||||
|
const body = await clone.json();
|
||||||
|
body.settings.osName = 'windows';
|
||||||
|
|
||||||
|
const newRequest = new Request(request, {
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
arg[0] = newRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgFetch(...arg);
|
||||||
|
}
|
||||||
|
|
||||||
// Work-around for browsers with no setCodecPreferences() support
|
// Work-around for browsers with no setCodecPreferences() support
|
||||||
if (PREF_USE_DESKTOP_CODEC && !HAS_CODECS_API_SUPPORT && url.endsWith('/sdp') && url.includes('/sessions/cloud/') && request.method === 'GET') {
|
if (PREF_USE_DESKTOP_CODEC && !HAS_CODECS_API_SUPPORT && url.endsWith('/sdp') && url.includes('/sessions/cloud/') && request.method === 'GET') {
|
||||||
const promise = orgFetch(...arg);
|
const promise = orgFetch(...arg);
|
||||||
@ -838,9 +943,6 @@ function injectVideoSettingsButton() {
|
|||||||
// Show Quick settings bar
|
// Show Quick settings bar
|
||||||
$quickBar.style.display = 'flex';
|
$quickBar.style.display = 'flex';
|
||||||
|
|
||||||
// Close HUD
|
|
||||||
document.querySelector('button[class*=StreamMenu-module__backButton]').click();
|
|
||||||
|
|
||||||
$parent.addEventListener('click', hideQuickBarFunc);
|
$parent.addEventListener('click', hideQuickBarFunc);
|
||||||
$parent.addEventListener('touchend', hideQuickBarFunc);
|
$parent.addEventListener('touchend', hideQuickBarFunc);
|
||||||
|
|
||||||
@ -851,6 +953,15 @@ function injectVideoSettingsButton() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$orgButton.parentElement.insertBefore($button, $orgButton.parentElement.firstChild);
|
$orgButton.parentElement.insertBefore($button, $orgButton.parentElement.firstChild);
|
||||||
|
|
||||||
|
// Hide Quick bar when closing HUD
|
||||||
|
document.querySelector('button[class*=StreamMenu-module__backButton]').addEventListener('click', e => {
|
||||||
|
$quickBar.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render stream badges
|
||||||
|
const $menu = document.querySelector('div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]');
|
||||||
|
$menu.appendChild(StreamStatus.render());
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -867,6 +978,10 @@ function patchVideoApi() {
|
|||||||
showFunc = function() {
|
showFunc = function() {
|
||||||
this.style.visibility = 'visible';
|
this.style.visibility = 'visible';
|
||||||
this.removeEventListener('playing', showFunc);
|
this.removeEventListener('playing', showFunc);
|
||||||
|
|
||||||
|
if (this.videoWidth) {
|
||||||
|
StreamStatus.dimension = {width: this.videoWidth, height: this.videoHeight};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play;
|
HTMLMediaElement.prototype.orgPlay = HTMLMediaElement.prototype.play;
|
||||||
@ -1156,3 +1271,24 @@ window.onload = () => {
|
|||||||
if (document.readyState === 'complete' && !onLoadTriggered) {
|
if (document.readyState === 'complete' && !onLoadTriggered) {
|
||||||
watchHeader();
|
watchHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RTCPeerConnection.prototype.orgSetRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription;
|
||||||
|
RTCPeerConnection.prototype.setRemoteDescription = function(...args) {
|
||||||
|
const sdpDesc = args[0];
|
||||||
|
if (sdpDesc.sdp) {
|
||||||
|
StreamStatus.hqCodec = sdpDesc.sdp.includes('profile-level-id=4d');
|
||||||
|
}
|
||||||
|
return this.orgSetRemoteDescription.apply(this, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RTCPeerConnection.prototype.orgAddIceCandidate = RTCPeerConnection.prototype.addIceCandidate;
|
||||||
|
RTCPeerConnection.prototype.addIceCandidate = function(...args) {
|
||||||
|
const candidate = args[0].candidate;
|
||||||
|
if (candidate && candidate.startsWith('a=candidate:1 ')) {
|
||||||
|
StreamStatus.ipv6 = candidate.substring(20).includes(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.orgAddIceCandidate.apply(this, args);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user