Parcourir la source

Merge pull request #12 from ForeverHYH/master

修改web端项目结构,支持TS语法
hujinliang il y a 5 ans
Parent
commit
b4ccc88463

+ 4 - 8
web/.babelrc

@@ -1,14 +1,10 @@
 {
   "presets": [
-    [
-      "es2015",
-      {
-        "modules": false
-      }
-    ]
+    "@babel/preset-env",
+    "@babel/preset-typescript"
   ],
   "plugins": [
-    "external-helpers",
-    "async-to-promises"
+    "async-to-promises",
+    ["@babel/plugin-transform-runtime"]
   ]
 }

Fichier diff supprimé car celui-ci est trop grand
+ 1240 - 282
web/dist/vap.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
web/dist/vap.min.js


+ 14 - 21
web/package.json

@@ -15,28 +15,21 @@
   "author": "hujinliang",
   "license": "MIT",
   "devDependencies": {
-    "babel-core": "^6.26.0",
+    "@babel/core": "^7.11.6",
+    "@babel/plugin-external-helpers": "^7.10.4",
+    "@babel/plugin-transform-runtime": "^7.11.5",
+    "@babel/preset-env": "^7.11.5",
+    "@babel/preset-typescript": "^7.10.4",
+    "@rollup/plugin-node-resolve": "^9.0.0",
     "babel-plugin-async-to-promises": "^1.0.5",
-    "babel-plugin-external-helpers": "^6.22.0",
-    "babel-plugin-transform-async-to-generator": "^6.24.1",
-    "babel-plugin-transform-regenerator": "^6.26.0",
     "babel-preset-env": "^1.7.0",
-    "babel-preset-latest": "^6.24.1",
-    "babel-preset-stage-2": "^6.24.1",
-    "babel-runtime": "^6.26.0",
-    "es6-promise": "^4.2.4",
-    "postcss-url": "^7.3.2",
-    "promise-polyfill": "^8.1.0",
-    "rollup": "0.58.2",
-    "rollup-plugin-babel": "^3.0.3",
-    "rollup-plugin-commonjs": "^9.1.5",
-    "rollup-plugin-ejs": "^1.1.1",
-    "rollup-plugin-image": "^1.0.2",
-    "rollup-plugin-node-resolve": "^3.3.0",
-    "rollup-plugin-postcss": "^1.6.1",
-    "rollup-plugin-uglify": "^3.0.0",
-    "rollup-plugin-url": "^1.4.0",
-    "text-encoding": "^0.7.0",
-    "whatwg-fetch": "^3.0.0"
+    "babel-preset-typescript": "^7.0.0-alpha.19",
+    "rollup": "^2.28.2",
+    "rollup-plugin-babel": "^4.4.0",
+    "rollup-plugin-commonjs": "^10.1.0",
+    "rollup-plugin-node-resolve": "^5.2.0",
+    "rollup-plugin-typescript2": "^0.27.3",
+    "rollup-plugin-uglify": "^6.0.4",
+    "typescript": "^4.0.3"
   }
 }

+ 31 - 22
web/rollup.config.dist.js

@@ -1,29 +1,38 @@
 import resolve from "rollup-plugin-node-resolve";
 import babel from "rollup-plugin-babel";
-import uglify from "rollup-plugin-uglify";
+import { uglify } from "rollup-plugin-uglify";
 import commonjs from 'rollup-plugin-commonjs';
+import typescript from "rollup-plugin-typescript2";
 
+const extensions = ['.js','.ts'];
 
 export default [
-    {
-        input: "src/index.js",
-        output: {
-            name: "howLongUntilLunch",
-            file: "dist/vap.min.js",
-            format: "umd"
-        },
-        plugins: [
-            resolve(), // so Rollup can find `ms`
-            commonjs(),
-            babel({
-                exclude: "node_modules/**"
-            }),
-            uglify({
-                compress: {
-                    drop_console: true,
-                    drop_debugger: true
-                }
-            })
-        ]
-    }
+  {
+    input: "src/index.ts",
+    output: {
+      name: "howLongUntilLunch",
+      file: "dist/vap.min.js",
+      format: "umd"
+    },
+    plugins: [
+      typescript({
+        tsconfig: "rollup.tsconfig.json"
+      }),
+      resolve(), // so Rollup can find `ms`
+      commonjs({
+        include:'node_modules/**'
+      }),
+      babel({
+        exclude: "node_modules/**",
+        extensions,
+        runtimeHelpers: true
+      }),
+      uglify({
+        compress: {
+          drop_console: true,
+          drop_debugger: true
+        }
+      })
+    ]
+  }
 ];

+ 24 - 15
web/rollup.config.js

@@ -1,22 +1,31 @@
 import resolve from "rollup-plugin-node-resolve";
 import babel from "rollup-plugin-babel";
 import commonjs from 'rollup-plugin-commonjs';
+import typescript from "rollup-plugin-typescript2";
 
+const extensions = ['.js','.ts'];
 
 export default [
-    {
-        input: "src/index.js",
-        output: {
-            name: "howLongUntilLunch",
-            file: "dist/vap.js",
-            format: "umd"
-        },
-        plugins: [
-            resolve(), // so Rollup can find `ms`
-            commonjs(),
-            babel({
-                exclude: "node_modules/**"
-            }),
-        ]
-    }
+  {
+    input: "src/index.ts",
+    output: {
+      name: "howLongUntilLunch",
+      file: "dist/vap.js",
+      format: "umd"
+    },
+    plugins: [
+      typescript({
+        tsconfig: "rollup.tsconfig.json"
+      }),
+      resolve(), // so Rollup can find `ms`
+      commonjs({
+        include:'node_modules/**'
+      }),
+      babel({
+        exclude: "node_modules/**",
+        extensions,
+        runtimeHelpers: true
+      }),
+    ]
+  }
 ];

+ 15 - 0
web/rollup.tsconfig.json

@@ -0,0 +1,15 @@
+{
+  "compilerOptions": {
+    "target": "es6",
+    "module": "esnext",
+    "allowSyntheticDefaultImports": true,
+    "importHelpers": true,
+    "typeRoots": ["./node_modules/@types"],
+    "declaration": true,
+    "noImplicitReturns": true,
+    "noImplicitThis": true,
+    "noImplicitUseStrict": false,
+    "noImplicitAny": false
+  },
+  "exclude": ["node_modules"]
+}

+ 0 - 70
web/src/gl-util.js

@@ -1,70 +0,0 @@
-/*
- * Tencent is pleased to support the open source community by making vap available.
- *
- * Copyright (C) 2020 THL A29 Limited, a Tencent company.  All rights reserved.
- *
- * Licensed under the MIT License (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://opensource.org/licenses/MIT
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is
- * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-export function createShader(gl, type, source) {
-    const shader = gl.createShader(type)
-    gl.shaderSource(shader, source)
-    gl.compileShader(shader)
-    // if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
-    //     console.error(gl.getShaderInfoLog(shader))
-    // }
-    return shader
-}
-
-export function createProgram(gl, vertexShader, fragmentShader) {
-    const program = gl.createProgram()
-    gl.attachShader(program, vertexShader)
-    gl.attachShader(program, fragmentShader)
-    gl.linkProgram(program)
-    // if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
-    //     console.error(gl.getProgramInfoLog(program))
-    // }
-    gl.useProgram(program)
-    return program
-}
-
-export function createTexture(gl, index, imgData) {
-    const texture = gl.createTexture()
-    const textrueIndex = gl.TEXTURE0 + index
-    gl.activeTexture(textrueIndex)
-    gl.bindTexture(gl.TEXTURE_2D, texture)
-    // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
-    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
-    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
-    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
-    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
-    if (imgData) {
-        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgData)
-    }
-    return texture
-}
-
-export function cleanWebGL(gl, shaders, program, textures, buffers) {
-    try {
-        gl.clear(gl.COLOR_BUFFER_BIT)
-        textures.forEach(t => {
-            gl.deleteTexture(t)
-        })
-        buffers.forEach(b => {
-            gl.deleteBuffer(b)
-        })
-        shaders.forEach(shader => {
-            gl.detachShader(program, shader)
-            gl.deleteShader(shader)
-        })
-        gl.clear(gl.COLOR_BUFFER_BIT)
-        gl.deleteProgram(program)
-    } catch (e) {}
-}

+ 70 - 0
web/src/gl-util.ts

@@ -0,0 +1,70 @@
+/*
+ * Tencent is pleased to support the open source community by making vap available.
+ *
+ * Copyright (C) 2020 THL A29 Limited, a Tencent company.  All rights reserved.
+ *
+ * Licensed under the MIT License (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://opensource.org/licenses/MIT
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export function createShader(gl, type, source) {
+  const shader = gl.createShader(type);
+  gl.shaderSource(shader, source);
+  gl.compileShader(shader);
+  // if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+  //     console.error(gl.getShaderInfoLog(shader))
+  // }
+  return shader;
+}
+
+export function createProgram(gl, vertexShader, fragmentShader) {
+  const program = gl.createProgram();
+  gl.attachShader(program, vertexShader);
+  gl.attachShader(program, fragmentShader);
+  gl.linkProgram(program);
+  // if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+  //     console.error(gl.getProgramInfoLog(program))
+  // }
+  gl.useProgram(program);
+  return program;
+}
+
+export function createTexture(gl, index:number, imgData?:TexImageSource) {
+  const texture = gl.createTexture();
+  const textrueIndex = gl.TEXTURE0 + index;
+  gl.activeTexture(textrueIndex);
+  gl.bindTexture(gl.TEXTURE_2D, texture);
+  // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+  if (imgData) {
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgData);
+  }
+  return texture;
+}
+
+export function cleanWebGL(gl, shaders, program, textures, buffers) {
+  try {
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    textures.forEach(t => {
+      gl.deleteTexture(t);
+    });
+    buffers.forEach(b => {
+      gl.deleteBuffer(b);
+    });
+    shaders.forEach(shader => {
+      gl.detachShader(program, shader);
+      gl.deleteShader(shader);
+    });
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    gl.deleteProgram(program)
+  } catch (e) {}
+}

+ 24 - 22
web/src/index.js → web/src/index.ts

@@ -13,36 +13,38 @@
  * either express or implied. See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {VapConfig} from "./type";
 import WebglRenderVap from './webgl-render-vap'
-let isCanWebGL
+let isCanWebGL: Boolean;
 /**
  * @param options
  * @constructor
  * @return {null}
  */
-export default function(options) {
-    if (canWebGL()) {
-        return new WebglRenderVap(Object.assign({}, options))
-    } else {
-        throw new Error('your browser not support webgl')
-    }
+export default function(options: VapConfig) {
+  if (canWebGL()) {
+    return new WebglRenderVap(Object.assign({}, options));
+  } else {
+    throw new Error('your browser not support webgl');
+  }
 }
 
-function canWebGL() {
-    if (typeof isCanWebGL !== 'undefined') {
-        return isCanWebGL
+function canWebGL(): Boolean {
+  if (typeof isCanWebGL !== 'undefined') {
+    return isCanWebGL;
+  }
+  try {
+    // @ts-ignore
+    if (!window.WebGLRenderingContext) {
+      return false;
     }
-    try {
-        if (!window.WebGLRenderingContext) {
-            return false
-        }
-        const canvas = document.createElement('canvas')
-        let context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
+    const canvas = document.createElement('canvas');
+    let context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
 
-        isCanWebGL = !!context
-        context = null
-    } catch (err) {
-        isCanWebGL = false
-    }
-    return isCanWebGL
+    isCanWebGL = !!context;
+    context = null;
+  } catch (err) {
+    isCanWebGL = false;
+  }
+  return isCanWebGL;
 }

+ 11 - 0
web/src/type.ts

@@ -0,0 +1,11 @@
+export interface VapConfig {
+  container: HTMLElement;
+  src: string;
+  config: string | {[key:string]:any};
+  width: number;
+  height: number;
+  fps?: number;
+  mute?: boolean;
+  precache?: boolean;
+  [key:string]:any;
+}

+ 0 - 111
web/src/vap-frame-parser.js

@@ -1,111 +0,0 @@
-/*
- * Tencent is pleased to support the open source community by making vap available.
- *
- * Copyright (C) 2020 THL A29 Limited, a Tencent company.  All rights reserved.
- *
- * Licensed under the MIT License (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://opensource.org/licenses/MIT
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is
- * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-export default class FrameParser {
-    constructor(source, headData) {
-        this.config = source || {}
-        this.headData = headData
-        this.frame = []
-        this.textureMap = {}
-    }
-    async init() {
-        this.initCanvas()
-        await this.parseSrc(this.config)
-        this.canvas.parentNode.removeChild(this.canvas)
-        this.frame = this.config.frame || []
-        return this
-    }
-
-    initCanvas() {
-        const canvas = document.createElement('canvas')
-        const ctx = canvas.getContext('2d')
-        canvas.style.display = 'none'
-        document.body.appendChild(canvas)
-        this.ctx = ctx
-        this.canvas = canvas
-    }
-
-    loadImg(url) {
-        return new Promise((resolve, reject) => {
-            // console.log('load img:', url)
-            const img = new Image()
-            img.crossOrigin = 'anonymous'
-            img.onload = function() {
-                resolve(this)
-            }
-            img.onerror = function(e) {
-                console.error('frame 资源加载失败:' + url)
-                reject(new Error('frame 资源加载失败:' + url))
-            }
-            img.src = url
-        })
-    }
-
-    parseSrc(dataJson) {
-        const src = (this.srcData = {})
-        return Promise.all(
-            (dataJson.src || []).map(async item => {
-                item.img = null
-                if (!this.headData[item.srcTag.slice(1, item.srcTag.length - 1)]) {
-                    console.warn(`vap: 融合信息没有传入:${item.srcTag}`)
-                } else {
-                    if (item.srcType === 'txt') {
-                        item.textStr = item.srcTag.replace(/\[(.*)\]/, ($0, $1) => {
-                            return this.headData[$1]
-                        })
-                        item.img = this.makeTextImg(item)
-                    } else if (item.srcType === 'img') {
-                        item.imgUrl = item.srcTag.replace(/\[(.*)\]/, ($0, $1) => {
-                            return this.headData[$1]
-                        })
-                        try {
-                            item.img = await this.loadImg(item.imgUrl + '?t=' + Date.now())
-                        } catch (e) {}
-                    }
-                    if (item.img) {
-                        src[item.srcId] = item
-                    }
-                }
-            })
-        )
-    }
-    /**
-     * 文字转换图片
-     * @param {*} param0
-     */
-    makeTextImg({ textStr, w, h, color, style }) {
-        const ctx = this.ctx
-        ctx.canvas.width = w
-        ctx.canvas.height = h
-        const fontSize = Math.min(parseInt(w / textStr.length, 10), h - 8) // 需留一定间隙
-        const font = [`${fontSize}px`, 'Arial']
-        if (style === 'b') {
-            font.unshift('bold')
-        }
-        ctx.font = font.join(' ')
-        ctx.textBaseline = 'middle'
-        ctx.textAlign = 'center'
-        ctx.fillStyle = color
-        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
-        ctx.fillText(textStr, w / 2, h / 2)
-        // console.log('frame : ' + textStr, ctx.canvas.toDataURL('image/png'))
-        return ctx.getImageData(0, 0, w, h)
-    }
-    getFrame(frame) {
-        return this.frame.find(item => {
-            return item.i === frame
-        })
-    }
-}

+ 147 - 0
web/src/vap-frame-parser.ts

@@ -0,0 +1,147 @@
+/*
+ * Tencent is pleased to support the open source community by making vap available.
+ *
+ * Copyright (C) 2020 THL A29 Limited, a Tencent company.  All rights reserved.
+ *
+ * Licensed under the MIT License (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://opensource.org/licenses/MIT
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default class FrameParser {
+  constructor(source, headData) {
+    this.config = source || {};
+    this.headData = headData;
+    this.frame = [];
+    this.textureMap = {}
+  }
+
+  private config;
+  private headData;
+  private frame;
+  private textureMap;
+  private canvas:HTMLCanvasElement;
+  private ctx:CanvasRenderingContext2D | null;
+  private srcData;
+
+  async init() {
+    this.initCanvas();
+    // 判断是url还是json对象
+    if(/\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]\.json$/.test(this.config)){
+      this.config = await this.getConfigBySrc(this.config);
+    }
+    await this.parseSrc(this.config);
+    this.canvas.parentNode.removeChild(this.canvas);
+    this.frame = this.config.frame || [];
+    return this;
+  }
+
+  initCanvas() {
+    const canvas = document.createElement('canvas');
+    const ctx = canvas.getContext('2d');
+    canvas.style.display = 'none';
+    document.body.appendChild(canvas);
+    this.ctx = ctx;
+    this.canvas = canvas;
+  }
+
+  loadImg(url:string) {
+    return new Promise((resolve, reject) => {
+      // console.log('load img:', url)
+      const img = new Image();
+      img.crossOrigin = 'anonymous';
+      img.onload = function() {
+        resolve(this);
+      };
+      img.onerror = function(e) {
+        console.error('frame 资源加载失败:' + url);
+        reject(new Error('frame 资源加载失败:' + url));
+      };
+      img.src = url;
+    })
+  }
+
+  parseSrc(dataJson) {
+    const src = (this.srcData = {});
+    return Promise.all(
+      (dataJson.src || []).map(async item => {
+        item.img = null;
+        if (!this.headData[item.srcTag.slice(1, item.srcTag.length - 1)]) {
+          console.warn(`vap: 融合信息没有传入:${item.srcTag}`);
+        } else {
+          if (item.srcType === 'txt') {
+            item.textStr = item.srcTag.replace(/\[(.*)\]/, ($0, $1) => {
+              return this.headData[$1];
+            });
+            item.img = this.makeTextImg(item);
+          } else if (item.srcType === 'img') {
+            item.imgUrl = item.srcTag.replace(/\[(.*)\]/, ($0, $1) => {
+              return this.headData[$1]
+            });
+            try {
+              item.img = await this.loadImg(item.imgUrl + '?t=' + Date.now());
+            } catch (e) {}
+          }
+          if (item.img) {
+            src[item.srcId] = item;
+          }
+        }
+      })
+    )
+  }
+
+  /**
+   * 下载json文件
+   * @param jsonUrl json外链
+   * @returns {Promise}
+   */
+  getConfigBySrc(jsonUrl:string) {
+    return new Promise((resolve, reject) => {
+      const xhr = new XMLHttpRequest();
+      xhr.open("GET", jsonUrl, true);
+      xhr.responseType = "json";
+      xhr.onload = function() {
+        if (xhr.status === 200 || xhr.status === 304 && xhr.response) {
+          const res = xhr.response;
+          resolve(res);
+        } else {
+          reject(new Error("http response invalid" + xhr.status));
+        }
+      };
+      xhr.send();
+    });
+  }
+
+  /**
+   * 文字转换图片
+   * @param {*} param0
+   */
+  makeTextImg({ textStr, w, h, color, style }) {
+    const ctx = this.ctx;
+    ctx.canvas.width = w;
+    ctx.canvas.height = h;
+    const fontSize = Math.min(w / textStr.length, h - 8); // 需留一定间隙
+    const font = [`${fontSize}px`, 'Arial'];
+    if (style === 'b') {
+      font.unshift('bold');
+    }
+    ctx.font = font.join(' ');
+    ctx.textBaseline = 'middle';
+    ctx.textAlign = 'center';
+    ctx.fillStyle = color;
+    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+    ctx.fillText(textStr, w / 2, h / 2);
+    // console.log('frame : ' + textStr, ctx.canvas.toDataURL('image/png'))
+    return ctx.getImageData(0, 0, w, h);
+  }
+  getFrame(frame) {
+    return this.frame.find(item => {
+      return item.i === frame;
+    })
+  }
+}

+ 61 - 48
web/src/video.js → web/src/video.ts

@@ -13,10 +13,12 @@
  * either express or implied. See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {VapConfig} from "./type";
+
 export default class VapVideo {
   constructor(options) {
     if (!options.container || !options.src) {
-      return console.warn('[Alpha video]: options container and src cannot be empty!')
+      console.warn('[Alpha video]: options container and src cannot be empty!');
     }
     this.options = Object.assign(
       {
@@ -38,20 +40,30 @@ export default class VapVideo {
         config: ''
       },
       options
-    )
-    this.fps = 20
-    this.requestAnim = this.requestAnimFunc(this.fps)
-    this.container = this.options.container
+    );
+    this.fps = 20;
+    this.requestAnim = this.requestAnimFunc();
+    this.container = this.options.container;
     if (!this.options.src || !this.options.config || !this.options.container) {
-      console.error('参数出错:src(视频地址)、config(配置文件地址)、container(dom容器)')
+      console.error('参数出错:src(视频地址)、config(配置文件地址)、container(dom容器)');
     } else {
       // 创建video
-      this.initVideo()
+      this.initVideo();
     }
   }
 
-  precacheSource(source) {
-    const URL = window.webkitURL || window.URL;
+  public options:VapConfig;
+  private fps:number;
+  public requestAnim:Function;
+  public container:HTMLElement;
+  public video:HTMLVideoElement;
+  private events;
+  private _drawFrame: Function;
+  private animId: number;
+  private firstPlaying: boolean;
+
+  precacheSource(source): Promise<string> {
+    const URL = (window as any).webkitURL || window.URL;
     return new Promise((resolve, reject) => {
       const xhr = new XMLHttpRequest();
       xhr.open("GET", source, true);
@@ -61,9 +73,11 @@ export default class VapVideo {
           const res = xhr.response;
           if (/iphone|ipad|ipod/i.test(navigator.userAgent)) {
             const fileReader = new FileReader();
+
             fileReader.onloadend = function() {
+              const resultStr = (fileReader.result as string);
               const raw = atob(
-                fileReader.result.slice(fileReader.result.indexOf(",") + 1)
+                resultStr.slice(resultStr.indexOf(",") + 1)
               );
               const buf = Array(raw.length);
               for (let d = 0; d < raw.length; d++) {
@@ -87,19 +101,18 @@ export default class VapVideo {
 
 
   initVideo() {
-    const options = this.options
+    const options = this.options;
     // 创建video
-    const video = (this.video = document.createElement('video'))
-    video.crossOrigin = 'anonymous'
-    video.autoplay = false
-    video.preload = 'auto'
-    video.autoload = true
+    const video = (this.video = document.createElement('video'));
+    video.crossOrigin = 'anonymous';
+    video.autoplay = false;
+    video.preload = 'auto';
     if(options.mute){
-      video.muted = true
-      video.volume = 0
+      video.muted = true;
+      video.volume = 0;
     }
-    video.style.display = 'none'
-    video.loop = !!options.loop
+    video.style.display = 'none';
+    video.loop = !!options.loop;
     if(options.precache) {
       this.precacheSource(options.src)
         .then(blob => {
@@ -119,27 +132,27 @@ export default class VapVideo {
     // 绑定事件
     this.events = {}
     ;['playing', 'pause', 'ended', 'error'].forEach(item => {
-      this.on(item, this['on' + item].bind(this))
+      this.on(item, this['on' + item].bind(this));
     })
   }
   drawFrame() {
-    this._drawFrame = this._drawFrame || this.drawFrame.bind(this)
-    this.animId = this.requestAnim(this._drawFrame)
+    this._drawFrame = this._drawFrame || this.drawFrame.bind(this);
+    this.animId = this.requestAnim(this._drawFrame);
   }
 
   play() {
-    const prom = this.video && this.video.play()
+    const prom = this.video && this.video.play();
 
     if (prom && prom.then) {
       prom.catch(e => {
         if (!this.video) {
-          return
+          return;
         }
-        this.video.muted = true
-        this.video.volume = 0
+        this.video.muted = true;
+        this.video.volume = 0;
         this.video.play().catch(e => {
-          ;(this.events.error || []).forEach(item => {
-            item(e)
+          (this.events.error || []).forEach(item => {
+            item(e);
           })
         })
       })
@@ -147,16 +160,16 @@ export default class VapVideo {
   }
 
   requestAnimFunc() {
-    const me = this
+    const me = this;
     if (window.requestAnimationFrame) {
-      let index = -1
+      let index = -1;
       return function(cb) {
-        index++
+        index++;
         return requestAnimationFrame(() => {
           if (!(index % (60 / me.fps))) {
-            return cb()
+            return cb();
           }
-          me.animId = me.requestAnim(cb)
+          me.animId = me.requestAnim(cb);
         })
       }
     }
@@ -167,34 +180,34 @@ export default class VapVideo {
 
   cancelRequestAnimation() {
     if (window.cancelAnimationFrame) {
-      cancelAnimationFrame(this.animId)
+      cancelAnimationFrame(this.animId);
     }
-    clearTimeout(this.animId)
+    clearTimeout(this.animId);
   }
 
   destroy() {
     if (this.video) {
-      this.video.parentNode && this.video.parentNode.removeChild(this.video)
+      this.video.parentNode && this.video.parentNode.removeChild(this.video);
       this.video = null
     }
-    this.cancelRequestAnimation(this.animId)
+    this.cancelRequestAnimation();
   }
 
   clear() {
-    this.destroy()
+    this.destroy();
   }
 
-  on(event, callback) {
-    const cbs = this.events[event] || []
-    cbs.push(callback)
-    this.events[event] = cbs
-    this.video.addEventListener(event, callback)
+  on(event, callback:EventListenerObject) {
+    const cbs = this.events[event] || [];
+    cbs.push(callback);
+    this.events[event] = cbs;
+    this.video.addEventListener(event, callback);
     return this
   }
 
   onplaying() {
     if (!this.firstPlaying) {
-      this.firstPlaying = true
+      this.firstPlaying = true;
       this.drawFrame()
     }
   }
@@ -202,11 +215,11 @@ export default class VapVideo {
   onpause() {}
 
   onended() {
-    this.destroy()
+    this.destroy();
   }
 
   onerror(err) {
-    console.error('[Alpha video]: play error: ', err)
-    this.destroy()
+    console.error('[Alpha video]: play error: ', err);
+    this.destroy();
   }
 }

+ 0 - 321
web/src/webgl-render-vap.js

@@ -1,321 +0,0 @@
-/*
- * Tencent is pleased to support the open source community by making vap available.
- *
- * Copyright (C) 2020 THL A29 Limited, a Tencent company.  All rights reserved.
- *
- * Licensed under the MIT License (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://opensource.org/licenses/MIT
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is
- * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import VapFrameParser from './vap-frame-parser'
-import * as glUtil from './gl-util'
-import VapVideo from './video'
-
-let clearTimer = null
-let instances = {}
-const PER_SIZE = 9
-
-function computeCoord(x, y, w, h, vw, vh) {
-    // leftX rightX bottomY topY
-    return [x / vw, (x + w) / vw, (vh - y - h) / vh, (vh - y) / vh]
-}
-
-export default class WebglRenderVap extends VapVideo {
-    constructor(options) {
-        super(options)
-        this.insType = this.options.type
-        if (instances[this.insType]) {
-            this.instance = instances[this.insType]
-        } else {
-            this.instance = instances[this.insType] = {}
-        }
-        this.textures = []
-        this.buffers = []
-        this.shaders = []
-        this.init()
-    }
-
-    async init() {
-        this.setCanvas()
-        if (this.options.config) {
-            try {
-                this.vapFrameParser = await new VapFrameParser(this.options.config, this.options).init()
-                this.resources = this.vapFrameParser.srcData
-            } catch (e) {
-                console.error('[Alpha video] parse vap frame error.', e)
-            }
-        }
-        this.resources = this.resources || {}
-        this.initWebGL()
-        this.play()
-    }
-    setCanvas() {
-        let canvas = this.instance.canvas
-        const { width, height } = this.options
-        if (!canvas) {
-            canvas = this.instance.canvas = document.createElement('canvas')
-        }
-        canvas.width = width
-        canvas.height = height
-        this.container.appendChild(canvas)
-    }
-
-    initWebGL() {
-        const { canvas } = this.instance
-        let { gl, vertexShader, fragmentShader, program } = this.instance
-        if (!canvas) {
-            return
-        }
-        if (!gl) {
-            this.instance.gl = gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
-            gl.enable(gl.BLEND)
-            gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
-            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
-        }
-        if (gl) {
-            gl.viewport(0, 0, canvas.width, canvas.height)
-            if (!vertexShader) {
-                vertexShader = this.instance.vertexShader = this.initVertexShader()
-            }
-            if (!fragmentShader) {
-                fragmentShader = this.instance.fragmentShader = this.initFragmentShader()
-            }
-            if (!program) {
-                program = this.instance.program = glUtil.createProgram(gl, vertexShader, fragmentShader)
-            }
-            this.program = program
-            this.initTexture()
-            this.initVideoTexture()
-            return gl
-        }
-    }
-
-    /**
-     * 顶点着色器
-     */
-    initVertexShader() {
-        const { gl } = this.instance
-        return glUtil.createShader(
-            gl,
-            gl.VERTEX_SHADER,
-            `attribute vec2 a_position; // 接受顶点坐标
-             attribute vec2 a_texCoord; // 接受纹理坐标
-             attribute vec2 a_alpha_texCoord; // 接受纹理坐标
-             varying vec2 v_alpha_texCoord; // 接受纹理坐标
-             varying   vec2 v_texcoord; // 传递纹理坐标给片元着色器
-             void main(void){
-                gl_Position = vec4(a_position, 0.0, 1.0); // 设置坐标
-                v_texcoord = a_texCoord; // 设置纹理坐标
-                v_alpha_texCoord = a_alpha_texCoord; // 设置纹理坐标
-             }`
-        )
-    }
-
-    /**
-     * 片元着色器
-     */
-    initFragmentShader() {
-        const { gl } = this.instance
-        const bgColor = `vec4(texture2D(u_image_video, v_texcoord).rgb, texture2D(u_image_video,v_alpha_texCoord).r);`
-        const textureSize = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) - 1
-        // const textureSize =0
-        let sourceTexure = ''
-        let sourceUniform = ''
-        if (textureSize > 0) {
-            const imgColor = []
-            for (let i = 0; i < textureSize; i++) {
-                imgColor.push(
-                    `if(ndx == ${i}){
-                        color = texture2D(textures[${i}],uv);
-                    }`
-                )
-            }
-
-            sourceUniform = `
-            uniform sampler2D u_image[${textureSize}];
-            uniform float image_pos[${textureSize * PER_SIZE}];
-            vec4 getSampleFromArray(sampler2D textures[${textureSize}], int ndx, vec2 uv) {
-                vec4 color;
-                ${imgColor.join(' else ')}
-                return color;
-            }
-            `
-            sourceTexure = `
-            vec4 srcColor,maskColor;
-            vec2 srcTexcoord,maskTexcoord;
-            int srcIndex;
-            float x1,x2,y1,y2,mx1,mx2,my1,my2; //显示的区域
-
-            for(int i=0;i<${textureSize * PER_SIZE};i+= ${PER_SIZE}){
-                if ((int(image_pos[i]) > 0)) {
-                  srcIndex = int(image_pos[i]);
-    
-                    x1 = image_pos[i+1];
-                    x2 = image_pos[i+2];
-                    y1 = image_pos[i+3];
-                    y2 = image_pos[i+4];
-                    
-                    mx1 = image_pos[i+5];
-                    mx2 = image_pos[i+6];
-                    my1 = image_pos[i+7];
-                    my2 = image_pos[i+8];
-    
-    
-                    if (v_texcoord.s>x1 && v_texcoord.s<x2 && v_texcoord.t>y1 && v_texcoord.t<y2) {
-                        srcTexcoord = vec2((v_texcoord.s-x1)/(x2-x1),(v_texcoord.t-y1)/(y2-y1));
-                         maskTexcoord = vec2(mx1+srcTexcoord.s*(mx2-mx1),my1+srcTexcoord.t*(my2-my1));
-                         srcColor = getSampleFromArray(u_image,srcIndex,srcTexcoord);
-                         maskColor = texture2D(u_image_video, maskTexcoord);
-                         srcColor.a = srcColor.a*(maskColor.r);
-                      
-                         bgColor = vec4(srcColor.rgb*srcColor.a,srcColor.a) + (1.0-srcColor.a)*bgColor;
-                      
-                    }   
-                }
-            }
-            `
-        }
-
-        const fragmentSharder = `
-        precision lowp float;
-        varying vec2 v_texcoord;
-        varying vec2 v_alpha_texCoord;
-        uniform sampler2D u_image_video;
-        ${sourceUniform}
-        
-        void main(void) {
-            vec4 bgColor = ${bgColor}
-            ${sourceTexure}
-            // bgColor = texture2D(u_image[0], v_texcoord);
-            gl_FragColor = bgColor;
-        }
-        `
-        return glUtil.createShader(gl, gl.FRAGMENT_SHADER, fragmentSharder)
-    }
-
-    initTexture() {
-        const { gl } = this.instance
-        let i = 1
-        if (!this.vapFrameParser || !this.vapFrameParser.srcData) {
-            return
-        }
-        const resources = this.vapFrameParser.srcData
-        for (const key in resources) {
-            const resource = resources[key]
-            this.textures.push(glUtil.createTexture(gl, i, resource.img))
-            const sampler = gl.getUniformLocation(this.program, `u_image[${i}]`)
-            gl.uniform1i(sampler, i)
-            this.vapFrameParser.textureMap[resource.srcId] = i++
-        }
-        const dumpTexture = gl.createTexture()
-        gl.activeTexture(gl.TEXTURE0)
-        gl.bindTexture(gl.TEXTURE_2D, dumpTexture)
-        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
-
-        this.videoTexture = glUtil.createTexture(gl, i)
-        const sampler = gl.getUniformLocation(this.program, `u_image_video`)
-        gl.uniform1i(sampler, i)
-    }
-
-    initVideoTexture() {
-        const { gl } = this.instance
-        const vertexBuffer = gl.createBuffer()
-        this.buffers.push(vertexBuffer)
-        if (!this.vapFrameParser || !this.vapFrameParser.config || !this.vapFrameParser.config.info) {
-            return
-        }
-        const info = this.vapFrameParser.config.info
-        const ver = []
-        const { videoW: vW, videoH: vH } = info
-        const [rgbX, rgbY, rgbW, rgbH] = info.rgbFrame
-        const [aX, aY, aW, aH] = info.aFrame
-        const rgbCoord = computeCoord(rgbX, rgbY, rgbW, rgbH, vW, vH)
-        const aCoord = computeCoord(aX, aY, aW, aH, vW, vH)
-        ver.push(...[-1, 1, rgbCoord[0], rgbCoord[3], aCoord[0], aCoord[3]])
-        ver.push(...[1, 1, rgbCoord[1], rgbCoord[3], aCoord[1], aCoord[3]])
-        ver.push(...[-1, -1, rgbCoord[0], rgbCoord[2], aCoord[0], aCoord[2]])
-        ver.push(...[1, -1, rgbCoord[1], rgbCoord[2], aCoord[1], aCoord[2]])
-        const view = new Float32Array(ver)
-        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
-        gl.bufferData(gl.ARRAY_BUFFER, view, gl.STATIC_DRAW)
-
-        this.aPosition = gl.getAttribLocation(this.program, 'a_position')
-        gl.enableVertexAttribArray(this.aPosition)
-        this.aTexCoord = gl.getAttribLocation(this.program, 'a_texCoord')
-        gl.enableVertexAttribArray(this.aTexCoord)
-        this.aAlphaTexCoord = gl.getAttribLocation(this.program, 'a_alpha_texCoord')
-        gl.enableVertexAttribArray(this.aAlphaTexCoord)
-        // 将缓冲区对象分配给a_position变量、a_texCoord变量
-        const size = view.BYTES_PER_ELEMENT
-        gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, size * 6, 0) // 顶点着色器位置
-        gl.vertexAttribPointer(this.aTexCoord, 2, gl.FLOAT, false, size * 6, size * 2) // rgb像素位置
-        gl.vertexAttribPointer(this.aAlphaTexCoord, 2, gl.FLOAT, false, size * 6, size * 4) // rgb像素位置
-    }
-
-    drawFrame() {
-        const gl = this.instance.gl
-        if (!gl) {
-            super.drawFrame()
-            return
-        }
-        gl.clear(gl.COLOR_BUFFER_BIT)
-        if (this.vapFrameParser) {
-            const frame = Math.floor(this.video.currentTime * this.options.fps)
-            const frameData = this.vapFrameParser.getFrame(frame)
-            let posArr = []
-
-            if (frameData && frameData.obj) {
-                frameData.obj.forEach((frame, index) => {
-                    posArr[posArr.length] = +this.vapFrameParser.textureMap[frame.srcId]
-
-                    const info = this.vapFrameParser.config.info
-                    const { videoW: vW, videoH: vH } = info
-                    const [x, y, w, h] = frame.frame
-                    const [mX, mY, mW, mH] = frame.mFrame
-                    const coord = computeCoord(x, y, w, h, vW, vH)
-                    const mCoord = computeCoord(mX, mY, mW, mH, vW, vH)
-                    posArr = posArr.concat(coord).concat(mCoord)
-                })
-            }
-            //
-            const size = (gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) - 1) * PER_SIZE
-            posArr = posArr.concat(new Array(size - posArr.length).fill(0))
-            this._imagePos = this._imagePos || gl.getUniformLocation(this.program, 'image_pos')
-            gl.uniform1fv(this._imagePos, new Float32Array(posArr))
-        }
-        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.video) // 指定二维纹理方式
-        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
-        super.drawFrame()
-    }
-
-    destroy() {
-        const { canvas, gl } = this.instance
-        if (this.textures && this.textures.length) {
-            for (let i = 0; i < this.textures.length; i++) {
-                gl.deleteTexture(this.textures[i])
-            }
-        }
-        if (canvas) {
-            canvas.parentNode && canvas.parentNode.removeChild(canvas)
-        }
-        // glUtil.cleanWebGL(gl, this.shaders, this.program, this.textures, this.buffers)
-        super.destroy()
-        this.clearMemoryCache()
-    }
-
-    clearMemoryCache() {
-        if (clearTimer) {
-            clearTimeout(clearTimer)
-        }
-
-        clearTimer = setTimeout(() => {
-            instances = {}
-        }, 30 * 60 * 1000)
-    }
-}

+ 336 - 0
web/src/webgl-render-vap.ts

@@ -0,0 +1,336 @@
+/*
+ * Tencent is pleased to support the open source community by making vap available.
+ *
+ * Copyright (C) 2020 THL A29 Limited, a Tencent company.  All rights reserved.
+ *
+ * Licensed under the MIT License (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://opensource.org/licenses/MIT
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {VapConfig} from "./type";
+import VapFrameParser from './vap-frame-parser';
+import * as glUtil from './gl-util';
+import VapVideo from './video';
+
+let clearTimer = null;
+let instances = {};
+const PER_SIZE = 9;
+
+function computeCoord(x:number, y:number, w:number, h:number, vw:number, vh:number) {
+  // leftX rightX bottomY topY
+  return [x / vw, (x + w) / vw, (vh - y - h) / vh, (vh - y) / vh]
+}
+
+export default class WebglRenderVap extends VapVideo {
+  constructor(options:VapConfig) {
+    super(options);
+    this.insType = this.options.type;
+    if (instances[this.insType]) {
+      this.instance = instances[this.insType]
+    } else {
+      this.instance = instances[this.insType] = {}
+    }
+    this.textures = [];
+    this.buffers = [];
+    this.shaders = [];
+    this.init();
+  }
+
+  private insType;
+  private textures;
+  private buffers;
+  private shaders;
+  private vapFrameParser;
+  private resources;
+  private instance;
+  private program;
+  private videoTexture;
+  private aPosition;
+  private aTexCoord;
+  private aAlphaTexCoord;
+  private _imagePos;
+
+  async init() {
+    this.setCanvas();
+    if (this.options.config) {
+      try {
+        this.vapFrameParser = await new VapFrameParser(this.options.config, this.options).init();
+        this.resources = this.vapFrameParser.srcData;
+      } catch (e) {
+        console.error('[Alpha video] parse vap frame error.', e);
+      }
+    }
+    this.resources = this.resources || {};
+    this.initWebGL();
+    this.play();
+  }
+  setCanvas() {
+    let canvas = this.instance.canvas;
+    const { width, height } = this.options;
+    if (!canvas) {
+      canvas = this.instance.canvas = document.createElement('canvas');
+    }
+    canvas.width = width;
+    canvas.height = height;
+    this.container.appendChild(canvas);
+  }
+
+  initWebGL() {
+    const { canvas } = this.instance;
+    let { gl, vertexShader, fragmentShader, program } = this.instance;
+    if (!canvas) {
+      return
+    }
+    if (!gl) {
+      this.instance.gl = gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
+      gl.enable(gl.BLEND);
+      gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+    }
+    if (gl) {
+      gl.viewport(0, 0, canvas.width, canvas.height);
+      if (!vertexShader) {
+        vertexShader = this.instance.vertexShader = this.initVertexShader();
+      }
+      if (!fragmentShader) {
+        fragmentShader = this.instance.fragmentShader = this.initFragmentShader();
+      }
+      if (!program) {
+        program = this.instance.program = glUtil.createProgram(gl, vertexShader, fragmentShader);
+      }
+      this.program = program;
+      this.initTexture();
+      this.initVideoTexture();
+      return gl;
+    }
+  }
+
+  /**
+   * 顶点着色器
+   */
+  initVertexShader() {
+    const { gl } = this.instance;
+    return glUtil.createShader(
+      gl,
+      gl.VERTEX_SHADER,
+      `attribute vec2 a_position; // 接受顶点坐标
+             attribute vec2 a_texCoord; // 接受纹理坐标
+             attribute vec2 a_alpha_texCoord; // 接受纹理坐标
+             varying vec2 v_alpha_texCoord; // 接受纹理坐标
+             varying   vec2 v_texcoord; // 传递纹理坐标给片元着色器
+             void main(void){
+                gl_Position = vec4(a_position, 0.0, 1.0); // 设置坐标
+                v_texcoord = a_texCoord; // 设置纹理坐标
+                v_alpha_texCoord = a_alpha_texCoord; // 设置纹理坐标
+             }`
+    );
+  }
+
+  /**
+   * 片元着色器
+   */
+  initFragmentShader() {
+    const { gl } = this.instance;
+    const bgColor = `vec4(texture2D(u_image_video, v_texcoord).rgb, texture2D(u_image_video,v_alpha_texCoord).r);`;
+    const textureSize = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) - 1;
+    // const textureSize =0
+    let sourceTexure = '';
+    let sourceUniform = '';
+    if (textureSize > 0) {
+      const imgColor = [];
+      for (let i = 0; i < textureSize; i++) {
+        imgColor.push(
+          `if(ndx == ${i}){
+                        color = texture2D(textures[${i}],uv);
+                    }`
+        )
+      }
+
+      sourceUniform = `
+            uniform sampler2D u_image[${textureSize}];
+            uniform float image_pos[${textureSize * PER_SIZE}];
+            vec4 getSampleFromArray(sampler2D textures[${textureSize}], int ndx, vec2 uv) {
+                vec4 color;
+                ${imgColor.join(' else ')}
+                return color;
+            }
+            `;
+      sourceTexure = `
+            vec4 srcColor,maskColor;
+            vec2 srcTexcoord,maskTexcoord;
+            int srcIndex;
+            float x1,x2,y1,y2,mx1,mx2,my1,my2; //显示的区域
+
+            for(int i=0;i<${textureSize * PER_SIZE};i+= ${PER_SIZE}){
+                if ((int(image_pos[i]) > 0)) {
+                  srcIndex = int(image_pos[i]);
+    
+                    x1 = image_pos[i+1];
+                    x2 = image_pos[i+2];
+                    y1 = image_pos[i+3];
+                    y2 = image_pos[i+4];
+                    
+                    mx1 = image_pos[i+5];
+                    mx2 = image_pos[i+6];
+                    my1 = image_pos[i+7];
+                    my2 = image_pos[i+8];
+    
+    
+                    if (v_texcoord.s>x1 && v_texcoord.s<x2 && v_texcoord.t>y1 && v_texcoord.t<y2) {
+                        srcTexcoord = vec2((v_texcoord.s-x1)/(x2-x1),(v_texcoord.t-y1)/(y2-y1));
+                         maskTexcoord = vec2(mx1+srcTexcoord.s*(mx2-mx1),my1+srcTexcoord.t*(my2-my1));
+                         srcColor = getSampleFromArray(u_image,srcIndex,srcTexcoord);
+                         maskColor = texture2D(u_image_video, maskTexcoord);
+                         srcColor.a = srcColor.a*(maskColor.r);
+                      
+                         bgColor = vec4(srcColor.rgb*srcColor.a,srcColor.a) + (1.0-srcColor.a)*bgColor;
+                      
+                    }   
+                }
+            }
+            `;
+    }
+
+    const fragmentSharder = `
+        precision lowp float;
+        varying vec2 v_texcoord;
+        varying vec2 v_alpha_texCoord;
+        uniform sampler2D u_image_video;
+        ${sourceUniform}
+        
+        void main(void) {
+            vec4 bgColor = ${bgColor}
+            ${sourceTexure}
+            // bgColor = texture2D(u_image[0], v_texcoord);
+            gl_FragColor = bgColor;
+        }
+        `;
+    return glUtil.createShader(gl, gl.FRAGMENT_SHADER, fragmentSharder)
+  }
+
+  initTexture() {
+    const { gl } = this.instance;
+    let i = 1;
+    if (!this.vapFrameParser || !this.vapFrameParser.srcData) {
+      return
+    }
+    const resources = this.vapFrameParser.srcData;
+    for (const key in resources) {
+      const resource = resources[key];
+      this.textures.push(glUtil.createTexture(gl, i, resource.img));
+      const sampler = gl.getUniformLocation(this.program, `u_image[${i}]`);
+      gl.uniform1i(sampler, i);
+      this.vapFrameParser.textureMap[resource.srcId] = i++;
+    }
+    const dumpTexture = gl.createTexture();
+    gl.activeTexture(gl.TEXTURE0);
+    gl.bindTexture(gl.TEXTURE_2D, dumpTexture);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+    this.videoTexture = glUtil.createTexture(gl, i);
+    const sampler = gl.getUniformLocation(this.program, `u_image_video`);
+    gl.uniform1i(sampler, i);
+  }
+
+  initVideoTexture() {
+    const { gl } = this.instance;
+    const vertexBuffer = gl.createBuffer();
+    this.buffers.push(vertexBuffer);
+    if (!this.vapFrameParser || !this.vapFrameParser.config || !this.vapFrameParser.config.info) {
+      return
+    }
+    const info = this.vapFrameParser.config.info;
+    const ver = [];
+    const { videoW: vW, videoH: vH } = info;
+    const [rgbX, rgbY, rgbW, rgbH] = info.rgbFrame;
+    const [aX, aY, aW, aH] = info.aFrame;
+    const rgbCoord = computeCoord(rgbX, rgbY, rgbW, rgbH, vW, vH);
+    const aCoord = computeCoord(aX, aY, aW, aH, vW, vH);
+    ver.push(...[-1, 1, rgbCoord[0], rgbCoord[3], aCoord[0], aCoord[3]]);
+    ver.push(...[1, 1, rgbCoord[1], rgbCoord[3], aCoord[1], aCoord[3]]);
+    ver.push(...[-1, -1, rgbCoord[0], rgbCoord[2], aCoord[0], aCoord[2]]);
+    ver.push(...[1, -1, rgbCoord[1], rgbCoord[2], aCoord[1], aCoord[2]]);
+    const view = new Float32Array(ver);
+    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, view, gl.STATIC_DRAW);
+
+    this.aPosition = gl.getAttribLocation(this.program, 'a_position');
+    gl.enableVertexAttribArray(this.aPosition);
+    this.aTexCoord = gl.getAttribLocation(this.program, 'a_texCoord');
+    gl.enableVertexAttribArray(this.aTexCoord);
+    this.aAlphaTexCoord = gl.getAttribLocation(this.program, 'a_alpha_texCoord');
+    gl.enableVertexAttribArray(this.aAlphaTexCoord);
+    // 将缓冲区对象分配给a_position变量、a_texCoord变量
+    const size = view.BYTES_PER_ELEMENT;
+    gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, size * 6, 0); // 顶点着色器位置
+    gl.vertexAttribPointer(this.aTexCoord, 2, gl.FLOAT, false, size * 6, size * 2); // rgb像素位置
+    gl.vertexAttribPointer(this.aAlphaTexCoord, 2, gl.FLOAT, false, size * 6, size * 4); // rgb像素位置
+  }
+
+  drawFrame() {
+    const gl = this.instance.gl;
+    if (!gl) {
+      super.drawFrame();
+      return
+    }
+    gl.clear(gl.COLOR_BUFFER_BIT);
+    if (this.vapFrameParser) {
+      const frame = Math.floor(this.video.currentTime * this.options.fps);
+      const frameData = this.vapFrameParser.getFrame(frame);
+      let posArr = [];
+
+      if (frameData && frameData.obj) {
+        frameData.obj.forEach((frame, index) => {
+          posArr[posArr.length] = +this.vapFrameParser.textureMap[frame.srcId];
+
+          const info = this.vapFrameParser.config.info;
+          const { videoW: vW, videoH: vH } = info;
+          const [x, y, w, h] = frame.frame;
+          const [mX, mY, mW, mH] = frame.mFrame;
+          const coord = computeCoord(x, y, w, h, vW, vH);
+          const mCoord = computeCoord(mX, mY, mW, mH, vW, vH);
+          posArr = posArr.concat(coord).concat(mCoord);
+        })
+      }
+      //
+      const size = (gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) - 1) * PER_SIZE;
+      posArr = posArr.concat(new Array(size - posArr.length).fill(0));
+      this._imagePos = this._imagePos || gl.getUniformLocation(this.program, 'image_pos');
+      gl.uniform1fv(this._imagePos, new Float32Array(posArr));
+    }
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, this.video); // 指定二维纹理方式
+    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+    super.drawFrame();
+  }
+
+  destroy() {
+    const { canvas, gl } = this.instance;
+    if (this.textures && this.textures.length) {
+      for (let i = 0; i < this.textures.length; i++) {
+        gl.deleteTexture(this.textures[i]);
+      }
+    }
+    if (canvas) {
+      canvas.parentNode && canvas.parentNode.removeChild(canvas);
+    }
+    // glUtil.cleanWebGL(gl, this.shaders, this.program, this.textures, this.buffers)
+    super.destroy();
+    this.clearMemoryCache();
+  }
+
+  clearMemoryCache() {
+    if (clearTimer) {
+      clearTimeout(clearTimer);
+    }
+
+    clearTimer = setTimeout(() => {
+      instances = {};
+    }, 30 * 60 * 1000);
+  }
+}

+ 32 - 0
web/tsconfig.json

@@ -0,0 +1,32 @@
+{
+  "compileOnSave": false,
+  "compilerOptions": {
+    "target": "es6",
+    "module": "esnext",
+    "moduleResolution": "node",
+    "lib": ["dom", "esnext"],
+    "declaration": false,
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "importHelpers": true,
+    "noEmitHelpers": true,
+    "removeComments": false,
+    "inlineSourceMap": false,
+    "sourceMap": true,
+    "noEmitOnError": false,
+    "emitDecoratorMetadata": false,
+    "experimentalDecorators": true,
+    "noImplicitReturns": true,
+    "noImplicitThis": true,
+    "noImplicitUseStrict": false,
+    "noImplicitAny": false,
+    "strictNullChecks": false,
+    "pretty": true,
+    "strict": false,
+    "skipLibCheck": true,
+    "rootDir": "./src",
+    "typeRoots": ["./node_modules/@types"]
+  },
+  "exclude": ["node_modules"]
+}

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff