1import {setupSimpleApp} from '../bumble.js'; 2 3(function () { 4 'use strict'; 5 6 let codecText; 7 let packetsReceivedText; 8 let bytesReceivedText; 9 let streamStateText; 10 let connectionStateText; 11 let audioOnButton; 12 let mediaSource; 13 let sourceBuffer; 14 let audioElement; 15 let audioContext; 16 let audioAnalyzer; 17 let audioFrequencyBinCount; 18 let audioFrequencyData; 19 let packetsReceived = 0; 20 let bytesReceived = 0; 21 let audioState = 'stopped'; 22 let streamState = 'IDLE'; 23 let fftCanvas; 24 let fftCanvasContext; 25 let bandwidthCanvas; 26 let bandwidthCanvasContext; 27 let bandwidthBinCount; 28 let bandwidthBins = []; 29 30 const FFT_WIDTH = 800; 31 const FFT_HEIGHT = 256; 32 const BANDWIDTH_WIDTH = 500; 33 const BANDWIDTH_HEIGHT = 100; 34 35 36 function init() { 37 initUI(); 38 initMediaSource(); 39 initAudioElement(); 40 initAnalyzer(); 41 initBumble(); 42 } 43 44 function initUI() { 45 audioOnButton = document.getElementById('audioOnButton'); 46 codecText = document.getElementById('codecText'); 47 packetsReceivedText = document.getElementById('packetsReceivedText'); 48 bytesReceivedText = document.getElementById('bytesReceivedText'); 49 streamStateText = document.getElementById('streamStateText'); 50 connectionStateText = document.getElementById('connectionStateText'); 51 52 audioOnButton.onclick = startAudio; 53 54 codecText.innerText = 'AAC'; 55 56 requestAnimationFrame(onAnimationFrame); 57 } 58 59 function initMediaSource() { 60 mediaSource = new MediaSource(); 61 mediaSource.onsourceopen = onMediaSourceOpen; 62 mediaSource.onsourceclose = onMediaSourceClose; 63 mediaSource.onsourceended = onMediaSourceEnd; 64 } 65 66 function initAudioElement() { 67 audioElement = document.getElementById('audio'); 68 audioElement.src = URL.createObjectURL(mediaSource); 69 // audioElement.controls = true; 70 } 71 72 function initAnalyzer() { 73 fftCanvas = document.getElementById('fftCanvas'); 74 fftCanvas.width = FFT_WIDTH 75 fftCanvas.height = FFT_HEIGHT 76 fftCanvasContext = fftCanvas.getContext('2d'); 77 fftCanvasContext.fillStyle = 'rgb(0, 0, 0)'; 78 fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT); 79 80 bandwidthCanvas = document.getElementById('bandwidthCanvas'); 81 bandwidthCanvas.width = BANDWIDTH_WIDTH 82 bandwidthCanvas.height = BANDWIDTH_HEIGHT 83 bandwidthCanvasContext = bandwidthCanvas.getContext('2d'); 84 bandwidthCanvasContext.fillStyle = 'rgb(255, 255, 255)'; 85 bandwidthCanvasContext.fillRect(0, 0, BANDWIDTH_WIDTH, BANDWIDTH_HEIGHT); 86 } 87 88 async function initBumble() { 89 const bumbleControls = document.querySelector('#bumble-controls'); 90 const app = await setupSimpleApp('speaker.py', bumbleControls, console.log); 91 app.on('start', onStart); 92 app.on('stop', onStop); 93 app.on('suspend', onSuspend); 94 app.on('connection', onConnection); 95 app.on('disconnection', onDisconnection); 96 app.on('audio', onAudio); 97 } 98 99 function startAnalyzer() { 100 // FFT 101 if (audioElement.captureStream !== undefined) { 102 audioContext = new AudioContext(); 103 audioAnalyzer = audioContext.createAnalyser(); 104 audioAnalyzer.fftSize = 128; 105 audioFrequencyBinCount = audioAnalyzer.frequencyBinCount; 106 audioFrequencyData = new Uint8Array(audioFrequencyBinCount); 107 const stream = audioElement.captureStream(); 108 const source = audioContext.createMediaStreamSource(stream); 109 source.connect(audioAnalyzer); 110 } 111 112 // Bandwidth 113 bandwidthBinCount = BANDWIDTH_WIDTH / 2; 114 bandwidthBins = []; 115 } 116 117 function setStreamState(state) { 118 streamState = state; 119 streamStateText.innerText = streamState; 120 } 121 122 function onAnimationFrame() { 123 // FFT 124 if (audioAnalyzer !== undefined) { 125 audioAnalyzer.getByteFrequencyData(audioFrequencyData); 126 fftCanvasContext.fillStyle = 'rgb(0, 0, 0)'; 127 fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT); 128 const barCount = audioFrequencyBinCount; 129 const barWidth = (FFT_WIDTH / audioFrequencyBinCount) - 1; 130 for (let bar = 0; bar < barCount; bar++) { 131 const barHeight = audioFrequencyData[bar]; 132 fftCanvasContext.fillStyle = `rgb(${barHeight / 256 * 200 + 50}, 50, ${50 + 2 * bar})`; 133 fftCanvasContext.fillRect(bar * (barWidth + 1), FFT_HEIGHT - barHeight, barWidth, barHeight); 134 } 135 } 136 137 // Bandwidth 138 bandwidthCanvasContext.fillStyle = 'rgb(255, 255, 255)'; 139 bandwidthCanvasContext.fillRect(0, 0, BANDWIDTH_WIDTH, BANDWIDTH_HEIGHT); 140 bandwidthCanvasContext.fillStyle = `rgb(100, 100, 100)`; 141 for (let t = 0; t < bandwidthBins.length; t++) { 142 const lineHeight = (bandwidthBins[t] / 1000) * BANDWIDTH_HEIGHT; 143 bandwidthCanvasContext.fillRect(t * 2, BANDWIDTH_HEIGHT - lineHeight, 2, lineHeight); 144 } 145 146 // Display again at the next frame 147 requestAnimationFrame(onAnimationFrame); 148 } 149 150 function onMediaSourceOpen() { 151 console.log(this.readyState); 152 sourceBuffer = mediaSource.addSourceBuffer('audio/aac'); 153 } 154 155 function onMediaSourceClose() { 156 console.log(this.readyState); 157 } 158 159 function onMediaSourceEnd() { 160 console.log(this.readyState); 161 } 162 163 async function startAudio() { 164 try { 165 console.log('starting audio...'); 166 audioOnButton.disabled = true; 167 audioState = 'starting'; 168 await audioElement.play(); 169 console.log('audio started'); 170 audioState = 'playing'; 171 startAnalyzer(); 172 } catch (error) { 173 console.error(`play failed: ${error}`); 174 audioState = 'stopped'; 175 audioOnButton.disabled = false; 176 } 177 } 178 179 function onStart() { 180 setStreamState('STARTED'); 181 } 182 183 function onStop() { 184 setStreamState('STOPPED'); 185 } 186 187 function onSuspend() { 188 setStreamState('SUSPENDED'); 189 } 190 191 function onConnection(params) { 192 connectionStateText.innerText = `CONNECTED: ${params.get('peer_name')} (${params.get('peer_address')})`; 193 } 194 195 function onDisconnection(params) { 196 connectionStateText.innerText = 'DISCONNECTED'; 197 } 198 199 function onAudio(python_packet) { 200 const packet = python_packet.toJs({create_proxies : false}); 201 python_packet.destroy(); 202 if (audioState != 'stopped') { 203 // Queue the audio packet. 204 sourceBuffer.appendBuffer(packet); 205 } 206 207 packetsReceived += 1; 208 packetsReceivedText.innerText = packetsReceived; 209 bytesReceived += packet.byteLength; 210 bytesReceivedText.innerText = bytesReceived; 211 212 bandwidthBins[bandwidthBins.length] = packet.byteLength; 213 if (bandwidthBins.length > bandwidthBinCount) { 214 bandwidthBins.shift(); 215 } 216 } 217 218 window.onload = (event) => { 219 init(); 220 } 221}());