Newer
Older
safe_production_front / src / views / ycjg / ssjk / control-media.vue
<!-- 流媒体 -->
<script lang="ts" setup name="videoControl">
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import html2canvas from 'html2canvas'
import type { Ref } from 'vue'
import { getCurrentInstance, nextTick, reactive, ref } from 'vue'
import dayjs from 'dayjs'
import { getMediaStream, getMediaToken } from '@/api/ycjg/media'
import { debounce } from 'lodash-es'
import useWebsocketStore from '@/store/modules/websocket'
import { getDevInfo, ptzControl, restartDev } from '@/api/ptz/dev'
import controlImg from '@/assets/images/control.png'
import {
  devControl,
  devControlWithSpeed,
  devToPosition,
} from '@/api/ptz/control'
import { getPicListPage, pictureAdd } from '@/api/ycjg/ssjk'
import { base64ToBlob, exportFile } from '@/utils/exportUtils'
import { log } from '@/utils/log'
import { getPreviewUrl } from '@/utils/hik'
import { initPlugin, login, logout, preview, stop } from '@/utils/HKVideo'
import { loginDH } from '@/utils/DHVideo'
const websocket = useWebsocketStore()
const $route = useRoute()
const deviceData: Ref<any> = ref({})
const instance = getCurrentInstance()
const videoControl = ref('videoControl')
const loading = ref(false)
const total = ref(0)
const list = ref([])
const refreshHK = ref(true)
const hkPlugin: any = ref(null)
const dhVideo: any = ref(null)
const bottomRef = ref()
const lineList: Ref<any[]> = ref([])
const timeRange = ref<[any, any]>(['', ''])
const mediaToken = ref('') // 流媒体token
const mediaUrl = ref('')
const video = ref(null) as any
const videoRef = ref(null)

const listQuery = ref({
  devId: '',
  startTime: '',
  endTime: '',
  offset: 1,
  limit: 6,
})

// socket更新数据
const unwatch = watch(websocket, (newVal) => {
})

// 搜索重置
function fetchData() {
  search()
}

function search(isNowPage = false) {
  if (!isNowPage) {
    // 是否显示当前页,否则跳转第一页
    listQuery.value.offset = 1
  }
  loading.value = true
  getPicListPage(listQuery.value).then((res: any) => {
    list.value = res.data.rows.map((item: any) => {
      item.url = `${window.localStorage.getItem('baseurl-safe')}/picture/download/${item.url}?token=${window.localStorage.getItem('token')}`
      return item
    })
    total.value = res.data.total
    console.log(list.value)
    loading.value = false
  }).catch(() => {
    loading.value = false
  })
}

// 调整大小
const resize = debounce(() => {
  // if (deviceData.value.nvrManufacture === 'HIKVISION') {
  //   stop(0)
  //   refreshHK.value = true
  //   nextTick(() => {
  //     initPlugin(hkPlugin.value.clientWidth, hkPlugin.value.clientHeight, 1, '', false, () => {
  //       setTimeout(() => {
  //         preview(deviceData.value.nvrIp, '80', deviceData.value.nvrUser, deviceData.value.nvrPassword, 0, true, deviceData.value.nvrChannel)
  //       }, 1000)
  //     }, () => {
  //     })
  //   })
  // }
}, 1000)

const handleResize = () => {
  if (deviceData.value.nvrManufacture === 'HIKVISION') {
    refreshHK.value = false
  }
  resize()
}

function dataURLtoBlob(dataurl: any) {
  const arr = dataurl.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)
  }
  return new Blob([u8arr], { type: mime })
}

// 抓拍
async function takePhoto() {
  const time = dayjs()
  log('视频抓拍', `${deviceData.value.monitorName}_抓拍时间:${time.format('YYYY-MM-DD HH:mm:ss')}`)
  const name = `${deviceData.value.monitorName}_${time.format('YYYY-MM-DD HH_mm_ss')}`
  console.log('流媒体抓拍')
  // const canvas: any = document.getElementById('myCanvas')
  // // const canvas: any = document.createElement('canvas')
  // const ctx = canvas!.getContext('2d')
  // canvas.width = video.value.videoWidth
  // canvas.height = video.value.videoHeight
  // ctx.drawImage(video.value, 0, 0, canvas.width, canvas.height)
  // // domToPic(bottomRef.value, time)
  // console.log(canvas.toDataURL('image/png'))

  const player: any = document.getElementById('myVideo') // 获取video的Dom节点
  player.setAttribute('crossOrigin', 'anonymous') // 添加srossOrigin属性,解决跨域问题
  const canvas = document.createElement('canvas')
  // const canvas: any = document.getElementById('myCanvas')
  const img = document.createElement('img')
  img.crossOrigin = 'anonymous' // 这行很重要
  canvas.width = video.value.videoWidth
  canvas.height = video.value.videoHeight
  canvas.getContext('2d')!.drawImage(player, 0, 0, canvas.width, canvas.height) // 截图
  img.src = canvas.toDataURL('image/png', 1)+ '?time=' + new Date()

  // img.src = canvas.toDataURL('image/png', 1)
  // image.onload = () => {
  //   canvas.width = 500;
  //   canvas.height = 500;
  //   ctx.drawImage(image, 0, 0, 500, 500);
  //   var imgSrc = canvas.toDataURL('image/png', 1);
    // console.log(imgSrc);
  // }

  // const imgUrl = canvas.toDataURL('image/png') // 将图片转成base64格式

  // const aLink = document.createElement('a')
  // aLink.id = 'qrcodeimg'
  // aLink.href = imgUrl
  // aLink.download = `${deviceData.value.monitorName}_${time.format('YYYY-MM-DD HH_mm_ss')}.png`	// 导出文件名
  // document.body.appendChild(aLink)
  // // 模拟a标签点击事件
  // aLink.click()
  // // 事件已经执行,删除本次操作创建的a标签对象
  // document.body.removeChild(aLink)
  // // 上传到服务器
  // const jpg = canvas.toDataURL('image/jpeg')
  // console.log(jpg);

  // setTimeout(() => {
  //   pictureAdd({
  //     devId: $route.query.id,
  //     picture: jpg,
  //   }).then((res: any) => {
  //     ElMessage.success('抓图成功')
  //     search()
  //   })
  // }, 3000)

  // img.src = dataURL
  // img.width = player.clientWidth-200;   //控制截出来的图片宽的大小
  // img.height = player.clientHeight-200; //控制截出来的图片高的大小
  // img.style.border="1px solid #333333"   //控制截出来的图片边框的样式
  // document.getElementById("cutImage").appendChild(img);   //显示在页面中
  // this.downFile(dataURL, "图片.jpg");   //下载截图

  // canvas.toDataURL('image/png')
  // const a = document.createElement('a');
  // a.href = canvas.toDataURL('image/png');
  // a.download = `${Date.now()}`;
  // a.click();
}

// html2canvas此方法video截不了
function domToPic(dom: any, time: any) {
  html2canvas(dom).then((res) => {
    // 下载到本地
    const imgUrl = res.toDataURL('image/png')
    console.log('抓图url', imgUrl)
    const aLink = document.createElement('a')
    aLink.id = 'qrcodeimg'
    aLink.href = imgUrl
    aLink.download = `${deviceData.value.monitorName}_${time.format('YYYY-MM-DD HH_mm_ss')}.png`	// 导出文件名
    document.body.appendChild(aLink)
    // 模拟a标签点击事件
    aLink.click()
    // 事件已经执行,删除本次操作创建的a标签对象
    document.body.removeChild(aLink)
    // 上传到服务器
    const jpg = res.toDataURL('image/jpeg')
    pictureAdd({
      devId: $route.query.id,
      picture: jpg,
    }).then((res: any) => {
      ElMessage.success('抓图成功')
      search()
    })
  })
}
// 恢复
async function handleRealplay() {
  video.value.play()
  ElMessage.success('已恢复')
}
// 暂停
function handleStopPlay() {
  video.value.pause()
  ElMessage.success('已暂停')
}

// 放大
function zoomIn(action: string) {
  if (deviceData.value.deviceTypeName === '枪机') {
    ElMessage.warning('类型为枪机的设备,不可进行控制!')
    return false
  }
  const params = {
    command: action,
    horizonSpeed: '30',
    verticalSpeed: '30',
    zoomSpeed: '30',
  }
  ptzControl(deviceData.value.nvrIndexCode, deviceData.value.cameraIndexCode, params).then((res) => {})
}

// 上下左右
function controlWithSpeed(event: any, type: any) {
  if (deviceData.value.deviceTypeName === '枪机' && type !== 'stop') {
    ElMessage.warning('类型为枪机的设备,不可进行控制!')
    return false
  }
  if (event.detail === 1) {
    const params = {
      command: type,
      zoomSpeed: '30',
      horizonSpeed: '30',
      verticalSpeed: '30',
    }
    ptzControl(deviceData.value.nvrIndexCode, deviceData.value.cameraIndexCode, params).then((res) => {})
  }
}

function handleSizeChange(val: number) {
  // emit('change', { size: val })
  listQuery.value.limit = val
  search(true)
}

// 改变当前页
function handleCurrentChange(val: number) {
  // emit('change', { page: val })
  listQuery.value.offset = val
  search(true)
}
const disabledPastDateStart = (date: any) => {
  if (listQuery.value.endTime !== '') {
    return date.getTime() > new Date(`${listQuery.value.endTime} 23:59:59`).getTime()
  }
  else {
    return null
  }
}

const disabledPastDateEnd = (date: any) => {
  if (listQuery.value.startTime) {
    return date.getTime() < new Date(`${listQuery.value.startTime} 00:00:00`).getTime()
  }
  else {
    return null
  }
}

// 拉取流
async function fetchMediaStream(deviceId: string, channelId: string) {
  loading.value = true
  const res = await getMediaStream(deviceId, channelId, mediaToken.value)
  loading.value = false
  if (res && res.data) {
    mediaUrl.value = res.data.fmp4
    return res.data.fmp4
  }
  else {
    ElMessage.warning('设备未注册')
  }
}

watch(timeRange, (val) => {
  if (val) {
    listQuery.value.startTime = `${val[0]}`
    listQuery.value.endTime = `${val[1]}`
  }
  else {
    listQuery.value.startTime = ''
    listQuery.value.endTime = ''
  }
})

onMounted(() => {
  if ($route.query && $route.query.id) {
    listQuery.value.devId = $route.query.id!.toString()
    getDevInfo(listQuery.value.devId).then((response) => {
      deviceData.value = response.data
      setTimeout(() => {
        video.value = document.getElementById('myVideo')
        // 获取流媒体token
        getMediaToken().then((res: any) => {
          mediaToken.value = res.data
          fetchMediaStream(deviceData.value.cameraIndexCode, deviceData.value.nvrIndexCode)
        })
        handleResize()
        window.addEventListener('resize', handleResize)
      }, 200)
    })
    fetchData()
  }
})

onBeforeUnmount(() => {
  try {
    video.value = null
  }
  catch (e) {}
  unwatch()
  try {
    // logout(deviceData.value.nvrIp)
    // stop(0)
  }
  catch (e) {}
  window.removeEventListener('resize', handleResize)
})
</script>

<template>
  <div class="video-wrap">
    <video id="myVideo" ref="videoRef" :src="mediaUrl" controls muted autoPlay class="videoControl" />
    <!-- <canvas id="myCanvas" style="height: calc(100% - 165px);width: calc(100% - 390px);"/> -->
    <!-- <div class="right">
      <el-tabs tab-position="top" style="height: calc(100% - 30px);width: 100%;margin-bottom: 10px;margin-top: -10px;">
        <el-tab-pane label="历史截图">
          <div style="display: flex;margin-bottom: 10px">
            <el-date-picker
              v-model="timeRange" type="daterange" clearable size="small"
              format="YYYY-MM-DD" value-format="YYYY-MM-DD"
              start-placeholder="开始时间" end-placeholder="结束时间" range-separator="到"
              style="margin-right: 20px"
            />
            <el-button type="primary" @click="fetchData" style="width: 60px" size="small">查 询</el-button>
          </div>
          <div style="background-color: white;overflow-y: hidden;overflow-x: scroll;width: 100%;display: flex;flex-wrap: wrap;">
            <div v-for="(item, index) in list" :key="index" style="height: 100px;width: 160px;margin: 5px">
              <el-image style="width: 160px; height: 100px;" :src="item.url" fit="fill" />
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
      <div style="z-index: 99999999;margin: 10px 10px 0;">
        <el-pagination
          small
          :current-page="listQuery.offset"
          :page-sizes="[6]"
          :page-size="listQuery.limit"
          :total="total"
          layout="total,prev,pager,next"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </div> -->
    <div class="bottom" ref="bottomRef">
      <div class="control-title">
        设备控制
      </div>
      <div name="four" style="width: 150px; height: 130px;">
        <el-image style="width: 130px; height: 130px;position: absolute;top: 5px;left: 50px;" :src="controlImg" fit="fill" />
        <div>
          <div class="round-btn" style="top: 8px;left: 95px;" @mousedown="controlWithSpeed($event, 'up')" @mouseup="controlWithSpeed($event, 'stop')" />
          <div class="round-btn" style="top: 90px;left: 95px;" @mousedown="controlWithSpeed($event, 'down')" @mouseup="controlWithSpeed($event, 'stop')" />
          <div class="round-btn" style="top: 48px;left: 53px;" @mousedown="controlWithSpeed($event, 'left')" @mouseup="controlWithSpeed($event, 'stop')" />
          <div class="round-btn" style="top: 48px;left: 135px;" @mousedown="controlWithSpeed($event, 'right')" @mouseup="controlWithSpeed($event, 'stop')" />
        </div>
      </div>
      <div style="display: flex;max-width: 250px;flex-wrap: wrap;margin: 0 20px;">
        <el-button type="primary" class="btn" @click="handleStopPlay">
          <el-icon style="margin-right: 5px;">
            <svg-icon name="icon-stop" />
          </el-icon>暂 停
        </el-button>
        <el-button type="primary" class="btn" @click="handleRealplay">
          <el-icon style="margin-right: 5px;">
            <svg-icon name="icon-play" />
          </el-icon>恢 复
        </el-button>
        <el-button :disabled="deviceData.deviceTypeName === '枪机'" type="primary" class="btn" @mousedown="zoomIn('zoomin')" @mouseup="zoomIn('stop')">
          <el-icon style="margin-right: 5px;">
            <svg-icon name="icon-zoomin" />
          </el-icon>放 大
        </el-button>
        <el-button :disabled="deviceData.deviceTypeName === '枪机'" type="primary" class="btn" @mousedown="zoomIn('zoomout')" @mouseup="zoomIn('stop')">
          <el-icon style="margin-right: 5px;">
            <svg-icon name="icon-zoomout" />
          </el-icon>缩 小
        </el-button>
        <!-- <el-button type="primary" class="btn" @click="takePhoto">
          <el-icon style="margin-right: 5px;">
            <svg-icon name="icon-camera" />
          </el-icon>抓 拍
        </el-button> -->
      </div>
      <div style="width: calc(100% - 350px);height: 100%;display: flex;background-color: rgb(255 255 255 / 65%);border-radius: 10px;">
        <el-tabs v-if="deviceData !== {}" tab-position="left" style="height: 100%;width: 100%;">
          <el-tab-pane label="设备信息">
            <el-descriptions style="flex: 1;" class="margin-top" :column="3" border>
              <el-descriptions-item label="设备名称">
                {{ deviceData.monitorName }}
              </el-descriptions-item>
              <el-descriptions-item label="设备类型">
                {{ deviceData.deviceTypeName }}
              </el-descriptions-item>
              <el-descriptions-item label="所属单位">
                {{ deviceData.deptName }}
              </el-descriptions-item>
              <el-descriptions-item label="所属区域">
                {{ deviceData.areaName }}
              </el-descriptions-item>
              <el-descriptions-item label="经度">
                {{ deviceData.longitude }}
              </el-descriptions-item>
              <el-descriptions-item label="纬度">
                {{ deviceData.latitude }}
              </el-descriptions-item>
              <el-descriptions-item label="位置">
                {{ deviceData.location }}
              </el-descriptions-item>
              <el-descriptions-item label="备注">
                {{ deviceData.description }}
              </el-descriptions-item>
            </el-descriptions>
          </el-tab-pane>
        </el-tabs>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.video-wrap {
  display: flex;
  flex-wrap: wrap;
}

.videoControl {
  height: calc(100% - 165px);
  // width: calc(100% - 390px);
  width: calc(100% - 20px);
  margin-left: 10px;
  margin-top: 10px;
  position: absolute;
  background-color: #111;
}

.right {
  height: calc(100% - 165px);
  width: 360px;
  background-color: white;
  border-radius: 10px;
  padding: 10px;
  position: absolute;
  top: 10px;
  right: 10px;
}

.bottom {
  width: calc(100% - 10px);
  height: 140px;
  position: absolute;
  left: 0;
  bottom: 5px;
  display: flex;
  border-radius: 10px;
  margin: 5px;
  box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
  overflow: hidden;
  padding: 5px;
  justify-content: space-between;
}

.control-title {
  color: white;
  background-color: #ff9f11;
  border-bottom-right-radius: 10px;
  border-top-right-radius: 10px;
  line-height: 30px;
  font-weight: bold;
  text-align: center;
  letter-spacing: 1px;
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
  writing-mode: vertical-rl;
  text-orientation: upright;
  width: 30px;
  height: 100px;
}

.btn {
  margin: 3px auto;
  width: 100px;
}

.round-btn {
  position: absolute;
  top: 14px;
  left: 129px;
  width: 42px;
  height: 42px;
  z-index: 111111;
  border-radius: 21px;
  cursor: pointer;
}

.round-btn:hover {
  background-color: rgb(61 125 254 / 53%);
}
</style>