three.js 视频融合

MixVideo.js代码:

//视频融合

import * as THREE from '../build/three.module.js';
import { API } from '../js.my/API.js';
import { Msg } from '../js.my/Msg.js';
import { createDebounce } from '../js.my/Utils.js';
import { guiParams, createGuiParams } from '../js.my/MixVideoGui.js'

let api = new API();
let msg = new Msg();

let mesh;
let material;
let videoTexture;
let loadingVideoTexture;

let debounce = createDebounce(2000);

function createGeometry(params, mixVideoBounds) {
    let geometry = new THREE.Geometry();
    if (!params) {
        geometry.vertices.push(new THREE.Vector3(mixVideoBounds[0].x, mixVideoBounds[0].y, mixVideoBounds[0].z));
        geometry.vertices.push(new THREE.Vector3(mixVideoBounds[1].x, mixVideoBounds[1].y, mixVideoBounds[1].z));
        geometry.vertices.push(new THREE.Vector3(mixVideoBounds[2].x, mixVideoBounds[2].y, mixVideoBounds[2].z));
        geometry.vertices.push(new THREE.Vector3(mixVideoBounds[3].x, mixVideoBounds[3].y, mixVideoBounds[3].z));
    } else {
        geometry.vertices.push(new THREE.Vector3(params.bounds0_x, params.bounds0_y, params.bounds0_z));
        geometry.vertices.push(new THREE.Vector3(params.bounds1_x, params.bounds1_y, params.bounds1_z));
        geometry.vertices.push(new THREE.Vector3(params.bounds2_x, params.bounds2_y, params.bounds2_z));
        geometry.vertices.push(new THREE.Vector3(params.bounds3_x, params.bounds3_y, params.bounds3_z));
    }

    let normal = new THREE.Vector3(0, 0, 1);

    let face0 = new THREE.Face3(0, 1, 2, normal);
    let face1 = new THREE.Face3(0, 2, 3, normal);
    geometry.faces.push(face0, face1);

    let t0 = new THREE.Vector2(0, 0);
    let t1 = new THREE.Vector2(1, 0);
    let t2 = new THREE.Vector2(1, 1);
    let t3 = new THREE.Vector2(0, 1);
    let uv1 = [t0, t1, t2];
    let uv2 = [t0, t2, t3];
    geometry.faceVertexUvs[0].push(uv1, uv2);

    geometry.computeFaceNormals();
    geometry.computeVertexNormals();

    return geometry;
}

let changeMaterialMap = () => {
    if (material && videoTexture && material.map === loadingVideoTexture) {
        material.map = videoTexture;
    }
};

function createVideoMesh(scene, fly, video, loadingVideo, cameraId, mixVideoBounds, mixVideoCameraPosition, mixVideoCameraTarge) {

    videoTexture = new THREE.VideoTexture(video);
    videoTexture.minFilter = THREE.LinearFilter;
    videoTexture.magFilter = THREE.LinearFilter;
    videoTexture.format = THREE.RGBFormat;

    loadingVideoTexture = new THREE.VideoTexture(loadingVideo);
    loadingVideoTexture.minFilter = THREE.LinearFilter;
    loadingVideoTexture.magFilter = THREE.LinearFilter;
    loadingVideoTexture.format = THREE.RGBFormat;

    material = new THREE.MeshBasicMaterial({
        map: loadingVideoTexture,
        color: 0xffffff,
        depthTest: false,
        transparent: true,
        opacity: 0.95
    });

    mesh = new THREE.Mesh(createGeometry(undefined, mixVideoBounds), material);
    scene.add(mesh);

    fly.moveCameraOnly(mixVideoCameraPosition, mixVideoCameraTarge);

    createGuiParams(mixVideoBounds, () => {
        mesh.geometry = createGeometry(guiParams);

        let mixVideoBounds = [
            { x: guiParams.bounds0_x, y: guiParams.bounds0_y, z: guiParams.bounds0_z },
            { x: guiParams.bounds1_x, y: guiParams.bounds1_y, z: guiParams.bounds1_z },
            { x: guiParams.bounds2_x, y: guiParams.bounds2_y, z: guiParams.bounds2_z },
            { x: guiParams.bounds3_x, y: guiParams.bounds3_y, z: guiParams.bounds3_z }
        ];

        for (let i = 0; i < mixVideoBounds.length - 1; i++) {
            mixVideoBounds[i].x = parseFloat(mixVideoBounds[i].x.toFixed(6));
            mixVideoBounds[i].y = parseFloat(mixVideoBounds[i].y.toFixed(6));
            mixVideoBounds[i].z = parseFloat(mixVideoBounds[i].z.toFixed(6));
        }

        let data = {
            id: cameraId,
            mix_video_bounds: JSON.stringify(mixVideoBounds),
        }

        debounce(() => {
            api.updatePtCameraInfo(data, () => {
                msg.show("视频融合参数已保存");
            });
        });
    });
}

function mixVideo(scene, fly, cameraIndexCode, cameraId, mixVideoBounds, mixVideoCameraPosition, mixVideoCameraTarge) {
    msg.show("即将加载视频请稍等");
    mesh && scene.remove(mesh);

    //创建DOM
    if ($('#mixVideo').length == 0) {

        //video标签,外层div测试用
        let videoStr = `
            <div id="mixVideoDiv" style="display:none; z-index: -999999; position: absolute; float: left; top: 0; left: 0; background-color: #ff0000;">
                <video id="mixVideo" style="width:100px; height:100px;" loop="loop" poster="images/mix-video/loading.gif">
                    <source src="../../video/videoPlane.mp4" type="video/mp4">
                </video>
                <video id="loadingVideo" style="width:100px; height:100px;" loop="loop" >
                    <source src="images/mix-video/loading.mp4" type="video/mp4">
                </video>  
            </div>`

        $('body').append(videoStr);
    }

    let video = document.getElementById('mixVideo');
    let loadingVideo = document.getElementById('loadingVideo');

    //取流
    // api.getVideoUrl(cameraIndexCode, data => {
    //     createVideoMesh(scene, fly, video, loadingVideo, cameraId, mixVideoBounds, mixVideoCameraPosition, mixVideoCameraTarge);
    //     hlsPlay(video, loadingVideo, data);
    // }, errMsg => {
    //     playTestMp4(scene, fly, video, loadingVideo, cameraId, mixVideoBounds, mixVideoCameraPosition, mixVideoCameraTarge);

    //     msg.show("取流失败:" + errMsg);
    // });

    //测试播放hls流
    let testUrl = 'http://playertest.longtailvideo.com/adaptive/bipbop/gear4/prog_index.m3u8';
    let testUrl2 = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';
    createVideoMesh(scene, fly, video, loadingVideo, cameraId, mixVideoBounds, mixVideoCameraPosition, mixVideoCameraTarge);
    hlsPlay(video, loadingVideo, testUrl);

}

/** 播放hls流 */
function hlsPlay(video, loadingVideo, url) {
    loadingVideo.play();

    if (Hls.isSupported()) {
        const hls = new Hls();
        hls.loadSource(url);
        hls.attachMedia(video);
        hls.on(Hls.Events.MEDIA_ATTACHED, () => {

        });
        hls.on(Hls.Events.MANIFEST_PARSED, () => {
            video.play();
        });
        hls.on(Hls.Events.ERROR, (event, data) => {

        });
        hls.on(Hls.Events.FRAG_LOADED, () => {
            changeMaterialMap();
        });
    } else {
        msg.show("您的浏览器不支持播放该视频流");
    }
}

function playTestMp4(scene, fly, video, loadingVideo, cameraId, mixVideoBounds, mixVideoCameraPosition, mixVideoCameraTarge) {
    loadingVideo.play();
    video.play();
    createVideoMesh(scene, fly, video, loadingVideo, cameraId, mixVideoBounds, mixVideoCameraPosition, mixVideoCameraTarge);
    changeMaterialMap();
}

export { mixVideo }
View Code

如何使用:调用mixVideo方法,把scene、fly(用于场景飞行)和其它配置的参数传给它即可

涉及到的变量说明:

video 视频标签DOM

loadingVideo 视频加载出来前的loading动画的DOM,mp4格式

mixVideoBounds 播放视频的Geometry的四个顶点的坐标

mixVideoCameraPosition 场景相机position(PerspectiveCamera对象的position)

mixVideoCameraTarge 场景相机target(OrbitControls.js的OrbitControls对象的target)

 

mixVideoBounds参数不好调,我做了一个调参的功能,当参数调整时,自动保存到数据库

MixVideoGui.js代码:

//控制视频融合播放范围

import { GUI } from "../js/libs/dat.gui.module.js";

let gui = new GUI({ autoPlace: false, width: 260, hideable: true });

GUI.TEXT_CLOSED = '隐藏';
GUI.TEXT_OPEN = '展开';

let guiParams;

let folderLeftBottom;
let folderRightBottom;
let folderRightTop;
let folderLeftTop;

function createGuiParams(mixVideoBounds, onChange) {
    if (folderLeftBottom) {
        gui.removeFolder(folderLeftBottom);
        gui.removeFolder(folderRightBottom);
        gui.removeFolder(folderRightTop);
        gui.removeFolder(folderLeftTop);
    }

    guiParams = new function () {
        this.bounds0_x = mixVideoBounds[0].x;
        this.bounds0_y = mixVideoBounds[0].y;
        this.bounds0_z = mixVideoBounds[0].z;

        this.bounds1_x = mixVideoBounds[1].x;
        this.bounds1_y = mixVideoBounds[1].y;
        this.bounds1_z = mixVideoBounds[1].z;

        this.bounds2_x = mixVideoBounds[2].x;
        this.bounds2_y = mixVideoBounds[2].y;
        this.bounds2_z = mixVideoBounds[2].z;

        this.bounds3_x = mixVideoBounds[3].x;
        this.bounds3_y = mixVideoBounds[3].y;
        this.bounds3_z = mixVideoBounds[3].z;
    }

    folderLeftBottom = gui.addFolder('左下');
    folderRightBottom = gui.addFolder('右下');
    folderRightTop = gui.addFolder('右上');
    folderLeftTop = gui.addFolder('左上');

    folderLeftBottom.open();
    folderRightBottom.open();
    folderRightTop.open();
    folderLeftTop.open();

    let guiParamsDelta = 1000;
    let guiParamsDeltaY = 1000;
    let step = 0.1;

    let paramCtrls = [
        folderLeftBottom.add(guiParams, "bounds0_x", guiParams.bounds0_x - guiParamsDelta, guiParams.bounds0_x + guiParamsDelta, step),
        folderLeftBottom.add(guiParams, "bounds0_y", guiParams.bounds0_y - guiParamsDeltaY, guiParams.bounds0_y + guiParamsDeltaY, step),
        folderLeftBottom.add(guiParams, "bounds0_z", guiParams.bounds0_z - guiParamsDelta, guiParams.bounds0_z + guiParamsDelta, step),

        folderRightBottom.add(guiParams, "bounds1_x", guiParams.bounds1_x - guiParamsDelta, guiParams.bounds1_x + guiParamsDelta, step),
        folderRightBottom.add(guiParams, "bounds1_y", guiParams.bounds1_y - guiParamsDeltaY, guiParams.bounds1_y + guiParamsDeltaY, step),
        folderRightBottom.add(guiParams, "bounds1_z", guiParams.bounds1_z - guiParamsDelta, guiParams.bounds1_z + guiParamsDelta),

        folderRightTop.add(guiParams, "bounds2_x", guiParams.bounds2_x - guiParamsDelta, guiParams.bounds2_x + guiParamsDelta, step),
        folderRightTop.add(guiParams, "bounds2_y", guiParams.bounds2_y - guiParamsDeltaY, guiParams.bounds2_y + guiParamsDeltaY, step),
        folderRightTop.add(guiParams, "bounds2_z", guiParams.bounds2_z - guiParamsDelta, guiParams.bounds2_z + guiParamsDelta, step),

        folderLeftTop.add(guiParams, "bounds3_x", guiParams.bounds3_x - guiParamsDelta, guiParams.bounds3_x + guiParamsDelta, step),
        folderLeftTop.add(guiParams, "bounds3_y", guiParams.bounds3_y - guiParamsDeltaY, guiParams.bounds3_y + guiParamsDeltaY, step),
        folderLeftTop.add(guiParams, "bounds3_z", guiParams.bounds3_z - guiParamsDelta, guiParams.bounds3_z + guiParamsDelta, step)
    ];

    paramCtrls.forEach(ctrl => ctrl.onChange(onChange));

    if ($('#guiDomElement').length == 0) {
        let guiDomElement = `<div id="guiDomElement" style="position:absolute; z-index:1990; float:left; left:165px; top:220px; width:260px;" ></div> `;
        $('body').append(guiDomElement);
        $('#guiDomElement').append(gui.domElement);
        gui.open();
    }
}

export { gui, guiParams, createGuiParams }
View Code

效果图:

没有真实的视频,随便找了个在线的hls流

效果图gif:

说明:第1个模拟的是平视的摄像机,第2个和第3个模拟的是俯视的摄像机,第4个没有配置视频融合相关参数,直接弹出视频播放对话框。

 

 mixVideoBounds参数调整效果图:

现场测试效果图: 

效果不怎么样,也可能只是参数没调好。

 

posted @ 2021-12-26 15:20  0611163  阅读(845)  评论(0编辑  收藏  举报