File: C:/Users/fred/.codex/.tmp/plugins/plugins/zoom/skills/general/use-cases/custom-video.md
# Custom Video
Build fully branded video experiences with complete UI control.
## Overview
Use the Zoom Video SDK to create custom video applications with your own UI, branding, and user experience - powered by Zoom's infrastructure.
## Skills Needed
- **zoom-video-sdk** - Primary
## Meeting SDK vs Video SDK
| Aspect | Meeting SDK | Video SDK |
|--------|-------------|-----------|
| UI | Zoom's UI | Your custom UI |
| Branding | Zoom branding | Your branding |
| Experience | Zoom meetings | Video sessions |
| Features | Full Zoom features | Core video features |
## Platform Options
| Platform | Use Case | Guide |
|----------|----------|-------|
| Web | Browser-based custom video | [video-sdk/SKILL.md](../../video-sdk/SKILL.md) |
| Linux | Headless bots, raw media capture/injection | [video-sdk/linux/linux.md](../../video-sdk/linux/linux.md) |
| iOS | Custom video on iPhone/iPad | - |
| Android | Custom video on Android | - |
| Desktop | Custom desktop video apps | - |
## Quick Start (Web)
```javascript
import ZoomVideo from '@zoom/videosdk';
const client = ZoomVideo.createClient();
await client.init('en-US', 'CDN');
await client.join(topic, signature, userName, password);
const stream = client.getMediaStream();
await stream.startVideo();
await stream.startAudio();
```
## Common Tasks
### Building Custom Video Layouts
```javascript
// Initialize Video SDK
const client = ZoomVideo.createClient();
await client.init('en-US', 'CDN');
await client.join(topic, signature, userName, password);
const stream = client.getMediaStream();
// Start my video
await stream.startVideo();
await stream.renderVideo(
document.querySelector('#my-video'),
client.getSessionInfo().userId,
1280, 720, // width, height
0, 0, // x, y offset
3 // quality (1-4)
);
// Listen for other participants
client.on('user-added', (payload) => {
payload.forEach(async (user) => {
if (user.bVideoOn) {
await renderParticipantVideo(user.userId);
}
});
});
async function renderParticipantVideo(userId) {
const container = createVideoContainer(userId);
await stream.renderVideo(container, userId, 640, 360, 0, 0, 2);
}
```
### Gallery View Implementation
```javascript
class GalleryView {
constructor(containerEl, maxPerPage = 25) {
this.container = containerEl;
this.maxPerPage = maxPerPage;
this.currentPage = 0;
this.participants = [];
}
updateLayout() {
const count = Math.min(this.participants.length, this.maxPerPage);
const cols = Math.ceil(Math.sqrt(count));
const rows = Math.ceil(count / cols);
const cellWidth = this.container.clientWidth / cols;
const cellHeight = this.container.clientHeight / rows;
// Render each participant in grid
this.participants.slice(0, this.maxPerPage).forEach((userId, index) => {
const x = (index % cols) * cellWidth;
const y = Math.floor(index / cols) * cellHeight;
stream.renderVideo(
this.getCanvas(userId),
userId,
cellWidth, cellHeight,
x, y, 2
);
});
}
addParticipant(userId) {
this.participants.push(userId);
this.updateLayout();
}
removeParticipant(userId) {
this.participants = this.participants.filter(id => id !== userId);
this.updateLayout();
}
}
```
### Speaker View Implementation
```javascript
class SpeakerView {
constructor(mainEl, stripEl) {
this.mainVideo = mainEl;
this.stripContainer = stripEl;
this.activeSpeaker = null;
this.participants = [];
}
setActiveSpeaker(userId) {
if (this.activeSpeaker === userId) return;
this.activeSpeaker = userId;
// Render active speaker large
stream.renderVideo(
this.mainVideo,
userId,
1280, 720, 0, 0, 4 // High quality
);
// Update thumbnail strip
this.updateStrip();
}
updateStrip() {
const others = this.participants.filter(id => id !== this.activeSpeaker);
const thumbWidth = 160;
const thumbHeight = 90;
others.forEach((userId, index) => {
stream.renderVideo(
this.getStripCanvas(userId),
userId,
thumbWidth, thumbHeight,
index * thumbWidth, 0, 1 // Lower quality
);
});
}
}
// Auto-switch to active speaker
client.on('active-speaker', (payload) => {
if (payload.userId) {
speakerView.setActiveSpeaker(payload.userId);
}
});
```
### Custom Controls
```javascript
// Audio controls
async function toggleMute() {
const stream = client.getMediaStream();
const isMuted = stream.isAudioMuted();
if (isMuted) {
await stream.unmuteAudio();
} else {
await stream.muteAudio();
}
updateMuteButton(!isMuted);
}
// Video controls
async function toggleVideo() {
const stream = client.getMediaStream();
const isVideoOn = stream.isCapturingVideo();
if (isVideoOn) {
await stream.stopVideo();
} else {
await stream.startVideo();
await stream.renderVideo(myCanvas, myUserId, 1280, 720, 0, 0, 3);
}
updateVideoButton(!isVideoOn);
}
// Device selection
async function switchCamera(deviceId) {
const stream = client.getMediaStream();
await stream.switchCamera(deviceId);
}
async function switchMicrophone(deviceId) {
const stream = client.getMediaStream();
await stream.switchMicrophone(deviceId);
}
// Get available devices
const cameras = await ZoomVideo.getCameras();
const mics = await ZoomVideo.getMicrophones();
const speakers = await ZoomVideo.getSpeakers();
```
## Resources
- **Video SDK docs**: https://developers.zoom.us/docs/video-sdk/
- **Web sample**: https://github.com/zoom/videosdk-web-sample
- **UI Toolkit**: https://developers.zoom.us/docs/video-sdk/web/ui-toolkit/