Sfoglia il codice sorgente

change:
1. 播放器调试,云台控制面板调试

kindring 1 anno fa
parent
commit
5c919f427c

+ 22 - 132
README.md

@@ -1,132 +1,22 @@
-![logo](olDoc/_media/logo.png)
-# 开箱即用的28181协议视频平台
-
-[![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit)
-[![license](http://img.shields.io/badge/license-MIT-green.svg)](https://github.com/xia-chu/ZLMediaKit/blob/master/LICENSE)
-[![JAVA](https://img.shields.io/badge/language-java-red.svg)](https://en.cppreference.com/)
-[![platform](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/xia-chu/ZLMediaKit)
-[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/xia-chu/ZLMediaKit/pulls)
-
-
-WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网络视频平台,负责实现核心信令与设备管理后台部分,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将不带国标功能的摄像机/直播流/直播推流转发到其他国标平台。   
-
-流媒体服务基于@夏楚 ZLMediaKit [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)   
-播放器使用@dexter jessibuca [https://github.com/langhuihui/jessibuca/tree/v3](https://github.com/langhuihui/jessibuca/tree/v3)  
-前端页面基于@Kyle MediaServerUI [https://gitee.com/kkkkk5G/MediaServerUI](https://gitee.com/kkkkk5G/MediaServerUI) 进行修改.  
-
-# 应用场景:
-支持浏览器无插件播放摄像头视频。  
-支持摄像机、平台、NVR等设备接入。 
-支持国标级联。多平台级联。跨网视频预览。
-支持rtsp/rtmp等视频流转发到国标平台。  
-支持rtsp/rtmp等推流转发到国标平台。  
-
-# 项目目标
-旨在打造一个易配置,易使用,便于维护的28181国标信令系统, 依托优秀的开源流媒体服务框架ZLMediaKit, 实现一个完整易用GB28181平台. 
-
-# 部署文档
-[doc.wvp-pro.cn](https://doc.wvp-pro.cn)
-
-# gitee同步仓库
-https://gitee.com/pan648540858/wvp-GB28181-pro.git
-
-# 截图
-![index](olDoc/_media/index.png "index.png")
-![2](olDoc/_media/2.png "2.png")
-![3](olDoc/_media/3.png "3.png")
-![3-1](olDoc/_media/3-1.png "3-1.png")
-![3-2](olDoc/_media/3-2.png "3-2.png")
-![3-3](olDoc/_media/3-3.png "3-3.png")
-![build_1](https://images.gitee.com/uploads/images/2022/0304/101919_ee5b8c79_1018729.png "2022-03-04_10-13.png")
-
-# 功能特性 
-- [X] 集成web界面
-- [X] 兼容性良好
-- [X] 支持电子地图,支持接入WGS84和GCJ02两种坐标系,并且自动转化为合适的坐标系进行展示和分发
-- [X] 接入设备
-  - [X] 视频预览
-  - [X] 无限制接入路数,能接入多少设备只取决于你的服务器性能
-  - [X] 云台控制,控制设备转向,拉近,拉远
-  - [X] 预置位查询,使用与设置
-  - [X] 查询NVR/IPC上的录像与播放,支持指定时间播放与下载
-  - [X] 无人观看自动断流,节省流量
-  - [X] 视频设备信息同步
-  - [X] 离在线监控
-  - [X] 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址
-  - [X] 支持通过一个流地址直接观看摄像头,无需登录以及调用任何接口
-  - [X] 支持UDP和TCP两种国标信令传输模式
-  - [X] 支持UDP和TCP两种国标流传输模式
-  - [X] 支持检索,通道筛选
-  - [X] 支持通道子目录查询
-  - [X] 支持过滤音频,防止杂音影响观看
-  - [X] 支持国标网络校时
-  - [X] 支持播放H264和H265
-  - [X] 报警信息处理,支持向前端推送报警信息
-  - [X] 支持订阅与通知方法
-    - [X] 移动位置订阅
-    - [X] 移动位置通知处理
-    - [X] 报警事件订阅
-    - [X] 报警事件通知处理
-    - [X] 设备目录订阅
-    - [X] 设备目录通知处理
-  -  [X] 移动位置查询和显示
-  - [X] 支持手动添加设备和给设备设置单独的密码
--  [X] 支持平台对接接入
--  [X] 支持国标级联
-  - [X] 国标通道向上级联
-    - [X] WEB添加上级平台
-    - [X] 注册
-    - [X] 心跳保活
-    - [X] 通道选择
-    - [X] 通道推送
-    - [X] 点播
-    - [X] 云台控制
-    - [X] 平台状态查询
-    - [X] 平台信息查询
-    - [X] 平台远程启动
-    - [X] 每个级联平台可自定义的虚拟目录
-    - [X] 目录订阅与通知
-    - [X] 录像查看与播放
-    - [X] GPS订阅与通知(直播推流)
-- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;  
-- [X] 多流媒体节点,自动选择负载最低的节点使用。
-- [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能;
-- [X] 支持公网部署; 
-- [X] 支持wvp与zlm分开部署,提升平台并发能力
-- [X] 支持拉流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台
-- [X] 支持推流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台
-- [X] 支持推流鉴权
-- [X] 支持接口鉴权
-- [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载
-- [X] 支持打包可执行jar和war
-- [X] 支持跨域请求,支持前后端分离部署
- 
-
-# 遇到问题如何解决
-国标最麻烦的地方在于设备的兼容性,所以需要大量的设备来测试,目前作者手里的设备有限,再加上作者水平有限,所以遇到问题在所难免;
-1. 查看文档网站,仔细的阅读可以帮你避免几乎所有的问题
-2. 搜索issues,这里有大部分的答案
-3. 加QQ群(901799015),这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。
-4. 你可以请作者为你解答,但是我不是免费的。
-5. 你可以把遇到问题的设备寄给我,可以更容易的兼容设备和解决问题。
-
-# 使用帮助
-QQ群: 901799015, ZLM使用文档[https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)  
-QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对你有帮助,欢迎star和提交pr。
-
-# 授权协议
-本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议
-
-# 致谢
-感谢作者[夏楚](https://github.com/xia-chu) 提供这么棒的开源流媒体服务框架,并在开发过程中给予支持与帮助。     
-感谢作者[dexter langhuihui](https://github.com/langhuihui) 开源这么好用的WEB播放器。     
-感谢作者[Kyle](https://gitee.com/kkkkk5G) 开源了好用的前端页面     
-感谢各位大佬的赞助以及对项目的指正与帮助。包括但不限于代码贡献、问题反馈、资金捐赠等各种方式的支持!以下排名不分先后:  
-[lawrencehj](https://github.com/lawrencehj) [Smallwhitepig](https://github.com/Smallwhitepig) [swwhaha](https://github.com/swwheihei) 
-[hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen)
-[chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb)
-[ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666)
-[mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
-
-ps: 刚增加了这个名单,肯定遗漏了一些大佬,欢迎大佬联系我添加。
-
+# 合方圆国标平台
+> 本项目基于开源项目 [wvp](https://github.com/648540858/wvp-GB28181-pro)
+> 定制开发特色功能.
+## 项目开发环境
+
+## 数据库设计
+### 预置位表
+> 设备预置位通过预置位国标预置位查询指令实现.
+> 配合数据表存储预置位信息,从而实现调用功能指定预置位功能  
+
+#### preset
+
+| 字段         | 类型      | 值     | 备注         |
+|------------|---------|-------|------------|
+| id         | int     | pk    | id         |
+| deviceId   | int     | tk    | 预置位对应的设备id |
+| name       | varchar | ''    | 预置位的名称     |
+| presetCode | int     | 1-255 | 预置位编号      | 
+
+## 项目依赖
+### wvp
+### zlm

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java

@@ -207,6 +207,7 @@ public class PtzController {
 		if (logger.isDebugEnabled()) {
 			logger.debug("设备预置位查询API调用");
 		}
+		logger.info("[预置位查询] 获取设备预置位信息");
 		Device device = storager.queryVideoDevice(deviceId);
 		String uuid =  UUID.randomUUID().toString();
 		String key =  DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (ObjectUtils.isEmpty(channelId) ? deviceId : channelId);

+ 3 - 2
web_src/config/index.js

@@ -55,14 +55,15 @@ module.exports = {
     },
     // Various Dev Server settings
     host:"0.0.0.0",
-    useLocalIp: false, // can be overwritten by process.env.HOST
+    useLocalIp: true, // can be overwritten by process.env.HOST
     port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
     autoOpenBrowser: false,
     errorOverlay: true,
     notifyOnErrors: true,
     hot: true,//自动保存
     poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
-
+    allowedHosts: "all",
+    disableHostCheck: false,
 
     /**
      * Source Maps

+ 2 - 0
web_src/index.html

@@ -9,6 +9,8 @@
     <link rel="stylesheet" type="text/css" href="./static/css/login.css">
   </head>
   <body>
+    <script src="./static/dist/missile.js"></script>
+    <script src="./static/dist/h265webjs-v20221106.js"></script>
     <script type="text/javascript" src="./static/js/jessibuca/jessibuca.js"></script>
     <script type="text/javascript" src="./static/js/EasyWasmPlayer.js"></script>
     <script type="text/javascript" src="./static/js/liveplayer-lib.min.js"></script>

+ 35 - 0
web_src/src/assets/executor.js

@@ -0,0 +1,35 @@
+const PRESET_CONFIG = {
+  player: "glPlayer",
+  width: 960,
+  height: 540,
+  token:
+    "base64:QXV0aG9yOmNoYW5neWFubG9uZ3xudW1iZXJ3b2xmLEdpdGh1YjpodHRwczovL2dpdGh1Yi5jb20vbnVtYmVyd29sZixFbWFpbDpwb3JzY2hlZ3QyM0Bmb3htYWlsLmNvbSxRUTo1MzEzNjU4NzIsSG9tZVBhZ2U6aHR0cDovL3h2aWRlby52aWRlbyxEaXNjb3JkOm51bWJlcndvbGYjODY5NCx3ZWNoYXI6bnVtYmVyd29sZjExLEJlaWppbmcsV29ya0luOkJhaWR1",
+  extInfo: {
+    moovStartFlag: true,
+  },
+};
+
+// FYI. the Player class is a wrapper container provide the init and destory methods.
+// you should destory the player instance at the page unshift time.
+// By the way if you want to impl a progress bar you should view the normal_example.
+// It's a  full example. This demo only provide a minimalist case.
+// Why use class? Convenient der is enough :)
+// Should I registry the instnace at a microTask? Of course.
+// Pay attention to index.html. vite boy. Don't forget import the static source code :)
+
+export class Player {
+  _config = {};
+  instance;
+  constructor(opt = {}) {
+    const { presetConfig = PRESET_CONFIG } = opt;
+    if (presetConfig) Object.assign(this._config, presetConfig);
+  }
+
+  destory() {
+    //
+    this.instance.release();
+  }
+  init(url) {
+    this.instance = window.new265webjs(url, this._config);
+  }
+}

+ 11 - 12
web_src/src/components/common/flvVideo.vue

@@ -8,15 +8,15 @@
     </div>
     <div v-if="controller" class="v-control bg-red flex items-center">
       <div class="mx-2">
-        <a-button  @click="play" v-if="isPlay" :disabled="!isPush">播放</a-button>
-        <a-button @click="pause" v-else :disabled="!isPush">暂停</a-button>
+        <el-button  @click="play" v-if="isPlay" :disabled="!isPush">播放</-button>
+        <el-button @click="pause" v-else :disabled="!isPush">暂停</-button>
       </div>
-      <a-button @click="replay" >重新加载</a-button>
+      <el-button @click="replay" >重新加载</el-button>
       <div class="mx-1 flex">
-        <a-input v-model="flvUrl" allow-clear>
+        <el-input v-model="flvUrl" allow-clear>
           <svg-icon class="text-xl" slot="suffix" name="copy" :icon-class="'copy'"  @click.native="copyUrl"  />
-        </a-input>
-        <a-button @click="setFlvUrl(flvUrl)">手动拉流</a-button>
+        </el-input>
+        <el-button @click="setFlvUrl(flvUrl)">手动拉流</-button>
       </div>
     </div>
   </div>
@@ -70,10 +70,7 @@ export default {
         type: 'flv'
       })
       let videoEl = this.$refs.video;
-      // console.log(videoEl);
       flvPlayer.attachMediaElement(videoEl);
-      flvPlayer.load();
-      flvPlayer.play();
       flvPlayer.on(flv.Events.ERROR,(err)=>{
         console.log('---------------------------');
         console.log('------------err---------------');
@@ -91,10 +88,10 @@ export default {
     play(url){
       if(url){
         this.setFlvUrl(url);
-      }else{
-        this.isPlay = true;
-        this.videoPlayer.play();
+        this.videoPlayer.load();
       }
+      this.isPlay = true;
+      this.videoPlayer.play();
     },
     pause(){
       this.videoPlayer.pause();
@@ -116,6 +113,8 @@ export default {
   height: 0;
   overflow: hidden;
   background-color: rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
 }
 .v-parent-hide:hover .v-video {
   height: calc(100% - 50px);

+ 0 - 0
web_src/src/components/common/h265web.vue → web_src/src/components/common/h265webJessibuca.vue


+ 114 - 0
web_src/src/components/common/h265webJs.vue

@@ -0,0 +1,114 @@
+<script>
+import { Player } from "@/assets/executor";
+import copy from "@/until/copy";
+export default{
+  name: "h265WebJs",
+  props:{
+    isPush:{},
+    controller: {default:false},
+  },
+  data(){
+    return {
+      isOk: true,
+      isPlay: false,
+      loading: false,
+      playUrl: "",
+      instance: null,
+      tips: '',
+    }
+  },
+  mounted(){
+
+  },
+  beforeDestroy() {
+    this.destory();
+  },
+  methods: {
+    copyUrl(){
+      copy(this.flvUrl).then(()=>{
+        this.$message.success('复制成功');
+      }).catch(err=>{
+        this.$message.error('复制失败,请手动尝试复制');
+      })
+    },
+    initVideo(){
+      if(this.instance){
+        this.destroy();
+      }
+      // 获取宽高
+      const player = new Player({
+        player: "glPlayer",
+        width: 960,
+        height: 540,
+      });
+      player.init(this.playUrl);
+      player.instance.do();
+      this.instance = player.instance;
+    },
+    setPlayUrl(playUrl){
+      this.playUrl = playUrl;
+      this.initVideo();
+    },
+    play(url){
+      if(url){
+        this.setPlayUrl(url);
+      }
+      this.isPlay = true;
+      this.instance.play();
+    },
+    pause(){
+      if(!this.instance){
+        return console.error("[暂停] 播放器未创建")
+      }
+      this.instance.pause();
+    },
+    replay(){
+      this.initVideo();
+    },
+    destroy(){
+      if(!this.instance){
+        return console.error("[销毁播放器] 播放器未创建")
+      }
+      this.instance.release();
+    }
+  }
+}
+</script>
+
+<template>
+<div :class="`relative v-parent ${controller?'v-parent-hide':''}` ">
+  <div class="v-video flex justify-center items-center">
+    <div id="glPlayer" class="playerDom h-full"></div>
+  </div>
+
+  <div v-if="controller" class="v-control">
+    <div class="mx-2">
+      <el-button  @click="play" v-if="isPlay" :disabled="!isPush">播放</el-button>
+      <el-button @click="pause" v-else :disabled="!isPush">暂停</el-button>
+    </div>
+    <el-button @click="replay" >重新加载</el-button>
+  </div>
+</div>
+</template>
+
+<style scoped>
+.v-parent .v-video {
+  width: 100%;
+  height: 100%;
+}
+.v-parent .v-control {
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  background-color: rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+}
+.v-parent-hide:hover .v-video {
+  height: calc(100% - 50px);
+}
+.v-parent-hide:hover .v-control {
+  height: 50px;
+}
+
+</style>

+ 81 - 38
web_src/src/components/common/mpegtsVideo.vue

@@ -1,6 +1,7 @@
 <template>
   <div :class="`relative v-parent ${controller?'v-parent-hide':''}` ">
-    <div class="v-video flex justify-center items-center">
+    <div class="v-video flex justify-center items-center"
+         :style="`background-color: ${bgColor}`">
       <video class="h-full" ref="video" ></video>
       <!--      <div class="w-full h-full flex justify-center items-center" v-show="!isPlay">-->
 
@@ -8,28 +9,28 @@
     </div>
     <div v-if="controller" class="v-control bg-red flex items-center">
       <div class="mx-2">
-        <a-button  @click="play" v-if="isPlay" :disabled="!isPush">播放</a-button>
-        <a-button @click="pause" v-else :disabled="!isPush">暂停</a-button>
-      </div>
-      <a-button @click="replay" >重新加载</a-button>
-      <div class="mx-1 flex">
-        <a-input v-model="playUrl" allow-clear>
-          <svg-icon class="text-xl" slot="suffix" name="copy" :icon-class="'copy'"  @click.native="copyUrl"  />
-        </a-input>
-        <a-button @click="setplayUrl(playUrl)">手动拉流</a-button>
+        <el-button  @click="play" v-if="isPlay" :disabled="!isPush">播放</el-button>
+        <el-button @click="pause" v-else :disabled="!isPush">暂停</el-button>
       </div>
+      <el-button @click="replay" >重新加载</el-button>
     </div>
   </div>
 </template>
 
 <script>
-import mpegts from 'mpegts.js';
+import mpegTs from 'mpegts.js';
 import copy from '@/until/copy'
+import {sleep} from "@/until/time";
+
+let videoPlayer = null;
 export default {
   name: "mpegTsVideo",
   props:{
     isPush:{},
     controller: {default:false},
+    bgColor: {
+      default: "#000"
+    }
   },
   data(){
     return {
@@ -39,6 +40,8 @@ export default {
       playUrl: '',
       videoPlayer: null,
       tips: '',
+      fps: 30,
+      hasAudio: false,
     }
   },
   beforeDestroy() {
@@ -52,61 +55,91 @@ export default {
         this.$message.error('复制失败,请手动尝试复制');
       })
     },
-    setPlayUrl(playUrl){
-      this.playUrl = playUrl
-      this.initVideo()
+    async setPlayUrl(playUrl){
+      this.playUrl = playUrl;
+      await this.initVideo();
     },
     // 初始化视频播放组件
-    initVideo(){
+    async initVideo(){
       //清除
       if(!this.playUrl){
-        return this.$message.warn('请先进行推流')
+        return this.$message.warning('请先进行推流')
       }
       this.isPlay = false;
-      this.destroyVideo();
+      if(videoPlayer){
+        this.destroyVideo();
+        console.log(1)
+        // 等待2秒
+        await sleep(2 * 1000);
+      }
+      console.log(2)
       // 设置直播地址
-      let player = mpegts.createPlayer({
+      let player = mpegTs.createPlayer({
         type: 'mse',  // could also be mpegts, m2ts, flv
         isLive: true,
-        url: this.playUrl
+        url: this.playUrl,
+        liveBufferLatencyMaxLatency: 0.5,
+        enableWorker: true,
+        enableStashBuffer: true,
+        hasAudio: this.hasAudio,
+        fps: this.fps,
+        fixAudioTimestampGap: false
       });
       let videoEl = this.$refs.video;
       // console.log(videoEl);
       player.attachMediaElement(videoEl);
       player.load();
-      player.play();
-      player.on(mpegts.Events.ERROR,(err)=>{
-        console.log('---------------------------');
-        console.log('------------err---------------');
-        console.log('err');
-        console.log(err);
+      player.on(mpegTs.Events.ERROR,(err)=>{
+        // console.log('---------------------------');
+        // console.log('------------err---------------');
+        // console.log('err');
+        // console.log(err);
         this.$emit('error',err);
       })
-      this.videoPlayer = player;
+
+      player.on(mpegTs.Events.MEDIA_INFO, (obj)=>{
+        console.log(`media_info`);
+        console.log(obj);
+      })
+      videoPlayer = player;
     },
     destroyVideo(){
-      if(this.videoPlayer){
-        this.videoPlayer.destroy();
+      if(videoPlayer){
+        console.log(`[销毁播放器] `);
+        videoPlayer.pause();
+        videoPlayer.unload();
+        videoPlayer.detachMediaElement();
+        videoPlayer.destroy();
+        videoPlayer = null;
       }
     },
-    play(url){
+    async play(url,hasAudio = false,fps = 30){
       if(url){
-        this.setPlayUrl(url);
-      }else{
-        this.isPlay = true;
-        this.videoPlayer.play();
+        console.log(`[播放] 播放新url链接,初始化播放器 hasAudio:${hasAudio} fps:${fps}`);
+        this.hasAudio = hasAudio;
+        this.fps = fps || 30;
+        await this.setPlayUrl(url);
       }
+      console.log(`[播放] 继续播放视频`);
+      this.isPlay = true;
+      videoPlayer.play();
+
     },
     pause(){
-      this.videoPlayer.pause();
+      if(!videoPlayer){
+        console.log(`[暂停] 播放器未初始化`)
+        return false;
+      }
+      videoPlayer.pause();
     },
-    replay(){
-      this.initVideo();
+    async replay(){
+      await this.initVideo();
+      await this.play();
     },
     checkBrowser(){
       // 检查浏览器是否支持
-      if(this.videoPlayer){
-        return this.videoPlayer.isSupported();
+      if(videoPlayer){
+        return videoPlayer.isSupported();
       }else{
         return null;
       }
@@ -116,6 +149,11 @@ export default {
 </script>
 
 <style scoped >
+.v-parent{
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
 .v-parent .v-video {
   width: 100%;
   height: 100%;
@@ -125,12 +163,17 @@ export default {
   height: 0;
   overflow: hidden;
   background-color: rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
 }
 .v-parent-hide:hover .v-video {
   height: calc(100% - 50px);
 }
 .v-parent-hide:hover .v-control {
   height: 50px;
+  position: absolute;
+  left: 0;
+  bottom: 0;
 }
 
 

+ 11 - 0
web_src/src/components/common/ptzControl.vue

@@ -44,6 +44,15 @@
     </div>
 
     <div class="control-panel">
+<!--      预置位 -->
+      <el-tabs tab-position="left" style="height: 200px;">
+        <el-tab-pane label="预置位">
+<!--          预置位查询 -->
+
+        </el-tab-pane>
+        <el-tab-pane label="巡航">配置管理</el-tab-pane>
+        <el-tab-pane label="扫描">角色管理</el-tab-pane>
+      </el-tabs>
       <el-button-group>
         <el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag>
         <el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number>
@@ -133,6 +142,8 @@ export default {
       pushKey: "",
       httpsHook: "",
       httpHook: "",
+      presetList: [],
+      presetLoading: false,
     }
   },
   beforeMount() {

+ 79 - 12
web_src/src/components/dialog/customPlayer.vue

@@ -1,9 +1,21 @@
 <template>
   <div id="customPlayer" v-loading="isLoading">
-    <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="closeHandle()">
+    <el-dialog title="视频播放"
+               top="0"
+               width="956px"
+               :close-on-click-modal="false"
+               :visible.sync="showVideoDialog"
+               @close="closeHandle()">
       <div style="width: 100%; height: 100%">
         <rtc-player ref="webRTC" v-if="useActivePlayer === 'webRTC'" @eventCallback="playEventHandle"  :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
-        <mpeg-ts-video ref="flv" v-if="useActivePlayer === 'flv'" ></mpeg-ts-video>
+        <mpeg-ts-video ref="flv"
+                       :controller="true"
+                       v-if="useActivePlayer === 'flv'" ></mpeg-ts-video>
+        <h265-web-js
+          ref="h265"
+          :controller="true"
+          v-if="useActivePlayer === 'h265'"
+        ></h265-web-js>
       </div>
       <div id="shared" style="text-align: right; margin-top: 1rem;">
         <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" >
@@ -50,6 +62,11 @@
                 </el-tooltip>
               </el-radio-group>
             </div>
+            <div class="contentLine">
+              <div class="contentLabel">
+                切换播放格式:
+              </div>
+            </div>
           </el-tab-pane>
         </el-tabs>
       </div>
@@ -58,14 +75,17 @@
 </template>
 
 <script>
-import rtcPlayer from '../dialog/rtcPlayer.vue'
 import PtzControl from "@/components/common/ptzControl";
 import {webrtcEvent} from "@/map/eventMap";
 // import FlvVideo from "@/components/common/flvVideo.vue";
+
+import rtcPlayer from '../dialog/rtcPlayer.vue'
 import mpegTsVideo from "@/components/common/mpegtsVideo.vue"
+import H265WebJs from "@/components/common/h265webJs.vue";
 export default {
   name: "customPlayer",
   components: {
+    H265WebJs,
     mpegTsVideo,
     PtzControl,
     rtcPlayer,
@@ -98,6 +118,21 @@ export default {
           description: "延迟较低的播放器,需要浏览器支持",
           disable: false
         },
+        {
+          key: "flv",
+          text: "flv 播放器",
+          description: "用于播放flv格式视频,可支持aac音频",
+          disable: false
+        },
+        {
+          key: "h265",
+          text: "265 播放器",
+          description: "用于播放265格式视频",
+          disable: false
+        }
+      ],
+      // mpegTs流
+      mpegTs: [
         {
           key: "flv",
           text: "flv 播放器",
@@ -110,7 +145,8 @@ export default {
       // 如何你只是用一种播放器,直接注释掉不用的部分即可
       player: {
         webRTC: ["rtc", "rtcs"],
-        flv: ["flv", "https_flv"],
+        flv: ["ws_flv", "wss_flv"],
+        h265: ["ws_flv", "wss_flv"]
       },
       videoHistory: {
         date: '',
@@ -170,7 +206,6 @@ export default {
       console.log('切换播放器');
       // 暂停旧播放器视频
       this.$refs[this.useActivePlayer].pause();
-
       this.$nextTick(()=>{
         // 使用新播放器打开视频
         this.useActivePlayer = activePlayerKey;
@@ -188,7 +223,7 @@ export default {
         clearTimeout(this.playEventHandleTimer);
       }
       this.playEventHandleTimer = setTimeout(()=>{
-        this.webrtcEventExecute(type);
+        this.webrtcEventExecute(type,e);
       },300);
 
     },
@@ -230,6 +265,20 @@ export default {
         hasAudio = this.hasAudio;
       }
       this.streamInfo = streamInfo;
+
+      // 获取 tracks 中的 fps 值
+      if(streamInfo.tracks){
+        // 根据我们摄像头默认fps值来进行配置播放器
+        let fps = 15;
+        for (const tracksElement of streamInfo.tracks) {
+          if(tracksElement.fps){
+            fps = tracksElement.fps;
+            break;
+          }
+        }
+        this.fps = fps;
+      }
+
       this.hasAudio = hasAudio;
       this.isLoading = false;
       // this.videoUrl = streamInfo.rtc;
@@ -242,11 +291,17 @@ export default {
     },
     playFromStreamInfo(realHasAudio, streamInfo) {
       this.showVideoDialog = true;
-      this.hasaudio = realHasAudio && this.hasaudio;
+      console.log(realHasAudio);
+      // this.hasAudio = realHasAudio && this.hasaudio;
       this.videoUrl = this.getUrlByStreamInfo(streamInfo);
       console.log(this.videoUrl);
       this.$nextTick(()=>{
-        this.$refs[this.useActivePlayer].play(this.videoUrl,this.enableDebug)
+        this.$refs[this.useActivePlayer].play(
+          this.videoUrl,
+          this.hasAudio,
+          this.fps,
+          this.enableDebug
+        )
       });
     },
     openDialog: function (tab, deviceId, channelId, param) {
@@ -286,8 +341,8 @@ export default {
       });
     },
     getUrlByStreamInfo(){
-      console.log("获取基础拉流地址")
-      console.log(this.streamInfo)
+      console.log("获取基础拉流地址");
+      console.log(this.streamInfo);
       let playerData = this.player[this.useActivePlayer];
       if (location.protocol === "https:") {
         this.videoUrl = this.streamInfo[playerData[1]]
@@ -335,13 +390,16 @@ export default {
     },
     convertStopClick: function() {
       this.convertStop(()=>{
-        this.$refs[this.useActivePlayer].play(this.videoUrl)
+        console.log('播放默认格式');
+        this.$refs[this.useActivePlayer].play(
+          this.videoUrl,
+        )
       });
     },
     closeHandle(){
       this.$emit('close')
     },
-    webrtcEventExecute(type){
+    webrtcEventExecute(type,e){
       // 防抖,防止多次触发
       if(type === webrtcEvent.apiFail.code){
         this.$notify.error({
@@ -561,4 +619,13 @@ export default {
 .contentLine .contentLabel{
   margin-right: 5px;
 }
+
+.mx-2{
+  margin-left: 2rem;
+  margin-right: 2rem;
+}
+.mx-1{
+  margin-left: 1rem;
+  margin-right: 1rem;
+}
 </style>

+ 7 - 1
web_src/src/components/dialog/dialogPtzControl.vue

@@ -1,6 +1,12 @@
 <template>
   <div id="ptzControl" v-loading="isLoading">
-    <el-dialog title="云台控制" top="0" :close-on-click-modal="false" :visible.sync="showPTZDialog" @close="close()">
+    <el-dialog
+      title="云台控制"
+      width="956px"
+      top="0"
+      :close-on-click-modal="false"
+      :visible.sync="showPTZDialog"
+      @close="close()">
       <div style="width: 100%; height: 100%"></div>
     <div id="shared" style="text-align: right; margin-top: 1rem;">
       <el-tabs v-model="tabActiveName" @tab-click="tabHandleClick" >

+ 1 - 1
web_src/src/components/dialog/rtcPlayer.vue

@@ -39,7 +39,7 @@ export default {
     //     immediate:true
     // },
     methods: {
-        play: function (url,enableDebug = false) {
+        play: function (url,hasAudio,fps,enableDebug = false) {
           this.enableDebug = enableDebug;
           if(enableDebug){
             console.log("播放地址为: " + url);