监控视频回放

This commit is contained in:
yanran200730
2022-04-20 16:36:55 +08:00
parent 893a75c582
commit 53d7b3fc85
4 changed files with 319 additions and 66 deletions

View File

@@ -3,7 +3,7 @@
<device-slider :permissions="permissions" :show.sync="slider" :ins="instance" :dict="dict"
@treeCommand="handleSliderOption" @select="handleSelectMonitor"
:render-item="renderTreeItem" ref="DeviceSlider"/>
<div class="monitorPane">
<div class="monitorPane" v-loading="isLoading" element-loading-background="rgba(0, 0, 0, 0.6)">
<div class="headerBar">
<el-select default-first-option size="small" v-model="splitScreen" @change="onChange">
<!-- <i slot="prefix" class="iconfont iconjdq_led_Led1"/> -->
@@ -21,10 +21,27 @@
v-for="(m, i) in monitors"
:key="m.id"
:style="currentSplitStyle">
<AiMonitor :instance="instance" :deviceId="m.deviceId" :isShowBar="isShowBar" :id="m.id" type="slw" :name="m.name" @close="removeMonitor(i)" ref="AiMonitor"></AiMonitor>
<AiMonitor
:instance="instance"
:deviceId="m.deviceId"
:isShowBar="isShowBar"
:id="m.id"
:playbackUrls="playbackUrls"
:name="m.name"
@close="removeMonitor(i)"
ref="AiMonitor">
</AiMonitor>
</div>
</div>
<Synergy ref="Synergy" v-if="!isShowBar && monitors.length" style="width: 100%; height: 68px;"></Synergy>
<Synergy
ref="Synergy"
:ids="ids"
:instance="instance"
@replay="onReplay"
@backLiveing="playbackUrls = []"
v-if="!isShowBar && monitors.length"
style="width: 100%; height: 68px;">
</Synergy>
</div>
<ai-dialog title="修改名称" :visible.sync="dialog" width="500px" @onConfirm="handleSubmit(selected)"
@closed="selected={}">
@@ -69,7 +86,13 @@
let per = this.splitOps.find(e => e.value == this.splitScreen)?.per || "100%"
return {width: per, height: per}
},
...mapState(['user'])
...mapState(['user']),
ids () {
if (!this.monitors.length) return ''
return this.monitors.map(v => v.id).join(',')
}
},
data() {
@@ -80,11 +103,13 @@
monitors: [],
dialog: false,
locate: false,
isLoading: false,
isShowBar: true,
selected: {
areaId: ''
},
videoUrl: '',
playbackUrls: [],
latlng: null,
disabledLevel: 0,
rules: {
@@ -137,6 +162,27 @@
})
},
onReplay (e) {
this.isLoading = true
this.instance.post(`/app/appzyvideoequipment/getSlwPlaybackUrl`, null, {
params: {
ids: this.ids,
startTime: e.startTime,
endTime: e.endTime,
nvrCodes: this.ids
}
}).then(res => {
if (res.code == 0) {
if (res.data && res.data.length) {
this.playbackUrls = res.data
this.isLoading = false
}
}
}).catch(() => {
this.isLoading = false
})
},
removeMonitor(i) {
this.monitors.splice(i, 1)
},

View File

@@ -12,7 +12,7 @@
:id="iframeId"
allow="autoplay *; microphone *; fullscreen *" allowfullscreen allowtransparency key="" allowusermedia frameBorder="no"
style="width: 100%; height: 100%;"
:src="`https://cdn.cunwuyun.cn/slw2.0/index.html?url=${isLiveing ? src : replayUrl}`">
:src="`https://cdn.cunwuyun.cn/slw2.0/index.html?url=${src}`">
</iframe>
<div class="slw-bottom" v-if="isShowBar">
<Timeline class="Timeline" v-if="times.length" :times="times" @replay="onReplay" :isLiveing="isLiveing" :width="width" ref="timeline" :style="{width: width}"></Timeline>
@@ -45,9 +45,9 @@
<div class="live" v-if="isLiveing">
<span class="label"></span>
<i>直播中</i>
<em>{{ form.date }}</em>
<em>{{ date }}</em>
</div>
<div v-else class="back-btn" @click="backLiving">回到直播</div>
<div v-else class="back-btn" @click="backLiveing">回到直播</div>
</div>
</div>
<div class="right">
@@ -86,7 +86,7 @@
import Timeline from './Timeline'
export default {
props: ['name', 'isShowBar', 'instance', 'id', 'deviceId'],
props: ['name', 'isShowBar', 'instance', 'id', 'playbackUrls'],
name: 'slwVideo',
@@ -105,7 +105,6 @@
form: {
date: ''
},
src: '',
isLoading: false,
times: [],
date: '',
@@ -115,7 +114,22 @@
videoId: `slwvideo-${new Date().getTime()}`,
iframeId: `video-${new Date().getTime()}`,
isFullscreen: false,
replayUrl: ''
replayUrl: '',
liveingUrl: ''
}
},
computed: {
src () {
if (this.playbackUrls.length) {
return this.playbackUrls.filter(v => v.id === this.id)[0].playbackUrl
}
if (this.isLiveing) {
return this.liveingUrl
}
return this.replayUrl
}
},
@@ -129,9 +143,7 @@
this.isShow = true
})
}
},
immediate: true,
deep: true
}
}
},
@@ -155,7 +167,7 @@
document.removeEventListener('fullscreenchange', this.fullScreenChange)
},
backLiving () {
backLiveing () {
this.getLiveingUrl()
},
@@ -163,7 +175,7 @@
this.isLoading = true
this.instance.post(`/app/appzyvideoequipment/getWebSdkUrl?deviceId=${this.id}`).then(res => {
if (res.data) {
this.src = res.data
this.liveingUrl = res.data
this.isLiveing = true
}
@@ -179,7 +191,7 @@
params: {
ids: this.id,
startTime: `${this.form.date} ${e}`,
endTime: this.form.date + ` ${Number(e.substr(0, 2)) + 6 > 9 ? Number(e.substr(0, 2)) + 6 : '0' + (Number(e.substr(0, 2)) + 6)}:59:59`,
endTime: this.form.date + ` ${Number(e.substr(0, 2)) + 6 > 9 ? Number(e.substr(0, 2)) + 6 : '0' + (Number(e.substr(0, 2)) + 6)}:00:00`,
nvrCodes: ''
}
}).then(res => {
@@ -259,6 +271,7 @@
onConfirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.date = this.form.date
this.isShowDate = false
this.getSlwPlaybackTime()
}

View File

@@ -0,0 +1,134 @@
<template>
<div :class="wrapper" class="canvas" v-if="isInit">
<canvas
:id="id"
:style="{height: '10px'}"
v-if="canvasWidth"
:width="canvasWidth"
height="10">
</canvas>
</div>
</template>
<script>
export default {
props: ['times'],
data () {
return {
ctx: null,
canvasWidth: '',
canvasHeight: '',
isInit: false,
wrapper: `canvas-${new Date().getTime()}`,
id: `timeline-${new Date().getTime()}`,
timer: null
}
},
watch: {
times: {
deep: true,
handler (v) {
if (v.length && this.ctx) {
this.init()
}
}
}
},
mounted () {
this.$nextTick(() => {
this.init()
})
},
destroyed () {
clearInterval(this.timer)
},
methods: {
init () {
this.$nextTick(() => {
this.isInit = true
this.$nextTick(() => {
this.canvasWidth = document.querySelector(`.${this.wrapper}`).offsetWidth
this.canvasHeight = document.querySelector(`.${this.wrapper}`).offsetHeight
this.$nextTick(() => {
const el = document.querySelector(`#${this.id}`)
this.ctx = el.getContext('2d')
this.ctx.width = document.querySelector('.canvas').offsetWidth
this.ctx.height = document.querySelector('.canvas').offsetHeight
if (this.x > 0) {
// this.x = this.canvasWidth * this.ratioW
} else {
// this.initNowTime()
}
this.renderPlayback()
// this.countdown()
})
})
})
},
countdown () {
this.timer = setInterval(() => {
if (this.isLiveing) {
this.initNowTime()
} else {
this.x = this.x + this.canvasWidth / (24 * 60 * 60)
}
}, 1000)
},
initNowTime () {
const date = new Date()
const seconds = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds()
this.x = this.canvasWidth / (24 * 60 * 60) * seconds
},
drawLine(ctx, options) {
const { beginX, beginY, endX, endY, lineColor, lineWidth } = options
ctx.beginPath()
ctx.lineWidth = lineWidth
ctx.moveTo(beginX, beginY)
ctx.lineTo(endX, endY)
ctx.strokeStyle = lineColor
ctx.stroke()
},
renderPlayback () {
const ctx = this.ctx
const unit = this.canvasWidth / (24 * 60 * 60)
this.times.forEach(item => {
this.drawLine(ctx, {
beginX: item.startTime * unit,
beginY: 28,
endX: item.startTime * unit,
endY: 0,
lineColor: 'rgba(0, 156, 255, 1)',
lineWidth: item.endTime * unit - item.startTime * unit
})
})
}
}
}
</script>
<style lang="scss" scoped>
.canvas {
position: relative;
width: 100%;
height: 10px;
canvas {
width: 100%;
height: 10px;
}
}
</style>

View File

@@ -1,9 +1,13 @@
<template>
<div class="synergr" :id="videoId" @mousemove.stop="onMousemove" @mouseup="onMouseUp" @mouseleave="isHide = true" v-if="isInit">
<div class="synergr" :id="videoId" v-if="isInit">
<canvas
id="canvas"
id="synergr-canvas"
:style="{height: '28px'}"
v-if="canvasWidth"
@click="onClick"
@mousemove.stop="onMousemove"
@mouseup="onMouseUp"
@mouseleave="isHide = true"
:width="canvasWidth"
height="28">
</canvas>
@@ -36,9 +40,9 @@
<div class="live" v-if="isLiveing">
<span class="label"></span>
<i>直播中</i>
<em>{{ form.date }}</em>
<em>{{ date }}</em>
</div>
<div v-else class="back-btn" @click="isLiveing = true">回到直播</div>
<div v-else class="back-btn" @click="backLiveing">回到直播</div>
</div>
</div>
<div class="right">
@@ -53,10 +57,10 @@
<img title="展开" src="https://cdn.cunwuyun.cn/slw2.0/images/arrow.png" />
</div>
<div class="playback-list" v-if="isShowTimeline">
<div class="playback-item" v-for="(item, index) in 4" :key="index">
<div class="playback-item" v-for="(item, index) in times" :key="index">
<el-checkbox></el-checkbox>
<span>通道{{ index }}</span>
<div class="playback-item__timeline"></div>
<PlaybackTime class="playback-item__timeline" :key="'PlaybackTime' + index" :times="item"></PlaybackTime>
</div>
</div>
</div>
@@ -80,15 +84,15 @@
</template>
<script>
import Timeline from './Timeline'
import PlaybackTime from './PlaybackTime'
export default {
props: ['src', 'name', 'isShowBar'],
props: ['ids', 'instance'],
name: 'slwVideo',
name: 'Synergy',
components: {
Timeline
PlaybackTime
},
data () {
@@ -102,6 +106,7 @@
form: {
date: ''
},
times: [],
isShowTimeline: false,
checkList: [],
isInit: false,
@@ -136,6 +141,8 @@
mounted () {
this.form.date = this.$moment(new Date()).format('YYYY-MM-DD')
this.date = this.$moment(new Date()).format('YYYY-MM-DD')
this.getSlwPlaybackTime()
this.$nextTick(() => {
this.init()
})
@@ -143,33 +150,42 @@
methods: {
onMousemove (e) {
const canvasInfo = document.querySelector(`#canvas`).getBoundingClientRect()
const canvasInfo = document.querySelector(`#synergr-canvas`).getBoundingClientRect()
const seconds = 24 * 60 * 60
const x = e.clientX - canvasInfo.left
const unit = seconds / this.canvasWidth * x
this.left = x
this.time = this.secTotime(unit)
this.isHide = false
if (e.clientY - canvasInfo.top < 29) {
const x = e.clientX - canvasInfo.left
const unit = seconds / this.canvasWidth * x
this.left = x
this.time = this.secTotime(unit)
this.isHide = false
if (!this.isChoose) return
this.x = e.clientX - canvasInfo.left - 4
this.ratioW = this.x / this.canvasWidth
} else {
this.isHide = true
}
if (!this.isChoose) return
this.x = e.clientX - canvasInfo.left - 4
this.ratioW = this.x / this.canvasWidth
},
onDragDown () {
this.$emit('pause')
clearInterval(this.timer)
this.timer = null
this.isChoose = true
},
backLiveing () {
this.$emit('backLiveing')
this.isLiveing = true
this.initNowTime()
},
onMouseUp () {
if (!this.isChoose) return
clearInterval(this.timer)
this.timer = null
this.isChoose = false
const time = this.secTotime((24 * 60 * 60) / this.canvasWidth * this.x)
this.$emit('replay', {
startTime: `${this.form.date} ${time}`,
endTime: this.form.date + ` ${Number(time.substr(0, 2)) + 6 > 9 ? Number(time.substr(0, 2)) + 6 : '0' + (Number(time.substr(0, 2)) + 6)}:59:59`
})
this.isLiveing = false
},
init () {
@@ -180,7 +196,7 @@
this.canvasHeight = document.querySelector(`#${this.videoId}`).offsetHeight
this.$nextTick(() => {
const el = document.querySelector(`#canvas`)
const el = document.querySelector(`#synergr-canvas`)
this.ctx = el.getContext('2d')
this.ctx.width = document.querySelector(`#${this.videoId}`).offsetWidth
this.ctx.height = document.querySelector(`#${this.videoId}`).offsetHeight
@@ -214,6 +230,19 @@
ctx.stroke()
},
onClick (e) {
const canvasInfo = document.querySelector(`#synergr-canvas`).getBoundingClientRect()
this.x = e.clientX - canvasInfo.left - 4
clearInterval(this.timer)
this.timer = null
const time = this.secTotime((24 * 60 * 60) / this.canvasWidth * this.x)
this.$emit('replay', {
startTime: `${this.form.date} ${time}`,
endTime: this.form.date + ` ${Number(time.substr(0, 2)) + 6 > 9 ? Number(time.substr(0, 2)) + 6 : '0' + (Number(time.substr(0, 2)) + 6)}:00:00`
})
this.isLiveing = false
},
renderTimeLine () {
const ctx = this.ctx
ctx.fillStyle = 'rgba(40, 43, 58, 1)'
@@ -253,35 +282,67 @@
},
secTotime (s) {
var t = ''
if(s > -1){
var hour = Math.floor(s / 3600)
var min = Math.floor(s / 60) % 60
var sec = s % 60
if(hour < 10) {
t = '0'+ hour + ":"
} else {
t = hour + ":"
}
if(min < 10){
t += "0"
}
t += min + ":"
if(sec < 10){
t += "0"
}
t += sec.toFixed(0)
}
return t
let second = parseInt(s)
let minute = 0
let hour = 0
if (second > 60) {
minute = parseInt(second / 60)
second = parseInt(second % 60)
if (minute > 60) {
hour = parseInt(minute / 60)
minute = parseInt(minute % 60)
}
}
hour = `${parseInt(hour) > 9 ? parseInt(hour) : '0' + parseInt(hour)}`
minute = `${parseInt(minute) > 9 ? parseInt(minute) : '0' + parseInt(minute)}`
second = `${parseInt(second) > 9 ? parseInt(second) : '0' + parseInt(second)}`
return `${hour}:${minute}:${second}`
},
onConfirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.isShowDate = false
this.date = this.form.date
this.getSlwPlaybackTime()
}
})
},
getSlwPlaybackTime () {
this.isLoading = true
this.instance.post(`/app/appzyvideoequipment/getSlwPlaybackTime`, null, {
params: {
ids: this.ids,
startTime: this.date + ' 00:00:00',
endTime: this.date + ' 23:59:59',
}
}).then(res => {
if (res.code == 0) {
if (res.data && res.data.length) {
this.times = res.data.map(v => {
return v.times.map(item => {
const startTime = (item.startTime - new Date(this.date + ' 00:00:00').getTime()) / 1000
const endTime = (item.endTime - new Date(this.date + ' 00:00:00').getTime()) / 1000
return {
startTime: Number(startTime.toFixed(0)),
endTime: Number(endTime.toFixed(0))
}
}).sort((a, b) => {
return a.startTime - b.startTime
})
})
}
this.isLoading = false
}
}).catch(() => {
this.isLoading = false
})
},
onVolume (e) {
const v = (e / 100).toFixed(1)
const subPage = document.querySelector(`#${this.id}`).contentWindow
@@ -356,9 +417,8 @@
.playback-item__timeline {
flex: 1;
height: 8px;
height: 12px;
border-radius: 6px;
background: linear-gradient(180deg, #00CBFF 0%, #009CFF 100%);
}
span {