Explorar o código

feat: support DevicePlayer component

add some constants (GATEWAY, GATEWAY_WS and GATEWAY_CAMERA) and adjust some styles
Casper Dai %!s(int64=3) %!d(string=hai) anos
pai
achega
809593f7b7

+ 62 - 137
src/views/device/back/components/Video.vue → src/components/external/DevicePlayer/index.vue

@@ -10,34 +10,18 @@
       ref="player"
       class="o-video__player o-simple-video"
       muted
-      autoplay
       :poster="poster"
     />
-    <div
-      v-else
-      class="o-video__offline"
-      :style="{ zoom: zoomnum }"
-    >
-      当前设备离线了
-    </div>
-    <div
-      class="o-video__tip"
-      :class="{ online }"
-    />
-    <div class="c-footer u-ellipsis">{{ device.name }}</div>
+    <slot />
     <div
       v-show="maskShow"
       class="o-video__mask"
     >
       <div
-        class="o-video__btn u-pointer"
-        :style="{ zoom: zoomnum }"
-        @click="onPlay"
+        class="l-flex--row center o-video__btn u-pointer"
+        @click="onClick"
       >
-        <i
-          class="o-video__icon has-bg"
-          :class="iconClass"
-        />
+        <i :class="iconClass" />
       </div>
     </div>
   </div>
@@ -46,87 +30,104 @@
 <script>
 import flvjs from 'flv.js'
 import { authCode } from '@/api/camera'
+import { GATEWAY } from '@/constant'
 import videoPoster from '@/assets/video-poster.png'
 
-const CAMERA_URL = `${location.protocol}//${process.env.VUE_APP_GATEWAY || location.host}`
-
 export default {
-  name: 'DeviceCard',
+  name: 'DevicePlayer',
   props: {
     device: {
       type: Object,
       required: true
     },
-    zoomnum: {
-      type: String,
-      default: null
+    simple: {
+      type: [Boolean, String],
+      default: false
+    },
+    autoplay: {
+      type: [Boolean, String],
+      default: false
     }
   },
   data () {
     return {
       poster: videoPoster,
+      loading: false,
       maskShow: false,
       refreshShow: false,
-      first: false
+      paused: !this.autoplay
     }
   },
   computed: {
     iconClass () {
+      if (this.loading) {
+        return 'el-icon-loading'
+      }
       if (this.refreshShow) {
-        return 'refresh'
+        return 'el-icon-refresh'
       }
-      return this.device.paused ? 'paused' : 'playing'
+      return this.paused ? 'el-icon-video-play' : 'el-icon-video-pause'
     },
     online () {
-      return this.device.onlineStatus === 1
+      return this.device.activate === 2 && this.device.onlineStatus === 1
     }
   },
   created () {
+    this.$timer = -1
     if (this.online) {
-      this.$timer = -1
       this.getAuthCode()
     }
   },
   beforeDestroy () {
     if (this.$player) {
-      clearTimeout(this.$timer)
       this.destroyPlay()
     }
+    this.$timer = null
   },
   methods: {
     mouseOver () {
-      if (this.online) {
-        this.maskShow = true
-      }
-      if (this.$player && !this.$refs.player.paused) {
-        this.device.paused = false
-      } else {
-        this.device.paused = true
+      if (!this.online) {
+        return
       }
+      this.maskShow = true
     },
     mouseLeave () {
       this.maskShow = false
     },
-    onPlay () {
-      this.first = true
-      if (this.$refs.player.paused) {
-        this.maskShow = false
+    onClick () {
+      if (this.loading) {
+        return
       }
-      this.refreshShow = false
-      if (this.$player && !this.$refs.player.paused) {
-        this.device.paused = true
-        this.$player.pause()
-      } else {
+      if (this.refreshShow) {
         if (this.$player) {
           this.destroyPlay()
         }
-        this.device.paused = false
         this.getAuthCode()
+      } else {
+        if (this.paused) {
+          this.$player.play()
+        } else {
+          this.$player.pause()
+        }
+        this.paused = !this.paused
       }
     },
     getAuthCode () {
-      authCode({ deviceId: this.device.id }).then(({ data }) => {
-        this.createPlayer(data)
+      if (this.loading) {
+        return
+      }
+      this.loading = true
+      authCode({ deviceId: this.device.id }).then(
+        ({ data }) => {
+          if (this.$timer !== null) {
+            this.createPlayer(data)
+          }
+        },
+        () => {
+          this.refreshShow = true
+        }
+      ).finally(() => {
+        this.loading = false
       })
     },
     createPlayer ({ timestamp, token, expire }) {
@@ -136,7 +137,7 @@ export default {
           type: 'flv',
           isLive: true,
           hasAudio: false,
-          url: `${CAMERA_URL}/live/${this.device.id}.flv?authorization=${token}&timestamp=${timestamp}&expire=${expire}`
+          url: `${GATEWAY}/live/${this.device.id}.flv?authorization=${token}&timestamp=${timestamp}&expire=${expire}`
         })
         this.$player.on('error', () => {
           this.refreshShow = true
@@ -146,7 +147,7 @@ export default {
             message: '设备视频流出错'
           })
         })
-        let decodedFrames = -1
+        let decodedFrames = 0
         this.$player.on('statistics_info', res => {
           decodedFrames = res.decodedFrames
         })
@@ -167,15 +168,17 @@ export default {
           // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
           this.$player.load()
           this.$player.play()
-          if (!this.first) {
+          if (this.paused) {
             this.$player.pause()
           }
+          this.refreshShow = false
         } catch (error) {
           console.log('连接websocker异常', error)
         }
       }
     },
     destroyPlay () {
+      clearTimeout(this.$timer)
       this.$player.pause()
       this.$player.unload()
       this.$player.detachMediaElement()
@@ -188,59 +191,6 @@ export default {
 
 <style lang="scss" scoped>
 .o-video {
-  position: relative;
-  padding-top: 56.25%;
-  border-radius: $radius--mini;
-  overflow: hidden;
-
-  &.offline {
-    background: url("~@/assets/image_offline.svg") 0 0 / 100% 100% no-repeat;
-  }
-
-  &__player {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-  }
-
-  &__offline {
-    position: absolute;
-    top: 73%;
-    left: 50%;
-    color: $info;
-    font-size: 24px;
-    transform: translateX(-50%);
-  }
-
-  &__tip {
-    position: absolute;
-    top: 0;
-    right: 0;
-    width: 48px;
-    height: 24px;
-    color: #fff;
-    font-size: 14px;
-    line-height: 24px;
-    text-align: center;
-    border-radius: 0 0 0 $radius--mini;
-    background-color: $error--dark;
-    z-index: 9;
-
-    &::after {
-      content: "离线";
-    }
-
-    &.online {
-      background-color: $success--dark;
-
-      &::after {
-        content: "在线";
-      }
-    }
-  }
-
   &__mask {
     position: absolute;
     top: 0;
@@ -255,38 +205,13 @@ export default {
     position: absolute;
     left: 50%;
     top: 50%;
-    width: 128px;
-    padding-top: 128px;
+    width: 64px;
+    height: 64px;
+    color: #fff;
+    font-size: 48px;
     border-radius: 50%;
-    background-color: rgba(#f4f7fb, 0.5);
-    transform: translate(-50%, -50%);
-  }
-
-  &__icon {
-    position: absolute;
-    top: 50%;
-    left: 50%;
+    background-color: rgba(#000, 0.5);
     transform: translate(-50%, -50%);
-
-    &.playing {
-      border-top: 22px solid transparent;
-      border-left: 37px solid #fff;
-      border-bottom: 22px solid transparent;
-      transform: translate(-15px, -50%);
-    }
-
-    &.paused {
-      width: 40%;
-      height: 60%;
-      border-left: 10px solid #fff;
-      border-right: 10px solid #fff;
-    }
-
-    &.refresh {
-      width: 60%;
-      height: 60%;
-      background-image: url("~@/assets/icon_refresh.png");
-    }
   }
 }
 </style>

+ 47 - 50
src/components/external/camera/CameraDetail/index.vue

@@ -1,24 +1,20 @@
 <template>
-  <div
-    v-loading="videoLoading"
-    class="c-detail"
-  >
-    <div class="c-detail-header">
-      <div class="c-detail-header__name u-ellipsis"> {{ camera.name }}</div>
+  <div class="c-traffic-camera-detail">
+    <div class="c-traffic-camera-detail__header">
+      <div class="c-traffic-camera-detail__name u-ellipsis"> {{ camera.name }}</div>
       <i
-        class="c-detail-header__close el-icon-close u-pointer"
+        class="c-traffic-camera-detail__close el-icon-close u-pointer"
         @click="close"
       />
     </div>
     <video
       ref="player"
-      class="o-simple-video"
+      class="c-traffic-camera-detail__player o-simple-video"
       muted
       autoplay
-      controls
       :poster="poster"
     />
-    <div class="c-detail-footer has-padding">
+    <div class="c-traffic-camera-detail__footer has-padding">
       <div class="l-flex--row c-sibling-item--v c-video-controls">
         <div
           v-show="settingBshow"
@@ -122,6 +118,7 @@
 </template>
 
 <script>
+import { mapGetters } from 'vuex'
 import flvjs from 'flv.js'
 import * as echarts from 'echarts'
 import {
@@ -130,10 +127,9 @@ import {
   getAvailableParam,
   setCamera
 } from '@/api/camera'
+import { GATEWAY_CAMERA } from '@/constant'
 import videoPoster from '@/assets/video-poster.png'
 
-const CAMERA_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${process.env.VUE_APP_GATEWAY || location.host}${process.env.VUE_APP_CAMERA_PROXY}`
-
 export default {
   name: 'CameraDetail',
   props: {
@@ -157,10 +153,12 @@ export default {
       player: null,
       availableParam: {},
       infoData: {},
-      videoLoading: false,
       settingDatacopy: []
     }
   },
+  computed: {
+    ...mapGetters(['token'])
+  },
   created () {
     this.getAvailableParam()
   },
@@ -183,7 +181,7 @@ export default {
           type: 'flv',
           isLive: true,
           // hasAudio: false,
-          url: `${CAMERA_URL}/${this.camera.identifier}?authorization=${this.$keycloak.token}`
+          url: `${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`
         })
 
         this.player.on('error', e => {
@@ -196,7 +194,6 @@ export default {
           // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
           this.player.load()
           this.player.play()
-          this.videoLoading = false
         } catch (error) {
           console.log('连接websocker异常', error)
         }
@@ -485,42 +482,42 @@ export default {
 <style lang="scss" scoped>
 $theme-blue: #003e90;
 
-.o-simple-video {
-  width: 100%;
-  height: 100%;
-}
-
-.c-detail {
+.c-traffic-camera-detail {
   position: relative;
   width: 100%;
   height: 100%;
   overflow: hidden;
-}
 
-.c-detail-header {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  padding-top: $spacing;
-  color: #fff;
-  font-size: 24px;
-  text-align: center;
-  background-color: rgba($theme-blue, 0.8);
-  z-index: 9;
+  &__video {
+    width: 100%;
+    height: 100%;
+  }
 
-  &::after {
-    content: "";
+  &__header {
     position: absolute;
-    top: 100%;
-    left: 50%;
-    width: 700px;
-    height: 0;
-    border-top: 16px solid rgba($theme-blue, 0.8);
-    border-right: 16px solid transparent;
-    border-bottom: 16px solid transparent;
-    border-left: 16px solid transparent;
-    transform: translateX(-50%);
+    top: 0;
+    left: 0;
+    width: 100%;
+    padding-top: $spacing;
+    color: #fff;
+    font-size: 24px;
+    text-align: center;
+    background-color: rgba($theme-blue, 0.8);
+    z-index: 9;
+
+    &::after {
+      content: "";
+      position: absolute;
+      top: 100%;
+      left: 50%;
+      width: 700px;
+      height: 0;
+      border-top: 16px solid rgba($theme-blue, 0.8);
+      border-right: 16px solid transparent;
+      border-bottom: 16px solid transparent;
+      border-left: 16px solid transparent;
+      transform: translateX(-50%);
+    }
   }
 
   &__name {
@@ -537,13 +534,13 @@ $theme-blue: #003e90;
     font-size: 42px;
     transform: translateY(-50%);
   }
-}
 
-.c-detail-footer {
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  width: 100%;
+  &__footer {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+  }
 }
 
 .c-block {

+ 6 - 35
src/components/external/camera/CameraPlayer/index.vue

@@ -9,9 +9,12 @@
       class="o-video__player o-simple-video"
       muted
       autoplay
-      controls
       :poster="poster"
     />
+    <div
+      v-else
+      class="o-video__tag"
+    />
     <slot />
   </div>
 </template>
@@ -19,10 +22,9 @@
 <script>
 import { mapGetters } from 'vuex'
 import flvjs from 'flv.js'
+import { GATEWAY_CAMERA } from '@/constant'
 import videoPoster from '@/assets/video-poster.png'
 
-const CAMERA_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${process.env.VUE_APP_GATEWAY || location.host}${process.env.VUE_APP_CAMERA_PROXY}`
-
 export default {
   name: 'CameraPlayer',
   props: {
@@ -67,7 +69,7 @@ export default {
           type: 'flv',
           isLive: true,
           hasAudio: false,
-          url: `${CAMERA_URL}/${identifier}?authorization=${this.token}`
+          url: `${GATEWAY_CAMERA}/${identifier}?authorization=${this.token}`
         })
         player.on('error', e => {
           console.log(e)
@@ -99,34 +101,3 @@ export default {
   }
 }
 </script>
-
-<style lang="scss" scoped>
-.o-video {
-  position: relative;
-  padding-top: 56.25%;
-  border-radius: $radius--mini;
-  overflow: hidden;
-
-  &.offline {
-    background: url("~@/assets/image_offline.svg") 0 0 / 100% 100% no-repeat;
-
-    &::before {
-      content: "当前设备离线了";
-      position: absolute;
-      top: 73%;
-      left: 50%;
-      color: $info;
-      font-size: 12px;
-      transform: translateX(-50%);
-    }
-  }
-
-  &__player {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-  }
-}
-</style>

+ 9 - 3
src/constant.js

@@ -1,3 +1,12 @@
+const protocol = location.protocol
+const host = process.env.VUE_APP_GATEWAY || location.host
+
+export const GATEWAY = `${protocol}//${host}`
+
+export const GATEWAY_WS = `${protocol === 'https:' ? 'wss:' : 'ws:'}//${host}`
+
+export const GATEWAY_CAMERA = `${GATEWAY_WS}${process.env.VUE_APP_CAMERA_PROXY}`
+
 export const AssetType = {
   IMAGE: 1,
   VIDEO: 2,
@@ -63,9 +72,6 @@ export const Transmitter = {
 }
 
 export const Access = {
-  // frontend
-  VIEW_CAMERAS: 'frontend:cameras',
-  VIEW_SENSORS: 'frontend:sensors',
   // backend
   MANAGE_TENANTS: 'manange-tenants',
   MANAGE_TENANT: 'manange-tenant',

+ 46 - 0
src/scss/bem/_object.scss

@@ -279,3 +279,49 @@
     display: none;
   }
 }
+
+.o-video {
+  position: relative;
+  padding-top: 56.25%;
+  border-radius: $radius--mini;
+  overflow: hidden;
+
+  &.offline {
+    background: url("~@/assets/image_offline.svg") 0 0 / 100% 100% no-repeat;
+
+    .o-video__tag {
+      background-color: $error--dark;
+
+      &::after {
+        content: "离线";
+      }
+    }
+  }
+
+  &__player {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+  }
+
+  &__tag {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 48px;
+    height: 24px;
+    color: #fff;
+    font-size: 14px;
+    line-height: 24px;
+    text-align: center;
+    border-radius: 0 0 0 $radius--mini;
+    background-color: $success--dark;
+    z-index: 9;
+
+    &::after {
+      content: "在线";
+    }
+  }
+}

+ 2 - 1
src/utils/mqtt.js

@@ -1,4 +1,5 @@
 import mqtt from 'mqtt'
+import { GATEWAY_WS } from '@/constant'
 import SM4 from '@/utils/sm4'
 
 const willTopic = 'web/offline'
@@ -15,7 +16,7 @@ const sm4 = new SM4({
   outType: 'uint8array'
 })
 
-const host = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${process.env.VUE_APP_GATEWAY || location.host}${process.env.VUE_APP_MQTT_PROXY}`
+const host = `${GATEWAY_WS}${process.env.VUE_APP_MQTT_PROXY}`
 const username = process.env.VUE_APP_MQTT_USER_NAME
 const password = process.env.VUE_APP_MQTT_PASSWORD
 

+ 5 - 20
src/views/device/back/index.vue

@@ -37,12 +37,14 @@
           class="c-back-grid"
           :class="gridClass"
         >
-          <c-video
+          <device-player
             v-for="item in options.list"
             :key="item.identifier"
             :device="item"
-            :zoomnum="zoomnum"
-          />
+          >
+            <div class="o-video__tag" />
+            <div class="c-footer u-ellipsis">{{ device.name }}</div>
+          </device-player>
         </div>
         <pagination
           :total="options.totalCount"
@@ -63,13 +65,9 @@
 <script>
 import { getDevices } from '@/api/device'
 import { createListOptions } from '@/utils'
-import CVideo from './components/Video'
 
 export default {
   name: 'Back',
-  components: {
-    CVideo
-  },
   data () {
     return {
       options: createListOptions({ activate: 2, pageSize: 4 })
@@ -86,16 +84,6 @@ export default {
           return 'one'
       }
     },
-    zoomnum () {
-      switch (this.options.params.pageSize) {
-        case 4:
-          return '.6'
-        case 9:
-          return '.4'
-        default:
-          return '1'
-      }
-    },
     isAbnormal () {
       const options = this.options
       return options.error || !options.loading && options.list.length === 0
@@ -112,9 +100,6 @@ export default {
       options.list = []
       getDevices(options.params).then(
         ({ data, totalCount }) => {
-          data.forEach(item => {
-            item.paused = true
-          })
           options.totalCount = totalCount
           options.list = data
         },

+ 16 - 14
src/views/external/camera/index.vue

@@ -25,20 +25,22 @@
       size="large"
     >
       <template #header="{ totalCount }">
-        <button
-          v-if="isSuperAdmin"
-          class="c-sibling-item o-button"
-          @click="onAdd"
-        >
-          <i class="o-button__icon el-icon-circle-plus-outline" />新增
-        </button>
-        <button
-          v-if="totalCount"
-          class="c-sibling-item o-button o-botton--white"
-          @click="onFull"
-        >
-          <i class="o-button__icon el-icon-full-screen" />全部
-        </button>
+        <div class="l-flex--row">
+          <button
+            v-if="isSuperAdmin"
+            class="c-sibling-item o-button"
+            @click="onAdd"
+          >
+            <i class="o-button__icon el-icon-circle-plus-outline" />新增
+          </button>
+          <button
+            v-if="totalCount"
+            class="c-sibling-item o-button o-botton--white"
+            @click="onFull"
+          >
+            <i class="o-button__icon el-icon-menu" />全部
+          </button>
+        </div>
       </template>
       <grid-table-item v-slot="item">
         <camera-player