React+wavesurfer.js播放/暂停按钮功能异常问题(已解决)
音频播放/暂停按钮异常修复方案
问题描述
已解决:我按照下方方法解决了问题。
我被这个问题困扰了好几个小时,一直没搞定。我做了个带FontAwesomeIcon的按钮,想根据音频播放状态切换图标:播放时显示暂停图标,否则显示播放图标。第一次点播放按钮,图标能正常切换,音频也能播放;但第二次点击时,音乐停不下来,图标也切不回去。
原代码
import React from 'react'; import { useState, useRef, useEffect } from 'react'; import wavesurfer from 'wavesurfer.js'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCirclePlay, faCirclePause } from "@fortawesome/free-solid-svg-icons"; const AudioVisualizer = (props) => { const audioRef = useRef(null); // const [playing, setPlay] = useState(null); const [isClicked, setIsClicked] = useState(false) const [volume, setVolume] = useState(0.5); const [isBtnClicked, setBtnClicked] = useState(false) const [icon, setIcon] = useState(faCirclePlay); let audioTrack; useEffect(()=>{ if (audioRef.current){ audioTrack = wavesurfer.create({ container: audioRef.current, progressColor: "#13AEA2", waveColor: "#eeee", cursorColor: "OrangeRed", preload: true, backend: "MediaElement", barWidth: 2, barHeight: 1, // the height of the wave fillParent: true, hideScrollbar: true, responsive: true, }); audioTrack.load(props.audio); } }, []) const onVolumeChange = e => { const { target } = e; const newVolume = +target.value; if (newVolume) { console.log("new volume true,,, volume: " + volume) setVolume(volume => newVolume); audioTrack.current.setVolume(newVolume || 1); } }; const clicking = (e) => { // Get a view of what the "click" registers: console.log(e.currentTarget.tagName); console.log(e.target); // if plying == pause if ( ! audioTrack.isPlaying() ) { console.log("not playing.. Start playing"); audioTrack.play() setIcon(icon => faCirclePause) return } console.log("Is playing.. will pause") audioTrack.play() setIcon(faCirclePlay); return }; return ( <> <div className='audio' ref={audioRef}> </div> <div className='audioKnobs'> <button className='playpausewrapper' onClick={clicking}> <FontAwesomeIcon className='playButton' icon={icon} /> </button> <input type="range" id="volume" name="volume" min="0.01" max="1" step=".025" onChange={onVolumeChange} defaultValue={volume}/> </div> </> ) } export default AudioVisualizer;
控制台报错信息
AudioVisualizer.js:75 Uncaught TypeError: Cannot read properties of undefined (reading 'isPlaying') at clicking (AudioVisualizer.js:75:1) at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1) at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1) at invokeGuardedCallback (react-dom.development.js:4277:1) at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:1) at executeDispatch (react-dom.development.js:9041:1) at processDispatchQueueItemsInOrder (react-dom.development.js:9073:1) at processDispatchQueue (react-dom.development.js:9086:1) at dispatchEventsForPlugins (react-dom.development.js:9097:1) at react-dom.development.js:9288:1
问题根源与修复方法
核心问题
audioTrack作用域失效:用普通let声明的变量会在组件每次渲染时重新初始化,导致点击事件触发时可能取不到wavesurfer实例。- 暂停逻辑错误:暂停操作时错误调用了
play()方法,而非pause()。 - 音量控制语法错误:错误使用
audioTrack.current.setVolume,wavesurfer实例本身直接提供setVolume方法。 - 未处理实例初始化前的调用:未判断wavesurfer实例是否已创建就调用方法,导致报错。
修改后的完整代码
import React from 'react'; import { useState, useRef, useEffect } from 'react'; import wavesurfer from 'wavesurfer.js'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCirclePlay, faCirclePause } from "@fortawesome/free-solid-svg-icons"; const AudioVisualizer = (props) => { const audioRef = useRef(null); const [volume, setVolume] = useState(0.5); const [icon, setIcon] = useState(faCirclePlay); // 用useRef持久化wavesurfer实例 const audioTrackRef = useRef(null); useEffect(()=>{ if (audioRef.current){ audioTrackRef.current = wavesurfer.create({ container: audioRef.current, progressColor: "#13AEA2", waveColor: "#eeee", cursorColor: "OrangeRed", preload: true, backend: "MediaElement", barWidth: 2, barHeight: 1, fillParent: true, hideScrollbar: true, responsive: true, }); audioTrackRef.current.load(props.audio); } // 组件卸载时销毁实例,避免内存泄漏 return () => { if (audioTrackRef.current) { audioTrackRef.current.destroy(); } } }, [props.audio]) // 依赖props.audio,音频地址变化时重新加载 const onVolumeChange = e => { const { target } = e; const newVolume = +target.value; // 先判断实例是否存在 if (newVolume && audioTrackRef.current) { setVolume(newVolume); audioTrackRef.current.setVolume(newVolume); } }; const clicking = (e) => { // 实例未初始化时直接返回,避免报错 if (!audioTrackRef.current) return; if ( !audioTrackRef.current.isPlaying() ) { console.log("not playing.. Start playing"); audioTrackRef.current.play(); setIcon(faCirclePause); } else { console.log("Is playing.. will pause"); audioTrackRef.current.pause(); // 修复为pause方法 setIcon(faCirclePlay); } }; return ( <> <div className='audio' ref={audioRef}></div> <div className='audioKnobs'> <button className='playpausewrapper' onClick={clicking}> <FontAwesomeIcon className='playButton' icon={icon} /> </button> {/* 改为受控组件,确保音量滑块与状态同步 */} <input type="range" id="volume" name="volume" min="0.01" max="1" step=".025" onChange={onVolumeChange} value={volume} /> </div> </> ) } export default AudioVisualizer;
关键修复点说明
- 用
useRef保存wavesurfer实例,确保组件渲染时实例引用不丢失。 - 修复暂停逻辑,调用
pause()方法替代错误的play()。 - 音量控制中修正方法调用,并添加实例存在性判断。
- 将音量滑块改为受控组件,绑定
value={volume}保证状态与UI同步。 - 添加组件卸载时的实例销毁逻辑,避免内存泄漏。
- 给
useEffect添加props.audio依赖,确保音频地址变化时重新加载。
内容的提问来源于stack exchange,提问作者Stian Larsen
相关产品推荐
相关产品推荐

