Tutorial: Enable Watermarking

Enable Watermarking

Function Description

This article mainly introduces how to implement the push stream watermark function based on canvas.

Prerequisites

The watermark function mainly uses canvas.captureStream for custom rendering. The compatibility can be referred to canvas.captureStream compatibility and TRTC Web SDK compatibility.

Implementation Process

  1. Use TRTC.startLocalVideo to capture the video stream.
  2. Create a video tag to play the video stream, which is used to draw the video to 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 to the canvas.
  5. Use canvas.captureStream to capture the video stream from the canvas and use TRTC.updateLocalVideo to replace the video stream.

Code Example

Call startLocalVideo to capture the video stream

const trtc = TRTC.create();
// 1. Capture the camera video
await trtc.startLocalVideo();
// Camera videoTrack
let sourceVideoTrack = null;
// IntervalId for rendering canvas
let intervalId = -1;
// Video tag for rendering camera videoTrack
let video = null;

Encapsulate the function to start watermark

// Used to load watermark images
function loadImage(imageUrl) {
  return new Promise((resolve) => {
    const image = new Image();
    // Enable cross-domain access to avoid the problem that the videoTrack generated by starting the watermark is black when loading non-origin images.
    image.crossOrigin = 'anonymous';
    image.src = imageUrl;
    image.onload = () => resolve(image);
  });
}
async function startWaterMark({ x, y, width, height, imageUrl }) {
  if (intervalId >= 0) return;
  // 2. Create a video tag to play the video stream
  const video = document.createElement('video');
  sourceVideoTrack = trtc.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 to 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 to the canvas
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
    // Draw the watermark image to the canvas, you can control the position and size of the watermark
    ctx.drawImage(image, x, y, width || image.width, height || image.height);
  }, Math.floor(1000 / settings.frameRate)); // Calculate the time interval for each drawing based on the frame rate
  // 5. Use canvas.captureStream to capture the video stream from the canvas and use updateLocalVideo to replace the video stream
  const canvasStream = canvas.captureStream();
  await trtc.updateLocalVideo({ option: { videoTrack: canvasStream.getVideoTracks()[0] } });
}
// Start the watermark
await startWaterMark({ x: 100, y: 100, imageUrl: './xxx.png' }); // The image url needs to be passed in

Encapsulate the function to stop watermark

// Stop the watermark
async function stopWaterMark() {
  if (intervalId >= 0) {
    clearInterval(intervalId);
    intervalId = -1;
    await trtc.updateLocalVideo({ option: { videoTrack: sourceVideoTrack } });
    if (video) {
      video.srcObject = null;
      video = null;
    }
  }
}

Notes

  • The image you use for drawing needs to allow cross-domain access. Click reference document

  • The above code example uses setInterval to draw the canvas. If the JS thread is congested, the drawing may be stuck. You can consider using requestAnimationFrame instead of setInterval to improve rendering performance.

  • The above code only draws one watermark. Maybe your business scenario requires the watermark to cover the entire video. At this time, there are two solutions. One is to make a watermark image larger than the video window, draw it once, and let the watermark image cover the entire canvas; the other solution is to call CanvasRenderingContext2D.drawImage multiple times, draw the same watermark file multiple times, and make the watermark cover the entire canvas.

  • You can use the CanvasRenderingContext2D.rotate interface to rotate the watermark by a certain angle. It should be noted that: after drawing the watermark, you need to call this interface again to rotate it back to avoid the next drawing, and the video also rotates 30 degrees.

    setInterval(() => {
      // Draw the video to the canvas
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      // 1. Rotate the watermark by 30 degrees
      ctx.rotate((30 * Math.PI) / 180);
      ctx.drawImage(image, x, y, width || image.width, height || image.height);
      // 2. After the watermark is drawn, it needs to be rotated back to avoid the next drawing, and the video also rotates 30 degrees.
      ctx.rotate((-30 * Math.PI) / 180);
    }, Math.floor(1000 / frameRate));
    
  • Known issues with using watermarks in iOS/Mac Safari:

    1. Versions below iOS 15 cannot use the video tag to play the video stream captured by canvas.captureStream. This issue is a defect in iOS, see: webkit bug.

    2. In Mac Safari 15.0+ versions, playing the video stream captured by canvas.captureStream using the video tag will cause a red screen. This issue is a defect in Mac Safari, see: webkit bug.

    Workaround:

    In versions below iOS 15 and above Mac Safari 15, do not use the video tag to play the video stream captured by canvas.captureStream, but directly render it on the page using canvas.

// 1. Add the following code to startWaterMark to place the canvas in the DOM for rendering
async function startWaterMark() {
  // ... 
  // You can use a third-party userAgent parsing library to determine the version of iOS Mac
  if (IOS_VERSION < 15 || MAC_SAFARI_VERSION >= 15) {
    // Stop rendering the video tag
    await trtc.updateLocalVideo({ view: null });
    // Place the canvas in the DOM for rendering.
    canvas.style.width = '100%';
    canvas.style.height = '100%';
    canvas.style.objectFit = 'cover';
    canvas.style.transform = 'rotateY(180deg)'; // The local video is displayed in mirror image, align here
    // 'local_stream' is the elementId passed in updateLocalVideo({view:elementId})
    document.querySelector('#local_stream').appendChild(canvas);
  }
}
// 2. Add the following code to stopWaterMark. When closing the watermark, remove the canvas and resume using the video tag for playback.
async function stopWaterMark() {
  // ...
  if (IOS_VERSION < 15 || MAC_SAFARI_VERSION >= 15) {
    await trtc.updateLocalVideo({ view: 'elementId' });
    this.canvas.remove()
    this.canvas = null;
  }
}