21 KiB
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:
- Invalid JWT token
- Session name doesn't match JWT
tpcclaim - Wrong SDK credentials
- 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
- SDK Architecture Pattern - Universal pattern
- Raw Audio Capture - Capture audio
- Raw Video Capture - Capture video
- Virtual Audio/Video - Custom media injection
- Command Channel - Custom command messaging with threading