web | Zoom Video SDK + Twilio migration
This guide walks you through how to migrate your existing Twilio Video implementation to the Zoom Video SDK. If you're starting a new project from scratch, we recommend using the Video SDK for web documentation.
Get Video SDK keys
Create a Video SDK developer account to access your Video SDK Keys. The Video SDK account type is separate from a standard Zoom user account.
Install
Install the Zoom Video SDK.
npm install @zoom/videosdk
Or, use the Zoom Video SDK CDN script tag.
You can remove the Twilio SDK from your project by uninstalling the package.
npm uninstall twilio-video
Or, if using the Twilio CDN, remove the script tag from index.html.
Authorize
Both Zoom and Twilio use JSON Web Tokens (JWT) to generate a token for users to join sessions. On your auth server, replace your Twilio Video JWT generation logic with the Zoom Video SDK JWT generation logic.
User Roles, Custom Identifiers, and more
The Zoom Video SDK JWT also offers a number of properties to control your session functionality including custom user identifiers, custom session identifier, data center region selection, roles (host and user), and more.
Start and Join sessions
Zoom offers the ability to specify a session name and passcode. Zoom and Twilio both require a token to join a session, but Zoom also has an optional session passcode. With Zoom you do not need to set the audio, video, or dominantSpeaker parameters as that is handled automatically by the Video SDK.
- The session is started when the first user joins.
- The session name must match the
tpcin the Video SDK JWT. - The host of the session is always the user whose
rolewas set to1in the Video SDK JWT. - The passcode is optional, but if set for a session, it's required for other users joining that session.
Replace your Twilio Video import and connect functions with the Zoom Video SDK import and init, and join functions.
For purposes of the Zoom code samples in this migration guide we are using different variable naming than what you may see in the main Video SDK documentation. Here we refer to client as zoomVideo and stream as zoomSession.
import * as TwilioVideo from "twilio-video";
var twilioVideo = TwilioVideo;
var twilioRoom;
twilioVideo
.connect(TOKEN, {
name: "yourName",
audio: false,
video: false,
dominantSpeaker: true,
})
.then((room) => {
twilioRoom = room;
});
import * as TwilioVideo from "twilio-video";
var twilioVideo = TwilioVideo;
var twilioRoom;
twilioRoom = await twilioVideo.connect(TOKEN, {
name: "yourName",
audio: false,
video: false,
dominantSpeaker: true,
});
import ZoomVideo from "@zoom/videosdk";
var zoomVideo = ZoomVideo.createClient();
var zoomSession;
zoomVideo.init("en-US", "Global", { patchJsMedia: true }).then(() => {
zoomVideo
.join("sessionName", TOKEN, "yourName", "sessionPasscode")
.then(() => {
zoomSession = zoomVideo.getMediaStream();
});
});
import ZoomVideo from "@zoom/videosdk";
var zoomVideo = ZoomVideo.createClient();
var zoomSession;
await zoomVideo.init("en-US", "Global", { patchJsMedia: true });
await zoomVideo.join("sessionName", TOKEN, "yourName", "sessionPasscode");
zoomSession = zoomVideo.getMediaStream();
Video
While Twilio requires additional configuration to set the quality of the video, Zoom handles the video quality automatically based on network conditions and device capabilities. In low bandwidth environments Zoom prioritizes audio and FPS over video resolution.
Turn the camera on
Replace your Twilio self video view code with the Zoom self video view code.
Twilio self video view code
Twilio has a concept of multiple video and audio tracks, which is always an array. To render the self view, Twilio appends a video element inside the specified div. Similar to Twilio, Zoom appends an element of the video inside the specified div.
</div>
#twilio-self-view-div video {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
twilioVideo
.createLocalVideoTrack({
height: { ideal: 720, min: 480, max: 1080 },
width: { ideal: 1280, min: 640, max: 1920 },
aspectRatio: 16 / 9,
})
.then((localVideoTrack) => {
twilioRoom.localParticipant.publishTrack(localVideoTrack);
const localMediaContainer = document.getElementById(
"twilio-self-view-div",
);
localMediaContainer.appendChild(localVideoTrack.attach());
});
let localVideoTrack = await twilioVideo.createLocalVideoTrack({
height: { ideal: 720, min: 480, max: 1080 },
width: { ideal: 1280, min: 640, max: 1920 },
aspectRatio: 16 / 9,
});
twilioRoom.localParticipant.publishTrack(localVideoTrack);
const localMediaContainer = document.getElementById("twilio-self-view-div");
localMediaContainer.appendChild(localVideoTrack.attach());
Zoom self video view code
You can start and render video using individual HTML elements. The startvideo function prompts the browser to ask for camera permissions. Be sure to tie the startVideo function to a user button click or the browser could block access to the camera. Once the user grants camera permission to the browser, you can attach the self view video within the video container.
<video-player-container></video-player-container>
/* adjust the CSS to your liking */
video-player-container {
width: 100%;
height: 1000px;
}
video-player {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
zoomSession.startVideo().then(() => {
zoomSession
.attachVideo(zoomVideo.getCurrentUserInfo().userId, RESOLUTION)
.then((userVideo) => {
document
.querySelector("video-player-container")
.appendChild(userVideo);
});
});
await zoomSession.startVideo();
let userVideo = await zoomSession.attachVideo(
zoomVideo.getCurrentUserInfo().userId,
RESOLUTION,
);
document.querySelector("video-player-container").appendChild(userVideo);
- The video will always be rendered in a 16:9 ratio.
- You can crop the HTML
videoelement using CSS or JavaScript to shape the video how you'd like. - You can send up to two video streams per connected user. To send a second camera stream use the screen share channel. Zoom renders the self view on the
videoelement depending on the device and browser.
Turn the camera off
Twilio Video is track based, so you must loop through each video track to unpublish the video, stop the video camera, and remove the video element from the DOM. Zoom simplifies this by providing one function.
twilioRoom.localParticipant.videoTracks.forEach((publication) => {
publication.unpublish();
publication.track.stop();
var selfTwilioVideo = document.getElementById("twilio-self-view-div");
selfTwilioVideo.querySelector("video").remove();
});
Replace your Twilio unpublish video track logic with the Zoom stopVideo() and detachVideo() functions.
zoomSession.stopVideo().then(() => {
zoomSession.detachVideo(USER_ID);
});
await stopVideo();
zoomSession.detachVideo(USER_ID);
Render remote user videos
Similar to the Twilio participantConnected and trackSubscribed event listeners, Zoom triggers the peer-video-state-change event listener every time a user turns their video on or off.
Zoom offers a Twilio-like attach mechanism to render videos, which allows you to render videos on individual HTML elements. To render a user's video, call the attachVideo() function to specify the userId of the user's video you want to render and the resolution. To stop rendering a user's video, call the detachVideo() function and pass in the userId.
<div class="twilio-user-view-div" width="1920" height="1080"></div>
#twilio-user-view-div video {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
twilioRoom.on("participantConnected", (participant) => {
participant.on("trackSubscribed", (track) => {
// a user turned on their video, render it
document
.getElementById("twilio-user-view-div")
.appendChild(track.attach());
});
participant.on("trackUnsubscribed", (track) => {
// a user turned off their video, stop rendering it
var selfTwilioVideo = document.getElementById("twilio-user-view-div");
selfTwilioVideo.querySelector("video").remove();
});
});
<video-player-container></video-player-container>
/* adjust the CSS to your liking */
video-player-container {
width: 100%;
height: 1000px;
}
video-player {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
zoomVideo.on("peer-video-state-change", (payload) => {
if (payload.action === "Start") {
// a user turned on their video, render it
zoomSession.attachVideo(payload.userId, 3).then((userVideo) => {
document
.querySelector("video-player-container")
.appendChild(userVideo);
});
} else if (payload.action === "Stop") {
// a user turned off their video, stop rendering it
zoomSession.detachVideo(payload.userId);
}
});
zoomVideo.on("peer-video-state-change", async (payload) => {
if (payload.action === "Start") {
// a user turned on their video, render it
let userVideo = await zoomSession.attachVideo(user.userId, 3);
document.querySelector("video-player-container").appendChild(userVideo);
} else if (payload.action === "Stop") {
// a user turned off their video, stop rendering it
zoomSession.detachVideo(payload.userId);
}
});
When a user joins for the first time, you can add logic to check if there are remote user videos already being sent, and if there are, you can render them.
zoomVideo.join(topic, token, userName, password).then(() => {
zoomSession = zoomVideo.getMediaStream();
zoomVideo.getAllUser().forEach((user) => {
if (user.bVideoOn) {
zoomSession.attachVideo(user.userId, 3).then((userVideo) => {
document
.querySelector("video-player-container")
.appendChild(userVideo);
});
}
});
});
await zoomVideo.join(topic, token, userName, password);
zoomSession = zoomVideo.getMediaStream();
zoomVideo.getAllUser().forEach(async (user) => {
if (user.bVideoOn) {
let userVideo = await zoomSession.attachVideo(user.userId, 3);
document.querySelector("video-player-container").appendChild(userVideo);
}
});
Video SDK for web can render up to 25 videos at a time on desktop browsers, and 4 videos on mobile browsers. You can show additional videos using pagination. The maximum rendering video quality on web is 720p.
The render remote videos example shows how to render four videos at a time on a 4x4 grid.
Audio
Since Twilio Video is track based, you must loop through each audio track to start the audio, and add the audio element to the DOM. Zoom simplifies this by providing one function.
Start audio
Replace your Twilio audio track logic with the Zoom startAudio() function. The startAudio() function connects to session audio and will also unmute the user. Tie the startAudio function to a user button click or the browser could block access to the microphone.
twilioVideo.createLocalAudioTrack().then((localAudioTrack) => {
twilioRoom.localParticipant.publishTrack(localAudioTrack);
const audioElement = localAudioTrack.attach();
document.body.appendChild(audioElement);
});
let localAudioTrack = await twilioVideo.createLocalAudioTrack();
twilioRoom.localParticipant.publishTrack(localAudioTrack);
const audioElement = localAudioTrack.attach();
document.body.appendChild(audioElement);
zoomSession.startAudio();
Mute microphone
Since Twilio Video is track based, you must loop through each audio track to mute the microphone. Zoom simplifies this by providing one function.
Replace your Twilio mute audio track logic with the Zoom muteAudio() function.
twilioRoom.localParticipant.audioTracks.forEach((publication) => {
publication.track.disable();
});
zoomSession.muteAudio();
Unmute microphone
Since Twilio Video is track based, you must loop through each audio track to unmute the microphone, but with Zoom, you need only one function.
Replace your Twilio unmute audio track logic with the Zoom unmuteAudio() function.
twilioRoom.localParticipant.audioTracks.forEach((publication) => {
publication.track.enable();
});
zoomSession.unmuteAudio();
Event Listeners
For user management, both Zoom and Twilio have event listeners to help you maintain the state for everyone connected.
- User connection changes, including join, leave, and when the host ends the session.
- User video changes, such as when the user turns the camera on or off.
- User audio changes, including start audio, mute, and unmute.
User connection changes
Use user connection change event listeners to know when a new user joins, show a mute or unmute icon next to their name when they change their microphone state, or know when a user leaves the session.
Replace the Twilio participantConnected event listener with the Zoom user-added event listener. See the other user connection event listeners for more.
twilioRoom.on("participantConnected", (participant) => {
// user joined
});
twilioVideo.on("participantDisconnected", (participant) => {
// user left
});
zoomVideo.on("user-added", (payload) => {
// user joined
});
zoomVideo.on("user-updated", (payload) => {
// user updated, like unmuting and muting
});
zoomVideo.on("user-removed", (payload) => {
// user left
});
zoomVideo.on("connection-change", (payload) => {
// session ended by host
});
Active speaker changes
Replace the Twilio dominant speaker event listener with the Zoom respective active speaker change event listener. This is useful if you want to highlight the user who is speaking.
twilioVideo.on("dominantSpeakerChanged", (participant) => {
// new active speaker
});
Best for highlighting who the active speaker is, since it fires quickly for each active mic.
zoomVideo.on("active-speaker", (payload) => {
// new active speaker, for example, use for microphone visuals, css video border, etc.
});
Best for re-rendering videos like for speaker view video layouts. Since this event listener fires when there is an active speaker that is talking for more than one second, this allows for a smoother user experience. It fires for active speakers, who have their video on or off, so you can render the "active speaker user" bigger (whether rendering a video or displaying a video off icon) for speaker view layouts.
zoomVideo.on("video-active-change", (payload) => {
// new active speaker, for example, use for video rendering changes, size changes, depending on your use case.
});
Leave and End sessions
Replace the Twilio disconnect function with the Zoom leave function.
twilioVideo.disconnect();
Users can either leave the session:
zoomVideo.leave();
Or the host can end the session for all users:
zoomVideo.leave(true);
Clean up Video SDK
When you are no longer using Zoom Video SDK in your app, you can call a simple method to clean up the Zoom Video SDK.
ZoomVideo.destroyClient();
REST APIs and Webhooks
Zoom Video SDK has a full suite of REST APIs and webhooks.
For reference, here's the Twilio REST API documentation.
More Video SDK features
For the complete feature map of Twilio Video to Zoom Video SDK, by platform, see the feature map.
Zoom has a number of additional features including live transcription and translation, cloud recording, Public Switched Telephone Network (PSTN) call out to join sessions by phone, chat, screen sharing, and command channel to send data to other users in a session. Zoom also supports additional Video SDK platforms including Android, iOS, Linux, macOS, Windows, and wrappers such as Flutter and React Native. See the Zoom Video SDK documentation to learn more.
The full Zoom Video SDK reference can also be found here.
Contact Us
See Video SDK Plans & Pricing for Developer for pricing.