yanghao 8 tuntia sitten
vanhempi
commit
01da587917

+ 1 - 1
.env.local

@@ -4,7 +4,7 @@ NODE_ENV=development
 VITE_DEV=true
 
 # 请求路径
-VITE_BASE_URL='http://172.21.10.222:8080' # 192.168.188.149:48080 172.21.10.222:8080
+VITE_BASE_URL='http://192.168.188.149:8080' # 192.168.188.149:48080 172.21.10.222:8080
 
 # MQTT服务地址
 VITE_MQTT_SERVER_URL = 'ws://localhost:8083/mqtt'

+ 1 - 1
src/api/pms/video/channel.ts

@@ -54,7 +54,7 @@ export function getStreaminfo(deviceId, channelId) {
   })
 }
 
-export function playback(deviceId, channelId, query) {
+export function playbackvideo(deviceId, channelId, query) {
   return request.get({
     url: '/rq/sip/player/playback/' + deviceId + '/' + channelId,
 

+ 3 - 3
src/api/pms/video/record.ts

@@ -2,7 +2,7 @@ import request from '@/config/axios'
 
 export function getDevRecord(deviceId,channelId,query) {
   return request.get({
-    url: '/sip/record/devquery/' + deviceId + "/" + channelId,
+    url: '/rq/sip/record/devquery/' + deviceId + "/" + channelId,
  
     params: query
   })
@@ -65,14 +65,14 @@ export function getServerRecordByDevice(query) {
 
 export function startPlayRecord(deviceId, channelId) {
   return request.get({
-    url: '/sip/record/play/' + deviceId + "/" + channelId,
+    url: '/rq/sip/record/play/' + deviceId + "/" + channelId,
    
   })
 }
 
 export function startDownloadRecord(deviceId, channelId, query) {
   return request.get({
-    url: '/sip/record/download/' + deviceId + "/" + channelId,
+    url: '/rq/sip/record/download/' + deviceId + "/" + channelId,
  
     params: query
   })

+ 3 - 3
src/views/pms/video_center/device/device-edit.vue

@@ -330,7 +330,7 @@
           <device-live-stream ref="deviceLiveStreamRef" :device="form" />
         </el-tab-pane>
 
-        <!-- <el-tab-pane
+        <el-tab-pane
           name="sipVideo"
           :disabled="form.deviceId === 0"
           v-if="form.deviceType === 3"
@@ -338,7 +338,7 @@
         >
           <template #label>{{ t('device.device-edit148398-46') }}</template>
           <device-video ref="deviceVideoRef" :device="form" />
-        </el-tab-pane> -->
+        </el-tab-pane>
 
         <!-- <el-tab-pane name="ossRecord" :disabled="form.deviceId === 0" v-if="form.deviceType === 3">
           <template #label>{{ t('device.device-edit148398-79') }}</template>
@@ -631,7 +631,7 @@ import productList from './product-list.vue'
 // import deviceModbusTask from './device-modbus-task.vue'
 // import deviceTimer from './device-timer.vue'
 import channel from '../sip/channel.vue'
-// import deviceVideo from '@/views/components/player/deviceVideo.vue'
+import deviceVideo from '@/views/pms/video_center/sip/components/player/deviceVideo.vue'
 // import OssRecord from '@/views/iot/record/record-oss.vue'
 import deviceLiveStream from '../sip/components/player/deviceLiveStream.vue'
 import sipid from '../sip/sipidGen.vue'

+ 367 - 2
src/views/pms/video_center/sip/components/player/deviceVideo.vue

@@ -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>