手动撸一个移动端音频播放组件

手动撸一个移动端音频播放组件

1.public/css/variable.scss

// 颜色定义规范
$color-background: #F2F3F4;
$color-background-d: rgba(0, 0, 0, 0.3);
$color-highlight-background: rgb(253, 108, 98);
$color-dialog-background: rgb(204, 204, 204);
$color-theme: rgb(212, 68, 57);
$color-theme-l: rgba(185, 9, 1, 0.2);
$color-theme-g: rgb(219, 219, 219);
$color-theme-d: rgba(19, 19, 19, 0.6);
$color-sub-theme: rgb(240, 116, 107);
$color-text: #2E3030;
$color-text-g: #757575; 
$color-text-ggg: #c7c7c7; 
$color-text-gg: rgb(219, 219, 219); 
$color-text-l: rgb(241, 241, 241);
$color-text-lm: rgb(228, 228, 228);
$color-text-ll: rgba(255, 255, 255, 0.8);

//字体定义规范
$font-size-small-ss: 9px;
$font-size-small-s: 10px;
$font-size-small: 11px;
$font-size-small-x: 12px;
$font-size-medium: 14px;
$font-size-medium-x: 16px;
$font-size-large-s: 17px;
$font-size-large: 18px;
$font-size-large-x: 22px;

2.src/commonJS/dom.js

export function addClass (el, className) {
  if (hasClass(el, className)) {
    return
  }
  let newClass = el.className.split(' ')
  newClass.push(className)
  el.className = newClass.join(' ')
}

export function hasClass (el, className) {
  let reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
  return reg.test(el.className)
}

export function getData (el, name, val) {
  const prefix = 'data-'
  name = prefix + name
  if (val) {
    // 如果有 val 就添加这个 val 到 dom 中
    // name="val"
    return el.setAttribute(name, val)
  } else {
    // 没有 val ,就获取 dom 中的 name
    return el.getAttribute(name)
  }
}

// 能力检测
let elementStyle = document.createElement('div').style

let vendor = (() => {
  // 定义游览器前缀
  let transformNames = {
    webkit: 'webkitTransform',
    Moz: 'MozTransform',
    O: 'OTransform',
    ms: 'msTransform',
    standard: 'transform'
  }

  // 遍历前缀,如果游览器支持的话,就返回对应 key
  for (let key in transformNames) {
    if (elementStyle[transformNames[key]] !== undefined) {
      return key
    }
  }

  // 如果都不支持,那肯定是有问题的,返回 false
  return false
})()

export function prefixStyle (style) {
  if (vendor === false) {
    return false
  }
  // 如果 vendor 为标准,就不改变 style
  if (vendor === 'standard') {
    return style
  }

  // 否则返回 vender(也就是 webkit Moz O ms 中的一个) + 样式首字母大写
  // 例如:webkit + transform ---> webkitTransform
  return vendor + style.charAt(0).toUpperCase() + style.substr(1)
}

3.components/common/progress-bar.vue

<template>
  <div class="progress-bar" ref="progressBar" @click="progressClick">
    <div class="bar-inner">
      <div class="progress" ref="progress"></div>
      <div class="progress-btn-wrapper" ref="progressBtn"
      @touchstart.prevent="progressTouchStart"
      @touchmove.prevent="progressTouchMove"
      @touchend.prevent="progressTouchEnd">
        <div class="progress-btn">
          <div class="inner-play-btn"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import {prefixStyle} from '../../commonJS/dom'
const progressBtnWidth = 16
const transform = prefixStyle('transform')

export default {
  data () {
    return {
      newPercent: 0
    }
  },
  props: {
    percent: {
      type: Number,
      default: 0
    }
  },
  created () {
    this.touch = {}
  },
  methods: {
    progressClick (e) {
      // this._offset(e.offsetX)
      const rect = this.$refs.progressBar.getBoundingClientRect()
      // rect.left 元素距离左边的距离
      // e.pageX 点击距离左边的距离
      const offsetWidth = e.pageX - rect.left
      // console.log(rect, e.pageX)
      this._offset(offsetWidth)
      const barWidth = this.$refs.progressBar.clientWidth - progressBtnWidth
      const percent = this.$refs.progress.clientWidth / barWidth
      this.$emit('percentChangeEnd', percent)
    },
    progressTouchStart (e) {
      this.touch.initiated = true
      this.touch.startX = e.touches[0].pageX
      this.touch.left = this.$refs.progress.clientWidth
    },
    progressTouchMove (e) {
      if (!this.touch.initiated) {
        return
      }
      this._triggerPercent()
      const deltaX = e.touches[0].pageX - this.touch.startX
      const offsetWidth = Math.min(Math.min(this.$refs.progressBar.clientWidth - progressBtnWidth, Math.max(0, this.touch.left + deltaX)))
      this._offset(offsetWidth)
    },
    progressTouchEnd (e) {
      this.touch.initiated = false
      const barWidth = this.$refs.progressBar.clientWidth - progressBtnWidth
      const percent = this.$refs.progress.clientWidth / barWidth
      this.$emit('percentChangeEnd', percent)
    },
    _triggerPercent () {
      const barWidth = this.$refs.progressBar.clientWidth - progressBtnWidth
      const percent = this.$refs.progress.clientWidth / barWidth
      this.$emit('percentChange', percent)
    },
    _offset (offsetWidth) {
      this.$refs.progress.style.width = `${offsetWidth}px`
      this.$refs.progressBtn.style[transform] = `translate3d(${offsetWidth}px, 0, 0)`
    }
  },
  watch: {
    percent (newPercent) {
      if (newPercent >= 0 && !this.touch.initiated) {
        const barWidth = this.$refs.progressBar.clientWidth - progressBtnWidth
        const offsetWidth = newPercent * barWidth
        this._offset(offsetWidth)
      }
    }
  }
}
</script>

<style scoped lang="scss">
@import "../../../public/css/variable";

.progress-bar {
  height: 20px;
  .bar-inner {
    position: relative;
    top: 10px;
    height: 4px;
    background: rgba(0, 0, 0, 0.3);
    .progress {
      position: absolute;
      height: 100%;
      background: $color-theme;
    }
    .progress-btn-wrapper {
      position: absolute;
      left: -8px;
      top: -13px;
      width: 30px;
      height: 30px;
      .progress-btn {
        position: relative;
        top: 7px;
        left: 7px;
        box-sizing: border-box;
        width: 15px;
        height: 15px;
        border: 4px solid $color-theme-l;
        border-radius: 50%;
        .inner-play-btn{
          position: absolute;
          top: -1px;
          left: -1px;
          height: 9px;
          width: 9px;
          border-radius: 50%;
          background: $color-theme;
        }
      }
    }
  }
}
</style>

4.use.vue

<style lang="scss" scoped>
.evening-broadcasting{
    background: #f7f7f7;
    padding: 10px 14px;
    .broadcasting{
        background: #fff;
        padding: 11px 14px 18px 15.5px;
        .play-box{
            border-radius: 50%;
            width: 42px;
            height: 42px;
            margin-top: 15px;
            .play-button{
                width: 42px;
                height: 42px;
            }
        }
        .play-content{
            box-sizing: border-box;
            padding-left: 19px;
            width: 270px;
            overflow: hidden;
            .play-date{
                font-size: 16px;
            }
            .play-title{
                font-size: 12px;
                color: #999999;
                padding-top: 2px;
            }
            .play-control{
                width: 270px;
                #play-audio{
                    width: 250px;
                }
                .timingLength{
                    width: 250px;
                    .time-l{
                        font-size: 11px;
                        color:#666;
                    }
                    .time-r{
                        font-size: 11px;
                        color:#999;
                    }
                }
            }
        }
        
    }
}
</style>

<template>
    <div class="evening-broadcasting">
        <div class="broadcasting clearfix">
            <div class="play-box left" @click="transPlayStatus">
                <img src="../../components/common/image/播放.png" class="play-button" @touchstart="play" v-if="!isPlay">
                <img src="../../components/common/image/暂停.png" class="play-button" @touchstart="pause" v-else>
            </div>
            <div class="play-content left">
                <div class="play-date">
                    {{nowDate|fomatTime2}}
                </div>
                <div class="play-title ">
                    晚间播报
                </div>
                <div class="play-control">
                    <audio id="audio" :src="audioUrl" ref="audio" @timeupdate="updateTime">该浏览器不支持audio属性</audio>
                    <progress-bar id="play-audio" :percent="percent" @percentChangeEnd="percentChangeEnd" @percentChange="percentChange"></progress-bar>
                    <div class="clearfix timingLength">
                        <span class="time time-l left" v-if="timeLoading">{{format(currentTime)}}</span>
                        <span class="time time-l left" v-else>-:--</span>
                        <span class="time time-r right" v-if="timeLoading">{{format(duration)}}</span>
                        <span class="time time-r right" v-else>-:--</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
import ProgressBar from '../../components/common/progress-bar'
export default {
    data() {
        return {
            nowDate:this.$route.params.nowDate,
            userid:this.$route.params.userid,
            playStatus:true,
            audioUrl:'test.mp3',
            isPlay: false, // 是否播放
            width: 0, // 视频音频的总时间的长度条
            percent: 0,
            currentTime: 0,// 播放时间
            duration: 0,// 视频音频的总时长
            playing: false,
            timeLoading:true
        };
    },
    components: {
        ProgressBar,
    },
    watch: {
        currentTime () {
            this.percent = this.currentTime / this.duration
        }
    },
    methods: {
        format (interval) {
            interval = interval | 0
            let minute = interval / 60 | 0
            let second = interval % 60
            if (second < 10) {
                second = '0' + second
            }
            return minute + ':' + second
        },
        percentChange (percent) {
            this.move = true
            const currentTime = this.duration * percent
            this.currentTime = currentTime
        },
        percentChangeEnd (percent) {
            this.move = false
            const currentTime = this.duration * percent
            this.$refs.audio.currentTime = currentTime
            if (!this.playing) {
                this.$refs.audio.play()
                this.playing=true;
            }
        },
        updateTime (e) {
            if (this.move) {
                return
            }
            this.currentTime = e.target.currentTime;
            if (this.audio.ended) {
                this.isPlay = false
            }
        },
        transPlayStatus(){
            this.playStatus=!this.playStatus;
        },
        // 播放
        play () {
            if(this.currentTime==0){
                var u = navigator.userAgent;
                if (!!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
                    this.audio.load()
                    this.timeLoading=false;
                    setTimeout(() => {
                        this.timeLoading=true;
                    }, 2000);
                    this.audio.play()
                    this.isPlay = true
                }
                if (u.indexOf("Android") > -1 || u.indexOf("Adr") > -1) {
                    this.audio.load()
                    this.timeLoading=false;
                    this.isPlay = true
                    setTimeout(() => {
                        this.timeLoading=true;
                        this.audio.play()
                    }, 2000);
                }
            }else{
                this.isPlay = true;
                this.audio.currentTime = this.currentTime;//重新播放
                this.audio.play();
            }
            
        },
        // 暂停
        pause () {
            this.audio.pause()
            this.isPlay = false
        },
    },
    created(){
        this.move = false;
        //加载屏关闭
        setTimeout(() => {
            document.getElementById('login').style.display = 'none';
        }, 250);
    },
    mounted(){
        this.audio = this.$refs.audio
        this.duration = 95 //后台给时长
    },
}
</script>

 

上一篇:OpenGL ES 3帧率控制


下一篇:c# npoi分批往excel追加数据