|
|
@@ -1,3 +1,368 @@
|
|
|
<template>
|
|
|
- <div></div>
|
|
|
-</template>
|
|
|
+ <div style="display: block; width: 1000px">
|
|
|
+ <div style="display: flex;margin-bottom: 10px;">
|
|
|
+ <el-row>
|
|
|
+ <span style="margin-left: 10px" prop="channelName">通道:</span>
|
|
|
+ <el-select
|
|
|
+ v-model="channelId"
|
|
|
+ placeholder="请选择"
|
|
|
+ @change="changeChannel"
|
|
|
+ style="width: 200px; margin-right: 10px"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="option in channelList"
|
|
|
+ :key="option.value"
|
|
|
+ :label="option.label"
|
|
|
+ :value="option.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <span style="overflow: auto; margin-left: 10px">日期:</span>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryDate"
|
|
|
+ type="date"
|
|
|
+ size="small"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ clearable
|
|
|
+ placeholder="选择日期"
|
|
|
+ style="width: 180px; margin-right: 10px"
|
|
|
+ />
|
|
|
+ <el-button-group style="margin: 0">
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ type="success"
|
|
|
+ title="查看录像"
|
|
|
+ @click="loadDevRecord()"
|
|
|
+ :disabled="channelId === '' || !queryDate"
|
|
|
+ >
|
|
|
+ <el-icon><VideoCamera /></el-icon>
|
|
|
+ <span class="pl-1">查看</span>
|
|
|
+ </el-button>
|
|
|
+ </el-button-group>
|
|
|
+
|
|
|
+ <span style="margin-left: 82px; overflow: auto">时间:</span>
|
|
|
+ <el-button-group>
|
|
|
+ <el-time-picker
|
|
|
+ size="small"
|
|
|
+ is-range
|
|
|
+ align="left"
|
|
|
+ v-model="timeRange"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始时间"
|
|
|
+ end-placeholder="结束时间"
|
|
|
+ placeholder="选择时间范围"
|
|
|
+ @change="timePickerChange"
|
|
|
+ style="width: 200px"
|
|
|
+ :disabled="channelId === '' || !queryDate"
|
|
|
+ />
|
|
|
+ </el-button-group>
|
|
|
+ <el-button-group style="margin: 0 0 0 10px">
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ title="下载选定录像"
|
|
|
+ @click="downloadRecord()"
|
|
|
+ :disabled="channelId === '' || !timeRange"
|
|
|
+ >
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ <span class="pl-1">转存</span>
|
|
|
+ </el-button>
|
|
|
+ </el-button-group>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ <player
|
|
|
+ ref="playbacker"
|
|
|
+ :playerinfo="playinfo"
|
|
|
+ class="components-container"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, watch, onBeforeUnmount } from 'vue'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { VideoCamera, Download } from '@element-plus/icons-vue'
|
|
|
+import player from '@/views/pms/video_center/sip/components/player/player.vue'
|
|
|
+import { listChannel, playbackvideo, closeStream, playbackSeek } from '@/api/pms/video/channel'
|
|
|
+import { getDevRecord, startDownloadRecord } from '@/api/pms/video/record'
|
|
|
+
|
|
|
+// 定义props
|
|
|
+interface Device {
|
|
|
+ deviceId: number
|
|
|
+ serialNumber: string
|
|
|
+ [key: string]: any
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ device: Device
|
|
|
+}>()
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const deviceId = ref('')
|
|
|
+const channelId = ref('')
|
|
|
+const streamId = ref('')
|
|
|
+const ssrc = ref('')
|
|
|
+const playurl = ref('')
|
|
|
+const queryDate = ref('')
|
|
|
+const playing = ref(false)
|
|
|
+const vodData = ref({})
|
|
|
+const hisData = ref([])
|
|
|
+const playinfo = ref({})
|
|
|
+const channelList = ref<{value: string, label: string}[]>([])
|
|
|
+const playbackinfo = ref({})
|
|
|
+const timeRange = ref<[string, string] | null>(null)
|
|
|
+const startTime = ref<string | null>(null)
|
|
|
+const endTime = ref<string | null>(null)
|
|
|
+
|
|
|
+// 查询参数
|
|
|
+const queryParams = ref({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ deviceSipId: null as string | null,
|
|
|
+ channelSipId: null as string | null,
|
|
|
+})
|
|
|
+
|
|
|
+const deviceInfo = ref<Device | null>(null)
|
|
|
+const playbacker = ref()
|
|
|
+
|
|
|
+// 监听设备变化
|
|
|
+watch(
|
|
|
+ () => props.device,
|
|
|
+ (newVal) => {
|
|
|
+ deviceInfo.value = newVal
|
|
|
+ if (deviceInfo.value && deviceInfo.value.deviceId !== 0) {
|
|
|
+ queryParams.value.deviceSipId = deviceInfo.value.serialNumber
|
|
|
+ deviceId.value = deviceInfo.value.serialNumber
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+// 初始化
|
|
|
+const init = () => {
|
|
|
+ queryParams.value.deviceSipId = props.device.serialNumber
|
|
|
+ deviceId.value = props.device.serialNumber
|
|
|
+ getList()
|
|
|
+ playinfo.value = {
|
|
|
+ playtype: 'playback',
|
|
|
+ deviceId: props.device.serialNumber,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 组件挂载时执行初始化
|
|
|
+init()
|
|
|
+
|
|
|
+// 查询监控设备通道信息列表
|
|
|
+async function getList() {
|
|
|
+ try {
|
|
|
+ const response = await listChannel(queryParams.value)
|
|
|
+ channelList.value = response.list.map((item: any) => {
|
|
|
+ return { value: item.channelSipId, label: item.channelName }
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取通道列表失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 改变通道
|
|
|
+const changeChannel = () => {
|
|
|
+ playinfo.value.channelId = channelId.value
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化URL
|
|
|
+const initUrl = (data: any) => {
|
|
|
+ if (data) {
|
|
|
+ streamId.value = data.ssrc
|
|
|
+ ssrc.value = data.ssrc
|
|
|
+ playurl.value = data.playurl
|
|
|
+ } else {
|
|
|
+ streamId.value = ''
|
|
|
+ ssrc.value = ''
|
|
|
+ playurl.value = ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取北京时间
|
|
|
+const getBeijingTime = (queryDate: string) => {
|
|
|
+ // 计算与UTC的时区差,对于北京时间来说是8小时
|
|
|
+ let offset = 8 * 60 * 60 * 1000
|
|
|
+ // 加上时区差,得到北京时间
|
|
|
+ let beijingTime = new Date(new Date(queryDate).getTime() - offset)
|
|
|
+ return beijingTime.getTime()
|
|
|
+}
|
|
|
+
|
|
|
+// 录像控制
|
|
|
+const loadDevRecord = async () => {
|
|
|
+ playbacker.value.registercallback('playbackSeek', seekPlay)
|
|
|
+ if (queryDate.value === '' || queryDate.value === null) {
|
|
|
+ ElMessage.error('请选择日期')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (deviceId.value && channelId.value) {
|
|
|
+ const date = getBeijingTime(queryDate.value)
|
|
|
+ const start = Math.floor(date / 1000)
|
|
|
+ const end = Math.floor((date + 24 * 60 * 60 * 1000 - 1) / 1000)
|
|
|
+ const query = {
|
|
|
+ start: start,
|
|
|
+ end: end,
|
|
|
+ }
|
|
|
+ vodData.value = {
|
|
|
+ start: start,
|
|
|
+ end: end,
|
|
|
+ base: start,
|
|
|
+ }
|
|
|
+ setTime(queryDate.value + ' 00:00:00', queryDate.value + ' 23:59:59')
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await getDevRecord(deviceId.value, channelId.value, query)
|
|
|
+ console.log('res :>> ', res)
|
|
|
+ hisData.value = res.data.recordItems
|
|
|
+ if (res.data.recordItems) {
|
|
|
+ const len = hisData.value.length
|
|
|
+ if (len > 0) {
|
|
|
+ if (hisData.value[0].start < start) {
|
|
|
+ hisData.value[0].start = start
|
|
|
+ vodData.value.start = start
|
|
|
+ } else {
|
|
|
+ vodData.value.start = hisData.value[0].start
|
|
|
+ }
|
|
|
+ if (hisData.value[0].end !== 0 && hisData.value[0].end < end) {
|
|
|
+ vodData.value.end = hisData.value[0].end
|
|
|
+ }
|
|
|
+ playback()
|
|
|
+ } else {
|
|
|
+ ElMessage({
|
|
|
+ type: 'warning',
|
|
|
+ message: '请确认设备是否支持录像,或者设备SD卡是否正确插入!',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ElMessage({
|
|
|
+ type: 'warning',
|
|
|
+ message: '请确认设备是否支持录像,或者设备SD卡是否正确插入!',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取录像记录失败:', error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 录像播放
|
|
|
+const playback = async () => {
|
|
|
+ const query = {
|
|
|
+ start: (vodData.value as any).start,
|
|
|
+ end: (vodData.value as any).end,
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (ssrc.value) {
|
|
|
+ await closeStream(deviceId.value, channelId.value, ssrc.value)
|
|
|
+ const res = await playbackvideo(deviceId.value, channelId.value, query)
|
|
|
+ initUrl(res.data)
|
|
|
+ } else {
|
|
|
+ const res = await playbackvideo(deviceId.value, channelId.value, query)
|
|
|
+ initUrl(res.data)
|
|
|
+ }
|
|
|
+ triggerPlay(hisData.value)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('播放录像失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 触发播放
|
|
|
+const triggerPlay = (playTimes: any) => {
|
|
|
+ playbacker.value.playback(playurl.value, playTimes)
|
|
|
+ playing.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 录像播放
|
|
|
+const seekPlay = (s: any) => {
|
|
|
+ const curTime = (vodData.value as any).base + s.hour * 3600 + s.min * 60 + s.second
|
|
|
+ const seekRange = curTime - (vodData.value as any).start
|
|
|
+ if (ssrc.value) {
|
|
|
+ const query = {
|
|
|
+ seek: seekRange,
|
|
|
+ }
|
|
|
+ playbackSeek(deviceId.value, channelId.value, streamId.value, query).then((res) => {
|
|
|
+ playbacker.value.setPlaybackStartTime(curTime)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭播放流
|
|
|
+const closeStreamFunc = async () => {
|
|
|
+ if (playing.value && streamId.value) {
|
|
|
+ try {
|
|
|
+ await closeStream(deviceId.value, channelId.value, streamId.value)
|
|
|
+ streamId.value = ''
|
|
|
+ ssrc.value = ''
|
|
|
+ playurl.value = ''
|
|
|
+ playing.value = false
|
|
|
+ } catch (error) {
|
|
|
+ console.error('关闭流失败:', error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 销毁录像播放器
|
|
|
+const destroy = () => {
|
|
|
+ if (playing.value && streamId.value) {
|
|
|
+ playbacker.value.destroy()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭并销毁
|
|
|
+const closeDestroy = () => {
|
|
|
+ closeStreamFunc()
|
|
|
+ destroy()
|
|
|
+}
|
|
|
+
|
|
|
+// 设置时间
|
|
|
+const timePickerChange = (val: [string, string] | null) => {
|
|
|
+ if (val) {
|
|
|
+ setTime(val[0], val[1])
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const setTime = (startTimeVal: string, endTimeVal: string) => {
|
|
|
+ startTime.value = startTimeVal
|
|
|
+ endTime.value = endTimeVal
|
|
|
+ timeRange.value = [startTimeVal, endTimeVal]
|
|
|
+}
|
|
|
+
|
|
|
+// 下载录像
|
|
|
+const downloadRecord = async () => {
|
|
|
+ if (!startTime.value || !endTime.value) {
|
|
|
+ ElMessage.error('请选择时间范围')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const start = Math.floor(new Date(startTime.value).getTime() / 1000)
|
|
|
+ const end = Math.floor(new Date(endTime.value).getTime() / 1000)
|
|
|
+ const query = {
|
|
|
+ startTime: start,
|
|
|
+ endTime: end,
|
|
|
+ speed: '4',
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await startDownloadRecord(deviceId.value, channelId.value, query)
|
|
|
+ console.log('开始转存到流服务器:' + deviceId.value + ' : ' + channelId.value)
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage({
|
|
|
+ type: 'success',
|
|
|
+ message: '转存到流服务器,请前往视频中心->录像管理查看!',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('开始下载录像失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 组件卸载前执行
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ closeStreamFunc()
|
|
|
+})
|
|
|
+</script>
|