You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 16:06:28