Tutorial: Enabling Watermarking

Enabling Watermarking

Feature Description

This document describes how to add a watermark to a push stream via canvas. You can try the watermark feature via demo.

Prerequisites

This feature mainly uses canvas.captureStream for custom rendering. For compatibility, please refer to canvas.captureStream compatibility and TRTC Web SDK compatibility.

Implementation Process

  1. Use TRTC.createStream to create a LocalStream and capture a video stream.
  2. Create a video tag for video stream playback and draw the video onto the canvas.
  3. Create an image instance and load the watermark image.
  4. Create a canvas tag and use setInterval to draw the video and watermark onto the canvas.
  5. Use canvas.captureStream to capture the video stream from the canvas. Use LocalStream.replaceTrack to replace the video stream.

Sample Code

Creating a LocalStream to capture video streams

// 1. Create a LocalStream to capture video streams
const localStream = TRTC.createStream({
  audio: true,
  video: true
});
await localStream.initialize();
let sourceVideoTrack = null;
let intervalId = -1;
let video = null;

Encapsulating the watermarking enabling function

// Used to load the watermark image
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. Create a video tag for video stream playback
  const video = document.createElement("video");
  sourceVideoTrack = localStream.getVideoTrack();
  const mediaStream = new MediaStream();
  mediaStream.addTrack(sourceVideoTrack);
  video.srcObject = mediaStream;
  await video.play();
  // 3. Load the watermark image
  const image = await loadImage(imageUrl);
  // 4. Create a canvas tag and use `setInterval` to draw the video and watermark onto the 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(() => {
    // Draw the video onto the canvas
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    // Draw the watermark image onto the canvas (the watermark position and size can be adjusted)
    ctx.drawImage(image, x, y, width || image.width, height || image.height);
  }, Math.floor(1000 / settings.frameRate)); // Calculate the time interval for each drawing according to the frame rate
  // 5. Use `canvas.captureStream` to capture the video stream from the canvas and use `LocalStream.replaceTrack` to replace the video stream
  const canvasStream = canvas.captureStream();
  await localStream.replaceTrack(canvasStream.getVideoTracks()[0]);
}
// Enable watermarking
await startWaterMark({ localStream, x: 100, y: 100, imageUrl: "./xxx.png" }); // The image URL must be passed in
localStream.play("elementId"); // Pass in `elementId` to play back the LocalStream
// 6. Push
await client.publish(localStream);

Encapsulating the watermarking disabling function

// Disable watermarking
async function stopWaterMark() {
  if (intervalId) {
    clearInterval(intervalId);
    intervalId = -1;
    await localStream.replaceTrack(sourceVideoTrack);
    if (video) {
      video.srcObject = null;
      video = null;
    }
  }
}

Attention

  • The image you are using for drawing needs to allow cross-domain access, Reference Document .

  • The code above demonstrates how to use setInterval to draw the canvas. If the JS thread is congested, drawing lag may occur. In that case, you can use requestAnimationFrame instead of setInterval to improve rendering performance.

  • In the code above, only one watermark is drawn. If you want the watermark to spread across the entire video, choose either of the following methods: create a watermark image that is wider and taller than the video window, and then draw the watermark image once so that the watermark image can spread across the canvas; call CanvasRenderingContext2D.drawImage multiple times and draw the same watermark image multiple times to make the watermark spread across the entire canvas.

  • You can use the CanvasRenderingContext2D.rotate API to rotate the watermark. Note that after drawing the watermark, you need to call this API to restore the rotation settings to avoid rotation in the next drawing.

    setInterval(() => {
      // Draw the video onto the canvas
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      // 1. Rotate the watermark 30 degrees
      ctx.rotate((30 * Math.PI) / 180);
      ctx.drawImage(image, x, y, width || image.width, height || image.height);
      // 2. After drawing the watermark, restore the rotation settings to avoid 30-degree rotation in the next drawing
      ctx.rotate((-30 * Math.PI) / 180);
    }, Math.floor(1000 / frameRate));
    
  • Known issues on iOS/Mac Safari

    1. The <video> tag cannot play mediaStream from canvas.captureStream on Safari version before iOS 15. webkit bug.
    2. Playing mediaStream from canvas.captureStream with <video> tag results a red video. webkit bug.

    Workaround: Using <canvas> tag to render video with watermark instead <video> tag on iOS Safari version before 15 or Mac Safari version after 15.

    // 1. add following code to startWaterMark
    async function startWaterMark() {
      // ...
      // your can use third party library to identify browser version.
      if (IOS_VERSION < 15 || MAC_SAFARI_VERSION >= 15) {
        // stop player
        localStream.stop();
        canvas.style.width = "100%";
        canvas.style.height = "100%";
        canvas.style.objectFit = "cover";
        // localStream play mirror by default, set css transform to mirror canvas
        canvas.style.transform = "rotateY(180deg)"; 
        // append canvas to dom
        // #local_stream is the elementId of stream.play(elementId)
        document.querySelector("#local_stream").appendChild(canvas);
      }
    }
    // 2. add following code to stopWaterMark
    async function stopWaterMark() {
      // ...
      if (IOS_VERSION < 15 || MAC_SAFARI_VERSION >= 15) {
        // remove canvas from dom
        document.querySelector("#local_stream").removeChild(this.canvas);
        this.canvas = null;
        // play localStream
        localStream.play("local_stream");
      }
    }