1

我有以下使用 wavesurfer.js 的功能性 React 组件

import * as React from "react"
import * as WaveSurfer from 'wavesurfer.js'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlayCircle, faPauseCircle } from '@fortawesome/free-regular-svg-icons'
import { IconDefinition } from '@fortawesome/fontawesome-svg-core'

interface WaveformPlayerProps {
  audioUrl: string
}

const WaveformPlayer = (props: WaveformPlayerProps) => {
  const [playing, setPlaying] = React.useState(false)
  const [audioContext, setAudioContext] = React.useState(new AudioContext())
  const [waveform, setWaveform] = React.useState(null)

  const waveformRef = React.useRef();

  React.useEffect(() => {
    if (waveformRef.current) {
      const wavesurfer = WaveSurfer.create({
        audioContext: audioContext,
        container: waveformRef.current
      });

      wavesurfer.on('finish', togglePlayPause)
      wavesurfer.load(props.audioUrl)
      setWaveform(wavesurfer)
    }
  }, [])

  function togglePlayPause(): void {
    if (audioContext.state == "suspended") {
      audioContext.resume()
    }

    waveform.playPause()
    setPlaying(!playing)
  }

  function stateButtonIcon(): IconDefinition {
    if (playing) {
      return faPauseCircle
    } else {
      return faPlayCircle
    }
  }

  return (
    <div>
      <button id="waveformAudioControl" className="button is-large" onClick={togglePlayPause}>
        <FontAwesomeIcon icon={stateButtonIcon()} />
      </button>

      <div ref={waveformRef}>
      </div>
    </div>
  )
}

export default WaveformPlayer

我认为我可以在外部访问 WaveSurfer 对象的唯一方法useEffect是使用,useState但这对我来说感觉不对,因为如果组件中的某些状态发生变化(例如,用户按下播放/暂停),WaveSurfer 组件不应该完全重新渲染按钮)。这导致我认为我的方法不正确?

编辑:忘了提到waveform.playPause()失败,因为单击播放按钮时它为空。

4

2 回答 2

1
import React, { Fragment, useState, useEffect } from 'react'
import WaveSurfer from 'wavesurfer.js'
import audioFile from './../assets/sample-wave-file-5MB.wav'

const WaveSurferStatelessForm = () => {
  let [isPlaying, setIsPlaying] = useState(false)
  let [waveSurfer, setWaveSurfer] = useState(null)

  useEffect(() => {
    setWaveSurfer(WaveSurfer.create({
      container: '#waveform'
    }))
  }, [])

  useEffect(() => {
    if(waveSurfer) {
      waveSurfer.load(audioFile)
    }
  }, [waveSurfer])

  const togglePlayPause = () => {
    waveSurfer.playPause()
    setIsPlaying(!isPlaying)
  }

  return (
    <Fragment>
      <div id="waveform" ></div>
      <button onClick={() => togglePlayPause()}>{isPlaying ? '||' : '+'}</button>
    </Fragment>
  )
}

export default WaveSurferStatelessForm

waveform.playPause()单击播放按钮后失败,因为您正在更新状态,playing这将导致您的组件重新渲染。由于您的 useEffect 仅在第一次渲染时运行,一旦状态更改,您的波形将设置为 null,并且 useEffect 在组件重新渲染时不会重新实例化它。要解决这个问题,您需要将波形设置为状态,因此它始终被实例化,或者在每次组件重新渲染时将playing变量添加到 useEffect 中重新实例化波形。[]我上面的解决方案使用了前者,下面有详细的解释。

在第一次渲染后设置 waveSurfer 对象,这样<div id="waveform" ></div>在创建 waveSurfer 对象之前将被挂载到 DOM。

  useEffect(() => {
    setWaveSurfer(WaveSurfer.create({
      container: '#waveform'
    }))
  }, [])

现在已经设置了 waveSurfer 对象,我们需要加载音频。这useEffect将运行两次,但只有在设置了 waveSurfer 对象后才加载音频,这将是最后一次useEffect调用它。在此之前,waveSurfer 对象为空。

  useEffect(() => {
    if(waveSurfer) {
      waveSurfer.load(audioFile)
    }
  }, [waveSurfer])

请注意,useEffect将始终在第一次渲染之后运行,然后在任何状态更改传递给[]. 如果没有[],它将在每次渲染后运行。所以你不需要检查 div 是否存在if (waveformRef.current)它会一直存在,因为它useEffect是在第一次渲染之后执行的。在有状态或class组件中,这类似于componentDidMount

于 2020-07-11T07:35:35.387 回答
0

我能想到的最好的方法是在文件的其余部分声明const waveform = React.useRef<WaveSurfer | null>(null)和使用waveform.current

于 2020-04-07T01:00:53.993 回答