Subchapter 118.2
concepts/high-level-scenarios.mdMarkdown15 KBView on GitHub
This guide covers common bot architectures and patterns that remain stable across SDK versions, with flexible approaches to handle API changes.
Goal: Join meetings to transcribe conversations in real-time.
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Meeting │───►│ Meeting SDK │───►│ Audio Stream │───►│ Transcription│
│ Starts │ │ Join + Auth │ │ (PCM 32kHz) │ │ Service │
└─────────────┘ └──────────────┘ └─────────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ StartRaw │ │ onMixedAudio │ │ Store │
│ Recording │ │ RawDataReceived │ │ Transcript │
└──────────────┘ └─────────────────┘ └──────────────┘// STEP 1: Initialize (stable across versions)
InitParam init_params;
init_params.strWebDomain = "https://zoom.us";
init_params.enableLogByDefault = true;
init_params.rawdataOpts.audioRawDataMemoryMode = ZoomSDKRawDataMemoryModeHeap;
InitSDK(init_params);
// STEP 2: Authenticate (JWT token - stable)
AuthContext auth_ctx;
auth_ctx.jwt_token = getJWTToken(); // Generate from SDK credentials
CreateAuthService(&auth_service);
auth_service->SDKAuth(auth_ctx);
// STEP 3: Join meeting (flexible - check SDK version for exact field names)
JoinParam join_param;
join_param.userType = SDK_UT_WITHOUT_LOGIN;
auto& params = join_param.param.withoutloginuserJoin;
params.meetingNumber = meeting_number;
params.userName = "Transcription Bot";
params.psw = meeting_password;
// Version-flexible: Check if these fields exist in your SDK version
// params.app_privilege_token = obf_token; // v5.15+
// params.onBehalfToken = obf_token; // Some versions
// params.userZAK = zak_token; // Alternative
meeting_service->Join(join_param);
// STEP 4: In onMeetingStatusChanged callback
if (status == MEETING_STATUS_INMEETING) {
// Get recording controller (pattern stable)
auto* record_ctrl = meeting_service->GetMeetingRecordingController();
// Start raw recording (enables raw data access)
if (record_ctrl->CanStartRawRecording() == SDKERR_SUCCESS) {
record_ctrl->StartRawRecording();
}
// Subscribe to audio
auto* audio_helper = GetAudioRawdataHelper();
audio_helper->subscribe(new TranscriptionAudioDelegate());
}
// STEP 5: Process audio
class TranscriptionAudioDelegate : public IZoomSDKAudioRawDataDelegate {
void onMixedAudioRawDataReceived(AudioRawData* data) override {
// Send PCM data to transcription service
sendToTranscriptionAPI(data->GetBuffer(), data->GetBufferLen());
}
};Flexible field access:
// Define a macro to check field existence at compile time
#ifdef HAS_APP_PRIVILEGE_TOKEN
params.app_privilege_token = token;
#elif defined(HAS_ON_BEHALF_TOKEN)
params.onBehalfToken = token;
#else
params.userZAK = token;
#endifRuntime capability detection:
SDKError canRecord = record_ctrl->CanStartRawRecording();
if (canRecord != SDKERR_SUCCESS) {
// Fallback: Try local recording
if (record_ctrl->CanStartRecording(true) == SDKERR_SUCCESS) {
record_ctrl->StartRecording(time, path);
}
}Goal: Record meetings with synchronized audio and video.
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Join Meeting │───►│ StartRawRecording│───►│ Subscribe │
│ │ │ │ │ Audio+Video │
└──────────────┘ └─────────────────┘ └──────────────┘
│
┌──────────────────────────┴─────────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Write YUV420 │ │ Write PCM │
│ video.yuv │ │ audio.pcm │
└──────────────┘ └──────────────┘
│ │
└──────────────────────────────────────────┬─┘
▼
┌──────────────┐
│ FFmpeg Merge │
│ output.mp4 │
└──────────────┘// After joining and starting raw recording...
// Video subscription
class VideoRecorderDelegate : public IZoomSDKRendererDelegate {
std::ofstream videoFile;
void onRawDataFrameReceived(YUVRawDataI420* data) override {
int width = data->GetStreamWidth();
int height = data->GetStreamHeight();
// YUV420 format: Y + U + V planes (contiguous)
videoFile.write(data->GetYBuffer(), width * height);
videoFile.write(data->GetUBuffer(), (width/2) * (height/2));
videoFile.write(data->GetVBuffer(), (width/2) * (height/2));
}
};
IZoomSDKRenderer* video_renderer;
createRenderer(&video_renderer, new VideoRecorderDelegate());
video_renderer->setRawDataResolution(ZoomSDKResolution_720P);
video_renderer->subscribe(user_id, RAW_DATA_TYPE_VIDEO);
// Audio subscription
class AudioRecorderDelegate : public IZoomSDKAudioRawDataDelegate {
std::ofstream audioFile;
void onMixedAudioRawDataReceived(AudioRawData* data) override {
audioFile.write((char*)data->GetBuffer(), data->GetBufferLen());
}
};
auto* audio_helper = GetAudioRawdataHelper();
audio_helper->subscribe(new AudioRecorderDelegate());
// Post-processing with FFmpeg
system("ffmpeg -video_size 1280x720 -pixel_format yuv420p -f rawvideo -i video.yuv "
"-f s16le -ar 32000 -ac 1 -i audio.pcm "
"-c:v libx264 -c:a aac -shortest output.mp4");Flexible resolution selection (adapt to SDK version):
// Try highest resolution available, fall back gracefully
ZoomSDKResolution resolutions[] = {
ZoomSDKResolution_1080P,
ZoomSDKResolution_720P,
ZoomSDKResolution_360P
};
for (auto res : resolutions) {
SDKError err = video_renderer->setRawDataResolution(res);
if (err == SDKERR_SUCCESS) {
std::cout << "Using resolution: " << res << std::endl;
break;
}
}Goal: Real-time meeting analysis with AI (sentiment, action items, summaries).
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────────┐
│ Audio Stream │───►│ Transcription │───►│ AI Analysis │───►│ Actions │
│ (Real-time) │ │ (AssemblyAI) │ │ (Anthropic) │ │ Identified │
└──────────────┘ └─────────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Speaker │ │ Sentiment │ │ Generate │
│ Diarization │ │ Analysis │ │ Summary │
└─────────────────┘ └─────────────────┘ └──────────────┘// Real-time streaming transcription
class AIAssistantAudioDelegate : public IZoomSDKAudioRawDataDelegate {
WebSocketClient transcription_ws; // AssemblyAI WebSocket
AIClient ai_client; // Anthropic/OpenAI
void onMixedAudioRawDataReceived(AudioRawData* data) override {
// Stream audio to real-time transcription
transcription_ws.send_audio(data->GetBuffer(), data->GetBufferLen());
}
};
// Process transcription results
void onTranscriptReceived(const std::string& text, const std::string& speaker) {
// Accumulate context
meeting_context += speaker + ": " + text + "\n";
// Every 30 seconds, analyze with AI
if (should_analyze()) {
std::string analysis = ai_client.analyze(meeting_context, {
"extract_action_items",
"detect_decisions",
"identify_blockers"
});
// Store results
meeting_insights.push_back(analysis);
}
}
// Post-meeting summary
void onMeetingEnded() {
std::string summary = ai_client.summarize(meeting_context, meeting_insights);
save_to_database(meeting_id, summary);
send_email_summary(participants, summary);
}Some SDK versions support in-meeting chat:
// Try to send AI insights to meeting chat
auto* chat_ctrl = meeting_service->GetMeetingChatController();
if (chat_ctrl) {
// Check if available
if (chat_ctrl->CanSendChat()) {
chat_ctrl->SendChatTo("AI detected action item: ...");
}
}Goal: Monitor meeting quality metrics and alert on issues.
// Get service quality info
auto* meeting_info = meeting_service->GetMeetingInfo();
// Polling loop (GLib timeout)
gboolean check_quality(gpointer data) {
// Audio stats
auto* audio_stats = meeting_info->GetAudioStatistics();
if (audio_stats) {
int jitter = audio_stats->jitter;
int packet_loss = audio_stats->packet_loss_percent;
if (packet_loss > 5) {
alert("High packet loss: " + std::to_string(packet_loss) + "%");
}
}
// Video stats
auto* video_stats = meeting_info->GetVideoStatistics();
if (video_stats) {
int fps = video_stats->fps;
int resolution_width = video_stats->width;
if (fps < 15) {
alert("Low FPS: " + std::to_string(fps));
}
}
return TRUE; // Continue polling
}
// Setup polling
g_timeout_add_seconds(10, check_quality, nullptr);-Werror=deprecated| Change Type | Example | Mitigation |
|---|---|---|
| Struct field rename | withoutloginuserJoin → withoutLoginUserJoin | Use #ifdef or update |
| Method signature change | subscribe(user_id) → subscribe(user_id, type) | Check return codes |
| Enum value rename | SDKERR_NORECORDINGINPROCESS → SDKERR_NO_RECORDING_IN_PROCESS | Map old → new |
| New required field | app_privilege_token becomes mandatory | Add to config |
// Version detection at compile time
#if ZOOM_SDK_VERSION >= 51500 // v5.15.0
#define USE_OBF_TOKEN
#endif
// Runtime capability testing
bool test_raw_recording() {
auto* ctrl = meeting_service->GetMeetingRecordingController();
return ctrl && ctrl->CanStartRawRecording() == SDKERR_SUCCESS;
}FROM ubuntu:22.04 AS builder
# Install dependencies (stable)
RUN apt-get update && apt-get install -y \
build-essential cmake \
libx11-xcb1 libxcb-xfixes0 libxcb-shape0 \
libglib2.0-dev libcurl4-openssl-dev
# Copy SDK (any version)
COPY zoom-meeting-sdk-linux_*.tar.gz /tmp/
RUN cd /tmp && tar xzf zoom-meeting-sdk-linux_*.tar.gz
# Build app
COPY . /app
WORKDIR /app
RUN cmake -B build && cd build && make
# Runtime stage
FROM ubuntu:22.04
COPY --from=builder /app/build/meetingBot /usr/local/bin/
COPY --from=builder /tmp/lib*.so /usr/local/lib/
# PulseAudio setup (required for raw audio)
RUN apt-get update && apt-get install -y pulseaudio && \
mkdir -p ~/.config && \
echo "[General]\nsystem.audio.type=default" > ~/.config/zoomus.conf
CMD ["meetingBot"]✅ Always check capabilities before calling methods
✅ Use heap memory mode for raw data
✅ Handle authentication fallbacks (OBF → ZAK → password)
✅ Detect SDK version at compile/runtime
✅ Test with actual SDK headers, not documentation examples
✅ Setup PulseAudio for Docker/headless environments
✅ Parse error codes to adapt behavior
✅ Keep authentication tokens fresh (regenerate before expiry)
✅ Log everything for debugging version-specific issues