前端如何使用js生成视频

1,153 阅读1分钟

js牛逼,不是白说的,本文就给大家分享一个浏览器端js生成视频的方案。

效果演示

001.gif

方案概述

上面这个演示效果的实现主要用到了以下几点。

关键代码和讲解

<template>
  <div class="box">
    <a-button :disabled="isRecording" @click="play">开启录制</a-button>
    <a-button :disabled="!isRecording" @click="stopRecord"
      >停止录制并生成视频</a-button
    >
    <a-space style="display: flex">
      <div>
        <h1>画布</h1>
        <canvas style="width: 50vw; height: 50vh" id="canvas" />
      </div>
    </a-space>
  </div>
</template>

<script lang="ts" setup>
/**
 * @title Lite Collision Detection
 * @category Physics
 */
import {
  WebGLEngine,
  Vector3,
  MeshRenderer,
  PointLight,
  Camera,
  Entity,
  GLTFResource,
  BoundingBox,
  SkinnedMeshRenderer,
} from "oasis-engine";
import { OrbitControl } from "@oasis-engine/controls";

import { LitePhysics } from "@oasis-engine/physics-lite";
import { onMounted, ref } from "vue";
import { onUnmounted } from "vue";

onMounted(() => {
  createOasis();
});

let engine: WebGLEngine;
let rootEntity: Entity;
let camera: Camera;

// 初始化oasis引擎,创建画布内容
function createOasis() {
  engine = new WebGLEngine("canvas", LitePhysics, {
    alpha: false, // 控制背景是否透明
    premultipliedAlpha: false,
  });
  engine.canvas.resizeByClientSize();
  const scene = engine.sceneManager.activeScene;
  scene.background.solidColor.setValue(0, 0, 0, 0);
  rootEntity = scene.createRootEntity("root");

  scene.ambientLight.diffuseSolidColor.setValue(1, 1, 1, 1);
  scene.ambientLight.diffuseIntensity = 1.2;

  // 初始化相机
  const cameraEntity = rootEntity.createChild("camera");
  camera = cameraEntity.addComponent(Camera);
  cameraEntity.transform.setPosition(10, 10, 10);
  let orbitControl = cameraEntity.addComponent(OrbitControl);

  // 初始化点光源
  const light = rootEntity.createChild("light");
  light.transform.setPosition(0, 3, 0);
  const pointLight = light.addComponent(PointLight);
  pointLight.intensity = 0.3;

  // 加载模型
  let modelEntity = rootEntity.createChild("ModelEntity");
  engine.resourceManager
    .load<GLTFResource>("./models/a0ff1c60-5e21-49e5-946a-675515d98736.glb")
    .then((asset) => {
      const { defaultSceneRoot } = asset;
      modelEntity.addChild(defaultSceneRoot);

      setTargetCenter(modelEntity, orbitControl);
    });

  // Run engine
  engine.run();
  onUnmounted(() => scene.destroy());
}

/**根据模型尺寸 将其设置为中间 */
function setTargetCenter(modelEntity: Entity, orbitControl: OrbitControl) {
  const meshRenderers = new Array<MeshRenderer>();
  modelEntity.getComponentsIncludeChildren(MeshRenderer, meshRenderers);

  const skinnedMeshRenderers: SkinnedMeshRenderer[] = [];
  modelEntity.getComponentsIncludeChildren(
    SkinnedMeshRenderer,
    skinnedMeshRenderers
  );

  const renderers = meshRenderers.concat(skinnedMeshRenderers);
  const boundingBox = new BoundingBox();
  const center = new Vector3();
  const extent = new Vector3();

  boundingBox.min.setValue(0, 0, 0);
  boundingBox.max.setValue(0, 0, 0);

  renderers.forEach((renderer) => {
    BoundingBox.merge(renderer.bounds, boundingBox, boundingBox);
  });
  boundingBox.getExtent(extent);
  const size = extent.length();

  boundingBox.getCenter(center);
  orbitControl.target.setValue(center.x, center.y, center.z);
  const cameraEntity = orbitControl.camera;
  const camera = cameraEntity.getComponent(Camera);
  cameraEntity.transform.setPosition(size * 2, size * 1.5, size * 4);
  // 记录模型初始化视角

  camera.farClipPlane = size * 12;

  if (camera.nearClipPlane > size) {
    camera.nearClipPlane = size / 10;
  } else {
    camera.nearClipPlane = 0.1;
  }
}

let stream = ref<MediaStream>();
let recorder: MediaRecorder;

// 点击录制按钮触发
function play() {
  if (!stream.value) {
    // 将画布内容生成媒体流
    stream.value = engine.canvas._webCanvas.captureStream(60);

    // 生成媒体录制器对象并传入一个媒体流以便录制
    stream.value &&
      (recorder = new MediaRecorder(stream.value, {
        mimeType: "video/webm;codecs=vp8",
      }));
    // 有可录制的媒体资源事件
    recorder.ondataavailable = (event) => {
      blobs.push(event.data);
    };
    // 媒体录制器停止录制事件
    recorder.onstop = (event) => {
      isRecording.value = false;
      saveVideo();
    };
  }
  // 媒体录制器开始录制
  isRecording.value = true;
  recorder.start();
}

let blobs: Blob[] = [];
let webm = ref<Blob>();
let isRecording = ref(false);
function saveVideo() {
  webm.value = new Blob(blobs, { type: "video/mp4" });
  open(URL.createObjectURL(webm.value));
  blobs = [];
}

/**
 * 停止录制
 */
function stopRecord() {
  recorder.stop();
}
</script>

<style scoped lang="scss">
.box {
  padding-top: 60px;
}
#canvas {
  background-color: #222;
}
</style>