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

如何在React函数组件中解决HTMLMediaElement重复连接MediaElementSourceNode问题

问题:React StrictMode下创建MediaElementSource报错

React StrictMode会触发组件两次渲染,导致<audio>元素在首次渲染时已与一个MediaElementSourceNode建立连接,二次渲染时再次调用createMediaElementSource会抛出以下错误:

Uncaught DOMException: Failed to execute 'createMediaElementSource' on 'AudioContext': HTMLMediaElement already connected previously to a different MediaElementSourceNode

需求是保留StrictMode,通过断开旧连接的方式解决该问题。


解决方案

核心思路

  1. 持久化音频资源引用:用useRef存储AudioContext、MediaElementSourceNode等对象,避免组件重渲染时丢失旧连接的引用
  2. 创建新连接前清理旧资源:每次创建新的MediaElementSource前,先断开旧的源节点连接并关闭旧的音频上下文
  3. 添加清理逻辑:在组件卸载时移除事件监听器并清理音频资源,避免内存泄漏

修改后的完整代码

import React, { useEffect, useRef } from "react";

const Canvas = () => {
  const audioRef = useRef(null);
  const filUploadRef = useRef(null);
  const canvasRef = useRef(null);
  // 用useRef持久化音频上下文、源节点等引用
  const audioContextRef = useRef(null);
  const audioSourceRef = useRef(null);
  const analyserRef = useRef(null);
  const dataArrayRef = useRef(null);

  useEffect(() => {
    const audioElement = audioRef.current;
    const fileUploadField = filUploadRef.current;

    const handleFileChange = () => {
      // 清理旧资源:断开旧源节点连接,关闭旧音频上下文
      if (audioSourceRef.current) {
        audioSourceRef.current.disconnect();
        audioSourceRef.current = null;
      }
      if (audioContextRef.current) {
        audioContextRef.current.close();
        audioContextRef.current = null;
      }

      // 创建新的音频上下文和连接
      const audioContext = new AudioContext();
      audioContextRef.current = audioContext;

      const files = fileUploadField.files;
      audioElement.src = URL.createObjectURL(files[0]);
      audioElement.load();
      audioElement.play();

      const audioSource = audioContext.createMediaElementSource(audioElement);
      audioSourceRef.current = audioSource;
      
      const analyser = audioContext.createAnalyser();
      analyserRef.current = analyser;
      
      audioSource.connect(analyser);
      analyser.connect(audioContext.destination);
      dataArrayRef.current = new Uint8Array(analyser.frequencyBinCount);
    };

    fileUploadField.addEventListener("change", handleFileChange);

    // 组件卸载时清理事件监听器和音频资源
    return () => {
      fileUploadField.removeEventListener("change", handleFileChange);
      if (audioSourceRef.current) {
        audioSourceRef.current.disconnect();
      }
      if (audioContextRef.current) {
        audioContextRef.current.close();
      }
    };
  }, []);

  const handleTogglePlayPause = () => {
    const audioElement = audioRef.current;
    audioElement.paused ? audioElement.play() : audioElement.pause();
  };

  return (
    <>
      <canvas ref={canvasRef} />
      <audio controls ref={audioRef} />
      <input type="file" id="file-upload" accept="audio/*" ref={filUploadRef} />
      <button onClick={handleTogglePlayPause} type="button">
        Play/Pause
      </button>
    </>
  );
};

export default Canvas;

内容的提问来源于stack exchange,提问作者Klem Lloyd Mwenya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 08:15:29