Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Shyamalp16/CloudGaming/llms.txt

Use this file to discover all available pages before exploring further.

Browser Client

The CloudGaming client is a browser-based application that receives video/audio streams via WebRTC and forwards input to the host.

Architecture Overview

The client consists of:
  • WebRTC Peer Connection: Video/audio track reception
  • Video Rendering: Hardware-accelerated direct rendering
  • Audio Playback: Dedicated audio element with low-latency configuration
  • Input Capture: Keyboard/mouse event forwarding via DataChannels
  • Latency Measurement: Round-trip time tracking via ping/pong
  • Matchmaker Integration: Automatic host discovery

Implementation

WebRTC Connection Establishment

Peer Connection Creation

// From Client/html-server/index.html:1167-1450
function createPeerConnection() {
    if (peerConnection) {
        log("PeerConnection already exists, not creating again.");
        return;
    }
    
    // Configure with ICE servers (STUN/TURN)
    const config = {
        iceServers: currentIceServers,
    };
    
    peerConnection = new RTCPeerConnection(config);
    
    // Handle remote tracks (video/audio)
    peerConnection.ontrack = (event) => {
        handleRemoteTrack(event);
    };
    
    // Forward ICE candidates to signaling server
    peerConnection.onicecandidate = (event) => {
        if (event.candidate) {
            ws.send(JSON.stringify({
                type: 'candidate',
                candidate: event.candidate.candidate,
                sdpMid: event.candidate.sdpMid,
                sdpMLineIndex: event.candidate.sdpMLineIndex
            }));
        }
    };
    
    // Monitor connection state
    peerConnection.oniceconnectionstatechange = () => {
        log('ICE connection state: ' + peerConnection.iceConnectionState);
        if (peerConnection.iceConnectionState === 'connected') {
            updateConnectionStatus('connected', 'ICE Connected');
        } else if (peerConnection.iceConnectionState === 'disconnected') {
            updateConnectionStatus('disconnected', 'ICE Disconnected');
            stopRenderingLoop();
        }
    };
    
    // Create data channels
    dataChannel = peerConnection.createDataChannel("keyPressChannel", { ordered: true });
    mouseChannel = peerConnection.createDataChannel("mouseChannel", { ordered: false, maxRetransmits: 0 });
    videoFeedbackChannel = peerConnection.createDataChannel("videoFeedbackChannel", { ordered: false, maxRetransmits: 0 });
}

SDP Negotiation

// From Client/html-server/index.html:1584-1629
async function startConnection() {
    createPeerConnection();
    
    // Add transceivers for receiving streams
    if (!peerConnection.getTransceivers().some(t => t.receiver?.track?.kind === 'video')) {
        peerConnection.addTransceiver('video', { direction: 'recvonly' });
        log("Added video transceiver.");
    }
    
    if (!peerConnection.getTransceivers().some(t => t.receiver?.track?.kind === 'audio')) {
        peerConnection.addTransceiver('audio', { direction: 'recvonly' });
        log("Added audio transceiver.");
    }
    
    // Create and send offer
    if (peerConnection.signalingState === 'stable' || peerConnection.signalingState === 'new') {
        const offer = await peerConnection.createOffer();
        await peerConnection.setLocalDescription(offer);
        
        ws.send(JSON.stringify({
            type: 'offer',
            sdp: offer.sdp
        }));
    }
}

async function handleAnswer(answerMsg) {
    if (peerConnection.signalingState !== 'have-local-offer') {
        log("Not expecting answer in current state", 'warn');
        return;
    }
    
    const remoteDesc = new RTCSessionDescription({ 
        type: 'answer', 
        sdp: answerMsg.sdp 
    });
    await peerConnection.setRemoteDescription(remoteDesc);
    log('Remote description (answer) set.');
}

Signaling via WebSocket

// From Client/html-server/index.html:1048-1164
function connectToSignalingServer(roomId, signalingUrl) {
    currentRoomId = roomId;
    const serverUrl = `${signalingUrl}?roomId=${roomId}`;
    
    ws = new WebSocket(serverUrl);
    
    ws.onopen = () => {
        log('Connected to signaling server');
        startConnection();
    };
    
    ws.onmessage = async (event) => {
        const msg = JSON.parse(event.data);
        
        switch (msg.type) {
            case 'answer':
                await handleAnswer(msg);
                break;
                
            case 'candidate':
                const ice = { 
                    candidate: msg.candidate, 
                    sdpMid: msg.sdpMid, 
                    sdpMLineIndex: msg.sdpMLineIndex 
                };
                await peerConnection.addIceCandidate(new RTCIceCandidate(ice));
                break;
                
            case 'peer-disconnected':
                log('Peer has disconnected', 'warn');
                updateConnectionStatus('disconnected', 'Peer Left');
                break;
        }
    };
    
    ws.onclose = (event) => {
        log(`WebSocket closed. Code: ${event.code}`, 'warn');
        stopRenderingLoop();
        
        // Exponential backoff reconnect
        let attempt = 0;
        const tryReconnect = () => {
            attempt++;
            const delay = Math.min(1000 * Math.pow(2, attempt - 1), 15000);
            setTimeout(() => connectToSignalingServer(currentRoomId, signalingUrl), delay);
        };
        tryReconnect();
    };
}

UI Components

Loading Overlay

<!-- From Client/html-server/index.html:557-561 -->
<div class="loading-overlay" id="loadingOverlay">
  <div class="loading-spinner"></div>
  <div class="loading-text" id="loadingText">Connecting to Game Server</div>
  <div class="loading-subtext" id="loadingSubtext">Establishing WebRTC connection...</div>
</div>

Performance Overlay

<!-- From Client/html-server/index.html:531-548 -->
<div class="performance-overlay" id="perfOverlay">
  <div class="perf-item">
    <span>Latency:</span>
    <span class="perf-value" id="latencyValue">-- ms</span>
  </div>
  <div class="perf-item">
    <span>FPS:</span>
    <span class="perf-value" id="fpsValue">-- fps</span>
  </div>
  <div class="perf-item">
    <span>Quality:</span>
    <span class="perf-value" id="qualityValue">--</span>
  </div>
  <div class="perf-item">
    <span>Bitrate:</span>
    <span class="perf-value" id="bitrateValue">-- kbps</span>
  </div>
</div>

Debug Console

<!-- From Client/html-server/index.html:569 -->
<div class="debug-log" id="debugLog"></div>
// From Client/html-server/index.html:984-1008
function log(message, level = 'info') {
    console.log(message);
    
    const timestamp = new Date().toLocaleTimeString();
    const logEntry = document.createElement('div');
    logEntry.className = 'log-entry';
    logEntry.innerHTML = `
        <span class="log-timestamp">[${timestamp}]</span>
        <span class="log-level-${level}">${message}</span>
    `;
    
    debugLog.appendChild(logEntry);
    debugLog.scrollTop = debugLog.scrollHeight;
    
    // Keep last 200 entries
    if (debugLog.children.length > 200) {
        debugLog.removeChild(debugLog.firstChild);
    }
}

Key Source Files

index.html

Main client application with WebRTC, rendering, and input handling.

adapter.js

WebRTC adapter shim for cross-browser compatibility.
Browser Compatibility:
  • Chrome 90+: Full support including requestVideoFrameCallback
  • Firefox 89+: Full support
  • Safari 15.4+: Limited support (no rVFC)
  • Edge 90+: Full support (Chromium-based)

Build docs developers (and LLMs) love