产品简介

产品概述


好视通aPaaS SDK(application Platform as a Service 应用平台即服务SDK,简称应用SDK)是好视通将多年在远程教育、远程培训、远程招聘、应急指挥等行业积累的云服务能力提供给第三方开发者,帮助开发者快速的搭建全方位、低延迟、高质量的音视频解决方案。

架构介绍

好视通SDK方案可以与好视通行业方案结合满足各种场景需求,客户可以通过服务器API以及应用SDK对业务进行管理。同时该方案也可以与好视通行业方案的软件、硬件共同使用,客户可通过SDK与自身业务软件相结合,打造垂直领域的解决方案。

功能介绍

SDK主要提供以下功能

功能 功能说明
音视频 支持一对一、一对多的音视频交流,参会者支持同时打开多个摄像头
外设管理 允许参会者管理自己的音视频外设的参数配置
屏幕共享 支持Web端把桌面作为数据进行共享以及接收
文字聊天 允许参会者聊天或与其他人私聊
布局 允许切换布局来同时显示多路视频
参会者管理 获取当前会中参会人的信息、权限、音视频状态,以及对其进行管理
会控 收取会议中的事件并响应, 支持控制权限的申请
共享白板 支持创建和接收白板数据


核心功能

集成介绍


1.SDK集成

1.1如何集成SDK 到新工程

1.1.1 获取SDK

  1. 从https://apaas.hst.com/下载SDK和SdkDemo。
  1. 解压。

dll 目录有Debug和Release两种编译方式的动态库和依赖文件,集成时需要全部拷贝到自己程序的运行目录。
lib 目录有Debug和Release两种编译方式的链接库,集成时需要放在自己工程设置的库目录路径中。
include 目录有集成时需要包含的头文件,使用时都放在同一个目录,包含ICommonsDef.h一个文件即可。
1.1.2 新建VS2015 工程

  1. 新建工程并选择工程路径。

  1. 点击下一步。

  1. 点击完成。

  1. 创建成功。


1.1.3配置工程

  1. 配置工程编译输出路径。

右键项目属性,Release,Debug 都设置成相应的配置路径。

image.png
注:常规配置下输出目录与目标文件名、扩展名与链接器配置中输出文件中目录、输出文件名、扩展名需要一一对应。

  1. 将依赖拷贝到输出目录 。

将从官网下载SDK包解压目录下dll 目录中Debug/Release 中的所有文件 dll、exe 文件拷入到输出路径对应的Debug/ Release目录。
注:
(1)Debug 需要从官网下载 SdkDemo解压后的 Debug 目录中拷贝下列文件和目录到自己工程输出的debug目录:


然后将SDK 解压的Debug 目录下的dll和exe文件 全部拷贝到debug 目录。
(2)Release 需要从 SdkDemo的 Release 拷贝下列文件和目录:

注:skin目录为SdkDemo皮肤文件,集成SDK不需要demo的皮肤文件。

  1. 配置头文件目录。

(1) 右键工程属性,设置对应的头文件目录。

(2)将SDK include 目录拷贝到工程配置的头文件目录。

  1. 设置lib 包含目录。

(1)右键工程,属性,设置lib 库目录。

(2) 拷贝SDK 目录的lib 到对应设置的lib目录(debug 和release 拷贝对应的lib目录)。

  1. 代码中需要包含引用。

头文件包含以及lib引用示例代码:

#include "ICommonsDef.h"   //SDK 相关包含 

#ifdef _DEBUG
#pragma comment(lib,"winsdkd.lib")  //SDK 相关包含 
#else
#pragma comment(lib,"winsdk.lib")   //SDK 相关包含 
#endif

这样,基本集成完SDK相关文件,就可以在程序中调用SDK 接口,和使用SDK相关数据结构了。
1.1.4 常见问题以及解决方案

  1. 提示 Can not PreInit


(1)Debug检查config.data、 meetingMgrCfg.data、UIConfig.xml、Resource目录下文件以及目录中res.xml、FMUIFrameWork.dll等SDK 动态库。
(2)Release检查config.data、 meetingMgrCfg.data、uiconfig.data、Resource目录下文件以及目录中
es.data、FMUIFrameWork.dll等SDK 动态库。

  1. 当前版本SDK 仅支持x86编译, 不支持x64 编译。

1.2 SDK调用结构

1.2.1 SDK文档常用语说明
文档常用语表:

常用语 含义 备注
ISdkManager SDK主接口,通过initSdk()初始化,通过getXxxManager()获取各模块接口
ISdkInterface::setEventHandler 设置SDK基础事件处理 通过实现ISdkManagerEventHandler接口
IXxxManager::setEventHandler 各模块独立的事件处理 如ILoginManager对应ILoginEventHandler
Manager* 表示SDK相关接口管理类
EventHandler 各模块独立的事件处理虚函数接口
1.2.2 事件处理机制

// 各模块对应的EventHandler接口
// ILoginEventHandler    - 登录事件
// IAudioEventHandler    - 音频事件
// IVideoEventHandler    - 视频事件
// IMeetingEventHandler  - 会议事件
// IUserEventHandler     - 用户事件
// IChatEventHandler     - 聊天事件
// IScreenShareEventHandler - 屏幕共享事件
// IMediaShareEventHandler  - 多媒体共享事件
// IPermissionEventHandler  - 权限事件
// IWhiteBoardEventHandler  - 电子白板事件
// IContactsEventHandler    - 通讯录事件
// ISdkManagerEventHandler - SDK基础事件

1.2.3 各模块EventHandler对应关系:

模块 Manager接口 EventHandler接口
SDK基础 ISdkManager ISdkManagerEventHandler
登录 ILoginManager ILoginEventHandler
音频 IAudioManager IAudioEventHandler
视频 IVideoManager IVideoEventHandler
会议 IMeetingManager IMeetingEventHandler
用户 IUserManager IUserEventHandler
聊天 IChatManager IChatEventHandler
屏幕共享 IScreenShareManager IScreenShareEventHandler
多媒体共享 IMediaShareManager IMediaShareEventHandler
权限 IPermissionManager IPermissionEventHandler
电子白板 IWhiteBoardManager IWhiteBoardEventHandler
通讯录 IContactsManager IContactsEventHandler

2.SDK 初始化

2.1 获取SdkManager并初始化

 FS_SDK::ISdkManager*       m_SdkManager = nullptr;  
 FS_SDK::ILoginManager*     m_LoginManager = nullptr;  
 FS_SDK::IAudioManager*     m_AudioManager = nullptr;  
 FS_SDK::IVideoManager*     m_VideoManager = nullptr;  
 FS_SDK::IChatManager*      m_ChatManager = nullptr;  
 FS_SDK::IMeetingManager*   m_MeetingManager = nullptr;  
 FS_SDK::IUserManager*      m_UserManager = nullptr;  
 FS_SDK::IScreenShareManager*     m_ScreenShareManager = nullptr;  
 FS_SDK::IPermissionManager*      m_PermissionManager = nullptr;  
 FS_SDK::IWhiteBoardManager*   m_WBManager = nullptr;    
 FS_SDK::IContactsManager*    m_ContactsManager = nullptr;
 FS_SDK::IMediaShareManager*   m_MediaShareManager = nullptr;
      
 // 创建SdkContext并初始化SDK
 FS_SDK::SdkContext context;
 context.logPath = _T(".");
 FS_SDK::ErrCode hR = FS_SDK::ISdkManager::initSdk(&context);
 if (FS_SDK::ERR_SUCCESS != hR)  
 {  
     g_pSuperLog->WriteLogMsg("initSdk Failed, Error Code %d\n", hR);  
     return;
 }  

 m_SdkManager = FS_SDK::ISdkManager::getInstance();
 if (!m_SdkManager)  
 {  
     g_pSuperLog->WriteFile(_T("initSdk调用失败!\n"));  
     return;  
 }  
   
 // 获取各模块Manager接口
 m_LoginManager = m_SdkManager->getLoginManager();  
 m_AudioManager = m_SdkManager->getAudioManager();  
 m_VideoManager = m_SdkManager->getVideoManager();  
 m_ChatManager = m_SdkManager->getChatManager();  
 m_MeetingManager = m_SdkManager->getMeetingManager();  
 m_UserManager = m_SdkManager->getUserManager();  
 m_ScreenShareManager = m_SdkManager->getScreenShareManager();  
 m_PermissionManager = m_SdkManager->getPermissionManager();  
 m_WBManager = m_SdkManager->getWhiteBoardManager();
 m_ContactsManager = m_SdkManager->getContactsManager();
 m_MediaShareManager = m_SdkManager->getMediaShareManager();
   
 if (!m_LoginManager || !m_AudioManager || !m_VideoManager || !m_MeetingManager || !m_UserManager || !m_ScreenShareManager || !m_ChatManager || !m_PermissionManager || !m_WBManager || !m_ContactsManager || !m_MediaShareManager)  
 {  
     g_pSuperLog->WriteFile(L"SDK 组件管理初始化失败\n");  
     return;  
 }  

2.2 SDK设置事件处理器

// 实现各模块的EventHandler接口
class MyLoginEventHandler : public FS_SDK::ILoginEventHandler  
{  
public:  
    virtual void onAccountLoginCompleted(FS_SDK::ErrCode err) override;
    virtual void onAnonymousLoginCompleted(FS_SDK::ErrCode err) override;
    virtual void onEnterRoomCompleted(FS_SDK::ErrCode err) override;
    virtual void onAccountLogout(FS_SDK::ErrCode err) override;
    virtual void onExitedRoom() override;
    virtual void onPreStartMeeting() override;
    virtual void onInitCompleted() override;
};

// 同理实现其他模块的EventHandler
class MyAudioEventHandler : public FS_SDK::IAudioEventHandler {/* ... */};
class MyVideoEventHandler : public FS_SDK::IVideoEventHandler {/* ... */};
class MyMeetingEventHandler : public FS_SDK::IMeetingEventHandler {/* ... */};
class MyUserEventHandler : public FS_SDK::IUserEventHandler {/* ... */};
class MyChatEventHandler : public FS_SDK::IChatEventHandler {/* ... */};
class MyScreenShareEventHandler : public FS_SDK::IScreenShareEventHandler {/* ... */};
class MyPermissionEventHandler : public FS_SDK::IPermissionEventHandler {/* ... */};
class MyWBEventHandler : public FS_SDK::IWhiteBoardEventHandler {/* ... */};

// 设置各模块的EventHandler
MyLoginEventHandler        m_loginHandler;
MyAudioEventHandler        m_audioHandler;
MyVideoEventHandler        m_videoHandler;
MyMeetingEventHandler      m_meetingHandler;
MyUserEventHandler         m_userHandler;
MyChatEventHandler         m_chatHandler;
MyScreenShareEventHandler  m_screenShareHandler;
MyPermissionEventHandler   m_permissionHandler;
MyWBEventHandler           m_wbHandler;

m_LoginManager->setEventHandler(&m_loginHandler);
m_AudioManager->setEventHandler(&m_audioHandler);
m_VideoManager->setEventHandler(&m_videoHandler);
m_MeetingManager->setEventHandler(&m_meetingHandler);
m_UserManager->setEventHandler(&m_userHandler);
m_ChatManager->setEventHandler(&m_chatHandler);
m_ScreenShareManager->setEventHandler(&m_screenShareHandler);
m_PermissionManager->setEventHandler(&m_permissionHandler);
m_WBManager->setEventHandler(&m_wbHandler);

2.3 SDK设置服务器地址与ClientInfo

在onAccountLoginCompleted或onInitCompleted事件回调后设置服务器地址和ClientInfo。
示例代码:

// 设置服务器地址
FS_SDK::ErrCode hR = m_LoginManager->setServerAddr(_T("apaas.hst.com"), 443);
if (hR != FS_SDK::ERR_SUCCESS)
{
    g_pSuperLog->WriteFile(L"设置服务器地址失败!\n");
}

// 设置ClientID和Secret
hR = m_LoginManager->setClientInfo(_T("your_client_id"), _T("your_client_secret"));
if (hR != FS_SDK::ERR_SUCCESS)
{
    g_pSuperLog->WriteFile(L"设置ClientID或者Secret失败!\n");
}

登录入会


SDK初始化并设置服务器地址后,即可调用登录接口。

1.账号密码登录入会

示例代码:

// 账号密码登录: loginAccount(name, pwd, force)
// force=true 表示强制挤下前一个登录
FS_SDK::ErrCode err = m_LoginManager->loginAccount((S_PTCHAR)username, (S_PTCHAR)pwd, true);
if (err != FS_SDK::ERR_SUCCESS) {
    // 登录调用失败,登录结果通过 onAccountLoginCompleted 回调通知
}

2.会议室号/邀请码入会

示例代码:

// 会议室号/邀请码入会: loginRoom(nick, roomId, roomPwd)
FS_SDK::ErrCode err = m_LoginManager->loginRoom((S_PTCHAR)nickname, roomId, (S_PTCHAR)roomPwd);
if (err != FS_SDK::ERR_SUCCESS) {
    // 入会调用失败,结果通过 onEnterRoomCompleted 回调通知
}

3.登录结果通知

登录结果通过ILoginEventHandler的相应回调通知:

// 账号登录完成回调
virtual void MyLoginEventHandler::onAccountLoginCompleted(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"账号登录成功\n");
    }
    else
    {
        g_pSuperLog->WriteLogMsg("账号登录失败, err=%d\n", err);
    }
}

// 匿名登录(入会)完成回调
virtual void MyLoginEventHandler::onAnonymousLoginCompleted(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"匿名入会成功\n");
        // 入会成功后可以获取本地用户ID
        S_UINT32 localUserId = m_UserManager->getLocalUser();
    }
}

// 入会完成回调
virtual void MyLoginEventHandler::onEnterRoomCompleted(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"入会成功\n");
    }
}

// SDK初始化完成,可以广播音视频等操作
virtual void MyLoginEventHandler::onInitCompleted() override
{
    g_pSuperLog->WriteFile(L"SDK初始化完成,可以广播音视频\n");
}

// 预开始会议
virtual void MyLoginEventHandler::onPreStartMeeting() override
{
    // 准备好会中资源后,调用startMeeting()
    m_MeetingManager->startMeeting();
}

4.退出

1、调用IMeetingManager::exitMeeting退出会议
2、当收到onExitedRoom事件后需要调用ISdkManager::releaseSdk接口
3、调用ILoginManager::logout退出登录
4、未登录时关闭程序需要调用releaseSdk()接口

// 退出会议
m_MeetingManager->exitMeeting();
// 退出登录
m_LoginManager->logout();
// 释放SDK
m_SdkManager->releaseSdk();

广播音频


1.音频状态

音频状态通过 IAudioEventHandler::onUserAudioEvent 回调获取,其中 state 参数含义:0=未广播,1=正在申请,2=正在广播。

// IAudioEventHandler中获取音频状态
virtual void MyAudioEventHandler::onUserAudioEvent(S_UINT32 userId, S_BYTE state) override
{
    g_pSuperLog->WriteLogMsg("用户%d音频状态变更: state=%d\n", userId, state);
}

2.广播音频相关流程

广播自己音频流程图如下:
广播音频流程.png
广播他人音频:

处理他人音频申请:

主动发起广播代码示例如下:

// 广播用户音频
FS_SDK::ErrCode CSdkManager::BroadcastUserAudio(S_UINT32 userId)
{
    g_pSuperLog->WriteLogMsg("广播用户%d音频\n", userId);
    FS_SDK::ErrCode hr = m_AudioManager->broadcastAudio(userId, true);
    if (hr == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"广播用户音频成功!\n");
    }
    else if (hr == FS_SDK::ERR_REQUEST_PENDING)
    {
        g_pSuperLog->WriteFile(L"询问对方是否可以广播...等待对方回复\n");
    }
    else
    {
        g_pSuperLog->WriteLogMsg("广播用户音频失败! ERR : %d\n", hr);
    }
    return hr;
}

// 停止广播用户音频
FS_SDK::ErrCode CSdkManager::StopBroadcastUserAudio(S_UINT32 userId)
{
    g_pSuperLog->WriteLogMsg("停止广播用户%d音频\n", userId);
    FS_SDK::ErrCode hr = m_AudioManager->broadcastAudio(userId, false);
    if (hr == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"停止广播用户音频成功!\n");
    }
    else
    {
        g_pSuperLog->WriteLogMsg("停止广播用户音频失败! ERR : %d\n", hr);
    }
    return hr;
}

音频状态变化通过 IAudioEventHandler 回调通知:

// 用户音频广播状态变化
virtual void MyAudioEventHandler::onUserAudioEvent(S_UINT32 userId, S_BYTE state) override
{
    g_pSuperLog->WriteLogMsg("音频状态变更 用户:%d, state:%d\n", userId, state);
    // state: 0=未广播, 1=申请中, 2=广播中
    notify->OnAudioState(userId, state);
}

别人广播音频,会自动接收,无需上层处理接收逻辑。

3.同意他人的音频广播申请

当收到他人申请音频广播时,通过 IAudioEventHandler::onApplyAudioEvent 回调获取,然后调用 answerApplyAudioRight 同意或拒绝:

// 收到他人申请打开音频的回调
virtual void MyAudioEventHandler::onApplyAudioEvent(const FS_SDK::ApplyAVRightInfo* info) override
{
    if (info)
    {
        g_pSuperLog->WriteLogMsg("收到用户%d的音频广播申请\n", info->userId);
        // 同意或拒绝,设置info中的agree字段
        FS_SDK::ApplyAVRightInfo answerInfo = *info;
        answerInfo.agree = true;  // 或 false拒绝
        m_AudioManager->answerApplyAudioRight(&answerInfo);
    }
}

广播视频


1.视频操作接口

// IVideoManager接口

/* broadcastVideo - 广播/停止广播视频 */
virtual ErrCode broadcastVideo(S_UINT32 userId, S_BYTE channel, S_BOOL state);

/* applyBroadcastVideo - 申请/放弃广播视频 */
virtual ErrCode applyBroadcastVideo(S_BYTE channel, S_BOOL state);

/* answerApplyVideoRight - 响应远程打开本地视频 */
virtual ErrCode answerApplyVideoRight(const ApplyAVRightInfo* info);

示例代码:

// 广播用户视频
FS_SDK::ErrCode CSdkManager::BroadcastUserVideo(S_UINT32 userId, S_BYTE channel)
{
    g_pSuperLog->WriteLogMsg("广播用户%d视频, channel=%d\n", userId, channel);
    FS_SDK::ErrCode hr = m_VideoManager->broadcastVideo(userId, channel, true);
    if (hr == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"广播视频成功!\n");
    }
    else if (hr == FS_SDK::ERR_REQUEST_PENDING)
    {
        g_pSuperLog->WriteFile(L"询问对方是否可以广播...等待对方回复\n");
    }
    else
    {
        g_pSuperLog->WriteLogMsg("广播视频失败! ERR : %d\n", hr);
    }
    return hr;
}

// 视频状态变化回调 (IVideoEventHandler)
virtual void MyVideoEventHandler::onUserVideoEvent(S_UINT32 userId, S_BYTE channel, S_BYTE state) override
{
    g_pSuperLog->WriteLogMsg("视频状态变更 用户:%d, channel:%d, state:%d\n", userId, channel, state);
    notify->OnVideoState(userId, channel, state);
}

// 收到他人申请打开视频的回调
virtual void MyVideoEventHandler::onApplyVideoEvent(const FS_SDK::ApplyAVRightInfo* info) override
{
    if (info)
    {
        g_pSuperLog->WriteLogMsg("收到用户%d的视频广播申请\n", info->userId);
        FS_SDK::ApplyAVRightInfo answerInfo = *info;
        answerInfo.agree = true;  // 或 false拒绝
        m_VideoManager->answerApplyVideoRight(&answerInfo);
    }
}

2.广播视频流程

  1. 广播自己视频

广播自己视频流程:

  1. 广播他人视频

共享屏幕


1.开启共享屏幕

需要使用的接口(IScreenShareManager):

// IScreenShareManager接口
isScreenSharing();  //是否正在屏幕共享
startScreenShare(const ScreenShareInfo* info); //开始屏幕共享
stopScreenShare(S_UINT32 userId);  //停止屏幕共享
//接收远程屏幕,只需要自己加窗口句柄
startRemoteScreenShareView(S_UINT32 userId, S_BYTE audioChannel, HWND wnd);
//停止接收远程屏幕
stopRemoteScreenShareView(S_UINT32 userId, S_BYTE audioChannel);

流程图如下:
共享桌面.png
示例代码如下:

// 开启屏幕共享
FS_SDK::ErrCode CSdkManager::StartScreenShare()
{
    // 检查是否已有屏幕共享
    if (m_ScreenShareManager->isScreenSharing())
    {
        g_pSuperLog->WriteFile(L"已有屏幕共享正在进行\n");
        return FS_SDK::ERR_FAILED;
    }

    // 构造屏幕共享信息
    FS_SDK::ScreenShareInfo info;
    // 设置info的相应参数...

    FS_SDK::ErrCode hr = m_ScreenShareManager->startScreenShare(&info);
    if (FS_SDK::ERR_SUCCESS != hr)
    {
        if (FS_SDK::ERR_NO_PRIVILEGE == hr || FS_SDK::ERR_NO_ADMIN_PRIVILEGE == hr)
        {
            g_pSuperLog->WriteFile(L"没有屏幕共享权限,请申请管理员!\n");
        }
        else
        {
            g_pSuperLog->WriteLogMsg("开启屏幕共享失败! ERR : %d\n", hr);
        }
        return hr;
    }
    g_pSuperLog->WriteFile(L"开启屏幕共享成功!\n");
    return hr;
}

2.停止共享屏幕

流程图如下:

停止共享示例代码如下:

// 停止屏幕共享
FS_SDK::ErrCode CSdkManager::StopScreenShare(S_UINT32 userId)
{
    g_pSuperLog->WriteLogMsg("停止用户%d的屏幕共享\n", userId);
    FS_SDK::ErrCode hr = m_ScreenShareManager->stopScreenShare(userId);
    if (FS_SDK::ERR_SUCCESS != hr)
    {
        g_pSuperLog->WriteLogMsg("结束屏幕共享失败! ERR : %d\n", hr);
    }
    else
    {
        g_pSuperLog->WriteFile(L"结束屏幕共享成功!\n");
    }
    return hr;
}

3.接收共享屏幕

流程图如下:

当收到他人的屏幕共享通知时,通过 IScreenShareEventHandler::onScreenShareEvent 回调获取,然后调用 startRemoteScreenShareView 接收:

// IScreenShareEventHandler中接收屏幕共享通知
virtual void MyScreenShareEventHandler::onScreenShareEvent(S_UINT32 userId, S_BYTE state, S_BYTE channel) override
{
    g_pSuperLog->WriteLogMsg("屏幕共享通知:用户:%d, state:%d, channel:%d\n", userId, state, channel);
    if (state)  // state不为0表示开始共享
    {
        m_dwShareUserId = userId;
        HWND hWnd = m_pMeetingDataDlg->GetHWND();
        m_ScreenShareManager->startRemoteScreenShareView(userId, channel, hWnd);
    }
    else  // state为0表示停止共享
    {
        m_ScreenShareManager->stopRemoteScreenShareView(userId, channel);
    }
}

// 共享结果回调
virtual void MyScreenShareEventHandler::onShareResult(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"屏幕共享操作成功\n");
    }
    else
    {
        g_pSuperLog->WriteLogMsg("屏幕共享操作失败, err=%d\n", err);
    }
}

4.调整接收白板窗口大小

当接收屏幕共享的窗口大小改变时,需要主动调用该接口刷新屏幕共享流。

// 更新屏幕共享渲染窗口
FS_SDK::ErrCode hR = m_ScreenShareManager->updateScreenShareWnd(hWnd);

共享白板


1.开启共享白板

使用 IWhiteBoardManager 管理白板。入会后需先调用 initialize() 初始化白板模块,然后调用 createWB 创建白板。

流程图如下:
共享白板.png
创建白板示例代码:

// 1. 入会后初始化白板模块
FS_SDK::ErrCode hr = m_WhiteBoardManager->initialize();
if (hr != FS_SDK::ERR_SUCCESS)
{
    g_pSuperLog->WriteLogMsg("初始化白板模块失败! ERR : %d\n", hr);
    return hr;
}
g_pSuperLog->WriteFile(L"白板模块初始化成功\n");

// 2. 创建空白板
S_SIZE wbSize;
wbSize.cx = 1920;
wbSize.cy = 1080;
S_UINT32 wbId = m_WhiteBoardManager->createWB(0, wbSize, 0, nullptr);
if (wbId > 0)
{
    g_pSuperLog->WriteLogMsg("创建白板成功,wbId=%d\n", wbId);
}

// 3. 通过文档创建白板
// S_UINT32 wbId = m_WhiteBoardManager->createWBFromDocFile(0, guid, true);

2.停止共享白板

// 关闭指定白板
m_WhiteBoardManager->closeWB(wbId);

// 释放白板模块 (退出会议前)
m_WhiteBoardManager->uninitialize();

3.接收共享白板

白板事件通过 IWhiteBoardEventHandler 回调通知:

// 白板初始化完成
virtual void MyWBEventHandler::onWbInitialize(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS)
    {
        g_pSuperLog->WriteFile(L"白板初始化成功,可以创建白板\n");
    }
}

// 白板事件通知 (创建/关闭/翻页等)
virtual void MyWBEventHandler::onWbEvent(S_UINT32 wbId, FS_SDK::WhiteBoardEvent event, S_UINT userData) override
{
    g_pSuperLog->WriteLogMsg("白板事件 wbId=%d, event=%d\n", wbId, event);
    // 根据event类型处理业务逻辑
}

详情见SdkDemo、

4.激活共享白板

激活/取消激活白板:

// active为true表示激活,false表示取消激活
m_WhiteBoardManager->activeWB(wbId, true);

5.插件安装

插件FMPrinter, 从SDK中的FMPrinter文件夹中获取;
插件如下:
image.png
image.png
image.png

使用时,安装时运行 FMPrinter.exe 即可
eg:在 Inno Setup 打包脚本中[Run]节点下写入

[Files]
Source: "{#ComonBinDir}\ZX\FMPrinter\*"; DestDir: "{userappdata}\{#MyPrinterDirName}"; Flags: ignoreversion recursesubdirs createallsubdirs uninsneveruninstall;Check:CheckVersion
;{#ComonBinDir}\ZX\FMPrinter 打包的那个路径下的所有文件拷贝到 安装的路径中
[Run]
Filename: "{userappdata}\{#MyPrinterDirName}\FMPrinter.exe";Parameters: "-u";Flags: runhidden;Check:CheckVersion
;相当于cmd FMPrinter.exe -u 
Filename: "{userappdata}\{#MyPrinterDirName}\FMPrinter.exe";Flags: runhidden;Check:CheckVersion   
;{userappdata}\{#MyPrinterDirName}\FMPrinter.exe 表示安装的插件所在位置,相当于绝对路径

如果只是开发阶段,只需要在命令行中运行即可;eg:
企业微信截图_16423991752559.png
安装的时候也是只需要像在命令行执行安装FMPrinter插件即可

注:
1、如果没有安装此插件,无法进行文档转换;相当于共享文档功能不可用
2、要使用共享office等相关文档,需安装office或者WPS

布局说明


1.数据结构说明

//会议室窗口布局类  
struct RoomWndState  
{  
    //区域布局风格  
    enum AreaLayoutStyle  
    {  
        AREA_LAYOUT_STYLE_TILE = 0,//平铺风格类型  
        AREA_LAYOUT_STYLE_TABLE = 1,//tab切换风格类型  
        AREA_LAYOUT_STYLE_SPLIT = 2,//分屏风格类型  
         AREA_LAYOUT_STYLE_POP = 3   //弹出风格类型  
     };  
   
     //会议室窗口布局模式  
     enum LayoutMode  
     {  
         LAYOUT_MODE_NORMAL = 1, //默认模式  
         LAYOUT_MODE_DATA,       //数据模式  
         LAYOUT_MODE_VIDEO,      //视频固定模式(1~64分屏)  
         LAYOUT_MODE_DATAFULL,   //视频模式全屏  
     };  
   
     //分屏风格  
     enum SplitStyle  
     {  
         SPLIT_STYLE_AUTO = 0,   //默认自动  
         SPLIT_STYLE_1 = 1,      //一分屏  
         SPLIT_STYLE_2 = 2,  
         SPLIT_STYLE_P_IN_P = 3, //画中画  
         SPLIT_STYLE_4 = 4,  
         SPLIT_STYLE_6 = 6,  
         SPLIT_STYLE_9 = 9,  
         SPLIT_STYLE_12 = 12,  
         SPLIT_STYLE_16 = 16,  
         SPLIT_STYLE_25 = 25,  
         SPLIT_STYLE_36 = 36,  
         SPLIT_STYLE_49 = 49,  
         SPLIT_STYLE_64 = 64,  
         SPLIT_STYLE_NO = 50,        //无分屏  
         SPLIT_STYLE_1_FOCUS = 1001, //一焦点  
         SPLIT_STYLE_2_FOCUS = 1002, //2焦点  
     };  
   
     //数据类型  
     enum DataType  
     {  
         DATA_TYPE_CONTAINER = 0,    //默认,容器类型  
         DATA_TYPE_WB = 1,           //白板  
         DATA_TYPE_APPSHARE = 2,     //屏幕共享  
         DATA_TYPE_WEB = 3,          //Web协同浏览  
         DATA_TYPE_MEDIASHARE = 4,   //媒体共享  
         DATA_TYPE_VOTE = 5,         //电子投票  
         DATA_TYPE_VIDEO = 6,        //视频  
         DATA_TYPE_VIDEOPOLLING = 7  //U视频轮巡  
     };  
   
     //数据块信息  
     struct DataBlock  
     {  
         S_BYTE      pos = 0;        //索引顺序  
         DataType    data_type = DATA_TYPE_CONTAINER;    //类型  
         S_UINT32    data_id = 0;    //业务数据ID   
         S_UINT32    user_data = 0;  //用户自定义类型  
     };  
   
     typedef LVector<DataBlock> DataBlockVector;  
   
     //数据区域信息  
     struct AreaData  
     {  
         S_BYTE              id = 0;  
         S_BYTE              screen_id = 0;                  //所属屏ID  
         AreaLayoutStyle     style = AREA_LAYOUT_STYLE_TILE; //区域布局样式  
         S_UINT32            user_data = 0;                  //自定义字段  
         DataBlockVector     data_block_vector;              //数据块列表  
     };  
   
     struct FullArea  
     {  
         AreaLayoutStyle     style = AREA_LAYOUT_STYLE_TILE; //全屏区域风格  
         S_UINT32            user_data = 0;//自定义字段  
         typedef LVector<S_BYTE>   IDVector;  
         IDVector            id_set; //全屏区域显示的区域ID集合  
     };  
   
     S_BYTE      screen_id = 0;  //屏幕ID  
     LayoutMode  layout_mode = LAYOUT_MODE_NORMAL;   //布局模式  
     FullArea    full_area;  //全屏区域  
     AreaData    tab_area;   //tab风格区域  
     AreaData    split_area; //split风格区域  
     DataBlock   full_data_block;    //单个全屏的数据项  
 };  
   
 typedef LVector<RoomWndState> RoomWndStateVector;  
   
 // 会议布局信息
 struct RoomLayoutInfo  
 {  
     S_BOOL              valid_full_mode;//保持全屏模式  
     S_UINT32            user;           //同步者  
     RoomWndStateVector  wnd_state;      //窗口布局  
 };  

2.解析结构参考

通过 IMeetingEventHandler::OnLayoutEvent 回调接收布局同步信息,然后解析 RoomWndState:

// IMeetingEventHandler中接收布局事件
virtual void MyMeetingEventHandler::OnLayoutEvent(const FS_SDK::RoomLayoutInfo& layout) override
{
    g_pSuperLog->WriteLogMsg("收到布局同步 user=%d\n", layout.user);
    if (layout.wnd_state.size() > 0)
    {
        // 解析窗口布局
        ModifyVideoPos(layout.wnd_state[0]);
    }
}

// 解析视频显示顺序
void CMeetingLayoutDlg::ModifyVideoPos(const FS_SDK::RoomWndState& state)  
{  
    for (auto &videopos : state.split_area.data_block_vector)
    {  
        if (videopos.data_type != FS_SDK::RoomWndState::DATA_TYPE_VIDEO)  
            continue;  

        S_UINT32 dwUserID = videopos.data_id;  
        S_BYTE  bMediaID = videopos.user_data;  
        int nPos = videopos.pos;  
        g_pSuperLog->WriteLogMsg("视频: 用户id:%d, 通道:%d POS:%d\n", dwUserID, bMediaID, nPos);  
        m_VideoRelay.SetVideoID(nPos, dwUserID, bMediaID);
    }  
    RelayWindow(); 
}  

详情见SdkDemo CMeetingLayoutDlg类中相关处理。

3.广播布局

调用 IMeetingManager::broadcastLayout 广播布局:

void CMeetingLayoutDlg::BroadcastLayout()  
{  
    FS_SDK::RoomLayoutInfo layoutinfo;  
    FS_SDK::RoomWndState wndstate;  
  
    FS_SDK::RoomWndState::AreaData split_area;  
    (auto& video : m_lsVideo)  
    {  
        FS_SDK::RoomWndState::DataBlock videodata;  
        videodata.data_id = video->GetVideoUserID();  
        videodata.user_data = video->GetVideoMediaID();  
        videodata.data_type = FS_SDK::RoomWndState::DATA_TYPE_VIDEO;  
        split_area.data_block_vector.push_back(videodata);  
    }  
    split_area.id = DEFAULT_SPLIT_AREA_ID;  
    wndstate.split_area = split_area;  

    layoutinfo.valid_full_mode = true;  
    layoutinfo.user = m_UserManager->getLocalUser();  
    layoutinfo.wnd_state.push_back(wndstate);  

    // broadcastLayout广播布局
    FS_SDK::ErrCode hR = m_MeetingManager->broadcastLayout(layoutinfo);  
    if (FS_SDK::ERR_SUCCESS != hR)  
    {  
        g_pSuperLog->WriteFile(L"同步布局失败\n");  
    }  
}

详情见demo。

角色权限


1.权限范围

权限 管理员 主持人 参会人 备注
可多种权限同时具有 一场会议可存在多个 一场会议最多存在一个 一场会议可存在多个
请出会议室 可以 无权限 无权限
音视频控制 控制他人与自己 控制他人与自己 仅能控制自己 与后台设置相关
修改昵称/名称 修改他人与自己 仅修改自己 仅修改自己 与后台设置相关
申请主持人 直接成为主持人 需原主持人转让,无主持人自动成为主持人
授予主持人 可以 可以,准确来说是转让 无权限
同步布局 除分享外不能同步 默认同步 除分享外不能同步
录制 有权限 有权限 所有人录制时有权限
权限设置 仅PC能设置 仅PC能设置 无权限
管理员可成为主持人,此时具有双重身份,两个角色的权限

2.申请主持人

调用 IMeetingManager::applyToBeHost 申请主持人,角色改变后通过 IMeetingEventHandler::onPresenterStateEvent 回调通知。

// 申请主持人
m_MeetingManager->applyToBeHost(true);

// 主持人角色变化回调
virtual void MyMeetingEventHandler::onPresenterStateEvent(S_UINT32 userId, S_BYTE state) override
{
    g_pSuperLog->WriteLogMsg("主持人角色变化 userId=%d, state=%d\n", userId, state);
}

3.申请管理员

调用 IMeetingManager::applyToBeManager 申请管理员,管理员角色变化通过 IMeetingEventHandler::onManagerStateEvent / onManagerApplyEvent 回调通知。

// 申请管理员
m_MeetingManager->applyToBeManager(pwd);

// 管理员申请结果回调
virtual void MyMeetingEventHandler::onManagerApplyEvent(FS_SDK::ErrCode err) override
{
    g_pSuperLog->WriteLogMsg("管理员申请结果 err=%d\n", err);
}

// 管理员角色变化回调
virtual void MyMeetingEventHandler::onManagerStateEvent(S_UINT32 userId, S_BYTE state) override
{
    g_pSuperLog->WriteLogMsg("管理员角色变化 userId=%d, state=%d\n", userId, state);
}

录制

1.主要接口

// IMeetingManager录制相关接口

// 获取录制参数
virtual RecordParam getLocalRecordParam();

// 设置录制参数
virtual ErrCode setLocalRecordParam(const RecordParam& param);

// 开启录制 - path为录制路径,name为文件名(可为默认路径)
virtual ErrCode startLocalRecord(S_PTCHAR path, S_PTCHAR name);

// 暂停/继续录制
virtual ErrCode pauseLocalRecord(S_BOOL pause);

// 停止录制
virtual ErrCode stopLocalRecord();

2.录制参数结构:

    struct RecordParam
	{
		enum class RecordType : BYTE
		{
			RECORDTYPE_MP4 = 2,				//mp4
			RECORDTYPE_WMV = 3,				//wmv
			RECORDTYPE_WMA = 4				//wma
		};
		enum class RecordQuality : BYTE
		{
			RECORDQUALITY_LOW = 1,			//录制质量低
			RECORDQUALITY_MEDIUM = 2,		//录制质量中
			RECORDQUALITY_HIGH = 3			//录制质量高
		};
		enum class RecordRect : BYTE
		{
			RECORDRECT_FULL = 1,			//录制当前桌面窗口
			RECORDRECT_SELF = 2,			//录制程序窗口
			RECORDRECT_SPECIAL = 3			//录制指定区域
		};

		RecordType		record_type;		//保存录制文件格式类型
		RecordQuality	record_quality;		//质量
		RecordRect		record_rect;		//需要录制的方式
		HWND			capture_hwnd;		//录制的窗口句柄
		RECT			record_area;		//录制区域
	};

当 RecordRect 值为 RECORDRECT_SELF时传入 HWND capture_hwnd,程序会录制该句柄区域;
当 RecordRect 值为 RECORDRECT_FULL时,表示录制整个桌面(主屏幕)
当 RecordRect 值为 RECORDRECT_SPECIAL时, 传入 RECT record_area 值,录制该屏幕区域。
注:当 RecordType 为 RECORDTYPE_WMA 时,仅录制音频,并且是录制会中的音频,如果会中无人发言,则录制文件中是没有声音的。

3.录制插件

录制插件在SDK中,内容如下:
企业微信截图_16424002701642.png
安装时需要注册录制插件,可将录制插件ThirdComponent 目录与程序放在同一个文件夹;
开发时,不安装,只需要在插件目录运行ThridCtrlReg.exe即可
注: 未注册改插件,无法使用录制功能

Inno Setup 打包示例:

;解决录制的问题
Filename: {#MyCommonDirName}\ThirdComponent\ThridCtrlReg.exe; Flags: runhidden   

4.录制使用示例

    // 判断录制权限
    bool canRecord = m_PermissionManager->checkRoomPermission(FS_SDK::ROOM_LOCAL_RECORD);
    if (!canRecord)
    {
        g_pSuperLog->WriteFile(L"没有权限录制会议!\n");
        return;
    }

    tstring path;
    // 获取录制参数
    FS_SDK::RecordParam recordParam = m_MeetingManager->getLocalRecordParam();

    FS_SDK::ErrCode hR = FS_SDK::ERR_FAIL;
    switch (recordParam.record_rect)
    {
    case FS_SDK::RecordParam::RecordRect::RECORDRECT_SELF:
        recordParam.capture_hwnd = m_hNotifyWnd;
        m_MeetingManager->setLocalRecordParam(recordParam);
        hR = m_MeetingManager->startLocalRecord(path.c_str(), nullptr);
        break;
    case FS_SDK::RecordParam::RecordRect::RECORDRECT_FULL:
        hR = m_MeetingManager->startLocalRecord(path.c_str(), nullptr);
        break;
    case FS_SDK::RecordParam::RecordRect::RECORDRECT_SPECIAL:
        recordParam.record_area = { 0, 0, 800, 600 };
        m_MeetingManager->setLocalRecordParam(recordParam);
        hR = m_MeetingManager->startLocalRecord(path.c_str(), nullptr);
        break;
    default:
        recordParam.record_rect = FS_SDK::RecordParam::RecordRect::RECORDRECT_FULL;
        m_MeetingManager->setLocalRecordParam(recordParam);
        hR = m_MeetingManager->startLocalRecord(path.c_str(), nullptr);
        break;
    }

    if (FS_SDK::ERR_SUCCESS == hR)
    {
        UpdateRecordState(RECORDING);
    }
    else
    {
        UpdateRecordState(RECORDSTOP);
        g_pSuperLog->WriteLogMsg("录制会议失败! err=%d\n", hR);
    }

    // 暂停/继续录制
    m_MeetingManager->pauseLocalRecord(true);   // 暂停
    m_MeetingManager->pauseLocalRecord(false);  // 继续

    // 停止录制
    m_MeetingManager->stopLocalRecord();

详情见demo。

呼叫邀请

1.主要接口

通讯录相关功能通过 IContactsManager 管理:

// IContactsManager主要接口

// 异步查询企业通讯录信息
virtual ErrCode queryCompanyUsers(S_UINT32 size);

// 获取组织架构信息
virtual DeptNodes getDeptNodes();

// 获取所有联系人信息
virtual ContactInfoList getAllContact();

// 邀请用户入会
virtual ErrCode inviteUsers(const S_UINT32* users, S_UINT32 sz, MeetingInvitation* invitation);

// 同意/拒绝邀请
virtual ErrCode acceptMeetingInvite(S_UINT32 hostUserId, S_UINT32 inviteId, bool isAccept);

// 放弃邀请
virtual ErrCode abandonInvite(S_UINT32 inviteId, const S_UINT32* users, S_UINT32 sz);

2.返回数据结构

// 组织架构信息 (DeptNodes,树状结构)
// 通过 getDeptNodes() 获取

// 联系人信息 (ContactInfoList)
// 通过 getAllContact() 获取

// 邀请信息
struct MeetingInvitation
{
    S_UINT32    inviteId = 0;         // 邀请ID
    S_UINT32    inviterUserId = 0;   // 邀请者ID
    FString     inviterUserName;   // 邀请者名称
    S_UINT32    meetingId = 0;     // 会议ID
    // ... 其他字段
};

事件通过 IContactsEventHandler 回调通知:

// 通讯录查询完成
virtual void MyContactsEventHandler::onQueryCompanyUsersCompleted() override
{
    g_pSuperLog->WriteFile(L"通讯录查询完成\n");
    // 获取组织架构
    FS_SDK::DeptNodes depts = m_ContactsManager->getDeptNodes();
    // 获取所有联系人
    FS_SDK::ContactInfoList contacts = m_ContactsManager->getAllContact();
}

// 收到邀请
virtual void MyContactsEventHandler::onMeetingInviteIncome(const FS_SDK::MeetingInvitation& invitation) override
{
    g_pSuperLog->WriteLogMsg("收到邀请 inviteId=%d, inviter=%d\n", invitation.inviteId, invitation.inviterUserId);
    // 同意或拒绝
    m_ContactsManager->acceptMeetingInvite(invitation.inviterUserId, invitation.inviteId, true);
}

// 邀请被接受/拒绝
virtual void MyContactsEventHandler::onMessageInviteResponse(S_UINT32 inviteId, S_UINT32 userId, FS_SDK::ErrCode code) override
{
    g_pSuperLog->WriteLogMsg("邀请响应 inviteId=%d, userId=%d, code=%d\n", inviteId, userId, code);
}

// 邀请被取消
virtual void MyContactsEventHandler::onMeetingInviteAbandoned(S_UINT32 inviteId, FS_SDK::ErrCode reason) override
{
    g_pSuperLog->WriteLogMsg("邀请被取消 inviteId=%d, reason=%d\n", inviteId, reason);
}

3.通讯录

成功登录会前在线后SDK立即请求组织架构、企业用户列表数据并保存在SDK层,应用层可调用 queryCompanyUsers 异步查询,查询完成后通过 IContactsEventHandler::onQueryCompanyUsersCompleted 回调通知,然后调用 getDeptNodesgetAllContact 获取数据。

// 异步查询通讯录
m_ContactsManager->queryCompanyUsers(0);
// 查询完成后在 onQueryCompanyUsersCompleted 回调中获取数据

4.呼叫邀请

会前和会中均可调用 inviteUsers 对通讯录在线用户发起邀请,会前发起邀请后SDK将首先创建即时会议并立即入会,然后将选择的用户邀请到该会议室。会中则直接将选择的用户邀请到当前会议室。

// 邀请用户
S_UINT32 userIds[] = { 1001, 1002, 1003 };
FS_SDK::MeetingInvitation invitation;
m_ContactsManager->inviteUsers(userIds, 3, &invitation);

邀请被接受/拒绝则由SDK层响应后通过 IContactsEventHandler::onMessageInviteResponse 回调通知应用层。

邀请时序:

详情见demo。


方法使用示例

1. ISdkManager — SDK 主接口

initSdk

void InitAndGetManagers()
{
    // 1. 创建 SDK 实例
    FS_SDK::ISdkManager* sdk = FS_SDK::ISdkManager::getInstance();

    // 2. 初始化 SDK
    FS_SDK::SdkContext ctx;
    ctx.appId  = _T("your_app_id");
    ctx.appVer = _T("1.0.0");
    ctx.rootPath = _T("C:\\sdk_data");
    FS_SDK::ErrCode ec = sdk->initSdk(&ctx);
    if (ec != FS_SDK::ERR_SUCCESS) {
        // 初始化失败处理
        return;
    }

    // 3. 设置全局事件处理
    class MyGlobalHandler : public FS_SDK::ISdkManagerEventHandler {
        void onConnectServerFailed() override { }
        void onNetReconnect() override { }
        void onNetReconnectSucess() override { }
        void onNetworkAborted() override { }
    };
    // ISdkInterface::setEventHandler
    // 需通过 ISdkInterface 指针设置

    // 4. 获取各模块管理器
    FS_SDK::ILoginManager*        loginMgr = sdk->getLoginManager();
    FS_SDK::IAudioManager*        audioMgr = sdk->getAudioManager();
    FS_SDK::IVideoManager*        videoMgr = sdk->getVideoManager();
    FS_SDK::IMeetingManager*      meetMgr  = sdk->getMeetingManager();
    FS_SDK::IUserManager*         userMgr  = sdk->getUserManager();
    FS_SDK::IChatManager*         chatMgr  = sdk->getChatManager();
    FS_SDK::IContactsManager*     contMgr  = sdk->getContactsManager();
    FS_SDK::IScreenShareManager*  ssMgr    = sdk->getScreenShareManager();
    FS_SDK::IMediaShareManager*   msMgr    = sdk->getMediaShareManager();
    FS_SDK::IPermissionManager*   permMgr  = sdk->getPermissionManager();
    FS_SDK::IWhiteBoardManager*   wbMgr    = sdk->getWhiteBoardManager();
}

setParam

sdk->setParam(_T("log_level"), _T("3"));
sdk->setParam(_T("enable_encrypt"), _T("1"));

uploadLog

S_UINT32 taskId = sdk->uploadLog(_T("C:\\logs\\sdk_log.zip"), FS_SDK::UploadLogSource::MANUAL);
// 结果通过 onUploadLog 回调通知

releaseSdk

void Cleanup()
{
    FS_SDK::ISdkManager* sdk = FS_SDK::ISdkManager::getInstance();
    sdk->releaseSdk();
}

setEventHandler (ISdkInterface)

class MyEventHandler : public FS_SDK::IEventHandler {
    void onConnectServerFailed() override {
        // 连接服务器失败,提示用户重试
    }
    void onNetReconnect() override {
        // 网络断开,正在重连
    }
    void onNetReconnectSucess() override {
        // 重连成功
    }
    void onNetworkAborted() override {
        // 网络无法恢复,需要重新初始化
    }
    void onUploadLog(S_UINT32 taskId, S_INT32 code, S_PCHAR msg) override {
        // 日志上传结果
    }
};

MyEventHandler g_handler;

void SetupEventHandler(FS_SDK::ISdkInterface* sdkIf)
{
    sdkIf->setEventHandler(&g_handler);
}

2. ILoginManager — 登录模块

setServerAddr

FS_SDK::ErrCode ec = m_LoginManager->setServerAddr(
    _T("apaas.hst.com"),
    443
);

setServerAddrEx

// 设置多个服务器地址,用分号分隔
FS_SDK::ErrCode ec = m_LoginManager->setServerAddrEx(
    _T("apaas1.hst.com:443;apaas2.hst.com:8443")
);

setClientInfo

FS_SDK::ErrCode ec = m_LoginManager->setClientInfo(
    _T("your_client_id"),
    _T("your_client_secret")
);

loginAccount

FS_SDK::ErrCode ec = m_LoginManager->loginAccount(
    (S_PTCHAR)_T("zhangsan"),
    (S_PTCHAR)_T("password123"),
    true  // 强制挤下前一个登录
);
if (ec != FS_SDK::ERR_SUCCESS) {
    // 调用失败,结果通过 onAccountLoginCompleted 回调通知
}

loginAccountWithToken

// 使用第三方鉴权 token 登录(需 CES 后台对接过)
FS_SDK::ErrCode ec = m_LoginManager->loginAccountWithToken(
    (S_PTCHAR)_T("user_id_from_3rd"),
    (S_PTCHAR)_T("token_from_3rd_auth"),
    true
);

loginRoom

// 匿名入会,直接通过会议号登录
FS_SDK::ErrCode ec = m_LoginManager->loginRoom(
    (S_PTCHAR)_T("nickname"),
    123456,
    (S_PTCHAR)_T("room123")
);

enterRoom

// 账号登录后,通过会议号入会
FS_SDK::ErrCode ec = m_LoginManager->enterRoom(
    true,        // mine: 是否我的会议室
    123456,      // roomId: 会议号
    (S_PTCHAR)_T("pwd123")  // roomPwd: 会议密码
);

cancelEnterRoom

// 取消正在进行的入会操作
m_LoginManager->cancelEnterRoom();

logout

// 退出登录
m_LoginManager->logout();

3. ILoginEventHandler — 登录事件回调

onAccountLoginCompleted

void MyLoginEventHandler::onAccountLoginCompleted(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS) {
        // 账号登录成功,可以调用 enterRoom 入会
        g_pLoginMgr->enterRoom(false, 123456, _T(""));
    } else {
        // 登录失败处理
    }
}

onAnonymousLoginCompleted

void MyLoginEventHandler::onAnonymousLoginCompleted(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS) {
        // 匿名登录(入会)成功
    }
}

onEnterRoomCompleted

void MyLoginEventHandler::onEnterRoomCompleted(FS_SDK::ErrCode err) override
{
    if (err == FS_SDK::ERR_SUCCESS) {
        // 入会成功,可以开始会议操作
        // 初始化白板等
    }
}

onInitCompleted

void MyLoginEventHandler::onInitCompleted() override
{
    // 会中资源初始化完成,可以广播音视频等操作
}

onPreStartMeeting

void MyLoginEventHandler::onPreStartMeeting() override
{
    // 准备好会中资源后,调用 startMeeting() 开始会议
    g_pMeetingMgr->startMeeting();
}

onExitedRoom

void MyLoginEventHandler::onExitedRoom() override
{
    // 已退会,需调用 releaseSdk
    // 然后调用 logout
    g_pLoginMgr->logout();
    FS_SDK::ISdkManager::getInstance()->releaseSdk();
}

onUpdateServerCompleted

void MyLoginEventHandler::onUpdateServerCompleted(FS_SDK::ErrCode err) override
{
    // 更新服务器地址完成
}

onAccountLogout

void MyLoginEventHandler::onAccountLogout(FS_SDK::ErrCode err) override
{
    // 账号已登出
}

onAnonymousLoginPaasCompleted

void MyLoginEventHandler::onAnonymousLoginPaasCompleted(FS_SDK::ErrCode err) override
{
    // 匿名登录 PaaS 完成
}

onWaitingForAllowToEnter

void MyLoginEventHandler::onWaitingForAllowToEnter(FS_SDK::ErrCode err) override
{
    // 等待主持人同意进入会议
}

4. IAudioManager — 音频模块

getSpeakerDeviceList / getMicrophoneDeviceList

// 获取扬声器列表
FS_SDK::AudioDeviceList speakers = m_AudioManager->getSpeakerDeviceList();
for (auto& dev : speakers) {
    _tprintf(_T("Speaker[%d]: %s\n"), dev.index, dev.name);
}

// 获取麦克风列表
FS_SDK::AudioDeviceList mics = m_AudioManager->getMicrophoneDeviceList();

getSpeakerDevice / getMicrophoneDevice

S_INT32 curSpeaker = m_AudioManager->getSpeakerDevice();
S_INT32 curMic     = m_AudioManager->getMicrophoneDevice();

setSpeakerDevice / setMicrophoneDevice

m_AudioManager->setSpeakerDevice(0);    // 设置第一个扬声器
m_AudioManager->setMicrophoneDevice(1);  // 设置第二个麦克风

setSpeakerDeviceEnable / setMicrophoneEnable

m_AudioManager->setSpeakerDeviceEnable(true);    // 启用扬声器
m_AudioManager->setMicrophoneEnable(true);        // 启用麦克风

getSpeakerVolume / setSpeakerVolume

S_INT32 vol, maxVol, minVol;
m_AudioManager->getSpeakerVolume(&vol, &maxVol, &minVol);
_tprintf(_T("Current speaker vol: %d/%d\n"), vol, maxVol);

m_AudioManager->setSpeakerVolume(80);  // 设置为 80

getMicrophoneVolume / setMicrophoneVolume

S_INT32 micVol, micMax, micMin;
m_AudioManager->getMicrophoneVolume(&micVol, &micMax, &micMin);
m_AudioManager->setMicrophoneVolume(70);

getAudioParam / setAudioParam

// 获取音频参数
S_INT32 param;
m_AudioManager->getAudioParam(FS_SDK::AudioParamKey::AUDIO_PARAM_BITRATE, param);

// 设置音频参数
m_AudioManager->setAudioParam(FS_SDK::AudioParamKey::AUDIO_PARAM_BITRATE, 64000);

applyBroadcastAudio

// 广播本端音频(自己申请广播)
FS_SDK::ErrCode ec = m_AudioManager->applyBroadcastAudio(true);   // 申请广播
// ec = m_AudioManager->applyBroadcastAudio(false);  // 取消广播

broadcastAudio

// 广播远端用户音频(主持人操作)
FS_SDK::ErrCode ec = m_AudioManager->broadcastAudio(userId, true);
if (ec == FS_SDK::ERR_REQUEST_PENDING) {
    // 需要等待对方应答
} else if (ec == FS_SDK::ERR_SUCCESS) {
    // 成功
}

answerApplyAudioRight

// 在 onApplyAudioEvent 中回应
void MyAudioEventHandler::onApplyAudioEvent(const FS_SDK::ApplyAVRightInfo* info) override
{
    FS_SDK::ApplyAVRightInfo answer = *info;
    answer.agree = true;  // 同意对方广播本端音频
    g_pAudioMgr->answerApplyAudioRight(&answer);
}

startRemoteAudio / stopRemoteAudio

// 开始接收某用户音频(非自动接收模式)
m_AudioManager->startRemoteAudio(userId);
// 停止接收
m_AudioManager->stopRemoteAudio(userId);

getUserEnergy

S_INT32 energy;
m_AudioManager->getUserEnergy(userId, &energy);
_tprintf(_T("User %d audio energy: %d\n"), userId, energy);

setLocalAudioDataObserver

// 监听本地采集的原始 PCM 数据
m_AudioManager->setLocalAudioDataObserver(true);
// 数据在 IAudioEventHandler::onLocalAudioCapData 中获取

getCapEnergy / getPlayEnergy

S_INT32 capE, capMax, capMin;
m_AudioManager->getCapEnergy(&capE, &capMax, &capMin);

S_INT32 playE, playMax, playMin;
m_AudioManager->getPlayEnergy(&playE, &playMax, &playMin);

5. IAudioEventHandler — 音频事件回调

onCaptureDeviceChanged / onPlayDeviceChanged

void MyAudioEventHandler::onCaptureDeviceChanged() override {
    // 采集设备变化,刷新设备列表
}
void MyAudioEventHandler::onPlayDeviceChanged() override {
    // 播放设备变化
}

onUserAudioEvent

void MyAudioEventHandler::onUserAudioEvent(S_UINT32 userId, S_BYTE state) override
{
    // state: 0=未广播, 1=申请中, 2=广播中
    switch (state) {
        case 0: _tprintf(_T("User %d stopped broadcasting\n"), userId); break;
        case 1: _tprintf(_T("User %d requesting audio\n"), userId); break;
        case 2: _tprintf(_T("User %d is broadcasting audio\n"), userId); break;
    }
}

onApplyAudioEvent

void MyAudioEventHandler::onApplyAudioEvent(const FS_SDK::ApplyAVRightInfo* info) override
{
    // 对方申请打开本端音频
    FS_SDK::ApplyAVRightInfo answer = *info;
    // UI 弹窗询问用户,然后设置 agree
    answer.agree = true;
    g_pAudioMgr->answerApplyAudioRight(&answer);
}

onLocalAudioCapData

void MyAudioEventHandler::onLocalAudioCapData(S_PBYTE data, S_UINT32 dataLen) override
{
    // 本地采集的 PCM 音频数据,可以写入文件或做处理
    // 需先调用 setLocalAudioDataObserver(true)
}

6. IVideoManager — 视频模块

getVideoDeviceList

FS_SDK::VideoDeviceList devices = m_VideoManager->getVideoDeviceList();
for (auto& dev : devices) {
    _tprintf(_T("Camera[%d]: %s\n"), dev.channel, dev.name);
}

setDefaultVideoDevice / getDefaultVideoDevice / isDefaultVideoDevice

// 设置默认视频设备
m_VideoManager->setDefaultVideoDevice(0);

// 获取默认视频设备
S_BYTE ch = m_VideoManager->getDefaultVideoDevice();

// 判断是否为默认设备
FS_SDK::ErrCode ec = m_VideoManager->isDefaultVideoDevice(ch);
if (ec == FS_SDK::ERR_SUCCESS) {
    // 是默认设备
}

getVideoParam / setVideoParam

// 获取视频参数
S_INT32 fps;
m_VideoManager->getVideoParam(0, FS_SDK::VideoParamKey::VIDEO_PARAM_FPS, fps);

// 设置视频参数
m_VideoManager->setVideoParam(0, FS_SDK::VideoParamKey::VIDEO_PARAM_FPS, 30);
m_VideoManager->setVideoParam(0, FS_SDK::VideoParamKey::VIDEO_PARAM_BITRATE, 1500000);
m_VideoManager->setVideoParam(0, FS_SDK::VideoParamKey::VIDEO_PARAM_RESOLUTION, 2); // 720p

startLocalVideoPreview / stopLocalVideoPreview

// 开始本端视频预览
HWND hPreviewWnd = GetDlgItem(IDC_LOCAL_VIDEO);
m_VideoManager->startLocalVideoPreview(0, hPreviewWnd);

// 停止预览
m_VideoManager->stopLocalVideoPreview(0);

startCheckVideoView / stopCheckVideoView

// 接收远端用户视频
HWND hRemoteWnd = GetDlgItem(IDC_REMOTE_VIDEO);
m_VideoManager->startCheckVideoView(userId, 0, hRemoteWnd);

// 停止接收
m_VideoManager->stopCheckVideoView(userId, 0);

pauseVideo

// 暂停/恢复接收远端视频
m_VideoManager->pauseVideo(userId, 0, true);   // 暂停
m_VideoManager->pauseVideo(userId, 0, false);  // 恢复

setDisplayMode

// 设置视频显示模式
m_VideoManager->setDisplayMode(userId, 0, FS_SDK::DisplayMode::DISPLAY_MODE_FIT);   // 适应
// m_VideoManager->setDisplayMode(userId, 0, FS_SDK::DisplayMode::DISPLAY_MODE_FILL); // 填充

cameraToggle

// 手机端切换摄像头
m_VideoManager->cameraToggle();

applyBroadcastVideo

// 广播本端视频
m_VideoManager->applyBroadcastVideo(0, true);    // 申请广播
m_VideoManager->applyBroadcastVideo(0, false);   // 停止广播

broadcastVideo

// 广播远端用户视频(主持人操作)
m_VideoManager->broadcastVideo(userId, 0, true);

answerApplyVideoRight

void MyVideoEventHandler::onApplyVideoEvent(const FS_SDK::ApplyAVRightInfo* info) override
{
    FS_SDK::ApplyAVRightInfo answer = *info;
    answer.agree = true;
    g_pVideoMgr->answerApplyVideoRight(&answer);
}

saveRawPicture

// 视频截图
m_VideoManager->saveRawPicture(
    userId,
    0,
    _T("C:\\screenshots"),
    _T("user_1234.jpg")
);

setVideoFrameDataObserver

// 监听视频数据
m_VideoManager->setVideoFrameDataObserver(userId, 0, true);
// 数据在 onUserVideoData 回调中获取

setVideoFrameRawDataObserver

// 监听解码后原始视频帧(YUV)
m_VideoManager->setVideoFrameRawDataObserver(userId, 0, true);
// 数据在 onUserVideoRawData 回调中获取

setLocalVideoFrameRawDataObserver

// 监听本地摄像头采集原始帧
m_VideoManager->setLocalVideoFrameRawDataObserver(0, true);
// 数据在 onLocalVideoRawData 回调中获取

addRtspDevice / removeRtspDevice

// 添加 RTSP 设备
S_INT32 devId = m_VideoManager->addRtspDevice(
    _T("Camera1"),
    _T("rtsp://admin:12345@192.168.1.100:554/stream")
);

// 移除 RTSP 设备
m_VideoManager->removeRtspDevice(devId);

startLocalCameraRecord / stopLocalCameraRecord / isLocalCameraRecording

// 开始本地摄像头录制
m_VideoManager->startLocalCameraRecord(
    _T("C:\\recordings"),
    0,
    true,      // is_record_audio
    60          // spit_time (分钟分割)
);

// 停止录制
m_VideoManager->stopLocalCameraRecord(0);

// 查询录制状态
S_BOOL isRecording = m_VideoManager->isLocalCameraRecording(0);

setRenderView

// 设置新的渲染窗口
m_VideoManager->setRenderView(userId, 0, hNewWnd);

7. IVideoEventHandler — 视频事件回调

onVideoDeviceChanged

void MyVideoEventHandler::onVideoDeviceChanged() override {
    // 视频设备变化,刷新设备列表
}

onUserVideoEvent

void MyVideoEventHandler::onUserVideoEvent(S_UINT32 userId, S_BYTE channel, S_BYTE state) override
{
    // state: 0=停止, 1=申请中, 2=广播中
    _tprintf(_T("User %d video state: %d\n"), userId, state);
}

onApplyVideoEvent

void MyVideoEventHandler::onApplyVideoEvent(const FS_SDK::ApplyAVRightInfo* info) override
{
    // 对方申请打开本端视频
}

onUserVideoData / onUserVideoRawData / onLocalVideoRawData

void MyVideoEventHandler::onUserVideoData(S_UINT32 userId, S_BYTE ch, S_PBYTE data, S_UINT32 len) override  { }
void MyVideoEventHandler::onUserVideoRawData(S_UINT32 userId, S_BYTE ch, S_PBYTE data, S_UINT32 len, S_UINT32 fourcc, S_SIZE wh, S_INT32 rot) override { }
void MyVideoEventHandler::onLocalVideoRawData(S_BYTE ch, S_PBYTE data, S_UINT32 len, S_UINT32 fourcc, S_SIZE wh, S_INT32 rot) override { }

onAddRtspResult

void MyVideoEventHandler::onAddRtspResult(FS_SDK::ErrCode err, S_UINT32 devId) override
{
    if (err == FS_SDK::ERR_SUCCESS) {
        _tprintf(_T("RTSP device added, id=%u\n"), devId);
    }
}

onLocalDeviceRecordFail

void MyVideoEventHandler::onLocalDeviceRecordFail(S_UINT32 devId) override
{
    _tprintf(_T("Local camera record failed, dev=%u\n"), devId);
}

8. IMeetingManager — 会议模块

queryRoomList

m_MeetingManager->queryRoomList(1, 20, _T(""));
// 结果在 IMeetingEventHandler::onQueryRoomList 中获取

queryRoomListOfSchedule

m_MeetingManager->queryRoomListOfSchedule(1, 20, _T(""), 7, 0);

queryRoomListOfInstant

m_MeetingManager->queryRoomListOfInstant();

createInstantMeeting

S_INT32 taskId = m_MeetingManager->createInstantMeeting(0);
// 结果在 onCreateInstantMeeting 中获取

queryRoomShareInfo

m_MeetingManager->queryRoomShareInfo(123456);

queryJoinMeetingRecords / insertJoinMeetingRecord / removeJoinMeetingRecord / clearJoinMeetingRecords

// 查询入会记录
m_MeetingManager->queryJoinMeetingRecords(50);

// 插入入会记录
m_MeetingManager->insertJoinMeetingRecord(_T("room_id:123456"));

// 移除指定记录
m_MeetingManager->removeJoinMeetingRecord(1);

// 清空所有记录
m_MeetingManager->clearJoinMeetingRecords();

getMeetingInfo

FS_SDK::MeetingInfo info = m_MeetingManager->getMeetingInfo();
_tprintf(_T("Room: %s, ID: %u\n"), info.roomName, info.roomId);

queryClientSupportAbility

m_MeetingManager->queryClientSupportAbility(userId);

applyToBeManager / abandonTheManager

// 申请成为管理员
m_MeetingManager->applyToBeManager(_T("admin_pwd"));

// 放弃管理员身份
m_MeetingManager->abandonTheManager();

grantTheManagerForRemoteUser

// 授予远端用户管理员权限
m_MeetingManager->grantTheManagerForRemoteUser(userId, true);

applyToBeHost / abandonTheHost

// 申请成为主持人
m_MeetingManager->applyToBeHost(true);

// 放弃主持人身份
m_MeetingManager->abandonTheHost();

agreeToBeHost

// 同意远端成为主持人
m_MeetingManager->agreeToBeHost(userId, true);

grantTheHostForRemoteUser

// 授予远端用户主持人权限
m_MeetingManager->grantTheHostForRemoteUser(userId, true);

startLocalRecord

m_MeetingManager->startLocalRecord(
    _T("C:\\recordings"),
    _T("meeting_20240725")
);

pauseLocalRecord

m_MeetingManager->pauseLocalRecord(true);   // 暂停
m_MeetingManager->pauseLocalRecord(false);  // 恢复

stopLocalRecord

m_MeetingManager->stopLocalRecord();

setLocalRecordParam

FS_SDK::RecordParam param;
param.format = FS_SDK::RecordParam::MP4;
param.fps    = 15;
param.includeAudio = true;
m_MeetingManager->setLocalRecordParam(param);

getLocalRecordParam

FS_SDK::RecordParam param = m_MeetingManager->getLocalRecordParam();

broadcastLayout

FS_SDK::RoomLayoutInfo layout;
layout.mode = FS_SDK::LayoutMode::GALLERY;
m_MeetingManager->broadcastLayout(layout);

kickUser

m_MeetingManager->kickUser(userId);  // 请出用户

quietMeeting

m_MeetingManager->quietMeeting(true);  // 静音会场,允许再次广播

enableWaterMark

m_MeetingManager->enableWaterMark(true);   // 开启水印
m_MeetingManager->enableWaterMark(false);  // 关闭水印

startCloudRecord

m_MeetingManager->startCloudRecord(true);   // 开始云录制
m_MeetingManager->startCloudRecord(false);  // 停止云录制

EnableSoundTouch

m_MeetingManager->EnableSoundTouch(true);  // 开启变声

privateTalk

// 私聊用户(语音私聊)
m_MeetingManager->privateTalk(userId, true);   // 发起私聊
m_MeetingManager->privateTalk(userId, false);  // 结束私聊

startMeeting

// 在 onPreStartMeeting 回调后调用
m_MeetingManager->startMeeting();

exitMeeting

// 退出会议(仅本端退出)
m_MeetingManager->exitMeeting();

closeMeeting

// 关闭会议(所有人退出)
m_MeetingManager->closeMeeting();

sendTransdataMsg

FS_SDK::UserList users;
users.push_back(targetUserId);
m_MeetingManager->sendTransdataMsg(users, _T("hello"));
// 传空 UserList 发送给所有人

startVote / stopVote / publicVoteResult

// 发起投票
FS_SDK::VoteInfo vote;
vote.title = _T("Do you agree?");
m_MeetingManager->startVote(vote);

// 停止投票
m_MeetingManager->stopVote(voteId);

// 公开结果
m_MeetingManager->publicVoteResult(voteId);

quickRollCallAck

// 快速签到应答
m_MeetingManager->quickRollCallAck(srcUser, dstUser, _T("guid-from-notification"));

9. IUserManager — 用户模块

getAllUsers

FS_SDK::RoomUserInfoList users = m_UserManager->getAllUsers();
for (auto& u : users) {
    _tprintf(_T("User %u: %s (offline=%d)\n"), u.userId, u.nickName, u.isOffline);
}

getUserInfo

const FS_SDK::RoomUserInfo* info = m_UserManager->getUserInfo(userId);
if (info) {
    _tprintf(_T("Nick: %s\n"), info->nickName);
}

getOnlineUserCount

S_UINT32 count = m_UserManager->getOnlineUserCount();
_tprintf(_T("Online users: %u\n"), count);

getLocalUser

S_UINT32 myId = m_UserManager->getLocalUser();
_tprintf(_T("My user ID: %u\n"), myId);

isManager / isHost

S_BOOL isAdmin = m_UserManager->isManager(userId);
S_BOOL isHost  = m_UserManager->isHost(userId);

setNickName

m_UserManager->setNickName(myId, _T("New Nickname"));

10. IChatManager — 聊天模块

sendChatMessage

// 发送给所有人
m_ChatManager->sendChatMessage(0, _T("Hello everyone!"));

// 发送给指定用户
m_ChatManager->sendChatMessage(userId, _T("Hi there!"));

checkChatMessage

// 审批聊天消息
m_ChatManager->checkChatMessage(msgId, true);   // 通过
m_ChatManager->checkChatMessage(msgId, false);  // 拒绝

getChatMessageRecallDuration

S_INT duration = m_ChatManager->getChatMessageRecallDuration();
// -1 表示服务器不支持,0 表示未开启

recallChatMessage

m_ChatManager->recallChatMessage(_T("msg_id_to_recall"));

11. IContactsManager — 通讯录模块

getAllContact

FS_SDK::ContactInfoList contacts = m_ContactsManager->getAllContact();

queryCompanyUsers

m_ContactsManager->queryCompanyUsers(100);
// 异步,结果在 onQueryCompanyUsersCompleted 中

getDeptNodes

FS_SDK::DeptNodes nodes = m_ContactsManager->getDeptNodes();

getGroupInfoList / queryGroupInfo

FS_SDK::GroupInfoList groups = m_ContactsManager->getGroupInfoList(FS_SDK::ContactsGroupType::ALL);
m_ContactsManager->queryGroupInfo(FS_SDK::ContactsGroupType::ALL);

mondifyGroupInfo

FS_SDK::GroupInfo group;
group.groupId = 1;
group.name    = _T("Team A");
m_ContactsManager->mondifyGroupInfo(group, false);  // false=编辑, true=创建

removeGroup

m_ContactsManager->removeGroup(groupId);

addGroupUser / removeGroupUser

FS_SDK::GroupInfo info;
info.groupId = 1;
info.userIds.push_back(userId);

m_ContactsManager->addGroupUser(info);     // 添加用户
m_ContactsManager->removeGroupUser(info);  // 移除用户

acceptMeetingInvite

// 同意/拒绝会议邀请
m_ContactsManager->acceptMeetingInvite(hostUserId, inviteId, true);

inviteUsers

// 邀请通讯录用户入会
S_UINT32 users[] = { userId1, userId2 };
FS_SDK::MeetingInvitation inv;
m_ContactsManager->inviteUsers(users, 2, &inv);

abandonInvite

S_UINT32 users[] = { userId1 };
m_ContactsManager->abandonInvite(inviteId, users, 1);

12. IScreenShareManager — 屏幕共享模块

startScreenShare

FS_SDK::ScreenShareInfo info;
info.monitorIndex  = 0;    // 显示器索引
info.captureCursor = true;  // 捕获鼠标
info.includeAudio  = true;  // 包含音频
FS_SDK::ErrCode ec = m_ScreenShareManager->startScreenShare(&info);

stopScreenShare

m_ScreenShareManager->stopScreenShare(0);  // 0 = 停止本端共享

startRemoteScreenShareView / stopRemoteScreenShareView

// 开始接收远端屏幕共享
HWND hScreenShareWnd = GetDlgItem(IDC_SCREEN_SHARE);
m_ScreenShareManager->startRemoteScreenShareView(userId, 0, hScreenShareWnd);

// 停止接收
m_ScreenShareManager->stopRemoteScreenShareView(userId, 0);

isScreenSharing

S_BOOL isSharing = m_ScreenShareManager->isScreenSharing();

queryVncControl / recoverVncControl / allowVncControl

// 请求远程控制
m_ScreenShareManager->queryVncControl(true);   // 请求
m_ScreenShareManager->queryVncControl(false);  // 释放

// 共享端收回远程控制
m_ScreenShareManager->recoverVncControl();

// 同意/拒绝远程控制
m_ScreenShareManager->allowVncControl(true);

setVncVideoRawDataObserver

// 在 startRemoteScreenShareView 之前调用
m_ScreenShareManager->setVncVideoRawDataObserver(true);
// 数据在 onVncVideoRawData 中获取(仅 FSP 路径有效)

13. IMediaShareManager — 多媒体共享模块

stopMediaShare

m_MediaShareManager->stopMediaShare(0, 0);  // 结束本端多媒体共享

pauseMediaShare

m_MediaShareManager->pauseMediaShare(0, 0, true);   // 暂停
m_MediaShareManager->pauseMediaShare(0, 0, false);  // 恢复

startRemoteMediaShareView / stopRemoteMediaShareView

// 开始接收远端多媒体
HWND hMediaWnd = GetDlgItem(IDC_MEDIA_SHARE);
m_MediaShareManager->startRemoteMediaShareView(userId, 0, hMediaWnd);

// 停止接收
m_MediaShareManager->stopRemoteMediaShareView(userId, 0);

isPureMusic

FS_SDK::ErrCode ec = m_MediaShareManager->isPureMusic(userId, 0);
if (ec == FS_SDK::ERR_SUCCESS) {
    // 是纯音乐共享
}

isMediaSharing

S_BOOL isSharing = m_MediaShareManager->isMediaSharing();

14. IPermissionManager — 权限模块

checkUserPermission

S_BOOL hasPerm = m_PermissionManager->checkUserPermission(
    FS_SDK::UserPermission::CAN_BROADCAST_AUDIO,
    userId
);

configUserPermission

m_PermissionManager->configUserPermission(
    FS_SDK::UserPermission::CAN_BROADCAST_AUDIO,
    true,    // 赋予权限
    userId
);

checkRolePermission

FS_SDK::UserRole role = m_PermissionManager->checkRolePermission(
    FS_SDK::RolePermission::CAN_ROLE_BROADCAST_AUDIO
);

configRolePermission

m_PermissionManager->configRolePermission(
    FS_SDK::RolePermission::CAN_ROLE_BROADCAST_AUDIO,
    FS_SDK::UserRole::HOST
);

checkRoomPermission

m_PermissionManager->checkRoomPermission(
    FS_SDK::RoomPermission::WHITEBOARD,
    0  // rights_index, 白板 0-6
);
// 结果在 IPermissionEventHandler::onCheckRoomPermission 中

requestRoomPermission

// 请求白板权限
m_PermissionManager->requestRoomPermission(
    FS_SDK::RoomPermission::WHITEBOARD,
    0,       // rights_index
    false     // free: false=请求, true=释放
);

lockRoomPermission

// 锁定白板权限,其他人无法请求
m_PermissionManager->lockRoomPermission(
    FS_SDK::RoomPermission::WHITEBOARD,
    0,
    false     // unlock: false=锁定, true=解锁
);

15. IWhiteBoardManager — 电子白板模块

initialize / uninitialize

// 入会后初始化白板
m_WBManager->initialize();

// 释放白板模块
m_WBManager->uninitialize();

createWB

// 创建空白白板
S_UINT32 wbId = m_WBManager->createWB(
    0,                    // rightsIndex
    S_SIZE{1920, 1080},     // sz
    0,                    // size (0=默认)
    nullptr                // file (nullptr=空白板)
);

createWBFromDocFile

// 通过共享文档创建白板
S_UINT32 wbId = m_WBManager->createWBFromDocFile(
    0,
    _T("doc_guid_from_server"),
    true  // shared: 是否共享给所有人
);

closeWB / closeAllWB

m_WBManager->closeWB(wbId);      // 关闭指定白板
m_WBManager->closeAllWB();        // 关闭所有白板

activeWB

m_WBManager->activeWB(wbId, true);   // 激活白板
m_WBManager->activeWB(wbId, false);  // 取消激活

setWBPage

m_WBManager->setWBPage(wbId, 0);  // 切换到第 0 页

setWBZoom

m_WBManager->setWBZoom(wbId, 100);  // 100% 缩放

setWBBkColor

m_WBManager->setWBBkColor(wbId, 0xFFFFFF);  // 白色背景

setWBIndicator

m_WBManager->setWBIndicator(wbId, S_POINT{100, 200});

setWBScroll

m_WBManager->setWBScroll(wbId, S_POINT{50, 50});

rotateWB

m_WBManager->rotateWB(wbId, true);   // 顺时针旋转 90 度
m_WBManager->rotateWB(wbId, false);  // 逆时针旋转 90 度

getCount

S_INT32 count = m_WBManager->getCount();
_tprintf(_T("Open whiteboards: %d\n"), count);

addWBObject

FS_SDK::WhiteBoardGraphicsObj obj;
obj.type = FS_SDK::WhiteBoardGraphicsObj::RECTANGLE;
obj.x = 100; obj.y = 100;
obj.w = 200; obj.h = 150;
obj.color = 0xFF0000;  // 红色
m_WBManager->addWBObject(wbId, 0, obj);

delWBObject

m_WBManager->delWBObject(wbId, 0, objId);

16. EventHandler 完整示例

以下是一个完整的 EventHandler 设置示例:

// ======== 登录事件 ========
class MyLoginHandler : public FS_SDK::ILoginEventHandler {
    void onAccountLoginCompleted(FS_SDK::ErrCode err) override { }
    void onAnonymousLoginCompleted(FS_SDK::ErrCode err) override { }
    void onEnterRoomCompleted(FS_SDK::ErrCode err) override { }
    void onInitCompleted() override { }
    void onPreStartMeeting() override { }
    void onExitedRoom() override { }
    void onUpdateServerCompleted(FS_SDK::ErrCode err) override { }
    void onAccountLogout(FS_SDK::ErrCode err) override { }
    void onAnonymousLoginPaasCompleted(FS_SDK::ErrCode err) override { }
    void onWaitingForAllowToEnter(FS_SDK::ErrCode err) override { }
};

// ======== 音频事件 ========
class MyAudioHandler : public FS_SDK::IAudioEventHandler {
    void onCaptureDeviceChanged() override { }
    void onPlayDeviceChanged() override { }
    void onUserAudioEvent(S_UINT32 userId, S_BYTE state) override { }
    void onApplyAudioEvent(const FS_SDK::ApplyAVRightInfo* info) override { }
    void onLocalAudioCapData(S_PBYTE data, S_UINT32 dataLen) override { }
};

// ======== 视频事件 ========
class MyVideoHandler : public FS_SDK::IVideoEventHandler {
    void onVideoDeviceChanged() override { }
    void onUserVideoEvent(S_UINT32 userId, S_BYTE ch, S_BYTE state) override { }
    void onApplyVideoEvent(const FS_SDK::ApplyAVRightInfo* info) override { }
    void onUserVideoData(S_UINT32 userId, S_BYTE ch, S_PBYTE d, S_UINT32 l) override { }
    void onUserVideoRawData(S_UINT32 userId, S_BYTE ch, S_PBYTE d, S_UINT32 l, S_UINT32 fc, S_SIZE wh, S_INT32 r) override { }
    void onLocalVideoRawData(S_BYTE ch, S_PBYTE d, S_UINT32 l, S_UINT32 fc, S_SIZE wh, S_INT32 r) override { }
    void onAddRtspResult(FS_SDK::ErrCode err, S_UINT32 devId) override { }
    void onLocalDeviceRecordFail(S_UINT32 devId) override { }
};

// ======== 会议事件 ========
class MyMeetingHandler : public FS_SDK::IMeetingEventHandler {
    void onQueryRoomList(S_INT32 code, S_PCHAR msg, const FS_SDK::MeetingInfo* infos, S_UINT32 sz, S_UINT32 total) override { }
    void onQueryRoomListOfInstant(S_INT32 code, S_PCHAR msg, const FS_SDK::MeetingInfo* infos, S_UINT32 sz) override { }
    void onCreateInstantMeeting(S_INT32 tid, S_INT32 code, S_PCHAR msg, const FS_SDK::MeetingInfo* info) override { }
    void onQueryRoomShareInfo(S_INT32 tid, S_INT32 code, S_PCHAR msg, const FS_SDK::MeetingShareInfo* info) override { }
    void onQueryJoinMeetingRecords(S_INT32 code, S_PCHAR msg, const FS_SDK::JoinMeetingRecord* r, S_UINT32 sz) override { }
    void onManagerApplyEvent(FS_SDK::ErrCode err) override { }
    void onManagerStateEvent(S_UINT32 userId, S_BYTE state) override { }
    void onPresenterStateEvent(S_UINT32 userId, S_BYTE state) override { }
    void onDataSharerStateEvent(S_UINT32 userId, S_BYTE state) override { }
    void onPrivateTalkStateEvent(S_UINT32 src, S_UINT32 dst, S_BYTE state) override { }
    void onPrivateTalkInterrupted(S_UINT32 src, S_UINT32 dst, S_UINT32 mgr) override { }
    void OnLayoutEvent(const FS_SDK::RoomLayoutInfo& layout) override { }
    void onKickUserEvent(S_UINT32 userId) override { }
    void onMeetingClosed() override { }
    void onMeetingSilent(S_UINT32 srcId) override { }
    void onMeetingWaterMarkState(S_BYTE state, S_INT32 time) override { }
    void onServerRecordEvent(S_UINT32 srcId, S_BYTE state, FS_SDK::ErrCode err) override { }
    void onSoundTouchEvent(S_BYTE state) override { }
    void onUserMsgIncome(S_UINT32 srcId, S_PCHAR msg) override { }
    void onClientSupportAbility(S_UINT32 userId, S_UINT ability) override { }
    void onUpdateVoteInfo(const FS_SDK::VoteInfo& info) override { }
    void onStartSubtitle(const FS_SDK::SubtitleInfo& info) override { }
    void onStopSubtitle() override { }
    void onStartQuickRollCall(S_UINT32 src, const FS_SDK::FString& guid, S_UINT32 dur) override { }
    void onStopQuickRollCall(S_UINT32 src, const FS_SDK::FString& guid) override { }
    void onStartManualRollCall(S_UINT32 src, const FS_SDK::FString& guid, FS_SDK::ManualRollCallType type) override { }
    void onVideoPollingState(const FS_SDK::VideoPollingState& state) override { }
    void onWebSendNotify(S_UINT32 userId, S_PTCHAR op, S_PTCHAR appBackNotify) override { }
};

// ======== 用户事件 ========
class MyUserHandler : public FS_SDK::IUserEventHandler {
    void onUserEntered(S_UINT32 userId) override { }
    void onOffineUser(S_UINT32 userId) override { }
    void onUserLeft(S_UINT32 userId) override { }
    void onUserInfoUpdated(S_UINT32 userId) override { }
};

// ======== 聊天事件 ========
class MyChatHandler : public FS_SDK::IChatEventHandler {
    void onUserChatEvent(const FS_SDK::ChatMsgInfo* msg) override { }
    void onCheckChatMessageResult(S_UINT32 msgNum, S_UINT32 uid, S_BOOL result) override { }
    void onRecallChatMessageResult(S_PTCHAR msgId, S_UINT32 result) override { }
    void onChatMessageRecalled(S_PTCHAR msgId, S_UINT32 srcUserId) override { }
    void onSystemChatMessage(S_UINT32 msgNum, S_PTCHAR content, S_BOOL isMsgBox) override { }
};

// ======== 设置事件处理器 ========
void SetupEventHandlers()
{
    FS_SDK::ISdkManager* sdk = FS_SDK::ISdkManager::getInstance();
    // 每个模块通过 setEventHandler 绑定对应的事件处理器
    sdk->getLoginManager()->setEventHandler(&g_loginHandler);
    sdk->getAudioManager()->setEventHandler(&g_audioHandler);
    sdk->getVideoManager()->setEventHandler(&g_videoHandler);
    sdk->getMeetingManager()->setEventHandler(&g_meetingHandler);
    sdk->getUserManager()->setEventHandler(&g_userHandler);
    sdk->getChatManager()->setEventHandler(&g_chatHandler);
    // ... 其他模块同理
}

17. 完整登录入会流程示例

// 全局变量
FS_SDK::ISdkManager*         g_pSdkManager    = nullptr;
FS_SDK::ILoginManager*       g_pLoginMgr      = nullptr;
FS_SDK::IMeetingManager*     g_pMeetingMgr    = nullptr;
FS_SDK::IWhiteBoardManager*  g_pWBMgr         = nullptr;

// 事件处理器实例
MyLoginHandler   g_loginHandler;
MyMeetingHandler g_meetingHandler;

void LoginAndJoinMeeting()
{
    // 1. 获取 SDK 实例并初始化
    g_pSdkManager = FS_SDK::ISdkManager::getInstance();
    FS_SDK::SdkContext ctx;
    ctx.appId  = _T("com.example.myapp");
    ctx.appVer = _T("1.0.0");
    FS_SDK::ErrCode ec = g_pSdkManager->initSdk(&ctx);
    if (ec != FS_SDK::ERR_SUCCESS) return;

    // 2. 获取各管理器
    g_pLoginMgr   = g_pSdkManager->getLoginManager();
    g_pMeetingMgr = g_pSdkManager->getMeetingManager();
    g_pWBMgr      = g_pSdkManager->getWhiteBoardManager();

    // 3. 设置事件处理器
    g_pLoginMgr->setEventHandler(&g_loginHandler);
    g_pMeetingMgr->setEventHandler(&g_meetingHandler);

    // 4. 设置服务器地址和 ClientInfo
    g_pLoginMgr->setServerAddr(_T("apaas.hst.com"), 443);
    g_pLoginMgr->setClientInfo(_T("client_id"), _T("client_secret"));

    // 5. 账号登录
    g_pLoginMgr->loginAccount(
        (S_PTCHAR)_T("zhangsan"),
        (S_PTCHAR)_T("mypassword"),
        true
    );
    // 登录结果在 MyLoginHandler::onAccountLoginCompleted 中处理
}

// 在 onAccountLoginCompleted 中
void MyLoginHandler::onAccountLoginCompleted(FS_SDK::ErrCode err) override
{
    if (err != FS_SDK::ERR_SUCCESS) {
        // 登录失败
        return;
    }
    // 登录成功,入会
    g_pLoginMgr->enterRoom(false, 123456, _T(""));
}

// 在 onEnterRoomCompleted 中
void MyLoginHandler::onEnterRoomCompleted(FS_SDK::ErrCode err) override
{
    if (err != FS_SDK::ERR_SUCCESS) return;
    // 入会成功,初始化白板
    g_pWBMgr->initialize();
}

// 退出流程
void LogoutAndExit()
{
    g_pMeetingMgr->exitMeeting();
    g_pLoginMgr->logout();
    g_pSdkManager->releaseSdk();
}