如何在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,通过断开旧连接的方式解决该问题。
解决方案
核心思路
- 持久化音频资源引用:用
useRef存储AudioContext、MediaElementSourceNode等对象,避免组件重渲染时丢失旧连接的引用 - 创建新连接前清理旧资源:每次创建新的MediaElementSource前,先断开旧的源节点连接并关闭旧的音频上下文
- 添加清理逻辑:在组件卸载时移除事件监听器并清理音频资源,避免内存泄漏
修改后的完整代码
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
相关产品推荐
相关产品推荐

