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
- Use TRTC.createStream to create a LocalStream and capture a video stream.
- Create a video tag for video stream playback and draw the video onto the canvas.
- Create an image instance and load the watermark image.
- Create a canvas tag and use
setInterval
to draw the video and watermark onto the canvas. - 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 ofsetInterval
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
- The
<video>
tag cannot play mediaStream fromcanvas.captureStream
on Safari version before iOS 15. webkit bug. - 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"); } }
- The