# 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 #include #include 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 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 // 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(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