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

本文主要是介绍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>

这篇关于vue实现相机拍摄,可录视频、拍照片、前置后置切换(简单小demo)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/863418

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象