产品简介

产品概述


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

架构介绍

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

功能介绍

SDK主要提供以下功能

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

快速入门

准备工作


SDK开发环境要求

企业能力

如需使用SDK,需要联系商务在企业先开通开发者功能。
1.png
开通后,可在新版公有云后台创建SDK应用并获取clientID和clientSecret,clientID和clientSecret用于校验其合法性。
2.png
私有云客户请联系商务获取服务器部署与SDK功能支持。

什么是clientID和clientSecret

clientID和clientSecret是好视通设计的一种识别应用的唯一签名,可以保护客户的云服务资源不被非认证的应用盗用消耗。
clientID和clientSecret是一起使用的,可以用于一个平台应用也可用于多个平台应用。如果clientID和clientSecret一起被泄漏,可通过停用或者删除来阻止攻击者使用。开发者只用再替换一套clientID和clientSecret继续做认证。
推荐clientID和clientSecret由服务端进行分发,这样可以更快的实现clientID和clientSecret的更新。
c2307280-7549-413c-809a-880e709cb365.png

clientID和clientSecret管理

把clientID和clientSecret放到客户端代码内是一种不推荐的做法,只适合快速运行Demo时提高便利性。客户端代码容易被反编译破译出其中的密钥信息。一旦clientID和clientSecret泄漏,攻击者就可能攻击您所在的服务器。
推荐将clientID和clientSecret放到您的服务器上,然后通过您内部的可信任鉴权方式让客户端获取到服务器上的clientID和clientSecret。同时这种方式也能方便用户在clientID和clientSecret泄漏时进行替换。
4.png

运行Demo


步骤1:下载SDK Demo
先在官网下载Demo
步骤2:导入项目

  1. 将文件解压可以获得Demo以及Demo工程。

1.png

  1. 右键解决方案,点击生成解决方案。

2.png

  1. 右键Demo 点击设为启动项目。

3.png
步骤3:编译运行

  1. 点击框选位置运行,或者按F5运行。

4.png

  1. 最后查看运行结果,得到一个可以入会的客户端。

5.png

打包说明


  1. SDK

image.png
打包时需要将上层APP文件和SDK的hstsdk\bin\release目录下所有文件都加入安装包中(debug版同理)。

  1. FMPrinter

image.png
安装APP的同时需要安装FMPrinter(用于会议中的部分功能),如上图,只需运行hstsdk\FMPrinter目录下的reinstall.bat脚本即可自动安装FMPrinter。

核心功能

集成介绍


1.SDK集成

1.1如何集成SDK 到新工程

1.1.1 获取SDK

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

image.png
点击下载:
image.png

  1. 解压。

image.png
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文档常用语说明
文档常用语表:

常用语 含义 备注
SDKCallback 表示添加给ISdkManager* 的回调函数 EV_SDK_前缀处理
LoginCallback 表示添加给ILoginManager* 的回调函数 EV_LOGIN_前缀处理
Manager* 表示SDK相关接口管理类
Other Callback 表示除SDKCallback, LoginCallback 外的其他Callback
Other Manager* 表示除ISdkManager, ILoginManager* 外的其他Manager*
MeetingCallback 会议回调 EV_MEETING_前缀处理
WBCallback 白板回调 EV_SHARE..._WB前缀处理
VideoCallback 视频相关回调 EV_VIDEO_前缀处理
AudioCallback 音频相关回调 EV_AUDIO_前缀处理
ChatCallback 聊天回调 EV_MEETING_前缀处理
UserCallback 用户回调 EV_USER_前缀处理
ScreenCallback 屏幕共享回调 EV_SHARE前缀处理

1.2.2 SDK应用整体结构图

1.2.3 SDK调用时序
sdk 调用时序.png
1.2.4 回调函数结构

typedef std::function<void(PVOID)> SNOTIFY;
Eg: void aaa(PVOID param);

1.2.5 事件结构
(1) 结构体

 struct SEventData  
 {  
     SDK_EVENT ev;  
     ErrCode   ec;  
     PVOID     pl;  
     SEventData() {}  
     SEventData(SDK_EVENT v, ErrCode c, PVOID p) :ev(v), ec(c), pl(p) {}  
 };  

(2)SDK 通知事件需要在哪里处理介绍:

前缀 使用
EV_SDK 在SDKCallback 中处理
EV_LOGIN 在LoginCallback中处理
EV_MEETING 在MeetingCallback中处理
EV_CHAT 在ChatCallback中处理
EV_VIDEO 在VideoCallback中处理
EV_AUDIO 在AudioCallback中处理
EV_USER 在UserCallback中处理
EV_SHARE 在ShareCallback中处理
EV_PERMISSON 在PermissionCallback 中处理
EV_SHARE...WB 在WBCallback 中处理

ErrCode 表示一些异步操作的通知结果 ERR_SUCCESS 表示成功。
PVOID     pl; 表示一些事件回调上来的数据,需要转换成对应的类型后使用 。
注:所有的Manager的回调可以是同一个回调函数。

2.SDK 初始化

2.1 获取SdkManager

通过getSdkManager() 接口获取ISdkManager* 实例。

 FS_SDK::ISdkManager*       m_MeetingApi = 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_ShareManager = nullptr;  
 FS_SDK::IPermissionManager*      m_PermissionManager = nullptr;  
 FS_SDK::IWBShareManager*   m_WBManager = nullptr;    
      
 m_MeetingApi = getSdkManager();  
 if (!m_MeetingApi)  
 {  
     g_pSuperLog->WriteFile(_T("getSdkManager调用失败! 返回nullptr\n"));  
     break;  
 }  
   
 // 这些必须同时初始化  
 m_LoginManager = m_MeetingApi->getLoginManager();  
 m_AudioManager = m_MeetingApi->getAudioManager();  
 m_VideoManager = m_MeetingApi->getVideoManager();  
 m_ChatManager = m_MeetingApi->getChatManager();  
 m_MeetingManager = m_MeetingApi->getMeetingManager();  
 m_UserManager = m_MeetingApi->getUserManager();  
 m_ShareManager = m_MeetingApi->getScreenShareManager();  
 m_PermissionManager = m_MeetingApi->getPermissionManager();  
 m_WBManager = m_MeetingApi->getWBShareManager();  
   
 if (!m_LoginManager || !m_AudioManager || !m_VideoManager || !m_MeetingManager \  
     || !m_UserManager || !m_ShareManager || !m_ChatManager || !m_PermissionManager \  
     || !m_WBManager)  
 {  
     g_pSuperLog->WriteFile(L"SDK 组件管理初始化失败\n");  
     break;  
 }  
 //end  

2.2 SDK添加事件回调

通过ISdkManager* 实例调用addEventListener()接口添加回调。

 // 添加sdk 通知监听  
 m_MeetingApi->addEventListener([&](PVOID pData) {  
    SDKCallBack(pData); }); 

2.3 SDK初始化

2.3.1 SDK 初始化代码
示例代码:

 FS_SDK::ErrCode hR = m_MeetingApi->initSdk(_T("127.0.0.1:1089"));  
 if (FS_SDK::ERR_SUCCESS != hR)  
 {  
     g_pSuperLog->WriteLogMsg("m_MeetingApi->Init Failed ,Error Code %d\n", hR);  
 }   

初始化后会有事件回调 EV_SDK_INIT 通知给SDKCallBack。
2.3.2 添加登录回调
SDKCallBack收到EV_SDK_INIT 事件后添加登录回调。
示例代码:

 void CSdkManager::SDKCallBack(PVOID pParam)  
 {  
     g_pSuperLog->WriteLogMsg("CSdkManager::SDKCallBack Enter \n");  
     FS_SDK::SEventData* pData = (FS_SDK::SEventData*)pParam;  
     if (!pData)  
     {  
         g_pSuperLog->WriteLogMsg("SDKCallBack 通知数据结构为 nullptr \n");  
         g_pSuperLog->WriteLogMsg("CSdkManager::SDKCallBack pData == nullptr leave \n");  
         return;  
     }  
   
     g_pSuperLog->WriteLogMsg("SDKCallBack 通知事件: %d\n", pData->ev);  
     if (m_bClose)  
     {  
         g_pSuperLog->WriteLogMsg("CSdkManager::SDKCallBack m_bClose == true leave \n");  
         return;  
     }  
   
     switch (pData->ev)  
     {  
     case FS_SDK::EV_SDK_INIT:  
     {  
         g_pSuperLog->WriteLogMsg("Notify: %s\n", "EV_SDK_INIT");  
         g_pSuperLog->WriteFile(L"初始化登录回调\n");  
         m_LoginManager->addEventListener([&](PVOID pData) {  
             LoginCallBack(pData); });  
     }  
     break;  
   
     default:  
         break;  
     }  
   
     g_pSuperLog->WriteLogMsg("CSdkManager::SDKCallBack leave \n");  
 } 

2.3.3 添加其他回调
LoginCallBack收到EV_LOGIN_ROOM_INIT 事件后添加登录回调。
示例代码:

void CSdkManager::LoginCallBack(PVOID pParam)  
{   
    FS_SDK::SEventData* pData = (FS_SDK::SEventData*)pParam;  
    if (!pData)  
    {  
        g_pSuperLog->WriteLogMsg("LoginCallBack 通知数据结构为 nullptr \n");  
        g_pSuperLog->WriteLogMsg("CSdkManager::LoginCallBack leave \n");  
        return;  
    }  
   
     g_pSuperLog->WriteLogMsg("LoginCallBack 通知事件: %d  \n", pData->ev);  
     if (m_bClose)  
     {  
         g_pSuperLog->WriteLogMsg("CSdkManager::LoginCallBack m_bClose == true leave \n");  
         return;  
     }  
     switch (pData->ev)  
     {  
     case FS_SDK::EV_LOGIN_ROOM_INIT:  
     {  
         g_pSuperLog->WriteLogMsg("Notify: %s\n", "EV_LOGIN_ROOM_INIT");  
         g_pSuperLog->WriteFile(L"初始化会议相关监听回调\n");  
         m_ChatManager->addEventListener([&](PVOID pData) {  
             ChatCallBack(pData);  
         });  
         m_ShareManager->addEventListener([&](PVOID pData) {  
             ShareCallBack(pData);  
         });  
         m_AudioManager->addEventListener([&](PVOID pData) {  
             AudioCallBack(pData);  
         });  
         m_VideoManager->addEventListener([&](PVOID pData) {  
             VideoCallBack(pData);  
         });  
         m_UserManager->addEventListener([&](PVOID pData) {  
             UserCallBack(pData);  
         });  
         m_MeetingManager->addEventListener([&](PVOID pData) {  
             MeetingCallBack(pData);  
         });  
   
         m_PermissionManager->addEventListener([&](PVOID pData) {  
             PermissionCallBack(pData);  
         });  
   
         m_WBManager->addEventListener([&](PVOID pData) {  
             WBCallBack(pData);  
         });  
     }  
     break;  
     default:  
         break;  
     }  
 } 

2.4 SDK 设置ClientIdInfo

EV_LOGIN_ENV_INIT 事件回调后设置ClientInfo。
示例代码:

 FS_SDK::LSdkTokenParam tokenparam;  
 tokenparam.strOauthKey = m_LoginParam.strClientID.c_str();  
 tokenparam.strOauthSecret = m_LoginParam.strSecret.c_str();  
   
 FS_SDK::ErrCode hR = CSdkManager::GetInstance()->SetSdkClientInfo(tokenparam);  
 if (hR != FS_SDK::ERR_SUCCESS)  
 {  
     g_pSuperLog->WriteFile(L"设置ClientID或者Secret失败!\n"); 
 }

注意:setServerIp 后需要重新调用 setClientIdInfo, 否则无法登录。

登录入会


必须在LoginCallBack 回调收到EV_LOGIN_ENV_INIT 事件后,才可以调用登录接口。

1.账号密码登录入会

示例代码:

 FS_SDK::ErrCode CSdkManager::Login(const char* username, const char* pwd, const char* meetid)  
 {  
     g_pSuperLog->WriteLogMsg("账号登录, 用户: %s, 密码:******, 会议室号:%s ,\n", username, meetid);  
     return m_LoginManager->loginAccount(username, pwd, meetid);  
 }

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

示例代码:

 FS_SDK::ErrCode CSdkManager::LoginRoomID(const char* nickname, const char* pwd, const char* meetid)  
 {  
     g_pSuperLog->WriteLogMsg("会议室号登录, 昵称:%s, 会议密码:******, 会议室号:%s ,\n", nickname, meetid);  
     return m_LoginManager->loginRoomID(nickname, pwd, meetid);  
 } 

3.登录结果通知

  1. 登录接口调用后,LoginCallback 会收到EV_LOGIN_RESULT 事件,可能会有多个错误回调,但只有收到

ERR_LOGIN_SUCCESS才能确定登录成功。

  1. 入会成功MeetingCallback会收到EV_MEETING_ENTER事件,该事件会收到当前本地用户ID(自己的用户ID)。
case FS_SDK::EV_MEETING_ENTER:  
{  
    g_pSuperLog->WriteLogMsg("初始化完成   EV_MEETING_ENTER\n");  
    FS_SDK::LUserCommond* pBc = STATIC_CAST_(FS_SDK::LUserCommond*, pData->pl);  
    if (pBc)  
    {  
        m_dwLocalUserId = pBc->uUserID;  
        CSdkManager::GetInstance()->SetLocalUserID(m_dwLocalUserId);  
    }  
}  
break; 

4.退出

1、MeetingManager 中 exitMeeting退出会议
2、收到EV_LOGIN_EXIT_ROOM, EV_MEETING_CLOSE事件后需要调用releaseSdk() 接口
3、当收到EV_SDK_UNINIT 事件后方可关闭上层应用程序
4、收到EV_MEETING_KICK 需要调用exitMeeting() 接口
5、未登录时关闭程序需要调用releaseSdk()接口

广播音频


1.音频状态

struct AudioChannel  
{  
    S_BYTE          id = 0;;  
    S_BYTE          state = 0;;         //状态  
    S_BYTE          is_have_audio = 0;  //  
    S_INT32         cap_dev_index = 0;  //索引  
    S_INT32         operation = 0;;     //操作 DEV_OPERATION_ADD  
    S_UINT32        source_id = 0;      //源  
    S_TCHAR         name[256] = { 0 };  
};  
//RoomUserInfo 中 audio_channel  判断音频状态  

注: state等于0 未广播状态;等于1 正在申请广播状态;等于2 表示正在广播。

2.广播音频相关流程

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

处理他人音频申请:

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

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

他人音频广播状态通知:

 struct LUserBroadcast  
 {  
     S_UINT32    tType = 0;//类型  
     S_BYTE      bRecv = 0;//0:停止,1等待,2广播  
     S_BOOL      bShare = 0;//1表示广播  
     S_BYTE      bChannel = 0;//设备ID  
     HWND        hWnd = 0;//显示的窗口句柄  
     S_UINT32    uUserID = 0;//被操作用户  
 };  

接收SDK消息处理代码示例如下:

case FS_SDK::EV_AUDIO_MEDIA_MSG://音频状态  
{  
    g_pSuperLog->WriteLogMsg("音频状态变更  EV_AUDIO_MEDIA_MSG\n");  
    FS_SDK::LUserBroadcast* pBc = (FS_SDK::LUserBroadcast*)pData->pl;  
    if (!pBc)  
    {  
        g_pSuperLog->WriteLogMsg("FS_SDK::EV_AUDIO_MEDIA_MSG 视频数据  == nullptr \n");  
        break;  
    }  
     // 自己对应处理自己的业务逻辑
     notify->OnAudioState(pBc->uUserID, pBc->bRecv);  
     g_pSuperLog->WriteLogMsg("音频状态变更 接收: %s\n", pBc->bRecv ? "接收" : "不接收");  
 }  

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

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

控制他人音频代码示例如下:

FS_SDK::ErrCode hR = m_AudioManager->agreeApplyAudio(dwUserId, bAgree);  

广播视频


1.视频操作接口

/* 
*  @brief 广播\停止广播视频 
*  @param dwUserId 要广播\停止广播的用户ID 
*  @param bChannel 要广播\停止广播的设备id 
*  @param bState   是否广播 true 广播, false 停止广播 
*  @return 
*/  
virtual ErrCode broadcastVideo(S_UINT32, S_BYTE channel, S_BOOL bState);  
  
 /* 
 *  @brief 申请广播视频和放弃申请广播视频 
 *  @param bChanel 通道 
 *  @param state   state 2 申请  0 放弃申请 
 *  @return 
 */  
 virtual ErrCode applyBroadcastVideo(S_BYTE channel, S_BOOL bState);  
   
 /* 
 *  @brief 
 *  @param dwUserId 被同意或者被拒绝申请广播视频用户id 
 *  @param channel 通道id 
 *  @param bAgree 同意 true 拒绝 false 
 *  @return 
 */  
 virtual ErrCode agreeApplyVideo(S_UINT32 dwUserId, S_BYTE channel, S_BOOL bAgree); 

2.广播视频流程

  1. 广播自己视频

广播自己视频流程:

  1. 广播他人视频

共享屏幕


1.开启共享屏幕

需要使用的接口:

 isScreenSharing();  //是否正在屏幕共享
 isMultiShareEnable(); //是否支持多人共享
 getCurrentDataShareCount();  //当前共享数量(包括白板)
 //接收远程屏幕,只需要自己加窗口句柄
 startRemoteScreenShareView(S_UINT32 user, S_BYTE audioChannel, HWND hwnd); 
 //停止接收远程屏幕
 stopRemoteScreenShareView(S_UINT32 user, S_BYTE audioChannel);  
 startScreenShare(); //开始屏幕共享
 stopScreenShare(S_UINT32 user);  //停止屏幕共享

流程图如下:
共享桌面.png
部分实例代码如下:

if ((ScreenShareManager::GetInstance()->IsScreenSharing()\  
            || ScreenShareManager::GetInstance()->getCurrentDataSharerCount() > 0)\  
            && !ScreenShareManager::GetInstance()->isEnableMultiShare() \  
            && !ScreenShareManager::GetInstance()->IsLocalShare())  
{  
    uicontrol::MessageBoxWithOK(m_hWnd, _T("会议同时只允许一人共享,请等待对方结束后再共享!"),  
                STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
    return;  
}  
   
 if (ScreenShareManager::GetInstance()->IsLocalShare() && ScreenShareManager::GetInstance()->IsScreenSharing())// 切换到共享桌面  
 {  
     MeetingShareToolBar::GetInstance()->SelectShare(SHARE_DESKTOP_INDEX);  
     return;  
 }  
   
 if (ScreenShareManager::GetInstance()->isEnableMultiShare()  
             && ScreenShareManager::GetInstance()->IsScreenSharing())  
 {  
     uicontrol::MessageBoxWithOK(m_hWnd, _T("会议中有人正在共享桌面,请等待对方结束后再共享!"),  
                 STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
     return;  
 }  
   
 FS_SDK::ErrCode hR = ScreenShareManager::GetInstance()->StartShareScreen();  
 if (FS_SDK::ERR_SUCCESS != hR)  
 {  
     if (FS_SDK::ERR_NO_PRIVILEGE == hR || FS_SDK::ERR_NO_ADMIN_PRIVILEGE == hR)  
     {  
         if (CommonRight::IsLocalManager())  
         {  
             uicontrol::MessageBoxWithOK(m_hWnd, _T("您暂无屏幕共享权限!"),  
                         STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
         }  
         else  
         {  
             uicontrol::MessageBoxWithOK(m_hWnd, _T("没有屏幕共享权限,请申请管理员!"),  
                         STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
         }  
   
         return;  
     }  
     g_pSuperLog->WriteFile(L"开启屏幕共享失败!\n");  
     uicontrol::MessageBoxWithOK(m_hWnd, _T("开启屏幕共享失败!"), STRING_TIATLE,  
                 _T("确定"), uicontrol::MBI_WARN);  
     return;  
 }

2.停止共享屏幕

流程图如下:

停止共享实例代码如下:

DWORD dwLocalUserID = CSdkManager::GetInstance()->GetLocalUserID();  
if (!CommonRight::IsLocalScreenSharing()   
    && !PermissionManager::GetInstance()->CheckUserPermission(FS_SDK::CAN_SHARE_CLOSE_OTHER_APP, dwLocalUserID))  
{  
    uicontrol::MessageBoxWithOK(m_hWnd, _T("您不能关闭他人共享,请申请管理员!"),  
        STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
    return;  
}  
FS_SDK::ErrCode hR = ScreenShareManager::GetInstance()->StopShareScreen();  
 if (FS_SDK::ERR_SUCCESS != hR)  
 {  
     if (FS_SDK::ERR_NO_PRIVILEGE == hR || FS_SDK::ERR_NO_ADMIN_PRIVILEGE == hR)  
     {  
         uicontrol::MessageBoxWithOK(m_hWnd, _T("没有屏幕共享权限,请申请管理员!"),  
             STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
         return;  
     }  
     g_pSuperLog->WriteFile(L"结束屏幕共享失败!\n");  
     uicontrol::MessageBoxWithOK(m_hWnd, _T("结束屏幕共享失败!"), STRING_TIATLE,  
         _T("确定"), uicontrol::MBI_WARN);  
     return;  
 }  

3.接收共享屏幕

流程图如下:

示例代码如下:

case FS_SDK::EV_SHARE_SCREEN: //屏幕共享  
{  
    g_pSuperLog->WriteFile(L"屏幕共享通知  EV_SHARE_SCREEN\n");  
    FS_SDK::LShareInfo* pSh = (FS_SDK::LShareInfo*)pData->pl;  
    if (!pSh)  
    {  
        break;  
    }  
  
     g_pSuperLog->WriteLogMsg("屏幕共享通知: 用户:%d , state: %d, 通道: %d \n",  
         pSh->user_id, pSh->bState, pSh->bChannel);  
     if (pSh->bState)  
     {  
         m_dwShareUserId = pSh->user_id;  
         pSh->hWnd = m_pMeetingDataDlg->GetHWND();  
          m_ShareManager->startRemoteScreenShareView(pSh->user_id, pSh->bChannel, pSh->hWnd);  
     }  
     else  
     {  
         m_ShareManager->stopRemoteScreenShareView(pSh->user_id, pSh->bChannel);  
     }  
     notify->OnShareScreen(pSh);  
 }  
 break;  

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

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

FS_SDK::ErrCode hR = m_ShareManager->updateScreenShareWnd(hWnd);  

共享白板


1.开启共享白板

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

if ((ScreenShareManager::GetInstance()->IsScreenSharing()\  
    || ScreenShareManager::GetInstance()->getCurrentDataSharerCount() > 0)\  
    && !ScreenShareManager::GetInstance()->isEnableMultiShare() \  
    && !ScreenShareManager::GetInstance()->IsLocalShare())  
{  
    uicontrol::MessageBoxWithOK(m_hWnd, _T("会议同时只允许一人共享,请等待对方结束后再共享!"),  
        STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
    return;  
}  
   
 if (MeetingShareToolBar::GetInstance()->GetWBShareDataSize() == MAX_WB_SHARE_SIZE)  
 {  
     uicontrol::MessageBoxWithOK(m_hWnd, _T("当前打开的文档数量已超过限制,请先关闭一些已打开的文档!"),  
         STRING_TIATLE, _T("确定"), uicontrol::MBI_WARN);  
     return;  
 }  
   
 FS_SDK::ErrCode hr = WBShareManager::GetInstance()->StartWBShare();  
 if (hr == FS_SDK::ERR_SUCCESS)  
 {  
     g_pSuperLog->WriteFile(L"共享白板成功\n");  
     //MeetingShareToolBar::GetInstance()->SetShareSuccess();  
 }  
 else if (FS_SDK::ERR_NO_ADMIN_PRIVILEGE == hr || FS_SDK::ERR_NO_PRIVILEGE == hr)  
 {  
     uicontrol::MessageBoxWithOK(m_hWnd, _T("没有权限共享白板!"), STRING_TIATLE,  
         _T("确定"), uicontrol::MBI_INFO, MSG_CLOSE_TIME);  
 }  

2.停止共享白板

 if (FS_SDK::ERR_SUCCESS == WBShareManager::GetInstance()->StopWBShare(index))  
 {  
     //success  
 }  

3.接收共享白板

WBCallback示例代码如下:

case FS_SDK::EV_SHARE_START_WB:  
{  
    g_pSuperLog->WriteLogMsg("共享白板消息 EV_SHARE_WB");  
    FS_SDK::LShareWhiteBoard* pSh = (FS_SDK::LShareWhiteBoard*)pData->pl;  
    if (pSh && notify)  
    {  
        notify->OnWBShare(pSh, true);  
    }  
}  
break;  
case FS_SDK::EV_SHARE_STOP_WB:  
{  
    g_pSuperLog->WriteLogMsg("共享白板消息 EV_SHARE_WB");  
    FS_SDK::LShareWhiteBoard* pSh = (FS_SDK::LShareWhiteBoard*)pData->pl;  
    if (pSh && notify)  
    {  
        notify->OnWBShare(pSh, false);  
    }  
}  
break;    

详情见SdkDemo WBShareManager.h、WBShareManager.cpp

4.激活共享白板

激活白板调用情况:

  1. 当收到EV_SHARE_WB_INIT_STATE事件后调用。
  2. 切换白板时调用。
  3. getWBInfo GetWBType 接口需要收到EV_SHARE_WB_INIT_STATE 事件后调用。
  4. getWBInfo 当前版本只支持白板名称获取。
FS_SDK::ErrCode hR = m_WBManager->activeWB(index);  

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   
                                     //白板id  
                                     //视频,屏幕共享,媒体共享 为用户ID //,其他的需要在创建的时候手动生成  
         S_UINT32    user_data = 0;  //用户自定义类型  
                                     //白板 id  
                                     //视频 通道ID  
                                     //屏幕共享,媒体共享 为空 //,其他的需要在创建的时候手动生成  
     };  
   
     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;                  //自定义字段  
                                                             //新版本(tab风格中为当前选中的数据块pos;split分屏风格中为:SplitStyle,多少分屏)  
                                                             //所以需要应用层自己查找到对应pos,白板则需要继续以白板的active回调设置的当前激活白板  
                                                             //如果主讲是老版本,未使用新协议进行交互,splite 一样,tab风格中则userData字段值为:当前选中的数据类型             
         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集合  
                                     //包含全屏下需要显示的区域ID  
                                     //根据idSet是否为空判断,当前是否存在区域处于全屏状态  
     };  
   
     S_BYTE      screen_id = 0;  //屏幕ID  
                                 //用以区分主屏/分屏,从0递增  
                                 //可扩展用于同步分屏上的布局   
   
     LayoutMode  layout_mode = LAYOUT_MODE_NORMAL;   //原 bMode 客户端的当前屏的布局模式  
                                                     //(标准布局/数据布局/视频布局)  
                                                     //可扩展用于新的布局模式  
   
     FullArea    full_area;  //全屏区域  
                             //FullType  fullType;   //原 bFull 客户端全屏风格  
                             //(自动/数据全屏/视频全屏/数据+主讲视频全屏/数据+视频全屏)  
                             //可扩展用户全屏模式下的风格  
   
     AreaData    tab_area;   //tab风格区域  
                             //(windows客户端数据区域)  
                             //可扩展,一个屏可以有多个tab风格区域  
   
     AreaData    split_area; //split风格区域  
                             //(视频区域)  
                             //可扩展,一个屏可以有多个split风格区域  
   
     DataBlock   full_data_block;    //原FullVideoMedia  
                                     //单个全屏的数据项(单个视频选择全屏)  
                                     //可扩展,用于单独全屏任何数据模块  
 };  
   
 typedef LVector<RoomWndState> RoomWndStateVector;  
   
 struct LMeetingLayoutInfo  
 {  
     S_BOOL              valid_full_mode;//保持全屏模式(即使同步信息中为非全屏,也不退出全屏)  
     S_UINT32            user;           //同步者  
     RoomWndStateVector  wnd_state;      //窗口布局  可保存多屏
 };  

2.解析结构参考

实例代码如下:

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;  
        }  
  
        DWORD dwUserID = videopos.data_id;  
        BYTE  bMediaID = videopos.user_data;  
        int nPos = videopos.pos;  
   
        g_pSuperLog->WriteLogMsg("视频: 用户id : %d, 通道:%d POS:%d \n", dwUserID, bMediaID, nPos);  
   
        CDlgVideo *pDlgVideo = FindVideo(dwUserID, bMediaID, m_lsVideo);  
        if (nullptr == pDlgVideo)  
        {  
            //布局消息没有查看的视频先不管// 服务器bug 会缓存布局信息  
            continue;  
        }  
        else  
        {  
            m_VideoRelay.SetVideoID(nPos, dwUserID, bMediaID);  
            continue;  
        }  
     }  
 
     if (LayoutCommon::IsSingleVideoValid(state))// 单个视频全屏  
     {  
         DWORD dwUserID = state.full_data_block.data_id;  
         BYTE  bMediaID = state.full_data_block.user_data;  
         CDlgVideo *pDlgVideo = FindVideo(dwUserID, bMediaID, m_lsVideo);  
         if (pDlgVideo)  
         {  
             pDlgVideo->OnFullScreen();  
         }  
     }  
     else  
     {  
         VideoDlgState(&m_fullVideoItem);  
     }  
     RelayWindow(); 
     if (state.tab_area.data_block_vector.size() == 0)  
     {  
         return;  
     }  
   
     int index = 0;  
   
     FS_SDK::RoomWndState::DataBlock area;  
     for (auto &data : state.tab_area.data_block_vector)  
     {  
         if (index != state.tab_area.user_data)// user_data 表示激活的白板或者其他共享块  
         {  
             index++;  
             continue;  
         }  
   
         ActiveShareTab(data, index);  
         break;  
     }  
 }  

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

3.广播布局

void CMeetingLayoutDlg::BoardcastLayout()  
{  
    FS_SDK::LMeetingLayoutInfo layoutinfo;  
    FS_SDK::RoomWndState wndstate;  
  
    FS_SDK::RoomWndState::AreaData split_area;  
    for (auto& video : m_lsVideo)  
    {  
        FS_SDK::RoomWndState::DataBlock videodata;  
         DWORD dwUserID = video->GetVideoUserID();  
         BYTE  bMediaID = video->GetVideoMediaID();  
         videodata.data_id = video->GetVideoUserID();  
         videodata.user_data = video->GetVideoMediaID();  
         videodata.pos = m_VideoRelay.GetVideoPos( dwUserID, bMediaID);  
         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;  
   
     FS_SDK::RoomWndState::DataBlock singlefulldata;  
     singlefulldata.data_id = m_fullVideoItem.dwUserID;  
     singlefulldata.user_data = m_fullVideoItem.bMediaID;  
     singlefulldata.pos = 0;  
     if (singlefulldata.data_id != 0)  
     {  
         singlefulldata.pos = m_VideoRelay.GetVideoPos(m_fullVideoItem.dwUserID, m_fullVideoItem.bMediaID);  
     }  
     singlefulldata.data_type = FS_SDK::RoomWndState::DATA_TYPE_VIDEO;  
     wndstate.full_data_block = singlefulldata;  
   
     //FS_SDK::RoomWndState::AreaData full_area;  
     wndstate.tab_area.id = DEFAULT_TAB_AREA_ID;  
     wndstate.tab_area.style = FS_SDK::RoomWndState::AREA_LAYOUT_STYLE_TABLE;  
   
     LayoutCommon::LayoutToRoomWndstate(wndstate, m_VideoRelay.GetVideoRelayID());  
   
     layoutinfo.valid_full_mode = true;  
     layoutinfo.user = CSdkManager::GetInstance()->GetLocalUserID();  
   
     MeetingShareToolBar::GetInstance()->GetTabArea(wndstate.tab_area);  
   
     layoutinfo.wnd_state.push_back(wndstate);  
   
     FS_SDK::ErrCode hR = MeetingManager::GetInstance()->BroadcastLayout(layoutinfo);  
       
     if (FS_SDK::ERR_SUCCESS != hR)  
     {  
         g_pSuperLog->WriteFile(L"同步布局失败\n");  
     }  
 }

详情见demo。

角色权限


1.权限范围

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

2.申请主持人

ApplyToBeHost 申请主持人, 角色改变后会在UserCallback 回调中EV_USER_PRESENTER通知。

3.申请管理员

ApplyToBeHost 申请主持人, 角色改变后会在UserCallback 回调中EV_USER_ADMIN_APPLY通知。

录制

1.主要接口

  /*
	*  @brief
	*  @param
	*  @return 返回录制设置参数
	*/
	const FS_SDK::LRecordParam& getRecordParam();

	/*
	*  @brief 设置录制参数
	*  @param 录制设置参数
	*  @return
	*/
	FS_SDK::ErrCode setLocalRecordParam(FS_SDK::LRecordParam& recordParam);

	/*
	*  @brief 开启录制
	*  @param 录制路径
	*  @return
	*/
	FS_SDK::ErrCode startLocalRecord(FS_SDK::S_PWCHAR path);

	/*
	*  @brief 暂停继续录制
	*  @param true 暂停, false 继续
	*  @return
	*/
	FS_SDK::ErrCode pauseLocalRecord(FS_SDK::S_BOOL pause);

	/*
	*  @brief 停止录制
	*  @param 
	*  @return
	*/
	FS_SDK::ErrCode stopLocalRecord();

2.录制参数结构:

    struct LRecordParam
	{
		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 = PermissionManager::GetInstance()->CheckRoomPermission(FS_SDK::ROOM_LOCAL_RECORD);
	if (
		!canRecord
		&& ( !CommonRight::IsLocalManager() && !CommonRight::IsLocalPresenter() )
		)
	{
		uicontrol::MessageBoxWithOK(m_hWnd, L"没有权限录制会议!");
		return;
	}

	tstring path;
	CSdkManager::GetInstance()->GetConfig()->ReadRecordPath(path);
	// 获取录制参数
	auto recordParam = MeetingManager::GetInstance()->getRecordParam();

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

	if (FS_SDK::ERR_SUCCESS == hR)
	{
		UpdateRecordState(RECORDING);
	}
	else
	{
		UpdateRecordState(RECORDSTOP);
		uicontrol::MessageBoxWithOK(m_hWnd, L"录制会议失败!");
	}

	FS_SDK::ErrCode hR = MeetingManager::GetInstance()->pauseLocalRecord(pause);
	FS_SDK::ErrCode hR = MeetingManager::GetInstance()->stopLocalRecord();

详情见demo。

呼叫邀请

1.主要接口

/*
 *  @brief 获取组织架构信息
 *  @param refresh 是否刷新缓存数据
 *  @return 错误码
 */
ErrCode queryDeptInfo(S_BOOL refresh);

/*
 *  @brief 获取部门用户
 *  @param deptId 部门ID
 *  @return 错误码
 */
ErrCode queryDeptUser(S_UINT32 deptId);

/*
 *  @brief 邀请用户
 *  @param userList 受邀用户列表数组
 *  @param nSize 受邀用户数量
 *  @param pszMeetingName 会议名称(用于会前创建即时会议)
 *  @param pszMeetingPwd 会议密码(同上)
 *  @param pszAdminPwd 管理员密码(同上)
 *  @return 错误码
 */
ErrCode inviteUser(S_UINT32* userList, S_UINT32 nSize,
                           S_PWCHAR pszMeetingName = nullptr,
                           S_PWCHAR pszMeetingPwd = nullptr,
                           S_PWCHAR pszAdminPwd = nullptr);

/*
 *  @brief 接受/拒绝邀请
 *  @param accept 是否接受
 *  @return 错误码
 */
ErrCode acceptInvite(S_BOOL accept);

2.返回数据结构

//组织架构信息
EV_FRONT_DEPT_INFO
struct LDeptList
{
    struct DeptNode
    {
        S_UINT32 parent_id = 0;  //父部门id
        S_UINT32 id = 0;        //部门ID
        FString name;           //部门名称
        LVector<DeptNode> children;
    };

    DeptNode root;                 //根部门
    FS_UINT32 totalCount = 0;       //总数
    FS_UINT32 totalUserCount = 0;   //联系人总数
};
 
//部门用户列表
EV_FRONT_DEPT_USER
struct LUserInfo
{
    enum LContactUserState
    {
        USER_STATE_OFFLINE = 0,  //用户离线状态
        USER_STATE_MEETING,     //用户会议中状态
        USER_STATE_ONLINE       //用户在线状态
        };

    S_UINT32 id = 0;        //用户ID
    S_UINT32 deptId = 0;    //部门ID
    S_UINT32 sortId = 0;    //置顶排序ID,默认0没有置顶,值越大越前面
    FString name;           //用户名
    FString nickname;       //昵称
    FString depName;        //部门名称
    S_UINT32 typeFlag = 0;  //通讯录用户类型标识
    LContactUserState status = USER_STATE_OFFLINE;   //在线状态
};

using LUserList = LVector<LUserInfo>;
 
//用户在线状态变化
EV_FRONT_USER_STATUS
using LUserList = LVector<LUserInfo>;
  
//收到邀请
EV_FRONT_INVITE_INCOME
struct LInviteIncome
{
    enum LInviteType
    {
        INVITE_TYPE_AUDIO = 0,      // 音频
        INVITE_TYPE_VIDEO,         // 视频
        INVITE_TYPE_IN_MEETING,   // 会中邀请
    };

    S_BOOL      inviting = false;
    S_UINT32    inviteId = 0;
    S_UINT32    inviteCode = 0;        // 邀请码
    S_UINT64    roomCreateTime = 0;   // 会议室创建时间戳
    S_UINT32    roomCompanyId = 0;   // 会议室所属企业ID
    S_UINT8     isForce = 0;           // 强制邀请,预留后面会中也能接收邀请
    S_UINT32    proxyUserId = 0;       // 代理人,预留后面web会控代理发起邀请
    LInviteType inviteType = INVITE_TYPE_AUDIO; // 邀请类型<InviteType>
    S_UINT32    meetingId = 0;         // 即时会议id,用来通过boss获取邀请人列表
    S_UINT32    inviterUserId = 0;       // 邀请者ID
    FString       inviterUserName;
    FString       inviterTerminal;
};
      
//邀请被接受/拒绝
EV_FRONT_INVITE_ACCEPTED
struct LInviteResponse
{
    enum LRejectedReason
    {
        IRR_BYUSER = 0,      //用户操作拒绝
        IRR_REMOTE_TIMEOUT , //远端用户超时未处理
        IRR_LOCAL_TIMEOUT  //本地超时
        };

    S_UINT32 nInviteId = 0;
    S_UINT32 remoteUserId = 0;
    S_BOOL bAccepted = FALSE;
    LRejectedReason reason = IRR_LOCAL_TIMEOUT;
};

3.通讯录

成功登录会前在线后SDK立即请求组织架构、企业用户列表数据并保存在SDK层,应用层可调用 queryDeptInfo 获取组织架构,调用 queryDeptUser 获取对应部门用户,若此前SDK请求失败则重新发出请求,数据将以通知回调形式提供给应用层,从而驱动界面组织架构展示。

4.呼叫邀请

会前和会中均可调用 inviteUser 对通讯录在线用户发起邀请,会前发起邀请后SDK将首先创建即时会议并立即入会,然后将选择的用户邀请到该会议室。会中则直接将选择的用户邀请到当前会议室。
 
邀请被接受/拒绝则由SDK层响应后直接通知应用层。

邀请时序:

详情见demo。