Sfoglia il codice sorgente

+ 视频设备播放控制

chen.cheng 7 mesi fa
parent
commit
790d97211b

+ 2 - 0
wx-h5/.env.dev

@@ -4,3 +4,5 @@ VITE_BASE_API=https://aiot.huashoubilin.com:8090/aiot-api
 VITE_BASE_URL=https://aiot.huashoubilin.com:8090/aiot-api
 
 VITE_IMG_CDN=https://aiot.huashoubilin.com:8090/
+
+VITE_STREAM=https://wvp.wenhq.top:40001/

+ 2 - 0
wx-h5/.env.h5dev

@@ -4,3 +4,5 @@ VITE_BASE_API=/aiot-api
 VITE_BASE_URL=https://aiot.huashoubilin.com:8090/aiot-api
 
 VITE_IMG_CDN=https://aiot.huashoubilin.com:8090/
+
+VITE_STREAM=https://wvp.wenhq.top:40001/

+ 2 - 0
wx-h5/.env.h5prod

@@ -4,3 +4,5 @@ VITE_BASE_API=/aiot-api
 VITE_BASE_URL=http://172.192.13.145:8019/aiot-api
 
 VITE_IMG_CDN=http://172.192.13.145:8019
+
+VITE_STREAM=https://wvp.wenhq.top:40001/

+ 2 - 2
wx-h5/src/api/device.js

@@ -73,14 +73,14 @@ export const videoCommand = ({deviceNo, tunnel, command}) => {
 }
 
 export const getCameraList = ({page = Page.page, size = Page.size, ...params}) => {
-  return get(`/api/wvp/api/device/query/devices`, {
+  return get(`/wvp/api/device/query/devices`, {
     page,
     size,
     ...params
   });
 }
 export const getTunnelList = ({page = Page.page, size = Page.size, deviceNo, online = true, ...params}) => {
-  return get(`wvp/api/device/query/devices/${deviceNo}/channels`, {
+  return get(`/wvp/api/device/query/devices/${deviceNo}/channels`, {
     page,
     count: size,
     online,

+ 2 - 1
wx-h5/src/common/config.js

@@ -1,4 +1,5 @@
 export default {
   baseUrl: import.meta.env.VITE_BASE_API,
-  imgCdn: import.meta.env.VITE_IMG_CDN
+  imgCdn: import.meta.env.VITE_IMG_CDN,
+  videoCdn: import.meta.env.VITE_STREAM
 }

+ 19 - 0
wx-h5/src/common/consts/DeviceConst.js

@@ -46,4 +46,23 @@ export const DeviceAlarmState = {
     label: '已处理',
     color: '#52c41a'
   }
+}
+
+export const DeviceCameraState = {
+  ALL: {
+    value: "",
+    label: '全部'
+  },
+  // 未处理
+  OFFLINE: {
+    value: false,
+    label: '离线',
+    color: '#f5222d'
+  },
+  // 已处理
+  ONLINE: {
+    value: true,
+    label: '在线',
+    color: '#52c41a'
+  }
 }

+ 28 - 9
wx-h5/src/components/video-ctl/index.vue

@@ -1,8 +1,12 @@
 <template>
   <view class="video-ctl-content">
     <view class="video-content">
-      <video id="myVideo" src="https://wvp.wenhq.top:40001/rtp/34020000001320000103_34020000001320000001.live.mp4"
-             controls muted autoplay ref="videoPlay"></video>
+      <video
+          v-if="playStream"
+          :key="playStream"
+          :custom-cache="false"
+          :src="playStream"
+          controls muted autoplay ref="videoPlay"></video>
     </view>
     <view class="video-compass-content">
       <view class="compass">
@@ -28,28 +32,43 @@
   </view>
 
 </template>
-
+.value
 <script setup lang="ts">
-import {ref, watchEffect} from 'vue'
+import {onUnmounted, ref, watchEffect} from 'vue'
 import {videoCommand} from "@/api/device.js";
 
 const props = defineProps({
   stream: {
     type: String,
     default: ""
-  }
+  },
+  deviceNo: {
+    type: String,
+    default: ""
+  },
+  tunnel: {
+    type: String,
+    default: ""
+  },
 })
 const videoPlay = ref(null)
-const playStream = ref(null)
+const playStream = ref(props.stream)
 const onCommand = (command) => {
   videoCommand({
-    deviceNo: "34020000001320000103",
-    tunnel: "34020000001320000001",
+    deviceNo: props.deviceNo,
+    tunnel: props.tunnel,
     command
   })
 }
 watchEffect(() => {
-  playStream.value = props.stream
+  if (props.stream) {
+    playStream.value = props.stream
+  }
+})
+
+onUnmounted(() => {
+  playStream.value = ""
+  videoPlay.value
 })
 </script>
 

+ 14 - 0
wx-h5/src/pages.json

@@ -67,6 +67,20 @@
           }
         },
         {
+          "path": "device/camera-list",
+          "style": {
+            "navigationBarTitleText": "视频设备",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
+          "path": "device/camera-detail",
+          "style": {
+            "navigationBarTitleText": "视频设备详情",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
           "path": "device/detail",
           "style": {
             "navigationBarTitleText": "详细信息"

+ 2 - 2
wx-h5/src/pages/workbench/appctl.vue

@@ -61,11 +61,11 @@ const ctlItems = ref([
     title: '设备告警',
     color: '#0079fe',
     target: "/pages/workbenchsub/device/alarm-list"
-  },{
+  }, {
     name: video,
     title: '视频设备',
     color: '#0079fe',
-    target: "/pages/workbenchsub/device/alarm-list"
+    target: "/pages/workbenchsub/device/camera-list"
   },
 ]);
 

+ 109 - 0
wx-h5/src/pages/workbenchsub/device/camera-detail.vue

@@ -0,0 +1,109 @@
+<template>
+  <view class="device-info-wrap info-wrap">
+    <panel>
+      <template v-slot:header>
+        <up-icon size="28rpx" :name="list"></up-icon>
+        设备通道
+        <up-tag
+            :text="state.deviceInfo.statusName"
+            plain
+            size="mini"
+            :bgColor="valueToConst(DeviceState,state.deviceInfo.status).color"
+            class="tag-stat"/>
+      </template>
+      <template v-slot:content>
+        <up-collapse
+            @change="change"
+            @close="close"
+            @open="open"
+            accordion
+        >
+          <up-collapse-item
+              v-for="item in state.tunnels"
+              :title="`${item.name}:${item.channelId}`"
+              :key="item.channelId"
+              :name="item.channelId"
+          >
+            <video-ctl
+                :stream="state.openStream"
+                :device-no="deviceId"
+                :tunnel="item.channelId"
+                v-if="state.openTunnel === item.channelId"
+                :key="state.openStream"
+            />
+          </up-collapse-item>
+        </up-collapse>
+      </template>
+    </panel>
+  </view>
+</template>
+
+<script setup lang="ts">
+
+import list from "@/static/aiot/list.svg";
+import Panel from "@/components/pannel/index.vue";
+import VideoCtl from "@/components/video-ctl/index.vue";
+import {onMounted, reactive, ref} from 'vue';
+import {getCameraStream, getTunnelList} from "@/api/device.js";
+import {onLoad} from "@dcloudio/uni-app";
+import {valueToConst} from "@/common/consts/CommonConst.js";
+import {DeviceState} from "@/common/consts/DeviceConst.js";
+import {getVideoStream} from "@/util";
+
+const state = reactive({
+  deviceInfo: {},
+  tunnels: [],
+  openTunnel: "",
+  openStream: ''
+});
+const deviceId = ref('')
+onLoad((option) => {
+  deviceId.value = option.deviceId
+});
+onMounted(() => {
+  getTunnelList({deviceNo: deviceId.value, size: 99}).then((data) => {
+    const {list} = data
+    state.tunnels = list
+  })
+});
+const change = (e) => {
+  const openItem = e.find(item => {
+    return item.status === 'open'
+  })
+  if (!openItem) {
+    state.openTunnel = ""
+    return
+  }
+  state.openTunnel = openItem.name
+  getCameraStream({
+    deviceNo: deviceId.value,
+    tunnel: state.openTunnel
+  }).then((data) => {
+    const {https_fmp4} = data
+    state.openStream = getVideoStream(https_fmp4)
+  })
+}
+</script>
+
+
+<style lang="scss">
+.device-info-wrap {
+  padding-bottom: 140rpx;
+
+  .panel-wrap {
+    margin-bottom: $uni-block-gap;
+
+    .u-collapse {
+      width: 100%;
+
+      :deep(.u-cell__body__content) {
+        overflow: hidden;
+      }
+
+      :deep(.u-collapse-item__content) {
+        height: auto !important;
+      }
+    }
+  }
+}
+</style>

+ 197 - 0
wx-h5/src/pages/workbenchsub/device/camera-list.vue

@@ -0,0 +1,197 @@
+<template>
+  <view class="device-list">
+    <up-search v-model="searchValue"></up-search>
+    <view class="stat-ctl">
+      <view class="u-page__tag-item" v-for="(item, index) in DeviceCameraState" :key="index">
+        <up-tag
+            :text="item.label"
+            :plain="selectTag === item.value"
+            :plainFill="true"
+            bgColor="#007aff"
+            :name="item.value"
+            @click="()=>radioClick(item)"
+        >
+        </up-tag>
+      </view>
+    </view>
+    <view class="u-page-list" style="padding-bottom: 100rpx">
+      <up-list @scrolltolower="scrolltolower">
+        <up-list-item
+            v-for="(item, index) in indexList"
+            :key="item.id"
+        >
+          <view class="item-content">
+            <view class="content-info">
+              <view>
+                <up-text :lines="1" :text="`设备名称: ${formatTxt(item.name)}`" size="24rpx"/>
+                <up-tag
+                    :text="valueToConst(DeviceCameraState,item.onLine).label"
+                    plain
+                    size="mini"
+                    :bgColor="valueToConst(DeviceCameraState,item.onLine).color"
+                    class="tag-stat"
+                ></up-tag>
+                <up-text text="详情>" color="#007aff" style="flex:none;width: auto"
+                         @click="()=>onDetailClick(item)"></up-text>
+              </view>
+              <view>
+                <up-text :lines="1" :text="`设备编号:${formatTxt(item.deviceId)}`" size="24rpx"/>
+              </view>
+              <view>
+                <up-text :lines="1" :text="`地址:${formatTxt(item.hostAddress)}`" size="24rpx"/>
+              </view>
+            </view>
+          </view>
+        </up-list-item>
+        <up-loading-icon v-if="loading" text="加载中" textSize="12"></up-loading-icon>
+        <up-text
+            v-if="!loadmoreFlag" text="已经到底了" style="justify-content: center;"
+            size="24rpx"
+            color="gray"
+        >
+        </up-text>
+      </up-list>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import {ref} from "vue"
+import {DeviceCameraState} from '@/common/consts/DeviceConst.js'
+import {valueToConst} from '@/common/consts/CommonConst.js'
+import {onLoad, onPullDownRefresh, onShow} from '@dcloudio/uni-app';
+import {formatTxt, navigateTo,} from '@/util/index.js'
+import {getCameraList} from "@/api/device.js";
+
+let searchValue = ref("")
+let indexList = ref([])
+let selectTag = ref(DeviceCameraState.ALL.value)
+let loadmoreFlag = ref(true)
+let loading = ref(false)
+const page = ref(1)
+
+onLoad(() => {
+  page.value = 1
+  loading.value = true;
+  loadmore();
+});
+onPullDownRefresh(() => {
+  refreshPage(() => {
+    uni.stopPullDownRefresh();
+  })
+})
+
+onShow(() => {
+  uni.$on('refreshData', (res) => {
+    if (res.refresh) {
+      refreshPage()
+    }
+  })
+});
+
+const refreshPage = (callback = () => {
+}) => {
+  page.value = 1
+  indexList.value = []
+  loading.value = true;
+  loadmore(callback);
+}
+
+function radioClick(item) {
+  selectTag.value = item.value
+  page.value = 1
+  indexList.value = []
+  loading.value = true;
+  loadmore();
+}
+
+
+const loadmore = async (callback = null) => {
+  const {
+    list: records
+  } = await getCameraList({
+    page: page.value,
+    status: selectTag.value,
+  })
+  if (records && records.length > 0) {
+    indexList.value = indexList.value.concat(records)
+    page.value += 1
+    if (records.length < 10) {
+      loadmoreFlag.value = false;
+    }
+  } else {
+    loadmoreFlag.value = false;
+  }
+  loading.value = false;
+  callback && callback();
+};
+
+const scrolltolower = () => {
+  if (!loadmoreFlag.value) return;
+  loading.value = true;
+  loadmore();
+};
+const onDetailClick = (item) => {
+  navigateTo({
+    url: "/pages/workbenchsub/device/camera-detail",
+    param: item
+  })
+}
+</script>
+
+<style lang="scss">
+.device-list {
+  width: 680rpx;
+  margin: 14rpx auto 0;
+
+  .u-search__content {
+    background: $uni-bg-color !important;
+
+    .u-search__content__input {
+      background-color: $uni-bg-color !important;
+    }
+  }
+
+  .u-page-list {
+    margin-top: 14rpx;
+  }
+
+  .item-content {
+    width: 100%;
+    border-radius: $comm-border-radius;
+    display: flex;
+    box-sizing: border-box;
+    align-items: center;
+    justify-content: flex-start;
+    background: $uni-bg-color;
+    margin-bottom: 14rpx;
+
+    padding: 14rpx;
+
+    .content-info {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      justify-content: flex-start;
+      margin-left: 14rpx;
+
+      > view {
+        font-size: inherit;
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+
+        .u-link {
+          flex: none;
+        }
+
+        &:not(:first-child) {
+          margin-top: 14rpx;
+        }
+      }
+    }
+  }
+}
+</style>

+ 7 - 0
wx-h5/src/util/index.js

@@ -125,6 +125,13 @@ export const getDevImg = (devImg) => {
     return defaultImg;
   }
 };
+export const getVideoStream = (vieoStream) => {
+  if (!vieoStream) {
+    return defaultImg;
+  }
+  return vieoStream.replace(Rules.domainPort, config.videoCdn);
+
+};
 export const getImgs = (items) => {
   return JSON.parse(items).filter(item => !!item.url).map((item) => {
     return getImgUrl(item.url)