mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 03:21:08 +00:00
Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation Stream Xbox games with Xbox Cloud Gaming (Game Pass) from DroidDeck, outside the Linux session. - Backend: `xodus-cli cloud-token` (tools/xbox/droiddeck-cloud-token.patch) gets the streaming tokens; they stay in memory. - Web player (default): the Greenlight player page (tools/xcloud-player, MIT) in a WebView, plus Microsoft's own xbox.com/play player. In-stream menu with screen effects (sharpen, colour, deband, FXAA, CRT, NTSC, Toon, fps cap), stream quality, performance overlay (level, layout, top right / top left) and rumble. - Native player (opt-in, Android 10+, Player engine: Native): Google's WebRTC (io.github.webrtc-sdk 125.6422.07, BSD) with the Greenlight protocol ported to Kotlin, in its own process (:xcloudnative) with its own instance of DroidDeck's compositor. Decoded frames reach the compositor as AHardwareBuffers (new stream mode, VK_ANDROID_external_memory_android_ hardware_buffer import), so DDFG 2x and LSFG 2x/3x/4x run on them. Live frame generation, frame-rate target (presets and a 10-165 fps slider, even pacing), zero-copy picture, small network buffer, fast decoder, instant controller input, overlay with engine and on-screen fps, background end, restart and web fallback. It takes the main process's tokens through XboxCloudTokenService; they are fetched ahead while a cloud game is shown. - The compositor requests the dma-buf import extensions only where the driver has them (the system driver has none). - Catalog: cloud availability, Game Pass plan CFQ7TTC0K6L8 counted, library filter All / Owned / Game Pass / Cloud, Stream and Add to Games on the store page, stream-only Games-tab entries (Stream + Download / Play + Stream). - Xbox games get VC++ 2015-2022 and the ARM64 runtime registry entries (vcredist2022_arm64, built in until the catalog has it) before each launch. - Controller focus reaches pickers opened from a game page (install location, uninstall); Add / Remove from Games keep the focus. Credits: Greenlight by UnknownSKL (Jim Kroon), MIT, player and protocol; Better xCloud for the resolution / OS mapping; MGamer404's Bannerlator #627 for the ARM64 runtime approach. Ported from the412banner's Bannerlator.
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import xCloudPlayer from './player'
|
|
|
|
export default class Sdp {
|
|
private _player: xCloudPlayer
|
|
private _peerConnection:RTCPeerConnection
|
|
|
|
constructor(player:xCloudPlayer) {
|
|
this._player = player
|
|
this._peerConnection = player._peerConnection
|
|
}
|
|
|
|
getAvailableCodecs():Array<any> {
|
|
const capabilities = RTCRtpReceiver.getCapabilities('video')?.codecs
|
|
if(capabilities === undefined) {return []}
|
|
|
|
const codecs:Array<string> = []
|
|
for(const codec in capabilities) {
|
|
if(!codecs.includes(capabilities[codec].mimeType)) {codecs.push(capabilities[codec].mimeType)}
|
|
}
|
|
|
|
return codecs
|
|
}
|
|
|
|
// getServerCodecs() {
|
|
// for(const sender in this._peerConnection.getSenders()){
|
|
// console.log('Sender:', this._peerConnection.getSenders()[sender])
|
|
// }
|
|
// }
|
|
|
|
setLocalSDP(sdp:RTCSessionDescriptionInit) {
|
|
console.log('[SDP] setLocalSDP() Setting local SDP:', sdp)
|
|
|
|
// if(this._player._config.video_bitrate > 0){
|
|
// console.log('[SDP] Set max video bitrate to:', this._player._config.video_bitrate, 'kbps')
|
|
// sdp.sdp = this._setBitrate(sdp.sdp, 'video', this._player._config.video_bitrate)
|
|
// }
|
|
|
|
// if(this._player._config.audio_bitrate > 0){
|
|
// console.log('[SDP] Set max audio bitrate to:', this._player._config.audio_bitrate, 'kbps')
|
|
// sdp.sdp = this._setBitrate(sdp.sdp, 'audio', this._player._config.audio_bitrate)
|
|
// }
|
|
|
|
// if(this._player._config.audio_mono !== true){
|
|
// console.log('[SDP] setLocalSDP() Set audio to stereo')
|
|
// sdp.sdp = sdp.sdp?.replace('useinbandfec=1', 'useinbandfec=1; stereo=1')
|
|
// }
|
|
|
|
sdp.sdp = sdp.sdp?.replace('useinbandfec=1', 'useinbandfec=1; stereo=1')
|
|
|
|
return sdp
|
|
}
|
|
|
|
setRemoteSDP(sdp:string) {
|
|
console.log('[SDP] setRemoteSDP() Setting remote SDP:', sdp)
|
|
return sdp
|
|
}
|
|
|
|
getDefaultCodecPreferences() {
|
|
const capabilities = RTCRtpReceiver.getCapabilities('video')?.codecs
|
|
if(capabilities === undefined) {return []}
|
|
|
|
const t1:Array<any> = []
|
|
const t2:Array<any> = []
|
|
const t3:Array<any> = []
|
|
const t4:Array<any> = []
|
|
|
|
for(const codec in capabilities) {
|
|
if(capabilities[codec].mimeType.includes('H264')) {
|
|
if(capabilities[codec].sdpFmtpLine?.includes('profile-level-id=4d')) {
|
|
t1.push(capabilities[codec])
|
|
|
|
} else if(capabilities[codec].sdpFmtpLine?.includes('profile-level-id=42e')) {
|
|
t2.push(capabilities[codec])
|
|
|
|
} else if(capabilities[codec].sdpFmtpLine?.includes('profile-level-id=420')) {
|
|
t3.push(capabilities[codec])
|
|
}
|
|
} else if(capabilities[codec].mimeType.includes('ulpfec')) {
|
|
t4.push(capabilities[codec])
|
|
} else if(capabilities[codec].mimeType.includes('flexfec')) {
|
|
t4.push(capabilities[codec])
|
|
} else if(capabilities[codec].mimeType.includes('VP9')) {
|
|
t4.push(capabilities[codec])
|
|
} else if(capabilities[codec].mimeType.includes('VP8')) {
|
|
t4.push(capabilities[codec])
|
|
}
|
|
// else {
|
|
// t4.push(capabilities[codec])
|
|
// }
|
|
}
|
|
|
|
const codecOrder = [...t1, ...t2, ...t3, ...t4]
|
|
|
|
return codecOrder
|
|
}
|
|
|
|
_setBitrate(sdp, media, bitrate) {
|
|
const lines = sdp.split('\n')
|
|
let line = -1
|
|
for(let i=0; i < lines.length; i++) {
|
|
if(lines[i].indexOf('m='+media) === 0) {
|
|
line = i
|
|
break
|
|
}
|
|
}
|
|
if (line === -1) {
|
|
console.debug('Could not find the m line for', media)
|
|
return sdp
|
|
}
|
|
line++
|
|
|
|
while(lines[line].indexOf('i=') === 0 || lines[line].indexOf('c=') === 0) {
|
|
line++
|
|
}
|
|
|
|
if (lines[line].indexOf('b') === 0) {
|
|
lines[line] = 'b=AS:'+bitrate
|
|
return lines.join('\n')
|
|
}
|
|
|
|
let newLines = lines.slice(0, line)
|
|
newLines.push('b=AS:'+bitrate)
|
|
newLines = newLines.concat(lines.slice(line, lines.length))
|
|
|
|
return newLines.join('\n')
|
|
}
|
|
} |