# Session Join Pattern Complete working example for joining a Zoom Video SDK session. ## Prerequisites 1. Video SDK credentials from [Marketplace](https://marketplace.zoom.us/) 2. JWT token generated server-side 3. Modern browser (Chrome 80+, Firefox 75+, Safari 14+, Edge 80+) ## Complete Example ```javascript import ZoomVideo, { VideoQuality } from '@zoom/videosdk'; // State let client = null; let stream = null; // Configuration const config = { language: 'en-US', dependentAssets: 'Global', // or 'CDN', 'CN', or custom path options: { patchJsMedia: true, // webrtc: true, // Enable for HD video } }; /** * Initialize the SDK (call once) */ async function initializeSDK() { // Step 1: Create client (singleton) client = ZoomVideo.createClient(); // Optional: Check compatibility first const compatibility = ZoomVideo.checkSystemRequirements(); if (!compatibility.video || !compatibility.audio) { throw new Error('Browser not compatible'); } // Step 2: Initialize await client.init(config.language, config.dependentAssets, config.options); console.log('SDK initialized successfully'); return client; } /** * Join a session * @param {string} topic - Session name (must match JWT tpc) * @param {string} signature - Video SDK JWT from server * @param {string} userName - Display name * @param {string} password - Optional session password */ async function joinSession(topic, signature, userName, password = '') { try { // Step 3: Join session await client.join(topic, signature, userName, password); // Step 4: Get stream (ONLY AFTER JOIN!) stream = client.getMediaStream(); // Step 5: Set up event listeners setupEventListeners(); // Step 6: Get current user info const currentUser = client.getCurrentUserInfo(); console.log('Joined as:', currentUser.displayName, 'ID:', currentUser.userId); return { client, stream, currentUser }; } catch (error) { handleJoinError(error); throw error; } } /** * Set up all required event listeners */ function setupEventListeners() { // Connection state changes client.on('connection-change', (payload) => { console.log('Connection state:', payload.state); if (payload.state === 'Closed') { console.log('Disconnected. Reason:', payload.reason); cleanup(); } if (payload.state === 'Reconnecting') { console.log('Reconnecting...', payload.reason); } }); // New participant joined client.on('user-added', (payload) => { console.log('User(s) joined:', payload.map(u => u.displayName)); // Create UI containers for new users }); // Participant left client.on('user-removed', (payload) => { console.log('User(s) left:', payload.map(u => u.displayName)); // Clean up UI for users payload.forEach(user => { stream.detachVideo(user.userId); }); }); // Participant updated (name, mute, etc.) client.on('user-updated', (payload) => { console.log('User(s) updated:', payload); // Update UI state }); // Peer video state change (CRITICAL for rendering) client.on('peer-video-state-change', async (payload) => { const { action, userId } = payload; console.log(`User ${userId} video: ${action}`); if (action === 'Start') { const element = await stream.attachVideo(userId, VideoQuality.Video_360P); document.getElementById(`video-${userId}`)?.appendChild(element); } else { await stream.detachVideo(userId); } }); // Audio changes client.on('current-audio-change', (payload) => { console.log('Audio change:', payload.action, payload.type); }); // Active speaker client.on('active-speaker', (payload) => { if (payload.length > 0) { console.log('Active speaker:', payload[0].userId); } }); } /** * Start media after joining */ async function startMedia() { // Start video try { await stream.startVideo(); console.log('Video started'); // Attach own video const currentUser = client.getCurrentUserInfo(); const myVideoElement = await stream.attachVideo( currentUser.userId, VideoQuality.Video_360P ); document.getElementById('my-video')?.appendChild(myVideoElement); } catch (error) { console.error('Failed to start video:', error); } // Start audio try { await stream.startAudio(); console.log('Audio started'); } catch (error) { console.error('Failed to start audio:', error); } // Render existing participants (for mid-session join) await renderExistingParticipants(); } /** * Render videos of participants who joined before us */ async function renderExistingParticipants() { // Small delay to ensure all participants are loaded await new Promise(resolve => setTimeout(resolve, 500)); const users = client.getAllUser(); const currentUserId = client.getCurrentUserInfo().userId; for (const user of users) { if (user.bVideoOn && user.userId !== currentUserId) { const element = await stream.attachVideo(user.userId, VideoQuality.Video_360P); document.getElementById(`video-${user.userId}`)?.appendChild(element); } } } /** * Handle join errors */ function handleJoinError(error) { console.error('Join error:', error); const errorMessage = error.reason || error.message || 'Unknown error'; if (errorMessage.includes('signature')) { console.error('Invalid signature - regenerate JWT'); } else if (errorMessage.includes('Session')) { console.error('Session not found - host may not have started'); } else if (errorMessage.includes('password')) { console.error('Invalid password'); } else if (errorMessage.includes('Permission')) { console.error('Permission denied - check camera/mic access'); } } /** * Leave the session * @param {boolean} end - If true, ends session for all (host only) */ async function leaveSession(end = false) { try { await client.leave(end); console.log(end ? 'Session ended' : 'Left session'); cleanup(); } catch (error) { console.error('Leave error:', error); } } /** * Clean up resources */ function cleanup() { stream = null; // Remove event listeners, clear UI, etc. } // Usage async function main() { await initializeSDK(); const { currentUser } = await joinSession( 'my-session-topic', 'YOUR_JWT_TOKEN', 'User Name' ); await startMedia(); console.log('Ready! User:', currentUser.displayName); } main().catch(console.error); ``` ## CDN Version ```html