Generated banner for Video SDK Linux
Generated logo for Video SDK Linux

Video SDK Linux

AnthropicOfficialCreated by ZoomDistributed by Anthropic

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Zoom Video SDK for Linux - C++ headless bots, raw audio/video capture/injection, Qt/GTK integration, Docker support
— the description by Zoom

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides comprehensive documentation and architectural patterns for developing with the Zoom Video SDK on Linux. It covers the specific requirements for Linux environments, such as the mandatory use of raw data pipes instead of a Canvas API, PulseAudio configuration for audio, and integration with UI frameworks like Qt and GTK. The documentation includes setup guides, API references, and best practices for building headless bots, virtual devices, and custom media processing pipelines.

What it does

  • Provides architectural patterns for Zoom Video SDK integration on Linux.
  • Explains how to implement raw video and audio capture/injection.
  • Offers guidance on building headless bots for Docker environments.
  • Details integration steps for Qt and GTK UI frameworks.
  • Includes troubleshooting guides for common Linux-specific build and runtime issues.
  • Provides API references for session management, raw data pipes, and virtual devices.

When to use it

  • When developing C++ applications using the Zoom Video SDK on Linux.
  • When building headless Zoom bots that require virtual audio/video devices.
  • When implementing custom video rendering or audio processing pipelines.
  • When integrating Zoom video capabilities into Qt or GTK-based desktop applications.
  • When troubleshooting audio or build issues specific to the Linux SDK implementation.

Advantages

  • Enables headless operation for server-side automation.
  • Provides low-level access to raw media streams (YUV420/PCM).
  • Supports custom UI integration via Qt/GTK.
  • Includes clear guidance on Linux-specific architectural constraints.
  • Provides ready-to-use patterns for session management and media handling.

Usage examples

“How do I join a session using the Linux SDK?”
The agent provides the session join pattern, including the necessary JWT token, session context configuration, and the required virtual audio speaker setup for headless environments.
“Why is my video not rendering on Linux?”
The agent explains that the Linux SDK lacks a Canvas API and directs the user to implement a raw data pipe for video rendering.
“How do I configure audio for a headless bot?”
The agent provides the PulseAudio setup steps and the code required to initialize virtual audio speakers and microphones.
“What are the dependencies for building the SDK on Ubuntu?”
The agent lists the required system packages, including build-essential, CMake, and specific libxcb libraries, while noting the need to use bundled Qt5 libraries.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/anthropics/video-sdk-linux/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip video-sdk-linux.zip -d .agents/skills

Documentation by Zoom

Zoom Video SDK - Linux Development

Expert guidance for developing with the Zoom Video SDK on Linux. Build headless bots, raw media capture/injection applications, and custom UI integrations with Qt/GTK.

Official Documentation: https://developers.zoom.us/docs/video-sdk/linux/ API Reference: https://marketplacefront.zoom.us/sdk/custom/linux/ Sample Repository: https://github.com/zoom/videosdk-linux-raw-recording-sample

New to Video SDK? Follow this path:

  1. SDK Architecture Pattern - Universal 3-step pattern for ANY feature

  2. Session Join Pattern - Complete working code to join a session

  3. Raw Data vs Canvas - CRITICAL: Linux has NO Canvas API - raw data ONLY

  4. Raw Video Capture - Capture and process YUV420 frames

Reference:

Having issues?

Key Differences from Windows/macOS

FeatureLinuxWindows/Mac
Canvas API❌ Not available✅ Available
Raw Data Pipe✅ ONLY option✅ Available
UI IntegrationQt, GTK, SDL2, OpenGLWin32/WinForms/WPF, Cocoa
Headless Support✅ Excellent (Docker)Limited
AudioPulseAudio requiredNative
Virtual Devices✅ Required for headlessOptional

SDK Overview

The Zoom Video SDK for Linux is a C++ library optimized for:

  • Headless Bots: Docker/WSL support, no display required

  • Raw Data Access: Capture YUV420 video, PCM audio

  • Raw Data Injection: Virtual camera/mic for custom media

  • Screen Sharing: Capture or inject share data

  • Cloud Recording: Record sessions to Zoom cloud

  • Live Streaming: Stream to RTMP endpoints

  • Live Transcription: Real-time speech-to-text

  • Qt/GTK Integration: Full UI framework support

Prerequisites

System Requirements
  • OS: Ubuntu 20.04+, Debian 11+, or compatible

  • Architecture: x64 (recommended), ARM64

  • Compiler: GCC 9+, Clang 10+

  • CMake: 3.14 or later

  • Qt5: Bundled with SDK (do NOT install system Qt5)

Dependencies
sudo apt update
sudo apt install -y build-essential gcc cmake libglib2.0-dev liblzma-dev \
    libxcb-image0 libxcb-keysyms1 libxcb-xfixes0 libxcb-xkb1 libxcb-shape0 \
    libxcb-shm0 libxcb-randr0 libxcb-xtest0 libgbm1 libxtst6 libgl1 libnss3 \
    libasound2 libpulse0

# For headless Linux
sudo apt install -y pulseaudio

# PulseAudio configuration (CRITICAL for audio)
mkdir -p ~/.config
echo "[General]" > ~/.config/zoomus.conf
echo "system.audio.type=default" >> ~/.config/zoomus.conf

# Log directory
mkdir -p ~/.zoom/logs

Quick Start

#include "zoom_video_sdk_api.h"
#include "zoom_video_sdk_interface.h"
#include "zoom_video_sdk_delegate_interface.h"

USING_ZOOM_VIDEO_SDK_NAMESPACE

// 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(myDelegate);

// 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: Virtual audio speaker
ctx.virtualAudioSpeaker = new VirtualSpeaker();

IZoomVideoSDKSession* session = sdk->joinSession(ctx);

See Session Join Pattern for complete code.

Key Features

FeatureLinux SupportGuide
Session Management✅ FullSession Join
Raw Video (YUV420)✅ ONLY rendering optionRaw Video
Raw Audio (PCM)✅ FullRaw Audio
Virtual Camera/Mic✅ FullVirtual Devices
Cloud Recording✅ FullRecording
Live Streaming✅ FullLive Stream
Live Transcription✅ FullTranscription
Command Channel✅ FullCommands
Chat✅ FullChat
Qt Integration✅ RecommendedQt/GTK
GTK Integration✅ SupportedQt/GTK
Docker/Headless✅ ExcellentVirtual Devices

Critical Gotchas

⚠️ CRITICAL #1: No Canvas API on Linux

Problem: Linux SDK does NOT have Canvas API like Windows/Mac.

Solution: You MUST use Raw Data Pipe and implement your own rendering.

See: Raw Data vs Canvas

⚠️ CRITICAL #2: PulseAudio Required for Audio

Problem: SDK requires PulseAudio for raw audio functions.

Solution:

sudo apt install -y pulseaudio
mkdir -p ~/.config
echo "[General]" > ~/.config/zoomus.conf
echo "system.audio.type=default" >> ~/.config/zoomus.conf

See: PulseAudio Setup

⚠️ CRITICAL #3: Qt5 Dependencies

Problem: SDK requires Qt5 libraries (bundled, NOT system Qt5).

Solution:

# Copy from SDK package
cp -r samples/qt_libs/Qt/lib/* lib/zoom_video_sdk/

# Create symlinks
cd lib/zoom_video_sdk
for lib in libQt5*.so.5; do ln -sf $lib ${lib%.5}; done

See: Qt Dependencies

⚠️ CRITICAL #4: Heap Memory Mode

Always use heap mode for raw data:

init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
init_params.shareRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
init_params.audioRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
⚠️ CRITICAL #5: Virtual Audio for Headless

Problem: Docker/headless environments have no audio devices.

Solution: Use virtual audio speaker and mic.

session_context.virtualAudioSpeaker = new VirtualSpeaker();
session_context.virtualAudioMic = new VirtualMic();

See: Virtual Audio/Video

Sample Repositories

Official Samples
RepositoryDescription
raw-recording-sampleRaw audio/video capture
qt-quickstartQt6 UI integration
gtk-quickstartGTK3 UI integration
Sample Architecture
Headless Bot (Docker):
┌──────────────────────────────────┐
│  Virtual Audio Speaker/Mic       │
├──────────────────────────────────┤
│  Raw Data Processing             │
│  - YUV420 → File/Stream   


## Merged from video-sdk/linux/SKILL.md

# Zoom Video SDK Linux - Complete Documentation Index

## Quick Start Path

**If you're new to the SDK, follow this order:**

1. **Read the architecture pattern** → [concepts/sdk-architecture-pattern.md](concepts/sdk-architecture-pattern.md)
   - Universal formula: Singleton → Delegate → Subscribe
   - Once you understand this, you can implement any feature

2. **Understand Linux specifics** → [concepts/raw-data-vs-canvas.md](concepts/raw-data-vs-canvas.md)
   - **CRITICAL**: Linux has NO Canvas API - raw data ONLY

3. **Implement session join** → [examples/session-join-pattern.md](examples/session-join-pattern.md)
   - Complete working JWT + session join code

4. **Setup environment** → [troubleshooting/pulseaudio-setup.md](troubleshooting/pulseaudio-setup.md)
   - PulseAudio configuration (required for audio)
   - [troubleshooting/qt-dependencies.md](troubleshooting/qt-dependencies.md)
   - Qt5 library setup (bundled with SDK)

5. **Implement features** → Choose from examples below

---

## Documentation Structure

video-sdk/linux/ ├── SKILL.md # Main skill overview ├── SKILL.md # This file - navigation guide ├── linux.md # Platform summary │ ├── concepts/ # Core architectural patterns │ ├── sdk-architecture-pattern.md # Universal formula for ANY feature │ ├── singleton-hierarchy.md # 5-level navigation guide │ └── raw-data-vs-canvas.md # Linux-specific: raw data ONLY │ ├── examples/ # Complete working code │ ├── session-join-pattern.md # JWT auth + session join │ └── command-channel.md # Command channel with threading │ ├── troubleshooting/ # Problem solving guides │ ├── pulseaudio-setup.md # Audio configuration │ ├── qt-dependencies.md # Qt5 library setup │ ├── build-errors.md # Common build issues │ └── common-issues.md # Quick diagnostic workflow │ └── references/ # Reference documentation └── linux-reference.md # API hierarchy, methods, error codes


---

## By Use Case

### I want to build a headless bot
1. [SDK Architecture Pattern](concepts/sdk-architecture-pattern.md) - Understand the pattern
2. [Session Join Pattern](examples/session-join-pattern.md) - Join sessions
3. [PulseAudio Setup](troubleshooting/pulseaudio-setup.md) - Configure audio
4. [Raw Data vs Canvas](concepts/raw-data-vs-canvas.md) - Understand Linux differences

### I'm getting build errors
1. [Build Errors Guide](troubleshooting/build-errors.md) - SDK build issues
2. [Qt Dependencies](troubleshooting/qt-dependencies.md) - Qt5 setup
3. [Common Issues](troubleshooting/common-issues.md) - Quick diagnostics

### I'm getting runtime errors
1. [PulseAudio Setup](troubleshooting/pulseaudio-setup.md) - Audio not working
2. [Qt Dependencies](troubleshooting/qt-dependencies.md) - Library not found
3. [Common Issues](troubleshooting/common-issues.md) - Error code tables

### I want to use command channel
1. [Command Channel](examples/command-channel.md) - Send/receive commands
2. [Common Issues](troubleshooting/common-issues.md) - Threading requirements

### I want to implement a specific feature
1. [SDK Architecture Pattern](concepts/sdk-architecture-pattern.md) - **START HERE!**
2. [Singleton Hierarchy](concepts/singleton-hierarchy.md) - Navigate to the feature
3. [API Reference](references/linux-reference.md) - Method signatures

---

## Most Critical Documents

### 1. SDK Architecture Pattern (MASTER DOCUMENT)
**[concepts/sdk-architecture-pattern.md](concepts/sdk-architecture-pattern.md)**

The universal 3-step pattern:
1. Get singleton (SDK, helpers, session, users)
2. Implement delegate (event callbacks)
3. Subscribe and use

### 2. Raw Data vs Canvas (LINUX-SPECIFIC)
**[concepts/raw-data-vs-canvas.md](concepts/raw-data-vs-canvas.md)**

**CRITICAL**: Unlike Windows/Mac, Linux SDK has NO Canvas API. You MUST use raw data pipe.

### 3. PulseAudio Setup (MOST COMMON ISSUE)
**[troubleshooting/pulseaudio-setup.md](troubleshooting/pulseaudio-setup.md)**

Audio requires PulseAudio configuration.

### 4. Qt Dependencies
**[troubleshooting/qt-dependencies.md](troubleshooting/qt-dependencies.md)**

SDK requires bundled Qt5 libraries, NOT system Qt5.

---

## Key Learnings

### Critical Discoveries:

1. **Linux has NO Canvas API**
   - Windows/Mac have Canvas API for SDK-rendered video
   - Linux MUST use Raw Data Pipe
   - See: [Raw Data vs Canvas](concepts/raw-data-vs-canvas.md)

2. **PulseAudio is MANDATORY**
   - SDK requires PulseAudio for raw audio
   - Must configure ~/.config/zoomus.conf
   - See: [PulseAudio Setup](troubleshooting/pulseaudio-setup.md)

3. **Use Bundled Qt5, NOT System Qt5**
   - SDK includes specific Qt5 versions
   - Copy from samples/qt_libs/
   - See: [Qt Dependencies](troubleshooting/qt-dependencies.md)

4. **Helpers Control YOUR Streams Only**
   - `videoHelper->startVideo()` starts YOUR camera
   - To see others, subscribe to their VideoPipe
   - See: [Singleton Hierarchy](concepts/singleton-hierarchy.md)

5. **Virtual Devices for Headless**
   - Docker/headless needs virtual audio speaker/mic
   - Set before joining session
   - See: [Session Join Pattern](examples/session-join-pattern.md)

6. **Always Use Heap Memory Mode**
   ```cpp
   init_params.videoRawDataMemoryMode = ZoomVideoSDKRawDataMemoryModeHeap;
  1. GLib Main Loop Required

    • while/sleep loops don't dispatch SDK events

    • Must use g_main_loop_run()

    • See: Common Issues

  2. All SDK Calls Must Be on Main Thread

    • Background thread SDK calls return error 2 (Internal_Error)

    • Use g_idle_add() to schedule on GLib main thread

    • See: Command Channel

  3. Command Channel is Session-Scoped

    • Does NOT span across different sessions

    • Both sender and receiver must be in the same session

    • See: Command Channel


Sample Repositories


Quick Reference

"My code won't compile"

→ Build Errors Guide

"Audio not working"

→ PulseAudio Setup

"Library not found"

→ Qt Dependencies

"How do I implement [feature]?"

→ SDK Architecture Pattern

"What error code means what?"

→ Common Issues


Document Version

Based on Zoom Video SDK for Linux v2.x


Happy coding!

Remember: The SDK Architecture Pattern is your key to unlocking the entire SDK. Read it first!

Operations

  • RUNBOOK.md - 5-minute preflight and debugging checklist.

Files

examples/session-join-pattern.md

Session Join Pattern - Complete Working Example

Overview

This guide provides a complete, working example of joining a Zoom Video SDK session on Linux, including JWT generation, session configuration, event handling, and cleanup.

Prerequisites

# System dependencies
sudo apt update
sudo apt install -y build-essential gcc cmake libglib2.0-dev liblzma-dev \
    libxcb-image0 libxcb-keysyms1 libxcb-xfixes0 libxcb-xkb1 libxcb-shape0 \
    libxcb-shm0 libxcb-randr0 libxcb-xtest0 libgbm1 libxtst6 libgl1 libnss3 \
    libasound2 libpulse0

# For headless Linux
sudo apt install -y pulseaudio
mkdir -p ~/.config
echo "[General]" > ~/.config/zoomus.conf
echo "system.audio.type=default" >> ~/.config/zoomus.conf

# Create log directory
mkdir -p ~/.zoom/logs

JWT Token Generation

CRITICAL: You need a JWT token to join sessions. Generate from your SDK credentials.

Using Python
import jwt
import time

def generate_video_sdk_jwt(sdk_key, sdk_secret, session_name, role_type=1, session_key="", user_identity=""):
    iat = int(time.time()) - 30
    exp = iat + 60 * 60 * 2  # 2 hours
    
    payload = {
        "app_key": sdk_key,
        "iat": iat,
        "exp": exp,
        "tpc": session_name,
        "role_type": role_type,  # 0=participant, 1=host
    }
    
    if session_key:
        payload["session_key"] = session_key
    if user_identity:
        payload["user_identity"] = user_identity
    
    token = jwt.encode(payload, sdk_secret, algorithm="HS256")
    return token

# Usage
SDK_KEY = "YOUR_SDK_KEY"
SDK_SECRET = "YOUR_SDK_SECRET"
SESSION_NAME = "my-test-session"

jwt_token = generate_video_sdk_jwt(SDK_KEY, SDK_SECRET, SESSION_NAME, role_type=1)
print(f"JWT Token: {jwt_token}")
Using Node.js
const jwt = require('jsonwebtoken');

function generateVideoSDKJWT(sdkKey, sdkSecret, sessionName, roleType = 1) {
    const iat = Math.floor(Date.now() / 1000) - 30;
    const exp = iat + 60 * 60 * 2; // 2 hours
    
    const payload = {
        app_key: sdkKey,
        iat: iat,
        exp: exp,
        tpc: sessionName,
        role_type: roleType // 0=participant, 1=host
    };
    
    return jwt.sign(payload, sdkSecret);
}

const SDK_KEY = "YOUR_SDK_KEY";
const SDK_SECRET = "YOUR_SDK_SECRET";
const SESSION_NAME = "my-test-session";

const jwtToken = generateVideoSDKJWT(SDK_KEY, SDK_SECRET, SESSION_NAME, 1);
console.log(`JWT Token: ${jwtToken}`);

Complete C++ Implementation

Header File: BotDelegate.h
#ifndef BOT_DELEGATE_H
#define BOT_DELEGATE_H

#include "zoom_video_sdk_api.h"
#include "zoom_video_sdk_interface.h"
#include "zoom_video_sdk_delegate_interface.h"
#include <stdio.h>
#include <atomic>

USING_ZOOM_VIDEO_SDK_NAMESPACE

class BotDelegate : public IZoomVideoSDKDelegate {
public:
    BotDelegate() : running_(true) {}
    
    bool isRunning() const { return running_; }
    void stop() { running_ = false; }
    
    // Session events
    virtual void onSessionJoin() override;
    virtual void onSessionLeave() override;
    virtual void onError(ZoomVideoSDKErrors errorCode, int detailErrorCode) override;
    
    // User events
    virtual void onUserJoin(IZoomVideoSDKUserHelper* pUserHelper, 
                           IVideoSDKVector<IZoomVideoSDKUser*>* userList) override;
    virtual void onUserLeave(IZoomVideoSDKUserHelper* pUserHelper, 
                            IVideoSDKVector<IZoomVideoSDKUser*>* userList) override;
    virtual void onUserVideoStatusChanged(IZoomVideoSDKVideoHelper* pVideoHelper, 
                                         IVideoSDKVector<IZoomVideoSDKUser*>* userList) override;
    virtual void onUserAudioStatusChanged(IZoomVideoSDKAudioHelper* pAudioHelper, 
                                         IVideoSDKVector<IZoomVideoSDKUser*>* userList) override;
    
    // Password events
    virtual void onSessionNeedPassword(IZoomVideoSDKPasswordHandler* handler) override;
    virtual void onSessionPasswordWrong(IZoomVideoSDKPasswordHandler* handler) override;
    
    // Host/manager events
    virtual void onUserHostChanged(IZoomVideoSDKUserHelper* pUserHelper, 
                                  IZoomVideoSDKUser* pUser) override;
    virtual void onUserManagerChanged(IZoomVideoSDKUser* pUser) override;
    virtual void onUserNameChanged(IZoomVideoSDKUser* pUser) override;
    
    // Audio raw data (optional)
    virtual void onMixedAudioRawDataReceived(AudioRawData* data_) override;
    virtual void onOneWayAudioRawDataReceived(AudioRawData* data_, 
                                             IZoomVideoSDKUser* pUser) override;
    
    // Minimal stubs for required callbacks
    virtual void onUserShareStatusChanged(IZoomVideoSDKShareHelper*, IZoomVideoSDKUser*, 
                                         IZoomVideoSDKShareAction*) override {}
    virtual void onLiveStreamStatusChanged(IZoomVideoSDKLiveStreamHelper*, 
                                          ZoomVideoSDKLiveStreamStatus) override {}
    virtual void onCloudRecordingStatus(RecordingStatus, IZoomVideoSDKRecordingConsentHandler*) override {}
    virtual void onHostAskUnmute() override {}
    virtual void onUserActiveAudioChanged(IZoomVideoSDKAudioHelper*, IVideoSDKVector<IZoomVideoSDKUser*>*) override {}
    virtual void onSessionNeedPassword(IZoomVideoSDKPasswordHandler*) override {}
    virtual void onSessionPasswordWrong(IZoomVideoSDKPasswordHandler*) override {}
    virtual void onMixedAudioRawDataReceived(AudioRawData*) override {}
    virtual void onOneWayAudioRawDataReceived(AudioRawData*, IZoomVideoSDKUser*) override {}
    virtual void onShareAudioRawDataReceived(AudioRawData*) override {}
    virtual void onUserRecordingConsent(IZoomVideoSDKUser*) override {}
    virtual void onCommandReceived(IZoomVideoSDKUser*, const zchar_t*) override {}
    virtual void onCommandChannelConnectResult(bool) override {}
    virtual void onChatNewMessageNotify(IZoomVideoSDKChatHelper*, IZoomVideoSDKChatMessage*) override {}
    virtual void onChatMsgDeleteNotification(IZoomVideoSDKChatHelper*, const zchar_t*, 
                                            ZoomVideoSDKChatMessageDeleteType) override {}
    virtual void onShareContentChanged(IZoomVideoSDKShareHelper*, IZoomVideoSDKUser*, 
                                      IZoomVideoSDKShareAction*) override {}
    virtual void onLiveTranscriptionStatus(ZoomVideoSDKLiveTranscriptionStatus) override {}
    virtual void onLiveTranscriptionMsgReceived(const zchar_t*, IZoomVideoSDKUser*, 
                                               ZoomVideoSDKLiveTranscriptionOperationType) override {}
    virtual void onLiveTranscriptionMsgInfoReceived(ILiveTranscriptionMessageInfo*) override {}
    virtual void onLiveTranscriptionMsgError(ILiveTranscriptionLanguage*, 
                                            ILiveTranscriptionLanguage*) override {}
    virtual void onOriginalLanguageMsgReceived(ILiveTranscriptionMessageInfo*) override {}
    virtual void onInviteByPhoneStatus(PhoneStatus, PhoneFailedReason) override {}
    virtual void onCalloutJoinSuccess(IZoomVideoSDKUser*, const zchar_t*) override {}
    virtual void onCameraControlRequestResult(IZoomVideoSDKUser*, bool) override {}
    virtual void onCameraControlRequestReceived(IZoomVideoSDKUser*, ZoomVideoSDKCameraControlRequestType, 
                                               IZoomVideoSDKCameraControlRequestHandler*) override {}
    virtual void onProxyDetectComplete() override {}
    virtual void onProxySettingNotification(IZoomVideoSDKProxySettingHandler*) override {}
    virtual void onSSLCertVerifiedFailNotification(IZoomVideoSDKSSLCertificateInfo*) override {}
    virtual void onVideoAlphaChannelStatusChanged(bool) override {}
    virtual void onMultiCameraStreamStatusChanged(ZoomVideoSDKMultiCameraStreamStatus, 
                                                 IZoomVideoSDKUser*, IZoomVideoSDKRawDataPipe*) override {}
    virtual void onUserVideoNetworkStatusChanged(ZoomVideoSDKNetworkStatus, IZoomVideoSDKUser*) override {}
    virtual void onChatPrivilegeChanged(IZoomVideoSDKChatHelper*, ZoomVideoSDKChatPrivilegeType) override {}
    virtual void onVideoCanvasSubscribeFail(ZoomVideoSDKSubscribeFailReason, IZoomVideoSDKUser*, void*) override {}
    virtual void onShareCanvasSubscribeFail(ZoomVideoSDKSubscribeFailReason, IZoomVideoSDKUser*, void*) override {}
    
private:
    std::atomic<bool> running_;
};

#endif // BOT_DELEGATE_H
Implementation File: BotDelegate.cpp
#include "BotDelegate.h"

void BotDelegate::onSessionJoin() {
    printf("[EVENT] Session joined successfully!\n");
    
    // Get session info
    IZoomVideoSDKSession* session = video_sdk_obj->getSessionInfo();
    if (session) {
        printf("  Session Name: %s\n", session->getSessionName());
        printf("  Session ID: %s\n", session->getSessionID());
        
        // Get myself
        IZoomVideoSDKUser* myself = session->getMyself();
        if (myself) {
            printf("  My Name: %s\n", myself->getUserName());
            printf("  Is Host: %s\n", myself->isHost() ? "Yes" : "No");
        }
    }
    
    // Start audio
    IZoomVideoSDKAudioHelper* audio = video_sdk_obj->getAudioHelper();
    if (audio) {
        ZoomVideoSDKErrors err = audio->startAudio();
        if (err == ZoomVideoSDKErrors_Success) {
            printf("  Audio started\n");
            
            // Subscribe to raw audio (optional)
            audio->subscribe();
        } else {
            printf("  Failed to start audio: %d\n", err);
        }
    }
}

void BotDelegate::onSessionLeave() {
    printf("[EVENT] Session left\n");
    running_ = false;
}

void BotDelegate::onError(ZoomVideoSDKErrors errorCode, int detailErrorCode) {
    printf("[ERROR] Error occurred: %d, Detail: %d\n", errorCode, detailErrorCode);
    
    // Common errors
    switch (errorCode) {
        case ZoomVideoSDKErrors_Auth_Error:
            printf("  Authentication failed - check JWT token\n");
            break;
        case ZoomVideoSDKErrors_Auth_Wrong_Key_or_Secret:
            printf("  Wrong SDK key or secret\n");
            break;
        case ZoomVideoSDKErrors_Session_Join_Failed:
            printf("  Failed to join session\n");
            break;
        case ZoomVideoSDKErrors_Session_Need_Password:
            printf("  Session requires password\n");
            break;
        case ZoomVideoSDKErrors_Session_Password_Wrong:
            printf("  Wrong session password\n");
            break;
        default:
            break;
    }
}

void BotDelegate::onUserJoin(IZoomVideoSDKUserHelper* pUserHelper, 
                            IVideoSDKVector<IZoomVideoSDKUser*>* userList) {
    if (!userList) return;
    
    int count = userList->GetCount();
    printf("[EVENT] %d user(s) joined\n", count);
    
    for (int i = 0; i < count; i++) {
        IZoomVideoSDKUser* user = userList->GetItem(i);
        if (user) {
            printf("  User: %s\n", user->getUserName());
        }
    }
}

void BotDelegate::onUserLeave(IZoomVideoSDKUserHelper* pUserHelper, 
                             IVideoSDKVector<IZoomVideoSDKUser*>* userList) {
    if (!userList) return;
    
    int count = userList->GetCount();
    printf("[EVENT] %d user(s) left\n", count);
    
    for (int i = 0; i < count; i++) {
        IZoomVideoSDKUser* user = userList->GetItem(i);
        if (user) {
            printf("  User: %s\n", user->getUserName());
        }
    }
}

void BotDelegate::onUserVideoStatusChanged(IZoomVideoSDKVideoHelper* pVideoHelper, 
                                          IVideoSDKVector<IZoomVideoSDKUser*>* userList) {
    if (!userList) return;
    
    int count = userList->GetCount();
    for (int i = 0; i < count; i++) {
        IZoomVideoSDKUser* user = userList->GetItem(i);
        if (user) {
            printf("[EVENT] Video status changed for: %s\n", user->getUserName());
            // Subscribe to video here if needed
        }
    }
}

void BotDelegate::onUserAudioStatusChanged(IZoomVideoSDKAudioHelper* pAudioHelper, 
                                          IVideoSDKVector<IZoomVideoSDKUser*>* userList) {
    if (!userList) return;
    
    int count = userList->GetCount();
    for (int i = 0; i < count; i++) {
        IZoomVideoSDKUser* user = userList->GetItem(i);
        if (user) {
            IZoomVideoSDKAudioStatus* audioStatus = user->getAudioStatus();
            if (audioStatus) {
                printf("[EVENT] Audio status for %s: Muted=%s\n", 
                      user->getUserName(), 
                      audioStatus->isMuted() ? "Yes" : "No");
            }
        }
    }
}

void BotDelegate::onSessionNeedPassword(IZoomVideoSDKPasswordHandler* handler) {
    printf("[EVENT] Session requires password\n");
    // Provide password if available
    // handler->inputSessionPassword("password");
    // Or leave without password
    // handler->leaveSessionIgnorePassword();
}

void BotDelegate::onSessionPasswordWrong(IZoomVideoSDKPasswordHandler* handler) {
    printf("[EVENT] Wrong session password\n");
    // Retry with correct password or leave
    // handler->inputSessionPassword("correct-password");
    // handler->leaveSessionIgnorePassword();
}

void BotDelegate::onUserHostChanged(IZoomVideoSDKUserHelper* pUserHelper, 
                                   IZoomVideoSDKUser* pUser) {
    if (pUser) {
        printf("[EVENT] Host changed to: %s\n", pUser->getUserName());
    }
}

void BotDelegate::onUserManagerChanged(IZoomVideoSDKUser* pUser) {
    if (pUser) {
        printf("[EVENT] Manager changed: %s (Is Manager: %s)\n", 
              pUser->getUserName(), 
              pUser->isManager() ? "Yes" : "No");
    }
}

void BotDelegate::onUserNameChanged(IZoomVideoSDKUser* pUser) {
    if (pUser) {
        printf("[EVENT] User name changed to: %s\n", pUser->getUserName());
    }
}

void BotDelegate::onMixedAudioRawDataReceived(AudioRawData* data_) {
    // Process mixed audio (all participants)
    // char* buffer = data_->GetBuffer();
    // unsigned int len = data_->GetBufferLen();
    // unsigned int sampleRate = data_->GetSampleRate();
}

void BotDelegate::onOneWayAudioRawDataReceived(AudioRawData* data_, 
                                              IZoomVideoSDKUser* pUser) {
    // Process per-user audio
    // if (pUser) {
    //     printf("Audio from: %s\n", pUser->getUserName());
    // }
}
Main File: main.cpp

IMPORTANT: The SDK internally uses Qt/GLib for event dispatching. You MUST use a GLib main loop — a while (running) { sleep(); } loop will NOT dispatch SDK events, and callbacks like onSessionJoin will never fire. See Common Issues for details.

#include "BotDelegate.h"
#include <glib.h>
#include <thread>
#include <chrono>
#include <signal.h>

IZoomVideoSDK* video_sdk_obj = nullptr;
BotDelegate* delegate = nullptr;
static GMainLoop* g_loop = nullptr;

// GLib timeout callback - checks if bot should stop
static gboolean glib_timeout_callback(gpointer data) {
    BotDelegate* del = static_cast<BotDelegate*>(data);
    if (!del->isRunning()) {
        g_main_loop_quit(g_loop);
        return FALSE;  // Remove this timeout source
    }
    return TRUE;  // Keep checking
}

void signalHandler(int signum) {
    printf("\nReceived signal %d, cleaning up...\n", signum);
    if (delegate) {
        delegate->stop();
    }
    if (g_loop) {
        g_main_loop_quit(g_loop);
    }
}

int main(int argc, char* argv[]) {
    // Check arguments
    if (argc < 4) {
        printf("Usage: %s <session_name> <user_name> <jwt_token> [session_password]\n", argv[0]);
        return 1;
    }
    
    const char* sessionName = argv[1];
    const char* userName = argv[2];
    const char* jwtToken = argv[3];
    const char* sessionPassword = (argc >= 5) ? argv[4] : "";
    
    // Setup signal handlers
    signal(SIGINT, signalHandler);
    signal(SIGTERM, signalHandler);
    
    printf("Zoom Video SDK Bot\n");
    printf("==================\n");
    printf("Session: %s\n", sessionName);
    printf("User: %s\n", userName);
    printf("\n");
    
    // 1. Create SDK object
    video_sdk_obj = CreateZoomVideoSDKObj();
    if (!video_sdk_obj) {
        printf("Failed to create SDK object\n");
        return 1;
    }
    
    // 2. Initialize SDK
    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;
    init_params.enableIndirectRawdata = false;
    
    ZoomVideoSDKErrors err = video_sdk_obj->initialize(init_params);
    if (err != ZoomVideoSDKErrors_Success) {
        printf("Failed to initialize SDK: %d\n", err);
        DestroyZoomVideoSDKObj();
        return 1;
    }
    
    printf("SDK initialized\n");
    
    // 3. Add delegate
    delegate = new BotDelegate();
    video_sdk_obj->addListener(delegate);
    
    // 4. Configure session context
    ZoomVideoSDKSessionContext session_context;
    session_context.sessionName = sessionName;
    session_context.sessionPassword = sessionPassword;
    session_context.userName = userName;
    session_context.token = jwtToken;
    session_context.sessionIdleTimeoutMins = 40;
    session_context.autoLoadMutliStream = true;
    session_context.videoOption.localVideoOn = false;  // Headless bot
    session_context.audioOption.connect = true;
    session_context.audioOption.mute = false;
    
    // For headless Linux: Virtual audio speaker
    // Uncomment if you have implemented VirtualSpeaker class
    // session_context.virtualAudioSpeaker = new VirtualSpeaker();
    
    // 5. Join session
    printf("Joining session...\n");
    IZoomVideoSDKSession* session = video_sdk_obj->joinSession(session_context);
    
    if (!session) {
        printf("Failed to join session\n");
        video_sdk_obj->cleanup();
        DestroyZoomVideoSDKObj();
        delete delegate;
        return 1;
    }
    
    // 6. GLib main loop - REQUIRED for SDK event dispatching
    // A while/sleep loop does NOT work — SDK callbacks will never fire without GLib.
    printf("Bot is running. Press Ctrl+C to exit.\n\n");
    
    g_loop = g_main_loop_new(NULL, FALSE);
    g_timeout_add(100, glib_timeout_callback, delegate);
    g_main_loop_run(g_loop);  // Blocks here, SDK events dispatch on this thread
    g_main_loop_unref(g_loop);
    
    // 7. Cleanup
    printf("\nCleaning up...\n");
    
    if (video_sdk_obj->isInSession()) {
        video_sdk_obj->leaveSession(false);
        
        // Wait a bit for leave to complete
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    
    video_sdk_obj->cleanup();
    DestroyZoomVideoSDKObj();
    
    delete delegate;
    
    printf("Goodbye!\n");
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.14)
project(ZoomVideoSDKBot VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB REQUIRED glib-2.0)

# SDK paths
include_directories(
    ${CMAKE_SOURCE_DIR}/include
    ${CMAKE_SOURCE_DIR}/include/zoom_video_sdk
    ${GLIB_INCLUDE_DIRS}
)

link_directories(${CMAKE_SOURCE_DIR}/lib/zoom_video_sdk)

# Source files
set(SOURCES 
    src/main.cpp
    src/BotDelegate.cpp
)

add_executable(${PROJECT_NAME} ${SOURCES})

target_link_libraries(${PROJECT_NAME}
    videosdk
    ${GLIB_LIBRARIES}
    pthread
)

set_target_properties(${PROJECT_NAME} PROPERTIES
    BUILD_RPATH "${CMAKE_SOURCE_DIR}/lib/zoom_video_sdk"
    INSTALL_RPATH "${CMAKE_SOURCE_DIR}/lib/zoom_video_sdk"
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin
)

Build and Run

# 1. Extract SDK
tar -xf zoom-video-sdk-linux_x86_64.tar.xz
cd zoom-video-sdk-linux_x86_64

# 2. Copy Qt5 libraries and create symlinks
cp -r samples/qt_libs/Qt/lib/* lib/
cd lib
for lib in libQt5*.so.5; do
    ln -sf $lib ${lib%.5}
done
cd ..

# 3. Build
mkdir build && cd build
cmake ..
make

# 4. Generate JWT token (use Python script above)
JWT_TOKEN="your.jwt.token.here"

# 5. Run
cd ../bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../lib/zoom_video_sdk
./ZoomVideoSDKBot "test-session" "Linux Bot" "$JWT_TOKEN"

Testing

# Test with password
./ZoomVideoSDKBot "test-session" "Bot" "$JWT_TOKEN" "password123"

# Test as host (role_type=1 in JWT)
./ZoomVideoSDKBot "host-session" "Host Bot" "$HOST_JWT_TOKEN"

# Test as participant (role_type=0 in JWT)
./ZoomVideoSDKBot "join-session" "Participant Bot" "$PARTICIPANT_JWT_TOKEN"

Common Issues

Issue: Callbacks not firing

Solution: You MUST use a GLib main loop (g_main_loop_run). A while/sleep loop does not dispatch SDK events. See the main.cpp example above and Common Issues.

Issue: "Failed to join session"

Causes:

  1. Invalid JWT token

  2. Session name doesn't match JWT tpc claim

  3. Wrong SDK credentials

  4. Expired token

Solution: Verify JWT payload and regenerate token.

Issue: "Auth failed"

Solution: Check SDK_KEY and SDK_SECRET in JWT generation.

Issue: No audio on headless Linux

Solution: Use virtual audio speaker:

session_context.virtualAudioSpeaker = new VirtualSpeaker();

See Also

Requirements and compatibility

Written for: Claude Code, Claude apps

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Claude Code frontmatter

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Zoom
Publisher
Anthropic (Official)
Distributed by
Anthropic
Source
anthropics/knowledge-work-plugins · partner-built/zoom-plugin/skills/video-sdk/linux · commit e050c2e
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from partner-built/zoom-plugin/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Zoom or Anthropic.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download video-sdk-linux.zip