e 4 年之前
父節點
當前提交
d9bc431bbc
共有 88 個文件被更改,包括 26262 次插入2 次删除
  1. 18 2
      README.md
  2. 14 0
      babel.config.js
  3. 11363 0
      package-lock.json
  4. 30 0
      package.json
  5. 二進制
      public/favicon.ico
  6. 17 0
      public/index.html
  7. 二進制
      public/static/image/close.png
  8. 189 0
      public/static/index.html
  9. 154 0
      public/static/index1.html
  10. 1153 0
      public/static/src/Decoder.js
  11. 二進制
      public/static/src/Decoder.wasm
  12. 180 0
      public/static/src/H2642.js
  13. 387 0
      public/static/src/H264SPSParser.js
  14. 582 0
      public/static/src/H264Session.js
  15. 86 0
      public/static/src/H265.js
  16. 287 0
      public/static/src/H265SPSParser.js
  17. 416 0
      public/static/src/H265Session.js
  18. 563 0
      public/static/src/H265Session2.js
  19. 573 0
      public/static/src/MP4Remux.js
  20. 413 0
      public/static/src/MediaSource.js
  21. 262 0
      public/static/src/ROIDrawer.js
  22. 165 0
      public/static/src/SuperRender_20.js
  23. 250 0
      public/static/src/YUVPlayer.js
  24. 98 0
      public/static/src/drawer.js
  25. 141 0
      public/static/src/ivsDrawer.js
  26. 19 0
      public/static/src/jsFFMPEG.js
  27. 257 0
      public/static/src/md5.js
  28. 80 0
      public/static/src/player.js
  29. 570 0
      public/static/src/util.js
  30. 156 0
      public/static/src/videoBuffer.js
  31. 128 0
      public/static/src/videoWorker.js
  32. 653 0
      public/static/src/websocketServer.js
  33. 566 0
      public/static/src/workerManager.js
  34. 87 0
      src/App.vue
  35. 二進制
      src/assets/bg_header.png
  36. 二進制
      src/assets/box_bg.png
  37. 二進制
      src/assets/box_bg1.png
  38. 二進制
      src/assets/box_bg13.png
  39. 二進制
      src/assets/box_bg2.png
  40. 二進制
      src/assets/box_bg4.png
  41. 二進制
      src/assets/box_bg5.png
  42. 二進制
      src/assets/box_bg6.png
  43. 二進制
      src/assets/chilun.png
  44. 10 0
      src/assets/css/reset.scss
  45. 二進制
      src/assets/default.png
  46. 二進制
      src/assets/f7dc79dbfa71373458cc8ee92b7053c.jpg
  47. 二進制
      src/assets/icon_NH3.png
  48. 二進制
      src/assets/icon_T.png
  49. 二進制
      src/assets/icon_humidity.png
  50. 二進制
      src/assets/icon_meter.png
  51. 二進制
      src/assets/icon_ph.png
  52. 二進制
      src/assets/robot.gif
  53. 二進制
      src/assets/robot.mp4
  54. 二進制
      src/assets/u12544.png
  55. 二進制
      src/assets/u204.png
  56. 二進制
      src/assets/u208.png
  57. 二進制
      src/assets/u210.png
  58. 二進制
      src/assets/u23.png
  59. 二進制
      src/assets/u24.png
  60. 二進制
      src/assets/u25.png
  61. 二進制
      src/assets/u26.png
  62. 二進制
      src/assets/u262.png
  63. 二進制
      src/assets/u263.png
  64. 二進制
      src/assets/u525.png
  65. 7 0
      src/assets/u532.svg
  66. 二進制
      src/assets/u62.png
  67. 二進制
      src/assets/u861.png
  68. 二進制
      src/assets/user.png
  69. 二進制
      src/assets/user1.png
  70. 329 0
      src/components/imgDragScale.vue
  71. 411 0
      src/components/robotCanvas.vue
  72. 26 0
      src/main.js
  73. 44 0
      src/router/index.js
  74. 15 0
      src/store/index.js
  75. 613 0
      src/utils/drawingBoard.js
  76. 75 0
      src/utils/http.js
  77. 40 0
      src/utils/utils.js
  78. 928 0
      src/views/biology/biology.vue
  79. 1145 0
      src/views/environmentalProtection/environmentalProtection.vue
  80. 560 0
      src/views/home/Home.vue
  81. 149 0
      src/views/home/charts/ELeft1.vue
  82. 116 0
      src/views/home/charts/Eline1.vue
  83. 106 0
      src/views/home/charts/Eline2.vue
  84. 88 0
      src/views/home/charts/chartLine.vue
  85. 370 0
      src/views/home/swiperMulti.vue
  86. 524 0
      src/views/monitor/monitor.vue
  87. 836 0
      src/views/robot/robot.vue
  88. 13 0
      vue.config.js

+ 18 - 2
README.md

@@ -1,3 +1,19 @@
-# smScreen
+# sm.screen
 
-石湾大屏
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 14 - 0
babel.config.js

@@ -0,0 +1,14 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ],
+  // plugins: [
+  //   [
+  //     "component",
+  //     {
+  //       "libraryName": "element-ui",
+  //       "styleLibraryName": "theme-chalk"
+  //     }
+  //   ]
+  // ]
+}

File diff suppressed because it is too large
+ 11363 - 0
package-lock.json


+ 30 - 0
package.json

@@ -0,0 +1,30 @@
+{
+  "name": "sm.screen",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build"
+  },
+  "dependencies": {
+    "axios": "^0.21.1",
+    "core-js": "^3.6.5",
+    "echarts": "^4.9.0",
+    "echarts-gl": "^1.1.1",
+    "el-tree-transfer": "^2.4.7",
+    "element-ui": "^2.14.1",
+    "vue": "^2.6.11",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "sass": "^1.26.5",
+    "sass-loader": "^8.0.2",
+    "vue-template-compiler": "^2.6.11",
+    "worker-loader": "^3.0.7"
+  }
+}

二進制
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

二進制
public/static/image/close.png


+ 189 - 0
public/static/index.html

@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html lang="en" style="height: 100%;">
+<head>
+    <meta charset="UTF-8">
+    <title>player</title>
+    <script type="text/javascript" src="./src/SuperRender_20.js"></script>
+    <style>
+        .container {
+            position: relative;
+        }
+
+        #canvas {
+            position: absolute;
+        }
+        #draw {
+            position: absolute;
+            top: 0;
+            left: 0;
+        }
+        #video {
+            position: absolute;
+            top: 0;
+            left: 0;
+        }
+        .close {
+            width: 40px;
+            height: 40px;
+            background-image: url('./image/close.png') ;
+            background-size: 100% 100%;
+            position: fixed;
+            top: 20px;
+            right: 20px;
+            z-index: 99;
+            cursor: pointer;
+            display: none;
+        }
+    </style>
+</head>
+<body style="text-align: center; margin: 0; z-index: 999; height: 100%;">
+    <i class="close"></i>
+    <video id="video"></video>
+    <div class="container">
+        <canvas id="canvas"></canvas>
+        <canvas id="videoCanvas" width="900px" height="450px"></canvas>
+    </div>
+    <canvas id="draw" ></canvas>
+</body>
+<!--<script src="./node_modules/file-saver/FileSaver.js"/>-->
+<!--<script src="./src/md5.js" type="text/javascript"></script>-->
+<script type="module">
+    import Player from "./src/player.js";
+    window.onload = function() {
+        document.addEventListener('click', onDocumentClick);
+        document.addEventListener('dblclick', onDocumenDblClick);
+        let video = document.getElementById('videoCanvas');
+        video.style.width = data[3];
+    };
+    var clickTimeId;
+    let str  = location.href;
+    let num = str.indexOf('?');
+    str = str.substr(num+1);
+    let data = str.split('&');
+    let isShow = Number(data[0]);
+    console.log(isShow);
+    let key = data[4];
+    const close = document.getElementsByClassName('close')[0];
+    let video = document.getElementsByTagName('body')[0];
+    console.log(video);
+    video.dblclick = function () {
+    };
+    function onDocumentClick(event) {
+        // 取消上次延时未执行的方法
+        clearTimeout(clickTimeId);
+        //执行延时
+        clickTimeId = setTimeout(function() {
+            //此处为单击事件要执行的代码
+            console.log("鼠标单击");
+        }, 250);
+    }
+    close.onclick = function () {
+      this.style.display = 'none';
+      video.style.backgroundColor = 'transparent';
+        window.parent.postMessage({
+            cmd: 'returnDate',
+            params: {
+                key: '-1',
+                isNone: true
+            }
+        }, '*');
+    };
+    function onDocumenDblClick(event) {
+        // 取消上次延时未执行的方法
+        clearTimeout(clickTimeId);
+        console.log("鼠标双击");
+        if(isShow === 1) {
+            console.log(2222);
+            close.style.display = 'block';
+            video.style.backgroundColor = '#0E1E51';
+            // video.style.width = 1920 + 'px';
+            // video.style.top = 0;
+            // video.style.left = 0;
+            window.parent.postMessage({
+                cmd: 'returnDate',
+                params: {
+                    key: key,
+                    isNone: false,
+                }
+            }, '*');
+        }
+    }
+
+    const options = {
+        video: document.getElementById('video'),
+        canvas: document.getElementById('canvas'),
+        drawer: document.getElementById('draw'),
+        // wsUrl: 'ws://192.168.1.49:10080/camera_relay?tcpaddr=admin%3Ahm123456%40192.168.1.175',
+        // rtspUrl: 'rtsp://admin:hm123456@192.168.1.175',
+        user: '',
+        pwd: ''
+    };
+    options.wsUrl = data[1];
+    options.rtspUrl = data[2];
+    console.log(options);
+    let player = new Player(options);
+    player.init();
+    player.on('error', function () {
+        console.log('连接失败')
+    });
+    player.on('noStream', function () {
+        console.log('noStream');
+        player.close();
+        player = null;
+        player = new Player(options);
+        player.init();
+        player.connect();
+    });
+
+    player.on('canplay', function () {
+        //player.close();
+    });
+
+    player.on('initialCompleted', function () {
+        let data = [[
+            {x: 2861, y: 4395},
+            {x: 6403, y: 4013},
+            {x: 3260, y: 7986},
+            {x: 640, y: 6252}
+        ]];
+        console.log('initialCompleted')
+        player.setROI(data);
+    });
+
+    player.on('ROIFinished', function () {
+       console.log('ROIFinished');
+    });
+
+    player.connect();
+
+    document.getElementById('close').onclick = ()=>{
+        player.close();
+    };
+
+    document.getElementById('ROIreset').onclick = () =>{
+        player.resetROI();
+    };
+
+    document.getElementById('ROIset').onclick = () =>{
+        let data = [[
+            {x: 2861, y: 4395},
+            {x: 6403, y: 4013},
+            {x: 3260, y: 7986},
+            {x: 640, y: 6252}
+        ]];
+        player.setROI(data);
+    };
+
+    document.getElementById('ROIdata').onclick = () =>{
+        let result = player.getROIData();
+        console.log(result)
+    };
+
+    document.getElementById('ROIsetNum').onclick = ()=> {
+        let num = document.getElementById('ROINum').value;
+        player.setPolygonNum(num);
+    }
+</script>
+<script>
+</script>
+</html>

+ 154 - 0
public/static/index1.html

@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>player</title>
+</head>
+<body style="text-align: center; margin: 0">
+    <video  height="100%" id="video"></video>
+    <canvas id="canvas" ></canvas>
+    <canvas id="draw" ></canvas>
+</body>
+<!--<script src="./node_modules/file-saver/FileSaver.js"/>-->
+<!--<script src="./src/md5.js" type="text/javascript"></script>-->
+<script type="module">
+    import Player from "./src/player.js";
+
+    window.onload = function() {
+        document.addEventListener('click', onDocumentClick);
+        document.addEventListener('dblclick', onDocumenDblClick);
+        let video = document.getElementsByTagName('body')[0];
+        video.style.height = data[3];
+    };
+    var clickTimeId;
+	let str  = location.href;
+	let num = str.indexOf('?');
+	str = str.substr(num+1);
+	let data = str.split('&');
+	let isShow = Number(data[0]);
+	console.log(isShow);
+
+    let video = document.getElementsByTagName('body')[0];
+    console.log(video);
+    video.dblclick = function () {
+    }
+    function onDocumentClick(event) {
+        // 取消上次延时未执行的方法
+        clearTimeout(clickTimeId);
+        //执行延时
+        clickTimeId = setTimeout(function() {
+            //此处为单击事件要执行的代码
+            console.log("鼠标单击");
+        }, 250);
+    }
+    function onDocumenDblClick(event) {
+        // 取消上次延时未执行的方法
+        clearTimeout(clickTimeId);
+        console.log("鼠标双击");
+        if(isShow === 1) {
+            console.log(2222);
+            window.parent.postMessage({
+                cmd: 'returnDate',
+                params: {
+                    wsUrl: data[1],
+                    rtspUrl: data[2]
+                }
+            }, '*');
+        }
+    }
+	// let data = str.split('cameraOne=')[1];
+	// let data1 = JSON.parse(data);
+    const options = {
+        video: document.getElementById('video'),
+        canvas: document.getElementById('canvas'),
+        drawer: document.getElementById('draw'),
+        // wsUrl:'ws://115.238.57.190:10080/camera_relay?tcpaddr=admin%3Ahm123456%40192.168.1.123',
+        // rtspUrl: 'rtsp://admin:hm123456@192.168.1.123',
+        user: '',
+        pwd: ''
+    };
+    options.wsUrl = data[1];
+    options.rtspUrl = data[2];
+    let player = new Player(options);
+    player.init();
+    player.on('error', function () {
+        console.log('连接失败')
+    });
+    player.on('noStream', function () {
+        console.log('noStream');
+        player.close();
+        player = null;
+        player = new Player(options);
+        player.init();
+        player.connect();
+    });
+
+    player.on('canplay', function () {
+        //player.close();
+    });
+
+    player.on('initialCompleted', function () {
+        // let data = [[
+        //     {x: 2861, y: 4395},
+        //     {x: 6403, y: 4013},
+        //     {x: 3260, y: 7986},
+        //     {x: 640, y: 6252}
+        // ]];
+        // console.log('initialCompleted')
+        // player.setROI(data);
+    });
+
+    player.on('ROIFinished', function () {
+       console.log('ROIFinished');
+    });
+
+    player.connect();
+
+    document.getElementById('close').onclick = ()=>{
+        player.close();
+    };
+
+    document.getElementById('ROIreset').onclick = () =>{
+        player.resetROI();
+    };
+
+    document.getElementById('ROIset').onclick = () =>{
+        let data = [[
+            {x: 2861, y: 4395},
+            {x: 6403, y: 4013},
+            {x: 3260, y: 7986},
+            {x: 640, y: 6252}
+        ]];
+        player.setROI(data);
+    };
+
+    document.getElementById('ROIdata').onclick = () =>{
+        let result = player.getROIData();
+        console.log(result)
+    };
+
+    document.getElementById('ROIsetNum').onclick = ()=> {
+        let num = document.getElementById('ROINum').value;
+        player.setPolygonNum(num);
+    }
+
+    document.getElementById('ROIChange1').onclick = ()=> {
+        player.changeROIType('Polygon1');
+    }
+
+    document.getElementById('ROIChange2').onclick = ()=> {
+        player.changeROIType('Polygon2');
+    }
+
+    document.getElementById('ROIChange3').onclick = ()=> {
+        player.changeROIType('Polygon1Line1');
+    }
+
+    document.getElementById('ROIChange4').onclick = ()=> {
+        player.changeROIType('Line2');
+    }
+
+</script>
+<script>
+</script>
+</html>

File diff suppressed because it is too large
+ 1153 - 0
public/static/src/Decoder.js


二進制
public/static/src/Decoder.wasm


+ 180 - 0
public/static/src/H2642.js

@@ -0,0 +1,180 @@
+
+importScripts('./jsFFMPEG.js');
+let initDecoder = null;
+let decoderContext = null;
+let decodeByFFMPEG = null;
+let getWidth = null;
+let getHeight = null;
+let closeContext = null;
+let context = null;
+let outpic = null;
+let outpicptr = null;
+let ID = 264;
+let initialized = false;
+let firstIFrame = true;
+
+class H264Decoder {
+    constructor() {
+
+
+
+       //Module.onRuntimeInitialized = () => {
+            console.log('h264 decoder init')
+
+            initDecoder = Module.cwrap('init_jsFFmpeg', 'void', []);
+            decoderContext = Module.cwrap('context_jsFFmpeg', 'number', ['number']);
+            decodeByFFMPEG = Module.cwrap('decode_video_jsFFmpeg', 'number', ['number', 'array', 'number', 'number']);
+            getWidth = Module.cwrap('get_width', 'number', ['number']);
+            getHeight = Module.cwrap('get_height', 'number', ['number']);
+            closeContext = Module.cwrap('close_jsFFmpeg', 'number', ['number']);
+        initDecoder();
+            this.init();
+            initialized = true;
+        //};
+    }
+    init() {
+        if (context !== null) {
+            closeContext(context);
+            context = null;
+        }
+        context = decoderContext(ID);
+    }
+
+    setOutputSize(size) {
+        console.log("H264 Decoder setOutputSize");
+        let outpicsize = size * 1.5;
+        outpicptr = Module._malloc(outpicsize);
+        outpic = new Uint8Array(Module.HEAPU8.buffer, outpicptr, outpicsize);
+    }
+
+    decode(data) {
+        if(!initialized) {
+            console.log('未初始化完成')
+            return null;
+        }
+        console.log(data.subarray(0, 100))
+        let frameType = ((data[4] & 0x1f) === 7 || (data[4] & 0x1f) === 5) ? 'I' : 'P';
+    //console.log(data[4] & 0x1f)
+        if(frameType === 'I' && initialized) {
+            firstIFrame = true;
+        }
+        if(!firstIFrame) {
+            console.log('非 firstIFrame')
+            return null;
+        }
+        let beforeDecoding = null;
+        let decodingTime = null;
+        let frameData = null;
+        let bufferIdx = null;
+        var dataHeap = null;
+
+        beforeDecoding = Date.now();
+
+        decodeByFFMPEG(context, data, data.length, outpic.byteOffset);
+        decodingTime = Date.now() - beforeDecoding;
+
+
+
+        let width = getWidth(context);
+        let height = getHeight(context);
+
+        console.log(width, height)
+        if (width > 0 && height > 0) {
+            let copyOutput = new Uint8Array(outpic);
+
+            frameData = {
+                'data': copyOutput,
+                'bufferIdx': bufferIdx,
+                'width': width,
+                'height': height,
+                'codecType': 'h264',
+                'decodingTime': decodingTime,
+                'frameType': frameType,
+            };
+            console.log(frameData)
+
+            return frameData;
+        }
+
+        // if(decodeby) {
+        //     //avcodec_decode_video2(context, outpicptr, size, data);
+        //     //console.log(Module.HEAP8.subarray(outpicptr, outpicptr+20))
+        //
+        //     //
+        //     var memoryData = Module._malloc(data.byteLength);
+        //     Module.HEAPU8.set(data, memoryData);
+        //     var ptr = Module._video_decode_frame(video_decoder_ctx, memoryData, data.byteLength);
+        //     Module._free(memoryData);
+        //     if(ptr === 0) {
+        //         console.error("[ERROR] no Frame Data!");
+        //         return null;
+        //     }else {
+        //     }
+        // }else {
+        //     try {
+        //         //outpic = Module.HEAPU8.length - data.length - 1000;
+        //         Module.HEAPU8.set(data, outpic);
+        //         console.log(data.length, data.subarray(0, 30), outpic,  Module.HEAPU8.subarray(outpic))
+        //         //let result = avcodec_decode_video2(context, outpic, size/8, data);
+        //         console.log(h264_decode_frame(context, data, data.length, outpic));
+        //         //dataHeap = new Uint8Array(Module.HEAPU8.buffer, outpic, size);
+        //         //dataHeap.set(data);
+        //         //outpic.set(data);
+        //         //console.log(data.length, data.subarray(0, 30), outpic.byteOffset, outpic.subarray(0, 30), Module.HEAPU8.subarray(outpic.byteOffset))
+        //         //let result = avcodec_decode_video2(context, outpic.byteOffset, 0, data);
+        //         //console.log('result: ', result)
+        //     } catch (e) {
+        //         console.log(e)
+        //     }
+        //
+        // }
+
+
+        // if (!Constructor.prototype.isFirstFrame()) {
+        //     Constructor.prototype.setIsFirstFrame(true);
+        //     frameData = {
+        //         'firstFrame': true,
+        //     };
+        //     return frameData;
+        // }
+        // draw picture in canvas.
+        //console.log(outpic)
+
+        //console.log(width, height)
+        // if (width > 0 && height > 0) {
+        let copyOutput;
+        // if(decodeby) {
+        //     var width = Module.HEAPU32[ptr / 4],
+        //         height = Module.HEAPU32[ptr / 4 + 1],
+        //         YimgBufferPtr = Module.HEAPU32[ptr / 4 + 2],
+        //         UimgBufferPtr = Module.HEAPU32[ptr / 4 + 3],
+        //         VimgBufferPtr = Module.HEAPU32[ptr / 4 + 4],
+        //         YimageBuffer = Module.HEAPU8.subarray(YimgBufferPtr, YimgBufferPtr + width * height),
+        //         UimageBuffer = Module.HEAPU8.subarray(UimgBufferPtr, UimgBufferPtr + width * height / 4),
+        //         VimageBuffer = Module.HEAPU8.subarray(VimgBufferPtr, VimgBufferPtr + width * height / 4);
+        //
+        //     var ydata = new Uint8Array(YimageBuffer);
+        //     var udata = new Uint8Array(UimageBuffer);
+        //     var vdata = new Uint8Array(VimageBuffer);
+        //
+        //     frameData = {
+        //         'bufferIdx': bufferIdx,
+        //         'width': width,
+        //         'height': height,
+        //         'codecType': 'h264',
+        //         'decodingTime': decodingTime,
+        //         'frameType': frameType,
+        //         YData:ydata.buffer,
+        //         UData:udata.buffer,
+        //         VData:vdata.buffer,
+        //     };
+        //
+        //     return frameData;
+        // } else {
+        //     //console.log(outpic)
+        //     copyOutput = new Uint8Array(Module.HEAPU8.buffer, outpic, data.size);
+        //     //console.log(outpic)
+        //
+        // }
+    }
+}

+ 387 - 0
public/static/src/H264SPSParser.js

@@ -0,0 +1,387 @@
+//import Map from './Map.js';
+
+let BITWISE0x00000007 = 0x00000007;
+let BITWISE0x7 = 0x7;
+let BITWISE2 = 2;
+let BITWISE3 = 3;
+let BITWISE4 = 4;
+let BITWISE5 = 5;
+let BITWISE6 = 6;
+let BITWISE8 = 8;
+let BITWISE12 = 12;
+let BITWISE15 = 15;
+let BITWISE16 = 16;
+let BITWISE32 = 32;
+let BITWISE64 = 64;
+let BITWISE255 = 255;
+let BITWISE256 = 256;
+
+function H264SPSParser() {
+    let vBitCount = 0;
+    let spsMap = null;
+    let fps = null;
+
+
+    function constructor() {
+        spsMap = new Map();
+    }
+
+    constructor.prototype = {
+        parse (pSPSBytes) {
+            //console.log("=========================SPS START=========================");
+            vBitCount = 0;
+            spsMap.clear();
+
+            // forbidden_zero_bit, nal_ref_idc, nal_unit_type
+            spsMap.set("forbidden_zero_bit", readBits(pSPSBytes, 1));
+            spsMap.set("nal_ref_idc", readBits(pSPSBytes, BITWISE2));
+            spsMap.set("nal_unit_type", readBits(pSPSBytes, BITWISE5));
+
+            // profile_idc
+            spsMap.set("profile_idc", readBits(pSPSBytes, BITWISE8));
+            spsMap.set("profile_compatibility", readBits(pSPSBytes, BITWISE8));
+
+            // spsMap.set("constrained_set0_flag", readBits(pSPSBytes, 1));
+            // spsMap.set("constrained_set1_flag", readBits(pSPSBytes, 1));
+            // spsMap.set("constrained_set2_flag", readBits(pSPSBytes, 1));
+            // spsMap.set("constrained_set3_flag", readBits(pSPSBytes, 1));
+            // spsMap.set("constrained_set4_flag", readBits(pSPSBytes, 1));
+            // spsMap.set("constrained_set5_flag", readBits(pSPSBytes, 1));
+            // spsMap.set("reserved_zero_2bits", readBits(pSPSBytes, 2));
+
+            // level_idc
+            spsMap.set("level_idc", readBits(pSPSBytes, BITWISE8));
+            spsMap.set("seq_parameter_set_id", ue(pSPSBytes, 0));
+
+            let profileIdc = spsMap.get("profile_idc");
+            let BITWISE100 = 100;
+            let BITWISE110 = 110;
+            let BITWISE122 = 122;
+            let BITWISE244 = 244;
+            let BITWISE44 = 44;
+            let BITWISE83 = 83;
+            let BITWISE86 = 86;
+            let BITWISE118 = 118;
+            let BITWISE128 = 128;
+            let BITWISE138 = 138;
+            let BITWISE139 = 139;
+            let BITWISE134 = 134;
+
+            if ((profileIdc === BITWISE100) || (profileIdc === BITWISE110) ||
+                (profileIdc === BITWISE122) || (profileIdc === BITWISE244) ||
+                (profileIdc === BITWISE44) || (profileIdc === BITWISE83) ||
+                (profileIdc === BITWISE86) || (profileIdc === BITWISE118) ||
+                (profileIdc === BITWISE128) || (profileIdc === BITWISE138) ||
+                (profileIdc === BITWISE139) || (profileIdc === BITWISE134)) {
+                spsMap.set("chroma_format_idc", ue(pSPSBytes, 0));
+                if (spsMap.get("chroma_format_idc") === BITWISE3) {
+                    spsMap.set("separate_colour_plane_flag", readBits(pSPSBytes, 1));
+                }
+
+                spsMap.set("bit_depth_luma_minus8", ue(pSPSBytes, 0));
+                spsMap.set("bit_depth_chroma_minus8", ue(pSPSBytes, 0));
+                spsMap.set("qpprime_y_zero_transform_bypass_flag", readBits(pSPSBytes, 1));
+                spsMap.set("seq_scaling_matrix_present_flag", readBits(pSPSBytes, 1));
+
+                if (spsMap.get("seq_scaling_matrix_present_flag")) {
+                    let num = spsMap.get("chroma_format_idc") !== BITWISE3 ? BITWISE8 : BITWISE12;
+                    let seqScalingListPresentFlag = new Array(num);
+                    for (let i = 0; i < num; i++) {
+                        seqScalingListPresentFlag[i] = readBits(pSPSBytes, 1);
+
+                        if (seqScalingListPresentFlag[i]) {
+                            let slNumber = i < BITWISE6 ? BITWISE16 : BITWISE64;
+                            let lastScale = 8;
+                            let nextScale = 8;
+                            let deltaScale = 0;
+
+                            for (let j = 0; j < slNumber; j++) {
+                                if (nextScale) {
+                                    deltaScale = se(pSPSBytes, 0);
+                                    nextScale = (lastScale + deltaScale + BITWISE256) % BITWISE256;
+                                }
+                                lastScale = (nextScale === 0) ? lastScale : nextScale;
+                            }
+                        }
+                    }
+                    spsMap.set("seq_scaling_list_present_flag", seqScalingListPresentFlag);
+                }
+            }
+            spsMap.set("log2_max_frame_num_minus4", ue(pSPSBytes, 0));
+            spsMap.set("pic_order_cnt_type", ue(pSPSBytes, 0));
+
+            if (spsMap.get("pic_order_cnt_type") === 0) {
+                spsMap.set("log2_max_pic_order_cnt_lsb_minus4", ue(pSPSBytes, 0));
+            } else if (spsMap.get("pic_order_cnt_type") === 1) {
+                spsMap.set("delta_pic_order_always_zero_flag", readBits(pSPSBytes, 1));
+                spsMap.set("offset_for_non_ref_pic", se(pSPSBytes, 0));
+                spsMap.set("offset_for_top_to_bottom_field", se(pSPSBytes, 0));
+                spsMap.set("num_ref_frames_in_pic_order_cnt_cycle", ue(pSPSBytes, 0));
+                for (let numR = 0; numR < spsMap.get("num_ref_frames_in_pic_order_cnt_cycle"); numR++) {
+                    spsMap.set("num_ref_frames_in_pic_order_cnt_cycle", se(pSPSBytes, 0));
+                }
+            }
+            spsMap.set("num_ref_frames", ue(pSPSBytes, 0));
+            spsMap.set("gaps_in_frame_num_value_allowed_flag", readBits(pSPSBytes, 1));
+            spsMap.set("pic_width_in_mbs_minus1", ue(pSPSBytes, 0));
+            spsMap.set("pic_height_in_map_units_minus1", ue(pSPSBytes, 0));
+            spsMap.set("frame_mbs_only_flag", readBits(pSPSBytes, 1));
+
+            if (spsMap.get("frame_mbs_only_flag") === 0) {
+                spsMap.set("mb_adaptive_frame_field_flag", readBits(pSPSBytes, 1));
+            }
+            spsMap.set("direct_8x8_interence_flag", readBits(pSPSBytes, 1));
+            spsMap.set("frame_cropping_flag", readBits(pSPSBytes, 1));
+            if (spsMap.get("frame_cropping_flag") === 1) {
+                spsMap.set("frame_cropping_rect_left_offset", ue(pSPSBytes, 0));
+                spsMap.set("frame_cropping_rect_right_offset", ue(pSPSBytes, 0));
+                spsMap.set("frame_cropping_rect_top_offset", ue(pSPSBytes, 0));
+                spsMap.set("frame_cropping_rect_bottom_offset", ue(pSPSBytes, 0));
+            }
+
+            //vui parameters
+            spsMap.set("vui_parameters_present_flag", readBits(pSPSBytes, 1));
+            if (spsMap.get("vui_parameters_present_flag")) {
+                vuiParameters(pSPSBytes);
+            }
+
+            //console.log("=========================SPS END=========================");
+
+
+            return true;
+        },
+        getSizeInfo () {
+            let SubWidthC = 0;
+            let SubHeightC = 0;
+
+            if (spsMap.get("chroma_format_idc") === 0) { //monochrome
+                SubWidthC = SubHeightC = 0;
+            } else if (spsMap.get("chroma_format_idc") === 1) { //4:2:0
+                SubWidthC = SubHeightC = BITWISE2;
+            } else if (spsMap.get("chroma_format_idc") === BITWISE2) { //4:2:2
+                SubWidthC = BITWISE2;
+                SubHeightC = 1;
+            } else if (spsMap.get("chroma_format_idc") === BITWISE3) { //4:4:4
+                if (spsMap.get("separate_colour_plane_flag") === 0) {
+                    SubWidthC = SubHeightC = 1;
+                } else if (spsMap.get("separate_colour_plane_flag") === 1) {
+                    SubWidthC = SubHeightC = 0;
+                }
+            }
+
+            let PicWidthInMbs = spsMap.get("pic_width_in_mbs_minus1") + 1;
+
+            let PicHeightInMapUnits = spsMap.get("pic_height_in_map_units_minus1") + 1;
+            let FrameHeightInMbs = (BITWISE2 - spsMap.get("frame_mbs_only_flag")) * PicHeightInMapUnits;
+
+            let cropLeft = 0;
+            let cropRight = 0;
+            let cropTop = 0;
+            let cropBottom = 0;
+
+            if (spsMap.get("frame_cropping_flag") === 1) {
+                cropLeft = spsMap.get("frame_cropping_rect_left_offset");
+                cropRight = spsMap.get("frame_cropping_rect_right_offset");
+                cropTop = spsMap.get("frame_cropping_rect_top_offset");
+                cropBottom = spsMap.get("frame_cropping_rect_bottom_offset");
+            }
+            let decodeSize = (PicWidthInMbs * BITWISE16) * (FrameHeightInMbs * BITWISE16);
+            let width = (PicWidthInMbs * BITWISE16) - (SubWidthC * (cropLeft + cropRight));
+            let height = (FrameHeightInMbs * BITWISE16) -
+                (SubHeightC * (BITWISE2 - spsMap.get("frame_mbs_only_flag")) * (cropTop + cropBottom));
+
+            let sizeInfo = {
+                'width': width,
+                'height': height,
+                'decodeSize': decodeSize,
+            };
+
+            return sizeInfo;
+        },
+        getSpsValue (key) {
+            return spsMap.get(key);
+        },
+        getCodecInfo () {
+            let profileIdc = spsMap.get("profile_idc").toString(BITWISE16);
+            let profileCompatibility = spsMap.get("profile_compatibility") < BITWISE15 ?
+                "0" + spsMap.get("profile_compatibility").toString(BITWISE16) :
+                spsMap.get("profile_compatibility").toString(BITWISE16);
+
+            let levelIdc = spsMap.get("level_idc").toString(BITWISE16);
+
+            //console.log("getCodecInfo = " + (profile_idc + profile_compatibility + level_idc));
+            return profileIdc + profileCompatibility + levelIdc;
+
+        },
+
+        getSpsMap() {
+            return spsMap;
+        },
+
+        getFPS() {
+            return fps;
+        }
+    }
+
+    return new constructor();
+
+    function getBit(base, offset) {
+        let offsetData = offset;
+        let vCurBytes = (vBitCount + offsetData) >> BITWISE3;
+        offsetData = (vBitCount + offset) & BITWISE0x00000007;
+        return (((base[(vCurBytes)])) >> (BITWISE0x7 - (offsetData & BITWISE0x7))) & 0x1;
+    }
+
+    function readBits(pBuf, vReadBits) {
+        let vOffset = 0;
+        let vTmp = 0,
+            vTmp2 = 0;
+
+        if (vReadBits === 1) {
+            vTmp = getBit(pBuf, vOffset);
+        } else {
+            for (let i = 0; i < vReadBits; i++) {
+                vTmp2 = getBit(pBuf, i);
+                vTmp = (vTmp << 1) + vTmp2;
+            }
+        }
+
+        vBitCount += vReadBits;
+        return vTmp;
+    }
+
+    function ue(base, offset) {
+        let zeros = 0,
+            vTmp = 0,
+            vReturn = 0;
+        let vIdx = offset;
+        do {
+            vTmp = getBit(base, vIdx++);
+            if (vTmp === 0) {
+                zeros++;
+            }
+        } while (0 === vTmp);
+
+        if (zeros === 0) {
+            vBitCount += 1;
+            return 0;
+        }
+
+        vReturn = 1 << zeros;
+
+        for (let i = zeros - 1; i >= 0; i--, vIdx++) {
+            vTmp = getBit(base, vIdx);
+            vReturn |= vTmp << i;
+        }
+
+        let addBitCount = (zeros * BITWISE2) + 1;
+        vBitCount += addBitCount;
+
+        return (vReturn - 1);
+    }
+
+    function se(base, offset) {
+        let vReturn = ue(base, offset);
+
+        if (vReturn & 0x1) {
+            return (vReturn + 1) / BITWISE2;
+        } else {
+            return -vReturn / BITWISE2;
+        }
+    }
+
+    function hrdParameters(pSPSBytes) {
+        spsMap.set("cpb_cnt_minus1", ue(pSPSBytes, 0));
+        spsMap.set("bit_rate_scale", readBits(pSPSBytes, BITWISE4));
+        spsMap.set("cpb_size_scale", readBits(pSPSBytes, BITWISE4));
+        let cpdCntMinus1 = spsMap.get("cpb_cnt_minus1");
+        let bitRateValueMinus1 = new Array(cpdCntMinus1);
+        let cpbSizeValueMinus1 = new Array(cpdCntMinus1);
+        let cbrFlag = new Array(cpdCntMinus1);
+        //Todo: 原本为i <= cpdCntMinus1,运行到此处时直接停住,原因不明,改为<后正常
+        for (let i = 0; i < cpdCntMinus1; i++) {
+            bitRateValueMinus1[i] = ue(pSPSBytes, 0);
+            cpbSizeValueMinus1[i] = ue(pSPSBytes, 0);
+            cbrFlag[i] = readBits(pSPSBytes, 1);
+        }
+        spsMap.set("bit_rate_value_minus1", bitRateValueMinus1);
+        spsMap.set("cpb_size_value_minus1", cpbSizeValueMinus1);
+        spsMap.set("cbr_flag", cbrFlag);
+
+        spsMap.set("initial_cpb_removal_delay_length_minus1", readBits(pSPSBytes, BITWISE4));
+        spsMap.set("cpb_removal_delay_length_minus1", readBits(pSPSBytes, BITWISE4));
+        spsMap.set("dpb_output_delay_length_minus1", readBits(pSPSBytes, BITWISE4));
+        spsMap.set("time_offset_length", readBits(pSPSBytes, BITWISE4));
+    }
+
+    function vuiParameters(pSPSBytes) {
+        spsMap.set("aspect_ratio_info_present_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("aspect_ratio_info_present_flag")) {
+            spsMap.set("aspect_ratio_idc", readBits(pSPSBytes, BITWISE8));
+            //Extended_SAR
+            if (spsMap.get("aspect_ratio_idc") === BITWISE255) {
+                spsMap.set("sar_width", readBits(pSPSBytes, BITWISE16));
+                spsMap.set("sar_height", readBits(pSPSBytes, BITWISE16));
+            }
+        }
+
+        spsMap.set("overscan_info_present_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("overscan_info_present_flag")) {
+            spsMap.set("overscan_appropriate_flag", readBits(pSPSBytes, 1));
+        }
+        spsMap.set("video_signal_type_present_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("video_signal_type_present_flag")) {
+            spsMap.set("video_format", readBits(pSPSBytes, BITWISE3));
+            spsMap.set("video_full_range_flag", readBits(pSPSBytes, 1));
+            spsMap.set("colour_description_present_flag", readBits(pSPSBytes, 1));
+            if (spsMap.get("colour_description_present_flag")) {
+                spsMap.set("colour_primaries", readBits(pSPSBytes, BITWISE8));
+                spsMap.set("transfer_characteristics", readBits(pSPSBytes, BITWISE8));
+                spsMap.set("matrix_coefficients", readBits(pSPSBytes, BITWISE8));
+            }
+        }
+        spsMap.set("chroma_loc_info_present_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("chroma_loc_info_present_flag")) {
+            spsMap.set("chroma_sample_loc_type_top_field", ue(pSPSBytes, 0));
+            spsMap.set("chroma_sample_loc_type_bottom_field", ue(pSPSBytes, 0));
+        }
+        spsMap.set("timing_info_present_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("timing_info_present_flag")) {
+            spsMap.set("num_units_in_tick", readBits(pSPSBytes, BITWISE32));
+            spsMap.set("time_scale", readBits(pSPSBytes, BITWISE32));
+            spsMap.set("fixed_frame_rate_flag", readBits(pSPSBytes, 1));
+
+            fps =  spsMap.get("time_scale") / spsMap.get("num_units_in_tick");
+            if(spsMap.get("fixed_frame_rate_flag")) {
+                fps = fps / 2;
+            }
+        }
+        spsMap.set("nal_hrd_parameters_present_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("nal_hrd_parameters_present_flag")) {
+            hrdParameters(pSPSBytes);
+        }
+        spsMap.set("vcl_hrd_parameters_present_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("vcl_hrd_parameters_present_flag")) {
+            hrdParameters(pSPSBytes);
+        }
+        if (spsMap.get("nal_hrd_parameters_present_flag") ||
+            spsMap.get("vcl_hrd_parameters_present_flag")) {
+            spsMap.set("low_delay_hrd_flag", readBits(pSPSBytes, 1));
+        }
+        spsMap.set("pic_struct_present_flag", readBits(pSPSBytes, 1));
+        spsMap.set("bitstream_restriction_flag", readBits(pSPSBytes, 1));
+        if (spsMap.get("bitstream_restriction_flag")) {
+            spsMap.set("motion_vectors_over_pic_boundaries_flag", readBits(pSPSBytes, 1));
+            spsMap.set("max_bytes_per_pic_denom", ue(pSPSBytes, 0));
+            spsMap.set("max_bits_per_mb_denom", ue(pSPSBytes, 0));
+            spsMap.set("log2_max_mv_length_horizontal", ue(pSPSBytes, 0));
+            spsMap.set("log2_max_mv_length_vertical", ue(pSPSBytes, 0));
+            spsMap.set("max_num_reorder_frames", ue(pSPSBytes, 0));
+            spsMap.set("max_dec_frame_buffering", ue(pSPSBytes, 0));
+        }
+    }
+}
+
+
+
+//export default H264SPSParser;

+ 582 - 0
public/static/src/H264Session.js

@@ -0,0 +1,582 @@
+function H264Session() {
+    let rtpTimeStamp = 0;
+    let size1M = 1048576; //1024 * 1024
+    let inputBuffer = new Uint8Array(size1M);
+    let spsSegment = null;
+    let ppsSegment = null;
+
+    let SPSParser = null;
+
+    let width = 0;
+    let height = 0;
+    let inputLength = 0;
+
+    let initalSegmentFlag = true; //用于确定是否是initSegment
+    let initalMediaFrameFlag = true;
+
+    let frameRate = null; //根据SDP或者SPS设置
+    let preSample = null; //上一个Sample
+
+    let inputSegBufferSub = null;
+
+    //MSE使用的数据以及相关配置,顺序codecInfo -> initSegmentData -> mediaSample -> frameData
+    //时间戳用于绘制人脸框
+    let decodedData = {
+        frameData: null, //视频数据
+        timeStamp: null, //时间戳
+        initSegmentData: null, //MP4配置,用于initsegment
+        mediaSample: null, //使用duration控制每一帧的播放时间
+        codecInfo: "", //MSE init时传入,用于创建mediasource
+    };
+    let FRAMETYPE = {
+        1: 'P',
+        5: 'I',
+        6: 'SEI',
+        7: 'I'
+    };
+    let frameType = '';
+    let decodeMode = 'canvas';
+    let outputSize = 0;
+    let curSize = 0;
+
+    const PREFIX = new Uint8Array(['0x00', '0x00', '0x00', '0x01']);
+
+    let firstIframe = false;
+
+    let SEIInfo = {
+        ivs: null,
+        timestamp:null,
+    };
+
+    let preWidth = null,
+        preHeight = null;
+    let resetTimeCount = 0;
+    let lastTimeStamp = 0;
+    //const RESETTIME = 162000000;
+    const RESETTIME = 4320000;
+    let SEIBuffer = null;
+    let lastTime =0;
+    let decoder = null;
+    function constructor() {
+
+    }
+
+    constructor.prototype = {
+        init() {
+            SPSParser = new H264SPSParser();
+            decoder = new H264Decoder();
+            this.resolutionChangedCallback = ()=>{};
+        },
+
+        remuxRTPData(rtspInterleaved, rtpHeader, rtpPayload) {
+            //console.log(rtspInterleaved)
+            //console.log(rtpHeader)
+            let PaddingSize = 0;
+            let extensionHeaderLen = 0; //如果RtpHeader.X=1,则在RTP报头后跟有一个扩展报头
+            let PAYLOAD = null;
+//console.log(rtpHeader)
+//console.log(rtspInterleaved, rtpHeader, rtpPayload.subarray(0,5))
+            let RtpHeader = {
+                V: rtpHeader[0] >>> 6,
+                P: rtpHeader[0] & 0x20,
+                X: rtpHeader[0] & 0x10,
+                CC: rtpHeader[0] & 0x0F,
+                M: (rtpHeader[1] & 0x80) >> 7,
+                PT: rtpHeader[1] & 127,
+                SN: (rtpHeader[2] << 8) + rtpHeader[3],
+                timeStamp: (rtpHeader[4] << 24) + (rtpHeader[5] << 16) + (rtpHeader[6] << 8) + rtpHeader[7],
+                SSRC: (rtpHeader[8] << 24) + (rtpHeader[9] << 16) + (rtpHeader[10] << 8) + rtpHeader[11],
+            };
+            if (RtpHeader.P) { //填充
+                PaddingSize = rtpPayload[rtpPayload.length - 1];
+                //console.log("Padding - " + PaddingSize);
+            }
+
+            if (RtpHeader.X) { //扩展
+                extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;
+                console.log('X: ' + rtpPayload[0])
+            }
+//console.log('extensionHeaderLen: '+ extensionHeaderLen)
+            PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);
+            rtpTimeStamp = RtpHeader.timeStamp;
+
+            //console.log(rtpTimeStamp, rtpHeader[4], rtpHeader[5], rtpHeader[6] , rtpHeader[7], PAYLOAD[0] & 0x1f)
+            /* 载荷结构(https://blog.csdn.net/davebobo/article/details/52994596)
+            +---------------+
+            |0|1|2|3|4|5|6|7|
+            +-+-+-+-+-+-+-+-+
+            |F|NRI|  Type   |
+            +---------------+
+            Type = 1-23 单个NAL单元包
+            Type = 24,25, 26, 27聚合包
+            Type = 28,29, 分片单元
+            */
+            //console.log(rtspInterleaved,rtpHeader, PAYLOAD[0]);
+            let nalType = (PAYLOAD[0] & 0x1f);
+            //console.log(PAYLOAD[0] + ' nalType: ' + nalType);
+
+//console.log('rtpPayload.length: ' + rtpPayload.length)
+//console.log(nalType, PAYLOAD[0])
+//console.log('nalType: ' + nalType, RtpHeader.M, rtpTimeStamp)
+            //console.log('nalType: ', nalType, ' timestamp: ', rtpTimeStamp);
+            switch (nalType) {
+                case 6: //SEI
+                    //console.log(PAYLOAD, String.fromCharCode.apply(null, PAYLOAD))
+                    if(SEIParse(PAYLOAD) === null) {
+                        return;
+                    }
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    break;
+                case 7: //SPS
+                    //console.log('SPS');
+                    SPSParser.parse(removeH264or5EmulationBytes(PAYLOAD));
+                    let sizeInfo = SPSParser.getSizeInfo();
+                    //console.log(SPSParser.getSpsMap())
+                    width = sizeInfo.width;
+                    height = sizeInfo.height;
+
+                    if(preWidth !== width || preHeight !== height) {
+                        console.log('resolution changed!');
+                        console.log('preWidth: ', preWidth, ' preHeight: ', preHeight, ' width: ', width, ' height: ', height);
+                        preWidth = width;
+                        preHeight = height;
+                    }
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    spsSegment = PAYLOAD;
+                    //console.log('width: ',width, 'height: ', height)
+                    curSize = sizeInfo.decodeSize;
+                    firstIframe = true;
+//console.log(spsSegment)
+                    if (frameRate === null) {
+                        frameRate = SPSParser.getFPS();
+                    }
+                    break;
+                case 8: //PPS
+                    //console.log('PPS')
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    ppsSegment = PAYLOAD;
+//console.log(ppsSegment)
+                    break;
+                case 28: //FU
+                    //console.log('FU');
+                    let startBit = ((PAYLOAD[1] & 0x80) === 0x80),
+                        endBit = ((PAYLOAD[1] & 0x40) === 0x40),
+                        fuType = PAYLOAD[1] & 0x1f,
+                        payloadStartIndex = 2;
+                    //console.log('startBit: ' + startBit + ' endBit: ' + endBit)
+                    //console.log('fuType: ' + fuType)
+                    if (startBit === true && endBit === false) {
+                        let newNalHeader = new Uint8Array(1);
+                        newNalHeader[0] = ((PAYLOAD[0] & 0xe0) | fuType);
+                        //console.log('newNalHeader: ', newNalHeader[0])
+                        //console.log('fuType: ' + fuType)
+                        //console.log((PAYLOAD[2] << 8) + PAYLOAD[3])
+//console.log(new Uint8Array(PAYLOAD.subarray(0, 100)));
+                        if (false) { //赛兰摄像头,SPS,PPS,I帧打在一个RTP包中
+                            PAYLOAD[1] = newNalHeader[0];
+                            SPSParser.parse(removeH264or5EmulationBytes(PAYLOAD.subarray(1, 27)));
+                            let sizeInfo = SPSParser.getSizeInfo();
+//console.log(sizeInfo, SPSParser.getSpsMap())
+                            //SPS
+                            width = sizeInfo.width;
+                            height = sizeInfo.height;
+                            inputBuffer = setBuffer(inputBuffer, PREFIX);
+                            inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(1, 27));
+                            spsSegment = PAYLOAD.subarray(1, 27);
+                            //console.log('width: ',width, 'height: ', height)
+                            curSize = sizeInfo.decodeSize;
+                            firstIframe = true;
+
+                            //PPS
+                            inputBuffer = setBuffer(inputBuffer, PREFIX);
+                            inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(30, 34));
+                            ppsSegment = PAYLOAD.subarray(30, 34);
+
+                            //I
+                            inputBuffer = setBuffer(inputBuffer, PREFIX);
+                            inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(38, PAYLOAD.length));
+
+                        } else {
+                            //console.log(newNalHeader[0] & 0x1f)
+                            inputBuffer = setBuffer(inputBuffer, PREFIX);
+                            inputBuffer = setBuffer(inputBuffer, newNalHeader);
+                            inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                        }
+
+                    } else {
+                        //console.log(startBit, endBit, 'endBit')
+                        inputBuffer = setBuffer(inputBuffer,
+                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                    }
+//console.log(startBit,endBit)
+                    // if(endBit === true) {
+                    //     end = true;
+                    // }
+                    break;
+                case 1:
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    break;
+                default:
+                    //console.log('nalType: ' + nalType);
+                    //console.log(PAYLOAD)
+                    break;
+            }
+
+            //console.log('ppsSegment: ', ppsSegment)
+            //console.log('nalType: ' + nalType);
+//console.log('M: ' + RtpHeader.M + ' ' + (rtpHeader[1] & 0x80))
+            //console.log(RtpHeader)
+
+
+//console.log('RtpHeader.M: ', RtpHeader.M)
+            //check marker bit
+            if (RtpHeader.M) {
+                if (!firstIframe) {
+                    inputLength = 0;
+                    return;
+                }
+                let inputBufferSub = inputBuffer.subarray(0, inputLength);
+                frameType = FRAMETYPE[inputBufferSub[4] & 0x1f];
+
+                //只根据视频帧计算resetTimeCount
+                if(frameType !== 'SEI') {
+                    // rtp时间戳周期为RESETTIME,如果单向递增,设为0
+                    if(lastTimeStamp - rtpTimeStamp > (RESETTIME / 2)) { //判断lastTimeStamp远大于rtpTimeStamp,防止后一帧比前一帧先到的情况
+                        //console.log(lastTimeStamp - rtpTimeStamp)
+                        console.warn('时间戳重置', lastTimeStamp, rtpTimeStamp, frameType, new Date())
+                        resetTimeCount ++;
+                    }
+                    rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
+                } else {
+                    //同一帧的SEI比视频发送慢时
+                    if (rtpTimeStamp - lastTimeStamp > (RESETTIME / 2)) {
+                        console.warn('SEI翻转', rtpTimeStamp , lastTimeStamp);
+                        rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount - 1);
+                    } else {
+                        rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
+                    }
+                    //console.log('SEI', rtpTimeStamp, 'video', lastTimeStamp, lastTimeStamp+ RESETTIME * resetTimeCount - rtpTimeStamp)
+                    // //同一帧的SEI比视频发送快时
+                    // if(rtpTimeStamp - lastTimeStamp === 3600) {
+                    //     if(rtpTimeStamp === 0 ) {
+                    //         rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount + 1);
+                    //     } else {
+                    //         rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
+                    //     }
+                    // }
+                }
+
+                if(frameType === 'SEI') {
+                    //SEI被分片(nal === 28)时,分片包发送完后marker为1,不会和视频帧一起
+                    SEIBuffer = inputBuffer.subarray(4, inputLength);
+                    //console.log(SEIBuffer)
+                    inputBufferSub = new Uint8Array();
+                }
+                if(SEIBuffer) {
+                    let SEI = SEIParse(SEIBuffer);
+                    if (SEI) {
+                        SEIInfo.ivs = SEI;
+                        SEIInfo.timestamp = rtpTimeStamp;
+                        //console.log('GET SEI', rtpTimeStamp)
+                        //SEI信息
+                        decodedData.SEIInfo = SEIInfo;
+
+                        if (!(rtpTimeStamp - lastTime)) {
+                            console.log('上个SET包时间和本次相等');
+                            console.log('lastTime: ', lastTime, ' rtpTimeStamp: ', rtpTimeStamp, RtpHeader.timeStamp, rtpHeader[4], rtpHeader[5], rtpHeader[6], rtpHeader[7])
+                        }
+                        if ((rtpTimeStamp - lastTime) !== 3600) {
+                            //console.log('SEI 时间差:', (rtpTimeStamp - lastTime), rtpTimeStamp, lastTime)
+                        }
+                        lastTime = rtpTimeStamp;
+                        SEIInfo = {
+                            ivs: null,
+                            timestamp: 0,
+                        };
+                    }
+                    SEIBuffer = null;
+                }
+
+                if(decodeMode === 'canvas' && (frameType !== 'SEI')) {
+                    if (outputSize !== curSize) {
+                        outputSize = curSize;
+                        decoder.setOutputSize(curSize);
+                    }
+
+                    decodedData.frameData = decoder.decode(inputBufferSub);
+                    decodedData.decodeMode = 'canvas';
+                    decodedData.timeStamp = rtpTimeStamp;
+                    decodedData.width = width;
+                    decodedData.height = height;
+                    this.handleDecodedData(decodedData)
+                } else {
+                    if (!initalSegmentFlag) {
+                        decodedData.initSegmentData = null;
+                        decodedData.codecInfo = null;
+                    } else {
+                        initalSegmentFlag = false;
+                        const info = {
+                            id: 1,
+                            width: width,
+                            height: height,
+                            type: "video",
+                            profileIdc: SPSParser.getSpsValue("profile_idc"),
+                            profileCompatibility: 0,
+                            levelIdc: SPSParser.getSpsValue("level_idc"),
+                            sps: [spsSegment],
+                            pps: [ppsSegment],
+                            timescale: 1e3,
+                            fps: frameRate
+                        };
+                        decodedData.initSegmentData = info;
+                        decodedData.codecInfo = SPSParser.getCodecInfo();
+                        //console.log(info.pps)
+                    }
+
+                    if (frameType === 'I') {
+//console.log('ppsSegment: ', ppsSegment)
+                        let h264parameterLength = spsSegment.length + ppsSegment.length + 8;
+                        inputSegBufferSub = inputBufferSub.subarray(h264parameterLength, inputBufferSub.length);
+                    } else {
+                        inputSegBufferSub = inputBufferSub.subarray(0, inputBufferSub.length);
+                    }
+
+                    let segSize = inputSegBufferSub.length - 4;
+                    //mp4 box头
+                    inputSegBufferSub[0] = (segSize & 0xFF000000) >>> 24;
+                    inputSegBufferSub[1] = (segSize & 0xFF0000) >>> 16;
+                    inputSegBufferSub[2] = (segSize & 0xFF00) >>> 8;
+                    inputSegBufferSub[3] = (segSize & 0xFF);
+
+                    decodedData.frameData = new Uint8Array(inputSegBufferSub);
+
+                    let sample = {
+                        duration: Math.round((1 / frameRate) * 1000),
+                        size: inputSegBufferSub.length,
+                        frame_time_stamp: null,
+                        frameDuration: null,
+                    };
+                    sample.frame_time_stamp = rtpTimeStamp; //Todo:暂时为null,通过帧率控制duration
+                    if (initalMediaFrameFlag) {
+                        sample.frameDuration = 0;
+                        initalMediaFrameFlag = false;
+                    } else {
+                        if(frameRate) {
+                            sample.frameDuration = Math.round(1000 / frameRate);
+                        }else {
+                            sample.frameDuration = (sample.frame_time_stamp - preSample.frame_time_stamp) / 90; // 时钟频率90000,timescale=1000
+                        }
+                    }
+                    preSample = sample;
+
+                    decodedData.mediaSample = sample;
+
+                    decodedData.timeStamp = rtpTimeStamp;
+
+                    this.handleDecodedData(decodedData);
+                }
+
+
+                inputLength = 0;
+                decodedData.SEIInfo = null;
+                inputSegBufferSub = null;
+                if(frameType !== 'SEI') {
+                    lastTimeStamp = RtpHeader.timeStamp;
+                }
+                frameType = '';
+            }
+            //console.log('xxxxxxxxxxxxxxxxxxxxxxxxx')
+        },
+
+        set rtpSessionCallback(func) {
+            this.handleDecodedData = func;
+        },
+
+        setFrameRate(fps) {
+            frameRate = fps;
+            //console.log('frameRate: ', frameRate)
+        },
+
+        setResolutionChangedCallback(callback) {
+            this.resolutionChangedCallback = callback;
+        }
+    }
+
+    return new constructor();
+
+    function setBuffer(buffer1, buffer2) {
+        let bufferTemp = buffer1;
+        if ((inputLength + buffer2.length) > buffer1.length) {
+            bufferTemp = new Uint8Array(buffer1.length + size1M);
+        }
+
+        bufferTemp.set(buffer2, inputLength);
+        inputLength += buffer2.length;
+        return bufferTemp;
+    }
+}
+
+
+
+
+/**
+ * 去除SPS中的Emulation字节
+ * @param data SPS源数据
+ * @returns {Array} 去除后Emulation字节后的SPS
+ */
+function removeH264or5EmulationBytes(data) {
+    let toSize = 0;
+    let i = 0;
+    let to = [];
+    let dataLength = data.length;
+    while (i < dataLength) {
+        if (i + 2 < dataLength && data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 3) {
+            to[toSize] = to[toSize + 1] = 0;
+            toSize += 2;
+            i += 3;
+        } else {
+            to[toSize] = data[i];
+            toSize += 1;
+            i += 1;
+        }
+    }
+    return to;
+}
+
+/**
+ * 解析SEI信息
+ * @param data
+ * @return {Array}
+ */
+function SEIParse(data) {
+    //console.log(data)
+    if((data[0] & 0x1f) !== 6) {
+        //非SEI
+        return null;
+    }
+
+    if(data[1] !== 0x55 || data[2] !== 0x4C ||data[3] !== 0x53 ||data[4] !== 0x40) {
+        //ULS@开头
+        console.warn('unknown SEI type');
+        return null;
+    }
+
+    let dataLength = data.length;
+    let type = (data[5] << 8) + data[6];
+    let checkSum = (data[7] << 8) + data[8];
+    // if(dataLength !== (checkSum + 1 + 4 + 4)) {
+    //     console.log('SEI check fail!');
+    //     return null;
+    // }
+    let result;
+    data = data.subarray(9);
+    switch(type) {
+        case 0:
+            result = parseRect(data);
+            break;
+        case 1:
+            //console.log(parseBody(data))
+            result = parseBody(data);
+            break;
+        default:
+            result = null;
+            break;
+    }
+    return result;
+}
+
+function parseRect(data) {
+    //console.log(data)
+    let dataLength = data.length;
+    let contents = [];
+    while(dataLength > 0) {
+        //console.log('dataLength: ', dataLength)
+        let x0 = ((data[4] & 0x7f) << 8) + data[5],
+            y0 = ((data[6] & 0x7f) << 8) + data[7],
+            width = ((data[8] & 0x7f) << 8) + data[9] - x0,
+            height = ((data[10] & 0x7f) << 8) + data[11] - y0;
+        let content = {
+            type: 'rect',
+            id: (data[2] << 8) + data[3],
+            rect: [x0, y0, width, height],
+            state: data[1] & 0x01,
+            quality: (data[1] & 0x02) >> 1,
+        };
+        Array.prototype.push.apply(contents, [content]);
+        data = data.subarray(12);
+        dataLength = data.length;
+    }
+
+    return contents;
+}
+
+function parseBody(data) {
+    let dataLength = data.length;
+    let contents = [];
+    while(dataLength > 0) {
+        //console.log('dataLength: ', dataLength)
+        let x0 = ((data[4] & 0x7f) << 8) + data[5],
+            y0 = ((data[6] & 0x7f) << 8) + data[7],
+            width = ((data[8] & 0x7f) << 8) + data[9] - x0,
+            height = ((data[10] & 0x7f) << 8) + data[11] - y0,
+            boxConfidence = ((data[12] & 0x7f) << 8) + data[13];
+        let points = [];
+
+        for(let i = 0; i < 17; i++) {
+            let point = {
+                x: ((data[16 + i * 8] & 0x7f) << 8) + data[17 + i * 8],
+                y: ((data[18 + i * 8] & 0x7f) << 8) + data[19 + i * 8],
+                confidence: ((data[20 + i * 8] & 0x7f) << 8) + data[21 + i * 8],
+                // x: Math.random() * 8191,
+                // y: Math.random() * 8191,
+                // confidence: 1,
+            };
+            points.push(point);
+        }
+        let content = {
+            type: 'coco-pose',
+            id: (data[2] << 8) + data[3],
+            handsUp: data[1] & 0x04,
+            boundingBox: [x0, y0, width, height],
+            boxConfidence,
+            points: parseBodyToTree(points),
+            state: data[1] & 0x01,
+        };
+        Array.prototype.push.apply(contents, [content]);
+        data = data.subarray(152);
+        dataLength = data.length;
+
+    }
+    return contents;
+}
+
+/**
+ * 将智能帧中的人体姿态点转化为树结构(双亲表示法)
+ * @param points
+ */
+function parseBodyToTree(points) {
+    let newPoints = [];
+    newPoints[0] = {...points[0], parent: -1, pointColor:'#FF0002', lineColor: '#FF0002'};
+    newPoints[1] = {...points[1], parent: 0, pointColor:'#FF0002', lineColor: '#FF0002'};
+    newPoints[2] = {...points[2], parent: 0, pointColor:'#FF0002', lineColor: '#FF0002'};
+    newPoints[3] = {...points[5], parent: 17, pointColor:'#D9E34F', lineColor: '#FF0002'};
+    newPoints[4] = {...points[6], parent: 17, pointColor:'#D9E34F', lineColor: '#FF0002'};
+    newPoints[5] = {...points[3], parent: 1, pointColor:'#00FF00', lineColor: '#FF0002'};
+    newPoints[6] = {...points[4], parent: 2, pointColor:'#FFAC00', lineColor: '#FF0002'};
+    newPoints[7] = {...points[7], parent: 3, pointColor:'#00FF45', lineColor: '#'};
+    newPoints[8] = {...points[11], parent: 17, pointColor:'#EEC446', lineColor: '#'};
+    newPoints[9] = {...points[8], parent: 4, pointColor:'#43D3AF', lineColor: '#'};
+    newPoints[10] = {...points[12], parent: 17, pointColor:'#7A93E8', lineColor: '#'};
+    newPoints[11] = {...points[9], parent: 7, pointColor:'#EFB842', lineColor: '#'};
+    newPoints[12] = {...points[13], parent: 8, pointColor:'#E56C00', lineColor: '#'};
+    newPoints[13] = {...points[10], parent: 9, pointColor:'#47CD43', lineColor: '#0096FF'};
+    newPoints[14] = {...points[14], parent: 10, pointColor:'#3868D2', lineColor: '#00FF51'};
+    newPoints[15] = {...points[15], parent: 12, pointColor:'#DF4D01', lineColor: '#5100FF'};
+    newPoints[16] = {...points[16], parent: 14, pointColor:'#1E48D4', lineColor: '#00FFA0'};
+    newPoints[17] = {x: (points[5].x + points[6].x) / 2,y: (points[5].y + points[6].y) / 2, parent: -1, pointColor:'#D9E34F', lineColor: '#00FFA0'};
+    return newPoints;
+}

+ 86 - 0
public/static/src/H265.js

@@ -0,0 +1,86 @@
+
+importScripts('./jsFFMPEG.js');
+let initDecoder = null;
+let decoderContext = null;
+let decodeByFFMPEG = null;
+let getWidth = null;
+let getHeight = null;
+let closeContext = null;
+let context = null;
+let outpic = null;
+let outpicptr = null;
+let ID = 265;
+let initialized = false;
+let firstIFrame = true;
+
+class H265Decoder {
+    constructor() {
+
+        initDecoder = Module.cwrap('init_jsFFmpeg', 'void', []);
+        decoderContext = Module.cwrap('context_jsFFmpeg', 'number', ['number']);
+        decodeByFFMPEG = Module.cwrap('decode_video_jsFFmpeg', 'number', ['number', 'array', 'number', 'number']);
+        getWidth = Module.cwrap('get_width', 'number', ['number']);
+        getHeight = Module.cwrap('get_height', 'number', ['number']);
+        closeContext = Module.cwrap('close_jsFFmpeg', 'number', ['number']);
+
+        initDecoder();
+        this.init();
+        initialized = true;
+    }
+    init() {
+        // console.log("H265 Decoder init");
+        if (context !== null) {
+            closeContext(context);
+            context = null;
+        }
+        context = decoderContext(ID);
+    }
+
+    setOutputSize(size) {
+        // console.log("H265 Decoder setOutputSize");
+        let outpicsize = size * 1.5;
+        outpicptr = Module._malloc(outpicsize);
+        outpic = new Uint8Array(Module.HEAPU8.buffer, outpicptr, outpicsize);
+    }
+
+    decode(data) {
+        if(!initialized) {
+            console.log('未初始化完成')
+            return null;
+        }
+
+        let frameType = (data[4] == 0x40) ? 'I' : 'P';
+        //console.log(data[4] & 0x1f)
+        if(frameType === 'I' && initialized) {
+            firstIFrame = true;
+        }
+        if(!firstIFrame) {
+            console.log('非 firstIFrame')
+            return null;
+        }
+        let beforeDecoding = Date.now();
+        let decodingTime = null;
+        let frameData = null;
+        decodeByFFMPEG(context, data, data.length, outpic.byteOffset);
+        decodingTime = Date.now() - beforeDecoding;
+
+        let width = getWidth(context);
+        let height = getHeight(context);
+
+        //console.log(width, height)
+        if (width > 0 && height > 0) {
+            let copyOutput = new Uint8Array(outpic);
+            frameData = {
+                'data': copyOutput,
+                'width': width,
+                'height': height,
+                'codecType': 'h264',
+                'decodingTime': decodingTime,
+                'frameType': frameType,
+            };
+
+            return frameData;
+        }
+
+    }
+}

+ 287 - 0
public/static/src/H265SPSParser.js

@@ -0,0 +1,287 @@
+"use strict";
+
+
+
+function H265SPSParser() {
+    var vBitCount = 0;
+    var spsMap = null;
+    var pSPSBytes = null;
+
+    function Constructor() {
+        vBitCount = 0;
+        spsMap = new Map();
+    }
+
+    function get_bit(base, offset) {
+        var vCurBytes = (vBitCount + offset) >> 3;
+        offset = (vBitCount + offset) & 0x00000007;
+        return (((base[(vCurBytes)])) >> (0x7 - (offset & 0x7))) & 0x1;
+    }
+
+    function read_bits(pBuf, vReadBits) {
+        var vCurBytes = vBitCount / 8;
+        var vCurBits = vBitCount % 8;
+        var vOffset = 0;
+        var vTmp = 0,
+            vTmp2 = 0;
+
+        if (vReadBits == 1) {
+            vTmp = get_bit(pBuf, vOffset);
+        } else {
+            for (var i = 0; i < vReadBits; i++) {
+                vTmp2 = get_bit(pBuf, i);
+                vTmp = (vTmp << 1) + vTmp2;
+            }
+        }
+
+        vBitCount += vReadBits;
+        return vTmp;
+    }
+
+    function ue(base, offset) {
+        var zeros = 0,
+            vTmp = 0,
+            vReturn = 0;
+        var vIdx = offset;
+        do {
+            vTmp = get_bit(base, vIdx++);
+            if (vTmp == 0)
+                zeros++;
+        } while (0 == vTmp);
+
+        if (zeros == 0) {
+            vBitCount += 1;
+            return 0;
+        }
+
+        // insert first 1 bit
+        vReturn = 1 << zeros;
+
+        for (var i = zeros - 1; i >= 0; i-- , vIdx++) {
+            vTmp = get_bit(base, vIdx);
+            vReturn |= vTmp << i;
+        }
+
+        vBitCount += zeros * 2 + 1;
+
+        return (vReturn - 1);
+    }
+
+    function se(base, offset) {
+        var vReturn = ue(base, offset);
+
+        if (vReturn & 0x1) {
+            return (vReturn + 1) / 2;
+        } else {
+            return -vReturn / 2;
+        }
+    }
+
+    function byte_aligned() {
+        if ((vBitCount & 0x00000007) == 0)
+            return 1;
+        else
+            return 0;
+    }
+
+    function profile_tier_level(profilePresentFlag, maxNumSubLayersMinus1) {
+        if (profilePresentFlag) {
+            spsMap.set("general_profile_space", read_bits(pSPSBytes, 2));
+            spsMap.set("general_tier_flag", read_bits(pSPSBytes, 1));
+            spsMap.set("general_profile_idc", read_bits(pSPSBytes, 5));
+            var generalProfileCompatibilityFlag = new Array(32);
+
+            for (var j = 0; j < 32; j++) {
+                generalProfileCompatibilityFlag[j] = read_bits(pSPSBytes, 1);
+            }
+
+            spsMap.set("general_progressive_source_flag", read_bits(pSPSBytes, 1));
+            spsMap.set("general_interlaced_source_flag", read_bits(pSPSBytes, 1));
+            spsMap.set("general_non_packed_constraint_flag", read_bits(pSPSBytes, 1));
+            spsMap.set("general_frame_only_constraint_flag", read_bits(pSPSBytes, 1));
+
+            var generalProfileIdc = spsMap.get("general_profile_idc");
+            if (generalProfileIdc === 4 || generalProfileCompatibilityFlag[4] ||
+                generalProfileIdc === 5 || generalProfileCompatibilityFlag[5] ||
+                generalProfileIdc === 6 || generalProfileCompatibilityFlag[6] ||
+                generalProfileIdc === 7 || generalProfileCompatibilityFlag[7]) {
+                spsMap.set("general_max_12bit_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_max_10bit_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_max_8bit_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_max_422chroma_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_max_420chroma_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_max_monochrome_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_intra_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_one_picture_only_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_lower_bit_rate_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("general_reserved_zero_34bits", read_bits(pSPSBytes, 34));
+            } else {
+                spsMap.set("general_reserved_zero_43bits", read_bits(pSPSBytes, 43));
+            }
+
+            if ((generalProfileIdc >= 1 && generalProfileIdc <= 5) ||
+                generalProfileCompatibilityFlag[1] || generalProfileCompatibilityFlag[2] ||
+                generalProfileCompatibilityFlag[3] || generalProfileCompatibilityFlag[4] ||
+                generalProfileCompatibilityFlag[5]) {
+                /* The number of bits in this syntax structure is not affected by this condition */
+                spsMap.set("general_inbld_flag", read_bits(pSPSBytes, 1));
+            } else {
+                spsMap.set("general_reserved_zero_bit", read_bits(pSPSBytes, 1));
+            }
+        }
+
+        spsMap.set("general_level_idc", read_bits(pSPSBytes, 8));
+        var subLayerProfilePresentFlag = new Array(maxNumSubLayersMinus1);
+        var subLayerLevelPresentFlag = new Array(maxNumSubLayersMinus1);
+
+        for (i = 0; i < maxNumSubLayersMinus1; i++) {
+            subLayerProfilePresentFlag[i] = read_bits(pSPSBytes, 1);
+            subLayerLevelPresentFlag[i] = read_bits(pSPSBytes, 1);
+        }
+
+        var reservedZero2bits = new Array(8);
+        var subLayerProfileIdc = new Array(maxNumSubLayersMinus1);
+
+        if (maxNumSubLayersMinus1 > 0) {
+            for (var i = maxNumSubLayersMinus1; i < 8; i++) {
+                reservedZero2bits[i] = read_bits(pSPSBytes, 2);
+            }
+        }
+
+        for (var i = 0; i < maxNumSubLayersMinus1; i++) {
+            if (subLayerProfilePresentFlag[i]) {
+                spsMap.set("sub_layer_profile_space", read_bits(pSPSBytes, 2));
+                spsMap.set("sub_layer_tier_flag", read_bits(pSPSBytes, 1));
+                subLayerProfileIdc[i] = read_bits(pSPSBytes, 5);
+
+                for (var j = 0; j < 32; j++) {
+                    subLayerProfileCompatibilityFlag[i][j] = read_bits(pSPSBytes, 1);
+                }
+
+                spsMap.set("sub_layer_progressive_source_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("sub_layer_interlaced_source_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("sub_layer_non_packed_constraint_flag", read_bits(pSPSBytes, 1));
+                spsMap.set("sub_layer_frame_only_constraint_flag", read_bits(pSPSBytes, 1));
+
+                if (subLayerProfileIdc[i] === 4 || subLayerProfileCompatibilityFlag[i][4] ||
+                    subLayerProfileIdc[i] === 5 || subLayerProfileCompatibilityFlag[i][5] ||
+                    subLayerProfileIdc[i] === 6 || subLayerProfileCompatibilityFlag[i][6] ||
+                    subLayerProfileIdc[i] === 7 || subLayerProfileCompatibilityFlag[i][7]) {
+                    /* The number of bits in this syntax structure is not affected by this condition */
+                    spsMap.set("sub_layer_max_12bit_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_max_10bit_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_max_8bit_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_max_422chroma_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_max_420chroma_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_max_monochrome_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_intra_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_one_picture_only_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_lower_bit_rate_constraint_flag", read_bits(pSPSBytes, 1));
+                    spsMap.set("sub_layer_reserved_zero_34bits", read_bits(pSPSBytes, 34));
+                } else {
+                    spsMap.set("sub_layer_reserved_zero_43bits", read_bits(pSPSBytes, 43));
+                }
+
+                if ((subLayerProfileIdc[i] >= 1 && subLayerProfileIdc[i] <= 5) ||
+                    subLayerProfileCompatibilityFlag[1] || subLayerProfileCompatibilityFlag[2] ||
+                    subLayerProfileCompatibilityFlag[3] || subLayerProfileCompatibilityFlag[4] ||
+                    subLayerProfileCompatibilityFlag[5]) {
+                    /* The number of bits in this syntax structure is not affected by this condition */
+                    spsMap.set("sub_layer_inbld_flag", read_bits(pSPSBytes, 1));
+                } else {
+                    spsMap.set("sub_layer_reserved_zero_bit", read_bits(pSPSBytes, 1));
+                }
+            }
+
+            if (subLayerLevelPresentFlag[i]) {
+                spsMap.set("sub_layer_level_idc", read_bits(pSPSBytes, 8));
+            }
+        }
+    }
+
+    Constructor.prototype = {
+        parse: function (spsPayload) {
+            pSPSBytes = spsPayload;
+            //console.log("=========================SPS START=========================");
+            vBitCount = 0;
+            spsMap.clear();
+
+            spsMap.set("forbidden_zero_bit", read_bits(pSPSBytes, 1));
+            spsMap.set("nal_unit_type", read_bits(pSPSBytes, 6));
+            spsMap.set("nuh_layer_id", read_bits(pSPSBytes, 6));
+            spsMap.set("nuh_temporal_id_plus1", read_bits(pSPSBytes, 3));
+
+            spsMap.set("sps_video_parameter_set_id", read_bits(pSPSBytes, 4));
+            if (spsMap.get("nuh_layer_id") === 0) {
+                spsMap.set("sps_max_sub_layers_minus1", read_bits(pSPSBytes, 3));
+            } else {
+                spsMap.set("sps_ext_or_max_sub_layers_minus1", read_bits(pSPSBytes, 3));
+            }
+            var MultiLayerExtSpsFlag = (spsMap.get("nuh_layer_id") !== 0 && spsMap.get("sps_ext_or_max_sub_layers_minus1") === 7);
+
+            if (!MultiLayerExtSpsFlag) {
+                spsMap.set("sps_max_sub_layers_minus1", read_bits(pSPSBytes, 1));
+                //profile_tier_level(1, spsMap.get("sps_max_sub_layers_minus1"));
+            }
+
+            read_bits(pSPSBytes, 84);
+
+            spsMap.set("sps_seq_parameter_set_id", ue(pSPSBytes, 0));
+            if (MultiLayerExtSpsFlag) {
+                spsMap.set("update_rep_format_flag", read_bits(pSPSBytes, 1));
+                if (spsMap.get("update_rep_format_flag")) {
+                    spsMap.set("sps_rep_format_idx", read_bits(pSPSBytes, 8));
+                }
+            } else {
+                spsMap.set("chroma_format_idc", ue(pSPSBytes, 0));
+
+                if (spsMap.get("chroma_format_idc") === 3) {
+                    spsMap.set("separate_colour_plane_flag", read_bits(pSPSBytes, 1));
+                }
+
+                spsMap.set("pic_width_in_luma_samples", ue(pSPSBytes, 0));
+                spsMap.set("pic_height_in_luma_samples", ue(pSPSBytes, 0));
+
+                spsMap.set("conformance_window_flag", read_bits(pSPSBytes, 1));
+
+                if (spsMap.get("conformance_window_flag")) {
+                    spsMap.set("conf_win_left_offset", ue(pSPSBytes, 0));
+                    spsMap.set("conf_win_right_offset", ue(pSPSBytes, 0));
+                    spsMap.set("conf_win_top_offset", ue(pSPSBytes, 0));
+                    spsMap.set("conf_win_bottom_offset", ue(pSPSBytes, 0));
+                }
+            }
+
+            //console.log("=========================SPS END=========================");
+            return true;
+        },
+        getSizeInfo: function () {
+            var width = spsMap.get("pic_width_in_luma_samples");
+            var height = spsMap.get("pic_height_in_luma_samples");
+            if (spsMap.get("conformance_window_flag")) {
+                var chromaFormatIdc = spsMap.get("chroma_format_idc");
+                var separateColourPlaneFlag = spsMap.get("separate_colour_plane_flag");
+                if (typeof separateColourPlaneFlag === "undefined") {
+                    separateColourPlaneFlag = 0;
+                }
+                var subWidthC = ((1 === chromaFormatIdc) || (2 === chromaFormatIdc)) && (0 === separateColourPlaneFlag) ? 2 : 1;
+                var subHeightC = (1 === chromaFormatIdc) && (0 === separateColourPlaneFlag) ? 2 : 1;
+                width -= (subWidthC * spsMap.get("conf_win_right_offset") + subWidthC * spsMap.get("conf_win_left_offset"));
+                height -= (subHeightC * spsMap.get("conf_win_bottom_offset") + subHeightC * spsMap.get("conf_win_top_offset"));
+            }
+            var decodeSize = width * height;
+
+            var sizeInfo = {
+                'width': width,
+                'height': height,
+                'decodeSize': decodeSize
+            };
+
+            return sizeInfo;
+        },
+        getSpsValue: function (key) {
+            return spsMap.get(key);
+        },
+    };
+    return new Constructor();
+}

+ 416 - 0
public/static/src/H265Session.js

@@ -0,0 +1,416 @@
+/* exported H265Session */
+/* global Uint8Array, H265SPSParser, H265Decoder, inheritObject,
+  RtpSession, VideoBufferList, ArrayBuffer, decodeMode */
+/* eslint-disable no-magic-numbers */
+
+var H265Session = function () {
+    'use strict';
+    var rtpTimeStamp = 0,
+        inputLength = 0,
+        size1K = 1024,
+        size1M = size1K * size1K,
+        playback = false,
+        outputSize = 0,
+        curSize = 0,
+        inputBuffer = new Uint8Array(size1M),
+        PREFIX = new Uint8Array(4),
+        SPSParser = null,
+        privIRtpTime = 0,
+        // frameDiffTime = 0,
+        // needDropCnt = 0,
+        delayingTime = 0,
+        DELAY_LIMIT = 8000,
+        decodedData = {
+            frameData: null,
+            timeStamp: null,
+        },
+        timeData = {
+            'timestamp': null,
+            'timezone': null,
+        },
+        frameRate = 0;
+    var width = 0,
+        height = 0;
+
+    PREFIX[0] = '0x00';
+    PREFIX[1] = '0x00';
+    PREFIX[2] = '0x00';
+    PREFIX[3] = '0x01';
+
+    var setBuffer = function (buffer1, buffer2) {
+        var tempBuffer = buffer1;
+        if ((inputLength + buffer2.length) > tempBuffer.length) {
+            tempBuffer = new Uint8Array(tempBuffer.length + size1M);
+        }
+
+        tempBuffer.set(buffer2, inputLength);
+        inputLength += buffer2.length;
+        return tempBuffer;
+    };
+
+    var decodeMode = 'canvas';
+
+    function Constructor() {
+        this.decoder = new H265Decoder();
+    }
+
+    Constructor.prototype = {
+        init: function () {
+            SPSParser = new H265SPSParser();
+            console.log(SPSParser.parse)
+            //this.decoder.setIsFirstFrame(false);
+            this.videoBufferList = new VideoBufferList();
+            this.firstDiffTime = 0;
+            this.checkDelay = true;
+        },
+        setFrameRate(fps) {
+            frameRate = fps;
+            //console.log('frameRate: ', frameRate)
+        },
+        remuxRTPData: function (rtspInterleaved, rtpHeader, rtpPayload, isBackup) {
+            var HEADER = rtpHeader,
+                PAYLOAD = null,
+                timeData = {
+                    'timestamp': null,
+                    'timezone': null,
+                },
+                extensionHeaderLen = 0,
+                PaddingSize = 0,
+                data = {};
+
+            if (rtspInterleaved[0] !== 0x24) {
+                console.log("H265Session::it is not valid interleave header (RTSP over TCP)");
+                return;
+            } else if ((rtpHeader[0] & 0x0F) === 0x0F) {
+                console.log("H265Session::There is additional CSRC which is not handled in this version");
+                return;
+            } else if ((rtpHeader[0] & 0x20) === 0x20) {
+                PaddingSize = rtpPayload[rtpPayload.length - 1];
+                console.log("H265Session::PaddingSize - " + PaddingSize);
+            }
+
+            //Extension bit check in RTPHeader
+            if ((rtpHeader[0] & 0x10) === 0x10) {
+                extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;
+
+                //Playback check
+                if (rtpPayload[0] === 0xAB && rtpPayload[1] === 0xAD) {
+                    var startHeader = 4,
+                        NTPmsw = new Uint8Array(new ArrayBuffer(4)),
+                        NTPlsw = new Uint8Array(new ArrayBuffer(4)),
+                        gmt = new Uint8Array(new ArrayBuffer(2)),
+                        fsynctime = {
+                            'seconds': null,
+                            'useconds': null,
+                        },
+                        microseconds = null;
+
+                    NTPmsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
+                    startHeader += 4;
+
+                    NTPlsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
+                    startHeader += 6;
+
+                    gmt.set(rtpPayload.subarray(startHeader, startHeader + 2), 0);
+
+                    microseconds = (this.ntohl(NTPlsw) / 0xffffffff) * 1000;
+                    fsynctime.seconds = ((this.ntohl(NTPmsw) - 0x83AA7E80) >>> 0);
+                    fsynctime.useconds = microseconds;
+                    gmt = (((gmt[0] << 8) | gmt[1]) << 16) >> 16;
+
+                    timeData = {
+                        timestamp: fsynctime.seconds,
+                        timestamp_usec: fsynctime.useconds,
+                        timezone: gmt,
+                    };
+
+                    if ((this.getFramerate() === 0 || typeof this.getFramerate() === "undefined") &&
+                        (typeof this.getTimeStamp() !== "undefined")) {
+                        var diffUsec = timeData.timestamp_usec - this.getTimeStamp().timestamp_usec;
+                        this.setFramerate(Math.round(1000 / (((timeData.timestamp -
+                            this.getTimeStamp().timestamp) === 0 ? 0 : 1000) + (diffUsec))));
+                    }
+                    this.setTimeStamp(timeData);
+                    playback = true;
+                }
+            }
+
+            PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);
+            rtpTimeStamp = this.ntohl(rtpHeader.subarray(4, 8));
+
+            var nalType = (PAYLOAD[0] >> 1) & 0x3f;
+
+            switch (nalType) {
+                default:
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    break;
+                // Fragmentation unit(FU)
+                case 49:
+                    var startBit = ((PAYLOAD[2] & 0x80) === 0x80);
+                    var endBit = ((PAYLOAD[2] & 0x40) === 0x40);
+                    var fuType = PAYLOAD[2] & 0x3f;
+                    var payloadStartIndex = 3;
+
+                    if (startBit === true && endBit === false) {
+                        var newNalHeader = new Uint8Array(2);
+                        newNalHeader[0] = (PAYLOAD[0] & 0x81) | (fuType << 1);
+                        newNalHeader[1] = PAYLOAD[1];
+
+                        inputBuffer = setBuffer(inputBuffer, PREFIX);
+                        inputBuffer = setBuffer(inputBuffer, newNalHeader);
+                        inputBuffer = setBuffer(inputBuffer,
+                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                    } else {
+                        inputBuffer = setBuffer(inputBuffer,
+                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                    }
+                    break;
+                //SPS
+                case 33:
+                    SPSParser.parse(PAYLOAD);
+                    var resolution = SPSParser.getSizeInfo();
+                    curSize = resolution.width * resolution.height;
+                    if (playback && !isBackup) {
+                        var limitSize = 1280 * 720;
+                        if (curSize > limitSize) {
+                            data.error = {
+                                errorCode: "998",
+                                description: "Resolution is too big",
+                                place: "h265Session.js",
+                            };
+                            this.rtpReturnCallback(data);
+                            return; // data;
+                        }
+                    }
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    width = resolution.width;
+                    height = resolution.height;
+                    break;
+            } //end of switch(nalType)
+            //case 48 Remove because not use more
+
+            //check marker bit
+            var frameType = '';
+            if ((HEADER[1] & 0x80) === 0x80) {
+                // var DepacketizingTime = Date.now() - beforeDepacketizing;
+                if (outputSize !== curSize) {
+                    outputSize = curSize;
+                    this.decoder.setOutputSize(outputSize);
+                }
+
+                var inputBufferSub = inputBuffer.subarray(0, inputLength);
+                if (inputBufferSub[4] === 0x40) {
+                    frameType = 'I';
+                    if (this.firstDiffTime == 0) {
+                        privIRtpTime = rtpTimeStamp;
+                        delayingTime = 0;
+                        this.firstDiffTime = (Date.now() - (rtpTimeStamp / 90).toFixed(0));
+                        // needDropCnt = 0;
+                    } else {
+                        // frameDiffTime = Math.round(((rtpTimeStamp / 90).toFixed(0) - privIRtpTime) / this.getGovLength());
+                        if((rtpTimeStamp - privIRtpTime) < 0){
+                            this.firstDiffTime = delayingTime + (Date.now() - (rtpTimeStamp / 90).toFixed(0));
+                            // console.log("firstDiffTime = " + this.firstDiffTime + " rtpTimeStamp = " + rtpTimeStamp + " privIRtpTime = " + privIRtpTime );
+                        }
+                        delayingTime = (Date.now() - (rtpTimeStamp / 90).toFixed(0)) - this.firstDiffTime;
+                        privIRtpTime = rtpTimeStamp;
+                        if (delayingTime > DELAY_LIMIT) {
+                            if (this.checkDelay === true && playback === false) {
+                                data.error = {
+                                    errorCode: "997",
+                                    description: "Delay time is too long",
+                                    place: "h265Session.js",
+                                };
+                                // console.log("h265Session::Delay time is too long 997 error ");
+
+                                this.rtpReturnCallback(data);
+                                return; // data;
+                            }
+                        }
+                        // needDropCnt = (needDropCnt > 0) ? Math.round(needDropCnt / frameDiffTime) : 0;
+                    }
+                } else {
+                    frameType = 'P';
+                }
+
+                decodedData.frameData = null;
+                if (isBackup !== true || playback !== true) {
+                    console.log('frameType', frameType)
+                    decodedData.frameData = this.decoder.decode(inputBufferSub);
+                }
+
+                decodedData.timeStamp = null;
+                inputLength = 0;
+                if (playback === true) {
+                    timeData = (timeData.timestamp === null ? this.getTimeStamp() : timeData);
+                    decodedData.timeStamp = timeData;
+                }
+                if (isBackup) {
+                    data.backupData = {
+                        'stream': inputBufferSub,
+                        'frameType': frameType,
+                        'width': width,
+                        'height': height,
+                        'codecType': 'h265',
+                    };
+                    if (timeData.timestamp !== null && typeof timeData.timestamp !== "undefined") {
+                        data.backupData.timestamp_usec = timeData.timestamp_usec;
+                    } else {
+                        data.backupData.timestamp = (rtpTimeStamp / 90).toFixed(0);
+                    }
+                }
+                data.decodedData = decodedData;
+                if (decodeMode !== "canvas") {
+                    data.decodeMode = "canvas";
+                }
+                console.log(data)
+                this.rtpReturnCallback(data);
+                return; // data;
+            }
+        },
+        bufferingRtpData: function (rtspInterleaved, rtpHeader, rtpPayload) {
+            var HEADER = rtpHeader,
+                PAYLOAD = null,
+                extensionHeaderLen = 0,
+                PaddingSize = 0;
+
+            if (rtspInterleaved[0] !== 0x24) {
+                console.log("H265Session::it is not valid interleave header (RTSP over TCP)");
+                return;
+            } else if ((rtpHeader[0] & 0x0F) === 0x0F) {
+                console.log("H265Session::There is additional CSRC which is not handled in this version");
+                return;
+            } else if ((rtpHeader[0] & 0x20) === 0x20) {
+                PaddingSize = rtpPayload[rtpPayload.length - 1];
+                console.log("H265Session::PaddingSize - " + PaddingSize);
+            }
+
+            //Extension bit check in RTPHeader
+            if ((rtpHeader[0] & 0x10) === 0x10) {
+                extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;
+
+                //Playback check
+                if (rtpPayload[0] === 0xAB && rtpPayload[1] === 0xAD) {
+                    var startHeader = 4,
+                        NTPmsw = new Uint8Array(new ArrayBuffer(4)),
+                        NTPlsw = new Uint8Array(new ArrayBuffer(4)),
+                        gmt = new Uint8Array(new ArrayBuffer(2)),
+                        fsynctime = {
+                            'seconds': null,
+                            'useconds': null,
+                        },
+                        microseconds = null;
+
+                    NTPmsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
+                    startHeader += 4;
+
+                    NTPlsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
+                    startHeader += 6;
+
+                    gmt.set(rtpPayload.subarray(startHeader, startHeader + 2), 0);
+
+                    microseconds = (this.ntohl(NTPlsw) / 0xffffffff) * 1000;
+                    fsynctime.seconds = ((this.ntohl(NTPmsw) - 0x83AA7E80) >>> 0);
+                    fsynctime.useconds = microseconds;
+                    gmt = (((gmt[0] << 8) | gmt[1]) << 16) >> 16;
+
+                    timeData = {
+                        timestamp: fsynctime.seconds,
+                        timestamp_usec: fsynctime.useconds,
+                        timezone: gmt,
+                    };
+                    playback = true;
+                }
+            }
+
+            PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);
+
+            rtpTimeStamp = new Uint8Array(new ArrayBuffer(4));
+            rtpTimeStamp.set(rtpHeader.subarray(4, 8), 0);
+            rtpTimeStamp = this.ntohl(rtpTimeStamp);
+
+            var nalType = (PAYLOAD[0] >> 1) & 0x3f;
+
+            switch (nalType) {
+                default:
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    break;
+                // Fragmentation unit(FU)
+                case 49:
+                    var startBit = ((PAYLOAD[2] & 0x80) === 0x80);
+                    var endBit = ((PAYLOAD[2] & 0x40) === 0x40);
+                    var fuType = PAYLOAD[2] & 0x3f;
+                    var payloadStartIndex = 3;
+
+                    if (startBit === true && endBit === false) {
+                        var newNalHeader = new Uint8Array(2);
+                        newNalHeader[0] = (PAYLOAD[0] & 0x81) | (fuType << 1);
+                        newNalHeader[1] = PAYLOAD[1];
+
+                        inputBuffer = setBuffer(inputBuffer, PREFIX);
+                        inputBuffer = setBuffer(inputBuffer, newNalHeader);
+                        inputBuffer = setBuffer(inputBuffer,
+                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                    } else {
+                        inputBuffer = setBuffer(inputBuffer,
+                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                    }
+                    break;
+                //SPS
+                case 33:
+                    SPSParser.parse(PAYLOAD);
+                    var resolution = SPSParser.getSizeInfo();
+                    curSize = resolution.width * resolution.height;
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    width = resolution.width;
+                    height = resolution.height;
+                    break;
+            } //end of switch(nalType)
+            //case 48 Remove because not use more
+
+            //check marker bit
+            if ((HEADER[1] & 0x80) === 0x80) {
+                if (outputSize !== curSize) {
+                    outputSize = curSize;
+                    this.decoder.setOutputSize(outputSize);
+                }
+
+                var stepBufferSub = new Uint8Array(inputBuffer.subarray(0, inputLength));
+
+                if (this.videoBufferList !== null) {
+                    this.videoBufferList.push(stepBufferSub, width, height,
+                        'h265', (stepBufferSub[4] === 0x40) ? 'I' : 'P', timeData);
+                }
+                inputLength = 0;
+            }
+        },
+        findIFrame: function () {
+            if (this.videoBufferList !== null) {
+                var bufferNode = this.videoBufferList.findIFrame();
+                if (bufferNode === null || typeof bufferNode === "undefined") {
+                    return false;
+                } else {
+                    var data = {};
+                    this.setTimeStamp(bufferNode.timeStamp);
+                    data.frameData = this.decoder.decode(bufferNode.buffer);
+                    data.timeStamp = bufferNode.timeStamp;
+                    return data;
+                }
+            }
+        },
+        ntohl: function (buffer) {
+            return (((buffer[0] << 24) + (buffer[1] << 16) +
+                (buffer[2] << 8) + buffer[3]) >>> 0);
+        },
+        set rtpSessionCallback(func) {
+            this.rtpReturnCallback = func;
+        },
+    };
+
+    return new Constructor();
+};
+/* eslint-enable no-magic-numbers */

+ 563 - 0
public/static/src/H265Session2.js

@@ -0,0 +1,563 @@
+function H265Session() {
+    let rtpTimeStamp = 0;
+    let size1M = 1048576; //1024 * 1024
+    let inputBuffer = new Uint8Array(size1M);
+    let spsSegment = null;
+    let ppsSegment = null;
+
+    let SPSParser = null;
+
+    let width = 0;
+    let height = 0;
+    let inputLength = 0;
+
+    let initalSegmentFlag = true; //用于确定是否是initSegment
+    let initalMediaFrameFlag = true;
+
+    let frameRate = null; //根据SDP或者SPS设置
+    let preSample = null; //上一个Sample
+
+    let inputSegBufferSub = null;
+
+    //MSE使用的数据以及相关配置,顺序codecInfo -> initSegmentData -> mediaSample -> frameData
+    //时间戳用于绘制人脸框
+    let decodedData = {
+        frameData: null, //视频数据
+        timeStamp: null, //时间戳
+        initSegmentData: null, //MP4配置,用于initsegment
+        mediaSample: null, //使用duration控制每一帧的播放时间
+        codecInfo: "", //MSE init时传入,用于创建mediasource
+    };
+    let FRAMETYPE = {
+        1: 'P',
+        5: 'I',
+        6: 'SEI',
+        7: 'I'
+    };
+    let frameType = '';
+    let decodeMode = 'canvas';
+    let outputSize = 0;
+    let curSize = 0;
+
+    const PREFIX = new Uint8Array(['0x00', '0x00', '0x00', '0x01']);
+
+    let firstIframe = false;
+
+    let SEIInfo = {
+        ivs: null,
+        timestamp:null,
+    };
+
+    let preWidth = null,
+        preHeight = null;
+    let resetTimeCount = 0;
+    let lastTimeStamp = 0;
+    //const RESETTIME = 162000000;
+    const RESETTIME = 4320000;
+    let SEIBuffer = null;
+    let lastTime =0;
+    let decoder = null;
+    function constructor() {
+
+    }
+
+    constructor.prototype = {
+        init() {
+            SPSParser = new H265SPSParser();
+            decoder = new H265Decoder();
+            this.resolutionChangedCallback = ()=>{};
+        },
+
+        remuxRTPData(rtspInterleaved, rtpHeader, rtpPayload) {
+            //console.log(rtspInterleaved)
+            //console.log(rtpHeader)
+            let PaddingSize = 0;
+            let extensionHeaderLen = 0; //如果RtpHeader.X=1,则在RTP报头后跟有一个扩展报头
+            let PAYLOAD = null;
+//console.log(rtpHeader)
+//console.log(rtspInterleaved, rtpHeader, rtpPayload.subarray(0,5))
+            let RtpHeader = {
+                V: rtpHeader[0] >>> 6,
+                P: rtpHeader[0] & 0x20,
+                X: rtpHeader[0] & 0x10,
+                CC: rtpHeader[0] & 0x0F,
+                M: (rtpHeader[1] & 0x80)  === 0x80,
+                PT: rtpHeader[1] & 127,
+                SN: (rtpHeader[2] << 8) + rtpHeader[3],
+                timeStamp: (rtpHeader[4] << 24) + (rtpHeader[5] << 16) + (rtpHeader[6] << 8) + rtpHeader[7],
+                SSRC: (rtpHeader[8] << 24) + (rtpHeader[9] << 16) + (rtpHeader[10] << 8) + rtpHeader[11],
+            };
+            var HEADER = rtpHeader,
+                timeData = {
+                    'timestamp': null,
+                    'timezone': null,
+                };
+
+            if (rtspInterleaved[0] !== 0x24) {
+                console.log("H265Session::it is not valid interleave header (RTSP over TCP)");
+                return;
+            } else if ((rtpHeader[0] & 0x0F) === 0x0F) {
+                console.log("H265Session::There is additional CSRC which is not handled in this version");
+                return;
+            } else if ((rtpHeader[0] & 0x20) === 0x20) {
+                PaddingSize = rtpPayload[rtpPayload.length - 1];
+                // console.log("H265Session::PaddingSize - " + PaddingSize);
+            }
+
+            //Extension bit check in RTPHeader
+            if ((rtpHeader[0] & 0x10) === 0x10) {
+                extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;
+
+                //Playback check
+                if (rtpPayload[0] === 0xAB && rtpPayload[1] === 0xAD) {
+                    var startHeader = 4,
+                        NTPmsw = new Uint8Array(new ArrayBuffer(4)),
+                        NTPlsw = new Uint8Array(new ArrayBuffer(4)),
+                        gmt = new Uint8Array(new ArrayBuffer(2)),
+                        fsynctime = {
+                            'seconds': null,
+                            'useconds': null,
+                        },
+                        microseconds = null;
+
+                    NTPmsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
+                    startHeader += 4;
+
+                    NTPlsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0);
+                    startHeader += 6;
+
+                    gmt.set(rtpPayload.subarray(startHeader, startHeader + 2), 0);
+
+                    microseconds = (this.ntohl(NTPlsw) / 0xffffffff) * 1000;
+                    fsynctime.seconds = ((this.ntohl(NTPmsw) - 0x83AA7E80) >>> 0);
+                    fsynctime.useconds = microseconds;
+                    gmt = (((gmt[0] << 8) | gmt[1]) << 16) >> 16;
+
+                    timeData = {
+                        timestamp: fsynctime.seconds,
+                        timestamp_usec: fsynctime.useconds,
+                        timezone: gmt,
+                    };
+
+                }
+            }
+//console.log('extensionHeaderLen: '+ extensionHeaderLen)
+            PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);
+            rtpTimeStamp = RtpHeader.timeStamp;
+
+            //console.log(rtpTimeStamp, rtpHeader[4], rtpHeader[5], rtpHeader[6] , rtpHeader[7], PAYLOAD[0] & 0x1f)
+            /* 载荷结构(https://blog.csdn.net/davebobo/article/details/52994596)
+            +---------------+
+            |0|1|2|3|4|5|6|7|
+            +-+-+-+-+-+-+-+-+
+            |F|NRI|  Type   |
+            +---------------+
+            Type = 1-23 单个NAL单元包
+            Type = 24,25, 26, 27聚合包
+            Type = 28,29, 分片单元
+            */
+            //console.log(rtspInterleaved,rtpHeader, PAYLOAD[0]);
+            let nalType = (PAYLOAD[0] >> 1) & 0x3f;
+            //console.log(PAYLOAD[0] + ' nalType: ' + nalType);
+
+//console.log('rtpPayload.length: ' + rtpPayload.length)
+//console.log(nalType, PAYLOAD[0])
+//console.log('nalType: ' + nalType, RtpHeader.M, rtpTimeStamp)
+            //console.log('nalType: ', nalType, ' timestamp: ', rtpTimeStamp);
+            switch (nalType) {
+                default:
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    break;
+                // Fragmentation unit(FU)
+                case 49:
+                    var startBit = ((PAYLOAD[2] & 0x80) === 0x80);
+                    var endBit = ((PAYLOAD[2] & 0x40) === 0x40);
+                    var fuType = PAYLOAD[2] & 0x3f;
+                    var payloadStartIndex = 3;
+
+                    if (startBit === true && endBit === false) {
+                        var newNalHeader = new Uint8Array(2);
+                        newNalHeader[0] = (PAYLOAD[0] & 0x81) | (fuType << 1);
+                        newNalHeader[1] = PAYLOAD[1];
+
+                        inputBuffer = setBuffer(inputBuffer, PREFIX);
+                        inputBuffer = setBuffer(inputBuffer, newNalHeader);
+                        inputBuffer = setBuffer(inputBuffer,
+                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                    } else {
+                        inputBuffer = setBuffer(inputBuffer,
+                            PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
+                    }
+                    break;
+                //SPS
+                case 33:
+                    SPSParser.parse(PAYLOAD);
+                    var resolution = SPSParser.getSizeInfo();
+                    curSize = resolution.width * resolution.height;
+
+                    inputBuffer = setBuffer(inputBuffer, PREFIX);
+                    inputBuffer = setBuffer(inputBuffer, PAYLOAD);
+                    width = resolution.width;
+                    height = resolution.height;
+                    break;
+            } //end of switch(nalType)
+
+            //console.log('ppsSegment: ', ppsSegment)
+            //console.log('nalType: ' + nalType);
+//console.log('M: ' + RtpHeader.M + ' ' + (rtpHeader[1] & 0x80))
+            //console.log(RtpHeader)
+
+            let inputBufferSub = inputBuffer.subarray(0, inputLength);
+            if (inputBufferSub[4] === 0x40) {
+                frameType = 'I';
+            } else {
+                frameType = 'P';
+            }
+
+//console.log('RtpHeader.M: ', RtpHeader.M)
+            //check marker bit
+            if (RtpHeader.M) {
+                // if (!firstIframe) {
+                //     inputLength = 0;
+                //     return;
+                // }
+                let inputBufferSub = inputBuffer.subarray(0, inputLength);
+
+                //只根据视频帧计算resetTimeCount
+                if(frameType !== 'SEI') {
+                    // rtp时间戳周期为RESETTIME,如果单向递增,设为0
+                    if(lastTimeStamp - rtpTimeStamp > (RESETTIME / 2)) { //判断lastTimeStamp远大于rtpTimeStamp,防止后一帧比前一帧先到的情况
+                        //console.log(lastTimeStamp - rtpTimeStamp)
+                        console.warn('时间戳重置', lastTimeStamp, rtpTimeStamp, frameType, new Date())
+                        resetTimeCount ++;
+                    }
+                    rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
+                } else {
+                    //同一帧的SEI比视频发送慢时
+                    if (rtpTimeStamp - lastTimeStamp > (RESETTIME / 2)) {
+                        console.warn('SEI翻转', rtpTimeStamp , lastTimeStamp);
+                        rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount - 1);
+                    } else {
+                        rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
+                    }
+                    //console.log('SEI', rtpTimeStamp, 'video', lastTimeStamp, lastTimeStamp+ RESETTIME * resetTimeCount - rtpTimeStamp)
+                    // //同一帧的SEI比视频发送快时
+                    // if(rtpTimeStamp - lastTimeStamp === 3600) {
+                    //     if(rtpTimeStamp === 0 ) {
+                    //         rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount + 1);
+                    //     } else {
+                    //         rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
+                    //     }
+                    // }
+                }
+
+                if(frameType === 'SEI') {
+                    //SEI被分片(nal === 28)时,分片包发送完后marker为1,不会和视频帧一起
+                    SEIBuffer = inputBuffer.subarray(4, inputLength);
+                    //console.log(SEIBuffer)
+                    inputBufferSub = new Uint8Array();
+                }
+                if(SEIBuffer) {
+                    let SEI = SEIParse(SEIBuffer);
+                    if (SEI) {
+                        SEIInfo.ivs = SEI;
+                        SEIInfo.timestamp = rtpTimeStamp;
+                        //console.log('GET SEI', rtpTimeStamp)
+                        //SEI信息
+                        decodedData.SEIInfo = SEIInfo;
+
+                        if (!(rtpTimeStamp - lastTime)) {
+                            console.log('上个SET包时间和本次相等');
+                            console.log('lastTime: ', lastTime, ' rtpTimeStamp: ', rtpTimeStamp, RtpHeader.timeStamp, rtpHeader[4], rtpHeader[5], rtpHeader[6], rtpHeader[7])
+                        }
+                        if ((rtpTimeStamp - lastTime) !== 3600) {
+                            //console.log('SEI 时间差:', (rtpTimeStamp - lastTime), rtpTimeStamp, lastTime)
+                        }
+                        lastTime = rtpTimeStamp;
+                        SEIInfo = {
+                            ivs: null,
+                            timestamp: 0,
+                        };
+                    }
+                    SEIBuffer = null;
+                }
+
+                if(decodeMode === 'canvas' && (frameType !== 'SEI')) {
+                    if (outputSize !== curSize) {
+                        outputSize = curSize;
+                        decoder.setOutputSize(curSize);
+                    }
+                    decodedData.frameData = decoder.decode(inputBufferSub);
+                    decodedData.decodeMode = 'canvas';
+                    decodedData.timeStamp = rtpTimeStamp;
+                    decodedData.width = width;
+                    decodedData.height = height;
+                    this.handleDecodedData(decodedData)
+                } else {
+                    if (!initalSegmentFlag) {
+                        decodedData.initSegmentData = null;
+                        decodedData.codecInfo = null;
+                    } else {
+                        initalSegmentFlag = false;
+                        const info = {
+                            id: 1,
+                            width: width,
+                            height: height,
+                            type: "video",
+                            profileIdc: SPSParser.getSpsValue("profile_idc"),
+                            profileCompatibility: 0,
+                            levelIdc: SPSParser.getSpsValue("level_idc"),
+                            sps: [spsSegment],
+                            pps: [ppsSegment],
+                            timescale: 1e3,
+                            fps: frameRate
+                        };
+                        decodedData.initSegmentData = info;
+                        //decodedData.codecInfo = SPSParser.getCodecInfo();
+                        //console.log(info.pps)
+                    }
+
+                    if (frameType === 'I') {
+//console.log('ppsSegment: ', ppsSegment)
+                        let h264parameterLength = spsSegment.length + ppsSegment.length + 8;
+                        inputSegBufferSub = inputBufferSub.subarray(h264parameterLength, inputBufferSub.length);
+                    } else {
+                        inputSegBufferSub = inputBufferSub.subarray(0, inputBufferSub.length);
+                    }
+
+                    let segSize = inputSegBufferSub.length - 4;
+                    //mp4 box头
+                    inputSegBufferSub[0] = (segSize & 0xFF000000) >>> 24;
+                    inputSegBufferSub[1] = (segSize & 0xFF0000) >>> 16;
+                    inputSegBufferSub[2] = (segSize & 0xFF00) >>> 8;
+                    inputSegBufferSub[3] = (segSize & 0xFF);
+
+                    decodedData.frameData = new Uint8Array(inputSegBufferSub);
+
+                    let sample = {
+                        duration: Math.round((1 / frameRate) * 1000),
+                        size: inputSegBufferSub.length,
+                        frame_time_stamp: null,
+                        frameDuration: null,
+                    };
+                    sample.frame_time_stamp = rtpTimeStamp; //Todo:暂时为null,通过帧率控制duration
+                    if (initalMediaFrameFlag) {
+                        sample.frameDuration = 0;
+                        initalMediaFrameFlag = false;
+                    } else {
+                        if(frameRate) {
+                            sample.frameDuration = Math.round(1000 / frameRate);
+                        }else {
+                            sample.frameDuration = (sample.frame_time_stamp - preSample.frame_time_stamp) / 90; // 时钟频率90000,timescale=1000
+                        }
+                    }
+                    preSample = sample;
+
+                    decodedData.mediaSample = sample;
+
+                    decodedData.timeStamp = rtpTimeStamp;
+
+                    this.handleDecodedData(decodedData);
+                }
+
+
+                inputLength = 0;
+                decodedData.SEIInfo = null;
+                inputSegBufferSub = null;
+                if(frameType !== 'SEI') {
+                    lastTimeStamp = RtpHeader.timeStamp;
+                }
+                frameType = '';
+            }
+            //console.log('xxxxxxxxxxxxxxxxxxxxxxxxx')
+        },
+
+        set rtpSessionCallback(func) {
+            this.handleDecodedData = func;
+        },
+
+        setFrameRate(fps) {
+            frameRate = fps;
+            //console.log('frameRate: ', frameRate)
+        },
+
+        setResolutionChangedCallback(callback) {
+            this.resolutionChangedCallback = callback;
+        }
+    }
+
+    return new constructor();
+
+    function setBuffer(buffer1, buffer2) {
+        let bufferTemp = buffer1;
+        if ((inputLength + buffer2.length) > buffer1.length) {
+            bufferTemp = new Uint8Array(buffer1.length + size1M);
+        }
+
+        bufferTemp.set(buffer2, inputLength);
+        inputLength += buffer2.length;
+        return bufferTemp;
+    }
+}
+
+
+
+
+/**
+ * 去除SPS中的Emulation字节
+ * @param data SPS源数据
+ * @returns {Array} 去除后Emulation字节后的SPS
+ */
+function removeH264or5EmulationBytes(data) {
+    let toSize = 0;
+    let i = 0;
+    let to = [];
+    let dataLength = data.length;
+    while (i < dataLength) {
+        if (i + 2 < dataLength && data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 3) {
+            to[toSize] = to[toSize + 1] = 0;
+            toSize += 2;
+            i += 3;
+        } else {
+            to[toSize] = data[i];
+            toSize += 1;
+            i += 1;
+        }
+    }
+    return to;
+}
+
+/**
+ * 解析SEI信息
+ * @param data
+ * @return {Array}
+ */
+function SEIParse(data) {
+    //console.log(data)
+    if((data[0] & 0x1f) !== 6) {
+        //非SEI
+        return null;
+    }
+
+    if(data[1] !== 0x55 || data[2] !== 0x4C ||data[3] !== 0x53 ||data[4] !== 0x40) {
+        //ULS@开头
+        console.warn('unknown SEI type');
+        return null;
+    }
+
+    let dataLength = data.length;
+    let type = (data[5] << 8) + data[6];
+    let checkSum = (data[7] << 8) + data[8];
+    // if(dataLength !== (checkSum + 1 + 4 + 4)) {
+    //     console.log('SEI check fail!');
+    //     return null;
+    // }
+    let result;
+    data = data.subarray(9);
+    switch(type) {
+        case 0:
+            result = parseRect(data);
+            break;
+        case 1:
+            //console.log(parseBody(data))
+            result = parseBody(data);
+            break;
+        default:
+            result = null;
+            break;
+    }
+    return result;
+}
+
+function parseRect(data) {
+    //console.log(data)
+    let dataLength = data.length;
+    let contents = [];
+    while(dataLength > 0) {
+        //console.log('dataLength: ', dataLength)
+        let x0 = ((data[4] & 0x7f) << 8) + data[5],
+            y0 = ((data[6] & 0x7f) << 8) + data[7],
+            width = ((data[8] & 0x7f) << 8) + data[9] - x0,
+            height = ((data[10] & 0x7f) << 8) + data[11] - y0;
+        let content = {
+            type: 'rect',
+            id: (data[2] << 8) + data[3],
+            rect: [x0, y0, width, height],
+            state: data[1] & 0x01,
+            quality: (data[1] & 0x02) >> 1,
+        };
+        Array.prototype.push.apply(contents, [content]);
+        data = data.subarray(12);
+        dataLength = data.length;
+    }
+
+    return contents;
+}
+
+function parseBody(data) {
+    let dataLength = data.length;
+    let contents = [];
+    while(dataLength > 0) {
+        //console.log('dataLength: ', dataLength)
+        let x0 = ((data[4] & 0x7f) << 8) + data[5],
+            y0 = ((data[6] & 0x7f) << 8) + data[7],
+            width = ((data[8] & 0x7f) << 8) + data[9] - x0,
+            height = ((data[10] & 0x7f) << 8) + data[11] - y0,
+            boxConfidence = ((data[12] & 0x7f) << 8) + data[13];
+        let points = [];
+
+        for(let i = 0; i < 17; i++) {
+            let point = {
+                x: ((data[16 + i * 8] & 0x7f) << 8) + data[17 + i * 8],
+                y: ((data[18 + i * 8] & 0x7f) << 8) + data[19 + i * 8],
+                confidence: ((data[20 + i * 8] & 0x7f) << 8) + data[21 + i * 8],
+                // x: Math.random() * 8191,
+                // y: Math.random() * 8191,
+                // confidence: 1,
+            };
+            points.push(point);
+        }
+        let content = {
+            type: 'coco-pose',
+            id: (data[2] << 8) + data[3],
+            handsUp: data[1] & 0x04,
+            boundingBox: [x0, y0, width, height],
+            boxConfidence,
+            points: parseBodyToTree(points),
+            state: data[1] & 0x01,
+        };
+        Array.prototype.push.apply(contents, [content]);
+        data = data.subarray(152);
+        dataLength = data.length;
+
+    }
+    return contents;
+}
+
+/**
+ * 将智能帧中的人体姿态点转化为树结构(双亲表示法)
+ * @param points
+ */
+function parseBodyToTree(points) {
+    let newPoints = [];
+    newPoints[0] = {...points[0], parent: -1, pointColor:'#FF0002', lineColor: '#FF0002'};
+    newPoints[1] = {...points[1], parent: 0, pointColor:'#FF0002', lineColor: '#FF0002'};
+    newPoints[2] = {...points[2], parent: 0, pointColor:'#FF0002', lineColor: '#FF0002'};
+    newPoints[3] = {...points[5], parent: 17, pointColor:'#D9E34F', lineColor: '#FF0002'};
+    newPoints[4] = {...points[6], parent: 17, pointColor:'#D9E34F', lineColor: '#FF0002'};
+    newPoints[5] = {...points[3], parent: 1, pointColor:'#00FF00', lineColor: '#FF0002'};
+    newPoints[6] = {...points[4], parent: 2, pointColor:'#FFAC00', lineColor: '#FF0002'};
+    newPoints[7] = {...points[7], parent: 3, pointColor:'#00FF45', lineColor: '#'};
+    newPoints[8] = {...points[11], parent: 17, pointColor:'#EEC446', lineColor: '#'};
+    newPoints[9] = {...points[8], parent: 4, pointColor:'#43D3AF', lineColor: '#'};
+    newPoints[10] = {...points[12], parent: 17, pointColor:'#7A93E8', lineColor: '#'};
+    newPoints[11] = {...points[9], parent: 7, pointColor:'#EFB842', lineColor: '#'};
+    newPoints[12] = {...points[13], parent: 8, pointColor:'#E56C00', lineColor: '#'};
+    newPoints[13] = {...points[10], parent: 9, pointColor:'#47CD43', lineColor: '#0096FF'};
+    newPoints[14] = {...points[14], parent: 10, pointColor:'#3868D2', lineColor: '#00FF51'};
+    newPoints[15] = {...points[15], parent: 12, pointColor:'#DF4D01', lineColor: '#5100FF'};
+    newPoints[16] = {...points[16], parent: 14, pointColor:'#1E48D4', lineColor: '#00FFA0'};
+    newPoints[17] = {x: (points[5].x + points[6].x) / 2,y: (points[5].y + points[6].y) / 2, parent: -1, pointColor:'#D9E34F', lineColor: '#00FFA0'};
+    return newPoints;
+}

+ 573 - 0
public/static/src/MP4Remux.js

@@ -0,0 +1,573 @@
+let _dtsBase;
+let _types = [];
+let datas = {};
+
+_types = {
+    avc1: [], avcC: [], btrt: [], dinf: [],
+    dref: [], esds: [], ftyp: [], hdlr: [],
+    mdat: [], mdhd: [], mdia: [], mfhd: [],
+    minf: [], moof: [], moov: [], mp4a: [],
+    mvex: [], mvhd: [], sdtp: [], stbl: [],
+    stco: [], stsc: [], stsd: [], stsz: [],
+    stts: [], tfdt: [], tfhd: [], traf: [],
+    trak: [], trun: [], trex: [], tkhd: [],
+    vmhd: [], smhd: []
+};
+
+class MP4Remux {
+    constructor() {
+
+    }
+
+    init() {
+        for (let name in _types) {
+            _types[name] = [
+                name.charCodeAt(0),
+                name.charCodeAt(1),
+                name.charCodeAt(2),
+                name.charCodeAt(3)
+            ];
+        }
+
+        _dtsBase = 0;
+
+        datas.FTYP = new Uint8Array([
+            0x69, 0x73, 0x6F, 0x6D, // major_brand: isom
+            0x0, 0x0, 0x0, 0x1,  // minor_version: 0x01
+            0x69, 0x73, 0x6F, 0x6D, // isom
+            0x61, 0x76, 0x63, 0x31  // avc1
+        ]);
+
+        datas.STSD_PREFIX = new Uint8Array([
+            0x00, 0x00, 0x00, 0x00, // version(0) + flags
+            0x00, 0x00, 0x00, 0x01  // entry_count
+        ]);
+
+        datas.STTS = new Uint8Array([
+            0x00, 0x00, 0x00, 0x00, // version(0) + flags
+            0x00, 0x00, 0x00, 0x00  // entry_count
+        ]);
+
+        datas.STSC = datas.STCO = datas.STTS;
+
+        datas.STSZ = new Uint8Array([
+            0x00, 0x00, 0x00, 0x00, // version(0) + flags
+            0x00, 0x00, 0x00, 0x00, // sample_size
+            0x00, 0x00, 0x00, 0x00  // sample_count
+        ]);
+
+        datas.HDLR_VIDEO = new Uint8Array([
+            0x00, 0x00, 0x00, 0x00, // version(0) + flags
+            0x00, 0x00, 0x00, 0x00, // pre_defined
+            0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
+            0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x56, 0x69, 0x64, 0x65,
+            0x6F, 0x48, 0x61, 0x6E,
+            0x64, 0x6C, 0x65, 0x72, 0x00 // name: VideoHandler
+        ]);
+
+        datas.HDLR_AUDIO = new Uint8Array([
+            0x00, 0x00, 0x00, 0x00, // version(0) + flags
+            0x00, 0x00, 0x00, 0x00, // pre_defined
+            0x73, 0x6F, 0x75, 0x6E, // handler_type: 'soun'
+            0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes
+            0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00,
+            0x53, 0x6F, 0x75, 0x6E,
+            0x64, 0x48, 0x61, 0x6E,
+            0x64, 0x6C, 0x65, 0x72, 0x00 // name: SoundHandler
+        ]);
+
+        datas.DREF = new Uint8Array([
+            0x00, 0x00, 0x00, 0x00, // version(0) + flags
+            0x00, 0x00, 0x00, 0x01, // entry_count
+            0x00, 0x00, 0x00, 0x0C, // entry_size
+            0x75, 0x72, 0x6C, 0x20, // type 'url '
+            0x00, 0x00, 0x00, 0x01  // version(0) + flags
+        ]);
+
+        // Sound media header
+        datas.SMHD = new Uint8Array([
+            0x00, 0x00, 0x00, 0x00, // version(0) + flags
+            0x00, 0x00, 0x00, 0x00  // balance(2) + reserved(2)
+        ]);
+
+        // video media header
+        datas.VMHD = new Uint8Array([
+            0x00, 0x00, 0x00, 0x01, // version(0) + flags
+            0x00, 0x00,             // graphicsmode: 2 bytes
+            0x00, 0x00, 0x00, 0x00, // opcolor: 3 * 2 bytes
+            0x00, 0x00
+        ]);
+    }
+
+    initSegment(meta) {
+        let ftyp = box(_types.ftyp, datas.FTYP);
+        let moov = Moov(meta);
+        let seg = new Uint8Array(ftyp.byteLength + moov.byteLength);
+        seg.set(ftyp, 0);
+        seg.set(moov, ftyp.byteLength);
+        return seg;
+    }
+
+    mediaSegment(sequenceNumber, track, data) {
+        let moof = Moof(sequenceNumber, track);
+        let frameData = mdat(data);
+        let seg = new Uint8Array(moof.byteLength + frameData.byteLength);
+        seg.set(moof, 0);
+        seg.set(frameData, moof.byteLength);
+        return seg
+    }
+}
+
+//组装initSegment
+
+function Moov(meta) {
+    let mvhd = Mvhd(meta.timescale, meta.duration);
+    let trak = Trak(meta);
+    let mvex = Mvex(meta);
+
+    return box(_types.moov, mvhd, trak, mvex);
+}
+
+//组装moov
+function Mvhd(timescale, duration) {
+    return box(_types.mvhd, new Uint8Array([
+        0x00, 0x00, 0x00, 0x00,    // version(0) + flags
+        0x00, 0x00, 0x00, 0x00,    // creation_time
+        0x00, 0x00, 0x00, 0x00,    // modification_time
+        (timescale >>> 24) & 0xFF, // timescale: 4 bytes
+        (timescale >>> 16) & 0xFF,
+        (timescale >>>  8) & 0xFF,
+        (timescale) & 0xFF,
+        (duration >>> 24) & 0xFF,  // duration: 4 bytes
+        (duration >>> 16) & 0xFF,
+        (duration >>>  8) & 0xFF,
+        (duration) & 0xFF,
+        0x00, 0x01, 0x00, 0x00,    // Preferred rate: 1.0
+        0x01, 0x00, 0x00, 0x00,    // PreferredVolume(1.0, 2bytes) + reserved(2bytes)
+        0x00, 0x00, 0x00, 0x00,    // reserved: 4 + 4 bytes
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00,    // ----begin composition matrix----
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x40, 0x00, 0x00, 0x00,    // ----end composition matrix----
+        0x00, 0x00, 0x00, 0x00,    // ----begin pre_defined 6 * 4 bytes----
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,    // ----end pre_defined 6 * 4 bytes----
+        0xFF, 0xFF, 0xFF, 0xFF     // next_track_ID
+    ]));
+}
+
+function Trak(meta) {
+    return box(_types.trak, Tkhd(meta), Mdia(meta));
+}
+
+function Mvex(meta) {
+    return box(_types.mvex, trex(meta));
+}
+
+//组装trak
+function Tkhd(meta) {
+    let trackId = meta.id;
+    let duration = meta.duration;
+    let width = meta.width;
+    let height = meta.height;
+
+    return box(_types.tkhd, new Uint8Array([
+        0x00, 0x00, 0x00, 0x07,   // version(0) + flags
+        0x00, 0x00, 0x00, 0x00,   // creation_time
+        0x00, 0x00, 0x00, 0x00,   // modification_time
+        (trackId >>> 24) & 0xFF,  // track_ID: 4 bytes
+        (trackId >>> 16) & 0xFF,
+        (trackId >>>  8) & 0xFF,
+        (trackId) & 0xFF,
+        0x00, 0x00, 0x00, 0x00,   // reserved: 4 bytes
+        (duration >>> 24) & 0xFF, // duration: 4 bytes
+        (duration >>> 16) & 0xFF,
+        (duration >>>  8) & 0xFF,
+        (duration) & 0xFF,
+        0x00, 0x00, 0x00, 0x00,   // reserved: 2 * 4 bytes
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,   // layer(2bytes) + alternate_group(2bytes)
+        0x00, 0x00, 0x00, 0x00,   // volume(2bytes) + reserved(2bytes)
+        0x00, 0x01, 0x00, 0x00,   // ----begin composition matrix----
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00,
+        0x40, 0x00, 0x00, 0x00,   // ----end composition matrix----
+        (width >>> 8) & 0xFF,     // width and height
+        (width) & 0xFF,
+        0x00, 0x00,
+        (height >>> 8) & 0xFF,
+        (height) & 0xFF,
+        0x00, 0x00
+    ]));
+}
+
+function Mdia(meta) {
+    return box(_types.mdia, mdhd(meta), hdlr(meta), minf(meta));
+}
+
+//组装mdia
+function mdhd(meta) {
+    let timescale = meta.timescale;
+    let duration = meta.duration;
+
+    return box(_types.mdhd, new Uint8Array([
+        0x00, 0x00, 0x00, 0x00,    // version(0) + flags
+        0x00, 0x00, 0x00, 0x00,    // creation_time
+        0x00, 0x00, 0x00, 0x00,    // modification_time
+        (timescale >>> 24) & 0xFF, // timescale: 4 bytes
+        (timescale >>> 16) & 0xFF,
+        (timescale >>>  8) & 0xFF,
+        (timescale) & 0xFF,
+        (duration >>> 24) & 0xFF,  // duration: 4 bytes
+        (duration >>> 16) & 0xFF,
+        (duration >>>  8) & 0xFF,
+        (duration) & 0xFF,
+        0x55, 0xC4,                // language: und (undetermined)
+        0x00, 0x00                 // pre_defined = 0
+    ]));
+}
+
+function hdlr(meta) {
+    let data = null;
+
+    if (meta.type === 'audio') {
+        data = datas.HDLR_AUDIO;
+    } else {
+        data = datas.HDLR_VIDEO;
+    }
+
+    return box(_types.hdlr, data);
+}
+
+function minf(meta) {
+    let xmhd = null;
+
+    if (meta.type === 'audio') {
+        xmhd = box(_types.smhd, datas.SMHD);
+    } else {
+        xmhd = box(_types.vmhd, datas.VMHD);
+    }
+
+    return box(_types.minf, xmhd, dinf(), stbl(meta));
+}
+
+//组装minf
+function dinf() {
+    return box(_types.dinf, box(_types.dref, datas.DREF));
+}
+
+function stbl(meta) {
+    let result = box(_types.stbl,   // type: stbl
+        stsd(meta),                   // Sample Description Table
+        box(_types.stts, datas.STTS), // Time-To-Sample
+        box(_types.stsc, datas.STSC), // Sample-To-Chunk
+        box(_types.stsz, datas.STSZ), // Sample size
+        box(_types.stco, datas.STCO)  // Chunk offset
+    );
+
+    return result;
+}
+
+//组装stbl
+function stsd(meta) {
+    if (meta.type === 'audio') {
+        return box(_types.stsd, datas.STSD_PREFIX, mp4a(meta));
+    } else {
+        return box(_types.stsd, datas.STSD_PREFIX, avc1(meta));
+    }
+}
+
+//组装stsd
+function mp4a(meta) {
+    let channelCount = meta.channelCount;
+    let sampleRate = meta.audioSampleRate;
+
+    let data = new Uint8Array([
+        0x00, 0x00, 0x00, 0x00,    // reserved(4)
+        0x00, 0x00, 0x00, 0x01,    // reserved(2) + data_reference_index(2)
+        0x00, 0x00, 0x00, 0x00,    // reserved: 2 * 4 bytes
+        0x00, 0x00, 0x00, 0x00,
+        0x00, channelCount,        // channelCount(2)
+        0x00, 0x10,                // sampleSize(2)
+        0x00, 0x00, 0x00, 0x00,    // reserved(4)
+        (sampleRate >>> 8) & 0xFF, // Audio sample rate
+        (sampleRate) & 0xFF,
+        0x00, 0x00
+    ]);
+
+    return box(_types.mp4a, data, esds(meta));
+}
+
+function avc1(meta) {
+    let width = meta.width;
+    let height = meta.height;
+
+    let sps = meta.sps || [], pps = meta.pps || [], sequenceParameterSets = [], pictureParameterSets = [];
+    for (let i = 0; i < sps.length; i++) {
+        sequenceParameterSets.push((sps[i].byteLength & 65280) >>> 8);
+        sequenceParameterSets.push(sps[i].byteLength & 255);
+        sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i]))
+    }
+    for (let i = 0; i < pps.length; i++) {
+        pictureParameterSets.push((pps[i].byteLength & 65280) >>> 8);
+        pictureParameterSets.push(pps[i].byteLength & 255);
+        pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]))
+    }
+
+    //Todo: 待测,如果视频有问题,修改这里
+    // let data = new Uint8Array([
+    //     0x00, 0x00, 0x00, 0x00, // reserved(4)
+    //     0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2)
+    //     0x00, 0x00, 0x00, 0x00, // pre_defined(2) + reserved(2)
+    //     0x00, 0x00, 0x00, 0x00, // pre_defined: 3 * 4 bytes
+    //     0x00, 0x00, 0x00, 0x00,
+    //     0x00, 0x00, 0x00, 0x00,
+    //     (width >>> 8) & 0xFF,   // width: 2 bytes
+    //     (width) & 0xFF,
+    //     (height >>> 8) & 0xFF,  // height: 2 bytes
+    //     (height) & 0xFF,
+    //     0x00, 0x48, 0x00, 0x00, // horizresolution: 4 bytes
+    //     0x00, 0x48, 0x00, 0x00, // vertresolution: 4 bytes
+    //     0x00, 0x00, 0x00, 0x00, // reserved: 4 bytes
+    //     0x00, 0x01,             // frame_count
+    //     0x0A,                   // strlen
+    //     0x78, 0x71, 0x71, 0x2F, // compressorname: 32 bytes
+    //     0x66, 0x6C, 0x76, 0x2E,
+    //     0x6A, 0x73, 0x00, 0x00,
+    //     0x00, 0x00, 0x00, 0x00,
+    //     0x00, 0x00, 0x00, 0x00,
+    //     0x00, 0x00, 0x00, 0x00,
+    //     0x00, 0x00, 0x00, 0x00,
+    //     0x00, 0x00, 0x00,
+    //     0x00, 0x18,             // depth
+    //     0xFF, 0xFF              // pre_defined = -1
+    // ]);
+
+    let data = new Uint8Array(
+        [0, 0, 0, 0,
+            0, 0, 0, 1,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            (65280 & width) >> 8,
+            255 & width,
+            (65280 & height) >> 8,
+            255 & height,
+            0, 72, 0, 0,
+            0, 72, 0, 0,
+            0, 0, 0, 0,
+            0, 1, 19, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 0,
+            0, 0, 0, 24, 17, 17]);
+
+    return box(_types.avc1, data, box(_types.avcC, new Uint8Array([1, meta.profileIdc, meta.profileCompatibility, meta.levelIdc, 255].concat([sps.length]).concat(sequenceParameterSets).concat([pps.length]).concat(pictureParameterSets))));
+}
+
+//组装mp4a
+function esds(meta) {
+    let config = meta.config;
+    let configSize = config.length;
+    let data = new Uint8Array([
+        0x00, 0x00, 0x00, 0x00, // version 0 + flags
+
+        0x03,                   // descriptor_type
+        0x17 + configSize,      // length3
+        0x00, 0x01,             // es_id
+        0x00,                   // stream_priority
+
+        0x04,                   // descriptor_type
+        0x0F + configSize,      // length
+        0x40,                   // codec: mpeg4_audio
+        0x15,                   // stream_type: Audio
+        0x00, 0x00, 0x00,       // buffer_size
+        0x00, 0x00, 0x00, 0x00, // maxBitrate
+        0x00, 0x00, 0x00, 0x00, // avgBitrate
+
+        0x05                    // descriptor_type
+    ].concat(
+        [configSize]
+    ).concat(
+        config
+    ).concat(
+        [0x06, 0x01, 0x02]      // GASpecificConfig
+    ));
+
+    return box(_types.esds, data);
+}
+
+//组装mvex
+function trex(meta) {
+    var trackId = meta.id;
+    var data = new Uint8Array([
+        0x00, 0x00, 0x00, 0x00,  // version(0) + flags
+        (trackId >>> 24) & 0xFF, // track_ID
+        (trackId >>> 16) & 0xFF,
+        (trackId >>>  8) & 0xFF,
+        (trackId) & 0xFF,
+        0x00, 0x00, 0x00, 0x01,  // default_sample_description_index
+        0x00, 0x00, 0x00, 0x00,  // default_sample_duration
+        0x00, 0x00, 0x00, 0x00,  // default_sample_size
+        0x00, 0x01, 0x00, 0x01   // default_sample_flags
+    ]);
+
+    return box(_types.trex, data);
+}
+
+//组装mediaSegment
+function Moof(sequenceNumber, track) {
+    return box(_types.moof, mfhd(sequenceNumber), traf(track));
+}
+
+function mdat(data) {
+    return box(_types.mdat, data);
+}
+
+//组装moof
+function mfhd(sequenceNumber) {
+    var data = new Uint8Array([
+        0x00, 0x00, 0x00, 0x00,
+        (sequenceNumber >>> 24) & 0xFF, // sequence_number: int32
+        (sequenceNumber >>> 16) & 0xFF,
+        (sequenceNumber >>>  8) & 0xFF,
+        (sequenceNumber) & 0xFF
+    ]);
+
+    return box(_types.mfhd, data);
+}
+
+function traf(track) {
+    //console.log(track)
+    var trackFragmentHeader = null, trackFragmentDecodeTime = null, trackFragmentRun = null, dataOffset = null;
+    trackFragmentHeader = box(_types.tfhd, new Uint8Array([0, 2, 0, 0, 0, 0, 0, 1]));
+    trackFragmentDecodeTime = box(_types.tfdt,
+        new Uint8Array([
+            0, 0, 0, 0,
+            track.baseMediaDecodeTime >>> 24 & 255,
+            track.baseMediaDecodeTime >>> 16 & 255,
+            track.baseMediaDecodeTime >>> 8 & 255,
+            track.baseMediaDecodeTime & 255
+        ]));
+    dataOffset = 16 + 16 + 8 + 16 + 8 + 8;
+    trackFragmentRun = trun(track, dataOffset);
+    return box(_types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun)
+}
+
+//组装traf
+function trun(track, offset) {
+    if (track.type === "audio") {
+        return audioTrun(track, offset)
+    }
+    return videoTrun(track, offset)
+}
+
+//组装trun
+function videoTrun(track, _offset) {
+    var bytes = null, samples = null, sample = null, i = 0;
+    var offset = _offset;
+    samples = track.samples || [];
+    if (samples[0].frameDuration === null) {
+        offset += 8 + 12 + 4 + 4 * samples.length;
+        bytes = trunHeader(samples, offset);
+        for (i = 0; i < samples.length; i++) {
+            sample = samples[i];
+            bytes = bytes.concat([(sample.size & 4278190080) >>> 24, (sample.size & 16711680) >>> 16, (sample.size & 65280) >>> 8, sample.size & 255])
+        }
+    } else {
+        offset += 8 + 12 + 4 + 4 * samples.length + 4 * samples.length;
+        bytes = trunHeader1(samples, offset);
+        for (i = 0; i < samples.length; i++) {
+            sample = samples[i];
+            bytes = bytes.concat([(sample.frameDuration & 4278190080) >>> 24, (sample.frameDuration & 16711680) >>> 16, (sample.frameDuration & 65280) >>> 8, sample.frameDuration & 255, (sample.size & 4278190080) >>> 24, (sample.size & 16711680) >>> 16, (sample.size & 65280) >>> 8, sample.size & 255])
+        }
+    }
+    return box(_types.trun, new Uint8Array(bytes))
+}
+
+function audioTrun(track, _offset) {
+    var bytes = null, samples = null, sample = null, i = 0;
+    var offset = _offset;
+    samples = track.samples || [];
+    offset += 8 + 12 + 8 * samples.length;
+    bytes = trunHeader(samples, offset);
+    for (i = 0; i < samples.length; i++) {
+        sample = samples[i];
+        bytes = bytes.concat([(sample.duration & 4278190080) >>> 24, (sample.duration & 16711680) >>> 16, (sample.duration & 65280) >>> 8, sample.duration & 255, (sample.size & 4278190080) >>> 24, (sample.size & 16711680) >>> 16, (sample.size & 65280) >>> 8, sample.size & 255])
+    }
+    return box(_types.trun, new Uint8Array(bytes))
+}
+
+//组装videoTurn
+function trunHeader(samples, offset) {
+    return [0, 0, 2, 5, (samples.length & 4278190080) >>> 24, (samples.length & 16711680) >>> 16, (samples.length & 65280) >>> 8, samples.length & 255, (offset & 4278190080) >>> 24, (offset & 16711680) >>> 16, (offset & 65280) >>> 8, offset & 255, 0, 0, 0, 0]
+}
+
+function trunHeader1(samples, offset) {
+    return [0, 0, 3, 5, (samples.length & 4278190080) >>> 24, (samples.length & 16711680) >>> 16, (samples.length & 65280) >>> 8, samples.length & 255, (offset & 4278190080) >>> 24, (offset & 16711680) >>> 16, (offset & 65280) >>> 8, offset & 255, 0, 0, 0, 0]
+}
+
+/**
+ *
+ * @param type
+ * @returns {Uint8Array}
+ */
+function box(type, ...items) {
+    let size = 8;
+    //Todo: 测试一下这里
+    //let arrs = Array.prototype.slice.call(arguments, 1);
+    let arrs = [];
+    arrs.push(...items);
+    for (let i = 0; i < arrs.length; i++) {
+        size += arrs[i].byteLength;
+    }
+
+    let data = new Uint8Array(size);
+    let pos = 0;
+
+    // set size
+    data[pos++] = size >>> 24 & 0xFF;
+    data[pos++] = size >>> 16 & 0xFF;
+    data[pos++] = size >>> 8 & 0xFF;
+    data[pos++] = size & 0xFF;
+
+    // set type
+    data.set(type, pos);
+    pos += 4;
+
+    // set data
+    for (let i = 0; i < arrs.length; i++) {
+        data.set(arrs[i], pos);
+        pos += arrs[i].byteLength;
+    }
+
+    return data;
+}
+
+// let mp4Remux = new MP4Remux();
+// mp4Remux.init();
+
+export default MP4Remux;

+ 413 - 0
public/static/src/MediaSource.js

@@ -0,0 +1,413 @@
+
+
+function VideoMediaSource(element) {
+    let videoElement = null;
+    let codecInfo = null;
+
+    let mediaSource = null;
+    let sourceBuffer = null;
+
+    let initSegmentData = null;
+
+    let ctrlDelayFlag = false;
+    let delay = 1;
+    const DELAY = 0.5;
+    let waitingCount = 0;
+    let time = 0;
+
+    let segmentWaitDecode = [];
+
+    let firstTimeStamp = null;
+    let isFirstTimeStamp = false;
+
+
+    let onDurationChangeCallback = null;
+    let onCanplayCallback = null;
+    let startPlay = false;
+
+    function constructor(element) {
+        videoElement = element;
+    }
+
+    constructor.prototype = {
+        init() {
+            videoElement.controls = false;
+            videoElement.autoplay = 'autoplay';
+            //videoElement.preload = "auto";
+            videoElement.muted = true;
+
+            addVideoEventListener(videoElement);
+
+            appendInitSegment();
+        },
+
+        setMediaSegment(mediaSegment) {
+            appendNextMediaSegment(mediaSegment)
+        },
+
+        setFirstTimeStamp(time) {
+            if(!isFirstTimeStamp) {
+                console.log('set firstTimeStamp:', time)
+                firstTimeStamp = time;
+                isFirstTimeStamp = true;
+            }
+        },
+
+        setDurationChangeCallBack(callback) {
+            onDurationChangeCallback = callback;
+        },
+
+        set CodecInfo(CodecInfo) {
+            codecInfo = CodecInfo;
+        },
+
+        get CodecInfo() {
+            return codecInfo;
+        },
+
+        set InitSegment(data) {
+            initSegmentData = data;
+        },
+
+        get InitSegment() {
+            return initSegmentData;
+        },
+
+        onCanplayCallback(callback) {
+            onCanplayCallback = callback;
+        },
+
+        close() {
+            videoElement.pause();
+            removeEventListener();
+            mediaSource.removeSourceBuffer(sourceBuffer);
+            mediaSource.endOfStream();
+            sourceBuffer = null;
+            mediaSource = null;
+            videoElement = null;
+        }
+    }
+
+    return new constructor(element);
+
+    function appendInitSegment() {
+        if(mediaSource == null || mediaSource.readyState === 'end') {
+            mediaSource = new MediaSource();
+            addMediaSourceEventListener(mediaSource);
+            videoElement.src = window.URL.createObjectURL(mediaSource);
+            //console.log('new MediaSource');
+            return;
+        }
+
+        //console.log('appendInitSegment start');
+        if(mediaSource.sourceBuffers.length === 0) {
+            mediaSource.duration = 0;
+            let codecs = 'video/mp4;codecs="avc1.' + codecInfo + '"';
+            if(!MediaSource.isTypeSupported(codecs)) {
+                //console.log('要播放视频格式 video/mp4;codecs="avc1.64002a", video/mp4;codecs="avc1.64002a",您还需要安装一个额外的微软组件,参见 https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows')
+                console.log('not support ' + codecs)
+                return;
+            }
+            sourceBuffer = mediaSource.addSourceBuffer(codecs);
+            addSourceBufferEventListener(sourceBuffer);
+        }
+
+        let initSegment = initSegmentData;
+        if(initSegment == null) {
+            mediaSource.endOfStream();
+            console.log('no initSegmentData');
+        }
+        //console.log(sourceBuffer)
+        sourceBuffer.appendBuffer(initSegment);
+        //console.log(sourceBuffer)
+        // saveAs(new File(initSegment, "test"));
+        //  Savesegments.set(initSegment, 0);
+        //  segmentsLength += initSegment.length;
+        //  segmentsNum --;
+        console.log('appendInitSegment end')
+        checkDelay();
+    }
+
+    function appendNextMediaSegment(mediaData) {
+
+
+        if(sourceBuffer == null) {
+            segmentWaitDecode.push(mediaData);
+            return;
+        }
+        //console.log(mediaSource.readyState, mediaSource.readyState,sourceBuffer.updating)
+        if(mediaSource.readyState === 'closed' || mediaSource.readyState === "ended") {
+            console.log('mediaSource closed or ended')
+            return;
+        }
+
+        if(onDurationChangeCallback) {
+            //90000为采样率,先写死
+            let rtpTimestamp = parseInt((videoElement.currentTime.toFixed(2) * 90000).toFixed(0)) + firstTimeStamp + 3600;//
+            //console.log('callback time: ', rtpTimestamp)
+            //console.log('sourceBuffer: ', sourceBuffer.timestampOffset)
+            onDurationChangeCallback(rtpTimestamp);
+        }
+
+        //console.count('一帧');
+
+        //try {
+        if(segmentWaitDecode.length) {
+            segmentWaitDecode.push(mediaData);
+            //console.log(segmentWaitDecode)
+        }else {
+            if(!sourceBuffer.updating) {
+                sourceBuffer.appendBuffer(mediaData);
+            } else {
+                segmentWaitDecode.push(mediaData);
+            }
+        }
+        //}catch (e){
+        //    console.log('appendNextMediaSegment Error')
+        //}
+
+        if(sourceBuffer && sourceBuffer.buffered && sourceBuffer.buffered.length &&  sourceBuffer.buffered.end(0) > DELAY) {
+            if(!startPlay) {
+                videoElement.play();
+                console.warn('playbakrate: ', videoElement.playbackRate)
+            }
+            startPlay = true;
+        } else {
+            if(!startPlay) {
+                videoElement.pause();
+            }
+        }
+        //console.log(sourceBuffer)
+    }
+
+    /**
+     * Video事件
+     * @param videoElement video对象
+     */
+    function addVideoEventListener(videoElement) {
+        videoElement.addEventListener('loadstart', onloadstart);
+
+        videoElement.addEventListener('waiting', onWaiting);
+
+        videoElement.addEventListener('durationchange', onDurationChange);
+
+        videoElement.addEventListener('timeupdate', timeupdate);
+
+        videoElement.addEventListener('canplay', oncanplay);
+
+        videoElement.addEventListener('canplaythrough', oncanplaythrough);
+
+        videoElement.addEventListener('error', onVideoError);
+
+        document.addEventListener('visibilitychange', onVisibilityChange);
+    }
+
+    function onVisibilityChange(e) {
+        if(document.visibilityState === 'visible') {
+            ctrlDelayFlag = true;
+            console.log(videoElement);
+            checkDelay();
+            videoElement.play();
+        } else {
+            ctrlDelayFlag = false;
+            videoElement.pause();
+        }
+        console.warn('visibilityState: ', document.visibilityState)
+    }
+
+    function onloadstart() {
+        console.log('loadstart');
+    }
+
+    function onDurationChange() {
+        //console.log('durationchange');
+        if (mediaSource === null) {
+            return;
+        }
+
+        if(sourceBuffer && sourceBuffer.buffered && sourceBuffer.buffered.length > 0) {
+            checkBuffer();
+        }
+        //console.log('currentTime:', videoElement.currentTime);
+        // if(onDurationChangeCallback) {
+        //     //90000为采样率,先写死
+        //     let rtpTimestamp = videoElement.currentTime * 90000 + firstTimeStamp ;
+        //     //console.log('callback time: ', rtpTimestamp)
+        //     onDurationChangeCallback(rtpTimestamp);
+        // }
+
+        //try {
+
+        //}catch(e) {
+        //    console.log('sourceBuffer has been moved')
+        //}
+
+    }
+
+    function checkDelay() {
+        if(sourceBuffer && sourceBuffer.buffered && sourceBuffer.buffered.length > 0) {
+            //console.log('end: ',sourceBuffer.buffered.end(0))
+            if(ctrlDelayFlag) {
+                let startTime = sourceBuffer.buffered.start(0);
+                let endTime = sourceBuffer.buffered.end(0);
+                let diffTime = (videoElement.currentTime === 0 ? endTime - startTime: endTime - videoElement.currentTime).toFixed(2);
+                if(diffTime >= delay + 0.1) {
+                    if(sourceBuffer.updating) {
+                        return;
+                    }
+                    let tempCurrntTime = endTime - delay;
+                    console.log('跳秒前', videoElement.currentTime)
+                    videoElement.currentTime = tempCurrntTime.toFixed(3);
+                    console.log('跳秒后', videoElement.currentTime, sourceBuffer.buffered.end(0), videoElement.duration)
+                    //ctrlDelayFlag = false;
+                } else if((diffTime < DELAY + 0.1 ) && diffTime >= DELAY) {
+                    //console.log('playbackRate:', 1, diffTime)
+                    videoElement.playbackRate = 1;
+                }
+                else if(diffTime < DELAY) {
+                    videoElement.playbackRate = 0.9;
+                }else {
+                    //console.log('playbackRate:', 1.1, diffTime)
+                    videoElement.playbackRate = 1.1;
+                }
+            }
+        }
+        window.requestAnimationFrame(checkDelay);
+    }
+
+    function timeupdate() {
+        // console.log('******timeupdate******');
+        // console.log(videoElement.currentTime);
+        // console.log('******timeupdate end******')
+    }
+
+    function oncanplay() {
+        // if(isFirstTimeStamp && (firstTimeStamp == null)) {
+        //     //firstTimeStamp =
+        //     isFirstTimeStamp = false;
+        // }
+
+        onCanplayCallback && onCanplayCallback(videoElement);
+        console.log('canplay');
+    }
+
+    function oncanplaythrough() {
+        if(document.visibilityState === 'visible' && startPlay){ctrlDelayFlag = true};
+        console.log('canplaythrough');
+    }
+
+    function onVideoError() {
+        console.error('error');
+        //console.log(e)
+        console.error(videoElement.currentTime);
+        console.error("Error " + videoElement.error.code + "; details: " + videoElement.error.message);
+    }
+
+
+    /**
+     * MediaSource事件
+     * @param mediaSource
+     */
+    function addMediaSourceEventListener(mediaSource) {
+        mediaSource.addEventListener('sourceopen', onSourceOpen);
+
+        mediaSource.addEventListener('error', onMediaSourceError);
+    }
+
+    function onSourceOpen() {
+        console.log('OnsourceOpen');
+        appendInitSegment(); //此处重新调用一次,是为了建立sourceBuffer
+    }
+
+    function onMediaSourceError() {
+        console.log('mediaSource error');
+        console.log(videoElement.currentTime)
+    }
+
+    /**
+     * sourceBuffer事件
+     */
+    function addSourceBufferEventListener(sourceBuffer) {
+        sourceBuffer.addEventListener('error', onSourceBufferError);
+
+        sourceBuffer.addEventListener('update', onUpdate);
+    }
+
+    function onSourceBufferError() {
+        console.log('sourceBuffer Error');
+        console.log(videoElement.currentTime)
+    }
+
+    function onUpdate() {
+        //console.log('sourceBuffer update');
+        if(segmentWaitDecode.length > 0) {
+            if(!sourceBuffer.updating) {
+                sourceBuffer.appendBuffer(segmentWaitDecode[0]);
+
+                //console.log('segmentWaitDecode:  ' + segmentWaitDecode.length)
+                segmentWaitDecode.shift();
+            }
+        }
+        //console.log(e)
+    }
+
+    function checkBuffer() {
+        let minute = 20;
+        let bufferTime = 10;
+        let startTime = sourceBuffer.buffered.start(0);
+        let endTime = sourceBuffer.buffered.end(0);
+        //console.log(endTime- videoElement.currentTime)
+        if (!sourceBuffer.updating && (endTime - startTime > minute)) {
+            sourceBuffer.remove(startTime, endTime - bufferTime);
+            console.log('remove buffer: ', startTime, ' - ', (endTime - bufferTime))
+        }else if(sourceBuffer.updating && (endTime - startTime > minute)) {
+            console.log('clear buffer failed!')
+        }
+    }
+
+    function onWaiting() {
+        console.log('waiting....');
+        ctrlDelayFlag = false;
+        // if(delay < 0.7) {
+        //     if(waitingCount === 0) {
+        //         time = Date.now();
+        //         waitingCount++;
+        //     }else {
+        //         if((Date.now() - time) <= 5000) {
+        //             waitingCount ++;
+        //             if(waitingCount >= 5) {
+        //                 delay += 0.1;
+        //                 console.log('delay: ', delay);
+        //                 time = Date.now();
+        //                 waitingCount = 0;
+        //             }
+        //         } else {
+        //             waitingCount = 1;
+        //             time = Date.now();
+        //         }
+        //     }
+        // }
+    }
+
+    function removeEventListener() {
+        document.removeEventListener('visibilitychange', onVisibilityChange);
+        videoElement.removeEventListener('loadstart', onloadstart);
+        videoElement.removeEventListener('waiting', onWaiting);
+        videoElement.removeEventListener('durationchange', onDurationChange);
+        videoElement.removeEventListener('timeupdate', timeupdate);
+        videoElement.removeEventListener('canplay', oncanplay);
+        videoElement.removeEventListener('canplaythrough', oncanplaythrough);
+        videoElement.removeEventListener('error', onVideoError);
+
+        mediaSource.removeEventListener('sourceopen', onSourceOpen);
+        mediaSource.removeEventListener('error', onMediaSourceError);
+
+        sourceBuffer.removeEventListener('error', onSourceBufferError);
+        sourceBuffer.removeEventListener('update', onUpdate);
+    }
+
+}
+
+
+
+export default VideoMediaSource;

+ 262 - 0
public/static/src/ROIDrawer.js

@@ -0,0 +1,262 @@
+import Drawer from './drawer.js';
+
+class ROIDrawer extends Drawer {
+    constructor(canvas) {
+        super(canvas);
+        this.currentState = 'end';
+        this.points = [];
+        this.polygons = [];
+        this.MAX_POLYGON = 1;
+        this.onDrawROIFinishedCallback = null;
+    }
+
+    _init() {
+        this.context.lineWidth = 2;
+        this.context.font = 'bold 20px Arial';
+
+        this.canvas.addEventListener('click', (e) => {
+            switch (this.currentState) {
+                case 'begin':
+                    this.points.push(getMousePos(this.canvas, e));
+                    this.currentState = 'firstPoint';
+                    break;
+                case 'firstPoint': // 防止初始点点两次
+                    break;
+                case 'move':
+                    this.currentState = 'points';
+                    break;
+                case 'points':
+                    let currentPoint = getMousePos(this.canvas, e);
+                    if ((currentPoint.x === this.points[this.points.length - 1].x) && (currentPoint.y === this.points[this.points.length - 1].y)) {
+                        return;
+                    }
+                    this.points.push(currentPoint);
+                    if (checkPolygon(this.points)) {
+                        this.points.pop();
+                    }
+                    //console.log(this.points);
+                    break;
+                case 'end':
+                    //console.log(this.points)
+                    break;
+                default:
+                    console.log('unknown state: ', this.currentState);
+                    break;
+            }
+            // this._drawPolygons(this.points, true);
+        });
+        this.canvas.addEventListener('contextmenu', (e) => {
+            e.preventDefault();
+            if(this.currentState === 'end') { //防止点击右键启动绘制
+                return ;
+            }
+            if (this.points.length <= 2) {
+                this._resetPoints();
+            } else {
+                if ((this.points[0].x !== this.points[this.points.length - 1].x) || (this.points[0].y !== this.points[this.points.length - 1].y)) {
+                    this.points.push(this.points[0]);
+                }
+                if (checkPolygon(this.points)) {
+                    this.points.pop();
+                } else {
+                    this.currentState = 'end';
+                    this.polygons.push([...this.points]);
+                    this.onDrawROIFinishedCallback && this.onDrawROIFinishedCallback();
+                    if(this.polygons.length < this.MAX_POLYGON) {
+                        this._resetPoints();
+                    } else {
+                        this._drawPolygons([]);
+                    }
+                }
+            }
+        });
+        this.canvas.addEventListener('mousemove', (event) => {
+            let pos = getMousePos(this.canvas, event);
+            //console.log('mousemove');
+            if (event.button === 0) {
+                if (this.currentState === 'firstPoint') {
+                    this.currentState = 'move';
+                    this.points.push(pos);
+                } else if (this.currentState === 'move') {
+                    this.points.pop();
+                    this.points.push(pos);
+                }
+                if (this.currentState !== 'end') {
+                    this.points.push(pos);
+                    this._drawPolygons(this.points);
+                    this.points.pop();
+                } else {
+                    //this._drawPolygons(this.points);
+                }
+                if(this.currentState === 'begin') {
+                    this._drawText('区域' + (this.polygons.length + 1), pos.x, pos.y)
+                }
+            }
+        });
+    }
+
+    getROIData() {
+        if(this.currentState !== 'end') { //非完成状态
+            return null;
+        }
+        let polygons = [];
+        this.polygons.map((points, k) =>{
+            let data = points.slice(0, points.length - 1);
+            data.map((point, k) => {
+                data[k] = this._to8191Coordinate(point, this.canvas);
+            });
+            polygons.push([...data]);
+        });
+
+        return polygons;
+    }
+
+    setROI(polygons) {
+        let data = [];
+        polygons.map((points, k) => {
+            points.map((point, k) => {
+                let result = this._toRealCoordinate(point.x, point.y);
+                data[k] = {x: result[0], y: result[1]};
+            });
+            if(data.length) {
+                data[data.length] = data[0];
+            }
+            this.polygons.push([...data]);
+            data = [];
+        });
+        this.currentState = 'end';
+        this.points = [];
+        this._drawPolygons(this.points);
+    }
+
+    redrawROI() {
+        this._drawPolygons(this.points);
+    }
+
+    reset() {
+        this.currentState = 'begin';
+        this.polygons = [];
+        this.points.length = 0;
+        this._drawPolygons(this.points);
+    }
+
+    setROIFinishedCallback(callback) {
+        this.onDrawROIFinishedCallback = callback;
+    }
+
+    setPolygonNum(num) {
+        this.MAX_POLYGON = num;
+    }
+
+    terminate() {
+        this.clearCanvas();
+        this.canvas.width = 0;
+        this.canvas.height = 0;
+        this.currentState = 'end';
+        this.points.length = 0;
+    }
+
+    _drawPolygons(points) {
+        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+        this.polygons.map((points, k)=> {
+            this._drawPolygon(points);
+        });
+        this._drawPolygon(points);
+    }
+
+    _drawPolygon(points) {
+        if (!points.length) {
+            return;
+        }
+        const end = this.currentState === 'end';
+        //console.log(points)
+        this.context.strokeStyle = end ? '#0000ff' : "#ffff00";
+
+        this.context.beginPath();
+        this.context.moveTo(points[0].x, points[0].y);
+        for (let i = 1; i < points.length; i++) {
+            this.context.lineTo(points[i].x, points[i].y);
+        }
+        if (end) {
+            // draw is done, fill polygon with a color
+            this.context.fillStyle = "rgba(255, 0, 0, 0.2)";
+            this.context.fill();
+        } else {
+            this.context.stroke();
+        }
+        this.context.closePath();
+
+        this.polygons.map((points, k)=> {
+            this._drawText('区域' + (k + 1), points[0].x - 20, points[0].y - 10);
+        });
+    }
+
+    _resetPoints() {
+        this.currentState = 'begin';
+        this.points.length = 0;
+        this._drawPolygons(this.points);
+    }
+
+    _drawText(text, x, y) {
+        this.context.beginPath();
+        this.context.fillStyle = "rgba(255, 255, 0, 1)";
+        this.context.fillText(text, x, y);
+        this.context.closePath();
+    }
+
+}
+
+function getMousePos(canvas, event) {
+    var rect = canvas.getBoundingClientRect();
+    var x = event.clientX - rect.left * (canvas.width / rect.width);
+    var y = event.clientY - rect.top * (canvas.height / rect.height);
+    //console.log("x:"+x+",y:"+y);
+    return {x: x, y: y};
+}
+
+function checkPolygon(points) {
+    for (let i = 0, length = points.length - 1; i < length; i++) {
+        for (let j = i + 1, len = points.length - 1; j < len; j++) {
+            let result = segmentsIntr(points[i], points[i + 1], points[j], points[j + 1]);
+            if (result) {
+                console.log('intersect:');
+                console.log(result)
+                return result;
+            }
+        }
+    }
+    return false;
+}
+
+function segmentsIntr(a, b, c, d) {
+
+    // 三角形abc 面积的2倍
+    var area_abc = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);
+
+    // 三角形abd 面积的2倍
+    var area_abd = (a.x - d.x) * (b.y - d.y) - (a.y - d.y) * (b.x - d.x);
+
+    // 面积符号相同则两点在线段同侧,不相交 (对点在线段上的情况,本例当作不相交处理);
+    if (area_abc * area_abd >= 0) {
+        return false;
+    }
+
+    // 三角形cda 面积的2倍
+    var area_cda = (c.x - a.x) * (d.y - a.y) - (c.y - a.y) * (d.x - a.x);
+    // 三角形cdb 面积的2倍
+    // 注意: 这里有一个小优化.不需要再用公式计算面积,而是通过已知的三个面积加减得出.
+    var area_cdb = area_cda + area_abc - area_abd;
+    if (area_cda * area_cdb >= 0) {
+        return false;
+    }
+
+    //计算交点坐标
+    var t = area_cda / (area_abd - area_abc);
+    var dx = t * (b.x - a.x),
+        dy = t * (b.y - a.y);
+    return {x: a.x + dx, y: a.y + dy};
+
+}
+
+
+export default ROIDrawer;

File diff suppressed because it is too large
+ 165 - 0
public/static/src/SuperRender_20.js


+ 250 - 0
public/static/src/YUVPlayer.js

@@ -0,0 +1,250 @@
+import {BufferNode, BufferQueue, ImagePool} from './BufferNode.js';
+import YUVWebGLCanvas from './WebGLCanvas.js';
+
+let Uniformity = true;
+let channelId = 0;
+let canvas = null;
+let drawer = null;
+let preWidth = null;
+let preHeight = null;
+let drawingStrategy = null;
+let frameInterval = null;
+let prevCodecType = null;
+let resizeCallback = null;
+let startTimestamp = 0;
+let frameTimestamp = null;
+let preTimestamp = 0;
+let progressTime = 0;
+let curTime = 0;
+let imagePool = new ImagePool;
+let bufferNode = null;
+let fileName = "";
+let captureFlag = false;
+let isRendering = false;
+let defaultInterval = 16.7;
+let defaultMaxDelay = 20;
+let milisecond = 1e3;
+let maxDelay = null;
+
+let videoBufferQueue = null;
+
+let canvasElem = null;
+
+class VideoBufferNode extends BufferNode{
+    constructor(data, width, height, codecType, frameType, timeStamp){
+        super();
+        //console.log(data)
+        this.buffer = data;
+        this.width = width;
+        this.height = height;
+        this.codecType = codecType;
+        this.frameType = frameType;
+        this.timeStamp = timeStamp
+    }
+}
+
+class VideoBufferQueue extends BufferQueue{
+
+    constructor() {
+        super();
+        this.MAX_LENGTH = 30;
+    }
+
+    enqueue(data, width, height, codecType, frameType, timeStamp) {
+        //console.log(arguments)
+        if(this.size >= this.MAX_LENGTH) {
+            this.clear();
+        }
+        let node = new VideoBufferNode(data, width, height, codecType, frameType, timeStamp);
+        if (this.first === null) {
+            this.first = node
+        } else {
+            let tempNode = this.first;
+            while (tempNode.next !== null) {
+                tempNode = tempNode.next
+            }
+            tempNode.next = node
+        }
+        this.size += 1;
+        return node
+    }
+}
+
+class Size {
+    constructor(width, height) {
+        this.w = width;
+        this.h = height;
+    }
+
+    toString() {
+        return "(" + this.w + ", " + this.h + ")"
+    }
+
+    getHalfSize() {
+        return new Size(this.w >>> 1, this.h >>> 1)
+    }
+
+    length() {
+        return this.w * this.h
+    }
+}
+
+class YUVPlayer {
+    constructor(canvas){
+        drawingStrategy = "YUVWebGL";
+        prevCodecType = null;
+        videoBufferQueue = new VideoBufferQueue;
+        frameInterval = defaultInterval;
+        isRendering = false
+        canvasElem = canvas;
+    }
+
+    draw(data, width, height, codecType, frameType, timeStamp) {
+        if (videoBufferQueue !== null) {
+            videoBufferQueue.enqueue(data, width, height, codecType, frameType, timeStamp)
+        }
+    }
+
+    startRendering() {
+        if (startTimestamp === 0 && Uniformity !== false) {
+            isRendering = true;
+            window.requestAnimationFrame(drawingInTime)
+        }
+    }
+
+    stopRendering() {
+        isRendering = false;
+        startTimestamp = 0
+    }
+
+    setFPS(fps) {
+        if (typeof fps === "undefined") {
+            frameInterval = defaultInterval;
+            maxDelay = defaultMaxDelay
+        } else if (fps === 0) {
+            frameInterval = defaultInterval;
+            maxDelay = defaultMaxDelay
+        } else {
+            frameInterval = milisecond / fps;
+            maxDelay = fps * 1
+        }
+    }
+
+    terminate() {
+        startTimestamp = 0;
+        frameTimestamp = null;
+        if (videoBufferQueue !== null) {
+            videoBufferQueue.clear();
+            videoBufferQueue = null
+        }
+        drawer = null
+    }
+}
+
+function resize(width, height) {
+    let size = new Size(width, height);
+    canvas = canvasElem;
+    switch (drawingStrategy) {
+        case"YUVWebGL":
+            drawer = new YUVWebGLCanvas(canvas, size);
+            break;
+        default:
+            break
+    }
+}
+
+function drawFrame(stepValue) {
+
+    bufferNode = videoBufferQueue.dequeue();
+    if (bufferNode !== null && bufferNode.buffer !== null && (bufferNode.codecType === "mjpeg" || bufferNode.buffer.length > 0)) {
+        if (typeof preWidth === "undefined" || typeof preHeight === "undefined" || preWidth !== bufferNode.width || preHeight !== bufferNode.height || prevCodecType !== bufferNode.codecType) {
+            drawingStrategy = bufferNode.codecType === "h264" || bufferNode.codecType === "h265" ? "YUVWebGL" : "ImageWebGL";
+            resize(bufferNode.width, bufferNode.height);
+            preWidth = bufferNode.width;
+            preHeight = bufferNode.height;
+            prevCodecType = bufferNode.codecType;
+            if (typeof resizeCallback !== "undefined" && resizeCallback !== null) {
+                resizeCallback("resize")
+            }
+        }
+        frameTimestamp = bufferNode.timeStamp;
+        //workerManager.timeStamp(frameTimestamp);
+        if (typeof drawer !== "undefined") {
+            drawer.drawCanvas(bufferNode.buffer);
+            canvas.updatedCanvas = true;
+            // if (captureFlag) {
+            //     captureFlag = false;
+            //     doCapture(canvas.toDataURL(), fileName)
+            // }
+            if (bufferNode.codecType === "mjpeg") {
+                imagePool.free(bufferNode.buffer)
+            } else {
+                delete bufferNode.buffer;
+                bufferNode.buffer = null
+            }
+            bufferNode.previous = null;
+            bufferNode.next = null;
+            bufferNode = null;
+            return true
+        } else {
+            console.log("drawer is undefined in StreamDrawer!")
+        }
+    } else {
+    }
+    return false
+}
+
+function drawingInTime(timestamp) {
+    let stampCheckTime = 200;
+    if (isRendering === true) {
+        if (startTimestamp === 0 || timestamp - startTimestamp < stampCheckTime) {
+            if (startTimestamp === 0) {
+                startTimestamp = timestamp
+            }
+            if (videoBufferQueue !== null) {
+                window.requestAnimationFrame(drawingInTime)
+            }
+            return
+        }
+        curTime += timestamp - preTimestamp;
+        if (curTime > progressTime) {
+            if(drawFrame()) {
+                progressTime += frameInterval
+            }
+        }
+        if (curTime > milisecond) {
+            progressTime = 0;
+            curTime = 0
+        }
+        preTimestamp = timestamp;
+        window.requestAnimationFrame(drawingInTime)
+    }
+}
+
+function drawImage(data) {
+    if (typeof preWidth === "undefined" || typeof preHeight === "undefined" || preWidth !== data.width || preHeight !== data.height) {
+        drawingStrategy = "ImageWebGL";
+        resize(data.width, data.height);
+        preWidth = data.width;
+        preHeight = data.height;
+        resizeCallback("resize")
+    }
+    frameTimestamp = data.time;
+    if (frameTimestamp !== null) {
+        //workerManager.timeStamp(frameTimestamp)
+    }
+    if (typeof drawer !== "undefined") {
+        drawer.drawCanvas(data);
+        if (captureFlag) {
+            captureFlag = false;
+            //doCapture(canvas.toDataURL(), fileName)
+        }
+        imagePool.free(data);
+        return true
+    } else {
+        console.log("drawer is undefined in StreamDrawer!")
+    }
+    return false
+}
+
+export default YUVPlayer;

+ 98 - 0
public/static/src/drawer.js

@@ -0,0 +1,98 @@
+class Drawer {
+    constructor(canvas) {
+        this.canvas = canvas;
+        this.context = canvas.getContext('2d');
+        this._init();
+    }
+
+    _init() {
+
+    }
+
+    cover(video) {
+        //console.log('cover')
+        let offsetLeft = 0, //canvas和video同级时
+            offsetTop = 0,
+            //offsetLeft = getOffsetRect(video).left, //canvas为body的子元素时,根据DOM文档定位
+            //offsetTop = getOffsetRect(video).top,
+            videoHeight = video.videoHeight,
+            videoWidth = video.videoWidth,
+            width = video.getBoundingClientRect().width || videoWidth,
+            height = video.getBoundingClientRect().height || videoHeight;
+        this.canvas.style.position = 'absolute';
+
+        //this.canvas.style.top = offsetTop +'px';
+
+        //this.canvas.style.height = height +'px';
+
+        let tempHeight = width * videoHeight / videoWidth;
+        if (tempHeight > height) { // 如果缩放后的高度大于标签宽度,则按照height缩放width
+            this.canvas.height = height;
+            this.canvas.width = videoWidth / videoHeight * height;
+            this.canvas.style.height = height + 'px';
+            this.canvas.style.width = videoWidth / videoHeight * height + 'px';
+            this.canvas.style.top = offsetTop + 'px';
+            this.canvas.style.left = offsetLeft + (width - videoWidth / videoHeight * height) / 2 + 'px';
+        } else {
+            this.canvas.width = width;
+            this.canvas.height = width * videoHeight / videoWidth;
+            this.canvas.style.width = width +'px';
+            this.canvas.style.height = width * videoHeight / videoWidth +'px';
+            this.canvas.style.left = offsetLeft + 'px';
+            this.canvas.style.top = offsetTop + (height - width * videoHeight / videoWidth) / 2 + 'px';
+        }
+    }
+
+    clearCanvas() {
+        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+    }
+
+    terminate() {
+        this.clearCanvas();
+        this.canvas.width = 0;
+        this.canvas.height = 0;
+    }
+
+    /**
+     * 8191坐标系转真实坐标
+     * @param x 8191坐标系 x坐标
+     * @param y 8191坐标系 y坐标
+     * @returns {number[]} 数组
+     * @private
+     */
+    _toRealCoordinate(x, y) {
+        return [parseInt(x * this.canvas.width / 8191), parseInt(y * this.canvas.height / 8191)];
+    }
+
+    /**
+     * 真实坐标系转8191坐标系
+     * @param point {x: x, y: y}
+     * @param canvas 坐标系所在的画布
+     * return {x: x, y: y}
+     */
+     _to8191Coordinate(point, canvas) {
+        return {x: parseInt(point.x * 8191 / canvas.width), y: parseInt(point.y * 8191 / canvas.height)}
+    }
+}
+
+
+/**
+ * 获取元素相对于dom文档的坐标
+ * @param elem
+ * @returns {{top: number, left: number}}
+ */
+function getOffsetRect(elem) {
+    let box = elem.getBoundingClientRect();
+    let body = document.body;
+    let docElem = document.documentElement;
+    let scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
+    let scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
+    let clientTop = docElem.clientTop || body.clientTop || 0;
+    let clientLeft = docElem.clientLeft || body.clientLeft || 0;
+    let top = box.top + scrollTop - clientTop;
+    let left = box.left + scrollLeft - clientLeft;
+    return {top: Math.round(top), left: Math.round(left)}
+}
+
+
+export default Drawer;

+ 141 - 0
public/static/src/ivsDrawer.js

@@ -0,0 +1,141 @@
+import Drawer from './drawer.js';
+class IvsDrawer extends Drawer {
+    constructor(canvas) {
+        super(canvas);
+        this.confidence = 200; //关键点最低置信度
+        this.displayNum = 7; //大于等于该值时才绘制姿态
+    }
+
+    _init() {
+        this.context.textAlign = 'left';
+        this.context.textBaseline = 'bottom';
+    }
+
+    draw(data, time) {
+        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+        let rect = null;
+        data.map((content, k) => {
+            switch (content.type) {
+                case 'rect':
+                    this.context.beginPath();
+                    this.context.strokeStyle = '#00ff00';
+                    this.context.fillStyle = '#00ff00';
+                    this.context.lineWidth = 1;//线条的宽度
+                    this.context.font = 'bold 20px Arial';
+                    if (!content.quality) {
+                        this.context.strokeStyle = '#ff0000';
+                    }
+
+                    rect = this._toRealCoordinate(content.rect[0], content.rect[1]);
+                    rect.push.apply(rect, this._toRealCoordinate(content.rect[2], content.rect[3]));
+                    this._drawRect(rect);
+
+                    //this.context.font = 'bold 20px Arial';
+                    //this.context.fillStyle = '#00ff00';
+                    // this._drawText(content.id, rect[0], rect[1]);
+                    // this._drawText(content.id, rect[0], rect[1]);
+                    // if (content.text) {
+                    //     this._drawText(content.text, rect[0], rect[1] - 20);
+                    // }
+                    if (content.text !== undefined) {
+                        this._drawText(content.text, rect[0], rect[1] - 5);
+                    }
+                    //console.log('绘制 ', time)
+                    this.context.stroke();
+                    this.context.closePath();
+                    break;
+                case 'text':
+                    break;
+                case 'coco-pose':
+                    let {points, boundingBox, handsUp, boxConfidence} = content,
+                        length = points.length,
+                        undrawPoints = [],
+                        bodyLineWidth = boundingBox[2] > 2048 ? 6 : ( boundingBox[2] > 512 ? 4 : 2);
+                    //过滤掉置信点不够的情况
+                    let confidencePoint = points.filter((point) => {
+                        return point.confidence > this.confidence;
+                    });
+                    if (confidencePoint.length >= this.displayNum) {
+                        this.context.lineWidth = bodyLineWidth;
+                        //绘制实线
+                        for (let i = 0; i < length; i++) {
+                            if (undrawPoints.includes(i) || points[i].parent === -1) {
+                                continue;
+                            }
+                            let point = this._toRealCoordinate(points[i].x, points[i].y);
+                            let parentNode = points[points[i].parent];
+                            let parentPoint = this._toRealCoordinate(parentNode.x, parentNode.y);
+
+                            this.context.strokeStyle = handsUp ? '#ff0000' : points[i].pointColor;
+                            //confidence高于阈值时画实线,否则透明度降低
+                            if ((points[i].confidence <= this.confidence) || (parentNode.confidence <= this.confidence)) {
+                                this.context.globalAlpha = 0.3;
+                            }
+                            this.context.beginPath();
+                            this.context.moveTo(point[0], point[1]);
+                            this.context.lineTo(parentPoint[0], parentPoint[1]);
+                            this.context.stroke();
+                            this.context.globalAlpha = 1;
+                            this.context.closePath();
+                        }
+                        //绘制圆
+                        for (let i = 0; i < length; i++) {
+                            if (undrawPoints.includes(i)) {
+                                continue;
+                            }
+                            this.context.fillStyle = handsUp ? '#ffff00' : points[i].pointColor;
+                            //if ((points[i].parent !== -1 && points[i].confidence > this.confidence && points[points[i].parent].confidence > this.confidence)
+                            //    || (points[i].parent === -1 && points[i].confidence > this.confidence)) {
+                            let point = this._toRealCoordinate(points[i].x, points[i].y);
+                            this.context.beginPath();
+                            this._drawArc(point[0], point[1], bodyLineWidth);
+                            this.context.fill();
+                            this.context.closePath();
+                            //}
+                        }
+                    } else {
+                        //console.log('only', confidencePoint.length, ' points');
+                    }
+                    //绘制人体框
+                    if (boxConfidence >= 0) {
+                        this.context.beginPath();
+                        this.context.lineWidth = 1;//线条的宽度
+                        this.context.strokeStyle = '#ff0000';
+                        this.context.fillStyle = '#ffff00';
+                        this.context.font = 'bold 20px Arial';
+                        rect = this._toRealCoordinate(boundingBox[0], boundingBox[1]);
+                        rect.push.apply(rect, this._toRealCoordinate(boundingBox[2], boundingBox[3]));
+                        this._drawRect(rect);
+                        this._drawText(content.id, rect[0], rect[1] - 10);
+                        this.context.stroke();
+                        this.context.closePath();
+                    }
+                    break;
+                default:
+                    console.log('unknown ivs type: ', content.type);
+                    break;
+            }
+        });
+
+    }
+
+
+    _drawRect(rect) {
+        //console.log(rect)
+        this.context.rect(rect[0], rect[1], rect[2], rect[3]);
+    }
+
+    _drawText(text, x, y) {
+        this.context.fillText(text, x, y);
+    }
+
+    _drawArc(x, y, r) {
+        this.context.arc(x, y, r, 0, 360);
+    }
+}
+
+function f() {
+
+}
+
+export default IvsDrawer;

File diff suppressed because it is too large
+ 19 - 0
public/static/src/jsFFMPEG.js


+ 257 - 0
public/static/src/md5.js

@@ -0,0 +1,257 @@
+/* eslint-disable */
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+export function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}

+ 80 - 0
public/static/src/player.js

@@ -0,0 +1,80 @@
+import WebSocketServer from './websocketServer.js';
+
+class Player {
+    constructor(option) {
+        this.ws = null;
+        this.options = option;
+        this.events = {
+            error: ()=>{}
+        };
+    }
+
+    init() {
+        //console.log('init');
+        this.ws = new WebSocketServer(this.options);
+        this.ws.init();
+    }
+
+    connect() {
+        for(let i in this.events) {
+            this.ws.setCallBack(i, this.events[i]);
+        }
+        this.ws.connect();
+    }
+
+    play() {
+        //console.log('player')
+    }
+
+    pause() {
+        //console.log('pause')
+    }
+
+    close() {
+        this.ws.close();
+        //console.log('close1')
+    }
+
+    /**
+     * 绘制额外信息
+     * @param obj
+     */
+    updateInfo(obj) {
+        this.ws.updateInfo(obj);
+    }
+
+    setROI(data) {
+        this.ws.setROI(data);
+    }
+
+    resetROI() {
+        this.ws.resetROI();
+    }
+
+    getROIData() {
+        return this.ws.getROIData();
+    }
+
+    setPolygonNum(num) {
+        return this.ws.setPolygonNum(num);
+    }
+
+    /**
+     * 自定义事件
+     * 目前支持如下事件
+     * [error] websocket连接失败
+     * [noStream] 收不到码流
+     * [canplay] 视频能够播放时触发
+     * [initialCompleted] 视频初始化完成, 首次canplay
+     * [ROIFinished] 手动绘制ROI完成时触发
+     *
+     * @param event 事件名
+     * @param callback 事件响应函数
+     */
+    on(event, callback) {
+        this.events[event] = callback;
+    }
+}
+
+export default Player;
+

+ 570 - 0
public/static/src/util.js

@@ -0,0 +1,570 @@
+/* exported assert, isPowerOfTwo, text, nextHighestPowerOfTwo, Size, inherit, inheritObject, isApp, BrowserDetect, doCapture, base64ArrayBuffer, cloneArray  */
+/* global Uint8Array, saveAs */
+/* jshint bitwise: false */
+'use strict';
+
+
+function error(message) {
+    console.error(message);
+    console.trace();
+}
+
+function assert(condition, message) {
+    if (!condition) {
+        error(message);
+    }
+}
+
+function isPowerOfTwo(x) {
+    return (x & (x - 1)) === 0;
+}
+
+/**
+ * Joins a list of lines using a newline separator, not the fastest
+ * thing in the world but good enough for initialization code.
+ */
+function text(lines) {
+    return lines.join("\n");
+}
+
+/**
+ * Rounds up to the next highest power of two.
+ */
+function nextHighestPowerOfTwo(x) {
+    --x;
+    for (var i = 1; i < 32; i <<= 1) {
+        x = x | x >> i;
+    }
+    return x + 1;
+}
+
+/**
+ * Represents a 2-dimensional size value.
+ */
+function Size(width, height) {
+    function Constructor(width, height) {
+        Constructor.prototype.w = width;
+        Constructor.prototype.h = height;
+    }
+    Constructor.prototype = {
+        toString: function() {
+            return "(" + Constructor.prototype.w + ", " + Constructor.prototype.h + ")";
+        },
+        getHalfSize: function() {
+            return new Size(Constructor.prototype.w >>> 1, Constructor.prototype.h >>> 1);
+        },
+        length: function() {
+            return Constructor.prototype.w * Constructor.prototype.h;
+        }
+    };
+    return new Constructor(width, height);
+}
+
+/**
+ * Creates a new prototype object derived from another objects prototype along with a list of additional properties.
+ *
+ * @param base object whose prototype to use as the created prototype object's prototype
+ * @param properties additional properties to add to the created prototype object
+ */
+function inherit(base, properties) {
+    var prot = Object.create(base.prototype);
+    var keyList = Object.keys(properties);
+    for (var i = 0; i < keyList.length; i++) {
+        prot[keyList[i]] = properties[keyList[i]];
+    }
+    return prot;
+}
+
+function inheritObject(base, properties) {
+    var keyList = Object.keys(properties);
+    for (var i = 0; i < keyList.length; i++) {
+        base[keyList[i]] = properties[keyList[i]];
+    }
+    return base;
+}
+
+function isApp() {
+    var isApplication = false;
+    if (document.URL.indexOf("http://") === -1 && document.URL.indexOf("https://") === -1) {
+        isApplication = true;
+    }
+    return isApplication;
+}
+
+function BrowserDetect() {
+    var agent = navigator.userAgent.toLowerCase(),
+        name = navigator.appName,
+        browser = null;
+
+    if (name === 'Microsoft Internet Explorer' || agent.indexOf('trident') > -1 || agent.indexOf('edge/') > -1) {
+        browser = 'ie';
+        if (name === 'Microsoft Internet Explorer') { // IE old version (IE 10 or Lower)
+            agent = /msie ([0-9]{1,}[\.0-9]{0,})/.exec(agent);
+            browser += parseInt(agent[1]);
+        } else { // IE 11+
+            if (agent.indexOf('trident') > -1) { // IE 11
+                browser += 11;
+            } else if (agent.indexOf('edge/') > -1) { // Edge
+                browser = 'edge';
+            }
+        }
+    } else if (agent.indexOf('safari') > -1) { // Chrome or Safari
+        if (agent.indexOf('chrome') > -1) { // Chrome
+            browser = 'chrome';
+        } else { // Safari
+            browser = 'safari';
+        }
+    } else if (agent.indexOf('firefox') > -1) { // Firefox
+        browser = 'firefox';
+    }
+
+    return browser;
+}
+
+function doCapture(data, filename) {
+    // var link = document.createElement('a');
+    var dataAtob = atob(data.substring("data:image/png;base64,".length));
+    var asArray = new Uint8Array(dataAtob.length);
+
+    for (var i = 0, len = dataAtob.length; i < len; ++i) {
+        asArray[i] = dataAtob.charCodeAt(i);
+    }
+
+    var blob = new Blob([asArray.buffer], {
+        type: "image/png"
+    });
+    saveAs(blob, filename + ".png");
+}
+
+var base64ArrayBuffer = function(arrayBuffer) {
+    var base64 = '';
+    var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+    var bytes = new Uint8Array(arrayBuffer);
+    var byteLength = bytes.byteLength;
+    var byteRemainder = byteLength % 3;
+    var mainLength = byteLength - byteRemainder;
+
+    var a = 0, b = 0, c = 0, d = 0;
+    var chunk = 0;
+
+    // Main loop deals with bytes in chunks of 3
+    for (var i = 0; i < mainLength; i = i + 3) {
+        // Combine the three bytes into a single integer
+        chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
+
+        // Use bitmasks to extract 6-bit segments from the triplet
+        a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
+        b = (chunk & 258048) >> 12; // 258048   = (2^6 - 1) << 12
+        c = (chunk & 4032) >> 6; // 4032     = (2^6 - 1) << 6
+        d = chunk & 63; // 63       = 2^6 - 1
+
+        // Convert the raw binary segments to the appropriate ASCII encoding
+        base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
+    }
+
+    // Deal with the remaining bytes and padding
+    if (byteRemainder === 1) {
+        chunk = bytes[mainLength];
+
+        a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
+
+        // Set the 4 least significant bits to zero
+        b = (chunk & 3) << 4; // 3   = 2^2 - 1
+
+        base64 += encodings[a] + encodings[b] + '==';
+    } else if (byteRemainder === 2) {
+        chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
+
+        a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
+        b = (chunk & 1008) >> 4; // 1008  = (2^6 - 1) << 4
+
+        // Set the 2 least significant bits to zero
+        c = (chunk & 15) << 2; // 15    = 2^4 - 1
+
+        base64 += encodings[a] + encodings[b] + encodings[c] + '=';
+    }
+
+    return base64;
+};
+
+function cloneArray(array) {
+    var bs = array.BYTES_PER_ELEMENT,
+        bo = array.byteOffset,
+        n = array.length;
+    return new array.constructor(array.buffer.slice(bo, bo + bs * n));
+}
+
+function BufferQueue() {
+    this.first = null;
+    this.size = 0;
+}
+
+function BufferNode(buffer) {
+    this.buffer = buffer; //new Uint8Array(buffer.length);
+    //    this.buffer.set(buffer, 0);
+    this.previous = null;
+    this.next = null;
+}
+
+BufferQueue.prototype.enqueue = function(buffer) {
+    var node = new BufferNode(buffer);
+    if (this.first === null) {
+        this.first = node;
+    } else {
+        var n = this.first;
+        while (n.next !== null) {
+            n = n.next;
+        }
+        n.next = node;
+    }
+
+    this.size += 1;
+    return node;
+};
+
+BufferQueue.prototype.dequeue = function() {
+    var temp = null;
+    if (this.first !== null) {
+        temp = this.first;
+        this.first = this.first.next;
+        this.size -= 1;
+    }
+
+    return temp;
+};
+
+BufferQueue.prototype.clear = function() {
+    console.log('BufferQueue clear!');
+    var temp = null;
+    while (this.first !== null) {
+        temp = this.first;
+        this.first = this.first.next;
+        this.size -= 1;
+        temp.buffer = null;
+        temp = null;
+    }
+
+    this.size = 0;
+    this.first = null;
+};
+
+function BufferList() {
+    this._length = 0;
+    this.head = null;
+    this.tail = null;
+    this.curIdx = 0;
+}
+
+BufferList.prototype.getCurIdx = function() {
+    return this.curIdx;
+};
+
+BufferList.prototype.push = function(buffer) {
+    var node = new BufferNode(buffer);
+
+    if (this._length > 0) {
+        this.tail.next = node;
+        node.previous = this.tail;
+        this.tail = node;
+    } else {
+        this.head = node;
+        this.tail = node;
+    }
+    this._length += 1;
+
+    return node;
+};
+
+BufferList.prototype.pop = function() {
+    var node = null;
+    if (this._length > 1) {
+        node = this.head;
+        this.head = this.head.next;
+        if (this.head !== null) {
+            this.head.previous = null;
+            // 2nd use-case: there is no second node
+        } else {
+            this.tail = null;
+        }
+        this._length -= 1;
+    }
+
+    return node;
+};
+
+BufferList.prototype.pushPop = function(buffer) {
+    var node = null;
+
+    if (buffer !== null) {
+        node = new BufferNode(buffer);
+        if (this._length > 0) {
+            this.tail.next = node;
+            node.previous = this.tail;
+            this.tail = node;
+        } else {
+            this.head = node;
+            this.tail = node;
+        }
+        this._length += 1;
+    } else {
+        if (this._length > 1) {
+            node = this.head;
+            this.head = this.head.next;
+            if (this.head !== null) {
+                this.head.previous = null;
+                // 2nd use-case: there is no second node
+            } else {
+                this.tail = null;
+            }
+            this._length -= 1;
+        }
+    }
+
+    return node;
+};
+
+BufferList.prototype.searchNodeAt = function(position) {
+    var currentNode = this.head,
+        length = this._length,
+        count = 1,
+        message = {
+            failure: 'Failure: non-existent node in this list.'
+        };
+
+    // 1st use-case: an invalid position
+    if (length === 0 || position < 1 || position > length) {
+        throw new Error(message.failure);
+    }
+
+    // 2nd use-case: a valid position
+    while (count < position) {
+        currentNode = currentNode.next;
+        count++;
+    }
+    this.curIdx = count;
+
+    // console.log('searchNodeAt curIdx ' + this.curIdx + ' count ' + count + ' _length ' + this._length);
+    return currentNode;
+};
+
+BufferList.prototype.clear = function() {
+    console.log('BufferList clear!');
+    var node = this.head;
+    var nodeToDelete = null;
+
+    while (node !== null) {
+        nodeToDelete = node;
+        node = node.next;
+        nodeToDelete.buffer = null;
+        nodeToDelete = null;
+    }
+
+    this._length = 0;
+    this.head = null;
+    this.tail = null;
+    this.curIdx = 0;
+};
+
+BufferList.prototype.remove = function(position) {
+    var currentNode = this.head,
+        length = this._length,
+        count = 1,
+        message = {
+            failure: 'Failure: non-existent node in this list.'
+        },
+        beforeNodeToDelete = null,
+        nodeToDelete = null,
+        afterNodeToDelete = null,
+        deletedNode = null;
+
+    // 1st use-case: an invalid position
+    if (length === 0 || position < 1 || position > length) {
+        throw new Error(message.failure);
+    }
+
+    // 2nd use-case: the first node is removed
+    if (position === 1) {
+        this.head = currentNode.next;
+
+        // 2nd use-case: there is a second node
+        if (!this.head) {
+            this.head.previous = null;
+            // 2nd use-case: there is no second node
+        } else {
+            this.tail = null;
+        }
+
+        // 3rd use-case: the last node is removed
+    } else if (position === this._length) {
+        this.tail = this.tail.previous;
+        this.tail.next = null;
+        // 4th use-case: a middle node is removed
+    } else {
+        while (count < position) {
+            currentNode = currentNode.next;
+            count++;
+        }
+
+        beforeNodeToDelete = currentNode.previous;
+        nodeToDelete = currentNode;
+        afterNodeToDelete = currentNode.next;
+
+        beforeNodeToDelete.next = afterNodeToDelete;
+        afterNodeToDelete.previous = beforeNodeToDelete;
+        deletedNode = nodeToDelete;
+        nodeToDelete = null;
+    }
+
+    this._length--;
+
+    return message.success;
+};
+
+BufferList.prototype.removeTillCurrent = function() {
+    var currentNode = null,
+        count = 1,
+        message = {
+            failure: 'Failure: non-existent node in this list.'
+        };
+
+    while (count < this.curIdx) {
+        currentNode = this.pop();
+        if (currentNode !== null) {
+            delete currentNode.buffer;
+            currentNode.buffer = null;
+            currentNode.previous = null;
+            currentNode.next = null;
+        }
+        count++;
+    }
+
+    // console.log('removeTillCurrent curIdx ' + this.curIdx + ' count ' + count + ' _length ' + this._length);
+};
+
+/**
+ * Create a new object pool of a certain class
+ */
+var BufferPool = function(size) {
+    // metrics for tracking internals
+    this.metrics = {};
+    this._clearMetrics();
+    // [private] the objpool stack
+    this._objpool = [];
+    this.bufferSize = size;
+};
+
+/**
+ * Allocate a new object from the pool
+ * @return the object
+ */
+BufferPool.prototype.alloc = function alloc() {
+    var obj = null;
+    if (this._objpool.length === 0) {
+        // nothing in the free list, so allocate a new object
+        obj = new Uint8Array(this.bufferSize);
+        this.metrics.totalalloc++;
+    } else {
+        // grab one from the top of the objpool
+        obj = this._objpool.pop();
+        this.metrics.totalfree--;
+    }
+    return obj;
+};
+
+/**
+ * Return an object to the object pool
+ */
+BufferPool.prototype.free = function(obj) {
+    // fix up the free list pointers
+    if (obj.length > 0) {
+        console.log('It is not zero length = ' + obj.length);
+    } else {
+        return;
+    }
+    this._objpool.push(obj);
+    this.metrics.totalfree++;
+};
+
+/**
+ * Allow collection of all objects in the pool
+ */
+BufferPool.prototype.collect = function(cls) {
+    // just forget the list and let the garbage collector reap them
+    this._objpool = []; // fresh and new
+    // but we might have allocated objects that are in use/not in
+    // the pool--track them in the metrics:
+    var inUse = this.metrics.totalalloc - this.metrics.totalfree;
+    this._clearMetrics(inUse);
+};
+
+/**
+ * [private] Clear internal metrics
+ */
+BufferPool.prototype._clearMetrics = function(allocated) {
+    this.metrics.totalalloc = allocated || 0;
+    this.metrics.totalfree = 0;
+};
+
+/**
+ * Create a new image pool of a certain class
+ */
+var ImagePool = function() {
+    // metrics for tracking internals
+    this.metrics = {};
+    this._clearMetrics();
+    // [private] the objpool stack
+    this._objpool = [];
+};
+
+/**
+ * Allocate a new object from the pool
+ * @return the object
+ */
+ImagePool.prototype.alloc = function alloc() {
+    var obj = null;
+    if (this._objpool.length === 0) {
+        // nothing in the free list, so allocate a new object
+        obj = new Image();
+        this.metrics.totalalloc++;
+    } else {
+        // grab one from the top of the objpool
+        obj = this._objpool.pop();
+        this.metrics.totalfree--;
+    }
+    return obj;
+};
+
+/**
+ * Return an object to the object pool
+ */
+ImagePool.prototype.free = function(obj) {
+    // fix up the free list pointers
+    if (obj.length > 0) {
+        console.log('It is not zero length = ' + obj.length);
+    } else {
+        return;
+    }
+    this._objpool.push(obj);
+    this.metrics.totalfree++;
+};
+
+/**
+ * Allow collection of all objects in the pool
+ */
+ImagePool.prototype.collect = function(cls) {
+    // just forget the list and let the garbage collector reap them
+    this._objpool = []; // fresh and new
+    // but we might have allocated objects that are in use/not in
+    // the pool--track them in the metrics:
+    var inUse = this.metrics.totalalloc - this.metrics.totalfree;
+    this._clearMetrics(inUse);
+};
+
+/**
+ * [private] Clear internal metrics
+ */
+ImagePool.prototype._clearMetrics = function(allocated) {
+    this.metrics.totalalloc = allocated || 0;
+    this.metrics.totalfree = 0;
+};

+ 156 - 0
public/static/src/videoBuffer.js

@@ -0,0 +1,156 @@
+"use strict";
+
+var VideoBufferNode = (function() {
+    function Constructor(data, width, height, codecType, frameType, timeStamp) {
+        BufferNode.call(this, data);
+        this.width = width;
+        this.height = height;
+        this.codecType = codecType;
+        this.frameType = frameType;
+        this.timeStamp = timeStamp;
+    }
+
+    return Constructor;
+})();
+
+function VideoBufferList() {
+    var MAX_LENGTH = 0,
+        BUFFERING = 0,
+        bufferFullCallback = null;
+
+    function Constructor() {
+        BufferList.call(this);
+        MAX_LENGTH = 360;
+        BUFFERING = 240;
+        bufferFullCallback = null;
+    }
+
+    Constructor.prototype = inherit(BufferList, {
+        push: function(data, width, height, codecType, frameType, timeStamp) {
+            var node = new VideoBufferNode(data, width, height, codecType, frameType, timeStamp);
+            if (this._length > 0) {
+                this.tail.next = node;
+                node.previous = this.tail;
+                this.tail = node;
+            } else {
+                this.head = node;
+                this.tail = node;
+            }
+            this._length += 1;
+
+            (bufferFullCallback !== null && this._length >= BUFFERING) ? bufferFullCallback(): 0; //PLAYBACK bufferFull
+            //      console.log("VideoBufferList after push node count is " + this._length + " frameType is " + frameType);
+
+            return node;
+        },
+        pop: function() {
+            //    console.log("before pop node count is " + this._length + " MINBUFFER is " + MINBUFFER);
+            var node = null;
+            if (this._length > 1) {
+                node = this.head;
+                this.head = this.head.next;
+                if (this.head !== null) {
+                    this.head.previous = null;
+                    // 2nd use-case: there is no second node
+                } else {
+                    this.tail = null;
+                }
+                this._length -= 1;
+            }
+            return node;
+        },
+        setMaxLength: function(length) {
+            MAX_LENGTH = length;
+            if (MAX_LENGTH > 360) {
+                MAX_LENGTH = 360;
+            } else if (MAX_LENGTH < 30) {
+                MAX_LENGTH = 30;
+            }
+        },
+        setBUFFERING: function(interval) {
+            BUFFERING = interval;
+            if (BUFFERING > 240) {
+                BUFFERING = 240;
+            } else if (BUFFERING < 6) {
+                BUFFERING = 6;
+            }
+        },
+        setBufferFullCallback: function(callback) {
+            bufferFullCallback = callback;
+            // console.log("setBufferFullCallback MAX_LENGTH is " + MAX_LENGTH );
+        },
+        searchTimestamp: function(frameTimestamp) {
+            //      console.log("searchTimestamp frameTimestamp = " + frameTimestamp.timestamp + " frameTimestamp usec = " + frameTimestamp.timestamp_usec);
+            var currentNode = this.head,
+                length = this._length,
+                count = 1,
+                message = {
+                    failure: 'Failure: non-existent node in this list.'
+                };
+
+            // 1st use-case: an invalid position
+            if (length === 0 || frameTimestamp <= 0 || currentNode === null) {
+                throw new Error(message.failure);
+            }
+
+            // 2nd use-case: a valid position
+            while (currentNode !== null &&
+            (currentNode.timeStamp.timestamp !== frameTimestamp.timestamp ||
+                currentNode.timeStamp.timestamp_usec !== frameTimestamp.timestamp_usec)) {
+                //        console.log("currentNode Timestamp = " + currentNode.timeStamp.timestamp + " Timestamp usec = " + currentNode.timeStamp.timestamp_usec);
+                currentNode = currentNode.next;
+                count++;
+            }
+
+            if (length < count) {
+                currentNode = null;
+            } else {
+                this.curIdx = count;
+                // console.log("searchTimestamp curIdx = " + this.curIdx + " currentNode.timeStamp.timestamp = " + currentNode.timeStamp.timestamp + " currentNode.timestamp_usec = " + currentNode.timeStamp.timestamp_usec + " frameTimestamp = " + frameTimestamp.timestamp + " frameTimestamp usec = " + frameTimestamp.timestamp_usec);
+            }
+
+            return currentNode;
+        },
+        findIFrame: function(isForward) {
+            var currentNode = this.head,
+                length = this._length,
+                count = 1,
+                message = {
+                    failure: 'Failure: non-existent node in this list.'
+                };
+
+            // 1st use-case: an invalid position
+            if (length === 0) {
+                throw new Error(message.failure);
+            }
+
+            // 2nd use-case: a valid position
+            while (count < this.curIdx) {
+                currentNode = currentNode.next;
+                count++;
+            }
+
+            if (isForward === true) {
+                while (currentNode.frameType !== "I") {
+                    currentNode = currentNode.next;
+                    count++;
+                }
+            } else {
+                while (currentNode.frameType !== "I") {
+                    currentNode = currentNode.previous;
+                    count--;
+                }
+            }
+
+            if (length < count) {
+                currentNode = null;
+            } else {
+                this.curIdx = count;
+                // console.log('findIFrame curIdx ' + this.curIdx + ' count ' + count + ' _length ' + this._length);
+            }
+
+            return currentNode;
+        }
+    });
+    return new Constructor();
+}

+ 128 - 0
public/static/src/videoWorker.js

@@ -0,0 +1,128 @@
+importScripts(
+    './H264SPSParser.js',
+    './H264Session.js',
+
+    './H265SPSParser.js',
+    './H265Session2.js',
+    './H265.js'
+);
+
+
+addEventListener('message', receiveMessage);
+
+let sdpInfo = null;
+let rtpSession = null;
+let videoCHID = -1;
+let videoRtpSessionsArray = [];
+
+function  receiveMessage(event) {
+    //console.log(event.data)
+    var message = event.data;
+
+    switch (message.type) {
+        case 'sdpInfo':
+            sdpInfo = message.data;
+
+            initRTPSession(sdpInfo.sdpInfo);
+            break;
+        case 'rtpDataArray':
+            //console.log(message.data.length)
+            for (let num = 0; num < message.data.length; num++) {
+                receiveMessage({
+                    'type': 'rtpData',
+                    'data': message.data[num],
+                });
+            }
+            break;
+        case 'rtpData':
+            videoCHID = message.data.rtspInterleave[1];
+            if (typeof videoRtpSessionsArray[videoCHID] !== "undefined") {
+                videoRtpSessionsArray[videoCHID].remuxRTPData(message.data.rtspInterleave,
+                    message.data.header, message.data.payload);
+            }else { // RTCP包
+                //console.log('Interleave:  ' + videoCHID);
+                //console.log(message.data.rtspInterleave, message.data.header);
+                //return;
+            }
+            break;
+    }
+}
+
+function initRTPSession(sdpInfo) {
+    for(let [i, len] = [0, sdpInfo.length]; i < len; i++) {
+        if(sdpInfo[i].codecName === 'H264') {
+            //console.log(sdpInfo)
+            rtpSession = new H264Session();
+            rtpSession.init();
+            rtpSession.rtpSessionCallback = RtpReturnCallback;
+            if(sdpInfo[i].Framerate) {
+                rtpSession.setFrameRate(sdpInfo[i].Framerate);
+            }
+        }
+
+        if(sdpInfo[i].codecName === 'H265') {
+            //console.log(sdpInfo)
+            rtpSession = new H265Session();
+            rtpSession.init();
+            rtpSession.rtpSessionCallback = RtpReturnCallback;
+            if(sdpInfo[i].Framerate) {
+                rtpSession.setFrameRate(sdpInfo[i].Framerate);
+            }
+        }
+
+        if(rtpSession !== null) {
+            videoCHID = sdpInfo[i].RtpInterlevedID;
+            videoRtpSessionsArray[videoCHID] = rtpSession;
+        }
+    }
+}
+
+function RtpReturnCallback(dataInfo) {
+
+    if(dataInfo == null || dataInfo == undefined) {
+        //console.log('数据为空')
+        return;
+    }
+    let mediaData = dataInfo;
+    if(mediaData.decodeMode === 'canvas') {
+
+        if(mediaData.SEIInfo !== null && mediaData.SEIInfo !== undefined) {//SEI信息
+            sendMessage('SEI', mediaData.SEIInfo);
+        } else if(mediaData.frameData !== null){
+            sendMessage('YUVData', mediaData);
+            sendMessage('videoTimeStamp', mediaData.timeStamp);
+        }
+        return;
+    }
+    //console.log( mediaData.SEIInfo)
+    if(mediaData.initSegmentData !== null && mediaData.initSegmentData !== undefined) {
+        //sendMessage('codecInfo', mediaData.codecInfo)
+        //sendMessage('initSegment', mediaData.initSegmentData);
+        sendMessage('videoInit', mediaData);
+        sendMessage('firstvideoTimeStamp', mediaData.timeStamp);
+
+    }else if(mediaData.SEIInfo !== null && mediaData.SEIInfo !== undefined) {//SEI信息
+        sendMessage('SEI', mediaData.SEIInfo);
+    }
+
+    if (mediaData.frameData && mediaData.frameData.length > 0) {
+        sendMessage('videoTimeStamp', mediaData.timeStamp);
+        sendMessage('mediaSample', mediaData.mediaSample);
+        //console.log(mediaData.frameData.length)
+        sendMessage('videoRender', mediaData.frameData);
+    }
+    mediaData = null;
+}
+
+function sendMessage(type, data) {
+    let event = {
+        type: type,
+        data: data
+    }
+    if(type === 'videoRender') {
+        postMessage(event, [data.buffer]);
+    }else {
+        postMessage(event);
+    }
+    event = null;
+}

+ 653 - 0
public/static/src/websocketServer.js

@@ -0,0 +1,653 @@
+import WorkerManager from './workerManager.js';
+import {hex_md5}  from "./md5.js";
+
+function WebSocketServer(options) {
+    let videoElement = null;
+    let canvasElement = null;
+    let ROIElement =null;
+    let websocket = null;
+    let wsURL = null;
+    let rtspURL = null;
+    let username = null;
+    let password = null;
+    let CSeq = 1;
+    let IsDescribe = false; //RTSP响应报文中,describe时有两段,以'\r\n'分段
+    let currentState = "Options";
+    let describekey = false;
+    let Authentication = '\r\n'; //认证,信令最后四个字节为'\r\n\r\n',为补足,默认为'\r\n'
+    let sessionID = '';
+    let rtspSDPData = {};
+    let SDPinfo = []; //SDP信息
+    let setupSDPIndex = 0;
+    let getParameterInterval = null; //保活
+    let AACCodecInfo = null;
+
+//RTP包处理相关
+    let rtspinterleave = null;
+    let RTPPacketTotalSize = 0;
+    let rtpheader = null;
+    let rtpPacketArray = null;
+
+    let workerManager = null;
+    let connectFailCallback = null;
+
+    let lastStreamTime = null; //记录收到码流的时间
+    let getStreamInterval = null;
+    let noStreamCallback = null;
+
+    const RTSP_INTERLEAVE_LENGTH = 4; //交织头占4个字节
+    const RTSP_STATE = {
+        OK: 200,
+        UNAUTHORIZED: 401,
+        NOTFOUND: 404,
+        INVALID_RANGE: 457,
+        NOTSERVICE: 503,
+        DISCONNECT: 999
+    };
+    const SEND_GETPARM_INTERVAL = 20000; //保活时间
+
+    function constructor({video, canvas, drawer,wsUrl, rtspUrl, user, pwd} = {options}) {
+        videoElement = video;
+        canvasElement = canvas;
+        ROIElement = drawer;
+        wsURL = wsUrl;
+        rtspURL = rtspUrl;
+        username = user;
+        password = pwd;
+
+    }
+
+    constructor.prototype = {
+        init() {
+            workerManager = new WorkerManager();
+            workerManager.init(videoElement,canvasElement, ROIElement);
+        },
+        connect() {
+            websocket = new WebSocket(wsURL);
+            websocket.binaryType = 'arraybuffer';
+            websocket.onmessage = ReceiveMessage;
+            websocket.onopen = () => {
+                let option = StringToU8Array("OPTIONS " + rtspURL + " RTSP/1.0\r\nCSeq: " + CSeq + "\r\n\r\n");
+                websocket.send(option);
+                //console.log('websocket connect')
+            };
+            websocket.onerror = ()=> {
+                if(connectFailCallback) {
+                    connectFailCallback('websocket connect fail');
+                }
+            }
+        },
+        close() {
+            clearInterval(getParameterInterval);
+            clearInterval(getStreamInterval);
+            SendRtspCommand(CommandConstructor("TEARDOWN", null));
+            websocket.close();
+            if(workerManager) {
+                workerManager.terminate();
+            }
+        },
+        setCallBack(event, callback) {
+            switch (event) {
+                case 'error':
+                    connectFailCallback = ()=>{
+                        callback();
+                        this.close();
+                    };
+                    break;
+                case 'noStream':
+                    noStreamCallback = ()=>{
+                        callback();
+                        this.close();
+                    };
+                    break;
+                case 'canplay':
+                case 'initialCompleted':
+                case 'ROIFinished':
+                    workerManager.setEventCallBack(event, callback);
+                    break;
+                default:
+                    console.log('unsupport event');
+            }
+        },
+        updateInfo(obj) {
+            workerManager.updateInfo(obj);
+        },
+        setROI(data) {
+            workerManager.setROI(data);
+        },
+        resetROI() {
+            workerManager.resetROI();
+        },
+        getROIData() {
+            return workerManager.getROIData();
+        },
+        setPolygonNum(num) {
+            return workerManager.setPolygonNum(num);
+        }
+    };
+
+
+
+    return new constructor(options);
+
+    /**
+     * websocket消息处理函数
+     * @param event
+     * @constructor
+     */
+    function ReceiveMessage(event) {
+        let data = event.data;
+        let receiveUint8 = new Uint8Array(data);
+        let PreceiveUint8 = new Uint8Array(receiveUint8.length);
+        PreceiveUint8.set(receiveUint8, 0);
+        let dataLength = PreceiveUint8.length;
+        // if(dataLength < 10) {
+        //     //console.log(String.fromCharCode.apply(null, PreceiveUint8))
+        // }
+        while (dataLength > 0) {
+            if (PreceiveUint8[0] != 36) {//非$符号表示RTSP
+                //console.log(PreceiveUint8[0], PreceiveUint8[1], PreceiveUint8[2], PreceiveUint8[3], PreceiveUint8[4])
+                //console.log(PreceiveUint8.length)
+                let PreceiveMsg = String.fromCharCode.apply(null, PreceiveUint8);
+                //console.log(PreceiveMsg)
+                let rtspendpos = null;
+                if (IsDescribe === true) {
+                    rtspendpos = PreceiveMsg.lastIndexOf("\r\n");
+                    IsDescribe = false
+                } else {
+                    rtspendpos = PreceiveMsg.search("\r\n\r\n");
+
+                }
+                let rtspstartpos = PreceiveMsg.search("RTSP");
+                if (rtspstartpos !== -1) {
+                    if (rtspendpos !== -1) {
+                        let RTSPResArray = PreceiveUint8.subarray(rtspstartpos, rtspendpos + RTSP_INTERLEAVE_LENGTH);
+                        PreceiveUint8 = PreceiveUint8.subarray(rtspendpos + RTSP_INTERLEAVE_LENGTH);
+                        let receiveMsg = String.fromCharCode.apply(null, RTSPResArray);
+                        RTSPResHandler(receiveMsg);
+                        dataLength = PreceiveUint8.length;
+                    } else {
+                        dataLength = PreceiveUint8.length;
+                        return
+                    }
+                } else {
+                    PreceiveUint8 = new Uint8Array;
+                    return
+                }
+            } else { //$表示RTP和RTCP
+                //console.log('RTP开始');
+                //console.log(PreceiveUint8.length)
+                // if(PreceiveUint8.length == 4) {
+                //    console.log(PreceiveUint8)
+                // }
+                lastStreamTime = Date.now();
+                rtspinterleave = PreceiveUint8.subarray(0, RTSP_INTERLEAVE_LENGTH);
+                //console.log(rtspinterleave)
+                RTPPacketTotalSize = rtspinterleave[2] * 256 + rtspinterleave[3];
+                if (RTPPacketTotalSize + RTSP_INTERLEAVE_LENGTH <= PreceiveUint8.length) {
+                    rtpheader = PreceiveUint8.subarray(RTSP_INTERLEAVE_LENGTH, 16);
+                    rtpPacketArray = PreceiveUint8.subarray(16, RTPPacketTotalSize + RTSP_INTERLEAVE_LENGTH);
+                    //rtpCallback(rtspinterleave, rtpheader, rtpPacketArray);
+                    workerManager.parseRtpData(rtspinterleave, rtpheader, rtpPacketArray);
+                    PreceiveUint8 = PreceiveUint8.subarray(RTPPacketTotalSize + RTSP_INTERLEAVE_LENGTH);
+                    //console.log('PreceiveUint8.length:  ' + PreceiveUint8.length)
+                    dataLength = PreceiveUint8.length;
+                } else {
+                    dataLength = PreceiveUint8.length;
+                    //console.count('11111111111')
+                    //console.log(PreceiveUint8)
+                    return
+                }
+            }
+        }
+    }
+
+    /**
+     * 将字符串转为arrayBuffer
+     * @param string
+     */
+    function StringToU8Array(string) {
+        CSeq++;
+        //console.log(string)
+        let stringLength = string.length;
+        let outputUint8Array = new Uint8Array(new ArrayBuffer(stringLength));
+        for (let i = 0; i < stringLength; i++) {
+            outputUint8Array[i] = string.charCodeAt(i);
+        }
+        //console.log(outputUint8Array)
+        return outputUint8Array;
+        //return string;
+    }
+
+    /**
+     * 处理收到的RTSP信令,解析后发送下一条
+     * @param stringMessage
+     * @constructor
+     */
+    function RTSPResHandler(stringMessage) {
+        //console.log(stringMessage)
+        //let seekPoint = stringMessage.search("CSeq: ") + 5;
+        let rtspResponseMsg = parseRtsp(stringMessage);
+//console.log(rtspResponseMsg)
+        if (rtspResponseMsg.ResponseCode === RTSP_STATE.UNAUTHORIZED && Authentication === "\r\n") { //需要鉴权
+            if(currentState === "Describe") {
+                IsDescribe = false;
+                describekey = false;
+            }
+            username= getUser(rtspURL).username;
+            password = getUser(rtspURL).password;
+            //console.log(rtspResponseMsg)
+            SendRtspCommand(formDigest(rtspResponseMsg));
+            Authentication = "\r\n";
+
+        } else if (rtspResponseMsg.ResponseCode === RTSP_STATE.OK) { //服务器端返回成功
+            switch (currentState) {
+                case 'Options':
+                    currentState = "Describe";
+                    SendRtspCommand(CommandConstructor("DESCRIBE", null));
+                    break;
+                case "Describe":
+                    rtspSDPData = parseDescribeResponse(stringMessage);
+                    if (typeof rtspResponseMsg.ContentBase !== "undefined") {
+                        rtspSDPData.ContentBase = rtspResponseMsg.ContentBase
+                    }
+                    //console.log(rtspSDPData.Sessions)
+                    for (let idx = 0; idx < rtspSDPData.Sessions.length; idx++) {
+                        let sdpInfoObj = {};
+                        if (rtspSDPData.Sessions[idx].CodecMime === "H264"  ||rtspSDPData.Sessions[idx].CodecMime === "H265") { //暂时只支持H264
+                            sdpInfoObj.codecName = rtspSDPData.Sessions[idx].CodecMime;
+                            //sdpInfoObj.trackID = rtspSDPData.Sessions[idx].ControlURL;
+                            sdpInfoObj.trackID = rtspSDPData.Sessions[idx].trackID;
+                            sdpInfoObj.ClockFreq = rtspSDPData.Sessions[idx].ClockFreq;
+                            sdpInfoObj.Port = parseInt(rtspSDPData.Sessions[idx].Port);
+                            if (typeof rtspSDPData.Sessions[idx].Framerate !== "undefined") {
+                                sdpInfoObj.Framerate = parseInt(rtspSDPData.Sessions[idx].Framerate)
+                            }
+                            if(typeof rtspSDPData.Sessions[idx].SPS !== "undefined") {
+                                sdpInfoObj.SPS = rtspSDPData.Sessions[idx].SPS;
+                            }
+                            SDPinfo.push(sdpInfoObj)
+                        } else {
+                            console.log("Unknown codec type:", rtspSDPData.Sessions[idx].CodecMime, rtspSDPData.Sessions[idx].ControlURL)
+                        }
+                    }
+                    setupSDPIndex = 0;
+                    currentState = "Setup";
+                    //console.log(SDPinfo[setupSDPIndex])
+                    SendRtspCommand(CommandConstructor("SETUP", SDPinfo[setupSDPIndex].trackID, setupSDPIndex));
+                    //SendRtspCommand(CommandConstructor("SETUP", 'track1'));
+                    break;
+                case "Setup":
+                    sessionID = rtspResponseMsg.SessionID;
+                    //多路流(如音频流)
+                    //在Describe中暂时只解析H264视频流,因此SDPinfo.length始终为1
+                    if (setupSDPIndex < SDPinfo.length) {
+                        SDPinfo[setupSDPIndex].RtpInterlevedID = rtspResponseMsg.RtpInterlevedID;
+                        SDPinfo[setupSDPIndex].RtcpInterlevedID = rtspResponseMsg.RtcpInterlevedID;
+                        setupSDPIndex += 1;
+                        if (setupSDPIndex !== SDPinfo.length) {
+                            SendRtspCommand(CommandConstructor("SETUP", SDPinfo[setupSDPIndex].trackID, setupSDPIndex));
+                        } else {
+                            workerManager.sendSdpInfo(SDPinfo);
+                            currentState = "Play";
+                            SendRtspCommand(CommandConstructor("PLAY"));
+                        }
+                    }
+
+                    sessionID = rtspResponseMsg.SessionID;
+                    //开始播放后,发送GET_PARAMETER进行保活
+                    clearInterval(getParameterInterval);
+                    getParameterInterval = setInterval(function () {
+                        SendRtspCommand(CommandConstructor("GET_PARAMETER", null))
+                    }, SEND_GETPARM_INTERVAL);
+
+                    getStreamInterval = setInterval(()=>{
+                        if(!getBitStream()) {
+                            console.log('超时!');
+                            noStreamCallback && noStreamCallback();
+                        }
+                    }, 5000);
+                    break;
+                case "Play":
+
+                    break;
+                default:
+                    console.log('暂不支持的信令');
+                    break;
+            }
+        } else if (rtspResponseMsg.ResponseCode === RTSP_STATE.NOTSERVICE) { //服务不可用
+
+        } else if (rtspResponseMsg.ResponseCode === RTSP_STATE.NOTFOUND) { //Not Found
+
+        }
+    }
+
+    /**
+     * 发送rtsp信令
+     * @param sendMessage
+     * @constructor
+     */
+    function SendRtspCommand(sendMessage) {
+        //console.log(sendMessage)
+        if (websocket !== null && websocket.readyState === WebSocket.OPEN) {
+            if (describekey === false) {
+                let describeCmd = sendMessage.search("DESCRIBE");
+                if (describeCmd !== -1) {
+                    IsDescribe = true;
+                    describekey = true;
+                }
+            }
+            //console.log(sendMessage)
+            websocket.send(StringToU8Array(sendMessage))
+        } else {
+            console.log('websocket未连接')
+        }
+    }
+
+    /**
+     * 组装RTSP信令
+     * @param method
+     * @param trackID
+     * @returns {*}
+     * @constructor
+     */
+    function CommandConstructor(method, trackID, interleaved) {
+        let sendMessage;
+        switch (method) {
+            case"OPTIONS":
+            case"TEARDOWN":
+            case"SET_PARAMETERS":
+            case"DESCRIBE":
+                //TODO: 保活
+                sendMessage = method + " " + rtspURL + " RTSP/1.0\r\nCSeq: " + CSeq + "\r\n" + Authentication;
+                break;
+            case"SETUP":
+                //console.log(trackID)
+                //TODO 多trackID的时候测试一下
+                sendMessage = method + " " + rtspURL + "/" + trackID + " RTSP/1.0\r\nCSeq: " + CSeq + Authentication + "Transport:RTP/AVP/TCP;unicast;interleaved=" + 2 * interleaved + "-" + (2 * interleaved + 1) + "\r\n";
+                if(sessionID == 0) {
+                    sendMessage += "\r\n";
+                } else {
+                    sendMessage += "Session: " + sessionID + "\r\n\r\n";
+                }
+                break;
+            case"PLAY":
+                sendMessage = method + " " + rtspURL + " RTSP/1.0\r\nCSeq: " + CSeq + "\r\nSession: " + sessionID + "\r\n" + "Range: npt=0.000-\r\n" + Authentication;
+                break;
+            case"PAUSE":
+                sendMessage = method + " " + rtspURL + " RTSP/1.0\r\nCSeq: " + CSeq + "\r\nSession: " + sessionID + "\r\n\r\n";
+                break;
+            case"GET_PARAMETER":
+                sendMessage = method + " " + rtspURL + " RTSP/1.0\r\nCSeq: " + CSeq + "\r\nSession: " + sessionID + "\r\n"  + Authentication;
+                break;
+            default:
+                console.log('暂不支持的RTSP信令');
+        }
+        //console.log(sendMessage);
+        return sendMessage;
+    }
+
+    /**
+     * 解析RTSP信令
+     * @param message1
+     */
+    function parseRtsp(message1) {
+        let RtspResponseData = {};
+        let cnt = 0, cnt1 = 0, ttt = null, LineTokens = null;
+        let message = null;
+        if (message1.search("Content-Type: application/sdp") !== -1) {
+            let messageTok = message1.split("\r\n\r\n");
+            message = messageTok[0]
+        } else {
+            message = message1
+        }
+        let TokenziedResponseLines = message.split("\r\n");
+        let ResponseCodeTokens = TokenziedResponseLines[0].split(" ");
+        if (ResponseCodeTokens.length > 2) {
+            RtspResponseData.ResponseCode = parseInt(ResponseCodeTokens[1]);
+            RtspResponseData.ResponseMessage = ResponseCodeTokens[2]
+        }
+        if (RtspResponseData.ResponseCode === RTSP_STATE.OK) {
+            for (cnt = 1; cnt < TokenziedResponseLines.length; cnt++) {
+                LineTokens = TokenziedResponseLines[cnt].split(":");
+                if (LineTokens[0] === "Public") {
+                    RtspResponseData.MethodsSupported = LineTokens[1].split(",")
+                } else if (LineTokens[0] === "CSeq") {
+                    RtspResponseData.CSeq = parseInt(LineTokens[1])
+                } else if (LineTokens[0] === "Content-Type") {
+                    RtspResponseData.ContentType = LineTokens[1];
+                    if (RtspResponseData.ContentType.search("application/sdp") !== -1) {
+                        RtspResponseData.SDPData = parseDescribeResponse(message1)
+                    }
+                } else if (LineTokens[0] === "Content-Length") {
+                    RtspResponseData.ContentLength = parseInt(LineTokens[1])
+                } else if (LineTokens[0] === "Content-Base") {
+                    let ppos = TokenziedResponseLines[cnt].search("Content-Base:");
+                    if (ppos !== -1) {
+                        RtspResponseData.ContentBase = TokenziedResponseLines[cnt].substr(ppos + 13)
+                    }
+                } else if (LineTokens[0] === "Session") {
+                    let SessionTokens = LineTokens[1].split(";");
+                    //RtspResponseData.SessionID = parseInt(SessionTokens[0])
+                    //console.log(SessionTokens[0])
+                    RtspResponseData.SessionID = SessionTokens[0].trim();
+                } else if (LineTokens[0] === "Transport") {
+                    let TransportTokens = LineTokens[1].split(";");
+                    for (cnt1 = 0; cnt1 < TransportTokens.length; cnt1++) {
+                        let tpos = TransportTokens[cnt1].search("interleaved=");
+                        if (tpos !== -1) {
+                            let interleaved = TransportTokens[cnt1].substr(tpos + 12);
+                            let interleavedTokens = interleaved.split("-");
+                            if (interleavedTokens.length > 1) {
+                                RtspResponseData.RtpInterlevedID = parseInt(interleavedTokens[0]);
+                                RtspResponseData.RtcpInterlevedID = parseInt(interleavedTokens[1])
+                            }
+                        }
+                    }
+                } else if (LineTokens[0] === "RTP-Info") {
+                    LineTokens[1] = TokenziedResponseLines[cnt].substr(9);
+                    let RTPInfoTokens = LineTokens[1].split(",");
+                    RtspResponseData.RTPInfoList = [];
+                    for (cnt1 = 0; cnt1 < RTPInfoTokens.length; cnt1++) {
+                        let RtpTokens = RTPInfoTokens[cnt1].split(";");
+                        let RtpInfo = {};
+                        for (let cnt2 = 0; cnt2 < RtpTokens.length; cnt2++) {
+                            let poss = RtpTokens[cnt2].search("url=");
+                            if (poss !== -1) {
+                                RtpInfo.URL = RtpTokens[cnt2].substr(poss + 4)
+                            }
+                            poss = RtpTokens[cnt2].search("seq=");
+                            if (poss !== -1) {
+                                RtpInfo.Seq = parseInt(RtpTokens[cnt2].substr(poss + 4))
+                            }
+                        }
+                        RtspResponseData.RTPInfoList.push(RtpInfo)
+                    }
+                }
+            }
+        } else if (RtspResponseData.ResponseCode === RTSP_STATE.UNAUTHORIZED) {
+            for (cnt = 1; cnt < TokenziedResponseLines.length; cnt++) {
+                LineTokens = TokenziedResponseLines[cnt].split(":");
+                if (LineTokens[0] === "CSeq") {
+                    RtspResponseData.CSeq = parseInt(LineTokens[1])
+                } else if (LineTokens[0] === "WWW-Authenticate") {
+                    let AuthTokens = LineTokens[1].split(",");
+                    for (cnt1 = 0; cnt1 < AuthTokens.length; cnt1++) {
+                        let pos = AuthTokens[cnt1].search("Digest realm=");
+                        if (pos !== -1) {
+                            ttt = AuthTokens[cnt1].substr(pos + 13);
+                            let realmtok = ttt.split('"');
+                            RtspResponseData.Realm = realmtok[1]
+                        }
+                        pos = AuthTokens[cnt1].search("nonce=");
+                        if (pos !== -1) {
+                            ttt = AuthTokens[cnt1].substr(pos + 6);
+                            let noncetok = ttt.split('"');
+                            RtspResponseData.Nonce = noncetok[1]
+                        }
+                    }
+                }
+            }
+        }
+        return RtspResponseData
+    }
+
+    /**
+     * 解析Describe信令
+     * @param message1
+     */
+    function parseDescribeResponse(message1) {
+        //console.log(message1)
+        let SDPData = {};
+        let Sessions = [];
+        SDPData.Sessions = Sessions;
+        let message = null;
+        if (message1.search("Content-Type: application/sdp") !== -1) {
+            let messageTok = message1.split("\r\n\r\n");
+            message = messageTok[1]
+        } else {
+            message = message1
+        }
+        let TokenziedDescribe = message.split("\r\n");
+        let mediaFound = false;
+        for (let cnt = 0; cnt < TokenziedDescribe.length; cnt++) {
+            let SDPLineTokens = TokenziedDescribe[cnt].split("=");
+            if (SDPLineTokens.length > 0) {
+                switch (SDPLineTokens[0]) {
+                    case"a":
+                        let aLineToken = SDPLineTokens[1].split(":");
+                        if (aLineToken.length > 1) {
+                            if (aLineToken[0] === "control") {
+                                let pos = TokenziedDescribe[cnt].search("control:");
+                                if (mediaFound === true) {
+                                    if (pos !== -1) {
+                                        SDPData.Sessions[SDPData.Sessions.length - 1].ControlURL = TokenziedDescribe[cnt].substr(pos + 8);
+                                        let trackPos = TokenziedDescribe[cnt].search("track");
+                                        SDPData.Sessions[SDPData.Sessions.length - 1].trackID = TokenziedDescribe[cnt].substr(trackPos);
+                                    }
+                                } else {
+                                    if (pos !== -1) {
+                                        SDPData.BaseURL = TokenziedDescribe[cnt].substr(pos + 8)
+                                    }
+                                }
+                            } else if (aLineToken[0] === "rtpmap") {
+                                //console.log(aLineToken)
+                                let rtpmapLine = aLineToken[1].split(" ");
+                                //console.log(rtpmapLine)
+                                SDPData.Sessions[SDPData.Sessions.length - 1].PayloadType = rtpmapLine[0];
+                                let MimeLine = rtpmapLine[1].split("/");
+                                SDPData.Sessions[SDPData.Sessions.length - 1].CodecMime = MimeLine[0];
+                                if (MimeLine.length > 1) {
+                                    SDPData.Sessions[SDPData.Sessions.length - 1].ClockFreq = MimeLine[1]
+                                }
+                            } else if (aLineToken[0] === "framesize") {
+                                let framesizeLine = aLineToken[1].split(" ");
+                                if (framesizeLine.length > 1) {
+                                    let framesizeinf = framesizeLine[1].split("-");
+                                    SDPData.Sessions[SDPData.Sessions.length - 1].Width = framesizeinf[0];
+                                    SDPData.Sessions[SDPData.Sessions.length - 1].Height = framesizeinf[1]
+                                }
+                            } else if (aLineToken[0] === "framerate") {
+                                SDPData.Sessions[SDPData.Sessions.length - 1].Framerate = aLineToken[1]
+                            } else if (aLineToken[0] === "fmtp") {
+                                let sessLine = TokenziedDescribe[cnt].split(" ");
+                                if (sessLine.length < 2) {
+                                    continue
+                                }
+                                for (let ii = 1; ii < sessLine.length; ii++) {
+                                    let sessToken = sessLine[ii].split(";");
+                                    let sessprmcnt = 0;
+                                    for (sessprmcnt = 0; sessprmcnt < sessToken.length; sessprmcnt++) {
+                                        let ppos = sessToken[sessprmcnt].search("mode=");
+                                        if (ppos !== -1) {
+                                            SDPData.Sessions[SDPData.Sessions.length - 1].mode = sessToken[sessprmcnt].substr(ppos + 5)
+                                        }
+                                        ppos = sessToken[sessprmcnt].search("config=");
+                                        if (ppos !== -1) {
+                                            SDPData.Sessions[SDPData.Sessions.length - 1].config = sessToken[sessprmcnt].substr(ppos + 7);
+                                            AACCodecInfo.config = SDPData.Sessions[SDPData.Sessions.length - 1].config;
+                                            AACCodecInfo.clockFreq = SDPData.Sessions[SDPData.Sessions.length - 1].ClockFreq;
+                                            AACCodecInfo.bitrate = SDPData.Sessions[SDPData.Sessions.length - 1].Bitrate
+                                        }
+                                        ppos = sessToken[sessprmcnt].search("sprop-vps=");
+                                        if (ppos !== -1) {
+                                            SDPData.Sessions[SDPData.Sessions.length - 1].VPS = sessToken[sessprmcnt].substr(ppos + 10)
+                                        }
+                                        ppos = sessToken[sessprmcnt].search("sprop-sps=");
+                                        if (ppos !== -1) {
+                                            SDPData.Sessions[SDPData.Sessions.length - 1].SPS = sessToken[sessprmcnt].substr(ppos + 10)
+                                        }
+                                        ppos = sessToken[sessprmcnt].search("sprop-pps=");
+                                        if (ppos !== -1) {
+                                            SDPData.Sessions[SDPData.Sessions.length - 1].PPS = sessToken[sessprmcnt].substr(ppos + 10)
+                                        }
+                                        ppos = sessToken[sessprmcnt].search("sprop-parameter-sets=");
+                                        if (ppos !== -1) {
+                                            let SPSPPS = sessToken[sessprmcnt].substr(ppos + 21);
+                                            let SPSPPSTokenized = SPSPPS.split(",");
+                                            if (SPSPPSTokenized.length > 1) {
+                                                SDPData.Sessions[SDPData.Sessions.length - 1].SPS = SPSPPSTokenized[0];
+                                                SDPData.Sessions[SDPData.Sessions.length - 1].PPS = SPSPPSTokenized[1]
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    case"m":
+                        let mLineToken = SDPLineTokens[1].split(" ");
+                        let Session = {};
+                        Session.Type = mLineToken[0];
+                        Session.Port = mLineToken[1];
+                        Session.Payload = mLineToken[3];
+                        SDPData.Sessions.push(Session);
+                        mediaFound = true;
+                        break;
+                    case"b":
+                        if (mediaFound === true) {
+                            let bLineToken = SDPLineTokens[1].split(":");
+                            SDPData.Sessions[SDPData.Sessions.length - 1].Bitrate = bLineToken[1]
+                        }
+                        break
+                }
+            }
+        }
+        return SDPData
+    };
+
+    function formDigest(message) {
+        let {Nonce, Realm} = message;
+        //Realm = '54c415830ec4';
+        //Nonce = 'fb01c51948704e59eb5a474b33caff8b';
+        let user = {
+            username: username,
+            password: password,
+        };
+
+        let hex1 = hex_md5(user.username + ":" + Realm + ":" + user.password);
+        let hex2 = hex_md5(currentState.toUpperCase() + ":" + rtspURL);
+        let responce = hex_md5(hex1 + ":" + Nonce + ":" + hex2);
+        Authentication = 'Authorization: Digest username="' + user.username + '", realm="' + Realm + '", nonce="' + Nonce + '",uri="' + rtspURL + '", response="' + responce + '"\r\n' + "Accept: application/sdp\r\n" + '\r\n';
+
+        return  currentState.toUpperCase() + " " + rtspURL + " RTSP/1.0\r\nCSeq: " + CSeq + "\r\n" + Authentication;
+    }
+
+    function getUser(rtspUrl) {
+        let user = rtspUrl.split('rtsp://')[1].split('@')[0];
+        let username = user.split(':')[0],
+            password = user.split(':')[1];
+        return {username, password};
+    }
+
+    function getBitStream() {
+        if(lastStreamTime === null) {
+            lastStreamTime = Date.now();
+        } else {
+            //console.log(Date.now() - lastStreamTime)
+            return Date.now() - lastStreamTime < 5000;
+        }
+    }
+}
+
+export default WebSocketServer;

+ 566 - 0
public/static/src/workerManager.js

@@ -0,0 +1,566 @@
+import VideoMediaSource from './MediaSource.js';
+import MP4Remux from './MP4Remux.js';
+import IvsDrawer from './ivsDrawer.js';
+import ROIDrawer from './ROIDrawer.js';
+
+let videoRenderer = null;
+
+function WorkerManager() {
+    let videoWorker;
+    let SDPInfo;
+    let messageArray = [];
+    let rtpStackCount = 0;
+    let videoElement = null;
+    let canvasElement = null;
+    let videoMS = null;
+
+    const rtpStackCheckNum = 10;
+
+    let codecInfo = null;
+    let initSegmentData = null;
+    let mediaInfo = {
+        id: 1,
+        samples: null,
+        baseMediaDecodeTime: 0
+    };
+    let numBox = 1;
+    let mediaSegNum = 0; //用于记录缓存的box个数
+    let mediaFrameData = null; //用于缓存未喂入mse的box
+    let mediaFrameSize = 0; //mediaFrameData的大小
+    let preBaseDecodeTime = 0; //上一个解码时间
+    let curBaseDecodeTime = 0; //从第一帧到当前帧的持续时间
+    let mediaSegmentData = null; //MP4化的数据
+    let sequenseNum = 1;
+
+    let mp4Remux;
+
+    let firstTimeStamp = null; //第一个视频帧的时间戳
+    let SEIinfo = null;
+    let ivsDrawer = null;
+    let info = null;
+    let MAX_INFO = 25; // 限制info最大长度
+    let startDrawIVS = false;
+    let lastTime = 0;
+    let onCanplayCallback = null;
+    let ROIdrawer = null;
+    let initialCompleted = false;
+    let onInitialCompletedCallback = null;
+    let SuperRender = null;
+    function constructor() {
+
+    }
+
+    constructor.prototype = {
+        init(video,canvas, ROI) {
+            videoWorker = new Worker('./src/videoWorker.js');
+            videoWorker.onmessage = videoWorkerMessage;
+            videoElement = video;
+            canvasElement = canvas;
+
+            mp4Remux = new MP4Remux();
+            mp4Remux.init();
+
+            SEIinfo = new IVSQueue();
+            info = new LruCache(MAX_INFO);
+            ivsDrawer = new IvsDrawer(canvasElement);
+            ROIdrawer = new ROIDrawer(ROI);
+
+            SuperRender = new SuperRender2('videoCanvas');
+
+            // videoRenderer = new YUVPlayer(canvasElement);
+            // videoRenderer.startRendering();
+        },
+
+        sendSdpInfo(SDPinfo) {
+            SDPInfo = SDPinfo;
+            // console.log(SDPinfo)
+            let message = {
+                type: "sdpInfo",
+                data: {
+                    sdpInfo: SDPInfo
+                }
+            };
+            videoWorker.postMessage(message);
+        },
+
+        parseRtpData(rtspinterleave, rtpheader, rtpPacketArray) {
+            // console.log(rtspinterleave)
+            // console.log( rtpheader)
+            // //console.log(rtpPacketArray)
+            // console.log(rtpheader[3])
+
+            let mediaType = rtspinterleave[1];
+            let idx = parseInt(mediaType / 2, 10);
+            let markerBitHex = 128;
+            let message = {
+                type: "rtpData",
+                data: {rtspInterleave: rtspinterleave, header: rtpheader, payload: rtpPacketArray}
+            };
+            //console.log(rtspinterleave)
+            //console.log('idx: ',idx)
+
+            if(idx !== 0) {
+                console.log('idx: ',rtspinterleave);
+                //console.log(SDPInfo)
+                return;
+            }
+            switch (SDPInfo[idx].codecName) {
+                case "H264":
+                case 'H265':
+                    messageArray.push(message);
+                    if (rtpStackCount >= rtpStackCheckNum || (rtpheader[1] & markerBitHex) === markerBitHex) {
+                        if((rtpheader[1] & markerBitHex) === markerBitHex) {
+                            //onsole.log('遇到终止位: ' + rtpheader[1])
+                        }
+                        let sendMessage = {type: "rtpDataArray", data: messageArray};
+                        if (videoWorker) {
+                            videoWorker.postMessage(sendMessage)
+                        }
+                        sendMessage = null;
+                        messageArray = [];
+                        rtpStackCount = 0
+                        //console.log('1111111111')
+                    } else {
+                        rtpStackCount++
+                    }
+                    break;
+                default:
+            }
+        },
+
+        /**
+         * 更新需要绘制的其它信息
+         * @param obj
+         */
+        updateInfo(obj) {
+            //if((obj.name !== '') && (obj.name !== undefined) && (obj.name !== null)) {
+                info.set(obj.id, obj.name);
+            //}
+        },
+
+        resetROI() {
+            ROIdrawer && ROIdrawer.reset();
+        },
+
+        setROI(data) {
+            ROIdrawer && ROIdrawer.setROI(data);
+        },
+
+        getROIData() {
+            if(ROIdrawer) {
+                return ROIdrawer.getROIData();
+            }
+            return null;
+        },
+
+        setPolygonNum(num) {
+            ROIdrawer && ROIdrawer.setPolygonNum(num);
+        },
+
+        setEventCallBack(event, callback) {
+            switch (event) {
+                case 'canplay':
+                    onCanplayCallback = callback;
+                    break;
+                case 'initialCompleted':
+                    onInitialCompletedCallback = callback;
+                    break;
+                case 'ROIFinished':
+                    ROIdrawer.setROIFinishedCallback(callback);
+                    break;
+                default:
+                    break;
+            }
+        },
+
+        terminate() {
+            videoWorker.terminate();
+            ivsDrawer.terminate();
+            ROIdrawer.terminate();
+            info.clear();
+            startDrawIVS = false;
+            window.onresize = null;
+            if(videoMS) {
+                videoMS.close();
+                videoMS = null;
+            }
+        }
+    }
+
+    return new constructor();
+
+    function videoWorkerMessage(event) {
+        let videoMessage = event.data;
+        let type = videoMessage.type;
+        //console.log(videoMessage.data)
+        switch (type) {
+            // case 'codecInfo': //设置codecType
+            //     break;
+            // case 'initSegment': //第一个buffer,设置SPS等
+            case 'videoInit'://合并codecInfo和initSegment
+                console.log(videoMessage)
+                codecInfo = videoMessage.data.codecInfo;
+                //console.log(videoMessage.data)
+                initSegmentData = mp4Remux.initSegment(videoMessage.data.initSegmentData);
+//console.log(initSegmentData)
+                videoMS = new VideoMediaSource(videoElement);
+                videoMS.CodecInfo = codecInfo;
+                videoMS.InitSegment = initSegmentData;
+                //console.log(videoMS.CodecInfo, videoMS.InitSegment)
+                videoMS.init();
+                videoMS.onCanplayCallback(()=>{
+                    ivsDrawer.cover(videoElement);
+                    onCanplayCallback && onCanplayCallback();
+                    if(!initialCompleted) {
+                        ROIdrawer.cover(videoElement);
+                        onInitialCompletedCallback && onInitialCompletedCallback();
+                        initialCompleted = true;
+                    }
+                });
+
+                elementResizeCallback(videoElement, ()=>{
+                    ivsDrawer.cover(videoElement);
+                    ROIdrawer.cover(videoElement);
+                    ROIdrawer.redrawROI();
+                });
+                break;
+            case 'firstvideoTimeStamp':
+                firstTimeStamp = videoMessage.data;
+
+                videoMS.setFirstTimeStamp(firstTimeStamp);
+                //videoMS.setDurationChangeCallBack(drawIVS);
+
+                console.log('first frame timestamp: ', firstTimeStamp);
+                startDrawIVS = true;
+                window.requestAnimationFrame(draw);
+                break;
+            case 'videoTimeStamp'://时间戳,用于智能同步
+                //videoMS.setFirstTimeStamp(videoMessage.data);
+                //console.log('frame timestamp: ', videoMessage.data);
+
+                //console.log('npt: ', ( videoMessage.data - firstTimeStamp)/90000)
+                break;
+            case 'mediaSample': //用于设置baseMediaDecodeTime
+                if(mediaInfo.samples == null) {
+                    mediaInfo.samples = new Array(numBox);
+                }
+                //console.log('frameDuration: ' + videoMessage.data.frameDuration)
+                curBaseDecodeTime += videoMessage.data.frameDuration;
+
+                mediaInfo.samples[mediaSegNum++] = videoMessage.data;
+                break;
+            case 'videoRender': //视频数据
+                //缓存该segment数据
+                let tempBuffer = new Uint8Array(videoMessage.data.length + mediaFrameSize);
+                if(mediaFrameSize !== 0) {
+                    tempBuffer.set(mediaFrameData);
+                }
+                //console.log(videoMessage)
+                tempBuffer.set(videoMessage.data, mediaFrameSize);
+                mediaFrameData = tempBuffer;
+                mediaFrameSize = mediaFrameData.length;
+
+                if(mediaSegNum % numBox === 0 && mediaSegNum !== 0) {
+                    if (sequenseNum === 1) {
+                        mediaInfo.baseMediaDecodeTime = 0
+                    } else {
+                        mediaInfo.baseMediaDecodeTime = preBaseDecodeTime;
+                    }
+                    preBaseDecodeTime = curBaseDecodeTime;
+
+//console.log(mediaInfo);
+                    mediaSegmentData = mp4Remux.mediaSegment(sequenseNum, mediaInfo, mediaFrameData);
+                    sequenseNum++;
+                    mediaSegNum = 0;
+                    mediaFrameData = null;
+                    mediaFrameSize = 0;
+
+                    if (videoMS !== null) {
+                        //console.log(mediaSegmentData)
+                        videoMS.setMediaSegment(mediaSegmentData)
+                    } else {
+
+                    }
+                }
+                break;
+            case 'YUVData'://FFMPEG解码的数据
+                //draw(videoMessage.data);
+                //yuv2canvas(videoMessage.data.data, videoMessage.data.width, videoMessage.data.height,canvasElement)
+               //setTimeout(()=> {
+                   var data = videoMessage.data.frameData.data;
+                   var width = videoMessage.data.frameData.width;
+                   var height = videoMessage.data.frameData.height;
+
+                var lumaSize = width * height;
+                var chromaSize = lumaSize >> 2;
+
+                    var ydata = new Uint8Array(data.subarray(0, lumaSize));
+                    var udata = new Uint8Array(data.subarray(lumaSize, lumaSize + chromaSize));
+                    var vdata = new Uint8Array(data.subarray(lumaSize + chromaSize, lumaSize + 2 * chromaSize));
+
+                   SuperRender.SR_DisplayFrameData(width, height, ydata, udata, vdata);
+                   drawIVS(videoMessage.data.timeStamp);
+               //}, 1000);
+
+                break;
+            //case 'YUVData'://FFMPEG解码的数据
+            //    //console.log(videoMessage.data)
+            //    drawCanvas(videoMessage.data);
+            //    //yuv2canvas(videoMessage.data.data, videoMessage.data.width, videoMessage.data.height,canvasElement)
+
+                break;
+            case 'SEI': //处理SEI信息
+                //console.log('SEI timestamp: ', videoMessage.data.timestamp);
+                //console.log('SEI-npt: ', (videoMessage.data.timestamp - firstTimeStamp)/90000)
+                if(videoMessage.data.ivs !== null) {
+                    let ivs = [];
+                    videoMessage.data.ivs.map((content, k) => {
+                        if(content.state) { //state=1, 绘制该信息
+                            ivs.push(content);
+                        }else { //state=0, 清除info中对应的id:name
+                            // let id = content.id;
+                            // console.log('删除', id, info[id]);
+                            // delete info[id];
+                            // console.log(info)
+                        }
+                    });
+
+                    //console.log('PUSH SEI: ', videoMessage.data.timestamp)
+                    SEIinfo.push(videoMessage.data.timestamp, ivs);
+
+                    //console.log(videoMessage.data.timestamp - lastTime)
+                    //lastTime = videoMessage.data.timestamp;
+                }
+                //console.log('timestamp: ', videoMessage.data.timestamp)
+                //console.log(SEIinfo)
+                break;
+            default:
+                console.log('暂不支持其他类型');
+                break;
+        }
+    }
+
+    function draw() {
+        let timestamp = parseInt((videoElement.currentTime.toFixed(2) * 90000).toFixed(0)) + firstTimeStamp + 3600;//
+        drawIVS(timestamp);
+        if(startDrawIVS) {
+            window.requestAnimationFrame(draw);
+        }
+    }
+
+    /**
+     * 根据时间戳获取相应的ivs信息
+     * @param timestamp 当前帧的时间戳
+     * @returns {*} ivs信息
+     */
+    function getIVS(timestamp) {
+        let preNode = null;
+        let nextNode = null;
+
+        preNode = SEIinfo.shift();
+        nextNode = SEIinfo.top();
+        while((preNode !== undefined) && (preNode !== null)) {
+            if(preNode[0] > timestamp) {
+                SEIinfo.unshift(preNode);
+                //console.log('SEI时间大于video: ', preNode[0], timestamp);
+                return null;
+            } else if(preNode[0] === timestamp) {
+                return preNode;
+            } else {
+
+                if(nextNode === undefined || nextNode === null) {
+                    console.log('last ivs info: ', timestamp, preNode[0], SEIinfo);
+                    //console.log(preNode[0] - lastTime);
+                    //lastTime = preNode[0];
+                    if(timestamp - preNode[0] < 3600) {
+                        return preNode;//最后一个node
+                    }
+                    return null;
+                }
+                if(nextNode[0] > timestamp) {
+                    // console.log('video time: ', timestamp, preNode[0], SEIinfo.length());
+                    // if(SEIinfo.length()) {
+                    //     SEIinfo.map((v, k)=>{
+                    //         console.log(v);
+                    //     });
+                    // }
+                    //console.log(preNode[0] - lastTime);
+                    //lastTime = preNode[0];
+                    return preNode;
+                } else if(nextNode[0] === timestamp){
+
+                    nextNode = SEIinfo.shift();
+                    //console.log('video time: ', timestamp, nextNode[0], SEIinfo);
+                    //console.log(nextNode[0] - lastTime);
+                    //lastTime = nextNode[0];
+                    return nextNode;
+                } else {
+                    preNode = SEIinfo.shift();
+                    nextNode = SEIinfo.top();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 绘制智能信息
+     * @param timestamp
+     */
+    function drawIVS(timestamp) {
+        let data = getIVS(timestamp);
+        if(data === undefined || data === null) {
+            //清空画布
+            //ivsDrawer.clearCanvas();
+            if(!SEIinfo.length() || (SEIinfo.length() && (SEIinfo.list[0][0] - timestamp) > 3600)) {
+                ivsDrawer.clearCanvas();
+            }
+        }else {
+            //console.log('GET SEI: ', data[0], ' videoTimestamp', timestamp);
+            data = data[1];
+            //console.log(info.map.length)
+            if(info.map.length > MAX_INFO) {
+                console.log('info length: ', info.map.length);
+            }
+            //获取鹰眼信息
+            data.map((content, k) =>{
+                let result = info.get(content.id);
+                if(result !== undefined && result !== null) {
+                    data[k].text = result.value;
+                }
+            });
+            ivsDrawer.draw(data, timestamp);
+        }
+    }
+}
+
+function elementResizeCallback(someElement, callback) {
+    const ro = new ResizeObserver( entries => {
+        // for (let entry of entries) {
+        //     const cr = entry.contentRect;
+        //     console.log('Element:', entry.target);
+        //     console.log(`Element size: ${cr.width}px x ${cr.height}px`);
+        // }
+        callback();
+    });
+    ro.observe(someElement);
+}
+
+function yuv2canvas(yuv, width, height, canvas) {
+
+    canvas.width = width;
+    canvas.height = height;
+
+    var context    = canvas.getContext("2d");
+    var output     = context.createImageData(width, height);
+    var outputData = output.data;
+
+    var yOffset = 0;
+    var uOffset = width * height;
+    var vOffset = width * height + (width*height)/4;
+    for (var h=0; h<height; h++) {
+        for (var w=0; w<width; w++) {
+            var ypos = w + h * width + yOffset;
+
+            var upos = (w>>1) + (h>>1) * width/2 + uOffset;
+            var vpos = (w>>1) + (h>>1) * width/2 + vOffset;
+
+            var Y = yuv[ypos];
+            var U = yuv[upos] - 128;
+            var V = yuv[vpos] - 128;
+
+            var R =  (Y + 1.371*V);
+            var G =  (Y - 0.698*V - 0.336*U);
+            var B =  (Y + 1.732*U);
+
+            var outputData_pos = w*4 + width*h*4;
+            outputData[0+outputData_pos] = R;
+            outputData[1+outputData_pos] = G;
+            outputData[2+outputData_pos] = B;
+            outputData[3+outputData_pos] = 255;
+        }
+    }
+
+    context.putImageData(output, 0, 0);
+}
+
+function drawCanvas(frameData) {
+    if (frameData !== null && videoRenderer !== null) {
+        //console.log(frameData)
+        videoRenderer.draw(frameData.data, frameData.width, frameData.height, frameData.codecType, frameData.frameType, frameData.timeStamp)
+
+    }
+}
+
+class IVSQueue {
+
+    constructor() {
+        this.list = [];
+    }
+
+    push(timestamp, ivs) {
+        this.list.push([timestamp, ivs]);
+    }
+
+    shift() {
+        let tmp = this.list.shift();
+        return tmp;
+    }
+
+    unshift(node) {
+        this.list.unshift(node);
+    }
+
+    top() {
+
+        let tmp = this.list[0];
+        return tmp;
+    }
+
+    length() {
+        return this.list.length;
+    }
+
+    map(v,k) {
+        return this.list.map(v,k);
+    }
+}
+
+class LruCache {
+    constructor(limit) {
+        this.limit = limit || 20;
+        this.map = [];
+    }
+    get(key) {
+        return this._search(key);
+    }
+    set(key, value) {
+        let result  = this._search(key);
+        if(!result) {
+            this.map.unshift({
+                key: key,
+                value: value
+            });
+            if(this.map.length > this.limit) {
+                this.map.pop();
+            }
+        }
+    }
+
+    //每次查找将该元素置于队首
+    _search(key) {
+        for(let i = 0, length = this.map.length; i < length; i++) {
+            if(this.map[i].key === key) {
+                let head = this.map.splice(i, 1);
+                this.map.unshift(head[0]);
+                return head[0];
+            }
+        }
+        return null;
+    }
+
+    clear() {
+        this.map = [];
+    }
+}
+export default WorkerManager;

+ 87 - 0
src/App.vue

@@ -0,0 +1,87 @@
+<template>
+    <div id="app" @mousemove="mousemove">
+        <nav id="nav" :style="{ height: navHeight + 'px' }">
+            <router-link to="/" class="a">首页</router-link>
+            <router-link to="/monitor" class="a">视频监控</router-link>
+            <router-link to="/biology" class="a">生物防控</router-link>
+            <router-link to="/robot" class="a">机器人巡查</router-link>
+            <router-link to="/environmentalProtection" class="a">环保监测</router-link>
+<!--            <router-link to="/userAdmin" class="a">人员管理</router-link>-->
+        </nav>
+        <header class="header">华腾石湾乡村振兴智能化管理平台</header>
+        <router-view />
+    </div>
+</template>
+
+<script>
+export default {
+    name: "app",
+    data() {
+        return {
+            navHeight: 0,
+        };
+    },
+    mounted() {
+        // this.init();
+    },
+    methods: {
+        init() {
+            this.$http.post('http://122.112.252.100:8088/publicNetwork/add')
+                    .then(res => {
+                        console.log(res);
+                    })
+        },
+        mousemove(e) {
+            if (e.y <= 80) {
+                this.navHeight = 70;
+            } else {
+                this.navHeight = 0;
+            }
+        },
+    },
+};
+</script>
+
+<style lang="scss">
+#app {
+    height: 100%;
+	min-width: 1600px;
+    max-width: 2000px;
+	/*min-height: 800px;*/
+   max-height: 1200px;
+	background-color: #060A2D;
+	display: flex;
+	flex-direction: column;
+    #nav {
+        width: 100%;
+        position: fixed;
+        background-color: #0e1e51;
+        color: #eee;
+        transition: height 0.4s;
+        overflow: hidden;
+        line-height: 80px;
+        padding-left: 30px;
+        z-index: 99;
+        .a {
+            color: #eee;
+            margin: 0 20px;
+            padding: 12px;
+            background-color: #142b77;
+            border-radius: 6px;
+        }
+    }
+    > .header {
+        // border: 1px solid #8877dd;
+        background: url(./assets/bg_header.png) no-repeat;
+        background-size: 100% 100%;
+        width: 100%;
+        height: 80px;
+        line-height: 50px;
+        // font-size: 1.3vw;
+        font-size: 22px;
+        font-weight: 600;
+        color: #fff;
+        text-align: center;
+    }
+}
+</style>

二進制
src/assets/bg_header.png


二進制
src/assets/box_bg.png


二進制
src/assets/box_bg1.png


二進制
src/assets/box_bg13.png


二進制
src/assets/box_bg2.png


二進制
src/assets/box_bg4.png


二進制
src/assets/box_bg5.png


二進制
src/assets/box_bg6.png


二進制
src/assets/chilun.png


+ 10 - 0
src/assets/css/reset.scss

@@ -0,0 +1,10 @@
+/* 公用scss */
+div,h1,h2,h3,h4,h5,h3,body,html,ul,li,p{margin: 0;padding: 0;}
+a{text-decoration: none;}
+li{list-style:none;} 
+/*css初始化完成*/ 
+html,body{
+    width: 100%;
+    height: 100%;
+    // overflow: hidden;
+}

二進制
src/assets/default.png


二進制
src/assets/f7dc79dbfa71373458cc8ee92b7053c.jpg


二進制
src/assets/icon_NH3.png


二進制
src/assets/icon_T.png


二進制
src/assets/icon_humidity.png


二進制
src/assets/icon_meter.png


二進制
src/assets/icon_ph.png


二進制
src/assets/robot.gif


二進制
src/assets/robot.mp4


二進制
src/assets/u12544.png


二進制
src/assets/u204.png


二進制
src/assets/u208.png


二進制
src/assets/u210.png


二進制
src/assets/u23.png


二進制
src/assets/u24.png


二進制
src/assets/u25.png


二進制
src/assets/u26.png


二進制
src/assets/u262.png


二進制
src/assets/u263.png


二進制
src/assets/u525.png


+ 7 - 0
src/assets/u532.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="57px" height="29px" xmlns="http://www.w3.org/2000/svg">
+  <g transform="matrix(1 0 0 1 -123 -728 )">
+    <path d="M 139.6 755.955218571911  L 123.732121546229 742.5  L 139.6 729.044781428089  L 139.6 736.33  L 179.5 736.33  L 179.5 748.67  L 139.6 748.67  L 139.6 755.955218571911  Z " fill-rule="nonzero" fill="#02a7f0" stroke="none" />
+    <path d="M 140.1 757  L 123 742.5  L 140.1 728  L 140.1 735.83  L 180 735.83  L 180 749.17  L 140.1 749.17  L 140.1 757  Z M 124.464243092458 742.5  L 139.1 754.910437143822  L 139.1 748.17  L 179 748.17  L 179 736.83  L 139.1 736.83  L 139.1 730.089562856178  L 124.464243092458 742.5  Z " fill-rule="nonzero" fill="#797979" stroke="none" />
+  </g>
+</svg>

二進制
src/assets/u62.png


二進制
src/assets/u861.png


二進制
src/assets/user.png


二進制
src/assets/user1.png


+ 329 - 0
src/components/imgDragScale.vue

@@ -0,0 +1,329 @@
+<template>
+    <!-- <div class="mapbox" id="mapbox" :style="'width:'+width+';height:'+height"> -->
+    <div class="mapbox" id="mapbox" style="width:100%;height:100%;">
+        <table class="imgbox" id="imgbox" :style="'top: '+imgtop+'px;left: '+imgleft+'px;'" border="0" cellpadding="0" cellspacing="0">
+            <tr style="width:100%;height:100%;">
+                <td style="position: relative;width:100%;height:100%;">
+<!--                    <canvas class="canvas" id="canvas"></canvas>-->
+                    <img id="backgroundImg" draggable="false" style="height: 560px;" :src="img" usemap="#Map"/>
+                        <!-- 图片点击 -->
+<!--                    <map name="Map" id="Map">-->
+<!--                        &lt;!&ndash; 1栋  &ndash;&gt;-->
+<!--                        <area shape="poly"  coords="969, 278, 999, 268, 943, 252, 927, 266" data-index="1-1"  href="#" alt="1栋1单元">-->
+<!--                        <area shape="poly" coords="928, 265, 944, 250, 891, 236, 876, 250"  data-index="1-2" href="#" alt="" />-->
+<!--                        <area shape="poly" coords="876, 250, 890, 235, 831, 221, 815, 233"   data-index="1-3" href="#" alt="" />-->
+<!--                        <area shape="poly" coords="816, 232, 832, 220, 783, 206, 763, 218"  data-index="1-4" href="#" alt="" />-->
+<!--                        &lt;!&ndash; 2栋  &ndash;&gt;-->
+<!--                        <area shape="poly" coords="921, 293, 952, 281, 899, 265, 876, 279"  data-index="2-1" href="#" alt="" />-->
+<!--                        <area shape="poly" coords="876, 278, 897, 264, 836, 246, 815, 260"  data-index="2-2" href="#" alt="" />-->
+<!--                        <area shape="poly" coords="815, 260, 835, 245, 782, 231, 760, 244"  data-index="2-3" href="#" alt="" />-->
+<!--                        <area shape="poly" coords=" 760, 244, 782, 231, 739, 218, 714, 230"  data-index="2-4" href="#" alt="" />-->
+<!--                        &lt;!&ndash; 3栋  &ndash;&gt;-->
+<!--                        <area shape="poly" coords="870, 308, 905, 296, 846, 278, 825 , 294"  data-index="3-1" href="#" alt="" />-->
+<!--                        <area shape="poly" coords="825 ,294, 846, 278, 792, 261, 776 , 277"  data-index="3-2" href="#" alt="" />-->
+<!--                        <area shape="poly" coords="776 ,277, 792, 261, 733, 242, 715 , 258"  data-index="3-3" href="#" alt="" />-->
+<!--                        <area shape="poly" coords="715 , 258, 733, 242, 689, 231, 666 , 243"  data-index="3-4" href="#" alt="" />-->
+<!--                    </map>-->
+                    <div class="box-content">
+                      <div :class="['next_box', position === '1-4' ? 'bg_red' : '']" @click="jump('1-4', 33)"></div>
+                      <div :class="['next_box', position === '1-3' ? 'bg_red' : '']" @click="jump('1-3', 31)"></div>
+                      <div :class="['next_box', position === '1-2' ? 'bg_red' : '']" @click="jump('1-2', 29)"></div>
+                      <div :class="['next_box', position === '1-1' ? 'bg_red' : '']" @click="jump('1-1', 27)"></div>
+                    </div>
+                    <div class="box-content box-content1">
+                      <div :class="['next_box', position === '2-4' ? 'bg_red' : '']" @click="jump('2-4', null)"></div>
+                      <div :class="['next_box', position === '2-3' ? 'bg_red' : '']" @click="jump('2-3', 40)"></div>
+                      <div :class="['next_box', position === '2-2' ? 'bg_red' : '']" @click="jump('2-2', 38)"></div>
+                      <div :class="['next_box', position === '2-1' ? 'bg_red' : '']" @click="jump('2-1', 36)"></div>
+                    </div>
+                    <div class="box-content box-content2">
+                      <div :class="['next_box', position === '3-4' ? 'bg_red' : '']" @click="jump('3-4', 47)"></div>
+                      <div :class="['next_box', position === '3-3' ? 'bg_red' : '']" @click="jump('3-3', 45)"></div>
+                      <div :class="['next_box', position === '3-2' ? 'bg_red' : '']" @click="jump('3-2', 44)"></div>
+                      <div :class="['next_box', position === '3-1' ? 'bg_red' : '']" @click="jump('3-1', 43)"></div>
+                    </div>
+<!--                    <canvas class="canvas" id="canvas"></canvas>-->
+                    <!-- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="svg" :style="'height: '+imgheight+'%;'">
+                        <polyline points="417,245 457,235 629,297 589,307 417,245"
+                        style="fill:#96E2FA66;" />
+                    </svg>
+                    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="svg" :style="'height: '+imgheight+'%;'">
+                        <polyline points="467,230 507,220 679,282 639,292 467,230"
+                        style="fill:#96E2FA66;" />
+                    </svg> -->
+                </td>
+            </tr>
+            <!-- style="fill:#f008;stroke:black;stroke-width:3" /> -->
+
+        </table>
+    </div>
+</template>
+<script>
+export default {
+    props: {
+        //图片路径 imgDragScale
+        img: {
+            type: String,
+            default: "",
+        },
+        //盒子的宽
+        // width: {
+        //     type: String,
+        //     default: "100%",
+        // },
+        // //盒子的高
+        // height: {
+        //     type: String,
+        //     default: "100%",
+        // },
+    },
+    data() {
+        return {
+            imgtop: 0, //图片距离左边的距离
+            imgleft: 0, //图片距离上边的距离
+            imgheight: 100, //图片高度百分比
+            DownUp: false, //用来判断鼠标是否长按
+            position: '',
+        };
+    },
+    mounted() {
+        document.onmousemove = this.mouseMove;
+        //等待图片加载
+        setTimeout((e) => {
+            this.getbackgroundImgWidth();
+        }, 500);
+
+    },
+    beforeDestroy() {
+        document.onmousemove = null;
+    },
+    methods: {
+        //鼠标按下
+        holdDown() {
+            this.DownUp = true;
+        },
+        //鼠标松开
+        holdUp() {
+            this.DownUp = false;
+        },
+        //ev:鼠标对象,id:盒子的id 判断鼠标是否在盒子内
+        inBoxIsoutbox(id, ev = event || window.event) {
+            let map = document.getElementById(id);
+            if (
+                this.mousePosition(ev).x > map.offsetLeft + map.offsetWidth ||
+                this.mousePosition(ev).x < map.offsetLeft ||
+                this.mousePosition(ev).y > map.offsetTop + map.offsetHeight ||
+                this.mousePosition(ev).y < map.offsetTop
+            ) {
+                return false;
+            } else {
+                return true;
+            }
+        },
+        //兼容后,返回X,Y
+        mousePosition(ev) {
+            if (ev.pageX || ev.pageY) {
+                return { x: ev.pageX, y: ev.pageY };
+            }
+            return {
+                x:
+                    ev.clientX +
+                    document.body.scrollLeft -
+                    document.body.clientLeft,
+                y:
+                    ev.clientY +
+                    document.body.scrollTop -
+                    document.body.clientTop,
+            };
+        },
+        // 鼠标移动触发该方法
+        mouseMove(ev) {
+            ev = ev || window.event;
+            if (this.inBoxIsoutbox("mapbox", ev)) {
+                // 鼠标在盒子内
+                this.runWheel(true);
+            } else {
+                // 鼠标在盒子外
+                this.runWheel(false);
+                this.holdUp();
+            }
+            if (this.DownUp) {
+                // 鼠标长按时改变图片位置
+                this.imgtop = this.imgtop + ev.movementY;
+                this.imgleft = this.imgleft + ev.movementX;
+            }
+        },
+        // 开启监听鼠标滑轮
+        runWheel(isWheel) {
+            //判断依据是 是否在盒子内部
+            //true 就是开启
+            if (isWheel) {
+                document.documentElement.style.overflow = "hidden";
+                //兼容性写法,该函数也是网上别人写的,不过找不到出处了,蛮好的,所有我也没有必要修改了
+                //判断鼠标滚轮滚动方向
+                if (window.addEventListener) {
+                    //FF,火狐浏览器会识别该方法
+                    window.addEventListener(
+                        "DOMMouseScroll",
+                        this.wheel,
+                        false
+                    );
+                }
+                window.onmousewheel = document.onmousewheel = this.wheel; //W3C
+            } else {
+                //false就是关闭
+                document.documentElement.style.overflow = "";
+                if (window.addEventListener) {
+                    //FF,火狐浏览器会识别该方法
+                    window.addEventListener("DOMMouseScroll", null, false);
+                }
+                window.onmousewheel = document.onmousewheel = null; //W3C
+            }
+        },
+        //统一处理滚轮滚动事件,中间件
+        wheel(event) {
+            let delta = 0;
+            if (!event) event = window.event;
+            if (event.wheelDelta) {
+                //IE、chrome浏览器使用的是wheelDelta,并且值为“正负120”
+                delta = event.wheelDelta / 120;
+                if (window.opera) delta = -delta; //因为IE、chrome等向下滚动是负值,FF是正值,为了处理一致性,在此取反处理
+            } else if (event.detail) {
+                //FF浏览器使用的是detail,其值为“正负3”
+                delta = -event.detail / 3;
+            }
+            if (delta) this.handle(delta);
+        },
+        //上下滚动时的具体处理函数
+        handle(delta) {
+            if (delta < 0) {
+                //向下滚动
+                if (this.imgheight > 10) {
+                    this.imgheight = this.imgheight - 1;
+                }
+            } else {
+                //向上滚动
+                if (this.imgheight < 500) {
+                    this.imgheight = this.imgheight + 1;
+                }
+            }
+            // let arr = [];
+            // let updateArr = [];
+            // let pic = document.getElementsByTagName('area');
+            // // console.log(pic);
+            // pic.forEach(item => {
+            //     var coordsArray = item.getAttribute('coords').split(',');
+            //     // console.log(coordsArray);
+            //     arr.push(coordsArray);
+            //     // coordsArray.forEach(e => {
+            //     //     console.log(e);
+            //     // });
+            // });
+            // arr.forEach(item => {
+            //   for(var i = 0; i < item.length; i++) {
+            //     item[i] = item[i] * this.imgheight / 100;
+            //   }
+            //   updateArr.push(item);
+            // });
+            // this.updateCoord(updateArr);
+            // // console.log(arr);
+            // // console.log(updateArr);
+        },
+        updateCoord(arr) {
+          let pic =  document.getElementsByTagName('area');
+          arr.forEach((item, i) => {
+            let coords = item.join(',');
+            pic[i].setAttribute('coords', coords);
+            console.log(pic[i].getAttribute('coords'));
+          });
+        },
+        //获取背景图片的宽度或高度,和实际距离相除得到 系数distanceCoefficient
+        getbackgroundImgWidth() {
+            if (this.actualDistanceWidth !== 0) {
+                let backgroundImg = document.getElementById("backgroundImg");
+                this.distanceCoefficient =
+                    backgroundImg.width / this.actualDistanceWidth;
+            } else if (this.actualDistanceHeight !== 0) {
+                let backgroundImg = document.getElementById("backgroundImg");
+                this.distanceCoefficient =
+                    backgroundImg.height / this.actualDistanceWidth;
+            }
+        },
+        example(e) {
+          console.log(e);
+            var ev = e || window.event;
+            var target = ev.target || ev.srcElement;
+            if(target.nodeName.toLowerCase() == 'area') {
+                let text = target.getAttribute('data-index');
+               this.$emit('openVideo', text);
+            } else {
+              return;
+            }
+            // console.log(dom);
+
+        },
+        jump(position, id) {
+          let params = {
+            position: position,
+            id: id
+          };
+          // this.position = data;
+          this.$emit('openVideo', params);
+        }
+    },
+};
+</script>
+<style scoped>
+.mapbox {
+    overflow: hidden;
+    position: relative;
+    text-align: center;
+    box-sizing: border-box;
+}
+.imgbox {
+    width:100%;
+    height:100%;
+    position: absolute;
+}
+.imgbox img {
+    cursor: pointer;
+}
+.canvas {
+    cursor: pointer;
+    z-index: 1;
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+}
+  .box-content {
+    width: 30px;
+    height: 220px;
+    /*transform: skewX(45deg);*/
+    transform: rotate(-72.5deg);
+    position: absolute;
+    top: 130px;
+    left: 860px;
+    /*z-index: 99;*/
+  }
+  .box-content1 {
+    top: 146px;
+    left: 811px;
+  }
+  .box-content2 {
+    top: 161px;
+    left: 760px;
+  }
+  .next_box {
+    width: 30px;
+    height: 24%;
+    cursor: pointer;
+    background-color: white;
+    opacity: .5;
+    margin-bottom: 2px;
+  }
+  .bg_red {
+    background-color: red;
+  }
+</style>

+ 411 - 0
src/components/robotCanvas.vue

@@ -0,0 +1,411 @@
+<template>
+  <div class="scrollView">
+    <div class="room-grid">
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+    </div>
+    <div class="room-grid">
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+    </div>
+    <div class="room-grid">
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="room-list">
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+        <div class="room"></div>
+      </div>
+      <div class="line"></div>
+      <div class="line1"></div>
+      <div class="line line-left1"></div>
+      <div class="line line-center1"></div>
+      <div class="line1 line-center2"></div>
+      <div class="line line-center3"></div>
+      <div class="line2"></div>
+      <div class="line line-center4"></div>
+      <div class="line1 line-center5"></div>
+      <div class="line line-center6"></div>
+      <div class="line line-right1"></div>
+      <div class="line1 line-right2"></div>
+      <div class="line line-right3"></div>
+      <div class="line2 line2-left"></div>
+      <div class="line2 line2-right"></div>
+      <div class="line3"></div>
+      <div class="line3 line3-bottom"></div>
+      <div class="line3 line3-right-top"></div>
+      <div class="line3 line3-right-bottom"></div>
+      <div class="line4"></div>
+      <div class="line4 line4-bottom"></div>
+    </div>
+    <div id="drawPanel">
+
+    </div>
+  </div>
+</template>
+
+<script>
+  import drawingBoard from '../utils/drawingBoard';
+  export default {
+    name: "robotCanvas",
+    methods: {
+      goUp() {
+        if(window._drawingBoard.valTrue) {
+          this.$message('请先停止机器人自动操作!')
+        } else {
+          // window._drawingBoard.GoUp();
+          this.$message('因不可抗力因素,暂时无法使用!');
+        }
+      },
+      goLeft() {
+        if(window._drawingBoard.valTrue) {
+          this.$message('请先停止机器人自动操作!')
+        } else {
+          // window._drawingBoard.GoLeft();
+          this.$message('因不可抗力因素,暂时无法使用!');
+        }
+      },
+      goRight() {
+        if(window._drawingBoard.valTrue) {
+          this.$message('请先停止机器人自动操作!')
+        } else {
+          // window._drawingBoard.GoRight();
+          this.$message('因不可抗力因素,暂时无法使用!');
+        }
+      },
+      goBottom() {
+        if(window._drawingBoard.valTrue) {
+          this.$message('请先停止机器人自动操作!')
+        } else {
+          // window._drawingBoard.GoDown();
+          this.$message('因不可抗力因素,暂时无法使用!');
+        }
+      },
+      startInterval() {
+        window._drawingBoard.startInterval();
+      },
+      stopInterval() {
+        // window._drawingBoard.stopInterval();
+      }
+    },
+    created() {
+      window._drawingBoard = new drawingBoard();
+    },
+    mounted() {
+      //DOMContentLoaded
+      // document.addEventListener('load', function() {
+        // console.log(window._drawingBoard);
+        window._drawingBoard.init({
+          drawPanel: "drawPanel",
+          //backgroundImg: "map.png",
+          width:1200,
+          height:270,
+          penColor: "#03A9F4",
+          lineWidth:3, //路径宽度 2-6
+          assist: true, //是否开启辅助线
+        }, function(e) {
+          // console.log(e);
+        });
+
+      // })
+    }
+  }
+</script>
+
+<style scoped>
+  * {
+    padding: 0;
+    margin: 0;
+  }
+  #drawPanel{
+    outline: 2px solid #000;
+
+  }
+  .scrollView {
+    margin: 0 auto;
+    position: relative;
+    width:1200px;
+    height:270px;
+    display:flex;
+    flex-direction:column;
+    justify-content:space-between;
+  }
+  .menuListView{
+
+    margin: 1em;
+    text-align: center;
+  }
+  .menuList {
+    position: relative;
+    display: inline-block;
+    vertical-align: middle;
+  }
+  .menuList>button{
+    position: relative;
+    display: inline-block;
+    float: left;
+    padding: 6px 12px;
+    margin-bottom: 0;
+    font-size: 13px;
+    font-weight: 400;
+    /* line-height: 1.42857143; */
+    text-align: center;
+    white-space: nowrap;
+    vertical-align: middle;
+    -ms-touch-action: manipulation;
+    touch-action: manipulation;
+    cursor: pointer;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    background-image: none;
+    border: 1px solid transparent;
+    border-radius: 4px;
+    color: #333;
+    background-color: #fff;
+    border-color: #ccc;
+    outline: none;
+    transition: all 0.3s;
+  }
+  .menuList>button:hover{
+    opacity:0.7;
+    border-color: #AAA;
+    background-image: -webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ebebeb));
+    background-image: -webkit-linear-gradient(top,#f2f2f2 0,#ebebeb 100%);
+    background-image: -o-linear-gradient(top,#f2f2f2 0,#ebebeb 100%);
+    background-image: linear-gradient(to bottom,#f2f2f2 0,#ebebeb 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#FFF2F2F2", endColorstr="#FFEBEBEB", GradientType=0);
+    color: #222;
+    text-decoration: none;
+  }
+  .menuList>button:active{
+    border-color: #AAA;
+    background-image: -webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ebebeb));
+    background-image: -webkit-linear-gradient(top,#f2f2f2 0,#ebebeb 100%);
+    background-image: -o-linear-gradient(top,#f2f2f2 0,#ebebeb 100%);
+    background-image: linear-gradient(to bottom,#f2f2f2 0,#ebebeb 100%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#FFF2F2F2", endColorstr="#FFEBEBEB", GradientType=0);
+    color: #222;
+    text-decoration: none;
+  }
+  .menuList>button:first-child:not(:last-child){
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+  .menuList>button:last-child:not(:first-child){
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+    padding: 7px 12px;
+  }
+  .menuList>button:not(:first-child):not(:last-child) {
+    border-radius: 0;
+  }
+  .menuList>button+button{
+    margin-left: -1px;
+  }
+  .room-grid{
+    display:flex;
+    flex-wrap:wrap;
+    width:100%;
+    justify-content:space-between;
+  }
+  .room-list{
+    display:flex;
+
+  }
+  .room{
+    width:50px;
+    height:50px;
+    border:1px solid #416C2B;
+    margin-left:3px;
+  }
+  .room-list>.room:first-child{
+    margin-left:0px;
+  }
+  .line {
+    width: 4px;
+    height: 52px;
+    position: absolute;
+    background-color: #416C2B;
+    top: 0;
+    left: 272px;
+  }
+  .line1 {
+    width: 4px;
+    height: 104px;
+    position: absolute;
+    background-color: #416C2B;
+    top: 83px;
+    left: 272px;
+  }
+  .line2 {
+    width: 5px;
+    height: 270px;
+    position: absolute;
+    background-color: #416C2B;
+    top: 0;
+    left: 598px;
+  }
+  .line2-left {
+    left: -2px;
+  }
+  .line2-right {
+    left: 1195px;
+  }
+  .line-left1 {
+    top: 218px;
+  }
+  .line-center1 {
+    left: 323px;
+  }
+  .line-center2 {
+    left: 323px;
+  }
+  .line-center3 {
+    left: 323px;
+    top: 218px;
+  }
+  .line-center4 {
+    left: 873px;
+  }
+  .line-center5 {
+    left: 873px;
+  }
+  .line-center6 {
+    top: 218px;
+    left: 873px;
+  }
+  .line-right1 {
+    left: 925px;
+  }
+  .line-right2 {
+    left: 925px;
+  }
+  .line-right3 {
+    left: 925px;
+    top: 218px;
+  }
+  .line3 {
+    width: 278px;
+    height: 5px;
+    position: absolute;
+    background-color: #416C2B;
+    top: -2px;
+    left: 0;
+  }
+  .line3-bottom {
+    top: 267px;
+  }
+  .line3-right-top {
+    left: 920px;
+  }
+  .line3-right-bottom {
+    left: 920px;
+    top: 267px;
+  }
+  .line4 {
+    width: 560px;
+    height: 5px;
+    position: absolute;
+    background-color: #416C2B;
+    top: -2px;
+    left: 320px;
+  }
+  .line4-bottom {
+    top: 267px;
+  }
+</style>

+ 26 - 0
src/main.js

@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+import './assets/css/reset.scss';
+import echarts from "echarts";
+import 'echarts-gl';
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+import http from './utils/http';
+import axios from 'axios';
+
+Vue.prototype.$http = http;
+Vue.prototype.$axios = axios;
+
+
+
+Vue.config.productionTip = false
+Vue.prototype.$echarts = echarts
+Vue.use(ElementUI)
+
+new Vue({
+  router,
+  store,
+  render: h => h(App)
+}).$mount('#app')

+ 44 - 0
src/router/index.js

@@ -0,0 +1,44 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+
+Vue.use(VueRouter)
+
+const routes = [
+	{
+		path: '/',
+		name: 'Home',
+		component: () => import('@/views/home/Home.vue')
+	},
+	{
+		path: '/monitor',
+		name: 'monitor',
+		component: () => import('@/views/monitor/monitor.vue')
+	},
+	{
+		path: '/biology',
+		name: 'biology',
+		component: () => import('@/views/biology/biology.vue')
+	},
+	{
+		path: '/robot',
+		name: 'robot',
+		component: () => import('@/views/robot/robot.vue')
+	},
+	{
+		path: '/environmentalProtection',
+		name: 'environmentalProtection',
+		component: () => import('@/views/environmentalProtection/environmentalProtection.vue')
+	},
+	// {
+	// 	path: '/userAdmin',
+	// 	name: 'userAdmin',
+	// 	component: () => import('../views/userAdmin/userAdmin')
+	// }
+
+]
+
+const router = new VueRouter({
+	routes
+})
+
+export default router

+ 15 - 0
src/store/index.js

@@ -0,0 +1,15 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  state: {
+  },
+  mutations: {
+  },
+  actions: {
+  },
+  modules: {
+  }
+})

File diff suppressed because it is too large
+ 613 - 0
src/utils/drawingBoard.js


+ 75 - 0
src/utils/http.js

@@ -0,0 +1,75 @@
+import axios from 'axios';
+import Qs from 'qs';
+
+// 请求超时时间
+axios.defaults.timeout = 1000000;
+
+axios.defaults.baseURL = 'http://122.112.252.100:8085';
+
+// post 请求头
+axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
+
+//请求拦截器
+axios.interceptors.request.use(
+  config => {
+
+    config.headers = {
+      'Content-Type' : 'application/x-www-form-urlencoded',
+    }
+    config.headers['Content-Type'] = "application/x-www-form-urlencoded";
+    return config
+  },
+  error => {
+    return Promise.error(error);
+  }
+);
+
+// 相应拦截器
+
+axios.interceptors.response.use(
+  response => {
+    if(response.status === 200) {
+      return Promise.resolve(response)
+    } else {
+      return Promise.reject(response)
+    }
+  },
+  error => {
+    return Promise.error(error);
+  }
+);
+
+// get 方法
+
+export function get(url, params) {
+  return new Promise((resolve, reject) => {
+    axios.get(url, {params: params})
+      .then(res => {
+        resolve(res.data);
+      })
+      .catch(err => {
+        reject(err.data);
+      })
+  })
+}
+
+// post 方法
+
+export function post(url, params) {
+  return new Promise((resolve, reject) => {
+    axios.post(url, Qs.stringify(params))
+      .then(res => {
+        resolve(res.data);
+      })
+      .catch(err => {
+        reject(err.data);
+      })
+  })
+
+}
+
+
+export default  {
+  get,
+  post
+}

+ 40 - 0
src/utils/utils.js

@@ -0,0 +1,40 @@
+function timestamp(timestamp) {
+  var date = new Date(timestamp);
+  var Y = date.getFullYear() + '-';
+  var M = (date.getMonth() + 1) < 10 ? '0'+ (date.getMonth()+1) + '-' : (date.getMonth()+1) + '-';
+  var D = date.getDate() < 10 ? '0' +  date.getDate() :  date.getDate();
+  return Y + M + D;
+}
+
+function timeMount(timestamp) {
+  var date = new Date(timestamp);
+  var M =  (date.getMonth() + 1) < 10 ? '0'+ (date.getMonth()+1) : (date.getMonth()+1) + '-';
+  var D = date.getDate() + ' ';
+  var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours() )+ ':';
+  var mm = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
+  var s = (date.getSeconds() + 1) < 10 ? '0'+ (date.getSeconds()+1) : (date.getSeconds()+1);
+  return M + D + h + mm + s;
+}
+
+function group(array, size) {
+  let data = [];
+  for (let i = 0; i < array.length; i += size) {
+    data.push(array.slice(i, i + size))
+  }
+  return data[0]
+}
+
+function resolverTime(dates) {
+  let date = dates.split(' ');
+  let HH = date[0].split('-')[1] + '-' + date[0].split('-')[2] + ' ';
+  let ss = date[1].split(':')[0] + ':' + date[1].split(':')[1];
+  return HH + ss;
+}
+
+
+export default {
+  timestamp,
+  group,
+  timeMount,
+  resolverTime
+}

+ 928 - 0
src/views/biology/biology.vue

@@ -0,0 +1,928 @@
+<template>
+    <div class="biology">
+       <div class="flex">
+           <div class="left">
+               <div class="wrapper">
+                   <div class="chart-tab-t">
+                       <div class="chart-tab-title">每日人员流动情况</div>
+                   </div>
+                   <div class="wrapper-content">
+                       <div ref="chartLeft1" style="width: 100%; height: 100%"></div>
+                   </div>
+               </div>
+               <div class="wrapper">
+                   <div class="chart-tab-t">
+                       <div class="chart-tab-title">每周人员流动情况</div>
+                   </div>
+                   <div class="wrapper-content">
+                       <div ref="chartLeft2" style="width: 100%; height: 100%"></div>
+                   </div>
+               </div>
+           </div>
+           <div class="center">
+               <div class="line1">
+                   <div class="today">{{date}}</div>
+                   <div class="test">生物防控</div>
+               </div>
+               <div class="line2">
+                   <div class="msg-tab-body">
+                       <div class="msg-list-content" v-for="(item, i) in outList" :key="i" @click="setOut(item)">
+                           <div class="msg-tab-list">
+                               <div class="man-dothis">
+                                   <img v-if="item.image" class="man-img" :src="item.image" alt="">
+                                   <img v-else  class="man-img" src="../../assets/user1.png" alt="">
+                               </div>
+                               <div class="this-content">
+                                   <span class="finish-time">{{item.time}}</span>
+                                   <span class="username">{{item.userName}} </span>
+                                   <span class="agent"> {{item.sbName}} 人脸识别终端</span>
+                                   <span class="gotype">外出</span>
+                               </div>
+                           </div>
+                       </div>
+                   </div>
+                   <div class="msg-detail">
+                       <div class="jk-img">
+                           <img :src="outFirst.image" class="img-size" alt="">
+                       </div>
+                       <div class="jk-info-list">
+                           <div class="jk-info-item">姓名:<span style="color: white; font-size: 16px;">{{outFirst.userName ? outFirst.userName : ''}}</span></div>
+                           <div class="jk-info-item">终端名称:<span style="color: white; font-size: 16px;">{{outFirst.userName ? '人脸识别' : ''}}</span></div>
+                           <div class="jk-info-item">出入方式:<span style="color: white; font-size: 16px;">{{outFirst.sbName ? outFirst.sbName : ''}}{{outFirst.sbName ? '人脸识别终端外出' : ''}}</span></div>
+                           <div class="jk-info-item">出入时间:<span style="color: white; font-size: 16px;">{{outFirst.time ? outFirst.time : ''}}</span></div>
+                       </div>
+                   </div>
+               </div>
+           </div>
+           <div class="right">
+               <div class="wrapper">
+                   <div class="tab-t">
+                       <div class="right-tabs">
+                           <div class="log-tab">视频监控</div>
+                       </div>
+                   </div>
+                    <!-- 视频监控 -->
+                   <div v-show="active === 1" class="switch-tab-list">
+                       <div class="play-box-content" v-for="(item, i) in spjkList" :key="i">
+                           <iframe :class="activeClass == i ? 'actived' : ''" :src="'static/index.html?'+'1'+'&' + item.wsUrl  +'&'+ item.rtspUrl + '&' + '100%' + '&' + i" frameborder="0"  style="height: 100%; width: 100%"></iframe>
+                       </div>
+                   </div>
+               </div>
+           </div>
+       </div>
+        <div class="flex-bottom">
+            <div class="chart-tab-t">
+                <div class="chart-tab-title">访问统计</div>
+            </div>
+            <div class="box-visit">
+                <!-- 每日统计访问 -->
+                <div class="col-30">
+                    <div ref="chartBottom1" style="width: 100%; height: 100%"></div>
+                </div>
+                <!-- 每周统计访问 -->
+                <div class="col-30">
+                    <div ref="chartBottom2" style="width: 100%; height: 100%"></div>
+                </div>
+                <!-- 每月统计访问 -->
+                <div class="col-30">
+                    <div ref="chartBottom3" style="width: 100%; height: 100%"></div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import utils  from "../../utils/utils";
+export default {
+    name: "biology",
+    components: {},
+    data() {
+        return {
+            date: '',
+            active: 1,
+            activeClass: -1,
+            spjkList: [
+                {
+                    wsUrl: 'ws://183.246.182.241:10080/camera_relay?tcpaddr=admin%3Atx123456%40192.168.1.2',
+                    rtspUrl: 'rtsp://admin:tx123456@192.168.1.2'
+                },
+                {
+                    wsUrl: 'ws://183.246.182.241:10080/camera_relay?tcpaddr=admin%3Atx123456%40192.168.1.23',
+                    rtspUrl: 'rtsp://admin:tx123456@192.168.1.23'
+                }
+            ],
+            data_rycr_legend: [], // 时间
+            outData: [], // 外出人数
+            enterData: [], // 进入人数
+            // 每日进入人数
+            options_stztzfb: {
+                color:['#b93b6a'],
+                tooltip : {
+                    trigger: 'axis',
+                    axisPointer : {            // 坐标轴指示器,坐标轴触发有效
+                        type : 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
+                    },
+
+
+                },
+                grid: {
+                    x: '3%',
+                    y: '13%',
+                    x2: '5%',
+                    y2: '5%',
+                    containLabel: true
+
+                },
+                legend: {
+                    data: ['进入人数'],
+                    icon:'circle',
+                    itemGap: 5,
+                    itemWidth:10,
+                    top: '2%',
+                    x:'center',
+                    textStyle:{
+
+                        color:'#ccf5f9',
+                    },
+                },
+                xAxis :{
+                    type : 'category',
+                    data : [],
+                    axisTick: {
+                        alignWithLabel: true
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#4865e3',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+                    axisLabel: {
+                        textStyle: {
+                            color: '#ccf5f9',
+
+                        },
+
+                    }
+
+                },
+                yAxis :{
+                    type : 'value',
+                    scale: true,
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#4865e3',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+                    splitLine:{
+                        lineStyle: {
+                            type: 'solid',
+                            color: 'rgba(72,101,227,0.7)',
+                            width:'1'
+                        }
+                    },
+                    axisLabel: {
+                        textStyle: {
+                            color: '#ccf5f9',
+
+                        },
+
+                    }
+                },
+                dataZoom: [{
+                    type: 'inside',
+                    start: 80,
+                    end: 100
+                }],
+                series : [
+                    {
+                        name:"进入人数",
+                        type:'bar',
+                        barWidth: '30%',
+                        data: [],
+                        itemStyle: {
+                            normal: {
+                                label: {
+                                    show: true, //开启显示
+                                    position: 'top', //在上方显示
+                                    textStyle: { //数值样式
+                                        color: '#ccf5f9',
+                                        fontSize: 12
+                                    },
+                                    formatter: '{c}'
+                                }
+                            }
+                        },
+                    }
+                ]
+            },
+            // 每周进入情况
+            options_mrldqk: {
+                color:['#b93b6a'],
+                tooltip : {
+                    trigger: 'axis',
+                    axisPointer : {            // 坐标轴指示器,坐标轴触发有效
+                        type : 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
+                    },
+
+
+                },
+                grid: {
+                    x: '3%',
+                    y: '13%',
+                    x2: '5%',
+                    y2: '5%',
+                    containLabel: true
+
+                },
+                legend: {
+                    data: ['进入人数'],
+                    icon:'circle',
+                    itemGap: 5,
+                    itemWidth:10,
+                    top: '2%',
+                    x:'center',
+                    textStyle:{
+
+                        color:'#ccf5f9',
+                    },
+                },
+                xAxis :{
+                    type : 'category',
+                    data : [],
+                    axisTick: {
+                        alignWithLabel: true
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#4865e3',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+                    axisLabel: {
+                        textStyle: {
+                            color: '#ccf5f9',
+
+                        },
+
+                    }
+
+                },
+                yAxis :{
+                    type : 'value',
+                    scale: true,
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#4865e3',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+                    splitLine:{
+                        lineStyle: {
+                            type: 'solid',
+                            color: 'rgba(72,101,227,0.7)',
+                            width:'1'
+                        }
+                    },
+                    axisLabel: {
+                        textStyle: {
+                            color: '#ccf5f9',
+
+                        },
+
+                    }
+                },
+                dataZoom: [{
+                    type: 'inside',
+                    start: 80,
+                    end: 100
+                }],
+                series : [
+                    {
+                        name:"进入人数",
+                        type:'bar',
+                        barWidth: '30%',
+                        data: [],
+                        itemStyle: {
+                            normal: {
+                                label: {
+                                    show: true, //开启显示
+                                    position: 'top', //在上方显示
+                                    textStyle: { //数值样式
+                                        color: '#ccf5f9',
+                                        fontSize: 12
+                                    },
+                                    formatter: '{c}'
+                                }
+                            }
+                        },
+                    }
+                ]
+            },
+            // 每日访问
+            options_mrfw: {
+                title: {
+                    left: 'center',
+                    text: '每日访问统计',
+                    textStyle: {
+                        color: '#fff'
+                    }
+                },
+                tooltip: {
+                    trigger: 'axis'
+                },
+                grid:{
+                    x:40,
+                    y:45,
+                    x2:40,
+                    y2:30,
+
+                },
+
+                xAxis: {
+                    type: 'category',
+                    boundaryGap: false,
+                    data: [],
+                    axisLabel:{
+                        color:"#fff",
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#fff',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+
+                },
+                yAxis: {
+                    type: 'value',
+                    name: '次数',
+                    axisLabel:{
+                        color:"#fff",
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#fff',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+                    splitLine:{
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#1c3860',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+
+                },
+                series: {
+                    name:'每日访问统计',
+                    type:'line',
+                    data:[],
+                }
+            },
+            // 每周访问
+            options_mzfw: {
+                title: {
+                    left: 'center',
+                    text: '每周访问统计',
+                    textStyle: {
+                        color: '#fff'
+                    }
+                },
+                tooltip: {
+                    trigger: 'axis'
+                },
+                grid:{
+                    x:40,
+                    y:45,
+                    x2:40,
+                    y2:30,
+
+                },
+
+                xAxis: {
+                    type: 'category',
+                    boundaryGap: false,
+                    data: [],
+                    axisLabel:{
+                        color:"#fff",
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#fff',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+
+                },
+                yAxis: {
+                    type: 'value',
+                    name: '次数',
+                    axisLabel:{
+                        color:"#fff",
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#fff',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+                    splitLine:{
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#1c3860',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+
+                },
+                series: {
+                    name:'每周访问统计',
+                    type:'line',
+                    data:[],
+                }
+            },
+            // 每月访问
+            options_myfw: {
+                title: {
+                    left: 'center',
+                    text: '每月访问统计',
+                    textStyle: {
+                        color: '#fff'
+                    }
+                },
+                tooltip: {
+                    trigger: 'axis'
+                },
+                grid:{
+                    x:40,
+                    y:45,
+                    x2:40,
+                    y2:30,
+
+                },
+
+                xAxis: {
+                    type: 'category',
+                    boundaryGap: false,
+                    data: [],
+                    axisLabel:{
+                        color:"#fff",
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#fff',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+
+                },
+                yAxis: {
+                    type: 'value',
+                    name: '次数',
+                    axisLabel:{
+                        color:"#fff",
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#fff',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+                    splitLine:{
+                        lineStyle: {
+                            type: 'solid',
+                            color: '#1c3860',//左边线的颜色
+                            width:'1'//坐标线的宽度
+                        }
+                    },
+
+                },
+                series: {
+                    name:'每月访问统计',
+                    type:'line',
+                    data:[],
+                }
+            },
+            // 闸机进出
+            outList: [],
+            // 默认第一位
+            outFirst: {
+                image: '',
+                sbName: '',
+                time: '',
+                userName: ''
+            }
+        }
+    },
+    watch: {
+        active(newVal) {
+            if(newVal) {
+                // console.log(newVal);
+            } else {
+                console.log('没有改变');
+            }
+        }
+    },
+	  methods: {
+        init() {
+            this.$axios.get('http://122.112.252.100:8081/face/findDb?timeHorizon=oneDay')
+            .then(res => {
+                let timeArr = [];
+                let valueArr = [];
+                    res.data.object.forEach(item => {
+                        timeArr.push(item.key);
+                        valueArr.push(item.value);
+                    });
+                    this.options_mrfw.xAxis.data = timeArr;
+                    this.options_stztzfb.xAxis.data = timeArr;
+                    this.options_mrfw.series.data = valueArr;
+                    this.options_stztzfb.series[0].data = valueArr;
+                    const chartBottom1 = this.$refs.chartBottom1;
+                    const myChartBottom1 = this.$echarts.init(chartBottom1);
+                    myChartBottom1.setOption(this.options_mrfw);
+                    const chartLeft1 = this.$refs.chartLeft1;
+                    const myChartLeft1 = this.$echarts.init(chartLeft1);
+                    myChartLeft1.setOption(this.options_stztzfb);
+
+            });
+            this.$http.get('http://122.112.252.100:8081/face/findDb?timeHorizon=oneWeek')
+            .then(res => {
+                let timeArr = [];
+                let valueArr = [];
+                let arr;
+                // console.log(res);
+                    arr =  res.object.reverse();
+                    arr.forEach(item => {
+                        timeArr.push(item.key);
+                        valueArr.push(item.value);
+                    });
+                    this.options_mzfw.xAxis.data = timeArr;
+                    this.options_mrldqk.xAxis.data = timeArr;
+                    this.options_mzfw.series.data = valueArr;
+                    this.options_mrldqk.series[0].data = valueArr;
+                    const chartBottom2 = this.$refs.chartBottom2;
+                    const myChartBottom2 = this.$echarts.init(chartBottom2);
+                    myChartBottom2.setOption(this.options_mzfw);
+                    const chartLeft2 = this.$refs.chartLeft2;
+                    const myChartLeft2 = this.$echarts.init(chartLeft2);
+                    myChartLeft2.setOption(this.options_mrldqk);
+
+            });
+            this.$http.get('http://122.112.252.100:8081/face/findDb?timeHorizon=oneYear')
+                    .then(res => {
+                        let timeArr = [];
+                        let valueArr = [];
+
+                            res.object.forEach(item => {
+                                timeArr.push(item.key);
+                                valueArr.push(item.value);
+                            });
+                            this.options_myfw.xAxis.data = timeArr;
+                            this.options_myfw.series.data = valueArr;
+                            const chartBottom3 = this.$refs.chartBottom3;
+                            const myChartBottom3 = this.$echarts.init(chartBottom3);
+                            myChartBottom3.setOption(this.options_myfw);
+
+                    });
+        },
+        setOut(item) {
+            this.outFirst = item;
+        }
+
+    },
+    created() {
+    },
+    mounted() {
+        let date = Date.parse(new Date());
+        this.date = utils.timestamp(date);
+        this.init();
+        this.$http.get('http://122.112.252.100:8081/face/findEnd')
+                .then(res => {
+                    this.outList = res.object;
+                    this.outFirst = res.object[0];
+                })
+    }
+};
+</script>
+
+<style lang="scss" scope>
+.biology {
+    box-sizing: border-box;
+    flex-grow: 1;
+    color: #eee;
+    padding: 20px;
+    .flex  {
+        display: flex;
+        margin-bottom: 20px;
+        .left {
+            flex: 0 1 475px;
+            .wrapper {
+                height: 270px;
+                /*background-color: #001346;*/
+                overflow: hidden;
+                /*background-image: url('../../assets/box_bg.png');*/
+                /*background-size: 100% 270px;*/
+                border-radius: 8px;
+                margin-bottom: 20px;
+                .chart-tab-t {
+                    /*height: 40px;*/
+                    padding-left: 80px;
+                    background-color: #0E1E51;
+                    line-height: 28px;
+                    .chart-tab-title {
+                        position: relative;
+                        padding-left: 10px;
+                    }
+                    .chart-tab-title:before {
+                        content: '';
+                        width: 14px;
+                        height: 14px;
+                        border-radius: 50%;
+                        position: absolute;
+                        left: -20px;
+                        top: 50%;
+                        background-color: #fff;
+                        transform: translateY(-50%);
+                        -ms-transform: translateY(-50%);
+                        -moz-transform: translateY(-50%);
+                        -webkit-transform: translateY(-50%);
+                        -o-transform: translateY(-50%);
+                    }
+                }
+                .wrapper-content {
+                    height: calc(100% - 27.5px);
+                    position: relative;
+                    background-color: #0D1943;
+                }
+            }
+            .wrapper:last-child {
+                margin-bottom: 0;
+            }
+        }
+        .right {
+            flex: 0 1 475px;
+            .wrapper {
+                border-radius: 6px;
+                height: 100%;
+                background-color: #001346;
+                overflow: hidden;
+                .tab-t {
+                    height: 40px;
+                    line-height: 40px;
+                    background-color: #0f215c;
+                    .right-tabs {
+                        height: 40px;
+                        display: flex;
+                        justify-content: center;
+                        align-items: center;
+                        .log-tab {
+                            width: 100%;
+                            height: 36px;
+                            text-align: center;
+                            background-color: #53BAFD;
+                            color: #fff;
+                            border-right: 2px solid #ddd;
+                            cursor: pointer;
+                        }
+                        .active {
+                        }
+                    }
+                }
+                .switch-tab-list {
+                    height: 480px;
+                    display: flex;
+                    flex-direction: column;
+                    justify-content: space-around;
+                    align-items: center;
+                    .play-box-content {
+                        width: 90%;
+                        height: 48%;
+                        text-align: center;
+                        .play-box-img {
+                            height: 100%;
+                        }
+                    }
+                }
+            }
+        }
+        .center {
+            flex-grow: 1;
+            padding: 0 10px;
+            box-sizing: border-box;
+            .line1 {
+                width: 100%;
+                /*text-align: center;*/
+                font-size: 1.3em;
+                display: flex;
+                margin-bottom: 10px;
+                .today {
+                    width: 50%;
+                    padding-left: 5%;
+                    position: relative;
+                    /*box-sizing: content-box;*/
+                }
+                .today:after {
+                    content: '';
+                    height: 0px;
+                    width: 52%;
+                    border: 1px solid #06dfff;
+                    position: absolute;         /*定位背景横线的位置*/
+                    top: 40%;
+                    left: 40%;
+                }
+                .test {
+                    width: 50%;
+                    position: relative;
+                    /*box-sizing: content-box;*/
+                }
+                .test:after {
+                    content: '';
+                    height: 0px;
+                    width: 52%;
+                    border: 1px solid #06dfff;
+                    position: absolute;         /*定位背景横线的位置*/
+                    top: 40%;
+                    left: 30%;
+                }
+            }
+            .line2 {
+                height: calc(100% - 27px);
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                .msg-tab-body {
+                    overflow-y: scroll;
+                    height: 480px;
+                    width: 50%;
+                    .msg-list-content {
+                        .msg-tab-list {
+                            height: 80px;
+                            position: relative;
+                            display: flex;
+                            align-items: center;
+                            justify-content: space-around;
+                            flex-wrap: nowrap;
+                            margin: 0 20px;
+                            padding: 10px;
+                            color: #a4cfe5;
+                            cursor: pointer;
+
+                            .man-dothis {
+                                flex: 1;
+                                .man-img {
+                                    width: 50px;
+                                    height: 50px;
+                                    border-radius: 50%;
+                                }
+                            }
+                            .this-content {
+                                flex: 4;
+                                display: -webkit-box;
+                                -webkit-box-orient: vertical;
+                                -webkit-line-clamp: 2;
+                                overflow: hidden;
+                                line-height: 1.2em;
+                                .finish-time {
+                                    margin-right: 15px;
+                                }
+                            }
+                        }
+
+                        .msg-tab-list:before {
+                            content: '';
+                            position: absolute;
+                            bottom: 0;
+                            left: 10%;
+                            height: 2px;
+                            width: 80%;
+                            background: linear-gradient(to right, rgba(11, 82, 224, 0), #0b52e0, rgba(11, 82, 224, 0));
+                        }
+                    }
+                }
+                .msg-tab-body::-webkit-scrollbar {
+                    /*滚动条整体样式*/
+                    width : 5px;  /*高宽分别对应横竖滚动条的尺寸*/
+                    height: 1px;
+                }
+                .msg-tab-body::-webkit-scrollbar-thumb {
+                    /*滚动条里面小方块*/
+                    border-radius   : 8px;
+                    background-color:  #535353;
+                    background-image: -webkit-linear-gradient(
+                                    45deg,
+                                    rgba(255, 255, 255, 0.2) 25%,
+                                    transparent 25%,
+                                    transparent 50%,
+                                    rgba(255, 255, 255, 0.2) 50%,
+                                    rgba(255, 255, 255, 0.2) 75%,
+                                    transparent 75%,
+                                    transparent
+                    );
+                }
+                .msg-tab-body::-webkit-scrollbar-track {
+                    /*滚动条里面轨道*/
+                    box-shadow   : inset 0 0 5px rgba(0, 0, 0, 0.2);
+                    /*background   : #ededed;*/
+                    border-radius: 10px;
+                }
+                .msg-detail {
+                    width: 50%;
+                    height: 100%;
+                    .jk-img {
+                        height: 50%;
+                        text-align: center;
+                        overflow: hidden;
+                        .img-size {
+                            height: 300px;
+                        }
+                    }
+                    .jk-info-list {
+                        padding: 20px 40px;
+                        .jk-info-item {
+                            color: #0b52e0;
+                            font-size: 20px;
+                            margin: 15px;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    .flex-bottom {
+        width: 100%;
+        height: calc(100% - 580px);
+        border-radius: 8px;
+        overflow: hidden;
+        .chart-tab-t {
+            /*height: 40px;*/
+            padding-left: 80px;
+            line-height: 28px;
+            background-color: #0E1E51;
+            .chart-tab-title {
+                position: relative;
+                padding-left: 10px;
+            }
+            .chart-tab-title:before {
+                content: '';
+                width: 14px;
+                height: 14px;
+                border-radius: 50%;
+                position: absolute;
+                left: -20px;
+                top: 50%;
+                background-color: #fff;
+                transform: translateY(-50%);
+                -ms-transform: translateY(-50%);
+                -moz-transform: translateY(-50%);
+                -webkit-transform: translateY(-50%);
+                -o-transform: translateY(-50%);
+            }
+        }
+        .box-visit {
+            width: 100%;
+            /*margin: 10px  auto 0;*/
+            height: calc(100% - 40px);
+            box-sizing: content-box;
+            display: flex;
+            justify-content: space-around;
+            align-items: center;
+            background-color: #0D1943;
+            .col-30 {
+                width: 30%;
+                height: 100%;
+            }
+        }
+    }
+}
+.actived {
+    position: fixed;
+    width: 1920px;
+    height: 100vh;
+    left: 0;
+    top: 0;
+}
+</style>

File diff suppressed because it is too large
+ 1145 - 0
src/views/environmentalProtection/environmentalProtection.vue


+ 560 - 0
src/views/home/Home.vue

@@ -0,0 +1,560 @@
+<template>
+    <div class="home">
+        <section class="top">
+            <div class="left">
+                <div class="left-1">
+                    <div class="chart-tab-title">生物防控</div>
+                    <div class="charts">
+                        <E-Left1 style="height: 100%"></E-Left1>
+                    </div>
+                </div>
+                <div class="left-2">
+                    <div class="chart-tab-title">环保信息</div>
+                    <div class="content">
+                        <article class="item"><span class="text-white">COD:</span>{{environmentdData.hxxyl}}mg/L</article>
+                        <article class="item"><span class="text-white">氨氮:</span>{{environmentdData.ad}}mg/L</article>
+                        <article class="item"><span class="text-white">流量:</span>{{environmentdData.ws}}T/H</article>
+                        <article class="item"><span class="text-white">累计流量:</span>{{environmentdData.wslj}}T</article>
+                        <article class="item"><span class="text-white">总磷:</span>{{environmentdData.zl}}mg/L</article>
+                        <article class="item"><span class="text-white">总氮:</span>{{environmentdData.zd}}mg/L</article>
+                        <article class="item"><span class="text-white">PH:</span>{{environmentdData.ph}}</article>
+                    </div>
+                </div>
+            </div>
+            <div class="center">
+                <div class="header_title">
+                    <span class="date">
+                        {{
+                            `${new Date().getFullYear()}-${
+                                new Date().getMonth() + 1
+                            }-${new Date().getDate()}`
+                        }}
+                    </span>
+                    <div class="title">首 页</div>
+<!--                    <img class="icon" src="../../assets/chilun.png" />-->
+                </div>
+                <div class="number">
+                    <div class="item" v-for="(item, i) in listData" :key="i">
+                        <div>
+                            <span class="title">{{item.name}}:</span>
+                            <span class="num">{{item.num}}只</span>
+                        </div>
+                    </div>
+                </div>
+                <div class="main_img_box">
+                    <imgDragScale :img="img" @openVideo="openVideo"></imgDragScale>
+                </div>
+            </div>
+            <div class="right">
+                <div class="right-1">
+                    <div class="chart-tab-title">机器人巡逻</div>
+                    <div class="content">
+                        <img src="../../assets/robot.gif" width="100%" />
+                        <div class="describe">
+                            <span>1号机器人巡查中</span>
+                            <span>当前位置1栋3单元</span>
+                        </div>
+                    </div>
+                </div>
+                <div class="right-2">
+                    <div class="chart-tab-title">报警信息</div>
+                    <div class="msg-tab-body">
+                        <div class="cell" v-for="(item, i) in callList" :key="i">
+                            <span class="cell-default">{{item.ZSMC}}:</span>
+                            <span>{{item.gzxx}}</span>
+                            <span style="float: right;">{{item.dates}}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </section>
+        <footer class="footer">
+            <swiperMulti @onOpen="onOpen" @openItem="openItem"></swiperMulti>
+        </footer>
+        <el-dialog
+            :title="item.position + '环境监测曲线'"
+            :visible.sync="isShow"
+             width="50%"
+            >
+            <div style="padding: 20px; height: 400px;">
+                <e-line1 v-if="num%2 !== 0 && isShow" :id="item.lqid"></e-line1>
+                <e-line2 v-else-if="num%2 === 0 && isShow" :id="item.lqid"></e-line2>
+            </div>
+        </el-dialog>
+        <el-dialog
+                :title="lists.position + '环境监测曲线'"
+                :visible.sync="isQk"
+                width="50%">
+            <div style="padding: 20px; height: 400px;">
+                <chartLine v-if="isQk" :echartsList="echartsList"></chartLine>
+            </div>
+        </el-dialog>
+        <el-dialog
+                :title="pList[0] + '栋' + pList[1] +'号舍视频监控'"
+                :visible.sync="isVideo"
+                width="60%"
+        >
+            <div style="padding: 20px;">
+                <div  style="width: 1100px; height: 580px;">
+                    <iframe v-if="isVideo" :src="'static/index.html?'+'0'+'&' + cameraOne  +'&'+ cameraTwo + '&' + '100%' + '&' + '0'" frameborder="0"  style="height: 100%; width: 100%"></iframe>
+                </div>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import imgDragScale from "@/components/imgDragScale";
+import swiperMulti from "./swiperMulti";
+import ELeft1 from "./charts/ELeft1";
+import ELine1 from "./charts/Eline1";
+import ELine2 from "./charts/Eline2";
+import chartLine from "./charts/chartLine";
+import img from "@/assets/u62.png";
+import utils from "../../utils/utils.js";
+let timer, timer1;
+export default {
+    name: "Home",
+    data() {
+        return {
+            img,
+            isShow: false,
+            isVideo: false,
+            item: {
+                position: ''
+            },
+            num: 0,
+            // 栋舍
+            pList: [],
+            environmentdData: {
+                hxxyl: '',
+                ad: '',
+                ws: '',
+                wslj: '',
+                zl: '',
+                zd: '',
+                ph: ''
+            },
+            // 报警信息
+            callList: [],
+            // 保育
+            listData: [],
+            cameraOne: '',
+            cameraTwo: '',
+            screenWidth: '',
+            // 水量
+            echartsList: [],
+            isQk: false,
+            lists: {},
+        };
+    },
+    components: {
+        imgDragScale,
+        ELeft1,
+        swiperMulti,
+        ELine1,
+        ELine2,
+        chartLine
+    },
+    watch: {
+        isShow(newVal) {
+            if(newVal == false) {
+                clearInterval(timer);
+            }
+        }
+    },
+    created() {},
+    methods: {
+        onOpen(i) {
+            this.isShow = true;
+            this.item = i;
+           this.setInvaler();
+        },
+        initList() {
+            this.$http.post('http://122.112.252.100:8095/dpSjlrSyS/find')
+                    .then(res => {
+                        if(res.code === 10000) {
+                            this.listData= [];
+                            Object.keys(res.data).forEach(key => {
+                                this.listData.push({name: key, num: res.data[key]});
+                            });
+                        }
+                    })
+        },
+        init() {
+            this.$http.get('/environment/details')
+            .then(res => {
+                if(res.code === 10000) {
+                    this.environmentdData = res.data;
+                }
+            });
+            this.$http.get('http://122.112.252.100:8081/face/alarm')
+            .then(res => {
+                if(res.code === 10001) {
+                    res.data.forEach(item => {
+                        let dates = utils.resolverTime(item.FSSJ);
+                        item.dates = dates;
+                    });
+                    this.callList = res.data;
+                }
+            });
+        },
+        openVideo(data) {
+            if(data.id == null) {
+                this.$message.error('暂无该栋舍的视频信息')
+                this.isVideo = false;
+            } else {
+                this.isVideo = true;
+                this.pList = data.position.split('-');
+                this.$http.post('http://122.112.252.100:8088/cameraInfo/getCameraDetails', {areaIds: data.id})
+                        .then(res => {
+                            if(res.status === 10000) {
+                                // this.showIfrme = true;
+                                let data = null;
+                                data = res.data[0];
+                                this.cameraOne = data.wsUrl;
+                                this.cameraTwo = data.rtspUrl;
+                            } else {
+                                this.showIfrme = false;
+                                this.$message.err(res.msg);
+                            }
+                        })
+            }
+        },
+        setInvaler() {
+            timer = setInterval(() => {
+                this.num++;
+            }, 8000)
+        },
+        setInit() {
+            let that = this;
+            timer1 = setInterval(() => {
+                this.init();
+            }, 3600000);
+        },
+        openItem(data) {
+            let item = data.item;
+            this.lists = item;
+            let i = data.index;
+            let arr1 = [];
+            let arr2 = [];
+            let arr3 = [];
+                if(i === 1) {
+                    arr1 = Object.values(item.slLists);
+                    arr2 = Object.keys(item.slLists);
+                    arr3  = ['水量', '吨'];
+                   this.echartsList = [arr1, arr2, arr3];
+                } else if( i === 2) {
+                    arr1 = Object.values(item.dlLists);
+                    arr2 = Object.keys(item.dlLists);
+                    arr3 = ['电量', 'kwh'];
+                    this.echartsList = [arr1, arr2, arr3];
+                } else if(i===3) {
+                    arr1 = Object.values(item.phLists);
+                    arr2 = Object.keys(item.phLists);
+                    arr3 = ['ph', ''];
+                    this.echartsList = [arr1, arr2, arr3];
+                } else if(i === 4) {
+                    arr1 = Object.values(item.syLists);
+                    arr2 = Object.keys(item.syLists);
+                    arr3 = ['水压', 'Mpa'];
+                    this.echartsList = [arr1, arr2, arr3];
+                }
+                this.isQk = true;
+        }
+    },
+    mounted() {
+        this.init();
+        this.setInit();
+        this.initList();
+    },
+    beforeRouteLeave(to, from, next) {
+        clearInterval(timer1);
+        clearInterval(timer);
+        next();
+    }
+};
+</script>
+
+<style lang="scss" scope>
+.home {
+    box-sizing: border-box;
+    flex-grow: 1;
+    color: #eee;
+    display: flex;
+    flex-direction: column;
+    > .top {
+        // border: 1px solid rgb(13, 165, 212);
+        height: 67%;
+        display: flex;
+        > .left {
+            width: 21%;
+            > div {
+                height: calc(50% - 5px);
+                /*background: url(../../assets/box_bg.png) no-repeat;*/
+                /*background-size: 100% 100%;*/
+                display: flex;
+                flex-direction: column;
+                border-radius: 8px;
+                overflow: hidden;
+
+            }
+            > .left-1 {
+                margin: 0 10px 10px 10px;
+                .charts {
+                    flex-grow: 1;
+                    background-color: #001346;
+                }
+            }
+            > .left-2 {
+                margin: 0 10px;
+                .content {
+                    padding: 20px;
+                    flex-grow: 1;
+                    display: flex;
+                    flex-wrap: wrap;
+                    justify-content: space-around;
+                    background-color: #001346;
+                    > .item {
+                        width: 44%;
+                        background-color: #060a2d;
+                        color: #81d3f8;
+                        padding: 5px;
+                        margin-left: 6px;
+                        height: 25px;
+                        line-height: 25px;
+                        font-size: 0.9rem;
+                        overflow: hidden;
+                        text-overflow: ellipsis;
+                        white-space: nowrap;
+                        .text-white {
+                            color: white;
+                        }
+                    }
+                }
+            }
+        }
+        > .center {
+            // border: 1px solid rgb(212, 13, 195);
+            width: 58%;
+            max-width: 1250px;
+            flex-grow: 2.6;
+            flex-shrink: 0;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            > .header_title {
+                width: 90%;
+                color: #fff;
+                display: flex;
+                justify-content: space-around;
+                margin: 5px 3%;
+                .date {
+                    vertical-align: middle;
+                    font-size: 1.2rem;
+                    // display: flex;
+                    // align-items: center;
+
+                    display: -webkit-box;
+                    -webkit-box-align: center;
+                    -webkit-box-pack: center;
+                }
+                .title {
+                    flex-grow: 1;
+                    font-size: 1.4rem;
+                    text-align: center;
+                    display: flex;
+                    align-items: center;
+                    &::before {
+                        content: "";
+                        border: 1px solid #06dfff;
+                        box-sizing: border-box;
+                        height: 0px;
+                        flex-grow: 1;
+                        margin: 0 30px;
+                    }
+                    &::after {
+                        content: "";
+                        border: 1px solid #06dfff;
+                        box-sizing: border-box;
+                        height: 0px;
+                        flex-grow: 1;
+                        margin: 0 30px;
+                    }
+                }
+                .icon {
+                    width: 30px;
+                    height: 30px;
+                }
+            }
+            > .number {
+                // border: 1px solid rgb(212, 13, 195);
+                width: 92%;
+                display: flex;
+                justify-content: space-around;
+                margin: 10px 0;
+                .item {
+                    height: 50px;
+                    width: 26%;
+                    // min-width: 200px;
+                    // max-width: 280px;
+                    // flex: 1 1 10%;
+                    box-sizing: border-box;
+                    border: 2px solid #53bafd;
+                    border-radius: 12px;
+                    background-color: #001346;
+                    font-size: 1.1vw;
+                    font-weight: 600;
+                    display: flex;
+                    justify-content: center;
+                    align-items: center;
+                    .title {
+                        color: #fff;
+                    }
+                    .num {
+                        color: #53bafd;
+                        font-size: 1vw;
+                    }
+                }
+            }
+            > .main_img_box {
+                // border: 1px solid rgb(212, 13, 195);
+                flex-grow: 1;
+                width: 93%;
+            }
+        }
+        > .right {
+            width: 21%;
+            flex-grow: 1;
+            > .right-1 {
+                height: calc(50% - 5px);
+                /*background: url(../../assets/box_bg.png) no-repeat;*/
+                /*background-size: 100% 100%;*/
+                margin: 0 10px 10px 10px;
+                border-radius: 8px;
+                overflow: hidden;
+                display: flex;
+                flex-direction: column;
+                .content {
+                    flex-grow: 1;
+                    /*margin: 10px 15px;*/
+                    overflow: hidden;
+                    background-color: #001346;
+                    padding: 10px 15px;
+                    > img {
+                        box-sizing: border-box;
+                        border: 1px solid #eee;
+                        border-radius: 10px;
+                    }
+                    > .describe {
+                        color: #04d919;
+                        display: flex;
+                        justify-content: space-around;
+                        align-items: center;
+                        height: 20%;
+                        min-height: 20px;
+                    }
+                }
+            }
+            > .right-2 {
+                height: calc(50% - 5px);
+                /*background: url(../../assets/box_bg.png) no-repeat;*/
+                /*background-size: 100% 100%;*/
+                margin: 0 10px;
+                display: flex;
+                border-radius: 8px;
+                overflow: hidden;
+                flex-direction: column;
+                .msg-tab-body {
+                    overflow-y: scroll;
+                    flex-grow: 1;
+                    padding: 15px 20px;
+                    /*overflow: hidden;*/
+                    background-color: #001346;
+                    > .cell {
+                        background-color: #060a2d;
+                        overflow: hidden;
+                        text-overflow: ellipsis;
+                        white-space: nowrap;
+                        margin: 3px 0;
+                        padding: 5px;
+                        color: #F56C6C;
+                        .cell-default {
+                            color: #81d3f8;
+                        }
+                    }
+                }
+                .msg-tab-body::-webkit-scrollbar {
+                    /*滚动条整体样式*/
+                    width : 5px;  /*高宽分别对应横竖滚动条的尺寸*/
+                    height: 1px;
+                }
+                .msg-tab-body::-webkit-scrollbar-thumb {
+                    /*滚动条里面小方块*/
+                    border-radius   : 8px;
+                    background-color:  #535353;
+                    background-image: -webkit-linear-gradient(
+                                    45deg,
+                                    rgba(255, 255, 255, 0.2) 25%,
+                                    transparent 25%,
+                                    transparent 50%,
+                                    rgba(255, 255, 255, 0.2) 50%,
+                                    rgba(255, 255, 255, 0.2) 75%,
+                                    transparent 75%,
+                                    transparent
+                    );
+                }
+                .msg-tab-body::-webkit-scrollbar-track {
+                    /*滚动条里面轨道*/
+                    box-shadow   : inset 0 0 5px rgba(0, 0, 0, 0.2);
+                    /*background   : #ededed;*/
+                    border-radius: 10px;
+                }
+            }
+        }
+    }
+    > .footer {
+        margin-top: 10px;
+        height: 33.3%;
+    }
+}
+
+.chart-tab-title {
+    position: relative;
+    padding: 5px 0 5px 90px ;
+    background-color: rgb(14,30,81);
+}
+.chart-tab-title:before {
+    content: "";
+    width: 14px;
+    height: 14px;
+    border-radius: 50%;
+    position: absolute;
+    left: 70px;
+    top: 50%;
+    background-color: #fff;
+    transform: translateY(-50%);
+    -ms-transform: translateY(-50%);
+    -moz-transform: translateY(-50%);
+    -webkit-transform: translateY(-50%);
+    -o-transform: translateY(-50%);
+}
+
+.footer {
+    overflow: hidden;
+}
+
+.my-swipe .van-swipe-item {
+    color: #fff;
+    font-size: 20px;
+    line-height: 150px;
+    text-align: center;
+    background-color: #39a9ed;
+}
+    .el-dialog {
+        background-color: #021429 !important;
+    }
+    .el-dialog__title {
+        color: white !important;
+    }
+</style>

+ 149 - 0
src/views/home/charts/ELeft1.vue

@@ -0,0 +1,149 @@
+<template>
+    <div class="container">
+        <div id="ELeft1" style="height: 100%"></div>
+    </div>
+</template>
+
+<script>
+export default {
+    name: "ELeft1",
+    data() {
+        return {
+            timeArr: [],
+            valueArr: [],
+        };
+    },
+    mounted() {
+        // this.drawChart();
+        this.init();
+    },
+    methods: {
+        drawChart() {
+            // 基于准备好的dom,初始化echarts实例
+            let myChart = this.$echarts.init(document.getElementById("ELeft1"));
+            let that = this;
+            // 指定图表的配置项和数据
+            let option = {
+                color: ["#b93b6a"],
+                tooltip: {
+                    trigger: "axis",
+                    axisPointer: {
+                        // 坐标轴指示器,坐标轴触发有效
+                        type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
+                    },
+                },
+                grid: {
+                    x: "3%",
+                    y: "18%",
+                    x2: "5%",
+                    y2: "5%",
+                    containLabel: true,
+                },
+                legend: {
+                    data: ["进入人数"],
+                    icon: "circle",
+                    itemGap: 5,
+                    itemWidth: 10,
+                    top: "2%",
+                    x: "center",
+                    textStyle: {
+                        color: "#ccf5f9",
+                    },
+                },
+                xAxis: {
+                    type: "category",
+                    data: that.timeArr,
+                    axisTick: {
+                        alignWithLabel: true,
+                    },
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: "solid",
+                            color: "#4865e3", //左边线的颜色
+                            width: "1", //坐标线的宽度
+                        },
+                    },
+                    axisLabel: {
+                        textStyle: {
+                            color: "#ccf5f9",
+                        },
+                    },
+                },
+                yAxis: {
+                    type: "value",
+                    scale: true,
+                    axisLine: {
+                        show: true,
+                        lineStyle: {
+                            type: "solid",
+                            color: "#4865e3", //左边线的颜色
+                            width: "1", //坐标线的宽度
+                        },
+                    },
+                    splitLine: {
+                        lineStyle: {
+                            type: "solid",
+                            color: "rgba(72,101,227,0.7)",
+                            width: "1",
+                        },
+                    },
+                    axisLabel: {
+                        textStyle: {
+                            color: "#ccf5f9",
+                        },
+                    },
+                },
+                dataZoom: [
+                    {
+                        type: "inside",
+                        start: 80,
+                        end: 100,
+                    },
+                ],
+                series: [
+                    {
+                        name: "进入人数",
+                        type: "bar",
+                        barWidth: "15%",
+                        data:  that.valueArr,
+                        itemStyle: {
+                            normal: {
+                                label: {
+                                    show: true, //开启显示
+                                    position: "top", //在上方显示
+                                    textStyle: {
+                                        //数值样式
+                                        color: "#ccf5f9",
+                                        fontSize: 12,
+                                    },
+                                    formatter: "{c}",
+                                },
+                            },
+                        },
+                    },
+                ],
+            };
+            // 使用刚指定的配置项和数据显示图表。
+            myChart.setOption(option);
+        },
+        init() {
+            this.$axios.get(`http://122.112.252.100:8081/face/findDb?timeHorizon=oneYear`)
+            .then(res => {
+                if(res.data.status === 10000) {
+                    this.timeArr = [];
+                    this.valueArr = [];
+                    res.data.object.forEach(item => {
+                        this.timeArr.push(item.key);
+                        this.valueArr.push(item.value);
+                    })
+                    this.drawChart();
+                }
+            })
+        }
+    },
+};
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 116 - 0
src/views/home/charts/Eline1.vue

@@ -0,0 +1,116 @@
+<template>
+  <div id="Eline1" style="height: 100%"></div>
+</template>
+
+<script>
+  export default {
+    name: "Eline1",
+    data() {
+      return {
+        timeData: [],
+        shiduList: [],
+        anqiList: [],
+      }
+    },
+    props: {
+      id: {
+        type: Number
+      }
+    },
+    watch: {
+      id(newVal, oldVal) {
+        if(newVal !== oldVal) {
+          this.init();
+        } else {
+          return;
+        }
+      }
+    },
+    mounted() {
+      // this.drawChart();
+      this.init();
+    },
+    methods: {
+      drawChart() {
+        // 基于准备好的dom,初始化echarts实例
+        let myChart = this.$echarts.init(document.getElementById("Eline1"));
+        let that = this;
+        let options = {
+          tooltip: {
+            trigger: 'axis'
+          },
+          legend: {
+            data: ['氨气', '湿度'],
+            textStyle: {
+              color: 'white',
+            },
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: that.timeData,
+            axisLabel: {
+              show: true,
+              textStyle: {
+                color: 'white',  //更改坐标轴文字颜色
+              }
+            },
+          },
+          yAxis: {
+            type: 'value',
+            axisLabel: {
+              show: true,
+              textStyle: {
+                color: 'white',  //更改坐标轴文字颜色
+              }
+            },
+          },
+          series: [
+            {
+              name: '湿度',
+              type: 'line',
+              lineStyle:{
+                color:'#00FF00'
+              },
+              itemStyle: {
+                normal: {
+                  color: '#275F82', //改变折线点的颜色
+                  lineStyle: {
+                    color: 'rgb(146 200 175)' //改变折线颜色
+                  }
+                }
+              },
+              data: that.shiduList,
+            },
+            {
+              name: '氨气',
+              type: 'line',
+              data: that.anqiList,
+            },
+          ]
+        };
+
+        myChart.setOption(options);
+      },
+      init() {
+        this.$http.get('/environment/getAmmoniaAndHumidity', {lqid: this.id})
+        .then(res => {
+          this.timeData = res.data.timeData;
+          this.shiduList = res.data.shiduList;
+          this.anqiList = res.data.anqiList;
+          this.drawChart();
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 106 - 0
src/views/home/charts/Eline2.vue

@@ -0,0 +1,106 @@
+<template>
+  <div id="Eline2" style="height: 100%"></div>
+</template>
+
+<script>
+  export default {
+    name: "Eline2",
+    data() {
+      return {
+        wenduTimeData: [],
+        wenduList: [],
+      }
+    },
+    props: {
+      id: {
+        type: Number
+      }
+    },
+    watch: {
+      id(newVal, oldVal) {
+        if(newVal != oldVal) {
+          this.init();
+        }
+      }
+    },
+    mounted() {
+      this.init();
+    },
+    methods: {
+      drawChart() {
+        // 基于准备好的dom,初始化echarts实例
+        let myChart = this.$echarts.init(document.getElementById("Eline2"));
+        let that  = this;
+        let options = {
+          tooltip: {
+            trigger: 'axis'
+          },
+          legend: {
+            data: ['温度'],
+            textStyle: {
+              color: 'white',
+            },
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: that.wenduTimeData,
+            axisLabel: {
+              show: true,
+              textStyle: {
+                color: 'white',  //更改坐标轴文字颜色
+              }
+            },
+          },
+          yAxis: {
+            type: 'value',
+            axisLabel: {
+              show: true,
+              textStyle: {
+                color: 'white',  //更改坐标轴文字颜色
+              }
+            },
+          },
+          series: [
+            {
+              name: '温度',
+              type: 'line',
+              lineStyle:{
+                color:'#fff'
+              },
+              itemStyle: {
+                normal: {
+                  color: '#275F82', //改变折线点的颜色
+                  lineStyle: {
+                    color: 'rgb(146 200 175)' //改变折线颜色
+                  }
+                }
+              },
+              data: that.wenduList,
+            },
+          ]
+        };
+
+        myChart.setOption(options);
+      },
+      init() {
+        this.$http.get('/environment/getTemperature', {lqid: this.id})
+          .then(res => {
+            this.wenduTimeData = res.data.timeData;
+            this.wenduList = res.data.wenduList;
+            this.drawChart();
+          })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 88 - 0
src/views/home/charts/chartLine.vue

@@ -0,0 +1,88 @@
+<template>
+  <div id="chartLine" style="width: 100%; height: 100%"></div>
+</template>
+
+<script>
+  export default {
+    name: "chartLine",
+    props: ['echartsList'],
+    methods: {
+      drawChart() {
+        // 基于准备好的dom,初始化echarts实例
+        let myChart = this.$echarts.init(document.getElementById("chartLine"));
+        let that = this;
+        let options = {
+          title: {
+            text: '',
+            x: 'center',
+            y: 'top',
+            textStyle: {
+              fontSize: 16,
+              color: '#ffffff'
+            }
+          },
+          tooltip: {
+            trigger: 'axis'
+          },
+          legend: {
+            data: [],
+            textStyle: {
+              color: 'white',
+            },
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            axisTick: {
+              alignWithLabel: true
+            },
+            data: [],
+            axisLabel: {
+              show: true,
+              textStyle: {
+                color: 'white',  //更改坐标轴文字颜色
+              }
+            },
+          },
+          yAxis: {
+            type: 'value',
+            name: '',
+            axisLabel: {
+              show: true,
+              textStyle: {
+                color: 'white',  //更改坐标轴文字颜色
+              }
+            },
+          },
+          series: [
+            {
+              type: 'bar',
+              data: [],
+            },
+          ]
+        };
+        options.title.text = this.echartsList[2][0] + '监测';
+        options.legend.data = this.echartsList[2][0];
+        options.xAxis.data = this.echartsList[1];
+        options.yAxis.name = this.echartsList[2][1];
+        options.series[0].name = this.echartsList[2][0];
+        options.series[0].data = this.echartsList[0];
+
+        myChart.setOption(options);
+      },
+    },
+    mounted() {
+      this.drawChart();
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 370 - 0
src/views/home/swiperMulti.vue

@@ -0,0 +1,370 @@
+<template>
+    <div class="swiperMulti" @mouseenter="mouseenter" @mouseleave="mouseleave">
+        <div class="el-toLeft guide" v-show="guideShow" @click.stop="toLeft">
+            <i class="el-icon-arrow-right icon"></i>
+        </div>
+        <div class="el-toRight guide" v-show="guideShow" @click.stop="toRight">
+            <i class="el-icon-arrow-left icon"></i>
+        </div>
+        <ul>
+            <li
+                v-for="(item, index) in data"
+                :key="item.lqid"
+                :style="`left:${(index - 1) * itemPositionInterval}px`"
+                :ref="'item' + index"
+                :data-position-left="(index - 1) * itemPositionInterval"
+            >
+                <template v-if="item.isQk">
+                    <h3 class="h3_type_1">{{item.position}}</h3>
+                    <div class="warp">
+                        <div class="gutter" @click="openItem(item, 1)">
+                            <el-row>
+                                <el-col :span="10" class="temp-line">
+                                    <span >水量:</span>
+                                </el-col>
+                                <el-col :span="14" style="text-align: left;">
+                                    <span v-if="item.sl === ''" class="text-red" style="font-size: 16px;">暂无数据</span>
+                                    <span v-else class="text-green" >{{item.sl}}吨</span>
+                                </el-col>
+                            </el-row>
+                        </div>
+                        <div class="gutter" @click="openItem(item, 2)">
+                            <el-row>
+                                <el-col :span="10" class="humidity-line">
+                                    <span >电量:</span>
+                                </el-col>
+                                <el-col :span="14" class="gutter_text" style="text-align: left;">
+                                    <span v-if="item.dl === ''" class="text-red" style="font-size: 16px;">暂无数据</span>
+                                    <span v-else class="text-green" >{{item.dl}}kwh</span>
+                                </el-col>
+                            </el-row>
+                        </div>
+                        <div class="gutter" @click="openItem(item, 3)">
+                            <el-row>
+                                <el-col :span="10" class="ammonia-line">
+                                    <span >ph:</span>
+                                </el-col>
+                                <el-col :span="14" style="text-align: left;">
+                                    <span v-if="item.ph === '' " class="text-red" style="font-size: 16px;">暂无数据</span>
+                                    <span v-else class="text-green" >{{item.ph}}</span>
+                                </el-col>
+                            </el-row>
+                        </div>
+                        <div class="gutter" @click="openItem(item, 4)">
+                            <el-row>
+                                <el-col :span="10" class="ammonia-line">
+                                    <span >水压:</span>
+                                </el-col>
+                                <el-col :span="14" style="text-align: left;">
+                                    <span v-if="item.sy === '' " class="text-red" style="font-size: 16px;">暂无数据</span>
+                                    <span v-else class="text-green" >{{item.sy}}Mpa</span>
+                                </el-col>
+                            </el-row>
+                        </div>
+                    </div>
+                </template>
+                <template v-else>
+                    <!-- {{ item.id }} -->
+                    <h3 class="h3_type_1">{{item.position}}</h3>
+                    <div class="warp"  @click="onOpen(item)">
+                        <div class="gutter">
+                            <el-row>
+                                <el-col :span="14" class="temp-line">
+                                    <i class="icon_temp"></i>
+                                    <span class="temp_text">温度:</span>
+                                </el-col>
+                                <el-col :span="10" style="text-align: left;">
+                                    <span v-if="item.temp === ''" class="text-red" style="font-size: 16px;">暂无数据</span>
+                                    <span v-else :class="item.temp > 35 ? 'text-red' : 'text-green'" >{{item.temp}}℃</span>
+                                </el-col>
+                            </el-row>
+                        </div>
+                        <div class="gutter"  @click="onOpen(item)">
+                            <el-row>
+                                <el-col :span="14" class="humidity-line">
+                                    <i class="icon_humidity"></i>
+                                    <span class="humidity_text">湿度:</span>
+                                </el-col>
+                                <el-col :span="10" class="gutter_text" style="text-align: left;">
+                                    <span v-if="item.humidity === ''" class="text-red" style="font-size: 16px;">暂无数据</span>
+                                    <span v-else :class="item.humidity > 93 ? 'text-red' : 'text-green'" >{{item.humidity}}RH</span>
+                                </el-col>
+                            </el-row>
+                        </div>
+                        <div class="gutter"  @click="onOpen(item)">
+                            <el-row>
+                                <el-col :span="14" class="ammonia-line">
+                                    <i class="icon_ammonia"></i>
+                                    <span class="ammonia_text">氨气:</span>
+                                </el-col>
+                                <el-col :span="10" style="text-align: left;">
+                                    <span v-if="item.ammonia === '' " class="text-red" style="font-size: 16px;">暂无数据</span>
+                                    <span v-else :class="item.ammonia > 3 ? 'text-red' : 'text-green'" >{{item.ammonia}}mg/m³</span>
+                                </el-col>
+                            </el-row>
+                        </div>
+                    </div>
+                </template>
+            </li>
+        </ul>
+    </div>
+</template>
+
+
+<script>
+let timer, timer1;
+
+export default {
+    data() {
+        return {
+            active: 1,
+            guideShow: false,
+            itemPositionInterval: 240, // 定位间隔
+            data: [],
+        };
+    },
+
+    created() {},
+    mounted() {
+        this.init();
+        this.setInit();
+    },
+    destroyed() {
+        clearInterval(timer);
+        clearInterval(timer1);
+    },
+    methods: {
+        toRight() {
+            let firstItemPositionLift = this.$refs.item0[0].getAttribute("data-position-left");
+            if (Number(firstItemPositionLift) <= -this.itemPositionInterval) {
+                this.data.unshift(this.data.pop());
+            }
+        },
+        toLeft() {
+            let firstItemPositionLift = this.$refs.item0[0].getAttribute("data-position-left");
+            if (Number(firstItemPositionLift) >= -this.itemPositionInterval) {
+                this.data.push(this.data.shift());
+            }
+        },
+        setInterval() {
+            let that = this;
+            timer = setInterval(() => {
+                that.toLeft();
+            }, 2000);
+        },
+        mouseenter(e) {
+            this.guideShow = true;
+            clearInterval(timer);
+        },
+        mouseleave() {
+            this.guideShow = false;
+            this.setInterval()
+        },
+        onOpen(item) {
+           this.$emit('onOpen', item);
+        },
+        openItem(item, i) {
+            this.$emit('openItem', {item: item, index: i});
+        },
+        init() {
+            this.$http.get('/environment/getZsda', {mcid: 39})
+                    .then(res => {
+                        // loading.close();
+                        if(res.code === 10000) {
+                            // this.data = res.data;
+                            this.data = [];
+                            let arr1 = [];
+                            let arr2 = [];
+                            res.data.forEach(item => {
+                                if(item.isQuanKong) {
+                                    this.$http.post('http://122.112.252.100:8088/allDataController/findByZSID', {ZSID: item.zsid, lqid: item.lqid})
+                                    .then(res => {
+                                        let slList = [];
+                                        let dlList = [];
+                                        let phList = [];
+                                        let syList = [];
+                                        Object.values(res.data.sl).forEach(key => {
+                                            slList.push(key);
+                                        });
+                                        let sl = Math.max(...slList);
+                                        Object.values(res.data.dl).forEach(key => {
+                                            dlList.push(key);
+                                        });
+                                        let dl = Math.max(...dlList);
+                                        Object.values(res.data.ph).forEach(key => {
+                                            phList.push(key);
+                                        });
+                                        Object.values(res.data.sy).forEach(key => {
+                                            syList.push(key);
+                                        });
+                                        let ph = Math.max(...phList);
+                                        let sy = Math.max(...syList);
+                                        if(res.data.zsid === item.zsid) {
+                                            arr1.push({
+                                                sl: sl,
+                                                dl: dl,
+                                                ph: ph,
+                                                sy: sy,
+                                                isQk: true,
+                                                position: item.zsmc,
+                                                lqid: res.data.lqid,
+                                                zsid: res.data.zsid,
+                                                dlLists: res.data.dl,
+                                                phLists: res.data.ph,
+                                                slLists: res.data.sl,
+                                                syLists: res.data.sy
+                                            });
+                                        }
+
+                                        this.data = arr1.concat(arr2);
+                                    })
+                                } else {
+                                    this.$http.get('/environment/getLastEnvByLq', {lqid: item.lqid})
+                                            .then(res => {
+                                                if(res.data.lqid === item.lqid) {
+                                                    res.data.position = item.zsmc;
+                                                    res.data.isQk = false;
+                                                }
+                                                arr2.push(res.data);
+                                                this.data = arr1.concat(arr2);
+                                            })
+                                }
+
+                            });
+                            // 数组排序
+                            this.data.sort(function(a, b){a.lqid - b.lqid});
+                            this.setInterval();
+                        }
+                    })
+                    .catch(err => {
+                        // this.loading = false;
+                        // loading.close();
+                    })
+        },
+        setInit() {
+            let that = this;
+            timer1 = setInterval(() => {
+                that.init();
+            }, 1800000)
+        }
+    },
+};
+</script>
+
+<style lang="scss" scope>
+.swiperMulti {
+    box-sizing: border-box;
+    width: 100%;
+    height: 100%;
+    position: relative;
+    cursor: pointer;
+    > .guide{
+        position: absolute;
+        font-size: 60px;
+        height: 100%;
+        display: flex;
+        align-items: center;
+        z-index: 1;
+        background-color: #4441;
+        &:hover{
+            background-color: #9991;
+        }
+        .icon{
+            transition: all 1s ease-in-out;
+        }
+    }
+    > .el-toRight {
+        left: 0;
+    }
+    > .el-toLeft {
+        right: 0;
+    }
+    ul {
+        width: 100%;
+        height: 100%;
+        li {
+            // border: 1px solid rgb(60, 206, 16);
+            background-color: #0D1943;
+            width: 220px;
+            height: 100%;
+            position: absolute;
+            transition: all 0.5s ease;
+            display: flex;
+            flex-direction: column;
+            > h3{
+                text-align: center;
+                font-size: 1.3rem;
+                font-weight: normal;
+            }
+            > .h3_type_1{
+                background-color: #207BC4;
+            }
+            > .h3_type_2{
+                background-color: #0E1E51;
+            }
+            > .warp {
+                flex-grow: 1;
+                // border: 1px solid rgb(221, 218, 16);
+                .gutter {
+                    padding: 30px 0 20px 0;
+                    text-align: center;
+                    font-size: 22px;
+                    .text-red {
+                        color: #F56C6C;
+                    }
+                    .text-green {
+                        color: #67C23A;
+                    }
+                    .temp-line {
+                        position: relative;
+                        .icon_temp {
+                            display: inline-block;
+                            width: 28px;
+                            height: 28px;
+                            background-image: url("../../assets/icon_T.png");
+                            background-size: 100% 100%;
+                            position: absolute;
+                            top: 0;
+                            left: 15px;
+                        }
+                        .temp_text {
+                            padding-left: 44px;
+                        }
+                    }
+                    .humidity-line {
+                        position: relative;
+                        .icon_humidity {
+                            display: inline-block;
+                            width: 28px;
+                            height: 28px;
+                            background-image: url("../../assets/icon_humidity.png");
+                            background-size: 100% 100%;
+                            position: absolute;
+                            top: 0;
+                            left: 15px;
+                        }
+                        .humidity_text {
+                            padding-left: 44px;
+                        }
+                    }
+                    .ammonia-line {
+                        .icon_ammonia {
+                            display: inline-block;
+                            width: 28px;
+                            height: 28px;
+                            background-image: url("../../assets/icon_meter.png");
+                            background-size: 100% 100%;
+                            position: absolute;
+                            top: 0;
+                            left: 15px;
+                        }
+                        .ammonia_text {
+                            padding-left: 44px;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+</style>

+ 524 - 0
src/views/monitor/monitor.vue

@@ -0,0 +1,524 @@
+<template>
+    <div class="monitor">
+        <div class="box">
+            <div class="tab-title">
+                <el-cascader
+                        ref="cascaderAddr"
+                        :options="poleOption"
+                        :props="{value: 'id', label: 'name', expandTrigger: 'hover'}"
+                        :show-all-levels="false"
+                        v-model="poleSelected"
+                        @change="unitChange"></el-cascader>
+                <div class="position">
+                    <img src="../../assets/u208.png" @click="open" alt="">
+                </div>
+            </div>
+            <div class="flex">
+                <template v-if="isSet">
+                    <template v-if="radio === 1">
+                        <div  class="default" v-for="(item, i) in selectList" :key="i">
+                            <div class="tab-title" v-show="isNone">{{item.name}}</div>
+                            <div class="img-size">
+                                <div class="video">
+                                  <iframe :class="activeClass == i ? 'actived' : ''"  :src="'static/index.html?'+'1'+'&' + item.wsUrl  +'&'+ item.rtspUrl + '&' + '100%' + '&' + i" frameborder="0"  style="height: 100%; width: 100%"></iframe>
+                                </div>
+                            </div>
+                        </div>
+                    </template>
+                    <template v-else-if="radio === 4">
+                        <div  class="col-4" v-for="(item, i) in selectList" :key="i">
+                            <div class="tab-title" v-show="isNone">{{item.name}}</div>
+                            <div class="img-size">
+                               <div class="video" >
+                                   <iframe :class="activeClass == i ? 'actived' : ''" :src="'static/index.html?'+'1'+'&' + item.wsUrl  +'&'+ item.rtspUrl + '&' + '100%' + '&' + i" frameborder="0"  style="height: 100%; width: 100%"></iframe>
+                               </div>
+                            </div>
+                        </div>
+                    </template>
+                    <template v-else-if="radio === 6">
+                        <div  class="col-6" v-for="(item, i) in selectList" :key="i">
+                            <div class="tab-title" v-show="isNone">{{item.name}}</div>
+                            <div class="img-size">
+                                <div class="video">
+                                  <iframe :class="activeClass == i ? 'actived' : ''" :src="'static/index.html?'+'1'+'&' + item.wsUrl  +'&'+ item.rtspUrl + '&' + '100%' + '&' + i" frameborder="0"  style="height: 100%; width: 100%"></iframe>
+                                </div>
+                            </div>
+                        </div>
+                    </template>
+                    <template v-else>
+                        <div  class="col-12" v-for="(item, i) in selectList" :key="i">
+                            <div class="tab-title" v-show="isNone">{{item.name}}</div>
+                            <div class="img-size">
+                              <div class="video">
+                                <iframe :class="activeClass == i ? 'actived' : ''" :src="'static/index.html?'+'1'+'&' + item.wsUrl  +'&'+ item.rtspUrl + '&' + '100%' + '&' + i" frameborder="0"  style="height: 100%; width: 100%"></iframe>
+                              </div>
+                            </div>
+                        </div>
+                    </template>
+                </template>
+                <template v-else>
+                    <div  class="default">
+                        <div class="tab-title" v-show="isNone">{{poleTitle}}</div>
+                        <div class="img-size">
+                            <div class="video" style="position: relative; text-align: center;">
+                                <iframe :class="activeClass == 0 ? 'actived' : ''" v-if="showIfrme" ref="iframe" :src="'static/index.html?'+'1'+'&'+ cameraOne +'&'+ cameraTwo + '&' +'100%' + '&' + '0'" frameborder="0"  style="height: 100%; width: 100%"></iframe>
+                            </div>
+                        </div>
+                    </div>
+                </template>
+            </div>
+        </div>
+        <el-dialog
+                title="视频窗口设置"
+                :visible.sync="dialogVisible"
+                width="40%">
+            <div>
+                <div class="radio">
+                    <span>视频展示数量:</span>
+                    <el-radio-group v-model="radio">
+                        <el-radio :label="1">1</el-radio>
+                        <el-radio :label="4">4</el-radio>
+                        <el-radio :label="6">6</el-radio>
+                        <el-radio :label="12">12</el-radio>
+                    </el-radio-group>
+                </div>
+<!--                <el-transfer v-model="value" :data="dataList" :titles="title" @change="handleChange"></el-transfer>-->
+                <h3 style="color: white; margin-bottom: 10px;">视频选择</h3>
+                <tree-transfer
+                        :title="title"
+                        :from_data="formData"
+                        :to_data="toData"
+                        :defaultProps="{label:'name'}"
+                        :mode='mode'
+                        openAll
+                        @addBtn='add'
+                        @removeBtn='remove'></tree-transfer>
+            </div>
+            <span slot="footer" class="dialog-footer">
+                 <el-button type="primary" @click="save" :disabled="getSave">保存</el-button>
+             </span>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import utils from "../../utils/utils";
+import treeTransfer from 'el-tree-transfer';
+export default {
+    name: "monitor",
+    components: {
+        treeTransfer
+    },
+    data() {
+		    return {
+		        // 栋楼
+            poleOption: [
+            ],
+            // 选中 栋楼
+            poleSelected: [],
+            // 单元楼
+            unitOption: [
+            ],
+            unitSelected: '',
+            //
+            title: ['视频列表', '已选中视频'],
+            radio: 1,
+            dataList: [],
+            formData: [],
+            toData: [],
+            dialogVisible: false,
+            rightList: [],
+            selectList: [],
+            mode: 'transfer',
+            disabled: true,
+            isSet: false,
+            showIfrme: false,
+            // 楼层选择
+            poleTitle: '',
+            cameraOne: '',
+            cameraTwo: '',
+            iframeWin: {},
+            isMag: false,
+            // 放大后
+            magOne: '',
+            magTwo: '',
+            screenWidth: '',
+            activeClass: -1,
+            isNone: true,
+        }
+    },
+    computed: {
+      getSave() {
+          if(this.toData.length > 0) {
+              return false
+          } else {
+              return true
+          }
+      }
+    },
+	  methods: {
+        open() {
+            this.dialogVisible = true;
+        },
+        save() {
+            let arr = [];
+            let arr1 = [];
+            arr = this.toData;
+            arr.forEach(item => {
+                item.children.forEach(e => {
+                    arr1.push(e);
+                })
+            });
+            let dataList = utils.group(arr1,  this.radio);
+            let strList = [];
+            dataList.forEach(item => {
+                strList.push(item.id);
+            });
+            let str = strList.join(',');
+            this.$http.post('http://122.112.252.100:8088/cameraInfo/getCameraDetails', {areaIds: str})
+            .then(res => {
+               if(res.status === 10000) {
+                   res.data.forEach(item => {
+                       dataList.forEach(list => {
+                           if(item.areaId === list.id) {
+                               list.rtspUrl = item.rtspUrl;
+                               list.wsUrl = item.wsUrl;
+                           }
+                       })
+                   });
+                   // console.log(dataList);
+                   this.selectList = dataList;
+                   this.isSet = true;
+               }else {
+                   this.$message.error(res.msg);
+                }
+            });
+            this.dialogVisible = false;
+        },
+        init() {
+            this.$http.post('http://122.112.252.100:8088/pastureArea/findAll')
+            .then(res => {
+                if(res.status === 10000) {
+                    this.poleOption = JSON.parse(JSON.stringify(res.data));
+                    let arr = [];
+                    arr = res.data;
+                    arr.forEach(item => {
+                        item.pid = item.parent;
+                        item.children.forEach(e => {
+                            e.pid = e.parent;
+                            e.children = [];
+                        })
+                    });
+                    this.formData = arr;
+                }
+            })
+        },
+        unitChange(item) {
+            this.isSet = false;
+            this.showIfrme = false;
+            if(this.poleSelected.length > 0) {
+                let val = this.poleSelected[this.poleSelected.length - 1];
+                this.$http.post('http://122.112.252.100:8088/cameraInfo/getCameraDetails', {areaIds: val})
+                        .then(res => {
+                            if(res.status === 10000) {
+                                this.showIfrme = true;
+                                let data = null;
+                                data = res.data[0];
+                                this.cameraOne = data.wsUrl;
+                                this.cameraTwo = data.rtspUrl;
+                            } else {
+                                this.showIfrme = false;
+                                this.$message.err(res.msg);
+                            }
+                        })
+            }
+
+           this.poleTitle = this.$refs['cascaderAddr'].getCheckedNodes()[0].pathLabels[1];
+        },
+        add(fromData,toData,obj) {
+            console.log(111);
+            console.log("fromData:", fromData);
+
+            console.log("toData:", toData);
+
+            console.log("obj:", obj);
+        },
+        remove(fromData,toData,obj) {
+            console.log("fromData:", fromData);
+
+            console.log("toData:", toData);
+
+            console.log("obj:", obj);
+        },
+        handleMessage(event) {
+            const data = event.data;
+            switch (data.cmd) {
+                case "returnDate":
+                    // this.isMag = true;
+                    // this.magOne = data.params.wsUrl;
+                    // this.magTwo = data.params.rtspUrl;
+                    // this.screenWidth = window.screen.width;
+                    this.activeClass = data.params.key;
+                    this.isNone = data.params.isNone;
+                    console.log(data.params);
+                    break;
+            }
+        }
+    },
+    created() {
+      this.init();
+    },
+    mounted() {
+        window.addEventListener("message", this.handleMessage);
+        this.unitChange();
+    },
+};
+</script>
+
+<style lang="scss" scope>
+.monitor {
+    box-sizing: border-box;
+    flex-grow: 1;
+    color: #eee;
+    padding: 20px;
+    .radio {
+        padding: 20px 0;
+    }
+    .box {
+        background-color: #060b2e;
+        width: 100%;
+        height: 100%;
+        .tab-title {
+            height: 45px;
+            line-height: 40px;
+            text-align: center;
+            position: relative;
+            margin-bottom: 10px;
+            .poleSelect {
+                border: solid 1px #000;
+                /*很关键:将默认的select选择框样式清除*/
+                /*加padding防止文字覆盖*/
+                padding-right: 14px;
+                padding-left: 15px;
+                height: 40px;
+                width: 200px;
+                margin-right: 20px;
+                background-color: #393d41;
+                cursor: pointer;
+                color: #ccc;
+                option {
+                    background-color: #F5F5F5;
+                    padding: 10px 0;
+                    color: #ccc;
+                }
+            }
+            .position {
+                position: absolute;
+                top: 8px;
+                right: 40px;
+                cursor: pointer;
+            }
+        }
+        .flex {
+            height: calc(100% - 55px);
+            display: flex;
+            justify-content: space-around;
+            flex-wrap: wrap;
+            width: 100%;
+
+            .default {
+                width: 100%;
+                height: 100%;
+                background-color: #0E1E51;
+                .tab-title {
+                    font-size: 16px;
+                    text-align: left;
+                    padding-left: 10%;
+                }
+                .tab-title:before {
+                    content: "";
+                    width: 14px;
+                    height: 14px;
+                    border-radius: 50%;
+                    position: absolute;
+                    left: 8%;
+                    top: 45%;
+                    background-color: #fff;
+                    transform: translateY(-50%);
+                    -ms-transform: translateY(-50%);
+                    -moz-transform: translateY(-50%);
+                    -webkit-transform: translateY(-50%);
+                    -o-transform: translateY(-50%);
+                }
+                .img-size {
+                    height: calc(100% - 45px);
+                    .video {
+                        width: 1650px;
+                        /*height: 90%;*/
+                        height: 100%;
+                        /*background-color: black;*/
+                        margin: 10px auto;
+                    }
+                }
+            }
+            .col-4 {
+                width: 45%;
+                height: 47%;
+                background-color: #0E1E51;
+                margin-bottom: 20px;
+                .tab-title {
+                    font-size: 16px;
+                    padding-left: 12%;
+                    text-align: left;
+                    margin-bottom: 5px;
+                }
+                .tab-title:before {
+                    content: "";
+                    width: 14px;
+                    height: 14px;
+                    border-radius: 50%;
+                    position: absolute;
+                    left: 8%;
+                    top: 45%;
+                    background-color: #fff;
+                    transform: translateY(-50%);
+                    -ms-transform: translateY(-50%);
+                    -moz-transform: translateY(-50%);
+                    -webkit-transform: translateY(-50%);
+                    -o-transform: translateY(-50%);
+                }
+                .img-size {
+                    height: calc(100% - 45px);
+                    .video {
+                        width: 700px;
+                        height: 100%;
+                        margin: 10px auto;
+                        /*background-color: black;*/
+                    }
+                }
+            }
+            .col-6 {
+                width: 33%;
+                height: 45%;
+                background-color: #0E1E51;
+                margin-bottom: 20px;
+                .tab-title {
+                    font-size: 16px;
+                    padding-left: 12%;
+                    text-align: left;
+                }
+                .tab-title:before {
+                    content: "";
+                    width: 14px;
+                    height: 14px;
+                    border-radius: 50%;
+                    position: absolute;
+                    left: 8%;
+                    top: 45%;
+                    background-color: #fff;
+                    transform: translateY(-50%);
+                    -ms-transform: translateY(-50%);
+                    -moz-transform: translateY(-50%);
+                    -webkit-transform: translateY(-50%);
+                    -o-transform: translateY(-50%);
+                }
+                .img-size {
+                    height: calc(100% - 45px);
+                    .video {
+                        /*width: 500px;*/
+                        width: 100%;
+                        height: 90%;
+                        margin: 10px auto;
+                        /*background-color: black;*/
+                    }
+                }
+            }
+            .col-12 {
+                width: 22%;
+                height: 32%;
+                background-color: #0E1E51;
+                margin-bottom: 20px;
+
+                .tab-title {
+                    font-size: 16px;
+                    padding-left: 15%;
+                    text-align: left;
+                }
+                .tab-title:before {
+                    content: "";
+                    width: 14px;
+                    height: 14px;
+                    border-radius: 50%;
+                    position: absolute;
+                    left: 8%;
+                    top: 45%;
+                    background-color: #fff;
+                    transform: translateY(-50%);
+                    -ms-transform: translateY(-50%);
+                    -moz-transform: translateY(-50%);
+                    -webkit-transform: translateY(-50%);
+                    -o-transform: translateY(-50%);
+                }
+                .img-size {
+                    height: calc(100% - 45px);
+                    text-align: center;
+                    .video {
+                        width: 100%;
+                        height: 90%;
+                        /*background-color: black;*/
+                        margin: 10px auto;
+                    }
+                }
+            }
+        }
+    }
+}
+option{
+    -moz-appearance:none; /* Firefox */
+    -webkit-appearance:none; /* Safari 和 Chrome */
+    appearance:none;
+}
+/* --背景色字体颜色--*/
+option:hover{
+    color:#fff;
+    background-color:#1E90FF;
+}
+.el-select .el-input__inner {
+    background-color: #393d41 !important;
+    color: white !important;
+}
+.magVideo .el-dialog {
+    background-color: #0E1E51 !important;
+    margin: 0 !important;
+    height: 100% !important;
+}
+.magVideo .el-dialog__header {
+    margin: 0 !important;
+    padding: 0 !important;
+    height: 0 !important;
+}
+.magVideo .el-dialog__body {
+    height: 100% !important;
+    padding: 0 !important;
+    margin: 0 !important;
+}
+.el-dialog, .el-pager li {
+    background-color: #021429 !important;
+}
+.wl-transfer .transfer-title {
+    background-color: #021429 !important;
+}
+.el-tree {
+    background-color: #021429 !important;
+}
+.el-dialog__headerbtn .el-dialog__close {
+    color: white !important;
+    font-size: 40px !important;
+}
+.actived {
+    position: fixed;
+    width: 1920px;
+    height: 100vh;
+    left: 0;
+    top: 0;
+}
+</style>

+ 836 - 0
src/views/robot/robot.vue

@@ -0,0 +1,836 @@
+<template>
+    <div class="robot">
+        <div class="flex">
+            <div class="left">
+                <!-- 人脸识别   -->
+                <div class="box">
+                    <div class="tab-t">
+                        <div class="tab-title">人脸识别记录</div>
+                    </div>
+                    <div class="box-content">
+                        <div>
+                            <img class="img-size" src="../../assets/u525.png" alt="">
+                        </div>
+                        <div class="person-text">
+                            <p>识别时间:{{dateValue}}</p>
+                            <p>识别地点:1栋3单元 </p>
+                            <p>姓名:张一力</p>
+                            <p>职位:饲养员</p>
+                            <p>体温:36.6℃</p>
+                        </div>
+                    </div>
+                </div>
+                <!-- 机器人操控 -->
+                <div class="control">
+                    <div class="tab-t">
+                        <div class="tab-title">机器人操控</div>
+                    </div>
+                    <div class="box-content">
+                        <div class="control-left">
+                            <img class="img-position img-top" @click="onDirection(1)" src="../../assets/u532.svg" alt="">
+                            <img class="img-position img-right" @click="onDirection(4)" src="../../assets/u532.svg" alt="">
+                            <img class="img-position img-bottom" @click="onDirection(2)" src="../../assets/u532.svg" alt="">
+                            <img class="img-position img-left"  @click="onDirection(3)" src="../../assets/u532.svg" alt="">
+                            <button class="button button-red" @click="stop">停止</button>
+                        </div>
+                        <div class="control-right">
+                            <div>
+                                <button class="button button-green" @click="start">自动</button>
+                                <button class="button button-blue">拉近</button>
+                            </div>
+                            <div>
+                                <button class="button button-gray" @click="operation">手动</button>
+                                <button class="button button-blue">拉远</button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <!-- 报警信息 -->
+                <div class="box box_height">
+                    <div class="tab-t">
+                        <div class="tab-title">报警信息</div>
+                    </div>
+                    <div class="call-text">
+                        <p class="call-detail" v-for="(item, i) in arr" :key="i">
+                            <span>{{item.date}}</span>
+                            <span style="padding-left: 15px;">{{item.position}}</span>
+                            <span style="padding-left: 15px;">{{item.asl}}</span>
+                        </p>
+                    </div>
+                </div>
+            </div>
+            <div class="right">
+                <div class="container">
+                    <div class="container-left">
+                        <div class="line1">
+                            <div class="today">{{date}}</div>
+                            <div class="test">机器人巡查</div>
+                        </div>
+                        <div class="container-content">
+                            <div class="line2">
+                                <select name="1" class="select-robot">
+                                    <option value ="1">1号机器人(巡查中-1栋)</option>
+                                    <option value ="2">2号机器人(充电中)</option>
+                                    <option value="3">3号机器人(故障)</option>
+                                </select>
+                            </div>
+                            <div class="container-video">
+                                <video src="../../assets/robot.mp4" controls style="width: 785px; height: 430px;">您的浏览器暂不支持Video播放</video>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="container-right">
+                        <!-- 温度统计图  -->
+                        <div class="wrapper">
+                            <div class="tab-t">
+                                <div class="tab-title">温度走势图</div>
+                            </div>
+                            <div class="wrapper-content">
+                                <div ref="chartRight1" style="width: 100%; height: 100%"></div>
+                            </div>
+                        </div>
+                        <!-- 湿度统计图  -->
+                        <div class="wrapper">
+                            <div class="tab-t">
+                                <div class="tab-title">湿度走势图</div>
+                            </div>
+                            <div class="wrapper-content">
+                                <div ref="chartRight2" style="width: 100%; height: 100%"></div>
+                            </div>
+                        </div>
+                        <!-- 温度统计图  -->
+                        <div class="wrapper">
+                            <div class="tab-t">
+                                <div class="tab-title">氨气浓度走势图</div>
+                            </div>
+                            <div class="wrapper-content">
+                                <div ref="chartRight3" style="width: 100%; height: 100%"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="robot-path">
+                    <div class="robot-title">机器人实时路线</div>
+                    <!-- canvas画图 -->
+                    <div class="robot-canvas">
+                        <robot-canvas ref="rb_canvas"></robot-canvas>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import utils from '../../utils/utils';
+import RobotCanvas from '../../components/robotCanvas';
+export default {
+    name: "robot",
+    components: {
+        RobotCanvas
+    },
+    data() {
+		return {
+		    optionsWd: {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    animation: false
+                }
+            },
+            xAxis: [
+                {
+                    type: 'category',
+                    boundaryGap: false,
+                    axisLine: {onZero: true},
+                    data: [],
+                    name: '时间',
+                    nameTextStyle: {
+                        color: '#ffffff',
+                    },
+                    axisLabel: {
+                        color: '#ffffff'
+                    },
+                    axisLine: {
+                        lineStyle: {
+                            color: '#ffffff'
+                        }
+                    }
+                },
+            ],
+            yAxis: [
+                {
+                    name: '摄氏度:℃',
+                    type: 'value',
+                    max: 35,
+                    nameTextStyle: {
+                        color: '#ffffff'
+                    },
+                    axisLabel: {
+                        color: '#ffffff'
+                    },
+                    axisLine: {
+                        lineStyle: {
+                            color: '#ffffff'
+                        }
+                    }
+                },
+            ],
+            series: [
+                {
+                    name: '摄氏度:℃',
+                    type: 'line',
+                    symbolSize: 8,
+                    hoverAnimation: false,
+                    data: [15, 20, 20, 21, 22, 26, 24, 28, 25, 26, 21, 22, 23, 24, 26, 26, 26, 24, 25, 22, 20, 19, 18, 25, 26, 27, 26, 25, 25, 21,
+                            20, 21, 20, 22, 23, 25, 24, 26, 28, 27, 26, 24, 21, 20, 24, 24, 25, 28
+                    ]
+                },
+            ]
+        },
+        optionsSd: {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    animation: false
+                }
+            },
+            xAxis: [
+                {
+                    type: 'category',
+                    boundaryGap: false,
+                    axisLine: {onZero: true},
+                    data: ['00:00', '00:30', '01:00', '01:30', "02:00", "02:30", "03:00", "03:30", "04:00", "04:30","05:00","05:30","06:00","06:30","07:00",
+                        "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", "10:30", "11:00", "11:30", "12:00", "12:30", "13:00", "13:30", "14:00", "14:30",
+                        "15:00", "15:30", "16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", "21:00", "21:30", "22:00",
+                        "22:30", "23:00", "23:30"
+                    ],
+                    name: '时间',
+                    nameTextStyle: {
+                        color: '#ffffff',
+                    },
+                    axisLabel: {
+                        color: '#ffffff'
+                    },
+                    axisLine: {
+                        lineStyle: {
+                            color: '#ffffff'
+                        }
+                    }
+                },
+            ],
+            yAxis: [
+                {
+                    name: 'RH',
+                    type: 'value',
+                    max: 100,
+                    nameTextStyle: {
+                        color: '#ffffff'
+                    },
+                    axisLabel: {
+                        color: '#ffffff'
+                    },
+                    axisLine: {
+                        lineStyle: {
+                            color: '#ffffff'
+                        }
+                    }
+                },
+            ],
+            series: [
+                {
+                    name: 'RH',
+                    type: 'line',
+                    symbolSize: 8,
+                    hoverAnimation: false,
+                    data: [70, 80, 75, 60, 52, 76, 84, 88, 75, 66, 71, 72, 73, 74, 76, 76, 67, 74, 75, 72, 80, 79, 78, 75, 76, 77, 67, 75, 85, 71,
+                        70, 71, 70, 72, 73, 75, 74, 76, 78, 77, 76, 74, 71, 70, 74, 74, 77, 78
+                    ]
+                },
+            ]
+        },
+        optionsAq: {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    animation: false
+                }
+            },
+            xAxis: [
+                {
+                    type: 'category',
+                    boundaryGap: false,
+                    axisLine: {onZero: true},
+                    data: ['00:00', '00:30', '01:00', '01:30', "02:00", "02:30", "03:00", "03:30", "04:00", "04:30","05:00","05:30","06:00","06:30","07:00",
+                        "07:30", "08:00", "08:30", "09:00", "09:30", "10:00", "10:30", "11:00", "11:30", "12:00", "12:30", "13:00", "13:30", "14:00", "14:30",
+                        "15:00", "15:30", "16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00", "19:30", "20:00", "20:30", "21:00", "21:30", "22:00",
+                        "22:30", "23:00", "23:30"
+                    ],
+                    name: '时间',
+                    nameTextStyle: {
+                        color: '#ffffff',
+                    },
+                    axisLabel: {
+                        color: '#ffffff'
+                    },
+                    axisLine: {
+                        lineStyle: {
+                            color: '#ffffff'
+                        }
+                    }
+                },
+            ],
+            yAxis: [
+                {
+                    name: 'mg/m³',
+                    type: 'value',
+                    max: 5,
+                    nameTextStyle: {
+                        color: '#ffffff'
+                    },
+                    axisLabel: {
+                        color: '#ffffff'
+                    },
+                    axisLine: {
+                        lineStyle: {
+                            color: '#ffffff'
+                        }
+                    }
+                },
+            ],
+            series: [
+                {
+                    name: 'mg/m³',
+                    type: 'line',
+                    symbolSize: 8,
+                    hoverAnimation: false,
+                    data: [
+                            0.5, 1.2, 2.8, 3, 2.1, 2.4, 2.3, 1.2, 1.1, 1, 0.8, 2.1, 2.2, 2.3, 2.4, 2.7, 2.1, 1.5, 1.4, 2.3, 2.4, 1.1, 1.2, 1.3, 1.4, 1.6, 2.2,
+                            2.8, 2.1, 1.5, 1.1, 1.2, 1.2, 1.4, 1.5, 1.1, 0.9, 0.8, 0.7, 0.5, 0.9, 1.2, 1.9, 1.4, 1.5, 1.6, 1.7, 1.5, 1.2, 1
+                    ]
+                },
+            ]
+        },
+        date: '',
+        dateValue: '',
+        arr: [ {date: '10-30 08:38:28', position: '1栋3单元', asl: '1号栏出现猪只体温异常'} ],
+    }
+    },
+    created() {},
+	  methods: {
+        getEchartData() {
+            const chartRight1 = this.$refs.chartRight1;
+            const chartRight2 = this.$refs.chartRight2;
+            const chartRight3 = this.$refs.chartRight3;
+
+            const myChartRight1 = this.$echarts.init(chartRight1);
+            const myChartRight2 = this.$echarts.init(chartRight2);
+            const myChartRight3 = this.$echarts.init(chartRight3);
+
+            myChartRight1.setOption(this.optionsWd);
+            myChartRight2.setOption(this.optionsSd);
+            myChartRight3.setOption(this.optionsAq);
+
+            window.addEventListener("resize", function () {
+                myChartRight1.resize();
+                myChartRight2.resize();
+                myChartRight3.resize();
+            });
+            this.$on('hook:destroyed:', () => {
+                window.removeEventListener("resize", function() {
+                    myChartRight1.resize();
+                    myChartRight2.resize();
+                    myChartRight3.resize();
+                });
+            })
+        },
+        onDirection(data) {
+            if(data == 1) {
+                // this.$refs.rb_canvas.goUp();
+            } else if(data == 2){
+                // this.$refs.rb_canvas.goBottom();
+            } else if (data == 3) {
+                // this.$refs.rb_canvas.goLeft();
+            } else {
+                // this.$refs.rb_canvas.goRight();
+            }
+            this.$message.error('暂时无法点击!');
+        },
+        start() {
+            // this.$message.error('自动操作暂时关闭!');
+            this.$refs.rb_canvas.startInterval();
+        },
+        stop() {
+            // this.$message.error('停止操作暂时关闭');
+            this.$refs.rb_canvas.stopInterval();
+        },
+        operation() {
+            this.$message.error('手动操作暂时关闭!');
+        },
+        getRandomIntInclusive(min, max) {
+            // min = Math.ceil(min);
+            // max = Math.floor(max);
+            return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
+        },
+        setInter() {
+            let that = this;
+            setInterval(() => {
+                let date = Date.parse(new Date());
+                let dateNum = utils.timeMount(date);
+                let num = this.getRandomIntInclusive(1, 3);
+                let dan = this.getRandomIntInclusive(1, 4);
+                let position = num + '栋' + dan + '单元';
+                let asl = num + '号栏出现猪只体温异常';
+                that.$nextTick(() => {
+                    that.arr.unshift({date: dateNum, position: position, asl: asl});
+                });
+            }, 60000);
+        },
+        randomData(i) {
+            let now = new Date();
+            now = new Date(now - 60000 * i);
+            let mm = now.getHours() < 10 ? '0' + now.getHours() : now.getHours();
+            let ss = now.getMinutes() < 10 ? '0' + now.getMinutes() : now.getMinutes();
+            return mm + ':' + ss;
+        },
+        wendu() {
+            return Math.floor(Math.random() * 11) + 18;
+        },
+        shidu() {
+            return Math.floor(Math.random() * 40) + 40;
+        },
+        anqi() {
+            return Math.floor(Math.random() * 6);
+        },
+        setTime() {
+            this.optionsWd.xAxis[0].data= [];
+            this.optionsWd.series[0].data = [];
+            for (var i = 0; i <= 10; i++) {
+                // this.$nextTick(() => {
+                    this.optionsWd.xAxis[0].data.unshift(this.randomData(i));
+                    this.optionsSd.xAxis[0].data = this.optionsWd.xAxis[0].data;
+                    this.optionsAq.xAxis[0].data = this.optionsWd.xAxis[0].data;
+                    // 温度
+                    this.optionsWd.series[0].data.unshift(this.wendu());
+                    // 湿度
+                    this.optionsSd.series[0].data.unshift(this.shidu());
+                    // 氨气
+                    this.optionsAq.series[0].data.unshift(this.anqi());
+                // })
+            }
+            this.getEchartData();
+        },
+        setcommit() {
+            setInterval(() => {
+                this.setTime();
+            }, 60000)
+        }
+    },
+    mounted() {
+        let date = Date.parse(new Date());
+        this.date = utils.timestamp(date);
+        let dates = utils.timestamp(date).split('-')[0];
+        this.dateValue =  utils.timeMount(date);
+        // this.getEchartData();
+        this.setInter();
+        this.setTime();
+        this.setcommit();
+    },
+    // 离开这个页面 关掉定时器
+    beforeRouteLeave (to, from, next) {
+        this.stop();
+        next();
+    }
+};
+</script>
+
+<style lang="scss" scope>
+.robot {
+    box-sizing: border-box;
+    flex-grow: 1;
+    color: #eee;
+    .button {
+        width: 95px;
+        height: 38px;
+        font-size: 16px;
+        margin-right: 10px;
+        margin-bottom: 10px;
+        color: white;
+        border: 0;
+        background-color: transparent;
+        outline: none;
+        border-radius: 5px;
+        cursor: pointer;
+    }
+    .flex {
+        height: calc(100% - 40px);
+        display: flex;
+        padding: 20px 20px 0 20px;
+        .left {
+            width: 500px;
+            .box {
+                border-radius: 20px;
+                overflow: hidden;
+                margin-bottom: 30px;
+                .tab-t {
+                    height: 40px;
+                    position: relative;
+                    line-height: 40px;
+                    padding-left: 80px;
+                    background-color: rgba(14, 30, 81, 1);
+                    .tab-title {
+                        font-size: 18px;
+                    }
+                    .tab-title:before {
+                        /*<!--content: '';-->*/
+                        /*<!--width: 16px;-->*/
+                        /*<!--height: 16px;-->*/
+                        /*<!--background: #1eff00;-->*/
+                        /*<!--border: 1px solid #1eff00;-->*/
+                        /*<!--transform: rotate(-45deg);-->*/
+                        /*<!--position: absolute;-->*/
+                        /*<!--top: 10px;-->*/
+                        /*<!--left: 30px;-->*/
+                        content: "";
+                        width: 14px;
+                        height: 14px;
+                        border-radius: 50%;
+                        position: absolute;
+                        left: 8%;
+                        top: 50%;
+                        background-color: #fff;
+                        transform: translateY(-50%);
+                        -ms-transform: translateY(-50%);
+                        -moz-transform: translateY(-50%);
+                        -webkit-transform: translateY(-50%);
+                        -o-transform: translateY(-50%);
+                    }
+                }
+                .box-content {
+                    padding: 10px;
+                    background-color: rgb(13, 25, 67);
+                    text-align: center;
+                    display: flex;
+                    justify-content: center;
+                    align-items: center;
+                    .img-size {
+                        width: 210px;
+                    }
+                    .person-text {
+                        text-align: left;
+                        padding-left: 20px;
+                        color: #81D3F8;
+                        font-size: 20px;
+                    }
+                }
+                .call-text {
+                    background-color: rgb(13, 25, 67);
+                    padding: 10px;
+                    overflow-y: scroll;
+                    height: 195px;
+                    .call-detail {
+                        background-color: rgb(6, 10, 45);
+                        color: #F56C6C;
+                        margin-bottom: 10px;
+                        font-size: 20px;
+                    }
+                }
+                .call-text::-webkit-scrollbar {
+                    /*滚动条整体样式*/
+                    width : 5px;  /*高宽分别对应横竖滚动条的尺寸*/
+                    height: 1px;
+                }
+                .call-text::-webkit-scrollbar-thumb {
+                    /*滚动条里面小方块*/
+                    border-radius   : 8px;
+                    background-color: #535353;
+                    background-image: -webkit-linear-gradient(
+                                    45deg,
+                                    rgba(255, 255, 255, 0.2) 25%,
+                                    transparent 25%,
+                                    transparent 50%,
+                                    rgba(255, 255, 255, 0.2) 50%,
+                                    rgba(255, 255, 255, 0.2) 75%,
+                                    transparent 75%,
+                                    transparent
+                    );
+                }
+                .call-text::-webkit-scrollbar-track {
+                    /*滚动条里面轨道*/
+                    box-shadow   : inset 0 0 5px rgba(0, 0, 0, 0.2);
+                    /*background   : #ededed;*/
+                    border-radius: 10px;
+                }
+            }
+            .box_height {
+                height: calc(100% - 642px);
+                margin-bottom: 0;
+            }
+            .control {
+                border-radius: 20px;
+                overflow: hidden;
+                margin-bottom: 30px;
+                .tab-t {
+                    height: 40px;
+                    position: relative;
+                    line-height: 40px;
+                    padding-left: 80px;
+                    background-color: rgba(14, 30, 81, 1);
+                    .tab-title {
+                        font-size: 18px;
+                    }
+                    .tab-title:before {
+                        /*content: '';
+                        width: 16px;
+                        height: 16px;
+                        background: #1eff00;
+                        border: 1px solid #1eff00;
+                        transform: rotate(-45deg);
+                        position: absolute;
+                        top: 10px;
+                        left: 30px;*/
+                        content: "";
+                        width: 14px;
+                        height: 14px;
+                        border-radius: 50%;
+                        position: absolute;
+                        left: 8%;
+                        top: 50%;
+                        background-color: #fff;
+                        transform: translateY(-50%);
+                        -ms-transform: translateY(-50%);
+                        -moz-transform: translateY(-50%);
+                        -webkit-transform: translateY(-50%);
+                        -o-transform: translateY(-50%);
+                    }
+                }
+                .box-content {
+                    padding: 30px 15px;
+                    background-color: rgb(13, 25, 67);
+                    display: flex;
+                    .control-left {
+                        width: 55%;
+                        position: relative;
+                        .img-position {
+                            position: absolute;
+                            width: 50px;
+                            cursor: pointer;
+                        }
+                        .img-top {
+                            transform: rotate(90deg);
+                            top: 10px;
+                            left: 130px;
+                        }
+                        .img-right {
+                            transform: rotate(180deg);
+                            right: 45px;
+                            top: 50px;
+                        }
+                        .img-bottom {
+                            transform: rotate(-90deg);
+                            bottom: 20px;
+                            left: 130px;
+                        }
+                        .img-left {
+                            top: 50px;
+                            left: 90px;
+                        }
+                        .button-red {
+                            background-color: #ee0a24;
+                        }
+                    }
+                    .control-right {
+                        padding: 20px 0;
+                        .button-blue {
+                            background-color: rgb(2, 167, 240);
+                        }
+                        .button-gray {
+                            background-color: rgb(170, 170, 170);
+                        }
+                        .button-green {
+                            background-color: #07c160;
+                        }
+                    }
+                }
+            }
+        }
+        .right {
+            width: calc(100% - 500px);
+            .container {
+                display: flex;
+                .container-left {
+                    flex-grow: 1;
+                    .line1 {
+                        width: 100%;
+                        /*text-align: center;*/
+                        font-size: 1.3em;
+                        display: flex;
+                        margin-bottom: 10px;
+                        .today {
+                            width: 50%;
+                            padding-left: 5%;
+                            position: relative;
+                            /*box-sizing: content-box;*/
+                        }
+                        .today:after {
+                            content: '';
+                            height: 0px;
+                            width: 52%;
+                            border: 1px solid #06dfff;
+                            position: absolute;         /*定位背景横线的位置*/
+                            top: 40%;
+                            left: 40%;
+                        }
+                        .test {
+                            width: 50%;
+                            position: relative;
+                            /*box-sizing: content-box;*/
+                        }
+                        .test:after {
+                            content: '';
+                            height: 0px;
+                            width: 52%;
+                            border: 1px solid #06dfff;
+                            position: absolute;         /*定位背景横线的位置*/
+                            top: 40%;
+                            left: 30%;
+                        }
+                    }
+                    .container-content {
+                        box-sizing: border-box;
+                        height: calc(100% - 50px);
+                        padding: 0 20px;
+                        .line2 {
+                            height: 50px;
+                            border-radius: 20px 20px 0 0;
+                            background-color: #0e1e51;
+                            margin-bottom: 5px;
+                            padding-top: 10px;
+                            box-sizing: border-box;
+                            .select-robot {
+                                /*-webkit-appearance:none;*/
+                                /*-webkit-tap-highlight-color: rgba(0, 0, 0, 0);*/
+                                height: 35px;
+                                margin-left: 50px;
+                                border: solid 1px #000;
+                                /*很关键:将默认的select选择框样式清除*/
+                                /*appearance:none;*/
+                                /*-moz-appearance:none;*/
+                                /*-webkit-appearance:none;*/
+                                /*outline: none;*/
+                                /*加padding防止文字覆盖*/
+                                padding-right: 14px;
+                                padding-left: 15px;
+                                /*height: 40px;*/
+                                width: 200px;
+                                margin-right: 20px;
+                                background-color: #393d41;
+                                cursor: pointer;
+                                color: #ccc;
+                                option {
+                                    background-color: #ffffff;
+                                    padding: 10px 0;
+                                    color: #ddd;
+                                }
+                            }
+                        }
+                        .container-video {
+                            height: calc(100% - 55px);
+                            background-color: #0d1943;
+                            border-radius: 0 0 20px 20px;
+                            box-sizing: border-box;
+                            padding: 20px;
+                        }
+                    }
+                }
+                .container-right {
+                    width: 500px;
+                    .wrapper {
+                        border-radius: 6px;
+                        height: 175px;
+                        /*background-color: #001346;*/
+                        overflow: hidden;
+                        margin-bottom: 10px;
+
+                        .tab-t {
+                            height: 40px;
+                            position: relative;
+                            line-height: 40px;
+                            padding-left: 80px;
+                            background-color: rgba(14, 30, 81, 1);
+
+                            .tab-title {
+                                font-size: 18px;
+                            }
+
+                            .tab-title:before {
+                                /*content: '';
+                                width: 8px;
+                                height: 8px;
+                                background: #1eff00;
+                                border: 1px solid #1eff00;
+                                transform: rotate(-45deg);
+                                position: absolute;
+                                top: 15px;
+                                left: 50px;*/
+                                content: "";
+                                width: 14px;
+                                height: 14px;
+                                border-radius: 50%;
+                                position: absolute;
+                                left: 8%;
+                                top: 50%;
+                                background-color: #fff;
+                                transform: translateY(-50%);
+                                -ms-transform: translateY(-50%);
+                                -moz-transform: translateY(-50%);
+                                -webkit-transform: translateY(-50%);
+                                -o-transform: translateY(-50%);
+                            }
+                        }
+
+                        .wrapper-content {
+                            height: calc(100% - 20px);
+                            background-color: #0d1943;
+                            position: relative;
+                        }
+                    }
+                }
+            }
+            .robot-path {
+                height: 330px;
+                padding: 0 0 0 20px;
+                .robot-title {
+                    height: 30px;
+                    background-color: rgb(14, 30, 81);
+                    line-height: 30px;
+                    padding-left: 50px;
+                }
+                .robot-canvas {
+                    height: 280px;
+                    background-color: #0d1943;
+                    padding-top: 10px;
+                    position: relative;
+                    .canvas {
+                        width: 1200px;
+                        height: 100%;
+                        position: absolute;
+                        left: 90px;
+                        top: 10px;
+                    }
+                }
+            }
+        }
+    }
+}
+option{
+    -moz-appearance:none; /* Firefox */
+    -webkit-appearance:none; /* Safari 和 Chrome */
+    appearance:none;
+}
+/* --背景色字体颜色--*/
+option:hover{
+    color:#fff;
+    background-color:#1E90FF;
+}
+</style>

+ 13 - 0
vue.config.js

@@ -0,0 +1,13 @@
+module.exports = {
+    publicPath: './', // 相对于 HTML 页面(目录相同)
+    // 添加web worker loader
+    configureWebpack: config => {
+        config.module.rules.push({
+            test: /\.worker\.js$/,
+            use: {
+                loader: 'worker-loader',
+                options: { inline: true }
+            }
+        })
+    },
+}