mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11: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.
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
import xCloudPlayer from '../player'
|
|
import Overlay from './overlay'
|
|
|
|
export default class VideoComponent {
|
|
private _player:xCloudPlayer
|
|
|
|
private _element:HTMLVideoElement | undefined
|
|
private _overlay:Overlay
|
|
|
|
constructor(player:any){
|
|
this._player = player
|
|
this._overlay = new Overlay(this, this._player)
|
|
}
|
|
|
|
create(stream:MediaStream) {
|
|
const videoElement = document.createElement('video')
|
|
videoElement.srcObject = stream
|
|
videoElement.autoplay = true
|
|
videoElement.muted = true
|
|
videoElement.playsInline = true
|
|
videoElement.style.width = '100%'
|
|
videoElement.style.height = '100%'
|
|
videoElement.style.objectFit = 'contain'
|
|
videoElement.style.backgroundColor = 'black'
|
|
videoElement.style.touchAction = 'none'
|
|
// DroidDeck: a transparent poster hides Android WebView's grey play icon before the first frame.
|
|
videoElement.poster = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
|
|
|
|
const element = document.getElementById(this._player.getElementId())
|
|
if(element === null) {return}
|
|
|
|
this._element = videoElement
|
|
element.appendChild(this._element)
|
|
const isStatic = getComputedStyle(element).position === 'static' ? true : false
|
|
if(isStatic === true){
|
|
element.style.position = 'relative'
|
|
}
|
|
|
|
this._element.requestVideoFrameCallback(this.processVideoMetadata.bind(this))
|
|
}
|
|
|
|
processVideoMetadata(timestamp, data:VideoFrameCallbackMetadata) {
|
|
if(this._element === undefined) {return}
|
|
|
|
this._element.requestVideoFrameCallback(this.processVideoMetadata.bind(this))
|
|
|
|
this._player._channels.input.queueMetadataFrame({
|
|
serverDataKey: data.rtpTimestamp as number,
|
|
firstFramePacketArrivalTimeMs: data.receiveTime as number,
|
|
frameSubmittedTimeMs: data.receiveTime as number,
|
|
frameDecodedTimeMs: data.expectedDisplayTime,
|
|
frameRenderedTimeMs: data.expectedDisplayTime,
|
|
})
|
|
}
|
|
|
|
getElement(){
|
|
return this._element
|
|
}
|
|
|
|
toggleDebugOverlay(){
|
|
this._overlay.toggleDebug()
|
|
}
|
|
|
|
destroy(){
|
|
const streamHolder = document.getElementById(this._player.getElementId())
|
|
const element = streamHolder?.querySelector('video')
|
|
|
|
if(this._overlay !== undefined){
|
|
this._overlay.destroy()
|
|
}
|
|
|
|
if(element){
|
|
element.remove()
|
|
}
|
|
}
|
|
} |