Parcourir la source

大屏添加视频

slowslo il y a 4 ans
Parent
commit
b1c0005c62

+ 159 - 0
src/api/device/wvpvideodevice.js

@@ -0,0 +1,159 @@
+import request from '@/router/axios';
+
+//获取设备列表
+export const getList = (current, size, params) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/query/devices',
+    method: 'get',
+    params: {
+      ...params,
+      current,
+      size,
+    }
+  })
+}
+
+//同步更新设备通道信息
+export const sync = (deviceId) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/devices/' + deviceId + "/sync",
+    method: 'post',
+  })
+}
+
+//获取设备通道列表
+export const getChannelList = (current, size, deviceId, params) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/query/' + deviceId + '/channels',
+    method: 'get',
+    params: {
+      ...params,
+      current,
+      size,
+    }
+  })
+}
+
+//设备点播
+export const startPlay = (deviceId, channelId) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/play/start/' + deviceId + "/" + channelId,
+    method: 'get',
+  })
+}
+
+//设备停止播放
+export const stopPlay = (deviceId, channelId) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/play/stop/' + deviceId + "/" + channelId,
+    method: 'get',
+  })
+}
+
+//获取设备录像
+export const getRecord = (deviceId, channelId, params) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/query/record/' + deviceId + "/" + channelId,
+    method: 'get',
+    params: {
+      ...params,
+    }
+  })
+}
+
+//查询设备视频流信息
+export const getMediaInfo = (params) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/query/mediaInfo',
+    method: 'get',
+    params: {
+      ...params,
+    }
+  })
+}
+
+//删除设备
+export const deleteDevice = (deviceId) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/query/' + deviceId + '/delete',
+    method: 'get',
+  })
+}
+
+//开始播放录像
+export const startPlayBack = (deviceId, channelId, params) => {
+  return request({
+    url: '/api/cyzh-third/wvp/playback/start/' + deviceId + "/" + channelId,
+    method: 'get',
+    params: {
+      ...params,
+    }
+  })
+}
+
+//开始播放录像
+export const stopPlayBack = (deviceId, channelId) => {
+  return request({
+    url: '/api/cyzh-third/wvp/playback/stopPlay/' + deviceId + "/" + channelId,
+    method: 'get',
+  })
+}
+
+
+
+/*export const getDetail = (id) => {
+  return request({
+    url: '/api/cyzh-smart-device/videoclouddevice/detail',
+    method: 'get',
+    params: {
+      id
+    }
+  })
+}
+
+export const remove = (ids) => {
+  return request({
+    url: '/api/cyzh-smart-device/videoclouddevice/remove',
+    method: 'post',
+    params: {
+      ids,
+    }
+  })
+}
+
+export const add = (row) => {
+  return request({
+    url: '/api/cyzh-smart-device/videoclouddevice/submit',
+    method: 'post',
+    data: row
+  })
+}
+
+export const update = (row) => {
+  return request({
+    url: '/api/cyzh-smart-device/videoclouddevice/submit',
+    method: 'post',
+    data: row
+  })
+}
+
+export const pageByBuilding = (current, size, params) => {
+  return request({
+    url: '/api/cyzh-smart-device/videoclouddevice/pageByBuilding',
+    method: 'get',
+    params: {
+      ...params,
+      current,
+      size,
+    }
+  })
+}
+export const pushStream = (params) => {
+  return request({
+    url: '/api/cyzh-smart-device/videoclouddevice/pushStream',
+    method: 'get',
+    params: {
+      ...params,
+    }
+  })
+}*/

+ 42 - 0
src/api/third/wvp.js

@@ -0,0 +1,42 @@
+import request from '@/router/axios';
+
+export const getDeviceList = (current, size, params) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/query/devices',
+    method: 'get',
+    params: {
+      ...params,
+      current,
+      size,
+    }
+  })
+}
+
+//获取设备通道列表
+export const getChannelList = (current, size, deviceId, params) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/query/' + deviceId + '/channels',
+    method: 'get',
+    params: {
+      ...params,
+      current,
+      size,
+    }
+  })
+}
+
+//设备点播
+export const startPlay = (deviceId, channelId) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/play/start/' + deviceId + "/" + channelId,
+    method: 'get',
+  })
+}
+
+//设备停止播放
+export const stopPlay = (deviceId, channelId) => {
+  return request({
+    url: '/api/cyzh-third/wvp/device/play/stop/' + deviceId + "/" + channelId,
+    method: 'get',
+  })
+}

+ 39 - 21
src/components/scene/mainScene.vue

@@ -47,24 +47,17 @@
     <!-- 视频监控  -->
     <!-- 视频监控  -->
     <info-box class="toLeft " top="8%" right="5px" width="22%" height="40%" title="视频监控" mode="left-min" :more="true">
     <info-box class="toLeft " top="8%" right="5px" width="22%" height="40%" title="视频监控" mode="left-min" :more="true">
       <div class="full" style="display: flex;flex-wrap: wrap">
       <div class="full" style="display: flex;flex-wrap: wrap">
-        <div  v-for="item of videoList" :key="item" style="width: 50%;height: 50%;background: #050505;">
-<!--          <video src="/video/video1.mp4"></video>-->
-            <div style="width: 95%;height: 95%;box-shadow:2px 2px 5px #020202;color: #dddddd;">
-              <div style="width: 100%;height: 100%;display: flex;align-items: center;justify-content: center">
-                <player ref="videoPlayer" :videoUrl="item.src"   fluent autoplay live ></player>
-<!--                <LivePlayer    :videoUrl="item.src" autoplay aspect='fullscreen'></LivePlayer>-->
-              </div>
-<!--              <video @mouseover="controls='controls'" @mouseleave="controls=''" style="width: 100%;height: 75%" :src="item.src" loop="loop" autoplay="autoplay" muted="muted" :controls="controls">-->
-<!--                您的浏览器不支持 video 标签。-->
-<!--              </video>-->
-<!--              <div class="center" style="width: 100%;height: 25%;background: linear-gradient(to top,rgb(25,58,84),rgb(40,91,140));color: white;border-radius: 5px;letter-spacing: 2px">-->
-<!--                {{item.time}}<br>-->
-<!--                {{item.label}}-->
-<!--              </div>-->
+        <div style="float: left;width: 1000%;height: 100%;overflow-y: scroll;display: flex;flex-wrap: wrap">
+          <div class="center event-item" v-for="(item,index) of channelList" style="width: 100%;height: 50px;flex-direction: column">
+            <div style="width: 90%;" @click="playVideo(item.deviceId, item.channelId)">
+              <div style="height: 100%;width: 40px;float: left; color: #065ec1">{{index + 1}}</div>
+              <div class="center" style="width: calc(100% - 40px);height: 100%;justify-content: left;text-indent: 20px;letter-spacing: 1px;font-size: 18px;color: #065ec1" >{{item.name}}</div>
             </div>
             </div>
+          </div>
         </div>
         </div>
       </div>
       </div>
     </info-box>
     </info-box>
+    <devicePlayer ref="devicePlayer"></devicePlayer>
 
 
     <!-- 车位信息  -->
     <!-- 车位信息  -->
     <info-box mode="left-min" class="toLeft " top="48.2%" right="5px" width="22%" height="25%" title="车位信息" :more="true">
     <info-box mode="left-min" class="toLeft " top="48.2%" right="5px" width="22%" height="25%" title="车位信息" :more="true">
@@ -131,9 +124,12 @@
 import InfoBox from "./infoBox";
 import InfoBox from "./infoBox";
 import player from './wvp/jessibuca'
 import player from './wvp/jessibuca'
 import * as echarts from 'echarts';
 import * as echarts from 'echarts';
+import {getDeviceList, getChannelList, startPlay, stopPlay} from "../../api/third/wvp";
+import deviceList from "../../views/device/deviceList";
+import devicePlayer from "./wvp/devicePlayer.vue";
 export default {
 export default {
   name: "mainScene",
   name: "mainScene",
-  components: {InfoBox,player},
+  components: {InfoBox,player,devicePlayer},
   mounted() {
   mounted() {
     this.initCharts();
     this.initCharts();
     window.addEventListener('resize', this.resize);
     window.addEventListener('resize', this.resize);
@@ -149,16 +145,15 @@ export default {
       this.resize()
       this.resize()
     }
     }
   },
   },
+  created() {
+    this.loadVideoList(1, 10);
+  },
   data(){
   data(){
     return{
     return{
       isOpen: true,
       isOpen: true,
       controls: {},
       controls: {},
-      videoList: [
-        {src: 'ws://8.135.22.196:8888/rtp/34020000001210000001/D17.flv',label: '园区东门',time: '2021/06/21 12:22'},
-        {src: 'ws://8.135.22.196:8888/rtp/34020000001210000001/D1.flv',label: '园区西门',time: '2021/06/21 12:22'},
-        {src: 'ws://8.135.22.196:8888/rtp/34020000001210000001/D22.flv',label: '体温检测',time: '2021/06/21 12:22'},
-        {src: 'ws://8.135.22.196:8888/rtp/34020000001210000001/D12.flv',label: '重点人员检测',time: '2021/06/21 12:22'},
-      ],
+      deviceList:[],
+      channelList:[],
       carInfoList: [
       carInfoList: [
         {label: '停车场',value: '3个',color: '#9f4518'},
         {label: '停车场',value: '3个',color: '#9f4518'},
         {label: '车位总数',value: '230位',color: '#1f848c'},
         {label: '车位总数',value: '230位',color: '#1f848c'},
@@ -293,6 +288,29 @@ export default {
       this.matingInfo = echarts.init(document.getElementById('matingInfo'));
       this.matingInfo = echarts.init(document.getElementById('matingInfo'));
       this.matingInfo.setOption(this.matingOption);
       this.matingInfo.setOption(this.matingOption);
     },
     },
+    loadVideoList(){
+      let that = this;
+      getDeviceList(1, 10, null).then(res =>{
+        that.deviceList = res.data.data.records;
+        that.deviceList.forEach(item =>{
+          getChannelList(1, 100, item.deviceId, null).then(res =>{
+            that.channelList = that.channelList.concat(res.data.data.records);
+          })
+        })
+      })
+    },
+    playVideo(deviceId, channelId){
+      startPlay(deviceId, channelId).then(res =>{
+        if (res.data.code == 200) {
+          this.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
+            streamInfo: res.data.data,
+            hasAudio: true
+          });
+        }else {
+          this.$message.error(res.data.msg);
+        }
+      })
+    }
   },
   },
 }
 }
 </script>
 </script>

+ 762 - 0
src/components/scene/wvp/devicePlayer.vue

@@ -0,0 +1,762 @@
+<template>
+<div id="devicePlayer" v-loading="isLoging">
+
+    <el-dialog center="true" title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()" :modal-append-to-body="false">
+        <!-- <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> -->
+        <player ref="videoPlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" :height="false" :hasaudio="hasaudio" fluent autoplay live ></player>
+        <div id="shared" style="text-align: right; margin-top: 0.25rem;">
+            <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick">
+                <el-tab-pane label="实时视频" name="media">
+                    <!--<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
+                        <span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
+                        <el-input v-model="getPlayerShared.sharedUrl" :disabled="true" >
+                          <template slot="append">
+                            <i class="cpoy-btn el-icon-document-copy"  title="点击拷贝" v-clipboard="getPlayerShared.sharedUrl" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
+                          </template>
+                        </el-input>
+                    </div>
+                    <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
+                        <span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
+                        <el-input v-model="getPlayerShared.sharedIframe" :disabled="true" >
+                          <template slot="append">
+                            <i class="cpoy-btn el-icon-document-copy"  title="点击拷贝" v-clipboard="getPlayerShared.sharedIframe" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
+                          </template>
+                        </el-input>
+                    </div>-->
+                    <div style="display: flex; margin-bottom: 0.5rem;" class="center">
+                        <span style="width: 5rem; line-height: 0.5rem; text-align: right">播放地址:</span>
+                        <el-input v-model="getPlayerShared.sharedRtmp" :disabled="true" >
+                          <template slot="append">
+                            <i class="cpoy-btn el-icon-document-copy"  title="点击拷贝" v-clipboard="getPlayerShared.sharedRtmp" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
+                          </template>
+                        </el-input>
+                    </div>
+                </el-tab-pane>
+                <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
+                <el-tab-pane label="录像查询" name="record" v-if="showRrecord">
+                    <el-date-picker size="mini" v-model="videoHistory.date" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="queryRecords()"></el-date-picker>
+                    <el-table :data="videoHistory.searchHistoryResult" height="150" v-loading="recordsLoading">
+                        <el-table-column label="名称" prop="name"></el-table-column>
+                        <el-table-column label="文件" prop="filePath"></el-table-column>
+                        <el-table-column label="开始时间" prop="startTime" :formatter="timeFormatter"></el-table-column>
+                        <el-table-column label="结束时间" prop="endTime" :formatter="timeFormatter"></el-table-column>
+
+                        <el-table-column label="操作">
+                            <template slot-scope="scope">
+                                <el-button icon="el-icon-video-play" size="mini" @click="playRecord(scope.row)">播放</el-button>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </el-tab-pane>
+                <!--遥控界面-->
+                <el-tab-pane label="云台控制" name="control" v-if="showPtz">
+                    <div  style="display: flex; justify-content: left;">
+                        <div class="control-wrapper">
+                            <div class="control-btn control-top" @mousedown="ptzCamera(0, 2, 0)" @mouseup="ptzCamera(0, 0, 0)">
+                                <i class="el-icon-caret-top"></i>
+                                <div class="control-inner-btn control-inner"></div>
+                            </div>
+                            <div class="control-btn control-left" @mousedown="ptzCamera(2, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
+                                <i class="el-icon-caret-left"></i>
+                                <div class="control-inner-btn control-inner"></div>
+                            </div>
+                            <div class="control-btn control-bottom" @mousedown="ptzCamera(0, 1, 0)" @mouseup="ptzCamera(0, 0, 0)">
+                                <i class="el-icon-caret-bottom"></i>
+                                <div class="control-inner-btn control-inner"></div>
+                            </div>
+                            <div class="control-btn control-right" @mousedown="ptzCamera(1, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
+                                <i class="el-icon-caret-right"></i>
+                                <div class="control-inner-btn control-inner"></div>
+                            </div>
+                            <div class="control-round">
+                                <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
+                            </div>
+                            <div style="position: absolute; left: 130px; top: 20px" @mousedown="ptzCamera(0, 0, 1)" @mouseup="ptzCamera(0, 0, 0)"><i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.5rem;"></i></div>
+                            <div style="position: absolute; left: 130px; top: 60px; font-size: 1.5rem;" @mousedown="ptzCamera(0, 0, 2)" @mouseup="ptzCamera(0, 0, 0)"><i class="el-icon-zoom-out control-zoom-btn"></i></div>
+                             <div class="contro-speed" style="position: relative; left: 4px; top: 130px; width: 160px;">
+                                 <el-slider v-model="controSpeed" :max="255"></el-slider>
+                             </div>
+                        </div>
+
+                        <div class="control-panel">
+                            <el-button-group>
+                                <el-tag style="position:absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium" type="info">预置位编号</el-tag>
+                                <el-input-number style="position: absolute; left: 5.1rem; top: 0rem; width: 5rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number>
+                                <el-button style="position: absolute; left: 10.2rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button>
+                                <el-button style="position: absolute; left: 25.5rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button>
+                                <el-button style="position: absolute; left: 15.3rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button>
+                                <el-tag style="position :absolute; left: 0rem; top: 2rem; width: 5rem; text-align: center" size="medium" type="info">巡航速度</el-tag>
+                                <el-input-number style="position: absolute; left: 5.1rem; top: 2rem; width: 5rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
+                                <el-button style="position: absolute; left: 10.2rem; top: 2rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button>
+                                <el-tag style="position :absolute; left: 15.3rem; top: 2rem; width: 5rem; text-align: center" size="medium" type="info">停留时间</el-tag>
+                                <el-input-number style="position: absolute; left: 20.4rem; top: 2rem; width: 5rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
+                                <el-button style="position: absolute; left: 25.5rem; top: 2rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button>
+                                <el-tag style="position :absolute; left: 0rem; top: 4rem; width: 5rem; text-align: center" size="medium" type="info">巡航组编号</el-tag>
+                                <el-input-number style="position: absolute; left: 5.1rem; top: 4rem; width: 5rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number>
+                                <el-button style="position: absolute; left: 10.2rem; top: 4rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button>
+                                <el-button style="position: absolute; left: 15.3rem; top: 4rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button>
+                                <el-button style="position: absolute; left: 20.4rem; top: 4rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button>
+                                <el-button style="position: absolute; left: 25.5rem; top: 4rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button>
+                                <el-tag style="position :absolute; left: 0rem; top: 6rem; width: 5rem; text-align: center" size="medium" type="info">扫描速度</el-tag>
+                                <el-input-number style="position: absolute; left: 5.1rem; top: 6rem; width: 5rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
+                                <el-button style="position: absolute; left: 10.2rem; top: 6rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>
+                                <el-tag style="position :absolute; left: 0rem; top: 8rem; width: 5rem; text-align: center" size="medium" type="info">扫描组编号</el-tag>
+                                <el-input-number style="position: absolute; left: 5.1rem; top: 8rem; width: 5rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number>
+
+                                <el-button style="position: absolute; left: 10.2rem; top: 8rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button>
+                                <el-button style="position: absolute; left: 15.3rem; top: 8rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button>
+                                <el-button style="position: absolute; left: 25.5rem; top: 6rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描</el-button>
+                                <el-button style="position: absolute; left: 25.5rem; top: 8rem; width: 5rem" size="mini" type="danger" icon="el-icon-switch-button" @click="ptzCamera(0, 0, 0)">停止</el-button>
+                            </el-button-group>
+                        </div>
+                    </div>
+                </el-tab-pane>
+                <el-tab-pane label="编码信息" name="codec" v-loading="tracksLoading">
+                    <p>
+                        无法播放或者没有声音?&nbsp&nbsp&nbsp试一试&nbsp
+                        <el-button size="mini" type="primary" v-if="!coverPlaying" @click="coverPlay">转码播放</el-button>
+                        <el-button size="mini" type="danger" v-if="coverPlaying" @click="convertStopClick">停止转码</el-button>
+                    </p>
+                    <div class="trank" >
+                      <p v-if="tracksNotLoaded" style="text-align: center;padding-top: 3rem;">暂无数据</p>
+                        <div v-for="(item, index) in tracks" style="width: 50%; float: left" loading>
+                            <span >流 {{index}}</span>
+                            <div class="trankInfo" v-if="item.codec_type == 0">
+                                <p>格式: {{item.codec_id_name}}</p>
+                                <p>类型: 视频</p>
+                                <p>分辨率: {{item.width}} x {{item.height}}</p>
+                                <p>帧率: {{item.fps}}</p>
+                            </div>
+                            <div class="trankInfo" v-if="item.codec_type == 1">
+                                <p>格式: {{item.codec_id_name}}</p>
+                                <p>类型: 音频</p>
+                                <p>采样位数: {{item.sample_bit}}</p>
+                                <p>采样率: {{item.sample_rate}}</p>
+                            </div>
+                        </div>
+
+                    </div>
+
+                </el-tab-pane>
+            </el-tabs>
+        </div>
+    </el-dialog>
+</div>
+</template>
+
+<script>
+// import player from '../dialog/rtcPlayer.vue'
+// import LivePlayer from '@liveqing/liveplayer'
+import player from './jessibuca'
+import {zlmHttpUrl} from "../../../config/env";
+import {getMediaInfo, getRecord, startPlayBack, stopPlayBack} from "../../../api/device/wvpvideodevice"
+import {convert, convertStop} from "../../../api/device/videoclouddevice"
+
+export default {
+    name: 'devicePlayer',
+    props: {},
+    components: {
+        player,
+    },
+    computed: {
+        getPlayerShared: function () {
+
+            return {
+                sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
+                sharedIframe: '' + window.location.origin + '<iframe src="/#/play/wasm/"></iframe>' + encodeURIComponent(this.videoUrl) + '',
+                sharedRtmp: this.videoUrl
+            };
+        }
+    },
+    created() {},
+    data() {
+        return {
+            video: 'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',
+            videoUrl: '',
+            videoHistory: {
+                date: '',
+                searchHistoryResult: [] //媒体流历史记录搜索结果
+            },
+            showVideoDialog: false,
+            streamId: '',
+            app : '',
+            convertKey: '',
+            deviceId: '',
+            channelId: '',
+            tabActiveName: 'media',
+            mediaServerId : '',
+            hasaudio: false,
+            loadingRecords: false,
+            recordsLoading: false,
+            isLoging: false,
+            controSpeed: 30,
+            timeVal: 0,
+            timeMin: 0,
+            timeMax: 1440,
+            presetPos: 1,
+            cruisingSpeed: 100,
+            cruisingTime: 5,
+            cruisingGroup: 0,
+            scanSpeed: 100,
+            scanGroup: 0,
+            tracks: [],
+            coverPlaying:false,
+            tracksLoading: false,
+            recordPlay: "",
+            showPtz: true,
+            showRrecord: true,
+            tracksNotLoaded: false,
+        };
+    },
+    methods: {
+        tabHandleClick: function(tab, event) {
+            console.log(tab)
+            var that = this;
+            that.tracks = [];
+            that.tracksLoading = true;
+            that.tracksNotLoaded = false;
+            if (tab.name == "codec") {
+                getMediaInfo({"streamId": this.streamId, "vhost":"__defaultVhost__", "app": this.app, "schema":"rtmp"}).then(res => {
+                  that.tracksLoading = false;
+                  if (res.data.code == 200 && res.data.data.online) {
+                    that.tracks = res.data.data.tracks;
+                  }else{
+                    that.tracksNotLoaded = true;
+                    that.$message({
+                      showClose: true,
+                      message: '获取编码信息失败',
+                      type: 'warning'
+                    });
+                  }
+                }).catch(function (e) {});
+            }
+        },
+        openDialog: function (tab, deviceId, channelId, param) {
+            this.tabActiveName = tab;
+            this.channelId = channelId;
+            this.deviceId = deviceId;
+            this.streamId = "";
+            this.mediaServerId = "";
+            this.app = "";
+            this.videoUrl = ""
+            if (!!this.$refs.videoPlayer) {
+                this.$refs.videoPlayer.pause();
+            }
+            switch (tab) {
+                case "media":
+                    this.play(param.streamInfo, param.hasAudio)
+                    break;
+                case "record":
+                    this.showVideoDialog = true;
+                    this.videoHistory.date = param.date;
+                    this.queryRecords()
+                    break;
+                case "streamPlay":
+                    this.tabActiveName = "media";
+                    this.showRrecord = false,
+                    this.showPtz = false,
+                    this.play(param.streamInfo, param.hasAudio)
+                    break;
+                case "control":
+                    break;
+            }
+        },
+        timeAxisSelTime: function (val) {
+            console.log(val)
+        },
+        play: function (streamInfo, hasAudio) {
+            this.hasAudio = hasAudio;
+            this.isLoging = false;
+            // this.videoUrl = streamInfo.rtc;
+            this.videoUrl = this.getUrlByStreamInfo(streamInfo);
+            this.streamId = streamInfo.streamId;
+            this.app = streamInfo.app;
+            this.mediaServerId = streamInfo.mediaServerId;
+            this.playFromStreamInfo(false, streamInfo)
+        },
+        getUrlByStreamInfo(streamInfo){
+            // let baseZlmApi = `${location.host}` + `/zlm`
+            console.log(zlmHttpUrl)
+            // return `${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
+            return streamInfo.ws_flv;
+        },
+        coverPlay: function () {
+            var that = this;
+            this.coverPlaying = true;
+            this.$refs.videoPlayer.pause();
+            convert(that.streamId).then(res => {
+              if (res.data.data.code == 0) {
+                that.convertKey = res.data.key;
+                setTimeout(()=>{
+                  that.isLoging = false;
+                  that.playFromStreamInfo(false, res.data.data.data);
+                }, 2000)
+              } else {
+                that.isLoging = false;
+                that.coverPlaying = false;
+                that.$message({
+                  showClose: true,
+                  message: '转码失败',
+                  type: 'error'
+                });
+              }
+            }).catch(error => {
+              console.log(error)
+              that.coverPlaying = false;
+              that.$message({
+                showClose: true,
+                message: '播放错误',
+                type: 'error'
+              });
+            });
+            // that.$axios({
+            //     method: 'post',
+            //     url: '/api/play/convert/' + that.streamId
+            //     }).then(function (res) {
+            //         if (res.data.code == 0) {
+            //             that.convertKey = res.data.key;
+            //             setTimeout(()=>{
+            //                 that.isLoging = false;
+            //                 that.playFromStreamInfo(false, res.data.data);
+            //             }, 2000)
+            //         } else {
+            //             that.isLoging = false;
+            //             that.coverPlaying = false;
+            //             that.$message({
+            //                 showClose: true,
+            //                 message: '转码失败',
+            //                 type: 'error'
+            //             });
+            //         }
+            //     }).catch(function (e) {
+            //         console.log(e)
+            //         that.coverPlaying = false;
+            //         that.$message({
+            //             showClose: true,
+            //             message: '播放错误',
+            //             type: 'error'
+            //         });
+            //     });
+        },
+        convertStopClick: function() {
+            this.convertStop(()=>{
+                this.$refs.videoPlayer.play(this.videoUrl)
+            });
+        },
+        convertStop: function(callback) {
+            var that = this;
+            that.$refs.videoPlayer.pause()
+            convertStop(this.convertKey).then(res =>{
+              if (res.data.data.code == 0) {
+                console.log(res.data.data.msg)
+              }else {
+                console.error(res.data.data.msg)
+              }
+              if (callback )callback();
+            }).catch(error => {
+              that.coverPlaying = false;
+              that.convertKey = "";
+            });
+            // this.$axios({
+            //     method: 'post',
+            //     url: '/api/play/convertStop/' + this.convertKey
+            //   }).then(function (res) {
+            //     if (res.data.code == 0) {
+            //       console.log(res.data.msg)
+            //     }else {
+            //       console.error(res.data.msg)
+            //     }
+            //      if (callback )callback();
+            //   }).catch(function (e) {});
+            // that.coverPlaying = false;
+            // that.convertKey = "";
+            // if (callback )callback();
+        },
+
+        playFromStreamInfo: function (realHasAudio, streamInfo) {
+          this.showVideoDialog = true;
+          this.hasaudio = realHasAudio && this.hasaudio;
+          this.$nextTick(() => {
+            this.$refs.videoPlayer.play(this.getUrlByStreamInfo(streamInfo))
+          })
+        },
+        close: function () {
+            console.log('关闭视频');
+            if (!!this.$refs.videoPlayer){
+              this.$refs.videoPlayer.pause();
+            }
+            this.videoUrl = '';
+            this.coverPlaying = false;
+            this.showVideoDialog = false;
+            if (this.convertKey != '') {
+              this.convertStop();
+            }
+            this.convertKey = ''
+            if (this.recordPlay != '') {
+              this.stopPlayRecord();
+            }
+            this.recordPlay = ''
+        },
+
+        copySharedInfo: function (data) {
+            console.log('复制内容:' + data);
+            this.coverPlaying = false;
+            this.tracks = []
+            let _this = this;
+            this.$copyText(data).then(
+                function (e) {
+                    _this.$message({
+                        showClose: true,
+                        message: '复制成功',
+                        type: 'success'
+                    });
+                },
+                function (e) {
+                    _this.$message({
+                        showClose: true,
+                        message: '复制失败,请手动复制',
+                        type: 'error'
+                    });
+                }
+            );
+        },
+        queryRecords: function () {
+            if (!this.videoHistory.date) {
+                return;
+            }
+            this.recordsLoading = true;
+            this.videoHistory.searchHistoryResult = [];
+            let that = this;
+            var startTime = this.videoHistory.date + " 00:00:00";
+            var endTime = this.videoHistory.date + " 23:59:59";
+            getRecord(this.deviceId, this.channelId, {"startTime": startTime, "endTime": endTime}).then(res => {
+              // 处理时间信息
+              that.videoHistory.searchHistoryResult = res.data.data.recordList;
+              that.recordsLoading = false;
+            }).catch(function (e) {
+              console.log(e.msg);
+              // that.videoHistory.searchHistoryResult = falsificationData.recordData;
+            });
+        },
+        onTimeChange: function (video) {
+            // this.queryRecords()
+        },
+        playRecord: function (row) {
+            let that = this;
+            if (that.streamId != "") {
+                that.stopPlayRecord(function () {
+                    that.streamId = "",
+                        that.playRecord(row);
+                })
+            } else {
+                startPlayBack(this.deviceId, this.channelId, {"startTime": row.startTime, "endTime": row.endTime}).then(res => {
+                  var streamInfo = res.data.data;
+                  that.app = streamInfo.app;
+                  that.streamId = streamInfo.streamId;
+                  that.mediaServerId = streamInfo.mediaServerId;
+                  that.videoUrl = that.getUrlByStreamInfo(streamInfo);
+                  that.recordPlay = true;
+                });
+                // this.$axios({
+                //     method: 'get',
+                //     url: '/api/playback/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' +
+                //         row.endTime
+                // }).then(function (res) {
+                //     var streamInfo = res.data.data;
+                //     that.app = streamInfo.app;
+                //     that.streamId = streamInfo.streamId;
+                //     that.videoUrl = that.getUrlByStreamInfo(streamInfo);
+                //     that.recordPlay = true;
+                // });
+            }
+        },
+        stopPlayRecord: function (callback) {
+            this.$refs.videoPlayer.pause();
+            this.videoUrl = '';
+            stopPlayBack(this.deviceId, this.channelId).then(res => {
+              if (callback) callback()
+            });
+            // this.$axios({
+            //     method: 'get',
+            //     url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId
+            // }).then(function (res) {
+            //     if (callback) callback()
+            // });
+        },
+        ptzCamera: function (leftRight, upDown, zoom) {
+            console.log('云台控制:' + leftRight + ' : ' + upDown + " : " + zoom);
+            let that = this;
+            this.$axios({
+                method: 'post',
+                // url: '/api/ptz/' + this.deviceId + '/' + this.channelId + '?leftRight=' + leftRight + '&upDown=' + upDown +
+                //     '&inOut=' + zoom + '&moveSpeed=50&zoomSpeed=50'
+                url: '/api/cyzh-third/wvp/ptz/control/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + (zoom * 16 + upDown * 4 + leftRight) + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.controSpeed
+            }).then(function (res) {});
+        },
+        //////////////////////播放器事件处理//////////////////////////
+        videoError: function (e) {
+            console.log("播放器错误:" + JSON.stringify(e));
+        },
+        presetPosition: function (cmdCode, presetPos) {
+            console.log('预置位控制:' + this.presetPos + ' : 0x' + cmdCode.toString(16));
+            let that = this;
+            this.$axios({
+                method: 'post',
+                url: '/api/cyzh-third/wvp/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=0&parameter2=' + presetPos + '&combindCode2=0'
+            }).then(function (res) {});
+        },
+        setSpeedOrTime: function (cmdCode, groupNum, parameter) {
+            let that = this;
+            let parameter2 = parameter % 256;
+            let combindCode2 = Math.floor(parameter / 256) * 16;
+            console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter2.toString(16) + ' 0x' + combindCode2.toString(16));
+            this.$axios({
+                method: 'post',
+                url: '/api/cyzh-third/wvp/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter2 + '&combindCode2=' + combindCode2
+            }).then(function (res) {});
+        },
+        setCommand: function (cmdCode, groupNum, parameter) {
+            let that = this;
+            console.log('前端控制:0x' + cmdCode.toString(16) + ' 0x' + groupNum.toString(16) + ' 0x' + parameter.toString(16) + ' 0x0');
+            this.$axios({
+                method: 'post',
+                url: '/api/cyzh-third/wvp/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '&parameter1=' + groupNum + '&parameter2=' + parameter + '&combindCode2=0'
+            }).then(function (res) {});
+        },
+        formatTooltip: function (val) {
+            var h = parseInt(val / 60);
+            var hStr = h < 10 ? ("0" + h) : h;
+            var s = val % 60;
+            var sStr = s < 10 ? ("0" + s) : s;
+            return h + ":" + sStr;
+        },
+        timeFormatter: function (row, column, cellValue, index) {
+            return cellValue.split(" ")[1];
+        },
+        mergeTime: function (timeArray) {
+            var resultArray = [];
+            for (let i = 0; i < timeArray.length; i++) {
+                var startTime = new Date(timeArray[i].startTime);
+                var endTime = new Date(timeArray[i].endTime);
+                if (i == 0) {
+                    resultArray[0] = {
+                        startTime: startTime,
+                        endTime: endTime
+                    }
+                }
+                for (let j = 0; j < resultArray.length; j++) {
+                    if (startTime > resultArray[j].endTime) { // 合并
+                        if (startTime - resultArray[j].endTime <= 1000) {
+                            resultArray[j].endTime = endTime;
+                        } else {
+                            resultArray[resultArray.length] = {
+                                startTime: startTime,
+                                endTime: endTime
+                            }
+                        }
+                    } else if (resultArray[j].startTime > endTime) { // 合并
+                        if (resultArray[j].startTime - endTime <= 1000) {
+                            resultArray[j].startTime = startTime;
+                        } else {
+                            resultArray[resultArray.length] = {
+                                startTime: startTime,
+                                endTime: endTime
+                            }
+                        }
+                    }
+                }
+            }
+            console.log(resultArray)
+            return resultArray;
+        }
+    }
+};
+</script>
+
+<style>
+.control-wrapper {
+    position: relative;
+    width: 120px;
+    height: 120px;
+    /* max-width: 1.25rem;
+    max-height: 1.25rem; */
+    border-radius: 100%;
+    margin-top: 0.3rem;
+    margin-left: 0.1rem;
+    float: left;
+}
+
+.control-panel {
+    position: relative;
+    top: 0;
+    left: 70px;
+    height: 170px;
+    /* max-height: 2.2rem; */
+}
+
+.control-btn {
+    display: flex;
+    justify-content: center;
+    position: absolute;
+    width: 44%;
+    height: 44%;
+    border-radius: 5px;
+    border: 1px solid #78aee4;
+    box-sizing: border-box;
+    transition: all 0.3s linear;
+}
+.control-btn:hover {
+    cursor:pointer
+}
+
+.control-btn i {
+    font-size: 20px;
+    color: #78aee4;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.control-btn i:hover {
+    cursor:pointer
+}
+.control-zoom-btn:hover {
+    cursor:pointer
+}
+
+.control-round {
+    position: absolute;
+    top: 21%;
+    left: 21%;
+    width: 58%;
+    height: 58%;
+    background: #fff;
+    border-radius: 100%;
+}
+
+.control-round-inner {
+    position: absolute;
+    left: 13%;
+    top: 13%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 70%;
+    height: 70%;
+    font-size: 40px;
+    color: #78aee4;
+    border: 1px solid #78aee4;
+    border-radius: 100%;
+    transition: all 0.3s linear;
+}
+
+.control-inner-btn {
+    position: absolute;
+    width: 60%;
+    height: 60%;
+    background: #fafafa;
+}
+
+.control-top {
+    top: -8%;
+    left: 27%;
+    transform: rotate(-45deg);
+    border-radius: 5px 100% 5px 0;
+}
+
+.control-top i {
+    transform: rotate(45deg);
+    border-radius: 5px 100% 5px 0;
+}
+
+.control-top .control-inner {
+    left: -1px;
+    bottom: 0;
+    border-top: 1px solid #78aee4;
+    border-right: 1px solid #78aee4;
+    border-radius: 0 100% 0 0;
+}
+
+.control-top .fa {
+    transform: rotate(45deg) translateY(-7px);
+}
+
+.control-left {
+    top: 27%;
+    left: -8%;
+    transform: rotate(45deg);
+    border-radius: 5px 0 5px 100%;
+}
+
+.control-left i {
+    transform: rotate(-45deg);
+}
+
+.control-left .control-inner {
+    right: -1px;
+    top: -1px;
+    border-bottom: 1px solid #78aee4;
+    border-left: 1px solid #78aee4;
+    border-radius: 0 0 0 100%;
+}
+
+.control-left .fa {
+    transform: rotate(-45deg) translateX(-7px);
+}
+
+.control-right {
+    top: 27%;
+    right: -8%;
+    transform: rotate(45deg);
+    border-radius: 5px 100% 5px 0;
+}
+
+.control-right i {
+    transform: rotate(-45deg);
+}
+
+.control-right .control-inner {
+    left: -1px;
+    bottom: -1px;
+    border-top: 1px solid #78aee4;
+    border-right: 1px solid #78aee4;
+    border-radius: 0 100% 0 0;
+}
+
+.control-right .fa {
+    transform: rotate(-45deg) translateX(7px);
+}
+
+.control-bottom {
+    left: 27%;
+    bottom: -8%;
+    transform: rotate(45deg);
+    border-radius: 0 5px 100% 5px;
+}
+
+.control-bottom i {
+    transform: rotate(-45deg);
+}
+
+.control-bottom .control-inner {
+    top: -1px;
+    left: -1px;
+    border-bottom: 1px solid #78aee4;
+    border-right: 1px solid #78aee4;
+    border-radius: 0 0 100% 0;
+}
+
+.control-bottom .fa {
+    transform: rotate(-45deg) translateY(7px);
+}
+.trank {
+    width: 80%;
+    height: 180px;
+    text-align: left;
+    padding: 0 10%;
+    overflow: auto;
+}
+.trankInfo {
+    width: 80%;
+    padding: 0 10%;
+}
+</style>

+ 729 - 0
src/components/scene/wvp/wvpvideodevice.vue

@@ -0,0 +1,729 @@
+<!-- 国标监控设备-->
+<template>
+  <basic-container>
+    <div v-if="!showDeviceChannelCrud">
+      <avue-crud :option="deviceOption" :table-loading="loading" :data="data" :page="page" :permission="permissionList"
+                 :before-open="beforeOpen" v-model="form" ref="crud" @row-update="rowUpdate" @row-save="rowSave" @row-del="rowDel"
+                 @search-change="searchChange" @search-reset="searchReset" @selection-change="selectionChange" @current-change="currentChange"
+                 @size-change="sizeChange" @refresh-change="refreshChange" @on-load="onLoad">
+        <template slot="streamMode" slot-scope="{row}">
+          <el-select size="mini" v-model="row.streamMode" placeholder="请选择">
+            <el-option key="UDP" label="UDP" value="UDP"></el-option>
+            <el-option key="TCP-ACTIVE" label="TCP主动模式" :disabled="true" value="TCP-ACTIVE"></el-option>
+            <el-option key="TCP-PASSIVE" label="TCP被动模式"  value="TCP-PASSIVE"></el-option>
+          </el-select>
+        </template>
+        <template slot="online" slot-scope="{row}">
+          <el-tag size="medium" v-if="row.online == 1">在线</el-tag>
+          <el-tag size="medium" v-else type="info">离线</el-tag>
+        </template>
+        <template slot="hostAddress" slot-scope="{row}">
+          <el-tag size="medium">{{row.hostAddress}}</el-tag>
+        </template>
+
+        <template slot="menu" slot-scope="{row}">
+          <el-button size="mini" v-if="row.online != 0" icon="el-icon-refresh" plain @click="handleSync(row.deviceId, row.$index)">刷 新</el-button>
+          <el-button-group>
+            <el-button size="mini" :disabled="row.online != 1" type="primary" icon="el-icon-video-camera-solid" @click="handleChannel(row.deviceId)">通 道</el-button>
+            <el-button size="mini" icon="el-icon-delete" type="danger" v-if="row.online != 1"  @click="deleteDevice(row.deviceId)">删除</el-button>
+          </el-button-group>
+        </template>
+
+      </avue-crud>
+    </div>
+    <basic-container v-if="showDeviceChannelCrud">
+      <avue-crud :option="deviceChannelOption" :table-loading="loading" :data="data" :page="page" :permission="permissionList"
+                 :before-open="beforeOpen" v-model="form" ref="crud" @row-update="rowUpdate" @row-save="rowSave" @row-del="rowDel"
+                 @search-change="channelSearchChange" @search-reset="searchReset" @selection-change="selectionChange" @current-change="currentChange"
+                 @size-change="sizeChange" @refresh-change="refreshChannelChange" @on-load="onLoadChannel">
+        <template slot-scope="{row}" slot="channelTypeSearch">
+          <el-select size="mini" style="margin-right: 1rem;" v-model="row.channelType" placeholder="请选择" default-first-option>
+            <el-option label="全部" value=""></el-option>
+            <el-option label="设备" value="false"></el-option>
+            <el-option label="子目录" value="true"></el-option>
+          </el-select>
+        </template>
+        <template slot-scope="{row}" slot="onlineSearch">
+          <el-select size="mini" style="margin-right: 1rem;" v-model="row.online" placeholder="请选择" default-first-option>
+            <el-option label="全部" value=""></el-option>
+            <el-option label="在线" value="true"></el-option>
+            <el-option label="离线" value="false"></el-option>
+          </el-select>
+        </template>
+        <template slot="menuLeft">
+          <el-button type="primary" size="mini" icon="el-icon-arrow-left" @click="backToDeviceList">返 回</el-button>
+        </template>
+        <template slot="hasAudio" slot-scope="{row}">
+          <el-switch @change="updateChannel(scope.row)" v-model="row.hasAudio" active-color="#409EFF">
+          </el-switch>
+        </template>
+        <template slot="status" slot-scope="{row}">
+          <el-tag size="medium" v-if="row.status == 1">开启</el-tag>
+          <el-tag size="medium" v-else="row.status == 0">关闭</el-tag>
+        </template>
+        <template slot="status" slot-scope="{row}">
+          <el-tag size="medium" v-if="row.status == 1">开启</el-tag>
+          <el-tag size="medium" v-else="row.status == 0">关闭</el-tag>
+        </template>
+        <template slot="menu" slot-scope="{row}">
+          <el-button size="mini" icon="el-icon-video-play" v-if="permission.wvpvideodevice_play" @click="devicePlay(row)">播放</el-button>
+          <el-button size="mini" icon="el-icon-switch-button" type="danger" :loading="stopBtnLoading" v-if="permission.wvpvideodevice_stop && !!row.streamId" @click="deviceStopPlay(row)">停止</el-button>
+          <el-button size="mini" type="primary" icon="el-icon-video-camera" v-if="permission.wvpvideodevice_record" @click="deviceRecord(row)">设备录像</el-button>
+        </template>
+      </avue-crud>
+    </basic-container>
+    <devicePlayer ref="devicePlayer"></devicePlayer>
+    <!--    小区列表-->
+    <el-dialog :modal-append-to-body="false" append-to-body :visible.sync="bindVisible" title="设备绑定" center width="20%">
+      <div style="text-align: center;">
+        <!-- <el-select v-model="selectValue" filterable placeholder="请选择">
+          <el-option v-for="item in residentialList" :key="item.id" :label="item.name" :value="item.id">
+          </el-option>
+        </el-select>
+        <p style="margin-top: 20px;">
+          <el-button type="primary" icon="el-icon-paperclip" @click="bindDevice">绑定</el-button>
+        </p> -->
+        <avue-form v-model="bindForm" :option="bindOption" @submit="bindDevice">
+
+        </avue-form>
+      </div>
+    </el-dialog>
+
+    <el-dialog :modal-append-to-body="false" append-to-body :visible.sync="bindBatchVisible" title="设备批量绑定" center
+               width="50%">
+      <div style="text-align: center;">
+        <avue-form v-model="bindBatchForm" :option="bindOption" @submit="bindBatchDevice">
+
+        </avue-form>
+      </div>
+    </el-dialog>
+
+    <el-dialog :modal-append-to-body="false" append-to-body :visible.sync="videoVisible" title="视频监控" center width="50%">
+      <div>
+        <div align="center">{{video.name}}</div>
+        <video :id="videoId" controls="true" width="100%" height="100%">
+        </video>
+      </div>
+    </el-dialog>
+    <el-dialog :modal-append-tobody="false" append-to-body :visible.sync="editVisiable" title="编辑" width="60%">
+      <avue-form :option="option" v-model="editForm" @submit="submitVideoclouddevice"></avue-form>
+    </el-dialog>
+    <el-dialog :modal-append-tobody="false" append-to-body :visible.sync="detailVisible" title="查看" width="60%">
+      <avue-form :option="option1" v-model="detailForm" ></avue-form>
+    </el-dialog>
+
+  </basic-container>
+</template>
+
+<script>
+import {
+  getList,
+  sync,
+  getChannelList,
+  startPlay,
+  stopPlay,
+  deleteDevice
+} from "../../../api/device/wvpvideodevice.js";
+import {
+  mapGetters
+} from "vuex";
+import devicePlayer from "./devicePlayer.vue";
+
+export default {
+  components: {devicePlayer},
+  data() {
+    return {
+      stopBtnLoading: false,
+      openVideoDialog: false,
+      parentChannelId: 0,
+      searchSrt: "",
+      channelType: "",
+      online: "",
+      curDeviceId: null,
+      showDeviceChannelCrud: false,
+      bindBatchForm:{},
+      bindForm: {},
+      videoVisible: false,
+      video: {},
+      editVisiable: false,
+      editForm: {},
+      detailVisible: false,
+      detailForm: {},
+      selectValue: "",
+      mediaServerId : '',
+      form: {},
+      query: {},
+      loading: true,
+      bindVisible: false,
+      bindBatchVisible: false,
+      residentialList: [],
+      page: {
+        pageSize: 10,
+        currentPage: 1,
+        total: 0
+      },
+      selectionList: [],
+
+      deviceOption: {
+        menu: true,
+        menuWidth:300,
+        height: 'auto',
+        calcHeight: 30,
+        tip: false,
+        searchShow: true,
+        searchMenuSpan: 6,
+        labelWidth: 150,
+        border: true,
+        index: true,
+        viewBtn: false,
+        editBtn: false,
+        delBtn: true,
+        selection: true,
+        dialogClickModal: false,
+        addBtn:false,
+        column: [{
+            label: "设备编号",
+            prop: "deviceId",
+            span: 12,
+          },
+          {
+            label: "设备名称",
+            prop: "name",
+            span: 12,
+          },
+          {
+            label: "厂家",
+            prop: "manufacturer",
+            span: 12,
+            width: 100
+          },
+          {
+            label: "流传输模式",
+            prop: "streamMode",
+            span: 12,
+            width: 120,
+            slot: true
+          },
+          {
+            label: "通道数",
+            prop: "channelCount",
+            span: 12,
+            width: 80
+          },
+          {
+            label: "状态",
+            prop: "online",
+            span: 12,
+            width:80,
+            slot: true
+          },
+          {
+            label: "最新心跳",
+            prop: "keepaliveTime",
+            span: 12,
+          },
+          {
+            label: "最新注册",
+            prop: "registerTime",
+            span: 12,
+          },
+          {
+            label: "ip地址",
+            prop: "hostAddress",
+            span: 12,
+            slot: true
+          },
+          {
+            label: "设备安装位置",
+            prop: "address",
+            span: 12,
+            hide: true,
+            rules: [{
+              required: true,
+              message: "请输入设备地址",
+              trigger: "blur"
+            }]
+          },
+        ]
+      },
+      deviceChannelOption: {
+        menu: true,
+        menuWidth: 300,
+        height: 'auto',
+        calcHeight: 30,
+        tip: false,
+        searchShow: true,
+        searchMenuSpan: 6,
+        labelWidth: 150,
+        border: true,
+        index: true,
+        viewBtn: false,
+        editBtn: false,
+        delBtn: true,
+        selection: true,
+        dialogClickModal: false,
+        addBtn:false,
+        column: [
+          {
+            label: "搜索关键字",
+            prop: "query",
+            searchLabelWidth:120,
+            hide: true,
+            display: false,
+            search: true
+          },
+          {
+            label: "通道类型",
+            prop: "channelType",
+            hide: true,
+            display: false,
+            search: true,
+            searchslot: true
+          },
+          {
+            label: "在线状态",
+            prop: "online",
+            hide: true,
+            display: false,
+            search: true,
+            searchslot: true
+          },
+          {
+            label: "通道编号",
+            prop: "channelId",
+            span: 12,
+          },
+          {
+            label: "设备编号",
+            prop: "deviceId",
+            span: 12,
+          },
+          {
+            label: "通道名称",
+            prop: "name",
+            span: 12,
+            width: 100
+          },
+          {
+            label: "子节点数",
+            prop: "subCount",
+            span: 12,
+            width: 120
+          },
+          {
+            label: "开启音频",
+            prop: "hasAudio",
+            span: 12,
+            width: 80,
+            slot: true
+          },
+          {
+            label: "状态",
+            prop: "status",
+            span: 12,
+            width:80,
+            slot: true
+          },
+          {
+            label: "云台类型",
+            prop: "ptztypeText",
+            span: 12,
+          },
+        ]
+      },
+      bindOption: {
+        column: [
+          {
+            label: "所属小区",
+            prop: "residentialId",
+            type: "select",
+            search: true,
+            span:24,
+            cascaderItem: ['buildingId'],
+            dicUrl: "/api/cyzh-community/residential/list?size=9999",
+            dicFormatter:(res)=>{
+              return res.data.records;//返回字典的层级结构
+            },
+            props: {
+              label: "name",
+              value: "id"
+            },
+            rules: [{
+              required: true,
+              message: "请选择所属小区",
+              trigger: "blur"
+            }]
+          },
+          {
+            label: "所属楼栋",
+            prop: "buildingId",
+            search: true,
+            type: "select",
+            span:24,
+            cascaderIndex: 0,
+            dicUrl: "/api/cyzh-community/building/list?residentialId={{key}}",
+            dicFormatter:(res) => {
+              return res.data.records;//返回字典层级结构
+            },
+            filterable: true,
+            props: {
+              label: "name",
+              value: "id"
+            },
+          }]
+      },
+      data: []
+    };
+  },
+  computed: {
+    ...mapGetters(["permission"]),
+    permissionList() {
+      return {
+        addBtn: this.vaildData(this.permission.wvpvideodevice_add, false),
+        viewBtn: this.vaildData(this.permission.wvpvideodevice_view, false),
+        delBtn: this.vaildData(this.permission.wvpvideodevice_delete, false),
+        editBtn: this.vaildData(this.permission.wvpvideodevice_edit, false),
+        playBtn: this.vaildData(this.permission.wvpvideodevice_play, false),
+        recordBtn: this.vaildData(this.permission.wvpvideodevice_record, false),
+        stopBtn: this.vaildData(this.permission.wvpvideodevice_stop, false)
+      };
+    },
+    ids() {
+      let ids = [];
+      this.selectionList.forEach(ele => {
+        ids.push(ele.id);
+      });
+      return ids.join(",");
+    }
+  },
+  methods: {
+    dateFormat(fmt, date) {
+      let ret;
+      const opt = {
+        "y+": date.getFullYear().toString(),        // 年
+        "M+": (date.getMonth() + 1).toString(),     // 月
+        "d+": date.getDate().toString(),            // 日
+        "H+": date.getHours().toString(),           // 时
+        "m+": date.getMinutes().toString(),         // 分
+        "s+": date.getSeconds().toString()          // 秒
+        // 有其他格式化字符需求可以继续添加,必须转化成字符串
+      };
+      for (let k in opt) {
+        ret = new RegExp("(" + k + ")").exec(fmt);
+        if (ret) {
+          fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
+        };
+      };
+      return fmt;
+    },
+    deleteDevice(deviceId) {
+      let that = this;
+      deleteDevice(deviceId).then(res => {
+        that.onLoad(that.page);
+      }).catch(error => {
+        console.log(error);
+      })
+    },
+    deviceRecord(row){
+      let deviceId = row.deviceId;
+      let channelId = row.channelId;
+      let format = this.dateFormat("yyyy-MM-dd", new Date());
+      this.$refs.devicePlayer.openDialog("record", deviceId, channelId, {date: format})
+    },
+    devicePlay(row){
+      this.openVideoDialog = true;
+      let deviceId = row.deviceId;
+      let channelId = row.channelId;
+      let that = this;
+      startPlay(deviceId, channelId).then(res => {
+        that.openVideoDialog = false;
+        if (res.data.code == 200) {
+          that.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
+            streamInfo: res.data.data,
+            hasAudio: row.hasAudio
+          });
+          that.onLoadChannel(that.page);
+        }else {
+          that.$message.error(res.data.msg);
+        }
+      });
+    },
+    deviceStopPlay(row) {
+      let that = this;
+      let deviceId = row.deviceId;
+      let channelId = row.channelId;
+      this.stopBtnLoading = true;
+      stopPlay(deviceId, channelId).then(res => {
+        console.log(JSON.stringify(res));
+        that.onLoadChannel(that.page);
+        this.stopBtnLoading = false;
+      }).catch(err => {
+        if (err.response.status === 402) { // 已经停止过
+          that.onLoadChannel(that.page);
+        }else {
+          console.log(err)
+        }
+      });
+    },
+    channelSearchChange(params, done){
+      this.query = params;
+      this.page.currentPage = 1;
+      if (typeof (this.parentChannelId) == "undefined" ||  this.parentChannelId == 0) {
+        this.onLoadChannel(this.page);
+      }
+      done();
+    },
+    backToDeviceList(){
+      this.showDeviceChannelCrud = false;
+      this.curDeviceId = null;
+    },
+    handleSync(deviceId, index){
+      sync(deviceId).then(res => {
+        let result = res.data.data;
+        this.$set(this.data, index, result)
+      });
+    },
+    handleChannel(deviceId){
+      this.showDeviceChannelCrud = true;
+      this.curDeviceId = deviceId;
+    },
+    showVideo(row) {
+      this.video.url = "ws://36.103.228.148:8888/live/" + row.id + ".flv";
+      this.video.name = row.deviceName;
+      this.videoVisible = true;
+      this.$nextTick(() => {
+        try {
+          let videoElement = document.getElementById('videoId');
+          let flvPlayer = flvjs.createPlayer({
+            type: 'flv',
+            url: this.video.url
+          });
+          flvPlayer.on(flvjs.Events.ERROR, (errType, errDetail) => {
+            // errType是 NetworkError时,对应errDetail有:Exception、HttpStatusCodeInvalid、ConnectingTimeout、EarlyEof、UnrecoverableEarlyEof
+            // errType是 MediaError时,对应errDetail是MediaMSEError
+          });
+          flvPlayer.on(flvjs.Events.MEDIA_SOURCE_CLOSE || flvjs.Events.MEDIA_SOURCE_ENDED, () => {})
+          flvPlayer.attachMediaElement(videoElement);
+          flvPlayer.load();
+          flvPlayer.play();
+        } catch (e) {
+          console.log(e);
+        }
+      })
+    },
+    showDeviceBindBatch() {
+      if (this.selectionList.length === 0) {
+        this.$message.warning("请选择至少一条数据");
+        return;
+      }
+      if (this.residentialList.length == 0) {
+        //  请求获取小区列表
+      } else {
+        this.selectValue = ""
+        this.bindBatchVisible = true;
+      }
+      // this.form = row;
+    },
+    showDeviceBind(row) {
+
+      if (this.residentialList.length == 0) {
+        //  请求获取小区列表
+      } else {
+        this.selectValue = ""
+        this.bindVisible = true;
+      }
+      this.bindForm = row;
+    },
+    bindBatchDevice(loading,done) {
+      // if (this.selectValue == "") {
+      //   this.$message.warning("请选择所属小区");
+      //   return;
+      // }
+      //保存
+      bindResidentialBatch(this.ids, this.bindBatchForm.residentialId,this.bindBatchForm.buildingId).then(() => {
+        this.bindBatchVisible = false;
+        this.bindBatchForm = {};
+        this.onLoad(this.page);
+        this.$message({
+          type: "success",
+          message: "操作成功!"
+        });
+        done();
+      }, error => {
+        loading();
+        console.log(error);
+      });
+    },
+    bindDevice(loading,done) {
+      // this.form.residentialId = this.selectValue;
+      //保存
+      updateVideoDevice(this.bindForm).then(() => {
+        this.onLoad(this.page);
+        this.bindVisible = false;
+        this.$message({
+          type: "success",
+          message: "操作成功!"
+        });
+        done();
+      }, error => {
+        loading();
+        console.log(error);
+      });
+    },
+    rowSave(row, done, loading) {
+      add(row).then(() => {
+        this.onLoad(this.page);
+        this.$message({
+          type: "success",
+          message: "操作成功!"
+        });
+        done();
+      }, error => {
+        loading();
+        window.console.log(error);
+      });
+    },
+    rowUpdate(row, index, done, loading) {
+      update(row).then(() => {
+        this.onLoad(this.page);
+        this.$message({
+          type: "success",
+          message: "操作成功!"
+        });
+        done();
+      }, error => {
+        loading();
+        console.log(error);
+      });
+    },
+    rowDel(row) {
+      this.$confirm("确定将选择数据删除?", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(() => {
+          return remove(row.id);
+        })
+        .then(() => {
+          this.onLoad(this.page);
+          this.$message({
+            type: "success",
+            message: "操作成功!"
+          });
+        });
+    },
+    handleDelete() {
+      if (this.selectionList.length === 0) {
+        this.$message.warning("请选择至少一条数据");
+        return;
+      }
+      this.$confirm("确定将选择数据删除?", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(() => {
+          return remove(this.ids);
+        })
+        .then(() => {
+          this.onLoad(this.page);
+          this.$message({
+            type: "success",
+            message: "操作成功!"
+          });
+          this.$refs.crud.toggleSelection();
+        });
+    },
+    beforeOpen(done, type) {
+      if (["edit", "view"].includes(type)) {
+        getDetail(this.form.id).then(res => {
+          this.form = res.data.data;
+        });
+      }
+      done();
+    },
+    searchReset() {
+      this.query = {};
+      this.onLoad(this.page);
+    },
+    searchChange(params, done) {
+      this.query = params;
+      this.page.currentPage = 1;
+      this.onLoad(this.page, params);
+      done();
+    },
+    selectionChange(list) {
+      this.selectionList = list;
+    },
+    selectionClear() {
+      this.selectionList = [];
+      this.$refs.crud.toggleSelection();
+    },
+    currentChange(currentPage) {
+      this.page.currentPage = currentPage;
+    },
+    sizeChange(pageSize) {
+      this.page.pageSize = pageSize;
+    },
+    refreshChange() {
+      this.onLoad(this.page, this.query);
+    },
+    refreshChannelChange() {
+      this.onLoadChannel(this.page, this.query);
+    },
+    edit(row) {
+      console.log(row,"编辑数据")
+      this.editForm = row;
+      this.editVisiable = true;
+    },
+    view(row) {
+      console.log(row,"详情数据")
+      this.detailForm = row;
+      this.detailVisible = true;
+    },
+    submitVideoclouddevice(row,done,loading) {
+      update(row).then(() => {
+        this.onLoad(this.page);
+        this.$message({
+          type: "success",
+          message: "操作成功!"
+        });
+        done();
+      }, error => {
+        loading();
+        console.log(error);
+      });
+    },
+    onLoad(page, params = {}) {
+      this.loading = true;
+      getList(page.currentPage, page.pageSize, Object.assign(params, this.query)).then(res => {
+        const data = res.data.data;
+        this.page.total = data.total;
+        this.data = data.records;
+        this.loading = false;
+        this.selectionClear();
+      });
+    },
+    onLoadChannel(page, params = {}) {
+      this.loading = true;
+      getChannelList(page.currentPage, page.pageSize, this.curDeviceId, Object.assign(params, this.query)).then(res => {
+        const data = res.data.data;
+        this.page.total = data.total;
+        this.data = data.records;
+        this.loading = false;
+        this.selectionClear();
+      });
+    }
+  }
+};
+</script>
+
+<style>
+.el-dropdown-link {
+  cursor: pointer;
+  color: #409eff;
+  font-size: small;
+  margin-left: 10px;
+}
+</style>