vue + record.js 实现音频录制/播放功能(H5,Web)

3,140 阅读1分钟

简介:录音功能是H5表单中的一个功能,该H5会通过webview嵌入到小程序外壳中使用。按照通用性要求,不能使用类似wx-jssdk等JSBridge库,需要使用H5原生开发。
技术选择:vue vant-UI Record.js
效果图如下所示
image.png

image.png

image.png

image.png

核心代码展示:需要使用loadsh和js-audio-recorder(自己百度安装吧)

样式中引用了一个flexbox() 这是我自己封装的..先展示一下吧 很好理解吧 哈哈哈哈

.flexbox(@fd: row, @jc: center, @ai: center) {
  display: flex;
  flex-direction: @fd;
  justify-content: @jc;
  align-items: @ai;
}

组件:voice (index.vue) 直接引用组件就行,无需其他代码。 由于代码还没有整理好,有几个样式直接写到标签里了...等洒家整理好了再更新

<template>
  <div class="container">
      <div v-for="item in voiceList" :key="item.stream.id" class="voice-list">
        <div @click="play(item)">播放{{ item.duration.toFixed(0)}} s</div>
        <div style="width: 50%; margin-left: 10px">
          <van-progress :percentage="item.percentage" stroke-width="20" :show-pivot="false" class="progress" :id="item.stream.id + progressId"/>
        </div>
        <span @click="delVoice(item)" style="margin-left: 10px">删除</span>
      </div>
    <div @touchstart="start" @touchend="end" class="press" v-show="canInputVoice">{{ title }}</div>
  <!-- 支持绘制波浪图  -->
  <!--    <canvas  id="canvas"></canvas>-->
    <van-dialog v-model="show"  cancelButtonText :overlay="false" :showConfirmButton="false" style="width: 150px;height: 150px">
      <div style="background-color: rgba(0, 0, 0, 0.7);width: 150px;height: 150px;display: flex;align-items: center;justify-content: center">
        <van-loading size="70px" vertical>正在录音...</van-loading>
      </div>
    </van-dialog>
  </div>
</template>
<script>
import Recorder from 'js-audio-recorder'
import _ from 'loadsh'
export default {
  data () {
    return {
      ctx: null,
      oCanvas: null,
      // 波浪图-录音
      drawRecordId: null,
      title: '长按录音',
      state: {
        normal: '长按录音',
        during: 0
      },
      progressId: 0,
      time: 1,
      timer: {},
      voiceList: [], // 录音列表
      recorder: {}, // 音频对象
      currenPlayTimer: null,
      show: false
    }
  },
  watch: {
    // 如果大于60s 停止录音
    time (val) {
      if (val > 60) {
        this.end()
      }
    }
  },
  computed: {
   // 控制最多录制三个
    canInputVoice () {
      return this.voiceList.length <= 2
    }
  },
  mounted () {
    this.recorder = new Recorder()
    Recorder.getPermission().then(() => {
    }, (error) => {
      this.$message({
        message: '请先允许该网页使用麦克风',
        type: 'info'
      })
      console.log(`${error.name} : ${error.message}`)
    })
    // this.startCanvas()
  },
  methods: {
    startCanvas () {
      // 录音波浪
      this.oCanvas = document.getElementById('canvas')
      this.ctx = this.oCanvas.getContext('2d')
    },
    /**
     * 绘制波浪图-录音
     * */
    drawRecord () {
      // 用requestAnimationFrame稳定60fps绘制
      this.drawRecordId = requestAnimationFrame(this.drawRecord)

      // 实时获取音频大小数据
      const dataArray = this.recorder.getRecordAnalyseData()
      const bufferLength = dataArray.length

      // 填充背景色
      this.ctx.fillStyle = 'rgb(200, 200, 200)'
      this.ctx.fillRect(0, 0, this.oCanvas.width, this.oCanvas.height)

      // 设定波形绘制颜色
      this.ctx.lineWidth = 2
      this.ctx.strokeStyle = 'rgb(0, 0, 0)'

      this.ctx.beginPath()

      var sliceWidth = this.oCanvas.width * 1.0 / bufferLength // 一个点占多少位置,共有bufferLength个点要绘制
      var x = 0 // 绘制点的x轴位置

      for (var i = 0; i < bufferLength; i++) {
        var v = dataArray[i] / 128.0
        var y = v * this.oCanvas.height / 2

        if (i === 0) {
          // 第一个点
          this.ctx.moveTo(x, y)
        } else {
          // 剩余的点
          this.ctx.lineTo(x, y)
        }
        // 依次平移,绘制所有点
        x += sliceWidth
      }

      this.ctx.lineTo(this.oCanvas.width, this.oCanvas.height / 2)
      this.ctx.stroke()
    },
    // 播放录音
    // 播放录音支持快速点击重复播放同一个录音,支持切换播放
    play (recorder) {
      clearInterval(this.currenPlayTimer)
      for (const item of this.voiceList) {
        item.stopPlay()
        item.percentage = 0
        this.progressId++
      }
      // getCurrentPlayTime()是个大坑,不能实时获取
      // 这里用到的两个0.1s延时必要的。否则切换语音播放时计算进度条动画会有问题
      setTimeout(() => {
        const r = _.cloneDeep(recorder)
        recorder.play() // 播放录音
        // 播放时长
        this.currenPlayTimer = setInterval(() => {
          try {
            const num = r.getPlayTime().toFixed(2) / r.duration.toFixed(2) * 100
            if (num < 100) {
              recorder.percentage = num
            } else {
              recorder.percentage = 100
            }
            if (r.getPlayTime().toFixed(1) === r.duration.toFixed(1)) {
              clearInterval(this.currenPlayTimer)
            }
          } catch (error) {
            this.currenPlayTimer = null
          }
          this.progressId++
        }, 100)
      }, 100)
    },
    // 开始录音
    start () {
      this.recorder = new Recorder()
      this.recorder.start() // 开始录音
      // this.drawRecord()
      this.timer = setInterval(() => {
        this.show = true
        this.state.during = this.time++
        this.title = this.state.during
      }, 1000)
    },
    // 停止录音
    end () {
      clearInterval(this.timer)
      this.recorder.stop() // 停止录音
      this.show = false
      // this.drawRecordId && cancelAnimationFrame(this.drawRecordId)
      // this.drawRecordId = null
      // 如果录音小于1.5秒就提示录音过短,toFixed是四舍五入所以是小于1.5秒
      if (this.recorder.duration.toFixed(0) < 1) {
        this.$toast({
          msg: '时常过短,请重新录制',
          type: 'fail'
        })
        this.recorder.destroy()
        setTimeout(() => {
          this.time = 1
          this.title = this.state.normal
        }, 100)
        return
      }
      this.recorder.percentage = 0
      this.voiceList.push(this.recorder)
      this.recorder = new Recorder()
      setTimeout(() => {
        this.time = 1
        this.title = this.state.normal
      }, 100)
    },
    // 删除录音
    delVoice (item) {
      item.stopPlay()
      this.voiceList = _.remove(this.voiceList, (i) => {
        return i.stream.id !== item.stream.id
      })
    },
    // 上传录音
    getVoiceList () {
      const urlList = []
      for (const item of this.voiceList) {
        console.log(item.duration.toFixed(0))
        const blob = item.getWAVBlob()
        const newbolb = new Blob([blob], { type: 'audio/wav' })
        const fileOfBlob = new File([newbolb], new Date().getTime() + '.wav')
        fileOfBlob.duration = parseInt(item.duration.toFixed(0))
        urlList.push(fileOfBlob)
      }
      return urlList
    },
    // 清除录音
    clearAll () {
      this.voiceList = []
    }
  }
}
</script>
<style scoped lang="less">
@import '../../../assets/css/mixins';
.container {
  width: 100%;
  .flexbox(@fd: column)
}
.press {
  .clearFontPressAction;
  .flexbox();
  width: 80%;
  height: 40px;
  background-color: #39b54a;
  border-radius: 10px;
  color: white;
}
.voice-list {
  .flexbox();
  width: 100%;
  margin-bottom: 15px;
}
.progress {
  width: 100%;
  .flexbox();
}
</style>