Browse Source

森防视频 对接ptz接口,实现截图功能

Hwf 3 months ago
parent
commit
09ae413bf0

+ 9 - 0
src/api/globalMap/forestDefenseVideo.ts

@@ -0,0 +1,9 @@
+import request from '@/utils/request';
+
+export const getPtzInfo = (params) => {
+  return request({
+    url: '/api/videoResource/hkvideo/get_ptz_info',
+    method: 'get',
+    params: params
+  });
+};

+ 42 - 17
src/components/HKVideo/hikvision-h5player.vue

@@ -8,6 +8,7 @@
 
 <script setup lang="ts">
 import { reactive, onMounted, onActivated, nextTick, onBeforeUnmount } from 'vue';
+import { parseTime } from '@/utils/ruoyi';
 
 const play = async (url) => {
   state.wsUrl = url;
@@ -19,30 +20,24 @@ const stop = async () => {
 };
 
 const playback = async (url, startTime, endTime) => {
-  console.log('playback', url, state.mode, startTime, endTime);
+  // console.log('playback', url, state.mode, startTime, endTime);
   startTime += 'Z';
   endTime += 'Z';
   state.player &&
     state.player.JS_Play(url, { playURL: url, mode: 1 }, 0, startTime, endTime).then(
       () => {
-        console.log('playback success 播放成功');
+        // console.log('playback success 播放成功');
         state.player && state.player.JS_Resize();
         state.isLoading = false;
         emits('onPlaying');
       },
       (e) => {
         emits('onPlayError');
-        console.error('playerror', e);
+        // console.error('playerror', e);
       }
     );
 };
 
-defineExpose({
-  play,
-  stop,
-  playback
-});
-
 const emits = defineEmits(['onPlaying', 'onPlayError']);
 
 const state = reactive({
@@ -97,11 +92,11 @@ const createPlayer = () => {
   state.player.JS_SetWindowControlCallback({
     windowEventSelect: function (iWndIndex) {
       //插件选中窗口回调
-      console.log('windowSelect callback: ', iWndIndex);
+      // console.log('windowSelect callback: ', iWndIndex);
     },
     pluginErrorHandler: function (iWndIndex, iErrorCode, oError) {
       //插件错误回调
-      console.log('插件错误回调pluginError callback: ', iWndIndex, iErrorCode, oError);
+      // console.log('插件错误回调pluginError callback: ', iWndIndex, iErrorCode, oError);
       // setTimeout(() => {
       //   realplay(state.wsUrl);
       // }, 100);
@@ -122,18 +117,18 @@ const createPlayer = () => {
     },
     windowFullCcreenChange: function (bFull) {
       //全屏切换回调
-      console.log('全屏切换回调fullScreen callback: ', bFull);
-      if (!bFull) {
-        console.log('退出全屏');
-      }
+      // console.log('全屏切换回调fullScreen callback: ', bFull);
+      // if (!bFull) {
+      //   console.log('退出全屏');
+      // }
     },
     firstFrameDisplay: function (iWndIndex, iWidth, iHeight) {
       //首帧显示回调
-      console.log('firstFrame loaded callback: ', iWndIndex, iWidth, iHeight);
+      // console.log('firstFrame loaded callback: ', iWndIndex, iWidth, iHeight);
     },
     performanceLack: function () {
       //性能不足回调
-      console.log('performanceLack callback: ');
+      // console.log('performanceLack callback: ');
       stopPlay();
     }
   });
@@ -190,6 +185,36 @@ const fullScreen = (type) => {
       }
     );
 };
+
+// 截图功能
+const handleScreenshot = (name) => {
+  const video = document.getElementById(state.id + '_playVideo0'); // 使用保存的引用或直接获取
+  if (!video) {
+    console.error('Video element not found!');
+    return;
+  }
+  const canvas = document.createElement('canvas');
+  canvas.width = video.videoWidth;
+  canvas.height = video.videoHeight;
+  const context = canvas.getContext('2d');
+  context.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+  let time = new Date().getTime();
+  let fileName = name ? name : '';
+  fileName += '截图' + time + '.png';
+  const dataURL = canvas.toDataURL('image/png');
+  const a = document.createElement('a');
+  a.href = dataURL;
+  a.download = fileName;
+  a.click();
+};
+
+defineExpose({
+  play,
+  stop,
+  playback,
+  handleScreenshot
+});
 </script>
 
 <style lang="scss">

+ 9 - 7
src/components/HKVideo/index.vue

@@ -7,7 +7,7 @@
         style="width: 100%; height: 100%; object-fit: fill"
         @on-playing="onHkPlaying"
         @on-play-error="onHKPlayError"
-      ></HikvisionPlayer>
+      />
       <img v-if="posterVisible" class="video-play" src="@/assets/images/video/play.png" alt="" />
       <img v-if="posterVisible && dot_data.poster" class="video-poster" :src="dot_data.poster" />
       <div v-if="errBKVisible" class="err_bk">
@@ -44,11 +44,6 @@ const refresh_data = () => {
   // });
 };
 
-defineExpose({
-  play,
-  refresh_data
-});
-
 const emits = defineEmits(['propClick', 'videoPreviewClick', 'favorClick']);
 
 // https://blog.csdn.net/weixin_49826079/article/details/135147184
@@ -123,7 +118,14 @@ const stop_now = async () => {
   }
   posterVisible.value = true;
 };
-
+const handleScreenshot = (name) => {
+  videoPlayer.value.handleScreenshot(name);
+};
+defineExpose({
+  play,
+  refresh_data,
+  handleScreenshot
+});
 onMounted(() => {
   if (!!props.autoplay) {
     play_now();

+ 61 - 19
src/views/globalMap/RightMenu/ForestDefenseVideo/DetailDialog.vue

@@ -28,7 +28,7 @@
         </div>
       </div>
       <div class="video-box">
-        <HKVideo :dot_data="detailData" autoplay style="height: 100%" />
+        <HKVideo ref="videoRef" :dot_data="detailData" autoplay style="height: 100%" />
       </div>
       <div class="control-container">
         <div class="common-title-box">操作台</div>
@@ -65,7 +65,7 @@
       </div>
       <div class="operate-box">
         <div class="common-btn-primary5">开始巡航</div>
-        <div class="common-btn-primary5">截图</div>
+        <div class="common-btn-primary5" @click="handleScreenshot">截图</div>
         <div class="common-btn-primary5">录像</div>
       </div>
     </div>
@@ -86,6 +86,7 @@ import videoImg from '@/assets/images/dotIcon/33_forest_defense_video.png';
 import VectorLayer from 'ol/layer/Vector';
 import VectorSource from 'ol/source/Vector';
 import Polygon from 'ol/geom/Polygon';
+import { getPtzInfo } from '@/api/globalMap/forestDefenseVideo';
 
 const props = defineProps({
   id: String,
@@ -113,24 +114,26 @@ let detailData = ref({
   lat: 0,
   type: '',
   speed: 0,
-  video_code: '',
-  radius: 0,
-  startAngle: 0,
-  endAngle: 0
+  video_code: ''
+});
+let ptzInfo = ref({
+  Distance: 0,
+  AngelH: 0,
+  AzimuthH: 0
 });
 
 const getData = () => {
+  getPtzInfo({ code: props.id }).then((res) => {
+    ptzInfo.value = res.data;
+  });
   detailData.value = {
-    title: '高州市根子镇上炕村委会23211312',
+    title: '高州市根子镇上炕村委会',
     address: '茂名市高州市坡心东南约400米',
     lng: 110.819207,
     lat: 21.711887,
     type: '云台',
     speed: 246,
-    video_code: '44092251001320000009',
-    radius: 10000,
-    startAngle: 0,
-    endAngle: 30
+    video_code: '44090000001321000033',
   };
   initDot();
 };
@@ -205,11 +208,9 @@ const handleFocusControl = (type) => {
 // 控制视角移动
 const handleControl = (type) => {
   if (type === 'left') {
-    detailData.value.startAngle -= 1;
-    detailData.value.endAngle -= 1;
+    ptzInfo.value.AzimuthH -= 10;
   } else if (type === 'right') {
-    detailData.value.startAngle += 1;
-    detailData.value.endAngle += 1;
+    ptzInfo.value.AzimuthH += 10;
   }
   if (visibleRange.value) {
     createSector();
@@ -218,8 +219,9 @@ const handleControl = (type) => {
 // 新增扇形
 const createSector = () => {
   removeSector();
+  const { startAngle, endAngle } = calculateAngle(ptzInfo.value.AzimuthH, ptzInfo.value.AngelH);
   const center = turf.point([detailData.value.lng, detailData.value.lat]);
-  const sectorData = turf.sector(center, detailData.value.radius / 1000, detailData.value.startAngle, detailData.value.endAngle);
+  const sectorData = turf.sector(center, ptzInfo.value.Distance / 1000, startAngle, endAngle);
   if (AMapType.includes(props.activeMap)) {
     sector = new AMap.Polyline({
       path: sectorData.geometry.coordinates,
@@ -282,7 +284,7 @@ const addVisibleRange = () => {
     // 圆形范围
     circle = new AMap.Circle({
       center: new AMap.LngLat(detailData.value.lng, detailData.value.lat),
-      radius: detailData.value.radius,
+      radius: ptzInfo.value.Distance,
       strokeColor: '#ff0000', // 线颜色
       strokeOpacity: 0.35, // 线透明度
       strokeWeight: 1, // 线宽
@@ -292,7 +294,7 @@ const addVisibleRange = () => {
     map.add(circle);
   } else if (YMapType.includes(props.activeMap)) {
     const center = turf.point([detailData.value.lng, detailData.value.lat]);
-    const sectorData = turf.sector(center, detailData.value.radius / 1000, 0, 360);
+    const sectorData = turf.sector(center, ptzInfo.value.Distance / 1000, 0, 360);
 
     circle = new Feature({
       geometry: new Polygon(sectorData.geometry.coordinates)
@@ -312,7 +314,7 @@ const addVisibleRange = () => {
     // const center = [detailData.value.lng, detailData.value.lat];
     // circle = new Feature({
     //   geometry: new Circle(center, 100)
-    //   // geometry: new Circle(center, detailData.value.radius)
+    //   // geometry: new Circle(center, ptzInfo.value.Distance)
     // });
     // circle.setStyle(
     //   new Style({
@@ -354,6 +356,46 @@ const removeSector = () => {
     }
   }
 };
+const calculateAngle = (azimuthH, angelH) => {
+  // Normalize the azimuthH to be within [0, 360) degrees in deci-degrees
+  azimuthH = azimuthH % 3600; // 360 degrees * 10 = 3600 deci-degrees
+
+  // Convert deci-degrees to degrees for calculations (but we'll still use azimuthH_deci for clarity)
+  const azimuthH_deci = azimuthH;
+  const azimuthH_deg = azimuthH / 10;
+
+  // Calculate the start angle in deci-degrees
+  let startAngle_deci = azimuthH_deci - angelH / 2;
+
+  // Ensure the start angle is within [0, 3600) deci-degrees
+  if (startAngle_deci < 0) {
+    startAngle_deci += 3600;
+  }
+
+  // Convert start angle back to degrees
+  const startAngle_deg = startAngle_deci / 10;
+
+  // Calculate the end angle in deci-degrees
+  let endAngle_deci = azimuthH_deci + angelH / 2;
+
+  // Ensure the end angle is within [0, 3600) deci-degrees
+  if (endAngle_deci >= 3600) {
+    endAngle_deci -= 3600;
+  }
+
+  // Convert end angle back to degrees
+  const endAngle_deg = endAngle_deci / 10;
+
+  return {
+    startAngle: startAngle_deg,
+    endAngle: endAngle_deg
+  };
+};
+// 截图
+let videoRef = ref(null);
+const handleScreenshot = () => {
+  videoRef.value.handleScreenshot(detailData.value.title);
+};
 watch(
   () => props.id,
   () => {

+ 10 - 10
src/views/globalMap/RightMenu/ForestDefenseVideo/index.vue

@@ -108,11 +108,11 @@ const getData = () => {
           num1: 5,
           num2: 5,
           children: [
-            { label: '茂南公馆蒲芦塘村', value: 3 },
-            { label: '茂南区羊角镇禄段横山庙1', value: 4 },
-            { label: '茂南区羊角镇禄段横山庙2', value: 5 },
-            { label: '茂南区羊角镇禄段横山庙3', value: 6 },
-            { label: '茂南区羊角镇禄段横山庙4', value: 7 }
+            { label: '茂南公馆蒲芦塘村', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙1', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙2', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙3', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙4', value: '44090000001321000033' }
           ]
         },
         {
@@ -121,11 +121,11 @@ const getData = () => {
           num1: 5,
           num2: 6,
           children: [
-            { label: '茂南公馆蒲芦塘村', value: 9 },
-            { label: '茂南区羊角镇禄段横山庙1', value: 10 },
-            { label: '茂南区羊角镇禄段横山庙2', value: 11 },
-            { label: '茂南区羊角镇禄段横山庙3', value: 12 },
-            { label: '茂南区羊角镇禄段横山庙4', value: 13 }
+            { label: '茂南公馆蒲芦塘村', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙1', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙2', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙3', value: '44090000001321000033' },
+            { label: '茂南区羊角镇禄段横山庙4', value: '44090000001321000033' }
           ]
         }
       ]

+ 1 - 1
src/views/globalMap/index.vue

@@ -73,7 +73,7 @@ let map2Ref = ref(null);
 let leftMenuRef = ref(null);
 let showMask = ref(true);
 //  vectorgraph satellite imageMap imageMap3 logical satellite2 satellite3
-let activeMap = ref('imageMap');
+let activeMap = ref('satellite');
 // 附近视频菜单数据
 let tempMenu = ref({
   name: '',