美文网首页
微信H5调用原生摄像头录像

微信H5调用原生摄像头录像

作者: 回到唐朝做IT | 来源:发表于2023-02-28 11:17 被阅读0次

使用H5调用原生 getUserMedia方法获取摄像头权限进行录制视频,并且获取录制后的视频文件向后端进行存储,包含暂停录制、重新录制、录制时提示时间等功能

image.png
录制视频组件 VideoRecorder.vue
<template>
  <div class='VideoRecorder'>
    <video ref="video" :controls="false" width="340px" muted height="auto" class="my-video" :webkit-playsinline="true" :playsinline="true"></video>
  </div>
</template>

<script>
import moment from 'moment'
var stopRecordCallback;
export default {
  name: 'VideoRecorder',
  data () {
    return {
      mediaRecorder:null,
      mediaStream:null,
      recorderFile:null,
      chunks:[],
    }
  },
  mounted () {
    this.openCamera()  //摄像头初始化
  },
  destroyed(){
    this.closeBtn()
  },
  methods: { 
    //开始录制
    start(){
      this.mediaRecorder.start();
    },
   //停止录制
    stop(){
      this.stopRecord(()=> {
        this.$Dialog.alert({
          message: '录制成功!',
          confirmButtonColor: '#fda21d'
        }).then(() => {
          this.send();
        })
      });
    },
  //重新录制
    reStart(){
      this.mediaRecorder.start();
    },
    // 停止录制
    stopRecord(callback) {
      stopRecordCallback = callback;
      // 终止录制器
      this.mediaRecorder.stop();
      // 关闭媒体流

    },
    closeBtn(){
      this.closeStream(this.mediaStream);
    },

    openCamera() {
      var constraints = { 
        audio: true,
        video: { 
          // width: { min: 1024, ideal: 1280, max: 1920 },
          // height: { min: 776, ideal: 720, max: 1080 },
          deviceId: "default",
          facingMode: "user" //调用前置摄像头
        } 
      };
      this.getUserMedia(constraints,(err, stream)=> {
        if(err) {
          this.$emit('error',err)
          throw err;
        } else {
          // 通过 MediaRecorder 记录获取到的媒体流
          console.log();
          const video = this.$refs.video;
          this.mediaRecorder = new MediaRecorder(stream);
          this.mediaStream = stream;
          this.chunks = [],
          video.srcObject = stream;
          video.play();

          this.mediaRecorder.ondataavailable = (e)=> {
            this.mediaRecorder.blobs.push(e.data);
            this.chunks.push(e.data);
          };
          this.mediaRecorder.blobs = [];

          this.mediaRecorder.onstop = (e)=> {
            this.recorderFile = new Blob(this.chunks, {
              'type': this.mediaRecorder.mimeType
            });
            this.chunks = [];
            if(null != stopRecordCallback) {
              stopRecordCallback();
            }
          };
        }
      });
    },

    /**
     * 获取用户媒体设备(处理兼容的问题)
     * @param videoEnable {boolean} - 是否启用摄像头
     * @param audioEnable {boolean} - 是否启用麦克风
     * @param callback {Function} - 处理回调
     */
     getUserMedia(constraints,callback) {
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
        navigator.msGetUserMedia || window.getUserMedia;
      if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
          callback(false, stream);
        })['catch'](function(err) {
          callback(err);
        });
      } else if(navigator.getUserMedia) {
        navigator.getUserMedia(constraints, function(stream) {
          callback(false, stream);
        }, function(err) {
          callback(err);
        });
      } else {
        callback(new Error('Not support userMedia'));
      }
    },

    /**
     * 关闭媒体流
     * @param stream {MediaStream} - 需要关闭的流
     */
    closeStream(stream) {
      if(!stream) return;
      if(typeof stream.stop === 'function') {
        stream.stop();
      } else {
        let trackList = [stream.getAudioTracks(), stream.getVideoTracks()];

        for(let i = 0; i < trackList.length; i++) {
          let tracks = trackList[i];
          if(tracks && tracks.length > 0) {
            for(let j = 0; j < tracks.length; j++) {
              let track = tracks[j];
              if(typeof track.stop === 'function') {
                track.stop();
              }
            }
          }
        }
      }
    },

    saver() {
      var file = new File([this.recorderFile], 'msr-' + (new Date).toISOString().replace(/:|\./g, '-') + '.mp4', {
        type: 'video/mp4'
      });
      FileSaver.saveAs(file);
    },

     send() {
      //格式: subject-张大宝-宣导页视频-20221214172357.mp4
      let time =moment().format("YYYYMMDDHHmmss");
      let localUrl=this.blobToUrl(this.recorderFile)
      let fileName=`subject-${this.subjectId}-宣导页视频-${time}.mp4`  // 可自定义文件名称
      var file = new File([this.recorderFile],fileName , {
        type: 'video/mp4'
      });
      var formData = new FormData();
      formData.append("fileName", fileName);
      formData.append("file", file);
      console.log('===176===录制文件', formData)
      const recorder = {
        formData: formData,
        localUrl: localUrl
      }
      this.$emit('success', recorder)
    },
    // blob 转化url
    blobToUrl(blob){
      let url = window.URL.createObjectURL(blob)
      return url
    }

  }
}
</script>

<style lang="scss" scoped>

  .VideoRecorder{
    position: relative;
    .my-video{
      position: relative;
      left: -44px;
    }
    .my-video::-webkit-media-controls-enclosure{ 
        display: none;
    }
  }

</style>
使用组件 recordVideo.vue
  <template>
  <div class='recordVideo'>
    <div class="content">
      {{videoContent}}
    </div>
    <div class="video-container">
      <video v-if="videoSrc" ref="myVideo" class="my-video" :controls="false" :src="videoSrc" autoplay loop :webkit-playsinline="true" :playsinline="true" x5-video-player-type="h5">
      </video>
      <VideoRecorder v-else ref='videoRecorder' @success='handlerSuccess' @error='handlerError'></VideoRecorder>
    </div>
    <div v-if="!videoSrc" class="record-time">
      <img v-if="isStart" src="@/assets/images/ing.gif"  alt=""> 
      <span >{{digitFormat(recrodTime)}}</span>
    </div>
    <div class="video-action" v-if="isCanUse">
      <div class="start" v-if="!videoSrc" @touchstart.prevent="touchstart"
      @touchend="touchend">长按录制</div>
      <span class="starting" v-if="isStart"></span>
      <div class="reStart" v-if="videoSrc" @click="handleReStart">重新录制</div>
      <div class="complate" v-if="videoSrc" @click="handleComplate">完成</div>
    </div>
    <div style="text-align: center;" v-else> 当前手机系统版本不支持摄像录制</div>
    <div class="tips">使用适中的语速,逐字清晰地朗读顶部文字</div>
  </div>
</template>

<script>
import VideoRecorder from '@/components/VideoRecorder'
import {fileuploadext, processfilesave } from '@/api/user.js'
export default {
  name: 'recordVideo',
  components: { 
    VideoRecorder
  },
  data () {
    return {
      recorder:null,
      videoSrc:'',
      // videoSrc:'https://oceanus-trial-file-dev.obs.cn-east-3.myhuaweicloud.com/20221221/b746d257a9644ff7b702e243f2cdd467.mp4',
      recrodTime:0,  
      intervalTime:null,
      isStart:false,
      // ---
      videoContent:'',  //录制所读内容
      formData:null, //录制文件
      isCanUse:true,  //录制组件是否可用
    }
  },
  created () {
    this.videoContent=this.$route.query.videoContent
  },
  mounted () {

  },
  destroyed(){
    this.clearIntervaltime()
  },
  methods: { 
    // 开始录制
    touchstart(){
      console.log('===53===开始')
      this.isStart=true
      this.$nextTick(()=>{
        this.startRecordReadTime() //读秒计时
        this.$refs.videoRecorder&&this.$refs.videoRecorder.start()
      })
    },
    // 暂停录制
    touchend(){
      console.log('===56===结束' )
      this.$refs.videoRecorder.stop()
      this.isStart=false
    },
    // 重新录制
    handleReStart(){
      this.clearIntervaltime()
      this.videoSrc='';
    },
    // 完成
    handleComplate(){
      // 上传文件获取文件id   自己业务逻辑
      fileuploadext(this.formData).then(({success, data}) => {
        if(success){
          //...
        }
      })
    },
    handlerSuccess(val){
      console.log('文件返回信息=============', val)
      this.formData=val.formData
      this.videoSrc=val.localUrl
      this.$nextTick(()=>{
        this.$refs.myVideo&&this.$refs.myVideo.play()
      })
    },
    handlerError(val){
      console.log('失败===70===', val)
      this.isCanUse=false
    },
    // 清除定时器
    clearIntervaltime() {
      this.recrodTime=0
      if (this.intervalTime) {
        clearInterval(this.intervalTime)
      }
    },
    // 录制时间
    startRecordReadTime() {
      this.intervalTime = setInterval(() => {
        if(this.isStart){
          this.recrodTime+=1
        }
      }, 1000)
    },
    digitFormat(time) {
      if (!time) {
        return '00:00'
      }
      var minute = Math.floor(time / 60)
      var second = time % 60
      var hour = Math.floor(minute / 60)
      minute = minute % 60
      if (hour < 10) {
        hour = '0' + hour
      }
      if (minute < 10) {
        minute = '0' + minute
      }
      if (second < 10) {
        second = '0' + second
      }
      return  minute + ':' + second
    }

  }
}
</script>

<style lang="scss" scoped>
  .recordVideo{
    .content{
      box-sizing: border-box;
      padding:24px 21px;
      width: 343px;
      border-radius: 8px;
      background: #fff;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.06);
      font-size: 20px;
      line-height: 30px;
      text-align: left;
      color: #666;
      margin:0 auto;
      margin-top:30px;
    }
    .video-container{
      width: 256px;
      height: 256px;
      background-color: #999999;
      border-radius: 100% !important;
      margin:0 auto;
      text-align: center;
      margin-top: 54px;
      overflow: hidden !important;
      -webkit-backface-visibility: hidden;  //兼容ios
      -webkit-transform: translate3d(0, 0, 0);
      position: relative;
      .my-video{
        position:absolute;
        left:0;
        top:0;
        width:100%;
        height:100%;
        align-items:center;
        // object-fit: fill; //视频铺满  仅android可以
        object-fit: cover;  //视频铺满 android ios 都可以
      }
      /* 隐藏video 所有控件 */
      .show-video::-webkit-media-controls-enclosure{ 
          display: none !important;
      }
    }
    .record-time{
      text-align: center;
      margin-top:20px;
      color:#333;
      img{
        width:10px;
        height:10px;
        margin-right:5px;
      }
    }
    .video-action{
      margin-top:70px;
      display: flex;
      position: relative;
      div{
        width: 166px;
        height: 40px;
        line-height:40px;
        text-align: center;
        border-radius: 20px;
        font-size: 14px;
        margin:0 auto;
      }
      .start{
        color: #fff;
        background: #fda21d;
        border: 1px solid #fda21d;
        z-index: 99;
      }
      .starting{
        position: absolute;
        width: 180px;
        height: 56px;
        border-radius: 28px;
        left:0;
        right:0;
        top:-7px;
        margin:0 auto;
        animation: breating 1.5s linear infinite;
      }
      @keyframes breating {
          0% {
            transform: scale(1.0);
            background: #fecaa7;
          }
          50% {
            transform: scale(1.1);
            background: #ffe4d0;
          }
          100% {
            background: #fff6ef;
            transform: scale(1.2)
          }
      }
      .stop{
        color:#666;
        background: #fff;
        border: 1px solid #dcdee2;
      }
      .reStart{
        color:#666;
        background: #fff;
        border: 1px solid #dcdee2;
      }
      .complate{
        color: #fff;
        background: #fda21d;
        border: 1px solid #fda21d;
      }
    }
    .tips{
      font-size: 14px;
      line-height: 20px;
      text-align: center;
      color: #4284f5;
      margin-top:24px;
    }
  }
</style>

相关文章

网友评论

      本文标题:微信H5调用原生摄像头录像

      本文链接:https://www.haomeiwen.com/subject/sjhlldtx.html