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

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

问题根源与修复方法

核心问题

  1. audioTrack 作用域失效:用普通let声明的变量会在组件每次渲染时重新初始化,导致点击事件触发时可能取不到wavesurfer实例。
  2. 暂停逻辑错误:暂停操作时错误调用了play()方法,而非pause()。
  3. 音量控制语法错误:错误使用audioTrack.current.setVolume,wavesurfer实例本身直接提供setVolume方法。
  4. 未处理实例初始化前的调用:未判断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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 14:25:24