vue实现相机拍摄,可录视频、拍照片、前置后置切换(简单小demo)

内容比较简单,不做过多赘述,只做分享,测试demo,功能有些缺陷,希望路过的大佬多多指正

/(*/ω\*)

<script setup>
import { showToast, showSuccessToast, showFailToast, showLoadingToast } from 'vant';
import { onBeforeMount, onMounted, reactive, ref } from 'vue'

const videoArr = ref([])
const odelSel = ref('')//当前使用的摄像头
const myInterval = ref(null)
const mediaStreamTrack = ref('') // 退出时关闭摄像头
const video_stream = ref('') // 视频stream
const recordedBlobs = ref([]) // 视频音频 blobs
const isRecord = ref(false) // 视频是否正在录制
const content = ref('按住拍摄,点击拍照')
let test = 'c'
const startStauts = ref(true)// 开始录制按钮样式
// video参数
const videoRef = ref(null);
// 画布参数(照片回显)
const cs = ref(null)
const css = ref(null)
const csss = ref(null)
const cssss = ref(null)
const csssss = ref(null)

// 回显画布宽高
const canWidth = ref('')
const canHeight = ref('')
const echo_Status = ref(false)
const canvas_echo = ref(null)

// 关闭摄像头
const closeStatus = ref(true)
// 切换按钮状态
const cutStatus = ref(false)

// 照片数量限制
const photoNum = ref([])
// 照片回显数组
const videoList = ref([])
const videoNum = ref(0)
const timeOutEvent = ref(null)
const returns = ref(false)

// 录制参数
const isRecording = ref(false)
const videoBlob = ref(null)

// 前置后置摄像头切换
const cameraStatu = ref(0)

// 预览内容
let contents = ref({});
const showCenter = ref(false)
onMounted(() => {
  let cedioele = document.getElementsByClassName('camera_video')[0]
  canWidth.value = cedioele.offsetWidth
  canHeight.value = cedioele.offsetHeight
  let canvasList = document.getElementsByClassName('canns')
})
// 前置后置切换
const changeDevice = () => {
  if(cameraStatu.value == 2){
    cameraStatu.value = 1
  }else{
    cameraStatu.value = 2
  }
  console.log(666);
  // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
  if (navigator.mediaDevices === undefined) {
    navigator.mediaDevices = {};
  }
  console.log(navigator.mediaDevices);
  navigator.mediaDevices.getUserMedia({
    video: cameraStatu.value == 1 ? { facingMode: 'user' } : { facingMode: { exact: "environment" } },
  })
    .then((stream) => {
      // 摄像头开启成功
      console.log(stream);
      startStauts.value = false
      mediaStreamTrack.value = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];
      video_stream.value = stream;
      console.log(videoRef);
      videoRef.value.srcObject = stream;
      videoRef.value.play();
      closeStatus.value = false
    })
    .catch(err => {
      console.log(err);
    });
  console.log(cameraStatu.value);
}
// ------------------------------摄像头开关按钮-------------------------------
// 开启摄像头事件
const getCamera = () => {

  cameraStatu.value = 1
  console.log(cameraStatu.value);
  navigator.mediaDevices
    .getUserMedia({
      audio: true,
      video: { facingMode: { exact: "environment" } },
    })
    .then((stream) => {
      // 摄像头开启成功
      startStauts.value = false
      mediaStreamTrack.value = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];
      video_stream.value = stream;
      console.log(videoRef);
      videoRef.value.srcObject = stream;
      videoRef.value.play();

    })
    .catch((err) => {
      console.log(err);
    });
  console.log(cameraStatu.value);
}
// 关闭摄像头
const closeCamera = () => {
  if (!videoRef.value.srcObject) return;
  let stream = videoRef.value.srcObject;
  let tracks = stream.getTracks();
  tracks.forEach(track => {
    track.stop();
  });
  videoRef.value.srcObject = null;
  startStauts.value = true
  closeStatus.value = true;
}
// ==========================点击拍照============================
const shoot = () => {
  if (startStauts.value == true) {
    showFailToast('请打开摄像头');
  } else {
    if (videoList.value.length < 5) {
      videoList.value.push({
        val: videoRef.value,
        id: videoNum.value++,
        type: 1
      })
      console.log(videoList.value);
      test = test += 's'
      photoNum.value.push(test)
      console.log(photoNum.value);
      showLoadingToast({
        message: '处理中...',
        forbidClick: true,
      });
      setTimeout(() => {
        console.log('拍单张');
        console.log(videoList.value.length);
        console.log(cs.value);
        let sc = cs.value[videoList.value.length - 1].getContext('2d');
        sc.drawImage(videoList.value[videoList.value.length - 1].val, 0, 0, 50, 50);
      }, 700)
    } else {
      showFailToast('照片已达上限');
    }
  }
}
// 检测点击按钮事件定时器
let times = null;
function echo_btn(index) {
  console.log(index);
  console.log(videoList.value);
  if (videoList.value[index].type == 1) {
    echo_Status.value = true
    closeStatus.value = true
    cutStatus.value = true
    startStauts.value = false
    const image = cs.value[index].toDataURL("image/png");
    contents = {
      index: videoNum.value++,
      type: 1,
      url: image
    }
    showCenter.value = true
    console.log(image);
  } else {
    echo_Status.value = true
    closeStatus.value = true
    cutStatus.value = true
    startStauts.value = false
    contents = {
      index: videoNum.value++,
      type: 2,
      url: videoList.value[index].val
    }
    showCenter.value = true
  }
}
// 用户是否长按 false 点击、ture 长按
let userStatus = ref(false);
const currentNum = ref(0)
// 手指点击触发
const photosStart = () => {
  console.log(videoList.value.length);
  if (startStauts.value == true) {
    showFailToast('请打开摄像头');
  } else {
    // 判断是否超过5个
    if (videoList.value.length > 5) {
      showFailToast("最多录制5个文件!");
      return;
    }
    times = setTimeout(() => {
      userStatus.value = true;
      pressEvenet();
    }, 500);
  }

};
// 长按录像
let mediaRecorder = null;
let inte = null;
let eouts = null;
let stops = false;
const pressEvenet = () => {
  console.log(videoList.value);
  if (videoList.value.length < 5) {
    currentNum.value = 0;
    let chunks = [];
    stops = true;
    mediaRecorder = new MediaRecorder(video_stream.value, {
      mimeType: "video/webm;codecs=vp9",
    });
    mediaRecorder.ondataavailable = (event) => {
      if (event.data && event.data.size > 0) {
        chunks.push(event.data);
      }
    };
    mediaRecorder.onstop = () => {
      const blob = new Blob(chunks, { type: "video/webm" });
      const url = URL.createObjectURL(blob);
      console.log(url);
      videoList.value.push({
        val: url,
        id: videoNum.value++,
        type: 2
      })
      chunks = [];
    };
    mediaRecorder.start();
    inte = setInterval(() => {
      currentNum.value++;
      if (currentNum.value >= 100) {
        showDeleteButton()
      }
    }, 100);
    eouts = setTimeout(() => {
      clearInterval(inte);
      showSuccessToast("录制完成");
      mediaRecorder.stop();
      stops = false;
    }, 10000);
  } else {
    showFailToast("最多录制5个文件!");
  }

};
const showDeleteButton = () => {
  currentNum.value = 0
  // 判断是否超过5个
  if (videoList.value.length > 5) {
    showFailToast("最多录制5个文件!");
    return;
  }
  clearTimeout(times);
  pressStop();
}

// 停止录制事件
const pressStop = () => {
  if (stops) {
    mediaRecorder.stop();
    showSuccessToast("录制完成");
    stops = false;
    clearInterval(inte);
    clearTimeout(eouts);
  }
};

function close(index) {
  console.log(666);
  videoList.value.splice(index, 1)
}

</script>

<template>
  <div class="camera_box">
    <!-- 成像区域 -->
    <div class="image_box">
      <!-- <vue-camera ref="camera"></vue-camera> -->

      <video ref="videoRef" autoplay width="100%" height="100%" class="camera_video"></video>
      <!-- <canvas class="canvas_echo" ref="canvas_echo" :width="canWidth" :height="canHeight" v-show="echo_Status"></canvas> -->

      <van-button type="success" class="start_live" v-show="startStauts" @click="getCamera">开启摄像头</van-button>
      <van-button type="success" class="end_live" @click="closeCamera" v-show="!closeStatus">关闭摄像头</van-button>
      <!-- 顶部装饰按钮 -->
      <div class="btn_box">
        <div><van-button round size="mini" color="#000" class="back_btn"><van-icon name="arrow-left" /></van-button>
        </div>
        <div><van-button type="success" size="mini" class="success_btn">完成</van-button></div>
      </div>
    </div>
    <!-- 图片预览列表 -->
    <div class="image_list">
      <!-- 加按钮 -->
      <div class="add_btn">
        <img src="../src/img/add.png" alt="">
      </div>
      <!-- 列表 -->
      <div class="list">
        <!-- v-for="(index,item) of photoNum" :key="index" -->
        <div class="list_item" @click.stop.prevent="echo_btn(index)" v-for="(item, index) in videoList">
          <canvas class="canns" ref="cs" width="50" height="50"></canvas>
          <div class="clack" v-if="item.type == 2">
            <img src="../src/img/vedio.png" alt="">
          </div>
          <div class="close_btn">
            <img src="../src//img/close.png" alt="" @click.stop.prevent="close(index)">
          </div>
        </div>
      </div>
      <!-- 减按钮 -->
      <div class="add_btn">
        <img src="../src/img/clear.png" alt="">
      </div>
    </div>

    <!-- 拍摄操作区域 -->
    <div class="btn_boxs">
      <!-- 图片按钮 -->
      <input type="file" id="file">
      <label for="file" class="image_go">
        <div><van-icon name="photo-o" size="24px" color="#FA9923" /></div>
      </label>
      <!-- 拍摄按钮 -->
      <div class="pach_box" @click.stop.prevent="shoot" @touchstart="photosStart" @touchend="showDeleteButton">
        <van-button plain class="pach_btn">
          <img class="pach_img" src="../src/img/live.png" alt="">
        </van-button>
      </div>
      <!-- 前置后置切换 -->
      <div class="camera_go" @click='changeDevice'><van-icon name="photograph" size="24px" color="#FA9923" /></div>
      <div class="pach_boxs">
        <van-circle v-model:current-rate="currentRate" :rate="currentNum" :speed="100" :text="text" size="80px"
          color="#FA9923" stroke-width="100" />
      </div>
    </div>
    <div class="message"><b>{{ content }}</b></div>
    <van-popup v-model:show="showCenter">
      <img :src="contents.url" alt="" v-if="contents.type == 1" :style="{ width: '70vw', height: 'auto' }">
      <video :src="contents.url" v-if="contents.type == 2" controls autoplay
        :style="{ width: '70vw', height: 'auto' }"></video>
    </van-popup>
  </div>
</template>

<style scoped>
.close_btn {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 10;
  width: 0;
  height: 0;
  border-top: 16px solid #EEEEEE;
  border-left: 16px solid transparent;
  display: flex;
  justify-content: center;
  align-items: center;
}

.close_btn>img {
  width: 8px;
  height: 8px;
  position: absolute;
  top: -13px;
  right: 1px;
}

.clack {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

.clack>img {
  width: 100%;
  height: 100%;
}

.canvas_echo {
  position: absolute;
  top: 0;
  left: 0;
}

.camera_box {
  width: calc(100vw);
  height: 100vh;
  padding: 10px;
}

.success_btn {
  padding: 10px;
}

.start_live {
  padding: 10px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 5;
}

.end_live {
  padding: 10px;
  position: absolute;
  bottom: 0;
  right: 0;
  transform: translate(-50%, -50%);
  z-index: 5;
}

.cut_live {
  padding: 10px;
  position: absolute;
  bottom: 0;
  left: 0;
  z-index: 5;
}

.image_box {
  width: 100%;
  height: 60vh;
  position: relative;
  border-radius: 0 0 10px 10px;
  border: 1px solid #EEEEEE;
}

.live_window {
  width: 100%;

}

.back_btn {
  width: 20px;
  height: 20px;
  border: 50%;
  opacity: 0.5;
}

.btn_box {
  width: 100%;
  display: flex;
  padding: 10px;
  justify-content: space-between;
  position: absolute;
  top: 0;
  font-size: 1rem;
}

.image_list {
  width: 100%;
  height: 50px;
  margin-top: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.btn_boxs {
  width: 100%;
  height: 100px;
  margin-top: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.image_go {
  width: 20%;
}

.camera_go {
  width: 20%;
  display: flex;
  justify-content: right;
}

.pach_box {
  border-radius: 50%;

}

.pach_boxs {
  width: 100%;
  height: 100%;
  position: absolute;
  border-radius: 50%;
  z-index: -1;
  display: flex;
  justify-content: center;
  align-items: center;
}

.pach_icon {
  width: 80%;
  height: 80%;
}

.pach_btn {
  width: 70px;
  height: 70px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.pach_img {
  width: 34px;
  height: 34px;
}

.add_btn {
  width: 20px;
  height: 20px;
  background-color: #FA9A24;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 5px;
  margin-left: 10px;
}

.add_btn>img {
  width: 12px;
  height: 12px;
}

.list {
  display: flex;
  align-items: center;
}

.list_item {
  width: 50px;
  height: 50px;
  background-color: #EEEEEE;
  margin-left: 10px;
  border-radius: 8px;
  overflow: hidden;
  position: relative;
}

#file {
  display: none;
}

.message {
  margin-top: 30px;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>

相关推荐

  1. Vue 导航

    2024-03-31 11:16:01       23 阅读
  2. uniapp摄像头拍照功能怎么实现

    2024-03-31 11:16:01       36 阅读
  3. ++i(自增)和 i++(自增)的区别

    2024-03-31 11:16:01       25 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-31 11:16:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-31 11:16:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-31 11:16:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-31 11:16:01       18 阅读

热门阅读

  1. springcloud第4季 远程调用openfegin的介绍4

    2024-03-31 11:16:01       13 阅读
  2. Hive窗口函数面试题(带答案版本)

    2024-03-31 11:16:01       13 阅读
  3. 什么是ORM

    2024-03-31 11:16:01       15 阅读
  4. 什么是 PostCSS

    2024-03-31 11:16:01       10 阅读
  5. ubuntu18.04安装qt

    2024-03-31 11:16:01       16 阅读
  6. 【python】pygame游戏框架

    2024-03-31 11:16:01       14 阅读