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.offsetWidthcanHeight.value = cedioele.offsetHeightlet 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 = falsemediaStreamTrack.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 = 1console.log(cameraStatu.value);navigator.mediaDevices.getUserMedia({audio: true,video: { facingMode: { exact: "environment" } },}).then((stream) => {// 摄像头开启成功startStauts.value = falsemediaStreamTrack.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 = truecloseStatus.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 = truecloseStatus.value = truecutStatus.value = truestartStauts.value = falseconst image = cs.value[index].toDataURL("image/png");contents = {index: videoNum.value++,type: 1,url: image}showCenter.value = trueconsole.log(image);} else {echo_Status.value = truecloseStatus.value = truecutStatus.value = truestartStauts.value = falsecontents = {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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/782256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VUE3——reactive对比ref

从定义数据角度对比: 。ref用来定义:基本类型数据 。reactive用来定义:对象(或数组)类型数据。 。备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过 reactive 转为代理对象。 从原理角度对比: 。ref通过 object.defineProperty()的 get 与set 来实现响应式(数据劫…

算法题1两数之和

问题&#xff1a;给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以…

金三银四面试题(八):JVM常见面试题(2)

今天我们继续探讨常见的JVM面试题。这些问题不比之前的问题庞大&#xff0c;多用于面试中​JVM部分的热身运动&#xff0c;开胃菜&#xff0c;但是大家已经要认真准备。 JRE、JDK、JVM 及JIT 之间有什么不同&#xff1f; JRE 代表Java 运行时&#xff08;Java run-time&#…

专升本-数字媒体

数字媒体 概念&#xff1a; 媒体&#xff1a;是信息的载体&#xff0c;传播信息的媒介&#xff0c;能为信息的传播提供平台 数字媒体&#xff1a;多重媒体&#xff0c;使用文字&#xff0c;数据&#xff0c;图像&#xff0c;声音等各种媒体 数字媒体技术&#xff1a;利用计…

Kafka入门到实战-第四弹

Kafka入门到实战 Kafka集群搭建官网地址Kafka概述使用Kraft搭建Kafka集群更新计划 Kafka集群搭建 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://kafka.apache.org/Kafka概述 Apache Kafka 是一个开源的分布式事件…

计算机视觉之三维重建(5)---双目立体视觉

文章目录 一、平行视图1.1 示意图1.2 平行视图的基础矩阵1.3 平行视图的极几何1.4 平行视图的三角测量 二、图像校正三、对应点问题3.1 相关匹配法3.2 归一化相关匹配法3.3 窗口问题3.4 相关法存在的问题3.5 约束问题 一、平行视图 1.1 示意图 如下图即是一个平行视图。特点&a…

Redis的Hash数据结构中100万对field和value,field是自增时如何优化?优化Hash结构。

ZipList使用是有条件的&#xff0c;当entry数据量太大时就会启用哈希结构&#xff0c;占用内存空间 1.设置bigkey的上限 在redis.config中设置 2.拆分为string类型 String底层结果没有太多优化&#xff0c;占用内存多 想要批量获取数据麻烦 3.拆分为小的hash 将id/100作为…

2核2G服务器优惠价格轻量61元一年,CVM价格313元15个月

腾讯云2核2G服务器多少钱一年&#xff1f;轻量服务器61元一年&#xff0c;CVM 2核2G S5服务器313.2元15个月&#xff0c;轻量2核2G3M带宽、40系统盘&#xff0c;云服务器CVM S5实例是2核2G、50G系统盘。腾讯云2核2G服务器优惠活动 txybk.com/go/txy 链接打开如下图&#xff1a;…

最小覆盖子串-java

最小覆盖子串-java 题目描述 : 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 "" 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找的子字符串中该字符数量必…

Mac上Matlab_R2023b ARM 版 启动闪退(意外退出)解决方法

安装好后&#xff0c;使用 "libmwlmgrimpl.dylib" 文件替换掉"/Applications/Matlab_R2023b.app/bin/maca64/matlab_startup_plugins/lmgrimpl"文件夹下的同名文件 在终端下执行如下命令&#xff1a; codesign --verbose --force --deep -s - /Applicat…

本地项目上传到GitHub

本文档因使用实际项目提交做为案例&#xff0c;故使用xxx等字符进行脱敏&#xff0c;同时隐藏了部分输出&#xff0c;已实际项目和命令行输出为准 0、 Git 安装与GitHub注册 1&#xff09; 在下述地址下载Git&#xff0c;安装一路默认下一步即可。安装完成后&#xff0c;随便…

乡村数字化转型:科技赋能打造智慧农村新生态

随着信息技术的迅猛发展&#xff0c;数字化转型已成为推动社会进步的重要引擎。在乡村振兴的大背景下&#xff0c;乡村数字化转型不仅是提升乡村治理能力和治理水平现代化的关键&#xff0c;更是推动农业现代化、农村繁荣和农民增收的重要途径。本文旨在探讨乡村数字化转型的内…

解决MySQL幻读?可重复读隔离级别背后的工作原理

什么是当前读和快照读 当前读&#xff1a;又称为 "锁定读"&#xff0c;它会读取记录的最新版本&#xff08;也就是最新的提交结果&#xff09;&#xff0c;并对读取到的数据加锁&#xff0c;其它事务不能修改这些数据&#xff0c;直到当前事务提交或回滚。"sele…

MATLAB 打开文件对话框选择点云输入 (52)

MATLAB 打开文件对话框选择点云输入 (52) 一、算法介绍二、算法实现1.代码一、算法介绍 从指定的路径中(具体的文件夹),选择指定的文件格式(比如ply文件),读取手动点击选择的点云。 整个过程通过打开资源管理器的对话框来手动点击选择点云读取。直接运行即可。 二、算…

Linux课程____shell脚本应用

:一、认识shell 常用解释器 Bash , ksh , csh 登陆后默认使用shell&#xff0c;一般为/bin/bash&#xff0c;不同的指令&#xff0c;运行的环境也不同 二、 编写简单脚本并使用 # vim /frist.sh //编写脚本文件&#xff0c;简单内容 #&#xff01;/bin/bash …

CSS3 简介

1.CSS3 概述 CSS3 是CSS2 的升级版本&#xff0c;它在CSS2 的基础上&#xff0c;新增了很多强大的新功能&#xff0c;从而解决一些实际 面临的问题。 CSS3 在未来会按照模块化的方式去发展&#xff1a; https://www.w3.org/Style/CSS/current-work.html CSS3 的新特性如下&am…

翻译 《The Old New Thing》 - Why is a registry file called a “hive“?

Why is a registry file called a “hive“?https://devblogs.microsoft.com/oldnewthing/20030808-00/?p42943 为什么注册表文件被称为‘蜂巢’&#xff1f; Raymond Chen 2003年8月8日 分享一个没用的知识&#xff1a; 话说有一位 Windows NT 的开发者十分讨厌蜜蜂。于是&a…

sectigo 单IP证书360元

IP数字证书使用了强大的加密功能&#xff0c;能够有效保护网站和用户数据在传输过程中的安全。由Sectigo颁发的各种数字证书兼容性高&#xff0c;可以支持几乎所有主流浏览器&#xff0c;在用户访问时不会出现“不安全”提示&#xff0c;Sectigo旗下的IP证书可以为只有公网IP地…

HAProxy + Vitess负载均衡

一、环境搭建 Vitess环境搭建&#xff1a; 具体vitess安装不再赘述&#xff0c;主要是需要启动3个vtgate&#xff08;官方推荐vtgate和vtablet数量一致&#xff09; 操作&#xff1a; 在vitess/examples/common/scripts目录中&#xff0c;修改vtgate-up.sh文件&#xff0c;…

阳光消费金融2023利润创新高,固收业务立功

来源 | 镭射财经&#xff08;leishecaijing&#xff09; 3月28日&#xff0c;光大银行披露了2023年年报&#xff0c;亦公布旗下消金公司阳光消费金融业绩。截至2023年末&#xff0c;阳光消费金融总资产116.77 亿元&#xff0c;同比下滑6.41%&#xff1b;净资产13.19亿元&#…