Tutorial: 开启水印

开启水印

功能描述

本文主要介绍如何基于 canvas 实现推流添加水印功能。您可以在这个 demo 中体验开启水印功能。

前提条件

添加水印功能主要使用到了 canvas.captureStream 进行自定义渲染,兼容性情况可参考 canvas.captureStream 兼容性情况TRTC Web SDK 兼容性情况

实现流程

  1. 使用 TRTC.createStream 创建 LocalStream,并采集视频流。
  2. 创建 video 标签播视频流,用于将视频绘制到 canvas 画布中。
  3. 创建 image 实例,加载水印图片。
  4. 创建 canvas 标签,并使用 setInterval 将视频和水印绘制到 canvas 画布中。
  5. 使用 canvas.captureStream 从画布中采集视频流,使用 LocalStream.replaceTrack 替换视频流。

代码示例

创建 LocalStream 采集视频流

// 1. 创建 LocalStream 采集视频流
const localStream = TRTC.createStream({ audio: true, video: true, mirror: false });
await localStream.initialize();
let sourceVideoTrack = null;
let intervalId = -1;
let video = null;

封装开启水印函数

// 用于加载水印图片
function loadImage(imageUrl) {
  return new Promise((resolve) => {
    const image = new Image();
    image.src = imageUrl;
    image.onload = () => resolve(image);
  });
}
async function startWaterMark({ localStream, x, y, width, height, imageUrl }) {
  // 2. 创建 video 标签播放视频流
  const video = document.createElement('video');
  sourceVideoTrack = localStream.getVideoTrack();
  const mediaStream = new MediaStream();
  mediaStream.addTrack(sourceVideoTrack);
  video.srcObject = mediaStream;
  await video.play();
  // 3. 加载水印图片
  const image = await loadImage(imageUrl); 
  // 4. 创建 canvas 标签,并使用 setInterval 将视频和水印绘制到 canvas 画布中
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const settings = sourceVideoTrack.getSettings();
  canvas.width = settings.width;
  canvas.height = settings.height;
  intervalId = setInterval(() => {
    // 将视频绘制到画布中
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
    // 将水印图片绘制到画布中,可以控制水印的位置和大小
    ctx.drawImage(image, x, y, width || image.width, height || image.height);
  }, Math.floor(1000 / settings.frameRate)); // 根据帧率计算每次绘制的时间间隔
  // 5. 使用 canvas.captureStream 从画布中采集视频流,使用 LocalStream.replaceTrack 替换视频流
  const canvasStream = canvas.captureStream();
  await localStream.replaceTrack(canvasStream.getVideoTracks()[0]);
}
// 开启水印
await startWaterMark({ localStream, x: 100, y: 100, imageUrl: './xxx.png'}); // 需传入图片 url
localStream.play('elementId'); // 传入 elementId 播放 LocalStream
// 6. 推流
await client.publish(localStream);

封装关闭水印函数

// 关闭水印
async function stopWaterMark() {
  if (intervalId) {
    clearInterval(intervalId);
    intervalId = -1;
    await localStream.replaceTrack(sourceVideoTrack);
    if (video) {
      video.srcObject = null;
      video = null;
    }
  }
}

更进一步

  • 上述代码示例使用了 setInterval 来绘制 canvas 画布,若 js 线程出现拥挤时,绘制可能会出现卡顿。可以考虑使用 requestAnimationFrame 来替代 setInterval,以提升渲染性能。

  • 上述代码只绘制了一个水印,可能您的业务场景需要让水印铺满整个视频,此时有两种方案,一种是制作一张宽高大于视频窗口的水印图片,绘制一次让水印图片铺满画布即可;另一种方案是多次调用 CanvasRenderingContext2D.drawImage,多次绘制同一张水印文件,使水印铺满整个画布。

  • 您可以使用 CanvasRenderingContext2D.rotate 接口让水印旋转一定的角度。需要注意的是:在绘制完水印后,需要再次调用该接口旋转回来,以避免视频绘制也出现旋转。

    setInterval(() => {
      // 将视频绘制到画布中
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
    	// 1. 让水印旋转30度
    	ctx.rotate((30 * Math.PI) / 180);
      ctx.drawImage(image, x, y, width || image.width, height || image.height);
    	// 2. 水印绘制完后,需旋转回来,以避免下一次绘制时,视频也出现旋转30度的情况发生。
    	ctx.rotate((-30 * Math.PI) / 180);
    }, Math.floor(1000 / frameRate))