React 音频显示波形与区间循环播放
侧边栏壁纸
  • 累计撰写 635 篇文章
  • 累计收到 0 条评论

React 音频显示波形与区间循环播放

加速器之家
2024-08-24 / 0 评论 / 4 阅读 / 正在检测是否收录...

先上 Saladict 中实现的效果:

Saladict Waveform

以及源码例子:

WaveSurfer

首先对于音频区间选择的交互,我第一反应是要做成音频处理软件那样的效果:显示波形,然后可以直接在波形上选择区间。

做 Waveform 算是小众需求,所以这方面的库不多,比较一番之后认为 WaveSurfer 这个开源项目相对靠谱,而且它可以通过插件支持区间选择。

但是它一个纯 JS 的库,结合 React 我们需要做些简单的处理,主要是组件挂载时加载以及销毁时释放。

export default class Waveform extends React.PureComponent {
  componentDidMount () {
    this.wavesurfer = WaveSurfer.create({
      container: '#waveform-container',
    })
  }

  componentWillUnmount () {
    if (this.wavesurfer) {
      this.wavesurfer.destroy()
      this.wavesurfer = null
    }
  }

  render () {
    return {
      <div id="waveform-container" />
    }
  }
}

这里面就有一个坑,Chrome 在不久前改了政策,在页面刚加载发生用户交互之前 AudioContext 是处于一个 suspended 状态,即无法进行播放,需要监听 statechange 事件在状态变成 running 之后再调用 resume() 方法恢复。

然而实测这个 statechange 事件发生的时机有点玄学,且通过 wavesurfer.backend.ac.resume() 之后不知为何还是不能加载出波形。所以最保险的方式是懒加载 WaveSurfer,在第一次播放的时候才初始化,这时几乎可以保证用户已经进行了交互。

export default class Waveform extends React.PureComponent {
initWaveSurfer = () => {    this.wavesurfer = WaveSurfer.create({
      container: '#waveform-container',
    })
}
loadAudio = (src) => {if (this.wavesurfer) {this.reset()} else {this.initWaveSurfer()}this.wavesurfer.load(src)}reset = () => {if (this.wavesurfer) {this.wavesurfer.pause()this.wavesurfer.empty()}}
  componentDidMount () {
// 监听交互事件// 回调 this.loadAudio(src)  }

  componentWillUnmount () {
    if (this.wavesurfer) {
      this.wavesurfer.destroy()
      this.wavesurfer = null
    }
  }

  render () {
    return {
      <div id="waveform-container" />
    }
  }
}

接下来处理播放和暂停,比较直接的方式是监听 Wavesurfer 的 playpause 再来改变组件 state,然而考虑到后面需要做其它处理,而这两个方法在我们后面特殊处理循环的时候触发频率会比较高,所以就不监听而分开处理。

export default class Waveform extends React.PureComponent {
  state = {
    isPlaying: false,
    loop: true
  }

  initWaveSurfer = () => {
    this.wavesurfer = WaveSurfer.create({
      container: '#waveform-container',
    })

    wavesurfer.on('ready', this.play)
    wavesurfer.on('finish', this.onPlayEnd)
  }

  play = () => {
    this.setState({ isPlaying: true })
    if (this.wavesurfer) {
      this.wavesurfer.play()
    }
  }

  pause = () => {
    this.setState({ isPlaying: false })
    if (this.wavesurfer) {
      this.wavesurfer.pause()
    }
  }

  togglePlay = () => {
    this.state.isPlaying ? this.pause() : this.play()
  }

  onPlayEnd = () => {
    this.state.loop ? this.play() : this.pause()
  }
  // ...
}

区间选择

接下来就是实现区间选择。Wavesurfer 提供了 Regions plugin,我们需要针对交互做些调整。

import RegionsPlugin from 'wavesurfer.js/dist/plugin/wavesurfer.regions.min.js'
export default class Waveform extends React.PureComponent {
  state = {
    isPlaying: false,
    loop: true
  }

  initWaveSurfer = () => {
    this.wavesurfer = WaveSurfer.create({
      container: '#waveform-container',
plugins: [RegionsPlugin.create()]    })

    // 允许鼠标划选区间
    wavesurfer.enableDragSelection({})
    // 鼠标按下去那一刻触发
    wavesurfer.on('region-created', region => {
      // Region 插件支持多个区间,我们只保留最新一个
      this.removeRegion()
      this.region = region
    })
    // 鼠标放开那一刻触发
    wavesurfer.on('region-update-end', this.play)
    // 播放指示器刚出区间区域那一刻触发
    wavesurfer.on('region-out', this.onPlayEnd)

    // 鼠标点任意波形区域时触发,改变指示器位置
    wavesurfer.on('seek', () => {
      if (!this.isInRegion()) {
        // 用户点击了区间外,把区间移除掉
        this.removeRegion()
      }
    })

    wavesurfer.on('ready', this.play)
    wavesurfer.on('finish', this.onPlayEnd)
  }

  /** 检查当前指示器是否在区间中 */
  isInRegion = (region = this.region) => {
    if (region && this.wavesurfer) {
      const curTime = this.wavesurfer.getCurrentTime()
      return curTime >= region.start && curTime <= region.end
    }
    return false
  }

  removeRegion = () => {
    if (this.region) {
      this.region.remove()
    }
    this.region = null
  }

  play = () => {
    this.setState({ isPlaying: true })
    if (this.region && !this.isInRegion()) {
      // 如果指示器不在区间中则重新从区间起点播放
      this.wavesurfer.play(this.region.start)
    } else {
      // 否则继续播放
      this.wavesurfer.play()
    }
  }

  // ...
}

注意 Regions 插件中其实提供了循环播放的方法,但是为了接下来更细粒度的控制,我们改为监听指示器移出区间右侧时的 region-out 事件,然后与 finish 事件用同样的方法处理。

本文介绍了如何结合 React 和 Wavesurfer 显示音频波形,实现区间选择和循环播放。下篇文章中我将继续分享如何实现音频的加速和减速。

0

评论

博主关闭了当前页面的评论