export function createAudioAnalyser(stream, fftSize) { const AudioContextCtor = window.AudioContext || window.webkitAudioContext; const audioContext = new AudioContextCtor(); const source = audioContext.createMediaStreamSource(stream); const analyser = audioContext.createAnalyser(); analyser.fftSize = fftSize; source.connect(analyser); return { audioContext, analyser, dataArray: new Uint8Array(analyser.frequencyBinCount) }; } export function getAudioLevel(analyser, dataArray) { analyser.getByteTimeDomainData(dataArray); let sum = 0; for (let i = 0; i < dataArray.length; i++) { const amplitude = dataArray[i] - 128; sum += amplitude * amplitude; } const rms = Math.sqrt(sum / dataArray.length); return rms / 128; } function formatPacketLossRate(packetsLost, packetsReceived) { if (packetsReceived <= 0) { return '0%'; } return `${((packetsLost / (packetsLost + packetsReceived)) * 100).toFixed(2)}%`; } export function buildStatsLogPayload(networkQuality, statsSummary) { return { networkQuality, video: { 'Packets Lost': statsSummary.video.packetsLost, 'Packets Received': statsSummary.video.packetsReceived, 'Packet Loss Rate': formatPacketLossRate( statsSummary.video.packetsLost, statsSummary.video.packetsReceived ), 'Jitter': `${(statsSummary.video.jitter * 1000).toFixed(2)}ms`, 'Round Trip Time': `${(statsSummary.video.roundTripTime * 1000).toFixed(2)}ms`, 'FPS': statsSummary.video.fps.toFixed(1), 'Bitrate': `${statsSummary.video.bitrate}kbps` }, audio: { 'Packets Lost': statsSummary.audio.packetsLost, 'Packets Received': statsSummary.audio.packetsReceived, 'Packet Loss Rate': formatPacketLossRate( statsSummary.audio.packetsLost, statsSummary.audio.packetsReceived ), 'Jitter': `${(statsSummary.audio.jitter * 1000).toFixed(2)}ms` } }; }