WebRTC Streaming Mastery
Advanced WebRTC configuration and optimization for low-latency cloud gaming
๐ฅ
Advanced Level
WebRTC Architecture Overview
WebRTC (Web Real-Time Communication) is the foundation of Playcast's streaming technology. Understanding its architecture is crucial for optimization and troubleshooting.
Playcast WebRTC Data Flow
Game Instance
โ
Video Capture
โ
H.264 Encoder
โ
WebRTC Peer
โ
Client Browser
Real-Time Performance Metrics
25ms
End-to-End Latency
โ 15% improvement
8.5
Bitrate (Mbps)
โ Stable
60
Frames Per Second
โ Optimal
0.02%
Packet Loss
โ Network stable
Advanced WebRTC Configuration
๐๏ธ Stream Configuration Options
๐ฌ
Video Codec
Choose the optimal codec for your use case
๐ก๏ธ
Bitrate Control
Adaptive bitrate based on network conditions
๐
Quality Profile
Optimize for different scenarios
๐ง
Hardware Acceleration
Leverage GPU encoding when available
webrtc-config.ts
streaming-service.ts
optimization.ts
// Advanced WebRTC configuration for optimal streaming
export interface WebRTCConfig {
video: {
codec: 'H264' | 'H265' | 'VP9' | 'AV1';
profile: 'baseline' | 'main' | 'high';
level: string;
bitrate: {
mode: 'adaptive' | 'constant' | 'variable' | 'constrained';
target: number; // kbps
min: number;
max: number;
};
resolution: {
width: number;
height: number;
frameRate: number;
};
keyFrameInterval: number; // seconds
lowLatency: boolean;
};
audio: {
codec: 'opus' | 'aac';
bitrate: number;
sampleRate: number;
channels: number;
};
network: {
iceServers: RTCIceServer[];
iceCandidatePoolSize: number;
bundlePolicy: 'balanced' | 'max-bundle' | 'max-compat';
rtcpMuxPolicy: 'negotiate' | 'require';
};
optimization: {
hardwareAcceleration: boolean;
adaptiveBitrate: boolean;
networkAdaptation: boolean;
contentHint: 'motion' | 'detail' | 'text';
};
}
export class PlaycastWebRTC {
private config: WebRTCConfig;
private peerConnection: RTCPeerConnection | null = null;
private stats: RTCStatsReport | null = null;
constructor(config: Partial<WebRTCConfig> = {}) {
this.config = this.mergeWithDefaults(config);
this.initializePeerConnection();
}
private mergeWithDefaults(userConfig: Partial<WebRTCConfig>): WebRTCConfig {
return {
video: {
codec: 'H264',
profile: 'high',
level: '4.1',
bitrate: {
mode: 'adaptive',
target: 8000, // 8 Mbps
min: 1000, // 1 Mbps
max: 20000 // 20 Mbps
},
resolution: {
width: 1920,
height: 1080,
frameRate: 60
},
keyFrameInterval: 2,
lowLatency: true,
...userConfig.video
},
audio: {
codec: 'opus',
bitrate: 128, // kbps
sampleRate: 48000,
channels: 2,
...userConfig.audio
},
network: {
iceServers: [
{ urls: 'stun:stun.playcast.io:3478' },
{ urls: 'turn:turn.playcast.io:3478', username: 'playcast', credential: 'gaming' }
],
iceCandidatePoolSize: 10,
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require',
...userConfig.network
},
optimization: {
hardwareAcceleration: true,
adaptiveBitrate: true,
networkAdaptation: true,
contentHint: 'motion',
...userConfig.optimization
}
};
}
private async initializePeerConnection(): Promise<void> {
this.peerConnection = new RTCPeerConnection({
iceServers: this.config.network.iceServers,
iceCandidatePoolSize: this.config.network.iceCandidatePoolSize,
bundlePolicy: this.config.network.bundlePolicy,
rtcpMuxPolicy: this.config.network.rtcpMuxPolicy
});
// Set up event handlers
this.peerConnection.onicecandidate = this.handleIceCandidate.bind(this);
this.peerConnection.ontrack = this.handleRemoteTrack.bind(this);
this.peerConnection.onconnectionstatechange = this.handleConnectionStateChange.bind(this);
// Start collecting stats
this.startStatsCollection();
}
async createOffer(constraints?: RTCOfferOptions): Promise<RTCSessionDescriptionInit> {
if (!this.peerConnection) throw new Error('Peer connection not initialized');
const offer = await this.peerConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
...constraints
});
// Modify SDP for optimization
offer.sdp = this.optimizeSDP(offer.sdp!);
await this.peerConnection.setLocalDescription(offer);
return offer;
}
private optimizeSDP(sdp: string): string {
let optimizedSDP = sdp;
// Set video codec preferences
if (this.config.video.codec === 'H264') {
optimizedSDP = this.setH264Profile(optimizedSDP);
}
// Set bitrate constraints
optimizedSDP = this.setBitrateConstraints(optimizedSDP);
// Enable low-latency features
if (this.config.video.lowLatency) {
optimizedSDP = this.enableLowLatency(optimizedSDP);
}
return optimizedSDP;
}
private setH264Profile(sdp: string): string {
const profile = this.config.video.profile;
const level = this.config.video.level;
return sdp.replace(
/a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g,
`a=fmtp:$1 profile-level-id=${this.getH264ProfileId(profile, level)}`
);
}
private setBitrateConstraints(sdp: string): string {
const { target, min, max } = this.config.video.bitrate;
return sdp.replace(
/(m=video.*\r?\n)/,
`$1b=TIAS:${target * 1000}\r\nb=CT:${max * 1000}\r\n`
);
}
private enableLowLatency(sdp: string): string {
return sdp
.replace(/(a=rtcp-fb:\d+ nack)\r?\n/g, '$1\r\na=rtcp-fb:$1 pli\r\n')
.replace(/(a=rtcp-fb:\d+ ccm fir)\r?\n/g, '$1\r\na=rtcp-fb:$1 goog-remb\r\n');
}
private getH264ProfileId(profile: string, level: string): string {
const profiles = {
baseline: '42',
main: '4d',
high: '64'
};
return `${profiles[profile as keyof typeof profiles] || '64'}00${level.replace('.', '')}`;
}
private handleIceCandidate(event: RTCPeerConnectionIceEvent): void {
if (event.candidate) {
console.log('ICE candidate:', event.candidate);
// Send candidate to signaling server
}
}
private handleRemoteTrack(event: RTCTrackEvent): void {
console.log('Received remote track:', event.track.kind);
// Handle incoming media stream
}
private handleConnectionStateChange(): void {
if (this.peerConnection) {
console.log('Connection state:', this.peerConnection.connectionState);
}
}
private startStatsCollection(): void {
setInterval(async () => {
if (this.peerConnection) {
this.stats = await this.peerConnection.getStats();
this.analyzeStats();
}
}, 1000);
}
private analyzeStats(): void {
if (!this.stats) return;
this.stats.forEach((report) => {
if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
// Extract important metrics
const jitter = report.jitter;
const packetsLost = report.packetsLost;
const bytesReceived = report.bytesReceived;
// Implement adaptive bitrate logic
if (packetsLost > 10) {
this.reduceBitrate();
} else if (jitter < 0.005) {
this.increaseBitrate();
}
}
});
}
private reduceBitrate(): void {
// Implement bitrate reduction logic
}
private increaseBitrate(): void {
// Implement bitrate increase logic
}
getStats(): Promise<RTCStatsReport> {
return this.peerConnection?.getStats() || Promise.resolve(new Map() as RTCStatsReport);
}
close(): void {
if (this.peerConnection) {
this.peerConnection.close();
this.peerConnection = null;
}
}
}
// Enhanced streaming service with WebRTC optimization
import { PlaycastWebRTC, WebRTCConfig } from './webrtc-config';
export class OptimizedStreamingService {
private webrtc: PlaycastWebRTC;
private mediaStream: MediaStream | null = null;
private isStreaming = false;
private performanceMonitor: PerformanceMonitor;
constructor(config?: Partial<WebRTCConfig>) {
this.webrtc = new PlaycastWebRTC(config);
this.performanceMonitor = new PerformanceMonitor();
}
async startStream(constraints: MediaStreamConstraints): Promise<void> {
try {
// Get media stream from game capture
this.mediaStream = await this.captureGameStream(constraints);
// Add tracks to WebRTC connection
this.mediaStream.getTracks().forEach(track => {
this.webrtc.addTrack(track, this.mediaStream!);
this.optimizeTrack(track);
});
// Create and send offer
const offer = await this.webrtc.createOffer();
await this.sendOffer(offer);
this.isStreaming = true;
this.startPerformanceMonitoring();
} catch (error) {
console.error('Failed to start stream:', error);
throw error;
}
}
private async captureGameStream(constraints: MediaStreamConstraints): Promise<MediaStream> {
// Use advanced capture methods for minimal latency
if ('getDisplayMedia' in navigator.mediaDevices) {
return navigator.mediaDevices.getDisplayMedia({
video: {
...constraints.video,
frameRate: { ideal: 60 },
width: { ideal: 1920 },
height: { ideal: 1080 },
cursor: 'never' // Hide cursor for gaming
},
audio: constraints.audio
});
}
throw new Error('Display capture not supported');
}
private optimizeTrack(track: MediaStreamTrack): void {
if (track.kind === 'video') {
// Apply video-specific optimizations
const videoTrack = track as MediaStreamVideoTrack;
// Set content hint for gaming
if ('contentHint' in videoTrack) {
videoTrack.contentHint = 'motion';
}
// Apply advanced constraints
videoTrack.applyConstraints({
frameRate: { exact: 60 },
width: { exact: 1920 },
height: { exact: 1080 },
resizeMode: 'crop-and-scale'
}).catch(console.error);
} else if (track.kind === 'audio') {
// Audio optimizations for low latency
const audioTrack = track;
audioTrack.applyConstraints({
sampleRate: { exact: 48000 },
channelCount: { exact: 2 },
latency: { exact: 0.01 }, // 10ms latency
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
}).catch(console.error);
}
}
private startPerformanceMonitoring(): void {
this.performanceMonitor.start();
// Monitor and adapt every second
setInterval(async () => {
const stats = await this.webrtc.getStats();
const metrics = this.performanceMonitor.analyzeStats(stats);
if (metrics.needsOptimization) {
await this.adaptStream(metrics);
}
}, 1000);
}
private async adaptStream(metrics: PerformanceMetrics): Promise<void> {
// Implement adaptive streaming logic
if (metrics.packetLoss > 0.05) { // 5% packet loss
await this.reduceBitrate();
} else if (metrics.latency > 100) { // 100ms latency
await this.optimizeForLatency();
} else if (metrics.bandwidth > metrics.currentBitrate * 1.5) {
await this.increaseBitrate();
}
}
private async reduceBitrate(): Promise<void> {
// Implement bitrate reduction
console.log('Reducing bitrate due to network conditions');
}
private async increaseBitrate(): Promise<void> {
// Implement bitrate increase
console.log('Increasing bitrate - network can handle more');
}
private async optimizeForLatency(): Promise<void> {
// Implement latency optimizations
console.log('Optimizing for lower latency');
}
stopStream(): void {
if (this.mediaStream) {
this.mediaStream.getTracks().forEach(track => track.stop());
this.mediaStream = null;
}
this.webrtc.close();
this.performanceMonitor.stop();
this.isStreaming = false;
}
}
interface PerformanceMetrics {
latency: number;
packetLoss: number;
bandwidth: number;
currentBitrate: number;
needsOptimization: boolean;
}
class PerformanceMonitor {
private isMonitoring = false;
start(): void {
this.isMonitoring = true;
}
stop(): void {
this.isMonitoring = false;
}
analyzeStats(stats: RTCStatsReport): PerformanceMetrics {
// Implement comprehensive stats analysis
return {
latency: 50,
packetLoss: 0.01,
bandwidth: 10000000, // 10 Mbps
currentBitrate: 8000000, // 8 Mbps
needsOptimization: false
};
}
}
// Advanced optimization techniques for WebRTC streaming
export class StreamOptimizer {
private static instance: StreamOptimizer;
private optimizationRules: OptimizationRule[] = [];
private constructor() {
this.initializeOptimizationRules();
}
static getInstance(): StreamOptimizer {
if (!StreamOptimizer.instance) {
StreamOptimizer.instance = new StreamOptimizer();
}
return StreamOptimizer.instance;
}
private initializeOptimizationRules(): void {
this.optimizationRules = [
new LatencyOptimizationRule(),
new BandwidthOptimizationRule(),
new QualityOptimizationRule(),
new NetworkAdaptationRule(),
new HardwareOptimizationRule()
];
}
async optimize(stream: MediaStream, connection: RTCPeerConnection): Promise<OptimizationResult> {
const context: OptimizationContext = {
stream,
connection,
networkConditions: await this.analyzeNetwork(),
hardwareCapabilities: await this.analyzeHardware(),
gameType: this.detectGameType(),
userPreferences: this.getUserPreferences()
};
const results: OptimizationResult[] = [];
for (const rule of this.optimizationRules) {
if (rule.shouldApply(context)) {
const result = await rule.apply(context);
results.push(result);
}
}
return this.mergeResults(results);
}
private async analyzeNetwork(): Promise<NetworkConditions> {
// Use WebRTC stats and network APIs to analyze conditions
const connection = (navigator as any).connection;
return {
bandwidth: this.estimateBandwidth(),
latency: await this.measureLatency(),
packetLoss: 0, // Will be updated from stats
jitter: 0,
connectionType: connection?.effectiveType || 'unknown',
isStable: true
};
}
private async analyzeHardware(): Promise<HardwareCapabilities> {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
return {
gpu: gl ? this.getGPUInfo(gl as WebGLRenderingContext) : 'unknown',
cpuCores: navigator.hardwareConcurrency || 4,
ram: (navigator as any).deviceMemory ? (navigator as any).deviceMemory * 1024 : 8192,
hardwareAcceleration: await this.testHardwareAcceleration(),
encodingSupport: this.testEncodingSupport()
};
}
private getGPUInfo(gl: WebGLRenderingContext): string {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || 'unknown';
}
return 'unknown';
}
private async testHardwareAcceleration(): Promise<boolean> {
// Test if hardware acceleration is available
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { alpha: false });
return ctx ? true : false;
} catch {
return false;
}
}
private testEncodingSupport(): EncodingSupport {
return {
h264: MediaRecorder.isTypeSupported('video/webm;codecs=h264'),
h265: MediaRecorder.isTypeSupported('video/webm;codecs=h265'),
vp9: MediaRecorder.isTypeSupported('video/webm;codecs=vp9'),
av1: MediaRecorder.isTypeSupported('video/webm;codecs=av01')
};
}
private estimateBandwidth(): number {
// Implement bandwidth estimation algorithm
return 10000000; // 10 Mbps default
}
private async measureLatency(): Promise<number> {
// Implement RTT measurement
const start = performance.now();
try {
await fetch('/api/ping', { method: 'HEAD' });
return performance.now() - start;
} catch {
return 50; // Default 50ms
}
}
private detectGameType(): GameType {
// Implement game type detection based on content analysis
return 'action'; // Default
}
private getUserPreferences(): UserPreferences {
return {
prioritizeLatency: true,
maxBandwidth: 20000000, // 20 Mbps
qualityPreference: 'balanced',
powerSavingMode: false
};
}
private mergeResults(results: OptimizationResult[]): OptimizationResult {
// Combine optimization results
return results.reduce((merged, result) => ({
...merged,
...result,
improvements: [...(merged.improvements || []), ...(result.improvements || [])]
}), {});
}
}
// Optimization rule implementations
class LatencyOptimizationRule implements OptimizationRule {
shouldApply(context: OptimizationContext): boolean {
return context.userPreferences.prioritizeLatency ||
context.gameType === 'competitive' ||
context.networkConditions.latency > 50;
}
async apply(context: OptimizationContext): Promise<OptimizationResult> {
const improvements: string[] = [];
// Reduce keyframe interval for faster recovery
improvements.push('Reduced keyframe interval to 1 second');
// Enable low-latency mode
improvements.push('Enabled ultra-low latency mode');
// Optimize buffer sizes
improvements.push('Optimized buffer configuration');
return {
type: 'latency',
improvements,
estimatedImpact: 'Reduced latency by 15-25ms'
};
}
}
class BandwidthOptimizationRule implements OptimizationRule {
shouldApply(context: OptimizationContext): boolean {
return context.networkConditions.bandwidth < 5000000 || // Less than 5 Mbps
context.networkConditions.connectionType === 'slow-2g' ||
context.networkConditions.connectionType === '2g';
}
async apply(context: OptimizationContext): Promise<OptimizationResult> {
const improvements: string[] = [];
// Reduce resolution or framerate
improvements.push('Adapted resolution based on bandwidth');
// Enable advanced compression
improvements.push('Enabled advanced compression algorithms');
// Implement adaptive bitrate
improvements.push('Configured adaptive bitrate streaming');
return {
type: 'bandwidth',
improvements,
estimatedImpact: 'Reduced bandwidth usage by 30-40%'
};
}
}
// Additional optimization rules would be implemented similarly...
// Type definitions
interface OptimizationContext {
stream: MediaStream;
connection: RTCPeerConnection;
networkConditions: NetworkConditions;
hardwareCapabilities: HardwareCapabilities;
gameType: GameType;
userPreferences: UserPreferences;
}
interface NetworkConditions {
bandwidth: number;
latency: number;
packetLoss: number;
jitter: number;
connectionType: string;
isStable: boolean;
}
interface HardwareCapabilities {
gpu: string;
cpuCores: number;
ram: number;
hardwareAcceleration: boolean;
encodingSupport: EncodingSupport;
}
interface EncodingSupport {
h264: boolean;
h265: boolean;
vp9: boolean;
av1: boolean;
}
interface UserPreferences {
prioritizeLatency: boolean;
maxBandwidth: number;
qualityPreference: 'quality' | 'balanced' | 'performance';
powerSavingMode: boolean;
}
interface OptimizationRule {
shouldApply(context: OptimizationContext): boolean;
apply(context: OptimizationContext): Promise<OptimizationResult>;
}
interface OptimizationResult {
type?: string;
improvements?: string[];
estimatedImpact?: string;
}
type GameType = 'action' | 'strategy' | 'casual' | 'competitive' | 'unknown';
๐ Pro Optimization Tips
Ultra-Low Latency
Use hardware encoding, reduce keyframe intervals, and enable WebRTC's low-latency features for sub-50ms end-to-end latency.
Adaptive Bitrate
Implement intelligent bitrate adaptation based on network conditions, packet loss, and available bandwidth.
Content-Aware Encoding
Adjust encoding parameters based on game type - fast motion for action games, high detail for strategy games.
Hardware Acceleration
Leverage GPU encoding (NVENC, QuickSync, AMF) for better performance and lower CPU usage.
๐งช Live Configuration Test
Click a button above to run live tests and see results here...