项目文件夹

文件
2026-07-13 12:20:06 +08:00

9.7 KiB

Command Channel

Complete working code for custom command messaging between participants on Linux.

Official Sample: 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 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 for details.


Complete Working Code

CommandHandler.h

#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

#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

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():

// 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:

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:

// More reliable — broadcast to all
cmdChannel->sendCommand(nullptr, msg.c_str());

// Risky — user pointer may be stale
cmdChannel->sendCommand(userPtr, msg.c_str());