File: C:/Users/fred/.codex/.tmp/plugins/plugins/zoom/skills/video-sdk/linux/examples/command-channel.md
# Command Channel
Complete working code for custom command messaging between participants on Linux.
**Official Sample**: [videosdk-linux-raw-recording-sample](https://github.com/zoom/videosdk-linux-raw-recording-sample)
---
## Overview
The command channel enables custom data exchange between participants within the same session. Use cases:
- Application-specific signaling
- Session transfer / waiting room coordination
- Real-time collaboration data
- Custom control messages
```
+-------------------------------------------------------------------+
| COMMAND CHANNEL FLOW (Linux) |
+-------------------------------------------------------------------+
| Sender: |
| getCmdChannel() -> sendCommand(nullptr, msg) [broadcast] |
| getCmdChannel() -> sendCommand(user, msg) [targeted] |
| |
| Receiver: |
| onCommandReceived(sender, command) callback |
| |
| IMPORTANT: Command channel is SESSION-SCOPED. |
| It does NOT span across different sessions. |
+-------------------------------------------------------------------+
```
**Key differences from Windows**: On Linux, strings are `const char*` (UTF-8), not `const wchar_t*` (wide strings). See [Windows Command Channel](../../windows/examples/command-channel.md) for comparison.
---
## Limitations
| Limit | Value |
|-------|-------|
| Max message rate | 60 messages/second |
| Max message size | ~1KB recommended |
| Reliability | Best effort (not guaranteed) |
| Scope | Same session only |
**Note**: Commands are not persisted - late joiners won't receive previous commands.
---
## Threading Requirement
ALL SDK calls — including `getCmdChannel()` and `sendCommand()` — must be made from the GLib main thread. Calling SDK methods from a `std::thread` or any background thread returns `ZoomVideoSDKErrors_Internal_Error` (error code 2).
Use `g_idle_add()` to schedule SDK calls from background threads. See [Common Issues](../troubleshooting/common-issues.md) for details.
---
## Complete Working Code
### CommandHandler.h
```cpp
#ifndef COMMAND_HANDLER_H
#define COMMAND_HANDLER_H
#include "zoom_video_sdk_api.h"
#include "zoom_video_sdk_interface.h"
#include <glib.h>
#include <string>
#include <functional>
USING_ZOOM_VIDEO_SDK_NAMESPACE
class CommandHandler {
public:
CommandHandler(IZoomVideoSDK* sdk);
// Send commands (MUST be called from GLib main thread)
bool SendToAll(const std::string& command);
bool SendToUser(IZoomVideoSDKUser* user, const std::string& command);
// Schedule send from a background thread (thread-safe)
void SendToAllFromBackground(const std::string& command);
// Connection status
bool IsConnected() const { return m_connected; }
// Callbacks from delegate
void OnCommandReceived(IZoomVideoSDKUser* sender, const char* command);
void OnConnectResult(bool success);
// Set message handler
using MessageCallback = std::function<void(IZoomVideoSDKUser*, const std::string&)>;
void SetMessageHandler(MessageCallback callback) { m_callback = callback; }
private:
IZoomVideoSDK* m_sdk;
IZoomVideoSDKCmdChannel* m_cmdChannel;
bool m_connected;
MessageCallback m_callback;
};
#endif // COMMAND_HANDLER_H
```
### CommandHandler.cpp
```cpp
#include "CommandHandler.h"
#include <cstdio>
// Context struct for g_idle_add() — used to schedule SDK calls from background threads
struct SendCmdContext {
IZoomVideoSDK* sdk;
std::string cmd;
};
// Runs on the GLib main thread — safe to call SDK methods here
static gboolean sendCommandOnMainThread(gpointer data) {
auto* ctx = static_cast<SendCmdContext*>(data);
IZoomVideoSDKCmdChannel* ch = ctx->sdk->getCmdChannel();
if (ch) {
ZoomVideoSDKErrors err = ch->sendCommand(nullptr, ctx->cmd.c_str());
if (err != ZoomVideoSDKErrors_Success) {
printf("[CMD] Send failed: %d\n", err);
}
}
delete ctx;
return G_SOURCE_REMOVE; // One-shot — do not repeat
}
CommandHandler::CommandHandler(IZoomVideoSDK* sdk)
: m_sdk(sdk)
, m_cmdChannel(nullptr)
, m_connected(false) {
}
bool CommandHandler::SendToAll(const std::string& command) {
if (!m_cmdChannel) {
m_cmdChannel = m_sdk->getCmdChannel();
}
if (!m_cmdChannel) {
printf("[CMD] Command channel not available\n");
return false;
}
ZoomVideoSDKErrors err = m_cmdChannel->sendCommand(nullptr, command.c_str());
if (err == ZoomVideoSDKErrors_Success) {
printf("[CMD] Sent to all: %s\n", command.c_str());
return true;
}
printf("[CMD] Send failed: %d\n", err);
return false;
}
bool CommandHandler::SendToUser(IZoomVideoSDKUser* user, const std::string& command) {
if (!user) return false;
if (!m_cmdChannel) {
m_cmdChannel = m_sdk->getCmdChannel();
}
if (!m_cmdChannel) {
return false;
}
ZoomVideoSDKErrors err = m_cmdChannel->sendCommand(user, command.c_str());
if (err == ZoomVideoSDKErrors_Success) {
printf("[CMD] Sent to %s: %s\n", user->getUserName(), command.c_str());
return true;
}
printf("[CMD] Send failed: %d\n", err);
return false;
}
void CommandHandler::SendToAllFromBackground(const std::string& command) {
// Thread-safe: g_idle_add queues work onto the GLib main loop
auto* ctx = new SendCmdContext{m_sdk, command};
g_idle_add(sendCommandOnMainThread, ctx);
}
void CommandHandler::OnCommandReceived(IZoomVideoSDKUser* sender, const char* command) {
if (!sender || !command) return;
std::string cmdStr(command);
printf("[CMD] From %s: %s\n", sender->getUserName(), cmdStr.c_str());
if (m_callback) {
m_callback(sender, cmdStr);
}
}
void CommandHandler::OnConnectResult(bool success) {
m_connected = success;
printf("[CMD] Command channel %s\n", success ? "connected" : "failed");
}
```
### Using in Delegate
```cpp
class BotDelegate : public IZoomVideoSDKDelegate {
private:
CommandHandler* m_cmdHandler;
public:
BotDelegate(IZoomVideoSDK* sdk) {
m_cmdHandler = new CommandHandler(sdk);
m_cmdHandler->SetMessageHandler([this](IZoomVideoSDKUser* sender,
const std::string& cmd) {
HandleCommand(sender, cmd);
});
}
void onCommandChannelConnectResult(bool isSuccess) override {
m_cmdHandler->OnConnectResult(isSuccess);
if (isSuccess) {
// Channel ready — safe to send commands now
m_cmdHandler->SendToAll("{\"type\":\"hello\"}");
}
}
void onCommandReceived(IZoomVideoSDKUser* sender, const zchar_t* strCmd) override {
m_cmdHandler->OnCommandReceived(sender, strCmd);
}
// ... other delegate methods ...
private:
void HandleCommand(IZoomVideoSDKUser* sender, const std::string& cmd) {
// Parse JSON commands
if (cmd.find("\"type\":\"ping\"") != std::string::npos) {
m_cmdHandler->SendToUser(sender, "{\"type\":\"pong\"}");
}
}
};
```
---
## Sending from a Background Thread
If you need to trigger a command from a polling thread, HTTP handler, or any non-main thread, use `SendToAllFromBackground()` which internally uses `g_idle_add()`:
```cpp
// From a background polling thread:
void pollingThread(CommandHandler* cmdHandler) {
while (running) {
std::string data = fetchDataFromServer();
if (!data.empty()) {
// Thread-safe — schedules on GLib main thread
cmdHandler->SendToAllFromBackground(data);
}
std::this_thread::sleep_for(std::chrono::seconds(3));
}
}
```
**Do NOT call `sendCommand()` directly from background threads** — it returns error code 2 (`Internal_Error`).
---
## Command Channel Lifecycle
1. Call `joinSession()` — the command channel connects automatically
2. `onCommandChannelConnectResult(true)` fires when ready
3. Send commands with `sendCommand(nullptr, msg)` (broadcast) or `sendCommand(user, msg)` (targeted)
4. Receive commands via `onCommandReceived(sender, command)` callback
5. Channel disconnects when you leave the session
**Session-scoped**: The command channel only works between participants in the same session. It does NOT span across different sessions.
---
## Common Issues
### Commands Not Received
**Cause**: Channel not connected yet
**Fix**: Wait for `onCommandChannelConnectResult(true)` before sending:
```cpp
void onCommandChannelConnectResult(bool isSuccess) override {
if (isSuccess) {
// NOW safe to send commands
}
}
```
### Error 2 (Internal_Error) on sendCommand
**Cause**: Calling SDK from a background thread
**Fix**: Use `g_idle_add()` to schedule on the GLib main thread (see SendToAllFromBackground above).
### Targeted Send Fails
**Cause**: User pointer may be stale if user disconnected
**Fix**: Use broadcast (`sendCommand(nullptr, msg)`) which is more reliable:
```cpp
// More reliable — broadcast to all
cmdChannel->sendCommand(nullptr, msg.c_str());
// Risky — user pointer may be stale
cmdChannel->sendCommand(userPtr, msg.c_str());
```
---
## Related Documentation
- [Session Join Pattern](session-join-pattern.md) - Session setup with GLib main loop
- [Common Issues](../troubleshooting/common-issues.md) - Threading and GLib requirements
- [Windows Command Channel](../../windows/examples/command-channel.md) - Windows equivalent (uses wchar_t)
- [Web Command Channel](../../web/examples/command-channel.md) - Web SDK equivalent
- [Authorization](../../references/authorization.md) - JWT roleType for host/co-host