better-xcloud/src/utils/monkey-patches.ts

159 lines
5.1 KiB
TypeScript

import { BxEvent } from "./bx-event";
import { getPref, PrefKey } from "./preferences";
import { STATES } from "./global";
import { UserAgent } from "./user-agent";
export function patchVideoApi() {
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.SKIP_SPLASH_VIDEO);
// Show video player when it's ready
const showFunc = function(this: HTMLVideoElement) {
this.style.visibility = 'visible';
this.removeEventListener('playing', showFunc);
if (!this.videoWidth) {
return;
}
BxEvent.dispatch(window, BxEvent.STREAM_PLAYING, {
$video: this,
});
}
const nativePlay = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function() {
if (this.className && this.className.startsWith('XboxSplashVideo')) {
if (PREF_SKIP_SPLASH_VIDEO) {
this.volume = 0;
this.style.display = 'none';
this.dispatchEvent(new Event('ended'));
return new Promise<void>(() => {});
}
return nativePlay.apply(this);
}
if (!!this.src) {
return nativePlay.apply(this);
}
this.addEventListener('playing', showFunc);
return nativePlay.apply(this);
};
}
export function patchRtcCodecs() {
const codecProfile = getPref(PrefKey.STREAM_CODEC_PROFILE);
if (codecProfile === 'default') {
return;
}
if (typeof RTCRtpTransceiver === 'undefined' || !('setCodecPreferences' in RTCRtpTransceiver.prototype)) {
return false;
}
const profilePrefix = codecProfile === 'high' ? '4d' : (codecProfile === 'low' ? '420' : '42e');
const profileLevelId = `profile-level-id=${profilePrefix}`;
const nativeSetCodecPreferences = RTCRtpTransceiver.prototype.setCodecPreferences;
RTCRtpTransceiver.prototype.setCodecPreferences = function(codecs) {
// Use the same codecs as desktop
const newCodecs = codecs.slice();
let pos = 0;
newCodecs.forEach((codec, i) => {
// Find high-quality codecs
if (codec.sdpFmtpLine && codec.sdpFmtpLine.includes(profileLevelId)) {
// Move it to the top of the array
newCodecs.splice(i, 1);
newCodecs.splice(pos, 0, codec);
++pos;
}
});
try {
nativeSetCodecPreferences.apply(this, [newCodecs]);
} catch (e) {
// Didn't work -> use default codecs
console.log(e);
nativeSetCodecPreferences.apply(this, [codecs]);
}
}
}
export function patchRtcPeerConnection() {
const nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel;
RTCPeerConnection.prototype.createDataChannel = function() {
// @ts-ignore
const dataChannel = nativeCreateDataChannel.apply(this, arguments);
BxEvent.dispatch(window, BxEvent.DATA_CHANNEL_CREATED, {
dataChannel: dataChannel,
});
return dataChannel;
}
const OrgRTCPeerConnection = window.RTCPeerConnection;
// @ts-ignore
window.RTCPeerConnection = function() {
const conn = new OrgRTCPeerConnection();
STATES.currentStream.peerConnection = conn;
conn.addEventListener('connectionstatechange', e => {
if (conn.connectionState === 'connecting') {
STATES.currentStream.audioGainNode = null;
}
console.log('connectionState', conn.connectionState);
});
return conn;
}
}
export function patchAudioContext() {
if (UserAgent.isSafari(true)) {
const nativeCreateGain = window.AudioContext.prototype.createGain;
window.AudioContext.prototype.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;
}
}