File size: 2,753 Bytes
8f9c4ef | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// WebRTC Helper β Manages peer connections for video/audio
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
export const ICE_SERVERS: RTCConfiguration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:stun3.l.google.com:19302' },
],
};
/**
* Create an RTCPeerConnection with the configured ICE servers
*/
export function createPeerConnection(): RTCPeerConnection {
return new RTCPeerConnection(ICE_SERVERS);
}
/**
* Get user media with fallback handling
*/
export async function getUserMedia(
video: boolean = true,
audio: boolean = true
): Promise<MediaStream> {
try {
return await navigator.mediaDevices.getUserMedia({ video, audio });
} catch (error: any) {
// Try audio-only if video fails
if (video) {
console.warn('Video access failed, trying audio only');
return await navigator.mediaDevices.getUserMedia({ video: false, audio });
}
throw error;
}
}
/**
* Get display media for screen sharing
*/
export async function getDisplayMedia(): Promise<MediaStream> {
return await navigator.mediaDevices.getDisplayMedia({
video: { cursor: 'always' } as any,
audio: true,
});
}
/**
* Check if WebRTC is supported
*/
export function isWebRTCSupported(): boolean {
return !!(
window.RTCPeerConnection &&
navigator.mediaDevices &&
navigator.mediaDevices.getUserMedia
);
}
/**
* Estimate connection quality based on RTCPeerConnection stats
*/
export async function getConnectionQuality(
pc: RTCPeerConnection
): Promise<'excellent' | 'good' | 'poor' | 'disconnected'> {
if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed') {
return 'disconnected';
}
try {
const stats = await pc.getStats();
let roundTripTime = 0;
let packetsLost = 0;
stats.forEach((report) => {
if (report.type === 'candidate-pair' && report.currentRoundTripTime) {
roundTripTime = report.currentRoundTripTime * 1000; // ms
}
if (report.type === 'inbound-rtp' && report.packetsLost) {
packetsLost = report.packetsLost;
}
});
if (roundTripTime < 100 && packetsLost < 5) return 'excellent';
if (roundTripTime < 300 && packetsLost < 20) return 'good';
return 'poor';
} catch {
return 'good'; // Default if stats unavailable
}
}
|