HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/Users/fred/.codex/.tmp/plugins/plugins/zoom/skills/video-sdk/web/concepts/singleton-hierarchy.md
# Singleton Hierarchy: Navigation Guide

## Overview

The Zoom Video SDK for Web uses a **service locator pattern** - a tree of objects where you navigate from the root `ZoomVideo` module to specific features. You don't construct objects; you traverse to them.

```
You want to...              You navigate to...
─────────────────────────────────────────────────────
Start your camera           client.getMediaStream() → stream.startVideo()
Mute audio                  client.getMediaStream() → stream.muteAudio()
Attach video                client.getMediaStream() → stream.attachVideo(userId, quality)
Send chat message           client.getChatClient() → chatClient.send(message)
Start screen share          client.getMediaStream() → stream.startShareScreen(canvas)
Start recording             client.getRecordingClient() → recordingClient.startCloudRecording()
```

---

## Complete Hierarchy (4 Levels Deep)

```
Level 0: Global Module
│
└─► ZoomVideo ──────────────────────────────────────────────────► Default Export
    │
    ├─► Static Methods (Before Session)
    │   ├── createClient()               → VideoClient (singleton)
    │   ├── destroyClient()              → Promise<void>
    │   ├── checkSystemRequirements()    → MediaCompatibility
    │   ├── checkFeatureRequirements()   → SupportFeatures
    │   ├── getDevices(skip?)            → Promise<MediaDeviceInfo[]>
    │   ├── preloadDependentAssets(path) → void
    │   ├── createLocalVideoTrack(id?)   → LocalVideoTrack
    │   ├── createLocalAudioTrack(id?)   → LocalAudioTrack
    │   └── VERSION                      → string
    │
    └─► Level 1: VideoClient (The Root Singleton)
        │
        ├─► Session Lifecycle
        │   ├── init(language, assets, options)   → ExecutedResult
        │   ├── join(topic, token, name, pass)    → ExecutedResult
        │   ├── leave(end?)                       → ExecutedResult
        │   ├── on(event, callback)               → void
        │   └── off(event, callback)              → void
        │
        ├─► Participant Info
        │   ├── getAllUser()              → Participant[]
        │   ├── getCurrentUserInfo()      → Participant
        │   ├── getUser(userId)           → Participant | undefined
        │   ├── getSessionHost()          → Participant | undefined
        │   ├── getSessionInfo()          → SessionInfo
        │   ├── isHost()                  → boolean
        │   ├── isManager()               → boolean
        │   └── isOriginalHost()          → boolean
        │
        ├─► Host Controls
        │   ├── makeHost(userId)          → ExecutedResult
        │   ├── makeManager(userId)       → ExecutedResult
        │   ├── revokeManager(userId)     → ExecutedResult
        │   ├── removeUser(userId)        → ExecutedResult
        │   ├── changeName(name, userId?) → ExecutedResult
        │   └── reclaimHost()             → ExecutedResult
        │
        ├─► Level 2: Media Stream (MAIN INTERFACE)
        │   │
        │   └── getMediaStream()          → Stream
        │       │
        │       ├─► Video Functions
        │       │   ├── startVideo(options?)            → ExecutedResult
        │       │   ├── stopVideo()                     → ExecutedResult
        │       │   ├── attachVideo(userId, quality)    → VideoPlayer
        │       │   ├── detachVideo(userId, element?)   → VideoPlayer | VideoPlayer[]
        │       │   ├── renderVideo(canvas, userId, ...) → ExecutedResult [DEPRECATED]
        │       │   ├── stopRenderVideo(canvas, userId) → ExecutedResult [DEPRECATED]
        │       │   ├── switchCamera(cameraId)          → ExecutedResult
        │       │   ├── getCameraList()                 → MediaDevice[]
        │       │   ├── getActiveCamera()               → string
        │       │   ├── isSupportHDVideo()              → boolean
        │       │   ├── getVideoMaxQuality()            → VideoQuality
        │       │   ├── getMaxRenderableVideos()        → number
        │       │   ├── isSupportMultipleVideos()       → boolean
        │       │   ├── isSupportVirtualBackground()    → boolean
        │       │   ├── updateVirtualBackgroundImage(img) → ExecutedResult
        │       │   ├── mirrorVideo(enable)             → ExecutedResult
        │       │   ├── spotlightVideo(userId)          → ExecutedResult
        │       │   └── screenshotVideo(userId?)        → Promise<Blob>
        │       │
        │       ├─► Audio Functions
        │       │   ├── startAudio(options?)            → ExecutedResult
        │       │   ├── stopAudio()                     → ExecutedResult
        │       │   ├── muteAudio(userId?)              → ExecutedResult
        │       │   ├── unmuteAudio(userId?)            → ExecutedResult
        │       │   ├── muteAllAudio()                  → ExecutedResult
        │       │   ├── unmuteAllAudio()                → ExecutedResult
        │       │   ├── switchMicrophone(micId)         → ExecutedResult
        │       │   ├── switchSpeaker(speakerId)        → ExecutedResult
        │       │   ├── getMicList()                    → MediaDevice[]
        │       │   ├── getSpeakerList()                → MediaDevice[]
        │       │   ├── getActiveMicrophone()           → string
        │       │   ├── getActiveSpeaker()              → string
        │       │   ├── isAudioMuted(userId?)           → boolean
        │       │   └── enableBackgroundNoiseSuppression(enable) → ExecutedResult
        │       │
        │       ├─► Screen Share Functions
        │       │   ├── startShareScreen(canvas, options?) → ExecutedResult
        │       │   ├── stopShareScreen()               → ExecutedResult
        │       │   ├── startShareView(canvas, userId)  → ExecutedResult
        │       │   ├── stopShareView()                 → ExecutedResult
        │       │   ├── attachShareView(userId, el?)    → VideoPlayer
        │       │   ├── detachShareView(userId, el?)    → VideoPlayer
        │       │   ├── pauseShareScreen()              → ExecutedResult
        │       │   ├── resumeShareScreen()             → ExecutedResult
        │       │   ├── isStartShareScreenWithVideoElement() → boolean
        │       │   ├── getActiveShareUserId()          → number
        │       │   ├── getShareStatus()                → ShareStatus
        │       │   ├── getShareUserList()              → Participant[]
        │       │   ├── lockShare(isLocked)             → ExecutedResult
        │       │   └── setSharePrivilege(privilege)    → ExecutedResult
        │       │
        │       ├─► Annotation Functions
        │       │   ├── startAnnotation(...)            → ExecutedResult
        │       │   ├── stopAnnotation()                → ExecutedResult
        │       │   ├── getAnnotationController()       → AnnotationController
        │       │   └── canDoAnnotation()               → boolean
        │       │
        │       ├─► Processor Functions (Custom Effects)
        │       │   ├── createProcessor(params)         → Processor
        │       │   ├── addProcessor(processor)         → Promise<"">
        │       │   ├── removeProcessor(processor)      → Promise<"">
        │       │   ├── isSupportVideoProcessor()       → boolean
        │       │   ├── isSupportAudioProcessor()       → boolean
        │       │   └── isSupportShareProcessor()       → boolean
        │       │
        │       ├─► Camera Control (PTZ)
        │       │   ├── controlCamera(option)           → ExecutedResult
        │       │   ├── requestFarEndCameraControl(userId) → ExecutedResult
        │       │   ├── approveFarEndCameraControl(userId) → ExecutedResult
        │       │   └── declineFarEndCameraControl(userId) → ExecutedResult
        │       │
        │       └─► Phone Functions
        │           ├── inviteByPhone(country, phone, name) → ExecutedResult
        │           ├── cancelInviteByPhone(...)        → ExecutedResult
        │           ├── hangup()                        → ExecutedResult
        │           └── isSupportPhoneFeature()         → boolean
        │
        ├─► Level 2: Feature Clients
        │   │
        │   ├── getChatClient()           → ChatClient
        │   │   ├── send(message)                 → ExecutedResult
        │   │   ├── sendToUser(userId, message)   → ExecutedResult
        │   │   ├── sendFile(file, receiverId)    → ExecutedResult
        │   │   └── downloadFile(fileUrl, ...)    → ExecutedResult
        │   │
        │   ├── getCommandClient()        → CommandChannel
        │   │   ├── send(text)                    → ExecutedResult
        │   │   └── sendToUser(userId, text)      → ExecutedResult
        │   │
        │   ├── getRecordingClient()      → RecordingClient
        │   │   ├── startCloudRecording()         → ExecutedResult
        │   │   ├── stopCloudRecording()          → ExecutedResult
        │   │   ├── pauseCloudRecording()         → ExecutedResult
        │   │   └── resumeCloudRecording()        → ExecutedResult
        │   │
        │   ├── getLiveTranscriptionClient() → LiveTranscriptionClient
        │   │   ├── startLiveTranscription()      → ExecutedResult
        │   │   ├── stopLiveTranscription()       → ExecutedResult
        │   │   ├── enableReceivingCaption(enable) → ExecutedResult
        │   │   └── setSpokenLanguage(language)   → ExecutedResult
        │   │
        │   ├── getLiveStreamClient()     → LiveStreamClient
        │   │   ├── startLiveStream(url, key)     → ExecutedResult
        │   │   └── stopLiveStream()              → ExecutedResult
        │   │
        │   ├── getSubsessionClient()     → SubsessionClient
        │   │   ├── createSubsessions(names)      → ExecutedResult
        │   │   ├── openSubsessions(rooms)        → ExecutedResult
        │   │   ├── closeAllSubsessions()         → ExecutedResult
        │   │   ├── broadcast(message)            → ExecutedResult
        │   │   └── getSubsessionList()           → Subsession[]
        │   │
        │   ├── getWhiteboardClient()     → WhiteboardClient
        │   │   ├── startWhiteboard(options?)     → ExecutedResult
        │   │   └── stopWhiteboard()              → ExecutedResult
        │   │
        │   ├── getBroadcastStreamingClient() → BroadcastStreamingClient
        │   │   ├── startBroadcast()              → ExecutedResult
        │   │   └── stopBroadcast()               → ExecutedResult
        │   │
        │   ├── getRealTimeMediaStreamsClient() → RealTimeMediaStreamsClient
        │   │   ├── startRealTimeMediaStream()    → ExecutedResult
        │   │   └── stopRealTimeMediaStream()     → ExecutedResult
        │   │
        │   └── getLoggerClient(options?) → LoggerClient
        │       ├── log(...)                      → void
        │       └── setLogLevel(level)            → void
        │
        └─► Level 3: Participant Object
            │
            └── Participant Interface
                ├── userId                    → number
                ├── displayName               → string
                ├── bVideoOn                  → boolean
                ├── muted                     → boolean
                ├── audio                     → '' | 'computer' | 'phone'
                ├── sharerOn                  → boolean
                ├── bShareAudioOn             → boolean
                └── isHost                    → boolean
```

---

## Key Difference from Windows SDK

| Aspect | Windows SDK | Web SDK |
|--------|-------------|---------|
| **Root Object** | `IZoomVideoSDK` | `ZoomVideo.createClient()` → `VideoClient` |
| **Feature Access** | Helpers (`getVideoHelper()`) | Stream + Clients (`getMediaStream()`, `getChatClient()`) |
| **Video Rendering** | Canvas API / Raw Data Pipe | `attachVideo()` returns VideoPlayer |
| **Events** | Delegate callbacks | `client.on('event', handler)` |
| **Depth** | 5 levels max | 4 levels max |

---

## When to Use Each Level

| Level | When | Example |
|-------|------|---------|
| **Level 0** | Before SDK init, check compatibility | `ZoomVideo.checkSystemRequirements()` |
| **Level 1** | Session lifecycle, get clients | `client.join(...)`, `client.getChatClient()` |
| **Level 2** | Media operations (Stream) | `stream.startVideo()`, `stream.attachVideo()` |
| **Level 2** | Feature-specific operations (Clients) | `chatClient.send(...)`, `recordingClient.start...()` |
| **Level 3** | Participant info | `user.bVideoOn`, `user.displayName` |

---

## Navigation by Feature

| Feature | Navigation Path |
|---------|-----------------|
| **Start camera** | `client.getMediaStream().startVideo()` |
| **Stop camera** | `client.getMediaStream().stopVideo()` |
| **Attach video** | `client.getMediaStream().attachVideo(userId, quality)` |
| **Detach video** | `client.getMediaStream().detachVideo(userId)` |
| **Switch camera** | `client.getMediaStream().switchCamera(deviceId)` |
| **Start audio** | `client.getMediaStream().startAudio()` |
| **Mute audio** | `client.getMediaStream().muteAudio()` |
| **Unmute audio** | `client.getMediaStream().unmuteAudio()` |
| **Start share** | `client.getMediaStream().startShareScreen(canvas)` |
| **Stop share** | `client.getMediaStream().stopShareScreen()` |
| **View share** | `client.getMediaStream().startShareView(canvas, userId)` |
| **Send chat** | `client.getChatClient().send(message)` |
| **Start recording** | `client.getRecordingClient().startCloudRecording()` |
| **Start transcription** | `client.getLiveTranscriptionClient().startLiveTranscription()` |
| **Send command** | `client.getCommandClient().send(text)` |
| **Get participants** | `client.getAllUser()` |
| **Get myself** | `client.getCurrentUserInfo()` |
| **Leave session** | `client.leave()` |
| **End session** | `client.leave(true)` |

---

## Event Subscription Pattern

All events are subscribed via `client.on()`:

```javascript
// Session events
client.on('connection-change', (payload) => { ... });
client.on('user-added', (payload) => { ... });
client.on('user-removed', (payload) => { ... });
client.on('user-updated', (payload) => { ... });

// Video events
client.on('peer-video-state-change', (payload) => { ... });
client.on('video-active-change', (payload) => { ... });
client.on('video-capturing-change', (payload) => { ... });

// Audio events
client.on('current-audio-change', (payload) => { ... });
client.on('active-speaker', (payload) => { ... });
client.on('host-ask-unmute-audio', (payload) => { ... });

// Share events
client.on('active-share-change', (payload) => { ... });
client.on('peer-share-state-change', (payload) => { ... });
client.on('passively-stop-share', (payload) => { ... });

// Chat events
client.on('chat-on-message', (payload) => { ... });

// Recording events
client.on('recording-change', (payload) => { ... });

// Transcription events
client.on('caption-message', (payload) => { ... });
```

---

## Critical Timing Rules

### 1. Stream is ONLY Available After Join

```javascript
// WRONG - stream is undefined
const stream = client.getMediaStream();
await client.join(...);

// CORRECT - get stream after join
await client.join(...);
const stream = client.getMediaStream();
```

### 2. Event-Driven Video Rendering

```javascript
// Listen for video changes
client.on('peer-video-state-change', async (payload) => {
  const { action, userId } = payload;

  if (action === 'Start') {
    const element = await stream.attachVideo(userId, VideoQuality.Video_360P);
    container.appendChild(element);
  } else {
    await stream.detachVideo(userId);
  }
});
```

### 3. Check Feature Support Before Using

```javascript
// HD Video
if (stream.isSupportHDVideo()) {
  await stream.startVideo({ hd: true });
}

// Virtual Background
if (stream.isSupportVirtualBackground()) {
  await stream.updateVirtualBackgroundImage('blur');
}

// Video Processor
if (stream.isSupportVideoProcessor()) {
  const processor = await stream.createProcessor(params);
  await stream.addProcessor(processor);
}
```

---

## Practical Rules

### 1. Get Stream After Join

```javascript
// WRONG
const stream = client.getMediaStream();  // undefined!
await client.join(...);

// CORRECT
await client.join(...);
const stream = client.getMediaStream();  // Works!
```

### 2. Check Element Type for Screen Share

```javascript
// Check which element to use
if (stream.isStartShareScreenWithVideoElement()) {
  // Use HTMLVideoElement
  await stream.startShareScreen(videoElement);
} else {
  // Use HTMLCanvasElement
  await stream.startShareScreen(canvasElement);
}
```

### 3. Render Existing Participants on Mid-Session Join

```javascript
// After joining, render existing participants
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);
    container.appendChild(element);
  }
}
```

---

## Related Documentation

- [SDK Architecture Pattern](sdk-architecture-pattern.md) - Universal 5-step pattern
- [API Reference](../references/web-reference.md) - Complete method signatures
- [SKILL.md](../SKILL.md) - Main skill overview

---

**TL;DR**: Start at `ZoomVideo.createClient()`, get stream AFTER join, use `stream.` for media and `client.getXXXClient()` for features. Events via `client.on()`.