# SDK Architecture Pattern - Universal Formula ## The 3-Step Pattern for ANY Feature **Once you understand this pattern, you can implement ANY Zoom Video SDK feature**. The SDK follows a consistent architectural pattern across all features. ### Pattern Overview ``` 1. Get Singleton/Helper → 2. Implement Delegate → 3. Subscribe & Use ``` This pattern applies to: - Video subscription - Audio processing - Screen sharing - Raw data capture - Raw data injection (virtual devices) - Recording - Live streaming - Transcription - Commands - Chat --- ## Step 1: Get Singleton/Helper The SDK uses a singleton hierarchy to access features: ```cpp // Main SDK singleton IZoomVideoSDK* video_sdk_obj = CreateZoomVideoSDKObj(); // Get helpers for specific features IZoomVideoSDKAudioHelper* audio = video_sdk_obj->getAudioHelper(); IZoomVideoSDKVideoHelper* video = video_sdk_obj->getVideoHelper(); IZoomVideoSDKShareHelper* share = video_sdk_obj->getShareHelper(); IZoomVideoSDKChatHelper* chat = video_sdk_obj->getChatHelper(); IZoomVideoSDKRecordingHelper* rec = video_sdk_obj->getRecordingHelper(); IZoomVideoSDKLiveStreamHelper* stream = video_sdk_obj->getLiveStreamHelper(); IZoomVideoSDKLiveTranscriptionHelper* transcription = video_sdk_obj->getLiveTranscriptionHelper(); ``` **Key Insight**: Helpers control YOUR streams/actions. To receive others' data, you subscribe to their pipes/canvas. --- ## Step 2: Implement Delegate The SDK communicates via callbacks. You implement delegate interfaces to receive events. ### Main Event Delegate ```cpp class MyDelegate : public IZoomVideoSDKDelegate { public: // Session events virtual void onSessionJoin() override { /* Session joined */ } virtual void onSessionLeave() override { /* Session left */ } virtual void onError(ZoomVideoSDKErrors errorCode, int detailErrorCode) override { /* Error occurred */ } // User events virtual void onUserJoin(IZoomVideoSDKUserHelper*, IVideoSDKVector*) override { /* Users joined */ } virtual void onUserLeave(IZoomVideoSDKUserHelper*, IVideoSDKVector*) override { /* Users left */ } // Video events virtual void onUserVideoStatusChanged(IZoomVideoSDKVideoHelper*, IVideoSDKVector*) override { /* Video status changed */ } // Audio events virtual void onUserAudioStatusChanged(IZoomVideoSDKAudioHelper*, IVideoSDKVector*) override { /* Audio status changed */ } virtual void onMixedAudioRawDataReceived(AudioRawData* data_) override { /* Mixed audio data */ } virtual void onOneWayAudioRawDataReceived(AudioRawData* data_, IZoomVideoSDKUser* pUser) override { /* Per-user audio */ } // Share events virtual void onUserShareStatusChanged(IZoomVideoSDKShareHelper*, IZoomVideoSDKUser*, IZoomVideoSDKShareAction*) override { /* Share status changed */ } // ... many more callbacks (see linux-reference.md for complete list) }; // Register delegate video_sdk_obj->addListener(new MyDelegate()); ``` ### Raw Data Delegates For raw video/share data: ```cpp class VideoDelegate : public IZoomVideoSDKRawDataPipeDelegate { public: virtual void onRawDataFrameReceived(YUVRawDataI420* data) override { // Process YUV420 frame int width = data->GetStreamWidth(); int height = data->GetStreamHeight(); char* yBuffer = data->GetYBuffer(); char* uBuffer = data->GetUBuffer(); char* vBuffer = data->GetVBuffer(); } virtual void onRawDataStatusChanged(RawDataStatus status) override { // Video on/off } }; ``` For virtual audio mic: ```cpp class VirtualMic : public IZoomVideoSDKVirtualAudioMic { public: virtual void onMicInitialize(IZoomVideoSDKAudioSender* sender) override { audio_sender_ = sender; // Store for sending } virtual void onMicStartSend() override { // Start sending audio frames } virtual void onMicStopSend() override { /* Stop sending */ } virtual void onMicUninitialized() override { audio_sender_ = nullptr; } private: IZoomVideoSDKAudioSender* audio_sender_ = nullptr; }; ``` For virtual video source: ```cpp class VirtualVideo : public IZoomVideoSDKVideoSource { public: virtual void onInitialize(IZoomVideoSDKVideoSender* sender, IVideoSDKVector* caps, VideoSourceCapability& suggest) override { video_sender_ = sender; // Store for sending } virtual void onStartSend() override { // Start sending video frames } virtual void onStopSend() override { /* Stop sending */ } virtual void onPropertyChange(...) override { /* Resolution changed */ } virtual void onUninitialized() override { video_sender_ = nullptr; } private: IZoomVideoSDKVideoSender* video_sender_ = nullptr; }; ``` --- ## Step 3: Subscribe & Use ### Pattern A: Control YOUR streams (via Helpers) ```cpp // Start YOUR audio IZoomVideoSDKAudioHelper* audio = video_sdk_obj->getAudioHelper(); audio->startAudio(); audio->muteAudio(true); // Start YOUR video IZoomVideoSDKVideoHelper* video = video_sdk_obj->getVideoHelper(); video->startVideo(); video->stopVideo(); // Start YOUR screen share IZoomVideoSDKShareHelper* share = video_sdk_obj->getShareHelper(); share->startShare(); share->stopShare(); ``` ### Pattern B: Receive data from OTHERS (via Pipes) ```cpp // Subscribe to remote user's video IZoomVideoSDKUser* user = /* get from onUserJoin */; IZoomVideoSDKRawDataPipe* pipe = user->GetVideoPipe(); pipe->subscribe(ZoomVideoSDKResolution_720P, videoDelegate); // Subscribe to remote user's share IZoomVideoSDKRawDataPipe* sharePipe = user->GetSharePipe(); sharePipe->subscribe(ZoomVideoSDKResolution_720P, shareDelegate); ``` ### Pattern C: Inject custom data (via Virtual Devices) ```cpp // Set virtual mic BEFORE joining session_context.virtualAudioMic = new VirtualMic(); session_context.audioOption.connect = true; session_context.audioOption.mute = false; // Set virtual video source BEFORE joining session_context.externalVideoSource = new VirtualVideo(); // Join session video_sdk_obj->joinSession(session_context); // Send data in onStartSend callbacks // For audio: audio_sender_->Send(data, len, sampleRate); // For video: video_sender_->sendVideoFrame(y, u, v, w, h, 0, rotation); ``` --- ## Common Patterns ### Pattern: Session Join ```cpp // 1. Get SDK singleton IZoomVideoSDK* sdk = CreateZoomVideoSDKObj(); // 2. Implement delegate sdk->addListener(new MyDelegate()); // 3. Configure and join ZoomVideoSDKSessionContext ctx; ctx.sessionName = "my-session"; ctx.userName = "Linux Bot"; ctx.token = "jwt-token"; ctx.audioOption.connect = true; ctx.audioOption.mute = false; ctx.videoOption.localVideoOn = false; // For headless: add virtual speaker ctx.virtualAudioSpeaker = new VirtualSpeaker(); IZoomVideoSDKSession* session = sdk->joinSession(ctx); ``` ### Pattern: Raw Video Capture ```cpp // 1. Create delegate class VideoCapture : public IZoomVideoSDKRawDataPipeDelegate { void onRawDataFrameReceived(YUVRawDataI420* data) override { // Save to file or process } }; // 2. In onUserJoin or onUserVideoStatusChanged IZoomVideoSDKUser* user = /* from callback */; IZoomVideoSDKRawDataPipe* pipe = user->GetVideoPipe(); pipe->subscribe(ZoomVideoSDKResolution_720P, new VideoCapture()); ``` ### Pattern: Virtual Audio Injection ```cpp // 1. Implement virtual mic class MyMic : public IZoomVideoSDKVirtualAudioMic { IZoomVideoSDKAudioSender* sender_; void onMicInitialize(IZoomVideoSDKAudioSender* sender) override { sender_ = sender; } void onMicStartSend() override { // Load PCM audio and send char* audioData = LoadPCMAudio(); sender_->Send(audioData, length, 32000); } }; // 2. Set before joining session_context.virtualAudioMic = new MyMic(); ``` ### Pattern: Cloud Recording ```cpp // 1. Get recording helper IZoomVideoSDKRecordingHelper* rec = sdk->getRecordingHelper(); // 2. Check permissions if (rec->canStartRecording() == ZoomVideoSDKErrors_Success) { // 3. Start recording rec->startCloudRecording(); } // Listen in delegate void onCloudRecordingStatus(RecordingStatus status, IZoomVideoSDKRecordingConsentHandler* handler) override { if (status == Recording_Start) { printf("Recording started\n"); } } ``` --- ## Linux-Specific Patterns ### Pattern: Headless Linux (Docker/WSL) **Problem**: No physical audio devices. **Solution**: Use virtual audio speaker and mic. ```cpp // Virtual speaker for receiving audio class MySpeaker : public IZoomVideoSDKVirtualAudioSpeaker { void onVirtualSpeakerMixedAudioReceived(AudioRawData* data) override { // Process or discard audio } void onVirtualSpeakerOneWayAudioReceived(AudioRawData* data, IZoomVideoSDKUser* user) override { // Per-user audio } void onVirtualSpeakerSharedAudioReceived(AudioRawData* data) override { // Share audio } }; // Virtual mic for sending audio class MyMic : public IZoomVideoSDKVirtualAudioMic { IZoomVideoSDKAudioSender* sender_; void onMicInitialize(IZoomVideoSDKAudioSender* sender) override { sender_ = sender; } void onMicStartSend() override { // Send PCM audio } void onMicStopSend() override {} void onMicUninitialized() override { sender_ = nullptr; } }; // Apply before joining session_context.virtualAudioSpeaker = new MySpeaker(); session_context.virtualAudioMic = new MyMic(); session_context.audioOption.connect = true; ``` ### Pattern: Qt/GTK UI Integration **Qt Pattern**: ```cpp // Use Qt signals/slots with SDK callbacks class QtVideoRenderer : public QWidget, public IZoomVideoSDKRawDataPipeDelegate { Q_OBJECT signals: void frameReceived(QImage frame); public: void onRawDataFrameReceived(YUVRawDataI420* data) override { // Convert YUV to RGB QImage img = ConvertYUVToRGB(data); // Emit signal (thread-safe) emit frameReceived(img); } }; // In Qt widget connect(renderer, &QtVideoRenderer::frameReceived, this, [this](QImage img) { // Update UI on main thread videoLabel->setPixmap(QPixmap::fromImage(img)); }); ``` **GTK Pattern**: ```cpp // Use Glib main loop for thread safety class GtkVideoRenderer : public IZoomVideoSDKRawDataPipeDelegate { void onRawDataFrameReceived(YUVRawDataI420* data) override { // Marshal to main thread g_idle_add([](gpointer user_data) { YUVRawDataI420* data = (YUVRawDataI420*)user_data; // Update GTK UI safely return G_SOURCE_REMOVE; }, data); } }; ``` --- ## Key Insights ### 1. Helpers vs Pipes | Component | Purpose | Example | |-----------|---------|---------| | **Helpers** | Control YOUR streams | `videoHelper->startVideo()` starts YOUR camera | | **Pipes** | Receive OTHERS' streams | `user->GetVideoPipe()->subscribe()` receives their video | ### 2. Virtual Devices for Injection To send custom audio/video, use virtual devices: - `IZoomVideoSDKVirtualAudioMic` - Send custom audio - `IZoomVideoSDKVirtualAudioSpeaker` - Receive audio (headless) - `IZoomVideoSDKVideoSource` - Send custom video - `IZoomVideoSDKShareSource` - Send custom share Set these **before** joining session. ### 3. Memory Modes Always use heap mode for raw data: ```cpp init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap; init_params.shareRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap; init_params.audioRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap; ``` ### 4. Qt5 Dependencies SDK requires Qt5 libraries (bundled in SDK package): - Copy from SDK `samples/qt_libs/Qt/lib/` - Create symlinks for versioned libraries - See [Qt Dependencies Guide](../troubleshooting/qt-dependencies.md) ### 5. PulseAudio for Audio Linux requires PulseAudio for raw audio features: ```bash sudo apt install -y pulseaudio mkdir -p ~/.config echo "[General]" > ~/.config/zoomus.conf echo "system.audio.type=default" >> ~/.config/zoomus.conf ``` --- ## Complete Example ```cpp #include "zoom_video_sdk_api.h" #include "zoom_video_sdk_interface.h" #include "zoom_video_sdk_delegate_interface.h" USING_ZOOM_VIDEO_SDK_NAMESPACE class BotDelegate : public IZoomVideoSDKDelegate { void onSessionJoin() override { printf("Joined session!\n"); // Start audio IZoomVideoSDKAudioHelper* audio = video_sdk_obj->getAudioHelper(); audio->startAudio(); audio->subscribe(); // For raw audio callbacks } void onUserJoin(IZoomVideoSDKUserHelper*, IVideoSDKVector* users) override { for (int i = 0; i < users->GetCount(); i++) { IZoomVideoSDKUser* user = users->GetItem(i); // Subscribe to video IZoomVideoSDKRawDataPipe* pipe = user->GetVideoPipe(); pipe->subscribe(ZoomVideoSDKResolution_720P, videoDelegate); } } void onMixedAudioRawDataReceived(AudioRawData* data) override { // Process audio char* buffer = data->GetBuffer(); unsigned int len = data->GetBufferLen(); unsigned int sampleRate = data->GetSampleRate(); } // ... implement all other required callbacks }; int main() { // 1. Create SDK IZoomVideoSDK* sdk = CreateZoomVideoSDKObj(); // 2. Initialize ZoomVideoSDKInitParams init_params; init_params.domain = "https://zoom.us"; init_params.enableLog = true; init_params.logFilePrefix = "bot"; init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap; init_params.shareRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap; init_params.audioRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap; sdk->initialize(init_params); // 3. Add delegate sdk->addListener(new BotDelegate()); // 4. Join session ZoomVideoSDKSessionContext ctx; ctx.sessionName = "my-session"; ctx.userName = "Linux Bot"; ctx.token = "jwt-token"; ctx.audioOption.connect = true; ctx.audioOption.mute = false; ctx.videoOption.localVideoOn = false; // For headless ctx.virtualAudioSpeaker = new VirtualSpeaker(); IZoomVideoSDKSession* session = sdk->joinSession(ctx); // Keep running while (running) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } // Cleanup sdk->leaveSession(false); sdk->cleanup(); DestroyZoomVideoSDKObj(); return 0; } ``` --- ## Next Steps - **[Singleton Hierarchy](singleton-hierarchy.md)** - Navigate the 5-level SDK structure - **[Session Join Pattern](../examples/session-join-pattern.md)** - Complete working session join - **[Raw Video Capture](../examples/raw-video-capture.md)** - Capture YUV420 frames - **[Raw Audio Capture](../examples/raw-audio-capture.md)** - Capture PCM audio - **[Virtual Audio/Video](../examples/virtual-audio-video.md)** - Inject custom media