vue3+setup使用rtsp视频流实现实时监控,全屏,拍摄,自动拍摄等功能(纯前端)
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并提供真实的地址,关于这点现在还没来得及试