相关文章推荐
奋斗的炒饭  ·  Dataset 的一些 Java api ...·  10 月前    · 
玩手机的饭卡  ·  python ...·  1 年前    · 
憨厚的黑框眼镜  ·  InputStream (Java 2 ...·  1 年前    · 
  • 点击播放只会在播放区域进行播放,再次点击暂停播放,鼠标右键点击播放区域也可播放
  • 可以添加播放区域,添加时会删除上一个播放区域,按住 shift + 鼠标右键可删除播放区域
  • 可以放大缩小波形图
  • WaveFormDivider/index.js

    /* eslint-disable no-unused-expressions */
    /* eslint-disable import/no-unresolved */
    /* eslint-disable no-param-reassign */
    import React from 'react';
    import WaveSurfer from 'waveSurfer.js';
    import RegionsPlugin from 'waveSurfer.js/dist/plugin/waveSurfer.regions';
    import TimelinePlugin from 'waveSurfer.js/dist/plugin/waveSurfer.timeline';
    import MinimapPlugin from 'waveSurfer.js/dist/plugin/waveSurfer.minimap';
    import { Slider, Button, Icon } from 'antd';
    import { fromJS } from 'immutable';
    import styles from './styles.css';
     * Use formatTimeCallback to style the notch labels as you wish, such
     * as with more detail as the number of pixels per second increases.
     * Here we format as M:SS.frac, with M suppressed for times < 1 minute,
     * and frac having 0, 1, or 2 digits as the zoom increases.
     * Note that if you override the default function, you'll almost
     * certainly want to override timeInterval, primaryLabelInterval and/or
     * secondaryLabelInterval so they all work together.
     * @param: seconds
     * @param: pxPerSec
    function formatTimeCallback(seconds, pxPerSec) {
      seconds = Number(seconds);
      const minutes = Math.floor(seconds / 60);
      seconds %= 60;
      // fill up seconds with zeroes
      let secondsStr = Math.round(seconds).toString();
      if (pxPerSec >= 25 * 10) {
        secondsStr = seconds.toFixed(2);
      } else if (pxPerSec >= 25 * 1) {
        secondsStr = seconds.toFixed(1);
      if (minutes > 0) {
        if (seconds < 10) {
          secondsStr = `0${secondsStr}`;
        return `${minutes}:${secondsStr}`;
      return secondsStr;
     * Use timeInterval to set the period between notches, in seconds,
     * adding notches as the number of pixels per second increases.
     * Note that if you override the default function, you'll almost
     * certainly want to override formatTimeCallback, primaryLabelInterval
     * and/or secondaryLabelInterval so they all work together.
     * @param: pxPerSec
    function timeInterval(pxPerSec) {
      let retval = 1;
      if (pxPerSec >= 25 * 100) {
        retval = 0.01;
      } else if (pxPerSec >= 25 * 40) {
        retval = 0.025;
      } else if (pxPerSec >= 25 * 10) {
        retval = 0.1;
      } else if (pxPerSec >= 25 * 4) {
        retval = 0.25;
      } else if (pxPerSec >= 25) {
        retval = 1;
      } else if (pxPerSec * 5 >= 25) {
        retval = 5;
      } else if (pxPerSec * 15 >= 25) {
        retval = 15;
      } else {
        retval = Math.ceil(0.5 / pxPerSec) * 60;
      return retval;
     * Return the cadence of notches that get labels in the primary color.
     * EG, return 2 if every 2nd notch should be labeled,
     * return 10 if every 10th notch should be labeled, etc.
     * Note that if you override the default function, you'll almost
     * certainly want to override formatTimeCallback, primaryLabelInterval
     * and/or secondaryLabelInterval so they all work together.
     * @param pxPerSec
    function primaryLabelInterval(pxPerSec) {
      let retval = 1;
      if (pxPerSec >= 25 * 100) {
        retval = 10;
      } else if (pxPerSec >= 25 * 40) {
        retval = 4;
      } else if (pxPerSec >= 25 * 10) {
        retval = 10;
      } else if (pxPerSec >= 25 * 4) {
        retval = 4;
      } else if (pxPerSec >= 25) {
        retval = 1;
      } else if (pxPerSec * 5 >= 25) {
        retval = 5;
      } else if (pxPerSec * 15 >= 25) {
        retval = 15;
      } else {
        retval = Math.ceil(0.5 / pxPerSec) * 60;
      return retval;
     * Return the cadence of notches to get labels in the secondary color.
     * EG, return 2 if every 2nd notch should be labeled,
     * return 10 if every 10th notch should be labeled, etc.
     * Secondary labels are drawn after primary labels, so if
     * you want to have labels every 10 seconds and another color labels
     * every 60 seconds, the 60 second labels should be the secondaries.
     * Note that if you override the default function, you'll almost
     * certainly want to override formatTimeCallback, primaryLabelInterval
     * and/or secondaryLabelInterval so they all work together.
     * @param pxPerSec
    function secondaryLabelInterval(pxPerSec) {
      // draw one every 10s as an example
      return Math.floor(10 / timeInterval(pxPerSec));
    export class WaveFormDivider extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
      static defaultProps = {
        startTime: undefined,
        endTime: undefined,
        src: '',
      static initState = {
        data: fromJS({
          zoom: 0,
          playing: false,
      state = WaveFormDivider.initState
      componentDidMount() {
        const { src } = this.props;
        src && this.initWaveSurfer();
      componentWillUnmount() {
        this.destroyWaveSurfer();
      setImmState = (fn, cb) => {
        this.setState(({ data }) => ({
          data: fn(data),
        }), cb);
      destroyWaveSurfer = () => {
        this.setState(WaveFormDivider.initState);
        this.waveSurfer && this.waveSurfer.destroy();
      initWaveSurfer = () => {
        const { data } = this.state;
        const { handleChangeStartEnd, src, startTime, endTime } = this.props;
        this.waveSurfer = WaveSurfer.create({
          container: this.waveForm,
          scrollParent: true,
          waveColor: '#A8DBA8',
          progressColor: '#3B8686',
          plugins: [
            MinimapPlugin.create(),
            RegionsPlugin.create({
              dragSelection: {
                slop: 5,
            TimelinePlugin.create({
              container: this.waveTimeline,
              formatTimeCallback,
              timeInterval,
              primaryLabelInterval,
              secondaryLabelInterval,
              primaryColor: 'blue',
              secondaryColor: 'red',
              primaryFontColor: 'blue',
              secondaryFontColor: 'red',
        this.waveSurfer.load(src);
        this.waveSurfer.on('ready', () => {
          this.waveSurfer.zoom(data.get('zoom'));
          if (data.get('playing')) {
            this.waveSurfer.play();
          startTime !== undefined && endTime !== undefined && this.addWaveFormRegion(startTime, endTime);
        this.waveSurfer.on('region-update-end', (e) => {
          const { start, end } = e;
          handleChangeStartEnd && handleChangeStartEnd(start, end);
        this.waveSurfer.on('region-created', () => {
          this.waveSurfer.clearRegions();
        // this.waveSurfer.on('region-removed', () => {
        //   handleChangeStartEnd && handleChangeStartEnd(undefined, undefined);
        // });
        this.waveSurfer.on('pause', () => {
          this.setImmState((dat) => dat.set('playing', false));
        this.waveSurfer.on('play', () => {
          this.setImmState((dat) => dat.set('playing', true));
        this.waveSurfer.on('zoom', (value) => {
          this.setImmState((dat) => dat.set('zoom', value));
        this.waveSurfer.on('region-click', (region, e) => {
          e.stopPropagation();
          if (e.shiftKey) {
            region.remove();
            handleChangeStartEnd && handleChangeStartEnd(undefined, undefined);
          } else {
            region.play();
      addWaveFormRegion = (start, end) => {
        this.waveSurfer.addRegion({
          start,
          color: 'hsla(400, 100%, 30%, 0.5)',
      handleWaveFormZoom = (value) => {
        let tmp = value;
        if (value < 0) {
          tmp = 0;
        if (value > 1000) {
          tmp = 1000;
        this.waveSurfer.zoom(tmp);
      handleWaveFormPlayOrPause = () => {
        const { data } = this.state;
        const { startTime, endTime } = this.props;
        if (data.get('playing')) {
          this.waveSurfer.pause();
        } else {
          this.waveSurfer.play(startTime, endTime);
      render() {
        const { data } = this.state;
        return (
          <div className={styles.container}>
            <div ref={(r) => { this.waveForm = r; }} className={styles.waveForm} />
            <div ref={(r) => { this.waveTimeline = r; }} />
            <div className={styles.control}>
              <div className={styles.zoomContainer}>
                <Icon type="minus-circle-o" onClick={() => this.handleWaveFormZoom((data.get('zoom') - 100))} />
                <Slider tipFormatter={(value) => `${value}%`} max={1000} onChange={(value) => this.handleWaveFormZoom(value)} value={data.get('zoom')} />
                <Icon type="plus-circle-o" onClick={() => this.handleWaveFormZoom((data.get('zoom') + 100))} />
              </div>
              <Button className={styles.playBtn} type="primary" onClick={this.handleWaveFormPlayOrPause}>
                  data.get('playing') ? '暂停' : '播放'
              </Button>
            </div>
          </div>
    WaveFormDivider.propTypes = {
      src: React.PropTypes.string,
      handleChangeStartEnd: React.PropTypes.func,
      startTime: React.PropTypes.number,
      endTime: React.PropTypes.number,
    export default WaveFormDivider;
    

    WaveFormDivider/styles.css

    @import "styles/vars.css";
    .control {
      text-align: center;
    .zoomContainer {
      position: relative;
      padding: 0 30px;
      :global {
        .anticon {
          position: absolute;
          top: -2px;
          width: 16px;
          height: 16px;
          line-height: 1;
          font-size: 16px;
          color: rgba(0, 0, 0, 0.25);
          cursor: pointer;
          &:hover {
            color: var(--color-primary);
        .anticon:first-child {
          left: 0;
        .anticon:last-child {
          right: 0;
    

    类似实现效果

    keller
    私信