如何让JavaScript Canvas响应浏览器SpeechSynthesisUtterance实时声音输出
如何让Canvas响应SpeechSynthesisUtterance的实时语音输出
我想还原《海绵宝宝》里的Karen角色,需要做一个能对浏览器通过SpeechSynthesisUtterance发出的实时语音做出反应的Canvas。目前的问题是,这个Canvas只能对预加载的音频做出响应,无法识别实时语音输出。我查过相关资料,但还是没搞懂具体实现原理,以下是我写的代码:
原JavaScript代码
const audioCtx = new AudioContext(); const analyser = audioCtx.createAnalyser(); let audio = document.getElementById("Audio"); const streamDestination = audioCtx.createMediaStreamDestination(); const mediaStream = streamDestination.stream; const mediaRecorder = new MediaRecorder(mediaStream); mediaRecorder.start(); analyser.connect(streamDestination); analyser.fftSize = 2048; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); analyser.getByteTimeDomainData(dataArray); // Capturing the canvas let canvas = document.getElementById("waves-canvas"); // Configuring the canvas canvas.width = window.innerWidth; canvas.height = window.innerHeight / 2; let wid = canvas.width; let hei = canvas.height; // Define the width of each segment based in the fftSize let sliceWidth = (wid * 1.0) / bufferLength; // CONFIGURANDO ESTILO DO CANVAS canvasCtx = canvas.getContext("2d"); canvasCtx.clearRect(0, 0, wid, hei); canvasCtx.fillStyle = "#151515"; // Line style canvasCtx.lineWidth = 2; canvasCtx.strokeStyle = "rgb(249, 251, 250)"; canvasCtx.shadowOffsetX = 1; canvasCtx.shadowOffsetY = 1; canvasCtx.shadowBlur = 10; canvasCtx.shadowColor = "rgb(28, 213, 254)"; function drawCanvas() { const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); analyser.getByteTimeDomainData(dataArray); canvasCtx.fillStyle = "#000000"; canvasCtx.fillRect(0, 0, canvas.width, canvas.height); canvasCtx.lineWidth = 2; canvasCtx.strokeStyle = "#ffffff"; canvasCtx.beginPath(); const sliceWidth = (canvas.width * 1.0) / bufferLength; let x = 0; for (let i = 0; i < bufferLength; i++) { const v = dataArray[i] / 128.0; const y = (v * canvas.height) / 2; if (i === 0) { canvasCtx.moveTo(x, y); } else { canvasCtx.lineTo(x, y); } x += sliceWidth; } canvasCtx.lineTo(canvas.width, canvas.height / 2); canvasCtx.stroke(); requestAnimationFrame(drawCanvas); } drawCanvas(); //var msg = new SpeechSynthesisUtterance(); //msg.text = "Test of speech"; //window.speechSynthesis.speak(msg);
原HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Karen</title> </head> <body> <!-- <audio id="Audio" controls> <source src="audio.mp3" type="audio/mpeg" /> </audio> --> <div class="waves-container"> <canvas id="waves-canvas"></canvas> </div> <script src="script.js"></script> </body> </html>
问题根源
你的代码里,AnalyserNode没有接入到SpeechSynthesisUtterance的音频输出链路中。SpeechSynthesis的语音是直接输出到系统扬声器,并没有经过你创建的AudioContext节点,所以Analyser拿不到实时语音数据。
解决思路
要捕获浏览器的音频输出,需要请求用户授权获取系统音频流,然后将这个流接入到AudioContext,再连接到AnalyserNode,这样就能获取实时语音数据来驱动Canvas绘制。
修改后的完整代码
HTML代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Karen</title> </head> <body> <button id="speakBtn">触发测试语音</button> <div class="waves-container"> <canvas id="waves-canvas"></canvas> </div> <script src="script.js"></script> </body> </html>
JavaScript代码
async function initAudioAnalysis() { const audioCtx = new AudioContext(); const analyser = audioCtx.createAnalyser(); // 请求系统音频捕获权限 try { // 获取桌面音频流(包含系统输出) const stream = await navigator.mediaDevices.getDisplayMedia({ video: false, audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false } }); // 将音频流接入AudioContext const source = audioCtx.createMediaStreamSource(stream); source.connect(analyser); // 配置Analyser参数 analyser.fftSize = 2048; const bufferLength = analyser.frequencyBinCount; // 初始化Canvas const canvas = document.getElementById("waves-canvas"); canvas.width = window.innerWidth; canvas.height = window.innerHeight / 2; const canvasCtx = canvas.getContext("2d"); // 绘制波形函数 function drawCanvas() { const dataArray = new Uint8Array(bufferLength); analyser.getByteTimeDomainData(dataArray); canvasCtx.fillStyle = "#000000"; canvasCtx.fillRect(0, 0, canvas.width, canvas.height); canvasCtx.lineWidth = 2; canvasCtx.strokeStyle = "#ffffff"; canvasCtx.shadowOffsetX = 1; canvasCtx.shadowOffsetY = 1; canvasCtx.shadowBlur = 10; canvasCtx.shadowColor = "rgb(28, 213, 254)"; canvasCtx.beginPath(); const sliceWidth = canvas.width / bufferLength; let x = 0; for (let i = 0; i < bufferLength; i++) { const v = dataArray[i] / 128.0; const y = (v * canvas.height) / 2; i === 0 ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y); x += sliceWidth; } canvasCtx.lineTo(canvas.width, canvas.height / 2); canvasCtx.stroke(); requestAnimationFrame(drawCanvas); } drawCanvas(); // 绑定语音触发按钮 document.getElementById("speakBtn").addEventListener("click", () => { if (audioCtx.state === "suspended") { audioCtx.resume(); } const msg = new SpeechSynthesisUtterance(); msg.text = "这是测试语音,看看Canvas有没有反应!"; window.speechSynthesis.speak(msg); }); } catch (err) { console.error("音频捕获失败:", err); alert("需要授权系统音频捕获权限才能正常工作"); } } // 页面加载后初始化 window.addEventListener("load", initAudioAnalysis);
注意事项
- 用户需要授权屏幕录制/系统音频捕获权限,浏览器才会允许获取音频流
- 部分浏览器可能需要HTTPS环境才能使用
getDisplayMediaAPI(本地localhost除外) - 如果只需要捕获浏览器标签页的音频,也可以在
getDisplayMedia的配置里指定audio: "tab"(部分浏览器支持)
内容的提问来源于stack exchange,提问作者alef-pasqualotto
相关产品推荐
相关产品推荐

