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 = page
  loadData()
}
// 搜索
const searchHandle = async () => {
  currentPage.value = 1
  loadData()
}
/** 选择条数  */
function handleSelectionChange(selection) {
  console.log(selection)
  idlist.value = selection
}
//单个点击观看
const videoLook = (row) => {
  console.log(row)
  form.value.videoList = [row]
  form.value.videoFlag = false
  openVideo.value = true
}
//多个观看
const videoAllLook = (row) => {
  console.log(row)
  form.value.videoList = idlist.value
  form.value.videoFlag = idlist.value.length > 1 ? true : false
  openVideo.value = true
}

//点击叉号
const closeHandle = () => {
  resetData()
}
//清空数据
const resetData = () => {
  openVideo.value = false
  imgArr.value = []  
  screenshotUrl.value = []
  screenNums = 0
  screenButton.value = false
  form.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 = false
    imageVideoApi()
  }
}
// 父子传值 获取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.length
  const 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-pagination
      background
      :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 = 0
    webRtcServer.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.innerWidth
  let h = (window.innerWidth / 16) * 9
  canvas.width = w
  canvas.height = h //设置canvas宽高
  const ctx = canvas.getContext('2d')
  ctx.drawImage(video, 0, 0, w, h) //video写入到canvas
  let 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并提供真实的地址,关于这点现在还没来得及试

### 集成海康威视摄像头与Vue3及WebRTC-Streamer进行RTSP流媒体处理 #### 准备工作 为了使Vue3项目能够成功连接并播放来自海康威视摄像机的RTSP视频流,需先完成必要的准备工作。这包括安装`webrtc-streamer`服务端软件,并将其配置好以便接收来自IP摄像机的RTSP请求[^1]。 #### 安装依赖库 确保已将`webrtcstreamer.js`和`adapter.min.js`这两个JavaScript文件放置于项目的公共资源目录下(通常是`public/html/libs`)。这些脚本对于建立客户端和服务端之间的通信至关重要[^4]。 #### 创建组件用于显示视频流 创建一个新的Vue组件来负责展示接收到的视频数据。此组件内部会初始化WebRTC连接并向指定URL发起获取媒体流的请求: ```javascript <template> <div class="video-container"> <video ref="remoteVideo" autoplay playsinline></video> </div> </template> <script setup lang="ts"> import { onMounted, ref } from &#39;vue&#39;; // 导入 WebRTC Streamer 库 import * as WRS from &#39;/libs/webrtcstreamer.js&#39;; const remoteVideo = ref<HTMLVideoElement | null>(null); onMounted(() => { const wsUrl = "wss://your-webrtc-server-url"; // 替换成实际的服务端WebSocket URL let wrs = new WRS.WebRtcStream(); wrs.connect(wsUrl).then((_) => { console.log(&#39;Connected to WebSocket&#39;); wrs.play(remoteVideo.value!, "rtsp://admin:password@camera-ip-address/stream").catch(console.error); }).catch(console.error); }); </script> <style scoped> .video-container video { width: 100%; height: auto; } </style> ``` 上述代码片段展示了如何在一个Vue3单文件组件内设置WebRTC连接逻辑,其中包含了对远程视频元素的操作以及向特定路径发送RTSP请求的过程[^2]。 #### 处理常见问题 当遇到诸如无法正常加载视频、画面停滞或其他异常情况时,可以尝试调整网络参数或优化服务器性能;另外也要确认所使用RTSP地址格式无误,并且用户名密码正确无误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值