一、参考连接
官方资料:https://doc.babylonjs.com/features/featuresDeepDive/materials/using/videoTexture
播放案例:
https://playground.babylonjs.com/#ZMCFYA#83
https://playground.babylonjs.com/#1BYH8W#4
本地摄像头:https://www.babylonjs-playground.com/#1R77YT#32
video标签播放:https://www.babylonjs-playground.com/#9FSDC7#13
video标签:https://www.babylonjs-playground.com/#9FSDC7#271
hsl源:https://playground.babylonjs.com/#GEA3P0
hsl源:https://playground.babylonjs.com/#YJLYCB#10
dashjs源+video标签:https://playground.babylonjs.com/#9FSDC7#234
二、资料总结
- babylonjs 可以播放mp4等视频文件;
- babylonjs 支持通过webrtc播放网络摄像头视频;
- babylonjs 支持通过获取本机摄像头实时视频;
- babylonjs 内部创建了一个HTML Video标签,当video标签被dispose的时候,视频会中断;
三、动手实践
经过初步研究后发现可能无法直接播放zlm的webrtc,可能只能选择通过隐藏的video标签播放。
以下代码后端的源使用了zlmediakit流媒体服务器。
左侧是原始摄像头流、中间是canvas镜像流,右侧是babylons材质的效果:

单独展示babylonjs的效果:

多边形UV映射:

代码见下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./jessibuca.js"></script>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        #wrapper {
            display: flex;
        }
        #container {
            /* 你可以根据需要设置宽度 */
            height: 500px;
            width: 33%;
            background-color: lightblue;
            /*display: none;*/
        }
        #mirror{
            height: 500px;
            width:33%;
            background-color: greenyellow;
            /*display: none;*/
        }
        #renderCanvasDiv {
            /* 你可以根据需要设置宽度 */
            height: 500px;
            width: 33%;
            background-color: lightcoral;
            /* 示例背景色 */
        }
        #renderCanvas {
            width: 100%;
            height: 100%;
            touch-action: none;
        }
    </style>
</head>
<body>
    <div id="wrapper">
        <div id="container"></div>
        <div id="mirror"><canvas id="mirrorCanvas"></canvas></div>
        <div id="renderCanvasDiv"><canvas id="renderCanvas"></canvas></div>
    </div>
    <script>
        var $container = document.getElementById('container');
        var showOperateBtns = false; // 是否显示按钮
        var forceNoOffscreen = true; //
        var jessibuca = null;
        function createStream() {
            jessibuca = null;
            jessibuca = new Jessibuca({
                container: $container,
                videoBuffer: 0.2, // 缓存时长
                isResize: false,
                text: "",
                loadingText: "",
                useMSE: false,
                debug: false,
                showBandwidth: showOperateBtns, // 显示网速
                operateBtns: {
                    fullscreen: showOperateBtns,
                    screenshot: showOperateBtns,
                    play: showOperateBtns,
                    audio: false,
                    recorder: false
                },
                forceNoOffscreen: forceNoOffscreen,
                isNotMute: false,
            },);
            var href = "wss://hd.iios.com.cn/zlmediakit/IIOS-CONSOLE/241113LH.live.flv?vhost=__defaultVhost__&session=BFF1E27336359CBA61C967E1D8F1AAAB";
            jessibuca.play(href);
            createBbl();
        }
        function refreshMirror(){
            const sourceCanvas = document.querySelector('#container canvas');
            const newCanvas = document.getElementById("mirrorCanvas");
            const newCtx = newCanvas.getContext('2d');
            newCanvas.width = sourceCanvas.width;
            newCanvas.height = sourceCanvas.height;
            newCtx.clearRect(0, 0, newCanvas.width, newCanvas.height);
            newCtx.drawImage(sourceCanvas, 0, 0, newCanvas.width, newCanvas.height);
        }
        function createBbl() {
            const sourceCanvas = document.querySelector('#container canvas');
            if (sourceCanvas) {
                console.log("sourceCanvas", sourceCanvas)
                const canvas = document.getElementById("renderCanvas");
                const engine = new BABYLON.Engine(canvas, true);
                const scene = new BABYLON.Scene(engine);
                var camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, BABYLON.Vector3.Zero(), scene);
                camera.attachControl(canvas, true);
                // 创建一个光源
                // 创建朝上的光源
                const light1 = new BABYLON.HemisphericLight("light1", BABYLON.Vector3.Up(), scene);
                light1.intensity = 0.7; // 设置光强度
                // 创建朝下的光源
                const light2 = new BABYLON.HemisphericLight("light2", BABYLON.Vector3.Down(), scene);
                light2.intensity = 0.7; // 设置光强度
                // 创建平面
                var plane = BABYLON.MeshBuilder.CreateGround(
                    "plane",  // 平面名称
                    { width: 10, height: 10 }, // 设置平面的宽度和高度
                    scene
                );
                // 创建球体
                var sphere = BABYLON.MeshBuilder.CreateSphere(
                    "sphere",  // 球体名称
                    { diameter: 2 }, // 设置球体的直径
                    scene
                );
                sphere.position.y = 1; // 设置球体的Y轴位置,让它悬浮在平面上方
                // 创建半立方体
                const halfCube = BABYLON.MeshBuilder.CreateBox("halfCube", { width: 2, height: 1, depth: 2 }, scene);
                halfCube.position.y = 3;
                const dynamicTexture = new BABYLON.DynamicTexture(
                    "dynamicTexture",
                    { width: 640, height: 360 },
                    scene,
                    false
                );
                // 创建多边形
                
                const positions = [];
                const indices = [];
                const uvs = [];
                const radius = 10;
                const sides = 9;
                // 计算顶点位置(在XZ平面上)
                // 计算顶点位置(在XZ平面上)
                for (let i = 0; i < sides; i++) {
                    const angle = (2 * Math.PI / sides) * i;
                    positions.push(Math.cos(angle) * radius, 0, Math.sin(angle) * radius);
                    uvs.push((Math.cos(angle) + 1) / 2, (Math.sin(angle) + 1) / 2); // UV坐标
                }
                // 中心顶点(在XZ平面上)
                positions.push(0, 0, 0);
                uvs.push(0.5, 0.5);
                // 计算索引
                for (let i = 0; i < sides; i++) {
                    indices.push(i, (i + 1) % sides, sides);
                }
                // 创建自定义网格
                const customMesh = new BABYLON.Mesh("custom", scene);
                const vertexData = new BABYLON.VertexData();
                vertexData.positions = positions;
                vertexData.indices = indices;
                vertexData.uvs = uvs;
                vertexData.applyToMesh(customMesh);
                customMesh.position.y = -3
                var material = new BABYLON.StandardMaterial("material", scene);
                material.diffuseTexture = dynamicTexture;
                material.reflectionTexture = null;
                material.useReflection = false;  // 禁用反射
                material.useMicroSurfaceFromReflectivityMap = false;  // 禁用反射微表面效果
                material.environmentTexture = null;  // 禁用环境光反射
                material.specularColor = new BABYLON.Color3(0, 0, 0); // 无高光反射
                material.environmentIntensity = 0; // 禁用环境光反射
                sphere.material = material;
                plane.material = material;
                halfCube.material = material;
                customMesh.material = material;
                customMesh.material.backFaceCulling = false;
                function updateTexture() {
                    if (!sourceCanvas) {
                        console.error("sourceCanvas is not found or not initialized.");
                        return;
                    }
                    const ctx = dynamicTexture.getContext();
                    if (!ctx) {
                        console.error("Failed to get context from dynamicTexture.");
                        return;
                    }
                    // Clear the dynamic texture
                    ctx.clearRect(0, 0, dynamicTexture.getSize().width, dynamicTexture.getSize().height);
                    // Draw the source canvas onto the dynamic texture
                    ctx.drawImage(sourceCanvas, 0, 0, dynamicTexture.getSize().width, dynamicTexture.getSize().height);
                    // Update the dynamic texture to apply changes
                    dynamicTexture.update();
                    console.log("update", sourceCanvas.width, sourceCanvas.height);
                }
                engine.runRenderLoop(function () {
                    refreshMirror();
                    updateTexture();
                    scene.render();
                });
                window.addEventListener("resize", function () {
                    engine.resize();
                });
            }
        }
        createStream();
    </script>
</body>
</html>