# web | Zoom Video SDK + Twilio migration This guide walks you through how to migrate your existing Twilio Video implementation to the [Zoom Video SDK](https://www.zoom.com/en/video-sdk/). If you're starting a new project from scratch, we recommend using the [Video SDK for web documentation](/docs/video-sdk/web/). ## Get Video SDK keys Create a [Video SDK developer account](https://zoom.us/pricing/developer) to access your [Video SDK Keys](/docs/video-sdk/get-credentials/). The Video SDK account type is separate from a standard Zoom user account. ## Install Install the Zoom Video SDK. ```shell npm install @zoom/videosdk ``` Or, use the [Zoom Video SDK CDN script tag](/docs/video-sdk/web/get-started/#install-from-cdn). You can remove the Twilio SDK from your project by uninstalling the package. ```shell 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](https://www.twilio.com/docs/video/tutorials/user-identity-access-tokens) generation logic with the [Zoom Video SDK JWT](/docs/video-sdk/auth/) generation logic. ### User Roles, Custom Identifiers, and more The [Zoom Video SDK JWT](/docs/video-sdk/auth/#payload) 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 `tpc` in the Video SDK JWT. - The host of the session is always the user whose `role` was set to `1` in the [Video SDK JWT](/docs/video-sdk/auth/). - 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`. ```javascript 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; }); ``` ```javascript import * as TwilioVideo from "twilio-video"; var twilioVideo = TwilioVideo; var twilioRoom; twilioRoom = await twilioVideo.connect(TOKEN, { name: "yourName", audio: false, video: false, dominantSpeaker: true, }); ``` ```javascript 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(); }); }); ``` ```javascript 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](https://www.twilio.com/docs/video/tutorials/developing-high-quality-video-applications), 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`. ```html ``` ```css #twilio-self-view-div video { width: 100%; height: auto; aspect-ratio: 16/9; } ``` ```javascript 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()); }); ``` ```javascript 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. ```html ``` ```css /* adjust the CSS to your liking */ video-player-container { width: 100%; height: 1000px; } video-player { width: 100%; height: auto; aspect-ratio: 16/9; } ``` ```javascript zoomSession.startVideo().then(() => { zoomSession .attachVideo(zoomVideo.getCurrentUserInfo().userId, RESOLUTION) .then((userVideo) => { document .querySelector("video-player-container") .appendChild(userVideo); }); }); ``` ```javascript 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 `video` element 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](/docs/video-sdk/web/share/#share-content-from-a-second-camera). Zoom renders the self view on the `video` element 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. ```javascript 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. ```javascript zoomSession.stopVideo().then(() => { zoomSession.detachVideo(USER_ID); }); ``` ```javascript 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`. ```html
``` ```css #twilio-user-view-div video { width: 100%; height: auto; aspect-ratio: 16/9; } ``` ```javascript 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(); }); }); ``` ```html ``` ```css /* adjust the CSS to your liking */ video-player-container { width: 100%; height: 1000px; } video-player { width: 100%; height: auto; aspect-ratio: 16/9; } ``` ```javascript 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); } }); ``` ```javascript 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. ```javascript 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); }); } }); }); ``` ```javascript 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](/docs/video-sdk/web/video/#render-720p-video). The [render remote videos](/docs/video-sdk/web/video/#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. ```javascript twilioVideo.createLocalAudioTrack().then((localAudioTrack) => { twilioRoom.localParticipant.publishTrack(localAudioTrack); const audioElement = localAudioTrack.attach(); document.body.appendChild(audioElement); }); ``` ```javascript let localAudioTrack = await twilioVideo.createLocalAudioTrack(); twilioRoom.localParticipant.publishTrack(localAudioTrack); const audioElement = localAudioTrack.attach(); document.body.appendChild(audioElement); ``` ```javascript 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. ```javascript twilioRoom.localParticipant.audioTracks.forEach((publication) => { publication.track.disable(); }); ``` ```javascript 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. ```javascript twilioRoom.localParticipant.audioTracks.forEach((publication) => { publication.track.enable(); }); ``` ```javascript 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. ```javascript twilioRoom.on("participantConnected", (participant) => { // user joined }); twilioVideo.on("participantDisconnected", (participant) => { // user left }); ``` ```javascript 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. ```javascript twilioVideo.on("dominantSpeakerChanged", (participant) => { // new active speaker }); ``` Best for highlighting who the active speaker is, since it fires quickly for each active mic. ```javascript 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. ```javascript 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. ```javascript twilioVideo.disconnect(); ``` Users can either leave the session: ```javascript zoomVideo.leave(); ``` Or the host can end the session for all users: ```javascript 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. ```javascript ZoomVideo.destroyClient(); ``` ## REST APIs and Webhooks Zoom Video SDK has a full suite of [REST APIs](/docs/api/video-sdk) and [webhooks](/docs/api/video-sdk/events/). _For reference, here's the [Twilio REST API documentation](https://www.twilio.com/docs/video/api)._ ## More Video SDK features For the complete feature map of Twilio Video to Zoom Video SDK, by platform, see the [feature map](/docs/video-sdk/twilio/map/). Zoom has a number of additional features including [live transcription and translation](/docs/video-sdk/web/transcription-translation/), [cloud recording](/docs/video-sdk/web/recording/), [Public Switched Telephone Network (PSTN) call out](/docs/video-sdk/web/pstn/) to join sessions by phone, [chat](/docs/video-sdk/web/chat/), [screen sharing](/docs/video-sdk/web/share/), and [command channel](/docs/video-sdk/web/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](/docs/video-sdk/) to learn more. The full [Zoom Video SDK reference can also be found here](https://marketplacefront.zoom.us/sdk/custom/web/modules/VideoClient.html). ## Contact Us - [Developer Sales](https://www.zoom.com/en/video-sdk/#contact-block-get-started-with-video-sdk-from-zoom) - [Developer Forum](https://devforum.zoom.us/c/video-sdk/) - [Developer Support](/support/) - [Developer Partner Services](https://explore.zoom.us/en/lp/developer-partner-services/) _See [Video SDK Plans & Pricing for Developer](https://zoom.us/pricing/developer) for pricing._