# Meeting SDK - Webinars

Embed webinar experiences using Meeting SDK.

## Overview

Meeting SDK can join webinars, not just meetings. This guide covers webinar-specific features and considerations.

## Webinar vs Meeting

| Feature | Meeting | Webinar |
|---------|---------|---------|
| Max participants | 100-1000 | 500-50,000 |
| Participant roles | Host, co-host, participant | Host, panelist, attendee |
| Attendee video | Yes | No (view-only) |
| Attendee audio | Yes | Raise hand to unmute |
| Q&A | No | Yes |
| Registration | Optional | Common |
| Practice session | No | Yes |

## Joining Webinars

### Web SDK

```javascript
const client = ZoomMtgEmbedded.createClient();

await client.init({
  zoomAppRoot: document.getElementById('meetingSDKElement'),
  language: 'en-US',
});

// Join webinar (same as meeting)
await client.join({
  sdkKey: SDK_KEY,
  signature: signature,
  meetingNumber: webinarId,  // Webinar ID
  userName: 'Attendee Name',
  userEmail: 'attendee@example.com',  // Required for registration
  passWord: password,
  tk: registrantToken,  // If registration required
});
```

### Registration Token

If the webinar requires registration:

```javascript
// 1. Register via API first
const response = await axios.post(
  `https://api.zoom.us/v2/webinars/${webinarId}/registrants`,
  {
    email: 'attendee@example.com',
    first_name: 'John',
    last_name: 'Doe'
  },
  { headers: { 'Authorization': `Bearer ${accessToken}` }}
);

// 2. Get registrant token
const registrantToken = response.data.registrant_id;

// 3. Use token when joining
await client.join({
  // ... other params
  tk: registrantToken,
});
```

### Native SDKs (iOS/Android/Windows)

```swift
// iOS - Join webinar
let joinParam = ZoomSDKJoinWebinarParam()
joinParam.webinarNumber = webinarNumber
joinParam.userName = "Attendee"
joinParam.userEmail = "attendee@example.com"
joinParam.webinarPassword = password
joinParam.webinarToken = registrantToken  // If registered

meetingService.joinWebinar(with: joinParam)
```

```kotlin
// Android - Join webinar
val joinParams = JoinMeetingParams().apply {
    meetingNo = webinarId
    displayName = "Attendee"
    password = webinarPassword
}

// Webinar uses same joinMeeting API
meetingService.joinMeetingWithParams(context, joinParams, JoinMeetingOptions())
```

## Attendee vs Panelist

### Role Detection

```javascript
// Web SDK
client.on('user-updated', (payload) => {
  const { userId } = payload;
  const user = client.getUser(userId);
  
  // Check role
  if (user.isHost) {
    console.log('User is host');
  } else if (user.userRole === 'panelist') {
    console.log('User is panelist');
  } else {
    console.log('User is attendee');
  }
});
```

### Attendee Limitations

Attendees in webinars have restricted capabilities:

| Action | Attendee Can Do? |
|--------|------------------|
| View video | Yes |
| Send video | No |
| Listen to audio | Yes |
| Unmute self | No (must be promoted) |
| Send chat | To panelists/everyone (if enabled) |
| Ask Q&A | Yes |
| Raise hand | Yes |

## Q&A Feature

### Web SDK Q&A

```javascript
// Check if Q&A is enabled
const qaClient = client.getQAClient();
const isQAEnabled = qaClient.isQAEnabled();

// Ask a question
await qaClient.askQuestion('What is the pricing?', false);  // false = public

// Answer a question (panelist/host only)
await qaClient.answerQuestion(questionId, 'The pricing is...');

// Get all questions
const questions = qaClient.getAllQuestions();
questions.forEach(q => {
  console.log(`Q: ${q.text}`);
  console.log(`A: ${q.answerText || 'Unanswered'}`);
});

// Q&A events
client.on('question-created', (payload) => {
  console.log('New question:', payload.question.text);
});

client.on('answer-created', (payload) => {
  console.log('Answer:', payload.answer.text);
});
```

### Native SDK Q&A

```swift
// iOS
let qaController = meetingService.getWebinarQAController()

// Ask question
qaController?.askQuestion("What is the pricing?", isAnonymous: false)

// Get questions (host/panelist)
let questions = qaController?.getAllQuestionList()
```

## Raise Hand

### Web SDK

```javascript
// Raise hand
client.raiseHand();

// Lower hand
client.lowerHand();

// Check status
const myself = client.getCurrentUser();
console.log('Hand raised:', myself.bRaiseHand);

// Event
client.on('user-updated', (payload) => {
  if (payload.bRaiseHand !== undefined) {
    console.log(`${payload.userId} ${payload.bRaiseHand ? 'raised' : 'lowered'} hand`);
  }
});
```

### Native SDK

```swift
// iOS
let webinarController = meetingService.getWebinarController()
webinarController?.raiseHand()
webinarController?.lowerHand()
```

## Promote/Demote Attendees

Hosts can promote attendees to panelists:

```javascript
// Web SDK (host only)
const webinarClient = client.getWebinarClient();

// Promote to panelist (allows video/audio)
await webinarClient.promoteAttendee(userId);

// Demote back to attendee
await webinarClient.demoteAttendee(userId);

// Allow attendee to talk (temporary unmute)
await webinarClient.allowAttendeeTalk(userId);
await webinarClient.disallowAttendeeTalk(userId);
```

## Polling

### Web SDK Polling

```javascript
// Get polling client
const pollingClient = client.getPollingClient();

// Check if polling available
const isAvailable = pollingClient.isPollingEnabled();

// Submit poll answer (attendee)
await pollingClient.submitPollAnswer(pollId, [
  { questionId: 'q1', answerIds: ['a1'] }
]);

// Poll events
client.on('poll-started', (payload) => {
  console.log('Poll started:', payload.poll);
  displayPoll(payload.poll);
});

client.on('poll-ended', (payload) => {
  console.log('Poll ended');
});
```

## Practice Session

Before a webinar starts, hosts can run a practice session:

```javascript
// Detect practice session
client.on('meeting-status-changed', (payload) => {
  if (payload.status === 'practice') {
    console.log('In practice session - attendees cannot join yet');
  } else if (payload.status === 'webinar') {
    console.log('Webinar is live');
  }
});
```

## Chat in Webinars

Chat permissions differ in webinars:

```javascript
// Get chat privilege
const chatClient = client.getChatClient();
const privilege = chatClient.getPrivilege();

// privilege values:
// 1 = No one
// 2 = Host and panelists only  
// 3 = Everyone publicly
// 4 = Everyone publicly and privately

// Send to panelists (attendee)
await chatClient.send('Question about pricing', /* to all panelists */);

// Send to everyone (if allowed)
await chatClient.sendToAll('Hello everyone!');
```

## Webinar-Specific Events

```javascript
// Web SDK events
client.on('webinar-invite', (payload) => {
  // Attendee invited to become panelist
});

client.on('webinar-depromote', (payload) => {
  // Demoted from panelist to attendee
});

client.on('webinar-allow-attendee-chat', (payload) => {
  // Chat permissions changed
});

client.on('webinar-attendee-status', (payload) => {
  // Attendee count updated
  console.log('Attendees:', payload.attendeeCount);
});
```

## Best Practices

### For Large Webinars

1. **Disable attendee video** - Already default for webinars
2. **Use Q&A instead of chat** - More organized for large audiences
3. **Pre-register attendees** - Better tracking and control
4. **Test with practice session** - Verify setup before going live

### UI Considerations

```javascript
// Adjust UI based on role
function setupUI(role) {
  if (role === 'attendee') {
    // Hide video controls (can't send video)
    hideElement('#videoControls');
    
    // Show Q&A and raise hand
    showElement('#qaPanel');
    showElement('#raiseHandButton');
    
    // Disable unmute (until promoted)
    disableElement('#unmuteButton');
  } else if (role === 'panelist' || role === 'host') {
    // Full controls
    showAllControls();
  }
}
```

## Resources

- **Webinar API**: https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#tag/Webinars
- **Meeting SDK Webinar**: https://developers.zoom.us/docs/meeting-sdk/web/webinar/
- **Developer Forum**: https://devforum.zoom.us/
