mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-07 08:07:18 +02:00
Add "Maximum video bitrate" option
This commit is contained in:
parent
c1b41663db
commit
1dee720f77
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 40px;
|
min-width: 40px;
|
||||||
font-family: var(--bx-monospaced-font);
|
font-family: var(--bx-monospaced-font);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ const SETTINGS_UI = {
|
|||||||
items: [
|
items: [
|
||||||
PrefKey.STREAM_TARGET_RESOLUTION,
|
PrefKey.STREAM_TARGET_RESOLUTION,
|
||||||
PrefKey.STREAM_CODEC_PROFILE,
|
PrefKey.STREAM_CODEC_PROFILE,
|
||||||
|
|
||||||
|
PrefKey.BITRATE_VIDEO_MAX,
|
||||||
|
|
||||||
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
|
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
|
||||||
PrefKey.AUDIO_MIC_ON_PLAYING,
|
PrefKey.AUDIO_MIC_ON_PLAYING,
|
||||||
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
|
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
|
||||||
|
@ -2,6 +2,7 @@ import { BxEvent } from "@utils/bx-event";
|
|||||||
import { getPref, PrefKey } from "@utils/preferences";
|
import { getPref, PrefKey } from "@utils/preferences";
|
||||||
import { STATES } from "@utils/global";
|
import { STATES } from "@utils/global";
|
||||||
import { BxLogger } from "@utils/bx-logger";
|
import { BxLogger } from "@utils/bx-logger";
|
||||||
|
import { patchSdpBitrate } from "./sdp";
|
||||||
|
|
||||||
export function patchVideoApi() {
|
export function patchVideoApi() {
|
||||||
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.SKIP_SPLASH_VIDEO);
|
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.SKIP_SPLASH_VIDEO);
|
||||||
@ -96,6 +97,22 @@ export function patchRtcPeerConnection() {
|
|||||||
return dataChannel;
|
return dataChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription;
|
||||||
|
RTCPeerConnection.prototype.setLocalDescription = function(description?: RTCLocalSessionDescriptionInit): Promise<void> {
|
||||||
|
// set maximum bitrate
|
||||||
|
try {
|
||||||
|
const maxVideoBitrate = getPref(PrefKey.BITRATE_VIDEO_MAX);
|
||||||
|
if (maxVideoBitrate > 0) {
|
||||||
|
arguments[0].sdp = patchSdpBitrate(arguments[0].sdp, maxVideoBitrate * 1000);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
BxLogger.error('setLocalDescription', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return nativeSetLocalDescription.apply(this, arguments);
|
||||||
|
};
|
||||||
|
|
||||||
const OrgRTCPeerConnection = window.RTCPeerConnection;
|
const OrgRTCPeerConnection = window.RTCPeerConnection;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.RTCPeerConnection = function() {
|
window.RTCPeerConnection = function() {
|
||||||
|
@ -32,6 +32,8 @@ export enum PrefKey {
|
|||||||
|
|
||||||
STREAM_DISABLE_FEEDBACK_DIALOG = 'stream_disable_feedback_dialog',
|
STREAM_DISABLE_FEEDBACK_DIALOG = 'stream_disable_feedback_dialog',
|
||||||
|
|
||||||
|
BITRATE_VIDEO_MAX = 'bitrate_video_max',
|
||||||
|
|
||||||
GAME_BAR_POSITION = 'game_bar_position',
|
GAME_BAR_POSITION = 'game_bar_position',
|
||||||
|
|
||||||
LOCAL_CO_OP_ENABLED = 'local_co_op_enabled',
|
LOCAL_CO_OP_ENABLED = 'local_co_op_enabled',
|
||||||
@ -314,6 +316,28 @@ export class Preferences {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[PrefKey.BITRATE_VIDEO_MAX]: {
|
||||||
|
type: SettingElementType.NUMBER_STEPPER,
|
||||||
|
label: 'Maximum video bitrate',
|
||||||
|
default: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 15,
|
||||||
|
steps: 1,
|
||||||
|
params: {
|
||||||
|
suffix: ' Mb/s',
|
||||||
|
exactTicks: 5,
|
||||||
|
customTextValue: (value: any) => {
|
||||||
|
value = parseInt(value);
|
||||||
|
|
||||||
|
if (value === 0) {
|
||||||
|
return t('default');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
[PrefKey.GAME_BAR_POSITION]: {
|
[PrefKey.GAME_BAR_POSITION]: {
|
||||||
label: t('position'),
|
label: t('position'),
|
||||||
default: 'bottom-left',
|
default: 'bottom-left',
|
||||||
|
61
src/utils/sdp.ts
Normal file
61
src/utils/sdp.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
export function patchSdpBitrate(sdp: string, video?: number, audio?: number) {
|
||||||
|
const lines = sdp.split('\n');
|
||||||
|
|
||||||
|
const mediaSet: Set<string> = new Set();
|
||||||
|
!!video && mediaSet.add('video');
|
||||||
|
!!audio && mediaSet.add('audio');
|
||||||
|
|
||||||
|
const bitrate = {
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
|
||||||
|
let media: string = '';
|
||||||
|
|
||||||
|
let line = lines[lineNumber];
|
||||||
|
if (!line.startsWith('m=')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const m of mediaSet) {
|
||||||
|
if (line.startsWith(`m=${m}`)) {
|
||||||
|
media = m;
|
||||||
|
// Remove matched media from set
|
||||||
|
mediaSet.delete(media);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid media, continue looking
|
||||||
|
if (!media) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bLine = `b=AS:${bitrate[media as keyof typeof bitrate]}`;
|
||||||
|
|
||||||
|
while (lineNumber++, lineNumber < lines.length) {
|
||||||
|
line = lines[lineNumber];
|
||||||
|
// Ignore lines that start with "i=" or "c="
|
||||||
|
if (line.startsWith('i=') || line.startsWith('c=')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith('b=AS:')) {
|
||||||
|
// Replace bitrate
|
||||||
|
lines[lineNumber] = bLine;
|
||||||
|
// Stop lookine for "b=AS:" line
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith('m=')) {
|
||||||
|
// "b=AS:" line not found, add "b" line before "m="
|
||||||
|
lines.splice(lineNumber, 0, bLine);
|
||||||
|
// Stop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
@ -12,6 +12,8 @@ type NumberStepperParams = {
|
|||||||
|
|
||||||
ticks?: number;
|
ticks?: number;
|
||||||
exactTicks?: number;
|
exactTicks?: number;
|
||||||
|
|
||||||
|
customTextValue?: (value: any) => string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SettingElementType {
|
export enum SettingElementType {
|
||||||
@ -131,9 +133,24 @@ export class SettingElement {
|
|||||||
const MAX = setting.max!;
|
const MAX = setting.max!;
|
||||||
const STEPS = Math.max(setting.steps || 1, 1);
|
const STEPS = Math.max(setting.steps || 1, 1);
|
||||||
|
|
||||||
|
const renderTextValue = (value: any) => {
|
||||||
|
value = parseInt(value as string);
|
||||||
|
|
||||||
|
let textContent = null;
|
||||||
|
if (options.customTextValue) {
|
||||||
|
textContent = options.customTextValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textContent === null) {
|
||||||
|
textContent = value.toString() + options.suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textContent;
|
||||||
|
};
|
||||||
|
|
||||||
const $wrapper = CE('div', {'class': 'bx-number-stepper'},
|
const $wrapper = CE('div', {'class': 'bx-number-stepper'},
|
||||||
$decBtn = CE('button', {'data-type': 'dec'}, '-') as HTMLButtonElement,
|
$decBtn = CE('button', {'data-type': 'dec'}, '-') as HTMLButtonElement,
|
||||||
$text = CE('span', {}, value + options.suffix) as HTMLSpanElement,
|
$text = CE('span', {}, renderTextValue(value)) as HTMLSpanElement,
|
||||||
$incBtn = CE('button', {'data-type': 'inc'}, '+') as HTMLButtonElement,
|
$incBtn = CE('button', {'data-type': 'inc'}, '+') as HTMLButtonElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -141,8 +158,7 @@ export class SettingElement {
|
|||||||
$range = CE('input', {'type': 'range', 'min': MIN, 'max': MAX, 'value': value, 'step': STEPS}) as HTMLInputElement;
|
$range = CE('input', {'type': 'range', 'min': MIN, 'max': MAX, 'value': value, 'step': STEPS}) as HTMLInputElement;
|
||||||
$range.addEventListener('input', e => {
|
$range.addEventListener('input', e => {
|
||||||
value = parseInt((e.target as HTMLInputElement).value);
|
value = parseInt((e.target as HTMLInputElement).value);
|
||||||
|
$text.textContent = renderTextValue(value);
|
||||||
$text.textContent = value + options.suffix;
|
|
||||||
onChange && onChange(e, value);
|
onChange && onChange(e, value);
|
||||||
});
|
});
|
||||||
$wrapper.appendChild($range);
|
$wrapper.appendChild($range);
|
||||||
@ -204,7 +220,7 @@ export class SettingElement {
|
|||||||
value = Math.min(MAX, value + STEPS);
|
value = Math.min(MAX, value + STEPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
$text.textContent = value.toString() + options.suffix;
|
$text.textContent = renderTextValue(value);
|
||||||
$range && ($range.value = value.toString());
|
$range && ($range.value = value.toString());
|
||||||
|
|
||||||
isHolding = false;
|
isHolding = false;
|
||||||
@ -237,7 +253,7 @@ export class SettingElement {
|
|||||||
|
|
||||||
// Custom method
|
// Custom method
|
||||||
($wrapper as any).setValue = (value: any) => {
|
($wrapper as any).setValue = (value: any) => {
|
||||||
$text.textContent = value + options.suffix;
|
$text.textContent = renderTextValue(value);
|
||||||
$range && ($range.value = value);
|
$range && ($range.value = value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user