Manage Video


This section goes over the video stream APIs.

All methods can be accessed through a stream instance retrieved through the client.getMediaStream method:

const stream = client.getMediaStream();

Render Video

Active video can be rendered directly to a canvas element in your application.

As a best practice, you should render all participant videos on a single canvas to optimize CPU usage and performance. This also prevents against browser crashes that can occur when using multiple canvases to display video.

We recommend a single canvas with a 16/9 aspect ratio. The origins of the canvas coordinates are at the lower left corner of the canvas.

In most cases, video will be rendered immediately. Listen to the peer-video-state-change event to obtain the latest active video status.

client.on('peer-video-state-change', async(payload) => {
	const {
		action,
		userId
	} = payload;
	if (action === 'Start') {
		await stream.renderVideo(canvas, userId, width, height, x, y, quality);
	} else if (action === 'Stop') {
		await stream.stopRenderVideo(canvas);
	}
})

If you need to display a list of users in pages, we recommend that you maintain a list of rendered videos.

For performance reasons, the SDK can only render 9 videos at the same time. When the users in the list change, we recommend that you stop rendering the old video and render a new video.

Listen to the user-updated and user-removed events to update the list of rendered videos.

let renderedList = [];
let page = 0
let pageSize = 5;
const canvas = document.querySelector('.video-canvas'); // canvas to render the video
const handleParticipantsChange = (participants) => {
	const pageParticipants = participants.filter((user, index) => Math.floor(index / pageSize) === page);
	const videoParticipants = pageParticipants.filter(user => user.bVideoOn);
	/** For performance reasons, the SDK can only render 9 videos at the same time. We recommend that you stop rendering the old video and then render a new video. **/
	const removedVideoParticipants = renderedList.filter(user => videoParticipants.findIndex(user2 => user2.userId === user.userId) === -1);
	if (removedVideoParticipants.length > 0) {
		removedVideoParticipants.forEach(async(user) => {
			await stream.stopRenderVideo(canvas, user.userId);
		})
	}
	const addedVideoParticipants = videoParticipants.filter(user => renderedList.findIndex(user2 => user2.userId === user.userId) === -1);
	if (addedVideoParticipants.length > 0) {
		addedVideoParticipants.forEach(async(user) => {
			// render new video
			await stream.renderVideo(canvas, user.userId, width, height, x, y, quality);
		});
	}
	renderedList = videoParticipants;
}
client.on('user-updated', () => {
	const participants = client.getParticipantsList();
	handleParticipantsChange(participants);
})
client.on('user-removed', () => {
	const participants = client.getParticipantsList();
	handleParticipantsChange(participants);
})

When the styled width and height of the canvas changes, the SDK needs to be notified to adjust the dimension of the canvas. If you directly modify canvas.width or canvas.height, you may get errors as the canvas may transfer control offscreen, so you should catch those errors as shown below.

const resizeCallback = _.debounce((width, height) => {
	// catch errors
	try {
		canvas.width = width;
		canvas.height = height;
	} catch (e) {
		stream.updateVideoCanvasDimension(canvas, width, height);
	}

}, 300);
const resizeOb = new ResizeObserver((entries) => {
	entries.forEach(entry => {
		const {
			width,
			height
		} = entry;
		resizeCallback(width, height);
	})
})
resizeOb.observe(canvas);

To adjust the position of the rendered video on the canvas, use the adjustRenderedVideoPosition method:

stream.adjustRenderedVideoPosition(canvas, userId, newWidth, newHeight, newX, newY);

If you want to render videos of the same user in different places on the canvas, you must use an additional key to distinguish these renderings from one another. Use the optional parameter additionalUserKey in the renderVideo, stopRenderVideo, adjustRenderedVideoPosition methods to do so. When you use the additionalUserKey parameter, the userId and additionalUserKey are combined to become the unique identifier of the video.

Capture Video

Meeting participants can start capturing video if video is not muted when they join a session. Once active, captured video content will be streamed to other participants and will be rendered in the local HTML canvas. If there are multiple users sending video the current speaker will become the active user.

To start capturing video using the system default camera, pass captureOptions to specify the camera device and capture width and height:

const captureOptions = {
  cameraId:'cameraId',
  captureWidth:640,
  captureHeight:360
}
// start capture
stream.startVideo();
// stop capture
stream.stopVideo();

The browser may ask for permission to access camera devices. Use isCaptureForbidden to check if the user forbade camera access by accident:

const stream = client.getMediaStream();
if (stream.isCaptureForbidden()) {
  alert('We cannot start video without the permission to access camera.');
}

Switch devices

When you call the stream.startVideo method, you can specify the camera device using the captureOptions method. After capturing video, you can use stream.switchCamera to dynamically switch the camera devices:

await stream.switchCamera(cameraId);

Hot plugging of audio and video input devices

Use the device-change event to detect if a new video or audio device has been plugged in or unplugged.

The SDK will not automatically switch to the new device. Use stream.switchMicrophone, stream.switchSpeaker, or stream.switchCamera to switch to this device. When the device in use is unplugged, the SDK will automatically switch to the system default device:

const activeCameraId = stream.getActiveCamera();
client.on('device-change',()=>{
  const cameras = stream.getCameraList();
  if(cameras.length>0&&cameras[length-1].deviceId!==activeCameraId){
    stream.switchCamera(cameras[length-1.deviceId]);
  }
})