• React自定义Audio播放组件


    还是直接上代码

    /**
     * 音频播放组件
     */
    import React, { Component } from 'react';
    import { Slider, Toast } from 'antd-mobile';
    
    import icon_play from '@/asset/image/panda_book/readpage/icon_play.png';
    import icon_pause from '@/asset/image/panda_book/readpage/icon_pause.png';
    
    import './AudioItem.less';
    
    class AudioItem extends Component {
    
      static defaultProps = {
        src: '', // 音频地址
      }
    
      state = {
        isCanPlay: false, // 判断音频是否加载完成
        playStatus: false, // 播放状态, true 播放中, false 暂停中,
        duration: 0, // 音频的时长
        currentDuration: 0, // 当前的播放时长 
      }
    
      audioItem = null; // 把dom暴露给外部使用
      audio = new Audio(); // 一个音频对象
      timer = null; // 做一个滑条的防抖
      interval = null; // 定时查询播放时的当前时间
    
      // 播放音频
      play = () => {
        this.audio.play();
        this.interval = setInterval(() => {
          const time = Math.floor(this.audio.currentTime);
          if(time < this.state.duration) {
            this.setState({
              currentDuration: time
            });
          } else {
            // 播放结束后,直接重置播放时间,停止播放
            Toast.info('播放完毕');
            clearInterval(this.interval);
            this.audio.currentTime = 0;
            this.audio.pause();
            this.setState({
              currentDuration: 0,
              playStatus: false
            });
          }
        }, 1000);
      }
    
      // 暂停音频
      pause = () => {
        this.audio.pause();
        if(this.interval) {
          clearInterval(this.interval);
        }
      }
    
      // 播放状态切换
      handlePlayStatusChange = () => {
        const { playStatus, isCanPlay } = this.state;
        // 由于ios中不会预加载音频资源,所以只好去掉加载状态的判断,如果有好的建议也可以提
        // if(!isCanPlay) {
        //   Toast.info('音频还没加载完呢~');
        //   return;
        // }
        if(!playStatus) {
          this.play();
        } else {
          this.pause();
        }
        this.setState({
          playStatus: !playStatus
        });
      };
    
      handleSilderChange = (value) => {
        if(this.timer) {
          clearTimeout(this.timer);
        }
        // 0.2s之内没有改动就修改当前的时间,做一个播放的防抖
        this.timer = setTimeout(() => {
          this.pause();
          this.audio.currentTime = value;
          this.setState({
            currentDuration: value
          }, () => {
            if(this.state.playStatus) {
              this.play();
            }
          })
        }, 200);
      }
    
      // 根据秒数,返回对应的xx:xx的时间格式
      getDurationString = (number) => {
        let num = Number(number);
        if(isNaN(num) || num <= 0) {
          return '00:00';
        }
        if(num === Infinity) {
          return '00:00';
        }
        if(num < 60) {
          return `00:${num.toString().padStart(2, 0)}`;
        } else if(num < 3600) {
          const minute = Math.floor(num / 60);
          const second = num % 60;
          return `${minute.toString().padStart(2, 0)}:${second.toString().padStart(2, 0)}`
        } else {
          const hour = Math.floor(num / 3600);
          const minute = Math.floor((num - (hour * 3600)) / 60);
          const second = num - (hour * 3600) - (minute * 60);
          return `${hour.toString().padStart(2, 0)}:${minute.toString().padStart(2, 0)}:${second.toString().padStart(2, 0)}`;
        }
      }
    
      init = (props) => {
        const { src } = props || this.props;
        if(!src) {
          return;
        }
        this.audio.preload = 'automatic';
        this.audio.src = src;
        this.audio.load();
        // this.audio.src = 'http://www.yinpin.com/upload/yingxiaobusanlingfuwu0413017j.mp3';
        // 监听音频的时长是否获取到了
        this.audio.ondurationchange = () => {
          const duration = Math.floor(this.audio.duration);
          this.setState({
            duration
          });
        }
        // 监听音频是否可以播放了
        this.audio.oncanplay = () => {
          const duration = Math.floor(this.audio.duration);
          this.setState({
            duration,
            isCanPlay: true
          });
        }
      };
    
      componentDidMount() {
        this.init();
      }
    
      componentWillReceiveProps(nextProps) {
        if(nextProps.src !== this.props.src) {
          this.init(nextProps);
        }
      }
    
      componentWillUnmount() {
        if(this.timer) {
          clearTimeout(this.timer);
        }
        if(this.interval) {
          clearInterval(this.interval);
        }
        if(this.audio) {
          this.audio.currentTime = 0;
          this.audio.pause();
        }
      }
    
      render() {
        const {
          playStatus,
          duration,
          currentDuration
        } = this.state;
        const btn_img = playStatus ? icon_pause : icon_play;
        const durationStr = this.getDurationString(duration);
        const currentDurationStr = this.getDurationString(currentDuration);
        return (
          <div className="AudioItem" ref={audioItem => this.audioItem = audioItem}>
            <div className="audio-item">
              {/* 播放按钮 */}
              <div className="audio-item-btn" onClick={this.handlePlayStatusChange}>
                <img src={btn_img} alt="icon" />
              </div>
              <div className="audio-item-content">
                <div className="audio-item-top">
                  {/* 播放中的动画 */}
                  <div className={`audio-item-bars ${playStatus ? 'animate' : ''}`}>
                    <div className="audio-item-bar"></div>
                    <div className="audio-item-bar"></div>
                    <div className="audio-item-bar"></div>
                    <div className="audio-item-bar"></div>
                    <div className="audio-item-bar"></div>
                  </div>
                  {/* 播放的时长 */}
                  <div className="audio-item-range">
                    <div className="audio-item-current">{currentDurationStr}</div>
                    <div className="audio-item-duration">{durationStr}</div>
                  </div>
                </div>
                <div className="audio-item-bottom">
                  <Slider
                    trackStyle={{
                      height: '0.13rem',
                      backgroundColor: '#10C0DC',
                      borderRadius: '0.07rem 0 0 0.07rem'
                    }}
                    railStyle={{
                      height: '0.13rem',
                      backgroundColor: '#DEEAEC',
                      borderRadius: '0 0.07rem 0.07rem 0'
                    }}
                    handleStyle={{
                       '0.32rem',
                      height: '0.32rem',
                      border: 'none',
                      marginTop: '-0.1rem',
                      borderRadius: '50%',
                      backgroundColor: '#FFFFFF',
                      boxShadow: '0.08rem 0.08rem 0.21rem rgba(140, 181, 187, 0.3), -0.08rem -0.08rem 0.21rem rgba(140, 181, 187, 0.3)'
                    }}
                    style={{ marginLeft: '0.34rem', marginRight: 0 }}
                    value={currentDuration}
                    min={0}
                    max={duration}
                    onChange={this.handleSilderChange}
                  />
                </div>
              </div>
            </div>
          </div>
        );
      }
    }
    
    export default AudioItem;
    
    .AudioItem {
      display: flex;
      justify-content: center;
       100%;
    
      .audio-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
         9.09rem;
        height: 1.87rem;
        padding: 0 0.4rem;
        background-color: #F6F8FA;
        border-radius: 0.2rem;
    
        &-btn {
           0.96rem;
          height: 0.96rem;
          img {
             100%;
            height: 100%;
          }
        }
    
        &-content {
           calc(100% - 1.32rem);
        }
    
        &-top,
        &-range {
          display: flex;
          align-items: center;
          justify-content: space-between;
        }
    
        &-top {
          margin-bottom: 0.26rem;
        }
    
        &-bars {
          display: flex;
          align-items: center;
           0.48rem;
          height: 0.48rem;
          overflow: hidden;
    
          &.animate {
            .audio-item-bar {
              &:nth-child(1),
              &:nth-child(5) {
                animation: playAudio1 0.8s infinite ease-in;
              }
    
              &:nth-child(2),
              &:nth-child(4) {
                animation: playAudio2 0.8s infinite ease-in-out;
              }
    
              &:nth-child(3) {
                animation: playAudio3 0.8s infinite ease-out;
              }
            }
            @keyframes playAudio1 {
              0%, 100% {
                height: 0.2rem;
              }
              50% {
                height: 0.48rem;
              }
            }
            @keyframes playAudio2 {
              0%, 50%, 100% {
                height: 0.3rem;
              }
              25% {
                height: 0.48rem;
              }
              75% {
                height: 0.2rem;
              }
            }
            @keyframes playAudio3 {
              0%, 100% {
                height: 0.48rem;
              }
              50% {
                height: 0.2rem;
              }
            }
          }
        }
    
        &-range {
           calc(100% - 0.88rem);
        }
    
        &-bottom {
           100%;
          height: 0.13rem;
        }
    
        &-bar {
          flex-shrink: 0;
          flex-grow: 0;
           0.4rem;
          height: 0.48rem;
          transform-origin: left center;
          transform: scaleX(0.1);
          border-radius: 0.1rem;
          background-color: #10C0DC;
    
          &:nth-child(1),
          &:nth-child(5) {
            height: 0.2rem;
          }
    
          &:nth-child(2),
          &:nth-child(4) {
            height: 0.3rem;
          }
    
          &:not(:first-child) {
            margin-left: -0.3rem;
          }
        }
      }
    }
    

    遇到的问题

    • 有时候音频无法设置currentTime属性,这个是由于服务端的响应头中的cache-control有问题,改一下响应头就可以了。
    • 针对有些音频无法在audio这边获取到duration的,比如ios端好像在播放之前拿不到duration,估计是要等播放之后才能去获取,还没尝试,主要是没ios设备,不好测试。这种情况建议是后端把音频的duration直接返回过来。
    • 滑动改变进度的时候,由于用的时候阿里的UI组件Slider,实际效果不是很好,有时间的可以自己写一个。
    • 针对于需要播放的音频如果过大的,最好做好预加载资源。
    • 其实还有很多地方没有考虑到,不过如果仅仅只是简单播放就足够了,引用的第三方组件实在是不喜欢改别人的样式,自己写的话,可定制化更高。

    样式预览


  • 相关阅读:
    如何解除任务管理器被禁用
    一、JavaScript概述
    001_html基本结构
    postman常见问题记录
    fidder工具使用
    SonarQube工具使用问题汇总
    业余书籍读后感
    jmater常见问题处理
    测试知识记录(更新中)
    HTTP协议
  • 原文地址:https://www.cnblogs.com/aloneMing/p/12721741.html
Copyright © 2020-2023  润新知