vue3+setup使用rtsp视频流实现实时监控,全屏,拍摄,自动拍摄等功能(纯前端)

server/2024/12/14 10:35:09/

vue3+setup使用rtsp视频流实现实时监控,全屏,拍摄,自动拍摄等功能(纯前端)

概要

本文介绍了如何在Vue应用中通过WebRTC技术获取摄像头的rtsp视频流,同时展示了实时监控,全屏,拍摄,自动拍摄等功能。

一、获取rtsp流并确保其可用

1.因为是纯前端角度,所以从后端可以获取http开头的视频流地址(其实是在后端电脑打开,然后本地接入后端电脑,进行查看或修改配置)因为rstp流需要转码,所以在摄像头配置里面要修改转码格式
2.前端自己可以下载安装VLC播放器然后进行调试设置
在这里插入图片描述

二、在vue3项目里引入rtsp流

1.下载webrtc-streamer
下载地址:https://github.com/mpromonet/webrtc-streamer/releases
下载版本根据自己电脑进行选择,双击webrtc-streamer.exe可执行文件后在浏览器访问http://127.0.0.1:8000,可以看到自己电脑正在操作的内容。
2.将/html/libs目录下的adapter.min.js和/html目录下面的webrtcstreamer.js文件分别拷贝到vue项目public中,并在index.html文件全局引入。

 <script src="/adapter.min.js"></script><script src="/webrtcstreamer.js"></script>

三、具体代码,video封装

1.父页面

<script setup>import { ref } from 'vue'import videoRtmp from './videoRtmp.vue'
const list = ref([])
const idlist = ref([])
const imgArr = ref([]) // 手动抓拍的图片
const screenshotUrl = ref([]) // 自动抓拍的图片const total = ref(0)
const currentPage = ref(1)
const keyWord = ref('')
const rtmpUrl = ref('rtsp://用户:密码@电脑网址:554')
// videoFlag 判断是单个还是多个video 对应不同的样式 false是单个 true是多个
const form = ref({ videoFlag: false, videoList: [] })
const openVideo = ref(false)
const screenButton = ref(false) // 默认解禁 点击抓拍按钮后禁止 抓拍完成后解禁
const sButton = ref(true) // 默认禁止 加载完成后解禁
let screenNums = 0 // 加载完成的数量const loadData = (pageNum = currentPage.value, pageSize = 10) => {list.value = [{id: 1,idName: 'id1',name: '设备1',rtmpUrl: 'rtsp://用户:密码@电脑网址:554',userName: 'admin',screenFlag: false,},{id: 2,idName: 'id2',name: '设备2',code: '002',userName: 'admin',password: 'VWEFKS',screenFlag: false,rtmpUrl: 'rtsp://用户:密码@电脑网址:554',},]total.value = list.value.length
}//分页
const handlePageChange = (page) => {currentPage.value = pageloadData()
}
// 搜索
const searchHandle = async () => {currentPage.value = 1loadData()
}
/** 选择条数  */
function handleSelectionChange(selection) {console.log(selection)idlist.value = selection
}
//单个点击观看
const videoLook = (row) => {console.log(row)form.value.videoList = [row]form.value.videoFlag = falseopenVideo.value = true
}
//多个观看
const videoAllLook = (row) => {console.log(row)form.value.videoList = idlist.valueform.value.videoFlag = idlist.value.length > 1 ? true : falseopenVideo.value = true
}//点击叉号
const closeHandle = () => {resetData()
}
//清空数据
const resetData = () => {openVideo.value = falseimgArr.value = []  screenshotUrl.value = []screenNums = 0screenButton.value = falseform.value = {}loadData()
}
// 抓拍
const screenshot = () => {form.value.videoList.forEach((v) => {v.screenFlag = true})screenButton.value = true
}
//父子传值 获取抓拍的
const handleScreenshot = (data, flag) => {console.log(data.id, data,flag)if (!flag) {// 自动抓拍screenshotUrl.value.push(data.image)imageVideoApi()return}// 手动抓拍form.value.videoList.forEach((v) => {if (v.id == data.id) {v.screenFlag = false}})imgArr.value.push(data.image)if (!form.value.videoList.some((a) => a.screenFlag)) {screenButton.value = falseimageVideoApi()}
}
// 父子传值 获取video是否成功
// 因为video加载需要一段时间,在这段时间里,不能抓拍,所以要在这段时间给它禁用掉,等到video加载完后,在解禁
// 这里做判断 在子页面加载完成emit返回主页面 然后判断返回的数量 符合video的数量,说明都加载完了
const screenButtonS = () => {screenNums++if (form.value.videoList.length == screenNums) {sButton.value = false}
}
// 图片检测接口
const imageVideoApi = async () => {let files = base64ToFile(imgArr.value[0], 'image.png')let res = await imageVideo({ file: files })
}
// base64转file
function base64ToFile(base64Data, filename) {// 将base64的数据部分提取出来const arr = base64Data.split(',')const mime = arr[0].match(/:(.*?);/)[1]const bstr = atob(arr[1])let n = bstr.lengthconst u8arr = new Uint8Array(n)while (n--) {u8arr[n] = bstr.charCodeAt(n)}// 将Uint8Array转换为Blob对象const blob = new Blob([u8arr], { type: mime })// 创建File对象const file = new File([blob], filename, { type: mime })return file
}
loadData()
</script>
<template><div><el-form class="main-form" :inline="true"><el-form-item label="设备名称:"><el-input v-model="keyWord" placeholder="请输入设备名称" /></el-form-item><el-form-item><el-button :icon="Search" type="primary" @click="searchHandle">搜索</el-button></el-form-item><el-form-item><el-button :icon="Search" type="primary" :disabled="idlist.length <= 0" @click="videoAllLook">多流播放</el-button></el-form-item></el-form><!-- 列表部分 --><el-table border :data="list" height="460" scrollbar-always-on="true" @selection-change="handleSelectionChange"><el-table-column align="center" type="selection" width="50" /><el-table-column label="设备名称" prop="name" /><el-table-column label="账号" prop="userName" width="100" /><el-table-column fixed="right" label="操作" width="160"><template #default="scope"><el-button size="small" type="primary" @click="videoLook(scope.row)">播放</el-button></template></el-table-column></el-table><el-paginationbackground:current-page="currentPage":hide-on-single-page="true"layout="prev, pager, next":total="total"@current-change="handlePageChange"/><el-dialog title="实时监控" width="900px" :close-on-click-modal="false" @close="closeHandle" v-model="openVideo"><div v-if="!form.videoFlag"><h2 style="text-align: center">{{ form.videoList[0]?.name }}</h2><video-rtmp:rtspUrl="form.videoList[0]?.rtmpUrl":id="form.videoList[0]?.idName":screenFlag="form.videoList[0]?.screenFlag":data="form.videoList[0]"@screenshot="handleScreenshot"@screenButton="screenButtonS"/></div><div class="videoAll" v-else><div v-for="(item, index) in form.videoList" :key="index"><h2 style="text-align: center">{{ item?.name }}</h2><video-rtmp:rtspUrl="item?.rtmpUrl":id="item?.idName":screenFlag="item?.screenFlag":data="item"@screenshot="handleScreenshot"@screenButton="screenButtonS"/></div></div><div><el-button type="primary" :disabled="screenButton || sButton" v-preventReClick @click="screenshot">抓拍</el-button></div><div>手动抓拍<img style="width: 300px; height: 100px" v-for="(item, index) in imgArr" :key="index" :src="item" /></div><div>自动抓拍<img style="width: 100px; height: 50px" v-for="(item, index) in screenshotUrl" :key="index" :src="item" /></div></el-dialog></div>
</template><style scoped lang="scss">
.videoAll {> div {width: 45%;height: 45%;display: inline-block;}> div:nth-child(odd) {margin-right: 5%;}
}
</style>

2.子页面

<script setup>
defineOptions({name: 'VideoRtmp',
})
// 如果不想放到public里面并在index.html里面引入,也可以选择其他位置
// import WebRtcStreamer from './webrtcstreamer.js'
import { ref } from 'vue'const emit = defineEmits()
const props = defineProps({rtspUrl: {type: String,default: '',},id: {type: String,default: '',},data: {type: Object,default: '',},screenFlag: {type: Boolean,default: '',},
})
const videoRef = ref(null)
const webRtcServer = ref(null)
const srcUrl = ref(process.env.NODE_ENV === 'development' ? 'http://网址:端口' : 'http://网址:端口') //这里看自己的需要,也可以传入另一台电脑的ip,前提是都得在在一个局域网内
// 设置抓拍间隔时间,单位为毫秒,这里设置为5000毫秒(即5秒)
const captureInterval = 5000
// 新增:用于存储定时器标识
const captureTimer = ref(null)watch(() => props.rtspUrl,(val) => {if (val) {// 这里放开会报错,还没有找到原因//   webRtcServer.value.disconnect()nextTick(() => {initVideo()})}},{ deep: true, immediate: true }
)
watch(() => props.screenFlag,(val) => {if (val) {handleScreenshot(true)}},{ deep: true, immediate: true }
)
function initVideo() {try {//video:需要绑定的video控件ID//127.0.0.1:8000:启动webrtc-streamer的设备IP和端口,默认8000//连接后端的IP地址和端口// 静音videoRef.value.volume = 0webRtcServer.value = new WebRtcStreamer(props.id, srcUrl.value)//需要查看的rtsp地址,根据自己的摄像头传入对应的rtsp地址即可。注意:视频编码格式必须是H264的,否则无法正常显示,编码格式可在摄像头的后台更改//向后端发送rtsp地址webRtcServer.value.connect(props.rtspUrl)setTimeout(() => {emit('screenButton')}, 1000)} catch (error) {console.log(error)}
}/* 处理双击 视频全屏*/
const dbClick = () => {const elVideo = document.getElementById(props.id)if (elVideo.webkitRequestFullScreen) {elVideo.webkitRequestFullScreen()} else if (elVideo.mozRequestFullScreen) {elVideo.mozRequestFullScreen()} else if (elVideo.requestFullscreen) {elVideo.requestFullscreen()}
}
/* 抓拍*/
function handleScreenshot(flag) {let video = document.getElementById(props.id) //获取dom节点let canvas = document.createElement('canvas') //创建canvas节点let w = window.innerWidthlet h = (window.innerWidth / 16) * 9canvas.width = wcanvas.height = h //设置canvas宽高const ctx = canvas.getContext('2d')ctx.drawImage(video, 0, 0, w, h) //video写入到canvaslet obj = {name: props.data.name,id: props.data.id,image: canvas.toDataURL('image/jpge'), //生成截图地址flags: flag,}// flag  true 手动抓拍  false 自动抓拍emit('screenshot', obj, flag)
}// 开始自动抓拍
const startAutoCapture = () => {captureTimer.value = setInterval(() => {handleScreenshot(false)}, captureInterval)
}
// 新增:停止自动抓拍功能
const stopAutoCapture = () => {if (captureTimer.value) {clearInterval(captureTimer.value)captureTimer.value = null}
}
onMounted(() => {startAutoCapture()
})
//销毁视频流
onUnmounted(() => {webRtcServer.value.disconnect()stopAutoCapture()
})
</script>
<!-- style="object-fit: fill; 设置视频内容撑满整个video标签 -->
<template><div class="video-contianer"><video ref="videoRef" :id="id" controls autoplay muted width="100%" height="100%" style="object-fit: fill"></video><div class="mask" @dblclick="dbClick"></div></div>
</template><style scoped lang="scss">.video-contianer {position: relative;.mask {position: absolute;cursor: pointer;top: 25%;left: 0;width: 100%;height: 50%;}
}
</style>

小结

第一次写这种实时监控加抓拍的功能,可能还有一些不足,后面再看看有没有其他更好的办法。 本地开发的时候要双击运行webrtc-streamer.exe可执行文件后,vue项目才能展示实时监控,打包发布到线上的时候可能要让后台在服务器上安装webrtc-streamer并提供真实的地址,关于这点现在还没来得及试


http://www.ppmy.cn/server/150068.html

相关文章

利用GeoWave导入矢量数据到HBase/Accumulo数据库

前言 最近在做有关地理时空大数据的实验&#xff0c;本文将介绍如何利用geowave框架&#xff0c;将矢量数据导入到HBase或Accumulo等NoSQL数据库中。 软件版本&#xff1a; Hadoop: 2.10.2 Zookeeper: 3.6.4 geowave: 1.2.0 Accumulo&#xff1a;1.9.3 HBase: 1.4.0 Ja…

MongoDB-单键索引与复合索引

在 MongoDB 中&#xff0c;索引是提高查询性能的一个重要手段。通过为集合中的字段创建索引&#xff0c;可以显著加快对数据的检索速度。MongoDB 支持多种类型的索引&#xff0c;其中 单键索引 和 复合索引 是最常用的两种类型。了解这两种索引的工作原理、使用场景以及区别&am…

本地体验新版springcloud-搭建工程学习笔记

为了快速体验下新版本springcloud.对照b站图灵视频简单记录下。起码入门不要钱&#xff0c;值得推荐。 基础知识&#xff1a; 会用springboot写demo。 会用mybatis操作MYSQL。 会用git拉取代码。 这都是基本操作。 环境准备&#xff1a; jdk17 demo是21.我实际测试17也可…

Nginx之配置防盗链(Configuring Anti-hotlinking in Nginx)

运维小白入门——Nginx配置防盗 什么是防盗链&#xff1a; 防盗链技术主要用于防止未经授权的第三方或域名访问网站的静态资源。例如&#xff0c;一个网站可能拥有独特的图片素材&#xff0c;为了防止其他网站通过直接链接图片URL的方式访问这些图片&#xff0c;网站管理员会采…

MedLSAM: 用于3D CT图像的局部化和分割模型|文献速递-生成式模型与transformer在医学影像中的应用

Title 题目 MedLSAM: Localize and segment anything model for 3D CT images MedLSAM: 用于3D CT图像的局部化和分割模型 01 文献速递介绍 最近&#xff0c;计算机视觉领域对开发大规模的基础模型的兴趣不断增加&#xff0c;这些模型能够同时处理多个视觉任务&#xff0c…

常见的网络命令

目录 1. ping2. netstat3. pidof 1. ping ping 命令可以用于检查两台主机是否连通&#xff08;是否可以进行通信&#xff09; ping -cn ip/域名 -cn: 指定 ping 的次数 n2. netstat netstat&#xff1a;一个查看网络状态的工具&#xff0c;常用于监听 常用选项 -n 拒绝显示别名…

pcl::PointCloud<pcl::PointXYZ>和pcl::PointCloud<pcl::PointXYZ>::Ptr 转换及新建点云显示

点云智能指针格式和非指针格式的转换 pcl::PointCloud<PointT>::Ptr cloud_ptr(new pcl::PointCloud<PointT>); pcl::PointCloud<PointT> cloud; cloud *cloud_ptr; cloud_ptr boost::make_shared<pcl::PointCloud<PointT>>(cloud);全部代码&…

电商数据API接口:安全与性能的双重挑战

随着电子商务的蓬勃发展&#xff0c;电商平台与外部服务、内部系统之间的数据交换和通信变得日益频繁。API&#xff08;应用程序编程接口&#xff09;接口作为这一过程中的关键枢纽&#xff0c;其安全性和性能表现对于电商平台的稳定运行和用户体验至关重要。然而&#xff0c;电…