基于 Vue2 封装大华 RTSP 回放视频组件(PlayerControl.js 实现)
参考链接:基于 Vue3 封装大华 RTSP 回放视频组件(PlayerControl.js 实现)_vue playercontrol大华的使用-CSDN博客
官方教程: WEB无插件开发包使用说明-浙江大华技术股份有限公司
碰到的问题: 1、PlayerControl.js默认是在根目录使用,如果不是在根目录使用需要修改对应文件中的的路径(我的前缀是cockpit)不然会找不到对应的文件
2、我对接的大华的摄像头是H265格式的只能在canvas中展现出来,我这边的功能需要是对视频进行回放和参考链接类似,但是参考链接是能在video中展示因此不需要添加播放、暂停、音量开关、抓图、刷新、全屏功能,canvas中就需要手动添加
<template>
<el-dialog :title="'文件预览'" class="prev-file-dialog" append-to-body :visible.sync="DialogVisible" :fullscreen="true">
<template #title>
<div class="vn-flex vn-flex-space-between vn-gap-8 vn-flex-y-center vn-fill-width">
<span>{{ '文件预览' }}</span>
</div>
</template>
<div class="preview-pdf">
<canvas ref="canvasElement" :style="{ width: '100%', height: '100%' }"></canvas>
<div class="operation">
<div class="operation-left vn-flex vn-flex-y-center vn-gap-8">
<div
class="play icon vn-pointer"
:class="canvasOperation.playState ? 'el-icon-video-pause' : 'el-icon-video-play'"
@click="handlePlay"
></div>
<div class="disconnect icon"></div>
<button class="control-btn" @click.stop="toggleMute">
<span v-if="!canvasOperation.muteState" class="icon-volume">🔊</span>
<span v-else class="icon-muted">🔇</span>
</button>
<div class="timestamp vn-flex vn-flex-y-center vn-gap-4">
<span class="first-time">{{ canvasOperation.firstTime }}</span>
/
<span class="total-time">{{ canvasOperation.totalTime }}</span>
<el-input-number
v-model="canvasOperation.backTime"
size="mini"
clearable
type="number"
:min="0"
:max="canvasOperation.totalTime"
:controls="false"
class="number-input"
:precision="0"
></el-input-number>
<el-button size="mini" type="primary" @click="playerBack">跳转</el-button>
</div>
</div>
<div class="operation-right vn-flex vn-flex-y-center vn-gap-12">
<!-- 捕获截图 -->
<div class="el-icon-crop icon" @click="handleCapture"></div>
<!-- 刷新 -->
<div class="el-icon-refresh-right icon" @click="handleRefresh"></div>
<!-- 全屏按钮 -->
<div class="el-icon-full-screen icon" @click.stop="toggleFullscreen"></div>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts">
import { Component, Vue, Ref, Prop, PropSync } from 'vue-property-decorator'
@Component({
name: 'DaHuaVideoPreview',
components: {}
})
export default class DaHuaVideoPreview extends Vue {
@Ref() canvasElement!: any
@PropSync('visible', { default: false }) DialogVisible!: boolean
// 接收外部参数
@Prop({
default: () => {
return {
wsURL: 'ws://xxx.xxx.xxx.xxx:9527/rtspoverwebsocket',
url: '',
ip: 'xxx.xxx.xxx.xxx',
port: '9527',
channel: 1,
username: 'admin',
password: 'admin123',
proto: 'Private3',
subtype: 0,
starttime: '2025_11_10_09_10_00',
endtime: '2025_11_10_10_10_00',
width: '100%',
height: '220px'
}
}
})
props!: any
player: any = null
canvasOperation = this.initCanvasData()
initCanvasData() {
return {
playState: false,
muteState: false,
isFullscreen: false,
backTime: 1,
totalTime: 0,
firstTime: 0
}
}
playerStop() {
this.player?.close()
}
playerPause() {
this.player?.pause()
}
playerContinue() {
// this.player?.play()
this.playerPlay()
}
playerCapture() {
this.player?.capture('test')
}
playerPlay() {
if (this.player) {
this.player.stop()
this.player.close()
this.player = null
}
if (!window.PlayerControl) {
console.error('❌ PlayerControl SDK 未加载,请在 index.html 中引入 /module/playerControl.js')
return
}
this.closePlayer()
var options = {
wsURL: `ws://${this.props.ip}:${this.props.port}/rtspoverwebsocket`,
rtspURL: this.buildRtspUrl(),
username: this.props.username,
password: this.props.password,
h265AccelerationEnabled: true
}
this.player = new window.PlayerControl(options)
let firstTime = 0
this.player.on('WorkerReady', (rs: any) => {
console.log('WorkerReady')
this.player.connect()
})
this.player.on('Error', (rs: any) => {
console.log('error')
console.log(rs)
})
this.player.on('PlayStart', () => {
console.log('PlayStart')
this.canvasOperation.playState = true
})
this.player.on('UpdateCanvas', (res: any) => {
if (firstTime === 0) {
firstTime = res.timestamp //获取录像文件的第一帧的时间戳
}
this.canvasOperation.firstTime = res.timestamp - firstTime
})
this.player.on('GetTotalTime', (res: any) => {
console.log(res, 'GetTotalTime')
this.canvasOperation.totalTime = res || 0
})
this.player.on('FileOver', (res: any) => {
console.log(res, 'FileOver')
this.handleRefresh()
})
this.player.init(this.canvasElement, null)
window.__player = this.player
}
mounted() {
console.log(this.props, 'props')
this.$nextTick(() => {
this.playerContinue()
})
}
beforeDestroy() {
this.closePlayer()
}
playerBack() {
this.player.playByTime(this.canvasOperation.backTime)
}
/** 拼接 RTSP URL 回放 */
buildRtspUrl() {
if (this.props?.url) return this.props?.url
return `rtsp://${this.props.ip}:${this.props.port}/cam/playback?channel=${this.props.channel}&subtype=${this.props.subtype}&starttime=${this.props.starttime}&endtime=${this.props.endtime}`
}
closePlayer() {
if (this.player) {
try {
this.player.close()
} catch (e) {
console.warn('旧播放器关闭异常:', e)
}
this.player = null
}
}
toggleMute() {
this.canvasOperation.muteState = !this.canvasOperation.muteState
// 如果要关闭声音,将 val 参数设置为 0 即可。WEB SDK 播放时,默认音量是 0。需要声音时,必须调用该方法,并且参数大于 0
this.player.setAudioVolume(Number(this.canvasOperation.muteState))
}
toggleFullscreen() {
const videoWrapper = this.$el.querySelector('.preview-pdf') as HTMLElement
if (!document.fullscreenElement) {
// 进入全屏
if (videoWrapper.requestFullscreen) {
videoWrapper.requestFullscreen()
}
this.canvasOperation.isFullscreen = true
} else {
// 退出全屏
if (document.exitFullscreen) {
document.exitFullscreen()
}
this.canvasOperation.isFullscreen = false
}
}
//
handlePlay() {
if (!this.canvasOperation.playState) {
this.player.play()
} else {
this.player.pause()
}
this.canvasOperation.playState = !this.canvasOperation.playState
}
handleCapture() {
this.player.capture(new Date().getTime())
}
handleRefresh() {
this.canvasOperation = this.initCanvasData()
this.playerPlay()
}
}
</script>
<style lang="scss" scoped>
.preview-pdf {
height: 100%;
width: 100%;
position: relative;
.operation {
width: 100%;
height: 40px;
position: absolute;
bottom: 0;
right: 0;
z-index: 1;
background-color: rgb(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 0 16px;
.play {
color: #fff;
font-size: 16px;
}
.icon {
font-size: 20px;
color: #fff;
cursor: pointer;
}
.control-btn {
background-color: transparent;
span {
font-size: 18px;
}
}
}
.timestamp {
color: #fff;
flex-shrink: 0;
.first-time,
.total-time {
flex-shrink: 0;
}
.number-input {
width: 100px;
}
}
}
.prev-file-dialog {
width: 100vw;
height: 100vh;
::v-deep {
.el-dialog.is-fullscreen {
height: 100%;
}
.el-dialog {
min-width: 80vw;
height: calc(70vh);
}
.el-dialog__body {
max-height: 100%;
min-height: 0;
height: 100%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
padding: 12px !important;
background: #fff;
}
}
}
</style>
实现效果: