Browse Source

在线设备直播

李富豪 1 year ago
parent
commit
c878970ecd

+ 1 - 1
Web/src/api/http/config.ts

@@ -5,7 +5,7 @@ export const CURRENT_CONFIG = {
   appLicense: 'ZK7Dzih4Qc9JCZhDiyDsWJwTW+1rhnnzT1SqDxbdSPVV24bbDC4r1KNjXo7tIPBnPne7ipnXeefP0lJ0OHvxMpkKiag5lFCIndKSvYYdQkyScT3dahCXjmYsd0YyWyHj4tvXoR2DRVq1PdBHLB1iUo2FGLCIZ8QHbGyqglyGdHY=',
   // livestreaming
   // RTMP  Note: This IP is the address of the streaming server. If you want to see livestream on web page, you need to convert the RTMP stream to WebRTC stream.
-  rtmpURL: 'rtmp://203.156.230.22:19350/rlive/stream_27?sign=nwo03DJb', // Example: 'rtmp://192.168.1.1/live/'
+  rtmpURL: 'rtmp://203.156.230.22:19350/rlive/stream_27?sign=nwo03DJb',
   // GB28181 Note:If you don't know what these parameters mean, you can go to Pilot2 and select the GB28181 page in the cloud platform. Where the parameters same as these parameters.
   /*gbServerIp: 'Please enter the server ip.',
   gbServerPort: 'Please enter the server port.',

+ 29 - 27
Web/src/components/GMap.vue

@@ -29,44 +29,43 @@
     </div>
     <!-- 飞机OSD -->
     <div v-if="osdVisible.visible && !osdVisible.is_dock" v-drag-window class="osd-panel fz12">
-      <div class="drag-title pl5 pr5 flex-align-center flex-row flex-justify-between"
+      <div class="pl5 pr5 flex-align-center flex-row flex-justify-between"
         style="border-bottom: 1px solid #515151; height: 18%;">
-        <span>{{ osdVisible.callsign }}</span>
-        <span>
-          <a class="fz16" style="color: white;" @click="() => osdVisible.visible = false">
-            <CloseOutlined />
-          </a>
-        </span>
+        <div class="drag-title">
+          <span>{{ osdVisible.callsign }}</span>
+        </div>
+        <a class="fz16" style="color: white;" @click="() => osdVisible.visible = false">
+          <CloseOutlined />
+        </a>
       </div>
       <div style="height: 82%;">
-        <div class="flex-column flex-align-center flex-justify-center"
-          style="margin-top: -5px; padding-top: 25px; float: left; width: 60px; background: #2d2d2d;">
-          <a-tooltip :title="osdVisible.model">
-            <div style="width: 90%;" class="flex-column flex-align-center flex-justify-center">
-              <span><a-image :src="M30" :preview="false" /></span>
-              <span>{{ osdVisible.model }}</span>
-            </div>
-          </a-tooltip>
-        </div>
         <div class="osd">
           <a-row>
-            <a-col span="16"
-              :style="deviceInfo.device.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;' : 'color: rgb(25,190,107)'">
-              <span>
+            <a-col span="4">
+              <a-tooltip :title="osdVisible.model">
+                <div style="width: 90%;" class="flex-column flex-align-center flex-justify-center">
+                  <span><a-image :src="M30" :preview="false" /></span>
+                  <span>{{ osdVisible.model }}</span>
+                </div>
+              </a-tooltip>
+            </a-col>
+            <a-col span="20">
+              <span
+                :style="deviceInfo.device.mode_code === EModeCode.Disconnected ? 'color: red; font-weight: 700;' : 'color: rgb(25,190,107)'">
                 {{ EModeCode[deviceInfo.device.mode_code] }}
               </span>
               <span>
                 飞行状态
               </span>
-            </a-col>
-          </a-row>
-          <a-row>
-            <a-col span="8">
-              <span class="openLiveButton">
+              <div class="openLiveButton" @click="state.deviceLiveStatus = true" v-if="!state.deviceLiveStatus">
                 开启直播
-              </span>
+              </div>
+              <div class="openLiveButton" @click="state.deviceLiveStatus = false" v-else>
+                关闭直播
+              </div>
             </a-col>
           </a-row>
+          <DeviceLive :sn="osdVisible.sn" v-if="state.deviceLiveStatus" />
           <a-row>
             <a-col span="6">
               <a-tooltip title="GPS卫星数">
@@ -541,6 +540,7 @@ import {
 } from '@ant-design/icons-vue'
 import { EDeviceTypeName, ELocalStorageKey } from '../types'
 import DockControlPanel from './g-map/DockControlPanel.vue'
+import DeviceLive from './deviceLive/index.vue'
 import { useDockControl } from './g-map/use-dock-control'
 import DroneControlPanel from './g-map/DroneControlPanel.vue'
 import { useConnectMqtt } from './g-map/use-connect-mqtt'
@@ -572,6 +572,7 @@ export default defineComponent({
     ArrowDownOutlined,
     DockControlPanel,
     DroneControlPanel,
+    DeviceLive,
     CarryOutOutlined,
     RocketOutlined,
     LivestreamOthers,
@@ -592,6 +593,7 @@ export default defineComponent({
       currentType: '',
       coverIndex: 0,
       isFlightArea: false,
+      deviceLiveStatus: false,// 设备直播状态
     })
     const str: string = '--'
     const deviceInfo: any = reactive({
@@ -1104,7 +1106,7 @@ export default defineComponent({
   background: #000;
   color: #fff;
   border-radius: 2px;
-  opacity: 0.8;
+  background-color: rgba(0, 0, 0, 0.8);
 }
 
 .openLiveButton {
@@ -1115,7 +1117,7 @@ export default defineComponent({
 
 .osd>div:not(.dock-control-panel) {
   margin-top: 5px;
-  padding-left: 5px;
+  padding: 5px;
 }
 
 .circle {

+ 191 - 0
Web/src/components/deviceLive/index.vue

@@ -0,0 +1,191 @@
+<template>
+  <div class="deviceLive">
+    <div class="deviceLive-frames">
+      <EasyPlayer :url="state.playerUrl" />
+    </div>
+    <div class="deviceLive-operation">
+      <a-select style="width:150px;margin-right: 13px;" placeholder="摄像头" v-model:value="state.cameraValue">
+        <a-select-option v-for="item in state.cameraList" :key="item.value" :value="item.value"
+          @click="onCameraSelect(item)">
+          {{ item.label }}
+        </a-select-option>
+      </a-select>
+      <a-select style="width:150px;margin-right: 13px;" placeholder="清晰度" v-model:value="state.clarityValue"
+        @select="onClaritySelect">
+        <a-select-option v-for="item in clarityList" :key="item.value" :value="item.value">
+          {{ item.label }}
+        </a-select-option>
+      </a-select>
+      <a-button style="margin-right: 13px;" type="primary" large @click="onStart">播放</a-button>
+      <a-button type="primary" large @click="onStop">停止</a-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, onMounted } from 'vue';
+import { message } from 'ant-design-vue';
+import EasyPlayer from '/@/components/easyPlayer/index.vue';
+import { CURRENT_CONFIG as config } from '/@/api/http/config';
+import { getLiveCapacity, startLivestream, stopLivestream } from '/@/api/manage';
+
+interface Props {
+  sn: string,
+};
+
+const props = withDefaults(defineProps<Props>(), {
+
+});
+
+interface SelectOption {
+  value: any,
+  label: string,
+  more?: any
+}
+
+const clarityList: SelectOption[] = [
+  {
+    value: 0,
+    label: '自适应'
+  },
+  {
+    value: 1,
+    label: '流畅'
+  },
+  {
+    value: 2,
+    label: '标清'
+  },
+  {
+    value: 3,
+    label: '高清'
+  },
+  {
+    value: 4,
+    label: '超清'
+  }
+]
+
+interface State {
+  cameraList: SelectOption[],
+  cameraValue?: string,
+  videoList: SelectOption[],
+  videoValue?: string,
+  clarityValue: number,
+  videoId: string,
+  playerUrl: string,
+}
+
+const state: State = reactive({
+  cameraList: [],
+  cameraValue: undefined,
+  videoList: [],
+  videoValue: undefined,
+  clarityValue: 0,
+  videoId: '',
+  playerUrl: '',
+})
+
+const fetchLiveCapacity = async () => {
+  try {
+    const res = await getLiveCapacity({});
+    if (res.code === 0) {
+      const deviceInfo = res.data.filter((item: any) => item.sn === props.sn)[0];
+      const cameraList = deviceInfo.cameras_list.map((item: any) => {
+        return {
+          label: item.name,
+          value: item.index,
+          more: item.videos_list
+        }
+      })
+      state.cameraList = cameraList;
+    }
+  } catch (e: any) {
+    message.destroy();
+    message.error(e);
+  }
+}
+
+onMounted(async () => {
+  await fetchLiveCapacity()
+})
+
+const onCameraSelect = (record: SelectOption) => {
+  state.cameraValue = record.value;
+  if (!record.more) {
+    return
+  }
+  const videoList = record.more.map((ele: any) => {
+    return {
+      label: ele.type,
+      value: ele.index,
+      more: ele.switch_video_types
+    }
+  })
+  state.videoList = videoList;
+  if (videoList.length === 0) {
+    return;
+  }
+  const firstVideo: SelectOption = videoList[0];
+  state.videoValue = firstVideo.value;
+}
+
+const onClaritySelect = (value: any) => {
+  state.clarityValue = value;
+}
+
+const onStart = async () => {
+  const { cameraValue, videoValue } = state;
+  if (!cameraValue) {
+    return message.warn('请选择摄像头');
+  }
+  const videoId = `${props.sn}/${cameraValue}/${videoValue || 'normal-0'}`;
+  state.videoId = videoId;
+  const liveURL = config.rtmpURL;
+  try {
+    const res = await startLivestream({
+      url: liveURL,
+      video_id: videoId,
+      url_type: 1,// RTMP 
+      video_quality: state.clarityValue
+    });
+    const playerUrl = res.data.url
+    state.playerUrl = playerUrl;
+    message.success('已开启直播');
+  } catch (e: any) {
+    message.destroy();
+    message.error(e);
+  }
+}
+
+const onStop = async () => {
+  const { cameraValue, videoValue } = state;
+  if (!cameraValue) {
+    return message.warn('请选择摄像头');
+  }
+  const videoId = `${props.sn}/${cameraValue}/${videoValue || 'normal-0'}`;
+  const res = await stopLivestream({
+    video_id: videoId,
+  });
+  if (res.code === 0) {
+    state.videoId = '';
+    state.playerUrl = '';
+    message.success('已停止直播');
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.deviceLive {
+  width: 100%;
+
+  &-frames {
+    width: 100%;
+    height: 300px;
+  }
+
+  &-operation {
+    margin-top: 20px;
+  }
+}
+</style>

+ 0 - 4
Web/src/pages/page-web/projects/trajectory/index/index.vue

@@ -1,8 +1,5 @@
 <template>
   <div class="trajectoryList">
-    <div style="width: 100%;height: 400px;">
-      <EasyPlayer :url="'webrtc://smsp.jkec.info:18000/rtc/stream_27'" />
-    </div>
     <Search :mode="state.mode" :selectedRowKeys="state.selectedRowKeys" :onClickDownload="onClickBatchDownload"
       :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
     <div style="background: #FFFFFF;padding: 20px;">
@@ -95,7 +92,6 @@ import Search from './components/Search.vue';
 import fileSrc from '/@/assets/media/file.svg';
 import { apis } from '/@/api/custom';
 import router from '/@/router/index';
-import EasyPlayer from '/@/components/easyPlayer/index.vue';
 
 interface State {
   listLoading: boolean,