Windows(PC) SDK 开发手册

网易云信服务概要

云信以提供客户端SDK(覆盖Android、iOS、Web、PC)和服务端OPEN API 的形式提供即时通讯云服务。开发者只需想好APP 的创意及UI 展现方案,根据APP 业务需求选择云信的相应功能接入即可。

注:SDK 兼容的系统有 Windows xp(sp2及以上)、Windows 7、Windows 8/8.1、Windows 10。SDK 从V3.2.5版本开始全面支持32位和64位程序接入。

开发准备

SDK内容

SDK不提供debug版本的动态链接库供开发者调试,如果遇到问题请联系技术支持或在线客服。

快速接入SDK

关于如何快速集成SDK到现有项目,初始化SDK登录退出,发送/接收第一条IM消息,发送/接收第一条聊天室消息,如何开始基于IM Demo源码开发的信息请前往Windows(PC) SDK Getting Started

SDK数据目录

当收到多媒体消息后,SDK 会负责下载这些多媒体文件,同时SDK 还要记录一些log,因此SDK 需要一个数据缓存目录。该目录由第三方App 通过nim_client_init 初始化接口传入,默认为存放到系统的AppData 目录下,App 传入一个目录名即可,SDK 会自动生成用户数据缓存目录。数据缓存目录默认为"{系统的AppData 目录}\{App 传入的目录名}\NIM\{某个用户对应的用户数据目录}”,还可以由App 完全自定义用户数据目录,需要传入完整的路径,并确保读写权限正确。如果第三方App 需要清除缓存功能,可扫描该目录下的文件,按照你们的规则清理即可。 具体某个用户对应的缓存目录下面包含如下子目录:

SDK 提供了接口nim_tool_get_user_specific_appdata_dir 获取某个用户对应的具体类型的App data 目录(如图片消息文件存放目录,语音消息文件存放目录等)(注意:需要调用nim_free_buf 接口释放其返回的内存)。

接口调用与约定

SDK 提供的所有API 都是 C接口 ,根据模块划分为不同的API 文件,根据业务需要可以显式或者隐式的调用(3.4.0开始)。显式调用下,开发者可以在程序进行到任何时间点按需加载SDK dll, 功能完成后如果不需要继续使用NIM可以卸载SDK dll, 更加灵活和节省内存, 该模式下开发者只需要将SDK包里nim_c_sdk\include 目录下的模块定义头文件(命名方式如nim_xxx_def.h)加入到工程项目中。隐式调用下,开发者除了链接lib文件外,需要将SDK包里nim_c_sdk 目录里的API 头文件(命名方式如nim_xxx.h)以及相关常量定义的头文件等其他头文件一并加入到工程项目中。一般来说,每个模块都有对应的API 头文件和相关常量的定义头文件,命名上一一对应。

SDK 提供了3种类型的接口。

  1. 全局广播类通知的注册接口。

    常见的几类消息或通知是通过该注册的回调函数中通知开发者的,比如收到的消息,会话数据更新,用户相关数据的变更等,开发者需要在登录前提前注册好这些接口,例如:

    C++

     //注册数据同步结果的回调
     nim::DataSync::RegCompleteCb(nbase::Bind(&nim_comp::DataSyncCallback::SyncCallback, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    
     /* 以下注册的回调函数,都是在收到服务器推送的消息或事件时执行的。因此需要在程序开始时就注册好。 */
     //注册重连、被踢、掉线、多点登录、把移动端踢下线的回调
     nim::Client::RegReloginCb(&nim_comp::LoginCallback::OnReLoginCallback);
     nim::Client::RegKickoutCb(&nim_comp::LoginCallback::OnKickoutCallback);
     nim::Client::RegDisconnectCb(&nim_comp::LoginCallback::OnDisconnectCallback);
     nim::Client::RegMultispotLoginCb(&nim_comp::LoginCallback::OnMultispotLoginCallback);
     nim::Client::RegKickOtherClientCb(&nim_comp::LoginCallback::OnKickoutOtherClientCallback);
     nim::Client::RegSyncMultiportPushConfigCb(&nim_comp::MultiportPushCallback::OnMultiportPushConfigChange);
    
     //注册返回发送消息结果的回调,和收到消息的回调。
     nim::Talk::RegSendMsgCb(nbase::Bind(&nim_comp::TalkCallback::OnSendMsgCallback, std::placeholders::_1));
     nim::Talk::RegReceiveCb(nbase::Bind(&nim_comp::TalkCallback::OnReceiveMsgCallback, std::placeholders::_1));
     nim::Talk::RegRecallMsgsCallback(nbase::Bind(&nim_comp::TalkCallback::OnReceiveRecallMsgCallback, std::placeholders::_1, std::placeholders::_2));
     nim::Talk::RegReceiveMessagesCb(nbase::Bind(&nim_comp::TalkCallback::OnReceiveMsgsCallback, std::placeholders::_1));
     nim::MsgLog::RegMessageStatusChangedCb(nbase::Bind(&nim_comp::TalkCallback::OnMsgStatusChangedCallback, std::placeholders::_1));

    C#

    //注册数据同步结果的回调
    NIM.DataSync.RegCompleteCb(DataSyncDelegate cb);
    //登录结果通知,开发者可以通过注册该事件获取登录结果,或者在调用登录接口NIM.ClientAPI.Login 时填充回调函数
    NIM.ClientAPI.LoginResultHandler
    //注册客户端掉线回调
    NIM.ClientAPI.RegDisconnectedCb(Action handler);
    //注册客户端多点登录通知回调
    NIM.ClientAPI.RegMultiSpotLoginNotifyCb(MultiSpotLoginNotifyResultHandler handler);
    //注册将登录当前账号的其他端踢下线的结果回调
    NIM.ClientAPI.RegKickOtherClientCb(KickOtherClientResultHandler handler);
    //注册当前客户端被踢下线通知回调
    NIM.ClientAPI.RegKickoutCb(KickoutResultHandler handler)
    //注册自动重连结果回调
    NIM.ClientAPI.RegAutoReloginCb(LoginResultDelegate handler, string jsonExtension = null)

     //接收消息
    NIM.TalkAPI.OnReceiveMessageHandler += ReceiveMessageHandler;
    //发送消息结果
    NIM.TalkAPI.OnSendMessageCompleted += SendMessageResultHandler;
    //注册消息撤回结果回调
    NIM.TalkAPI.RegRecallMessageCallback(RecallMessageDelegate cb);

C

    // 数据同步结果通知(nim_data_sync)
    void nim_data_sync_reg_complete_cb(nim_data_sync_cb_func cb, const void *user_data);        
    // 接收会话消息通知(nim_talk)
    void nim_talk_reg_receive_cb(const char *json_extension, nim_talk_receive_cb_func cb, const void *user_data);        
    // 接收系统消息通知(nim_sysmsg)
    void nim_sysmsg_reg_sysmsg_cb(const char *json_extension, nim_sysmsg_receive_cb_func cb, const void *user_data);    
    // 发送消息结果通知(nim_talk)
    void nim_talk_reg_arc_cb(const char *json_extension, nim_talk_arc_cb_func cb, const void *user_data);
    // 发送自定义系统通知的结果通知(nim_talk)
    void nim_talk_reg_custom_sysmsg_arc_cb(const char *json_extension, nim_custom_sysmsg_arc_cb_func cb, const void *user_data);
    // 群组事件通知(nim_team)
    void nim_team_reg_team_event_cb(const char *json_extension, nim_team_event_cb_func cb, const void *user_data);    
    // 帐号被踢通知(nim_client)
    void nim_client_reg_kickout_cb(const char *json_extension, nim_json_transport_cb_func cb, const void *user_data);        
    // 网络连接断开通知(nim_client)
    void nim_client_reg_disconnect_cb(const char *json_extension, nim_json_transport_cb_func cb, const void *user_data);    
    // 将本帐号的其他端踢下线的结果通知(nim_client)
    void nim_client_reg_kickout_other_client_cb(const char *json_extension, nim_json_transport_cb_func cb, const void *user_data); 
    // 好友通知 
    void nim_friend_reg_changed_cb(const char *json_extension, nim_friend_change_cb_func cb, const void *user_data);    
    // 用户特殊关系通知
    void nim_user_reg_special_relationship_changed_cb(const char *json_extension, nim_user_special_relationship_change_cb_func cb, const void *user_data);    
    ...
  1. 异步接口。

    回调函数作为参数,传入执行接口,然后执行接口时,会触发传入的回调函数。注意,回调函数中如果涉及到跨线程资源的需要开发者切换线程操作,即使不涉及到资源问题,也要保证回调函数中不会处理耗时任务,以免堵塞SDK底层线程。

  2. 同步接口。

    为方便开发者使用,我们提供了一些同步接口,调用接口获取的内容同步返回,以block命名的都是同步接口,该类会堵塞SDK线程,谨慎使用。

从版本2.7.0开始,服务器和客户端上线了频控策略,与服务器有交互的接口(接口命名结尾为_online的查询接口以及命令类接口,如同意群邀请等)增加频控控制,开发者关注错误码416。

C++ SDK

为了方便桌面应用开发者更快的接入云信SDK,PC SDK下载包中还提供了官方的C++ 封装层 项目文件及源码,接入和使用方法请看Windows(PC) SDK开发手册(C++ 封装层),目前IM Demo(C++)源码就是通过接入和调用该SDK完成IM功能的。

C# SDK

云信SDK还提供了C# 程序集,方便.net 开发人员接入,PC SDK下载包中包括官方的C# 封装层 项目文件及源码,接入和使用方法请看Windows(PC) SDK开发手册(C# 封装层),目前IM Demo(C#)源码就是通过接入和调用该SDK完成IM功能的。

如果开发者在调用C接口过程中或者解析接口返回结果过程中出现疑问,可以参考和借鉴C++封装层。

初始化SDK

准备工作:将SDK 相关的dll 文件(nim.dll,nim_audio.dll, nim_tools_http.dll, nrtc.dll)放到App 的运行目录下,并将SDK 的配置文件目录nim_conf(目录里包含一个ver_ctrl.dll文件)放到App 的运行目录下。SDK 基于vs2010 开发,如果App 没有对应的运行时库文件(msvcp100.dll和msvcr100.dll),请将其放到App 的运行目录下。

准备工作完成后,在程序启动时,如果直接使用C接口,需要调用LoadLibrary 函数动态加载nim.dll,然后调用GetProcAddress获取API 接口:nim_client_init,调用此接口初始化NIM SDK。同时,SDK 能力的一些参数以及如果SDK 需要连接独立部署的服务器的地址等配置也是在初始化SDK 时传入。例如:

C++

nim::SDKConfig config;

//组装SDK能力参数(必填)
config.database_encrypt_key_ = "Netease";     //string(db key必填,目前只支持最多32个字符的加密密钥!建议使用32个字符)
config.sdk_log_level_ = ;                     //bool 选填,是否需要预下载附件(图片和语音),SDK默认预下载,如果有对带宽流量有较高要求的请关闭该选项,改为上层开发者按需下载附件文件
config.preload_image_quality_ = ;             //int 预下载图片质量,选填,范围0-100
config.preload_image_resize_ = ;             //string 预下载图片基于长宽做内缩略,选填,比如宽100高50,则赋值为100x50,中间为字母小写x
config.sync_session_ack_ = true;             //bool 选填,消息已读未读是否多端同步,默认true
config.login_max_retry_times_ = ;             //int 登录重试最大次数,如需设置建议设置大于3次,默认填0,SDK默认设置次数

//组装SDK独立部署的服务器配置(选填)
config.use_private_server_ = true;
config.rsa_public_key_module_ = "http://xxx";
config.default_nos_download_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");
config.lbs_address_ = "http://xxx";
config.nos_lbs_address_ = "http://xxx";
config.default_link_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");
config.default_nos_upload_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");
config.default_nos_access_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");

bool ret = nim::Client::Init("45c6af3c98409b18a84451215d0bdd6e", "Netease", "", config); // 载入云信sdk,初始化安装目录和用户目录,第一个参数是appkey(此处填写的是测试示例)

C#

C# 程序在运行时需要使用到 C++ SDK 的dll,必须将SDK相关的dll 文件(nim.dll,nim\_audio.dll, nim\_tools\_http.dll, nrtc.dll)放到最终的生成文件目录,如果缺少运行时库文件(msvcp100.dll和msvcr100.dll)也需要拷贝到生成目录中,否则可能会出现找不到dll的异常。

C# 提供了参数配置类 NimUtility.NimConfig,通过创建该类对象完成参数配置。
var config = new NimUtility.NimConfig();
config.CommonSetting = new SdkCommonSetting();

//组装SDK能力参数
config.CommonSetting.DataBaseEncryptKey = "Netease"; //string(db key必填,目前只支持最多32个字符的加密密钥!建议使用32个字符)
config.CommonSetting.LogLevel = SdkLogLevel.Pro;  //SDK 日志记录级别 
config.CommonSetting.PredownloadAttachmentThumbnail = True;   //bool 选填,是否需要预下载附件(图片和语音),SDK默认预下载,如果有对带宽流量有较高要求的请关闭该选项,改为上层开发者按需下载附件文件
config.CommonSetting.UsePriviteServer = False;    //是否使用自定义服务器,默认False,如果使用自定义服务器,需设置config.PrivateServerSetting 对象

//组装SDK独立部署的服务器配置
config.PrivateServerSetting = new SdkPrivateServerSetting();
config.PrivateServerSetting.RSAPublicKey = "http://xxx";
config.PrivateServerSetting.DownloadServerList = new List<string>({"",""});
config.PrivateServerSetting.LbsAddress = "http://xxx";
config.PrivateServerSetting.NOSLbsAddress = "http://xxx";
config.PrivateServerSetting.LinkServerList = new List<string>({"",""});
config.PrivateServerSetting.UploadServerList = new List<string>({"",""});
config.PrivateServerSetting.AccessServerList = new List<string>({"",""});

bool ret = NIM.ClientAPI.Init("45c6af3c98409b18a84451215d0bdd6e", "NIMCSharpDemo", null, config); // 载入云信sdk,初始化安装目录和用户目录,第一个参数是appkey(此处填写的是测试示例)

C

typedef bool(*nim_client_init)(const char *app_data_dir, const char *app_install_dir, const char *json_extension);

void foo()
{
    //获取SDK初始化接口
    HINSTANCE hInst = LoadLibraryW(L"nim.dll");
    nim_client_init func = (nim_client_init) GetProcAddress(hInst, "nim_client_init");

    //组装SDK初始化参数
    Json::Value config_root;

    //组装SDK能力参数(必填)
    Json::Value config_values;
    config_values[kNIMAppKey] = "xxxx"; //用户的APP key
    config_values[kNIMDataBaseEncryptKey] = ""; //string(db key必填,目前只支持最多32个字符的加密密钥!建议使用32个字符)
    config_values[kNIMPreloadAttach] = ;        //bool 选填,是否需要预下载附件(图片和语音),SDK默认预下载,如果有对带宽流量有较高要求的请关闭该选项,改为上层开发者按需下载附件文件
    config_values[kNIMPreloadImageQuality] = ;  //int 预下载图片质量,选填,范围0-100
    config_values[kNIMPreloadImageResize] = ;   //string 预下载图片基于长宽做内缩略,选填,比如宽100高50,则赋值为100x50,中间为字母小写x
    config_values[nim::kNIMSyncSessionAck] = ;            //bool 选填,消息已读未读是否多端同步,默认true
    config_values[nim::kNIMLoginRetryMaxTimes] = ;        //int 登录重试最大次数,如需设置建议设置大于3次,默认填0,SDK默认设置次数
    config_root[kNIMGlobalConfig] = config_values;

    //组装SDK独立部署的服务器配置(选填)
    Json::Value server_values;
    server_values[kNIMLbsAddress] = "http://xxx";             //lbs地址
    server_values[kNIMNosLbsAddress] = "http://xxx";        //nos lbs地址
    server_values[kNIMDefaultLinkAddress].append("xxx.xxx.xxx.xxx:xxxx");                //默认的link地址
    server_values[kNIMDefaultNosUploadAddress].append("http://xxx.xxx.xxx.xxx:xxxx");     //默认的nos上传地址
    server_values[kNIMDefaultNosDownloadAddress].append("http://xxx.xxx.xxx.xxx:xxxx");    //默认的nos下载地址
    server_values[kNIMRsaPublicKeyModule] = "";                //密钥
    server_values[kNIMRsaVersion] = 0;                        //密钥版本号      
    config_root[kNIMPrivateServerSetting] = server_values;
    config_root[kNIMAppKey] = "45c6af3c98409b18a84451215d0bdd6e"; //必填appkey(此处填写的是测试示例)

    //初始化SDK
    func("appdata path", "app installation path", config_root.toStyledString().c_str());
}

在程序退出前,调用接口nim_client_cleanup 进行NIM SDK 的清理工作,然后调用FreeLibrary 函数释放nim.dll,对于清理工作的注意事项请查看后续的"登出/退出和清理SDK"章节。

C++

nim::Client::Cleanup();

C#

NIM.ClientAPI.Cleanup();

C

typedef    void(*nim_client_cleanup)(const char *json_extension);

void foo()
{
    nim_client_cleanup func = (nim_client_cleanup) GetProcAddress(hInst, "nim_client_cleanup");
    func("");
    FreeLibrary(hInst);
}

登录与登出

登录集成必读

客户端代理

SDK目前支持配置代理,支持Socket4/5,暂不支持Http代理,音视频模块只支持Socket5代理。

C++

void foo()
{
    nim::Global::SetProxy(nim::kNIMProxySocks5, "123.123.123.123", "400", "test", "test");
}

C#

void foo()
{
    NIM.GlobalAPI.SetProxy(NIM.NIMProxyType.kNIMProxySocks5, "123.123.123.123", "400", "test", "test");
}

C

typedef void(*nim_global_set_proxy)(NIMProxyType, const char*, int, const char*, const char*);

void foo()
{
    nim_global_set_proxy func = (nim_global_set_proxy) GetProcAddress(hInst, "nim_global_set_proxy");
    func(kNIMProxySocks5, "123.123.123.123", "400", "test", "test");
}

首次登录

SDK 登录后会同步群信息,离线消息,漫游消息,系统通知等数据,所以首次登录前需要 提前注册所需的全局广播类通知的回调 (具体说明请参阅API 文档),以下以注册最近会话变更通知回调为例:

C++

void OnSessionChangeCallback(nim::NIMResCode rescode, const nim::SessionData& data, int total_unread_counts)
{
    if (rescode != nim::kNIMResSuccess)
    {
        return;
    }

    switch (data.command_)
    {
    case nim::kNIMSessionCommandAdd:
    case nim::kNIMSessionCommandUpdate:
    case nim::kNIMSessionCommandMsgDeleted:
    break;
    ···
    }
}

void foo()
{
    //注册全局会话列表变更通知函数
    nim::Session::RegChangeCb(&OnSessionChangeCallback);
}

C#

C#通过订阅会话列表变更事件来获取更新通知:
NIM.Session.SessionAPI.RecentSessionChangedHandler += OnSessionChanged;

C

//全局会话列表变更通知函数
void CallbackSessionChange(int rescode, const char *result, int total_unread_counts, const char *json_extension, const void *user_data)
{
    if (rescode == kNIMResSuccess)
    {
        Json::Value value;
        Json::Reader reader;
        if (reader.parse(result, value))
        { 
            nim::NIMSessionCommand command = nim::NIMSessionCommand(value[nim::kNIMSessionCommand].asInt());
            switch (command)
            {
                case kNIMSessionCommandAdd:
                    ...
                case kNIMSessionCommandUpdate:    
                    ...
                ...                
            }

        }
    }
    else
    {
        ...
    }
}

typedef void(*nim_session_reg_change_cb)(const char *json_extension, nim_session_change_cb_func cb, const void *user_data);

void foo()
{
    //注册全局会话列表变更通知函数
    nim_session_reg_change_cb func = (nim_session_reg_change_cb)GetProcAddress(hInst, "nim_session_reg_change_cb");
    func(nullptr, &CallbackSessionChange, nullptr);    // 会话列表变更通知(nim_session)
}

登录

C++

void OnLoginCallback(const nim::LoginRes& login_res, const void* user_data)
{
    if (login_res.res_code_ == nim::kNIMResSuccess)
    {
        if (login_res.login_step_ == nim::kNIMLoginStepLogin)
        {
            ···
        }
    }
    else
    {
        ···
    }
}

void foo()
{
    nim::Client::Login(app_key, "app acount", "token", &OnLoginCallback);
}

C#

private void HandleLoginResult(NIM.NIMLoginResult result)
{
    //在UI线程进行处理登录结果
    Action action = () =>
    {
        if (result.LoginStep == NIM.NIMLoginStep.kNIMLoginStepLogin)
        {
            if (result.Code == NIM.ResponseCode.kNIMResSuccess)
            {
                //TODO:登录成功
            }
            else
            {
                //TODO:登录失败
            }
        }
    };
    this.Invoke(action);
}

private void foo()
{
    NIM.ClientAPI.Login(appkey, "app acount", "token", HandleLoginResult);
}

C

void CallbackLogin(const char* res, const void *user_data)
{
    Json::Value values;
    Json::Reader reader;
    if (reader.parse(res, values) && values.isObject())
    {
        if (values[kNIMLoginStep].asInt() == kNIMLoginStepLogin && values[kNIMErrorCode].asInt() == 200)
        {
            ...
        }
        ...
    }
}

typedef    void(*nim_client_login)(const char *app_key, const char *account, const char *password, const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_login func = (nim_client_login) GetProcAddress(hInst, "nim_client_login");
    func("app key", "app account", "token", nullptr, &CallbackLogin, nullptr);
    //app key: 应用标识,不同应用之间的数据(用户,消息,群组等)是完全隔离的。开发自己的应用时,需要替换成自己申请来的app key
    //注意:替换成客户自己的app key之后,请不要对登录密码进行md5加密。
}

自动重新登录

SDK 在网络连接断开后,会监听网络状况,如果网络状况好转,那么SDK 会进行重新登录,登录结果可以在注册的重新登录回调函数中处理。

注意 自动重新登录的机制可能会失效:

如果重新登录回调函数返回的的错误号既不是kNIMResSuccess,也不是网络错误相关错误号(kNIMResTimeoutError或者kNIMResConnectionError),那么说明自动重新登录的机制已经失效,需要开发者退回到登录界面进行手动重新登录。(详见nim_client_reg_auto_relogin_cb接口说明)

手动重新登录

SDK 在重新登录失败后,可以由用户手动调用重新登录接口nim_client_relogin。

登出/退出和清理SDK

执行接口nim_client_logout 进行登出或退出,登出或退出的过程因为涉及到和服务器的交互以及需要将缓存中的数据持久化到本地,因此根据实际情况回调通知会有1s到20s左右的延迟。清理SDK必须在完全退出(收到退出回调)后执行。

Trick:因为退出和其他接口一样,总是在某些情况下会出现未及时返回需要上层界面等待的情况(比如网络原因导致服务器返回慢甚至超时,缓存数据大持久化到本地慢等原因),因此开发者可以通过暂时隐藏UI,让进程等待退出完成的回调后再执行清理SDK,退出程序的工作。

C++

void LoginCallback::OnLogoutCallback(nim::NIMResCode res_code)
{
    //如果是退出程序
    nim::Client::Cleanup();
    ...

    //如果是登出到登录界面,这里不能执行清理SDK工作,否则会报错
    ...
}

void foo()
{
    //登出到登录界面(nim::kNIMLogoutChangeAccout) 或者 退出程序(nim::kNIMLogoutAppExit)
    nim::Client::Logout(nim::kNIMLogoutAppExit, &OnLogoutCallback);
}

C#

private void foo()
{
    System.Threading.Semaphore s = new System.Threading.Semaphore(0, 1);
    NIM.ClientAPI.Logout(NIM.NIMLogoutType.kNIMLogoutAppExit, (r) =>
    {
        s.Release();
    });
    //需要logout执行完才能退出程序
    s.WaitOne(TimeSpan.FromSeconds(10));
    NIM.ClientAPI.Cleanup();
}

C

void CallbackLogout(const char* res, const void *user_data)
{
    ...
}

typedef    void(*nim_client_logout)(nim::NIMLogoutType logout_type, const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_logout func = (nim_client_logout) GetProcAddress(hInst, "nim_client_logout");
    func(kNIMLogoutAppExit, nullptr, &CallbackLogout, nullptr);
}

断网重连通知

通过接口nim_client_reg_disconnect_cb 来注册连接断开回调函数,断开连接后根据实际需要做后续处理(比如提示用户)。

C++

void OnDisconnectCallback()
{
    ···
}

void foo()
{
    nim::Client::RegDisconnectCb(&OnDisconnectCallback);
}

C#

private foo()
{
    NIM.ClientAPI.RegDisconnectedCb(() =>
    {
         MessageBox.Show("网络连接断开,网络恢复后后会自动重连");
    });
}

C

void CallbackDisconnect(const char* res, const void* user_data)
{
    //解析res
}

typedef void(*nim_client_reg_disconnect_cb)(const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_reg_disconnect_cb func = (nim_client_reg_disconnect_cb) GetProcAddress(hInst, "nim_client_reg_disconnect_cb");
    func(nullptr, &CallbackDisconnect, nullptr);
}

被踢出通知

通过接口nim_client_reg_kickout_cb 来注册被踢出回调函数,当收到此通知时,一般是退出程序然后回到登录窗口,回调函数参数中详细说明了被踢出的原因。

注意:聊天室被踢出通知不是通过这个接口监听的,具体请看下文 聊天室开发说明

C++

void OnKickoutCallback(const nim::KickoutRes& res)
{
    ···
}

void foo()
{
    nim::Client::RegKickoutCb(&OnKickoutCallback);
}

C#

private foo()
{
    NIM.ClientAPI.RegKickoutCb((r) =>
    {
        MessageBox.Show(r.Dump(), "被踢下线,请注意账号安全");
    });
}

C

void CallbackKickout(const char* res, const void* user_data)
{
    //解析res
}

typedef void(*nim_client_reg_kickout_cb)(const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_reg_kickout_cb func = (nim_client_reg_kickout_cb) GetProcAddress(hInst, "nim_client_reg_kickout_cb");
    func(nullptr, &CallbackKickout, nullptr);
}

多端登录

通过调用nim_client_reg_multispot_login_notify_cb 来注册多端登录通知的回调函数,当用户在某个客户端登录时,其他没有被踢掉的端会触发这个回调函数,并携带当前时间登录的设备列表的数据。

云信内置踢人策略为:移动端(Android,iOS)互踢,桌面端(PC,Web)互踢,移动端和桌面端共存。

如果当前的互踢策略无法满足业务需求的话,可以联系我们取消内置互踢,根据多端登录的回调和当前的设备列表,判断本设备是否需要被踢出。如果需要踢出,直接调用登出接口并在界面上给出相关提示即可。

注意:聊天室多端登录相关的通知不是通过这个接口监听的,具体请看下文 聊天室开发说明

云信 ID

云信的每个用户由云信 ID 唯一标识。云信 ID是由用户帐号和token成功同步到云信服务器后自动分配的,可以通过 NIMMessage 的 from 字段获取消息发送方的云信 ID。

基础消息功能

消息功能概述

SDK(PC) 支持文本、图片、音频、视频、地理位置、通知消息、提醒消息、文件消息和自定义消息等多种种类型消息。

发送消息

以发送图片消息的为例:.

C++

//代码摘抄自DEMO源码
void SessionBox::SendImage( const std::wstring &src )
{
    nim::IMMessage msg;
    PackageMsg(msg);
    msg.type_ = nim::kNIMMessageTypeImage;

    std::wstring filename = nbase::UTF8ToUTF16(msg.client_msg_id_);
    std::wstring dest = GetUserImagePath() + filename;
    GenerateUploadImage(src, dest);
    msg.local_res_path_ = nbase::UTF16ToUTF8(dest);

    nim::IMImage img;
    img.md5_ = GetFileMD5(dest);
    img.size_ = (long)nbase::GetFileSize(dest);

    Gdiplus::Image image(dest.c_str());
    if (image.GetLastStatus() != Gdiplus::Ok)
    {
      assert(0);
    }
    else
    {
      img.width_ = image.GetWidth();
      img.height_ = image.GetHeight();
    }

    msg.attach_ = img.ToJsonString();

    std::string json_msg = nim::Talk::CreateImageMessage(msg.receiver_accid_, msg.session_type_, msg.client_msg_id_, img, msg.local_res_path_, msg.timetag_);
    nim::Talk::SendMsg(json_msg);
}

C#

void SendImage(string path)
{
    string fileName = System.IO.Path.GetFileNameWithoutExtension(path);
    string extension = System.IO.Path.GetExtension(path);

    NIM.NIMImageMessage imageMsg = new NIMImageMessage();
    imageMsg.LocalFilePath = path;
    imageMsg.ImageAttachment = new NIMImageAttachment();
    imageMsg.ImageAttachment.DisplayName = fileName;
    imageMsg.ImageAttachment.FileExtension = extension;
    imageMsg.ReceiverID = _peerId;
    imageMsg.SessionType = _sessionType;
    using (var i = Image.FromFile(path))
    {
        imageMsg.ImageAttachment.Height = i.Height;
        imageMsg.ImageAttachment.Width = i.Width;
    }
    TalkAPI.SendMessage(imageMsg);
}

C

typedef void(*nim_talk_send_msg)(const char* json_msg, const char *json_extension, nim_nos_upload_prg_cb_func prg_cb, const void *prg_user_data);

void foo()
{
    Json::Value json;
    json[kNIMMsgKeyToType]            = ; //int 会话类型(NIMSessionType 个人0 群组1)
    json[kNIMMsgKeyToAccount]        = ; //string 消息接收者id
    json[kNIMMsgKeyTime]            = ; //int64  消息发送时间(毫秒),可选
    json[kNIMMsgKeyType]            = ; //NIMMessageType 消息类型
    json[kNIMMsgKeyBody]            = ; //string 消息内容
    json[kNIMMsgKeyAttach]            = ; //string 附加内容
    json[kNIMMsgKeyClientMsgid]        = ; //string 消息id,一般使用guid
    json[kNIMMsgKeyResendFlag]        = ; //int    重发标记(0不是重发, 1是重发)
    json[kNIMMsgKeyLocalLogStatus]    = ; //NIMMsgLogStatus 消息状态
    json[kNIMMsgKeyType]             = kNIMMessageTypeImage;

    //图片详细信息
    Json::Value attach;
    attach[kNIMImgMsgKeyMd5] = ;      //string 文件md5,选填
    attach[kNIMImgMsgKeySize] = ;     //long   文件大小,选填
    attach[kNIMImgMsgKeyWidth] = ;  //int     图片宽度,必填
    attach[kNIMImgMsgKeyHeight] = ; //int     图片高度,必填
    json[kNIMMsgKeyAttach] = attach.toStyledString();

    json[kNIMMsgKeyLocalFilePath]    = ; //string    图片本地路径
    json[kNIMMsgKeyLocalLogStatus] = kNIMMsgLogStatusSending;

    nim_talk_send_msg func = (nim_talk_send_msg) GetProcAddress(hInst, "nim_talk_send_msg");
    func(json.toStyledString().c_str(), "", NULL, "");
}

发送消息状态通过登录前注册的全局回调获取。

C++

void OnSendMsgCallback(const nim::SendMessageArc& arc)
{
    ···
}


void foo()
{
    nim::Talk::RegSendMsgCb(&OnSendMsgCallback);
}

C#

void SendMessageResultHandler(object sender, MessageArcEventArgs args)
{
    if(args.ArcInfo.Response == ResponseCode.kNIMResSuccess)
    {
        ···
    }
    if (args.ArcInfo.Response == ResponseCode.kNIMResSuccess)
        return;

    Action action = () =>
    {
        MessageBox.Show(args.Dump(), "发送失败");
    };
    this.Invoke(action);
}

private void foo()
{
    NIM.TalkAPI.OnSendMessageCompleted += SendMessageResultHandler;
}

C

void CallbackSendMsgResult(const char* result, const void *user_data)
{
    //解析result {"msg_id" : "" , "talk_id" : "" , "rescode" : ""}
}

typedef void(*nim_talk_reg_arc_cb)(const char *json_extension, nim_talk_arc_cb_func cb, const void *user_data);

void foo()
{
    nim_talk_reg_arc_cb func = (nim_talk_reg_arc_cb) GetProcAddress(hInst, "nim_talk_reg_arc_cb");
    func(nullptr, &CallbackSendMsgResult, nullptr);
}

如果要停止正在发送中的消息(目前只支持发送文件消息时的终止),可以调用nim_talk_stop_send_msg 接口实现。

此外,发送消息还提供了以下消息属性设置:

针对群组消息,提供强推属性设置(可以用于实现@功能等):

消息多端推送开关

C++

void OnMultiportPushConfigChange(int rescode, bool switch_on)
{
    if (rescode == nim::kNIMResSuccess)
    {
        ···
    }
}

void OnInit()
{
    //登录前注册的全局回调获取当前多端推送开关状态
    nim::Client::RegSyncMultiportPushConfigCb(&OnMultiportPushConfigChange);
}

void SetMultiportConfig(bool switch_on)
{
    //当PC/Web端在线时,可以配置消息是否推送给移动端
    nim::Client::SetMultiportPushConfigAsync(switch_on, &OnMultiportPushConfigChange);
}

void GetMultiportConfig()
{
    //主动获取当前多端推送开关状态
    nim::Client::GetMultiportPushConfigAsync(&OnMultiportPushConfigChange);
}

C#

public delegate void ConfigMultiportPushDelegate(ResponseCode response,ConfigMultiportPushParam param)
{
     if (response == ResponseCode.kNIMResSuccess)
    {
        ···
    }
}

/// <summary>
/// 开启多端推送
/// </summary>
/// <param name="cb">操作结果委托</param>
public static void EnableMultiportPush(ConfigMultiportPushDelegate cb)
{
    ConfigMultiportPushParam param = new ConfigMultiportPushParam();
    param.Enabled = true;
    var ptr = DelegateConverter.ConvertToIntPtr(cb);
    ClientNativeMethods.nim_client_set_multiport_push_config(param.Serialize(), null, ConfigMultiportPushCb, ptr);
}

/// <summary>
/// 禁止多端推送
/// </summary>
/// <param name="cb">操作结果委托</param>
public static void DisableMultiportPush(ConfigMultiportPushDelegate cb)
{
    ConfigMultiportPushParam param = new ConfigMultiportPushParam();
    param.Enabled = false;
    var ptr = DelegateConverter.ConvertToIntPtr(cb);
    ClientNativeMethods.nim_client_set_multiport_push_config(param.Serialize(), null, ConfigMultiportPushCb, ptr);
}

/// <summary>
/// 获取多端推送控制开关
/// </summary>
/// <param name="cb"></param>
public static void IsMultiportPushEnabled(ConfigMultiportPushDelegate cb)
{
    var ptr = DelegateConverter.ConvertToIntPtr(cb);
    ClientNativeMethods.nim_client_get_multiport_push_config(null, ConfigMultiportPushCb, ptr);
}

/// <summary>
/// 注册多端推送设置同步回调
/// </summary>
/// <param name="cb"></param>
public static void RegMulitiportPushEnableChangedCb(ConfigMultiportPushDelegate cb)
{
    var ptr = DelegateConverter.ConvertToIntPtr(cb);
    ClientNativeMethods.nim_client_reg_sync_multiport_push_config_cb(null, OnMultiportPushEnableChanged, ptr);
}

C

static void CallbackMultiportPushConfig(int rescode, const char *content, const char *json_extension, const void *user_data)
{
    Json::Value values;
    Json::Reader reader;
    if (rescode == nim::kNIMResSuccess && reader.parse(content, values) && values.isObject())
    {
        bool open = values[kNIMMultiportPushConfigContentKeyOpen].asInt() == 1;
        ···
    }
}

typedef void(*nim_client_reg_sync_multiport_push_config_cb)(const char *json_extension, nim_client_multiport_push_config_cb_func cb, const void *user_data);
typedef void(*nim_client_set_multiport_push_config)(const char *switch_content, const char *json_extension, nim_client_multiport_push_config_cb_func cb, const void *user_data);
typedef void(*nim_client_get_multiport_push_config)(const char *json_extension, nim_client_multiport_push_config_cb_func cb, const void *user_data);

void OnInit()
{
    //登录前注册的全局回调获取当前多端推送开关状态
    nim_client_reg_sync_multiport_push_config_cb func = (nim_client_reg_sync_multiport_push_config_cb) GetProcAddress(hInst, "nim_client_reg_sync_multiport_push_config_cb");
    func("", &CallbackMultiportPushConfig, nullptr);
}

void SetMultiportConfig(bool switch_on)
{
    //当PC/Web端在线时,可以配置消息是否推送给移动端
    nim_client_set_multiport_push_config func = (nim_client_set_multiport_push_config) GetProcAddress(hInst, "nim_client_set_multiport_push_config");
    func(switch_on, "", &CallbackMultiportPushConfig, nullptr);
}

void GetMultiportConfig()
{
    //主动获取当前多端推送开关状态
    nim_client_get_multiport_push_config func = (nim_client_get_multiport_push_config) GetProcAddress(hInst, "nim_client_get_multiport_push_config");
    func("", &CallbackMultiportPushConfig, nullptr);
}

接收消息

提前注册好接收消息的回调函数。在接收到消息时,如果是图片、语音消息,那么SDK 会自动下载,然后通过注册的NOS回调函数通知。如果下载失败,则调用接口nim_nos_download_media 重新下载。此外,还可以调用nim_nos_stop_download_media 停止下载(目前仅对文件消息类型有效)。

C++

void OnReceiveMsgCallback(const nim::IMMessage& message)
{
    std::string id = GetSessionId(message);

    if (message.feature_ == nim::kNIMMessageFeatureDefault)
    {
        ···
    }
    else if (message.feature_ == nim::kNIMMessageFeatureSyncMsg || message.feature_ == nim::kNIMMessageFeatureRoamMsg)
    {
        ···
    }
    else if (message.feature_ == nim::kNIMMessageFeatureCustomizedMsg)
    {
        ···
    }
}

void foo()
{
    nim::Talk::RegReceiveCb(&OnReceiveMsgCallback);
}

C#

void OnReceiveMessage(object sender, NIM.NIMReceiveMessageEventArgs args)
{
    if (args.Message.MessageContent.SessionType == NIM.Session.NIMSessionType.kNIMSessionTypeP2P && args.Message.MessageContent.SenderID != _peerId)
        return;
    if (args.Message.MessageContent.SessionType == NIM.Session.NIMSessionType.kNIMSessionTypeTeam && args.Message.MessageContent.ReceiverID != _peerId)
        return;
    //处理自定义消息
    if (args.Message != null && args.Message.MessageContent.MessageType == NIMMessageType.kNIMMessageTypeCustom)
    {
        ···
    }
    if (args.Message != null && args.Message.MessageContent.MessageType == NIM.NIMMessageType.kNIMMessageTypeText)
    {
        ···
    }
}

private void foo()
{
    NIM.TalkAPI.OnReceiveMessageHandler += OnReceiveMessage;
}

C

void CallbackReceiveMsg(const char* msg, const char *json_exten, const void *user_data)
{
    Json::Value value;
    Json::Reader reader;
    if (reader.parse(msg, value))
    {
        int rescode = value[nim::kNIMMsgKeyLocalRescode].asInt();
        int feature = value[nim::kNIMMsgKeyLocalMsgFeature].asInt();

        switch (feature)
        {
        case kNIMMessageFeatureDefault:
            ...
        case kNIMMessageFeatureSyncMsg:
            ...
        ...
        }
    }
}

typedef void(*nim_talk_reg_receive_cb)(const char *json_extension, nim_talk_receive_cb_func cb, const void* user_data);

void foo()
{
    nim_talk_reg_receive_cb func = (nim_talk_reg_receive_cb) GetProcAddress(hInst, "nim_talk_reg_receive_cb");
    func(nullptr, &CallbackReceiveMsg, nullptr);
}

转发消息

用户通过转发消息接口获取消息结构Json字符串,然后需要调用发送消息接口。

C++

void foo(nim::IMMessage msg)
{
    std::string send_msg = nim::Talk::CreateRetweetMessage(msg.ToJsonString(false), QString::GetGUID(), nim::kNIMSessionTypeP2P, session_id, msg.msg_setting_, 1000 * nbase::Time::Now().ToTimeT());
    nim::Talk::SendMsg(send_msg);
}

C#

void foo(NIMImageMessage msg)
{
    NIM.NIMMessageSetting setting = new NIMMessageSetting();
    NIMImageMessage send_msg = TalkAPI.CreateRetweetMessage(msg, setting, NimUtility.Utilities.GenerateGuid(), session_id, NIM.Session.NIMSessionType.kNIMSessionTypeP2P, 0);
    TalkAPI.SendMessage(send_msg);  
}

C

typedef char*(*nim_talk_create_retweet_msg)(const char* src_msg_json, const char* client_msg_id, const NIMSessionType retweet_to_session_type, const char* retweet_to_session_id, const char* msg_setting, __int64 timetag);
typedef    void (*nim_global_free_buf)(void *data);

void foo(const char *src_msg_json, const char *msg_id, NIMSessionType session_type, const char *session_id, const char* msg_sessiong_json, __int64 timetag)
{
    nim_talk_create_retweet_msg func = (nim_talk_create_retweet_msg) GetProcAddress(hInst, "nim_talk_create_retweet_msg");
    nim_global_free_buf free_func = (nim_global_free_buf) GetProcAddress(hInst, "nim_global_free_buf");

    const char *msg = func(src_msg_json, msg_id, session_type, session_id, msg_sessiong_json, timetag);
    free_func((void *)msg);
}

撤回消息

用户通过发送消息发的消息可以通过该接口执行撤回操作(不支持聊天室消息),撤回操作一般有时限限制(该限制为全局的APP设置),超过限制返回508。 开发者通过注册撤回消息通知回调,接收其他端的撤回消息的通知,收到通知后SDK会标记主动在消息历史中标记该条消息为删除状态,同时开发者根据自身需求,删除界面上显示的消息,甚至插入一条提示,IM Demo有开发示例。

C++

void OnReceiveRecallMsgCallback(nim::NIMResCode code, const std::list<nim::RecallMsgNotify>& message)
{
    for (auto notify : message)
    {
        if (code == nim::kNIMResSuccess)
        {
            ···
        }
    }
}

void OnInit()
{
    nim::Talk::RegRecallMsgsCallback(&OnReceiveRecallMsgCallback);
}

void foo(const nim::IMMessage& msg)
{
    nim::Talk::RecallMsg(msg, "test notify when recall", &OnReceiveRecallMsgCallback);
}

C#

private void OnRecallMessage(ResponseCode result, RecallNotification[] notify)
{
    ···
}

void OnInit()
{
    NIM.TalkAPI.RegRecallMessageCallback(OnRecallMessage);
}

private void OnRecallMessageCompleted(ResponseCode result, RecallNotification[] notify)
{
    ···
}

void foo(string msg_id)
{
     NIM.TalkAPI.RecallMessage(msg_id, "撤回消息", OnRecallMessageCompleted);
}

C

void nim_talk_recall_msg(const char *json_msg, const char *notify, const char *json_extension, nim_talk_recall_msg_func cb, const void *user_data);

void nim_talk_reg_recall_msg_cb(const char *json_extension, nim_talk_recall_msg_func cb, const void *user_data);

void OnInit()
{
    nim_talk_reg_recall_msg_cb func = (nim_talk_reg_recall_msg_cb) GetProcAddress(hInst, "nim_talk_reg_recall_msg_cb");

    func("", &OnReceiveRecallMsgCallback, nullptr);
}

void foo(const nim::IMMessage& msg)
{
    nim_talk_recall_msg func = (nim_talk_recall_msg) GetProcAddress(hInst, "nim_talk_recall_msg");

    func(msg, "test notify when recall", "", &OnReceiveRecallMsgCallback, nullptr);
}

最近会话

最近会话记录了与用户最近有过会话的联系人信息,包括联系人帐号、联系人类型、最近一条消息的时间、消息状态、消息缩略和未读条数等信息。

C++

void OnQuerySessionListCallback(int unread_count, const nim::SessionDataList& session_list)
{
    ···
}

void foo()
{
    nim::Session::QueryAllRecentSessionAsync(&OnQuerySessionListCallback);
}

C#

void foo()
{
    NIM.Session.SessionAPI.QueryAllRecentSession((a, b) =>
    {
        if (b == null || b.SessionList == null) return;
        foreach (var item in b.SessionList)
        {
            ···
        }
    });
}

C

void CallbackQuerySessionListCb(int total_unread_count, const char* session_list, const char *json_exten, const void *user_data)
{
    ...
}

typedef void(*nim_session_query_all_recent_session_async)(const char *json_extension, nim_session_query_recent_session_cb_func cb, const void* user_data);

void foo()
{
    nim_session_query_all_recent_session_async func = (nim_session_query_all_recent_session_async) GetProcAddress(hInst, "nim_session_query_all_recent_session_async");
    func(nullptr, &CallbackQuerySessionListCb, nullptr);
}

在收发消息的同时,SDK会更新对应聊天对象的最近联系人资料。当有消息收发时,SDK 会发出最近联系人更新通知,使用nim_session_reg_change_cb 注册通知回调函数。

SDK 也提供了删除单个和删除全部最近联系人的接口。需要注意:删除会话项时本地和服务器会同步删除!

自定义消息

除了内建消息类型外,SDK 也支持收发自定义消息类型。和透传消息一样,SDK 也不会解析自定义消息的具体内容,解释工作由开发者完成。

消息已读回执

在会话界面中调用发送已读回执的接口并传入最后一条消息,即表示这之前的消息都已读,对端将收到此回执。

在发送端将需要发送的消息作为参数传入,而接收端可以通过解析result中的msg_timetag来得知发送端当前已读时间戳。此功能仅在 P2P 消息中有效。重复发送和通过无效消息构造的已读回执都将被 SDK 忽略。

多媒体消息

语音消息录制和播放

语音转文字

SDK 提供语音转文字接口。

void nim_tool_get_audio_text_async(const char *json_audio_info, const char *json_extension, nim_tool_get_audio_text_cb_func cb, const void *user_data);

例:

C++

void foo(nim::IMMessage &msg_data)
{
    nim::IMAudio audio;
    nim::Talk::ParseAudioMessageAttach(msg_data, audio);
    nim::AudioInfo audio_info;
    audio_info.samplerate_ = "16000";
    audio_info.url_ = audio.url_;
    audio_info.duration_ = audio.duration_;
    audio_info.mime_type_ = audio.file_extension_;

    nim::Tool::GetAudioTextAsync(audio_info, ToWeakCallback([this](int rescode, const std::string& text) {
        if (rescode == nim::kNIMResSuccess) {
            ···
        }
        else {
            ···
        }
    }));
}

C#

private void foo(NIM.NIMIMMessage msg)
{
    NIM.NIMAudioInfo info = new NIMAudioInfo();
    info.Duration = 1000;
    info.SampleRate = "16000";
    info.URL = "···";
    info.MimeType = "aac";
    NIM.ToolsAPI.GetAudioTextAsync(info, "", OnGetText);
}
private void OnGetText(int rescode, string text, string json_extension, IntPtr user_data)
{
}

C

void CallbackGetAudioText(int rescode, const char *text, const char *json_extension, const void *user_data)
{
    ...
}

typedef void(*nim_tool_get_audio_text_async)(const char *json_audio_info, const char *json_extension, nim_tool_get_audio_text_cb_func cb, const void *user_data);

void foo()
{
    Json::Value json_value;
    json_value[nim::kNIMTransAudioKeyMime] = ; //mime类型
    json_value[nim::kNIMTransAudioKeySample] = ; //采样率
    json_value[nim::kNIMTransAudioKeyAudioUrl] = ; //下载地址
    json_value[nim::kNIMTransAudioKeyDuration] = ; //时长(毫秒)

    nim_tool_get_audio_text_async func = (nim_tool_get_audio_text_async) GetProcAddress(hInst, "nim_tool_get_audio_text_async");
    func(json_value.toStyledString().c_str(), nullptr, &CallbackGetAudioText, nullptr);
}

获取图片缩略图

云信PC SDK目前默认收到图片消息后会提前下载原图缓存到本地,如果开发者想控制下载图片的质量,可以通过初始化SDK时设置kNIMPreloadImageQuality来控制图片质量,设置kNIMPreloadImageResize来控制图片长宽, IM Demo开发范例中默认下载原图,所以开发者如果想基于IM Demo工程源码开发,需要在图片预览环节或者其他需要显示原图的地方自行下载原图。

以下但不限于以下场景下开发者可以自行下载缩略图:

目前SDK提供两种获取缩略图方案:

目前IM Demo工程在消息显示图片的时候对图片进行了压缩处理,开发者可以参考bubble_image.cpp

群组功能

群组功能概述

云信SDK提供了普通群(kNIMTeamTypeNormal),以及高级群(kNIMTeamTypeAdvanced)两种形式的群聊功能。高级群拥有更多的权限操作,两种群聊形式在共有操作上保持了接口一致。在群组中,当前会话的ID就是群组的ID。

开发手册中所提及的普通群都等同于DEMO中的讨论组。普通群(讨论组)没有权限操作,适用于快速创建多人会话的场景。每个普通群只有一个管理员。管理员可以对群进行增减员操作,普通成员只能对群进行增员操作。在添加新成员的时候,并不需要经过对方同意。

高级群在权限上有更多的限制,权限分为群主、管理员、以及群成员。在添加成员的时候需要对方接受邀请。高级群的群成员资料提供了实时同步功能,并提供了群开关设置字段、第三方扩展字段(仅负责存储和透传)和第三方服务器扩展字段(该配置项只能通过服务器接口设置,对客户端只读)。

群操作 普通群 高级群
邀请成员 任何人 群主、管理员
踢出成员 群主 群主、管理员(管理员之间无法互相踢)
解散群 群主 群主
退群 任何人 管理员、普通成员
处理入群申请 / 群主、管理员
更改自己的群昵称 / 任何人
更改他人群昵称 / 群主、管理员
更改群名称 任何人 群主、管理员
更改群公告 / 群主、管理员
更改群介绍 / 群主、管理员
更新验证方式 / 群主、管理员
添加(删除)管理员 / 群主
移交群主 / 群主
成员禁言 / 群主、管理员
更新群头像 / 群主、管理员

群聊消息

群聊消息收发和管理与双人聊天完全相同,仅在消息类型(kNIMMsgKeyToType)上做了区分。

获取群组

SDK 在程序启动时会对本地群信息进行同步,所以只需要调用本地缓存接口获取群就可以了。SDK 提供了批量获取自己的群接口、以及根据单个群 ID 查询的接口。同样SDK 也提供了远程获取群信息的接口。

创建群组

云信群组分为两类:普通群和高级群,两种群组的消息功能都是相同的,区别在于管理功能。

普通群所有人都可以拉人入群,除群主外,其他人都不能踢人。

固定群则拥有完善的成员权限体系及管理功能。创建群的接口相同,传入不同的类型参数即可。

void nim_team_create_team_async(
const char *team_info, //群信息
const char *jsonlist_uids, //邀请的成员
const char *invitation_postscript, //邀请附言
const char *json_extension, //附加数据
nim_team_opt_cb_func cb,
const void *user_data);

例:

C++

void OnTeamEventCallback(const nim::TeamEvent& result)
{
    ...
}

void foo()
{
    std::list<std::string> id_list;
    id_list.push_back("test1");
    id_list.push_back("test2");

    nim::TeamInfo tinfo;
    tinfo.SetName("test");
    tinfo.SetType(nim::kNIMTeamTypeNormal);
    nim::Team::CreateTeamAsync(tinfo, id_list, "", &OnTeamEventCallback);
}

C#

void foo()
{
    NIM.Team.NIMTeamInfo tinfo = new NIM.Team.NIMTeamInfo();
    tinfo.Name = teamNameBox.Text;
    tinfo.Introduce = teamIntroBox.Text;
    tinfo.TeamType = NIM.Team.NIMTeamType.kNIMTeamTypeAdvanced;
    string[] uids = { "test1", "test2"};
    if (uids.Any())
    {
        NIM.Team.TeamAPI.CreateTeam(tinfo, uids, textBox1.Text, (a) =>
        {

        });
    }
}

C

void CallbackCreateTeam(int error, int team_event, const char *tid, const char* str, const char *json_exten, const void *user_data)
{
    ...
}    

typedef void(*nim_team_create_team_async)(const char *team_info, const char *jsonlist_uids, const char *invitation_postscript, const char *json_extension, nim_team_event_cb_func cb, const void* user_data);

void foo()
{
    Json::Value team_info;
    team_info[kNIMTeamInfoKeyName] = ; //群名称
    team_info[kNIMTeamInfoKeyType] = ; //群类型
    team_info[kNIMTeamInfoKeyIntro] = ; //群介绍
    team_info[kNIMTeamInfoKeyJoinMode] = ; //群验证方式
    team_info[kNIMTeamInfoKeyAnnouncement] = ; //群公告

    Json::Value team_member;
    team_member.append("litianyi01");    
    team_member.append("litianyi02");

    nim_team_create_team_async func = (nim_team_create_team_async) GetProcAddress(hInst, "nim_team_create_team_async");
    func(team_info.toStyledString().c_str(), team_member.toStyledString().c_str(), "welcome to new team", nullptr, &CallbackCreateTeam, nullptr);
}

加入群组

用户可以通过被动接受邀请和主动加入两种方式进入群组。

踢人出群

普通群仅拥有者可以踢人,高级群拥有者和管理员可以踢人,且管理员不能踢拥有者和其他管理员。

C++

void TeamEventCb(const nim::TeamEvent& team_event)
{
    ···
}

void foo()
{
    std::list<std::string> uids_list;
    uids_list.push_back("test_user");
    nim::Team::RejectJoinApplyAsync("12345", uids_list, &TeamEventCb);
}

C#

NIM.Team.TeamAPI.KickMemberOutFromTeam("12345", new string[] {"test_user"}, (a) =>
{
    if (a.TeamEvent.ResponseCode == NIM.ResponseCode.kNIMResSuccess)
    {
        ···
    }
});

C

void TeamEventCb(int res_code, int notification_id, const char *tid, const char *result, const char *json_extension, const void *user_data)
{
    ···
}

typedef void(*nim_team_kick_async)(const char *tid, const char *jsonlist_uids, const char *json_extension, nim_team_event_cb_func cb, const void* user_data);

void foo()
{
    nim_team_kick_async func = (nim_team_kick_async) GetProcAddress(hInst, "nim_team_kick_async");

    Json::Value json_value;
    json_value.append("litianyi01");    
    json_value.append("litianyi02");

    func("12345", json_value.toStyledString().c_str(), "", &TeamEventCb, nullptr);
}

主动退群

除拥有者外,其他用户均可以主动退群:

C++

void TeamEventCb(const nim::TeamEvent& team_event)
{
    ···
}

void foo()
{
    nim::Team::LeaveAsync("12345", &TeamEventCb);
}

C#

NIM.Team.TeamAPI.LeaveTeam("12345", (ret) =>
{
    if (ret.TeamEvent.ResponseCode == NIM.ResponseCode.kNIMResSuccess)
    {
        ···
    }
});

C

void TeamEventCb(int res_code, int notification_id, const char *tid, const char *result, const char *json_extension, const void *user_data)
{
    ···
}

typedef void(*nim_team_leave_async)(const char *tid, const char *json_extension, nim_team_event_cb_func cb, const void* user_data);

void foo()
{
    nim_team_leave_async func = (nim_team_leave_async) GetProcAddress(hInst, "nim_team_leave_async");

    func("12345", "", &TeamEventCb, nullptr);
}

编辑群组资料

普通群所有人均可以修改群名,高级群仅拥有者和管理员可修改群名及其他群资料。

void nim_team_update_team_info_async(const char *tid, const char *json_info, const char *json_extension, nim_team_opt_cb_func cb, const void *user_data);

例:

C++

void OnUpdateBroadCb(const nim::TeamEvent& team_event)
{
    if (team_event.res_code_ == 200)
    {
        ···
    }
}

void foo()
{
    Json::Value broad;
    broad["title"] = "title";
    broad["content"] = "内容";
    broad["creator"] = "test_user";

    Json::Value broads;
    broads.append(broad);

    Json::FastWriter writer;
    nim::TeamInfo param;
    param.SetAnnouncement(writer.write(broads));
    param.SetTeamID("tid_");

    nim::Team::UpdateTeamInfoAsync("tid_", param, &OnUpdateBroadCb);
}

C#

void foo()
{
    NIM.Team.NIMTeamInfo tinfo = new NIM.Team.NIMTeamInfo();
    tinfo.Announcement = "公告";
    tinfo.TeamId = "tid";

    NIM.Team.TeamAPI.UpdateTeamInfo("tid", tinfo, (ret) =>
    {
        if (ret.TeamEvent.ResponseCode == NIM.ResponseCode.kNIMResSuccess)
        {
            ···
        }
    });
}

C

void CallbackTeamOperate(int error, int team_operate, const char *tid, const char* str, const char *json_exten, const void *user_data)
{
    if (error == kNIMResSuccess)
    {
        ...
    }
    else
    {
        ...
    }
}

typedef void(*nim_team_update_team_info_async)(const char *tid, const char *json_info, const char *json_extension, nim_team_event_cb_func cb_func, const void* user_data);

void foo()
{
    Json::Value values;
    values[kNIMTeamInfoKeyID] = "tid";    
    values[kNIMTeamInfoKeyAnnouncement] = "123"; //修改群公告,同样的,目前可以修改群名称,群简介,具体参阅api文档
    values[kNIMTeamInfoKeyBits] = 1; //修改群开关设置(如开启或关闭群消息提醒),具体参阅api文档里NIMTeamBitsConfigMask(群组信息Bits属性kNIMTeamInfoKeyBits的配置定义)

    nim_team_update_team_info_async func = (nim_team_update_team_info_async) GetProcAddress(hInst, "nim_team_update_team_info_async");
    func("tid", values.toStyledString().c_str(), nullptr, &CallbackTeamOperate, nullptr);
}

管理群组权限

高级群群主可以对群进行权限管理,权限管理包括:

群组成员

解散群

群主可以调用接口解散所拥有的群:

C++

void OnTeamEventCallback(const nim::TeamEvent& result)
{
    ···
}

foo()
{
    nim::Team::DismissAsync("tid_", &OnTeamEventCallback);
}

C#

NIM.Team.TeamAPI.DismissTeam("_teamId", (ret) =>
{
    ···
});

C

void CallbackTeamOperate(int error, int team_operate, const char *tid, const char* str, const char *json_exten, const void *user_data)
{
    if (error == kNIMResSuccess)
    {
        ...
    }
    else
    {
        ...
    }
}

typedef void(*nim_team_dismiss_async)(const char *tid, const char *json_extension, nim_team_event_cb_func cb, const void* user_data);

void foo()
{
    nim_team_dismiss_async func = (nim_team_dismiss_async) GetProcAddress(hInst, "nim_team_dismiss_async");
    func("tid", nullptr, &CallbackTeamOperate, nullptr);
}

群组通知

用户在创建群或者进入群成功之后,任何关于群的变动(群组资料变动,群成员变动等),云信服务器都会下发一条群通知消息。APP 可以通过注册全局回调函数接受群组通知。

void nim_team_reg_team_event_cb(const char *json_extension, nim_team_event_cb_func cb, const void *user_data);

例:

C++

void OnTeamEventCallback(const nim::TeamEvent& result)
{
    ···
}

foo()
{
    nim::Team::RegTeamEventCb(&OnTeamEventCallback);
}

C#

void OnTeamEventNotify(object sender, NIMTeamEventArgs e)
{
    if (e.Data.TeamEvent.NotificationType == NIMNotificationType.kNIMNotificationIdLocalGetTeamList)
    {
        ···
    }
}

void foo()
{
    NIM.Team.TeamAPI.TeamEventNotificationHandler += OnTeamEventNotify;
}

C

void CallbackTeamEvent(int error, int team_event, const char *tid, const char* str, const char *json_exten, const void *user_data)
{
    switch (team_event)
    {
    case kNIMNotificationIdLocalCreateTeam:
        ...
    ...    
    }
}

typedef void(*nim_team_reg_team_event_cb)(const char *json_extension, nim_team_event_cb_func cb, const void *user_data);

void foo()
{
    nim_team_reg_team_event_cb func = (nim_team_reg_team_event_cb) GetProcAddress(hInst, "nim_team_reg_team_event_cb");
    func(nullptr, &CallbackTeamEvent, nullptr);
}

自定义拓展

SDK 提供了群信息的拓展接口,开发者可以通过维护群信息的两个属性来自行定义内容。

群成员禁言

历史记录

本地记录

nim_msglog.h 文件中的接口提供了比较完善的消息记录管理功能,开发者可本地查阅、在线查阅、删除和清空聊天记录,还可以调用nim_msglog_write_db_only_async 接口只往本地消息历史数据库里写入一条消息(通常是APP 的本地自定义消息,并不会发给服务器)。详见API 文档。此外,还要注意:本地消息历史操作(如删除)及状态变化(如发送失败或成功)会与会话列表里的消息状态自动同步,App 上层需要根据nim_session_reg_change_cb 注册好的会话列表通知回调进行相应通知事件的处理。

导入导出

云端记录

音视频网络通话

初始化与清理

在调用音视频网络通话相关功能前,需要在nim.dll所在目录中有对应版本的nrtc.dll文件。

初始化NIM VCHAT,需要在SDK的nim_client_init成功之后
bool nim_vchat_init(const char *json_extension);
    param[in] json_extension 无效的扩展字段
    return bool 初始化结果,如果是false则音视频网络通话所有接口调用无效

清理NIM VCHAT,需要在SDK的nim_client_cleanup之前
void nim_vchat_cleanup(const char *json_extension);
    param[in] json_extension 无效的扩展字段
    return void 无返回值

C++

void Init ()
{
    // 初始化云信SDK
    ···

    // 初始化云信音视频
    bool ret = nim::VChat::Init("");
    assert(ret);
}

void Cleaup()
{
    nim::VChat::Cleanup();

    // 清理云信SDK
    ···
}

C#

void Init ()
{
    // 初始化云信SDK
    ···

    // 初始化云信音视频
    NIM.VChatAPI.Init();
}

void Cleaup()
{
    NIM.VChatAPI.Cleanup();

    // 清理云信SDK
    ···
}

C

typedef    bool(*nim_vchat_init)(const char *json_extension);
typedef    void(*nim_vchat_cleanup)(const char *json_extension);

void Init ()
{
    // 初始化云信SDK
    ···

    // 初始化云信音视频
    nim_vchat_init func = (nim_vchat_init) GetProcAddress(hInst, "nim_vchat_init");
    func("");
}

void Cleaup()
{
    nim_vchat_cleanup func = (nim_vchat_cleanup) GetProcAddress(hInst, "nim_vchat_cleanup");
    func("");

    // 清理云信SDK
    ···
}

网络探测

底层进行udp的网络探测

typedef unsigned __int64(*nim_vchat_net_detect)(const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

C++ void NetDetectCallback(int, NetDetectCbInfo) { //... } void foo() { nim::VChat::NetDetect(&NetDetectCallback); }

C#

...

C

typedef void(*nim_vchat_net_detect)(const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_net_detect func = (nim_vchat_net_detect) GetProcAddress(hInst, "nim_vchat_net_detect");
    func("", NULL, NULL);
}

返回的 nim_vchat_opt_cb_func 中的json扩展:

static const char *kNIMNetDetectTaskId        = "task_id";        /**< uint64 任务id */
static const char *kNIMNetDetectLoss        = "loss";            /**< int 丢包率百分比 */
static const char *kNIMNetDetectRttmax        = "rttmax";            /**< int rtt 最大值 */
static const char *kNIMNetDetectRttmin        = "rttmin";            /**< int rtt 最小值 */
static const char *kNIMNetDetectRttavg        = "rttavg";            /**< int rtt 平均值 */
static const char *kNIMNetDetectRttmdev        = "rttmdev";        /**< int rtt 偏差值 mdev */
static const char *kNIMNetDetectDetail        = "detailinfo";        /**< string 扩展信息 */

在上述返回的字段中,loss、rttavg、rttmdev这三个值最能反应当前客户端的实际网络情况。由这三个值可以计算出当前的网络状况指数:

网络状况指数 = (loss/20)*50% +(rttavg/1200)*25% +(rttmdev/150)*25%

经过我们的反复测试,现提供三个网络状况指数节点

网络状况指数节点 loss(%) rttavg(ms) rttmdev(ms) 网络状况指数
A 3 500 50 0.2625
B 10 800 80 0.55
C 20 1200 150 1

备注:

  1. 当网络状况指数≤0.2625时,网络状况非常好,音视频通话流畅;

  2. 当0.2625<网络状况指数≤0.55时,网络状况好,音视频通话偶有卡顿;

  3. 当0.55<网络状况指数≤1时,网络状况差,音频通话流畅;

  4. 当网络状况指数>1时,网络状况非常差,音频通话偶有卡顿。

设置通话回调函数

在使用音视频网络通话前,需要先设置通话回调

注册通话回调函数
void nim_vchat_set_cb_func(nim_vchat_cb_func cb);

回调原型
void nim_vchat_cb_func(NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json_extension, const void *user_data);
    具体参数说明见nim_vchat_def.h;在接下来的功能中也将一一说明

C++

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{
    switch (type)
    {
    case nim::kNIMVideoChatSessionTypeStartRes:
    {
        ···
    }
    break;
    case nim::kNIMVideoChatSessionTypeInviteNotify:
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            ···
        }
    }
    break;
    }
}

void foo()
{
    nim::VChat::SetCbFunc(&VChatCb);
}

C#

private static NIM.NIMVChatSessionStatus _vchatHandlers;

foo()
{
    _vchatHandlers.onSessionStartRes = OnSessionStartRes;
    _vchatHandlers.onSessionInviteNotify = OnSessionInviteNotify;
    _vchatHandlers.onSessionCalleeAckRes = OnSessionCalleeAckRes;
    _vchatHandlers.onSessionCalleeAckNotify = OnSessionCalleeAckNotify;
    _vchatHandlers.onSessionControlRes = OnSessionControlRes;
    _vchatHandlers.onSessionControlNotify = OnSessionControlNotify;
    _vchatHandlers.onSessionConnectNotify = OnSessionConnectNotify;
    _vchatHandlers.onSessionPeopleStatus = OnSessionPeopleStatus;
    _vchatHandlers.onSessionNetStatus = OnSessionNetStatus;
    _vchatHandlers.onSessionHangupRes = OnSessionHangupRes;
    _vchatHandlers.onSessionHangupNotify = OnSessionHangupNotify;

    _vchatHandlers.onSessionSyncAckNotify =(channel_id,code,uid,  mode, accept,  time, client)=>
    {

    };

    //注册音视频会话交互回调
    NIM.VChatAPI.SetSessionStatusCb(_vchatHandlers);
}

C

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{
    switch (type)
    {
    case nim::kNIMVideoChatSessionTypeStartRes:
    {
        ···
    }
    break;
    case nim::kNIMVideoChatSessionTypeInviteNotify:
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            ···
        }
    }
    break;
    }
}

typedef void(*nim_vchat_set_cb_func)(nim_vchat_cb_func cb, const void *user_data);

void foo()
{
    // 初始化云信SDK 
    ···

    // 初始化云信音视频
    nim_vchat_set_cb_func func = (nim_vchat_set_cb_func) GetProcAddress(hInst, "nim_vchat_set_cb_func");
    func(&VChatCb, nullptr);
}

发起通话

发起一个音视频网络通话,结果将在nim_vchat_cb_func中返回

启动通话
bool nim_vchat_start(NIMVideoChatMode mode, const char* json_extension, const void *user_data)
    param[in] mode NIMVideoChatMode 启动音视频通话类型 见nim_vchat_def.h
    param[in] json_extension Json string 扩展,kNIMVChatUids成员id列表 如{ "uids":["uid_temp"]}
    param[in] user_data 无效的扩展字段
    return bool true 调用成功,false 调用失败可能有正在进行的通话

异步回调见nim_vchat_cb_func中type为以下值时
    kNIMVideoChatSessionTypeStartRes,            //创建通话结果 code=200成功,json无效
    kNIMVideoChatSessionTypeConnect,            //通话中链接状态通知 code对应NIMVChatConnectErrorCode, 非200均失败并底层结束 json无效
    kNIMVideoChatSessionTypeInfoNotify            //实时状态    json返回    {"static_info":{ "video": {"fps":20, "KBps":200, "width":1280,"height":720}, "audio": {"fps":17, "KBps":3}}} \n

C++

void foo()
{
    nim::NIMVideoChatMode mode = nim::kNIMVideoChatModeAudio;

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = "session_id";
    value[nim::kNIMVChatUids].append("uid");
    value[nim::kNIMVChatSound] = "video_chat_tip_receiver.aac";
    value[nim::kNIMVChatNeedBadge] = 0;
    value[nim::kNIMVChatNeedFromNick] = 0;
    if (1)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
    }
    std::string json_value = fs.write(value);
    nim::VChat::Start(mode, nbase::UTF16ToUTF8(mode == nim::kNIMVideoChatModeAudio ? L"语音通话邀请test"), "test custom info", json_value.c_str());
}

C#

void foo()
{
    NIMVChatInfo info = new NIMVChatInfo();
    info.Uids = new System.Collections.Generic.List<string>();
    info.Uids.Add("user_id");
    VChatAPI.Start(NIMVideoChatMode.kNIMVideoChatModeVideo, info);//邀请user_id进行语音通话
}

C

typedef bool(*nim_vchat_start)(NIMVideoChatMode mode, const char* apns_text, const char* custom_info, const char* json_extension, const void *user_data);

void foo()
{
    nim::NIMVideoChatMode mode = nim::kNIMVideoChatModeAudio;

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = "session_id";
    value[nim::kNIMVChatUids].append("uid");
    value[nim::kNIMVChatSound] = "video_chat_tip_receiver.aac";
    value[nim::kNIMVChatNeedBadge] = 0;
    value[nim::kNIMVChatNeedFromNick] = 0;
    if (1)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
L"视频通话邀请test"), "test custom info", json_value);
    }
    std::string json_value = fs.write(value);

    nim_vchat_start func = (nim_vchat_start) GetProcAddress(hInst, "nim_vchat_start");
    func(mode, nbase::UTF16ToUTF8(mode == nim::kNIMVideoChatModeAudio ? L"语音通话邀请test" : L"视频通话邀请test"), "test custom info", json_value.c_str(), nullptr);
}

收到通话邀请

通话邀请由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeInviteNotify,其中code无效,json返回 kNIMVChatUid为发起者,kNIMVChatType对应NIMVideoChatMode

通话邀请回应

通知邀请方自己的处理结果,结果将在nim_vchat_cb_func中返回

邀请回应接口
bool nim_vchat_callee_ack(__int64 channel_id, bool accept, const char* json_extension, const void *user_data)
    param[in] channel_id 音视频通话通道id
    param[in] accept true 接受,false 拒绝
    param[in] json_extension 无效的扩展字段
    param[in] user_data 无效的扩展字段
    return bool true 调用成功,false 调用失败(可能channel_id无匹配,如要接起另一路通话前先结束当前通话)

异步回调见nim_vchat_cb_func中type为以下值时
    kNIMVideoChatSessionTypeCalleeAckRes,        //接受拒绝结果 json 无效 code: 200:成功 9103 : 已经在其他端接听或拒绝过这通电话

C++

void foo(uint64_t channel_id, bool accept, const std::string& session_id)
{
    std::string json_value;
    if (accept)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        Json::FastWriter fs;
        Json::Value value;
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
        value[nim::kNIMVChatSessionId] = session_id;
        json_value = fs.write(value);
    }
    nim::VChat::CalleeAck(channel_id, accept, json_value);
}

C#

void foo()
{
    NIM.NIMVChatInfo info = new NIM.NIMVChatInfo();
    NIM.VChatAPI.CalleeAck(channel_id, true, info);
}

C

typedef bool(*nim_vchat_callee_ack)(__int64 channel_id, bool accept, const char* json_extension, const void *user_data);

void foo(uint64_t channel_id, bool accept, const std::string& session_id)
{
    std::string json_value;
    if (accept)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        Json::FastWriter fs;
        Json::Value value;
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
        value[nim::kNIMVChatSessionId] = session_id;
        json_value = fs.write(value);
    }

    nim_vchat_callee_ack func = (nim_vchat_callee_ack) GetProcAddress(hInst, "nim_vchat_callee_ack");
    func(channel_id, accept, json_value.c_str(), nullptr);
}

收到被邀请方的回应

被邀请方的回应由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeCalleeAckNotify,其中code无效,json返回 kNIMVChatUid为发起者,kNIMVChatType对应VideoChatMode, kNIMVChatAccept

收到自己在其他端的同步回应通知

本人在其他端回应了由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeSyncAckNotify,其中code无效,json返回 kNIMVChatTime,kNIMVChatType(对应NIMVideoChatMode),kNIMVChatAccept,kNIMVChatClient

设置通话类型

开始通话,或者和对方协商转换通话类型后,告诉SDK当前的通话类型(不告知对方)

bool nim_vchat_set_talking_mode(NIMVideoChatMode mode, const char* json_extension)
    param[in] mode NIMVideoChatMode 音视频通话类型 见nim_vchat_def.h
    param[in] json_extension 无效的扩展字段
    return bool true 调用成功,false 调用失败

C++

void foo()
{
    nim::VChat::SetTalkingMode(nim::kNIMVideoChatModeVideo, "");
}

C#

void foo()
{
    NIM.VChatAPI.SetMode(NIM.NIMVideoChatMode.kNIMVideoChatModeAudio);
}

C

typedef bool(*nim_vchat_set_talking_mode)(NIMVideoChatMode mode, const char* json_extension);

void foo()
{
    nim_vchat_set_talking_mode func = (nim_vchat_set_talking_mode) GetProcAddress(hInst, "nim_vchat_set_talking_mode");
    func(nim::kNIMVideoChatModeVideo, "");
}

通话控制

协商或通知对方当前会话的一些变化

bool nim_vchat_control(__int64 channel_id, NIMVChatControlType type, const char* json_extension, const void *user_data)
    param[in] channel_id 音视频通话通道id
    param[in] type NIMVChatControlType 见nim_vchat_def.h
    param[in] json_extension 无效的扩展字段
    param[in] user_data 无效的扩展字段
    return bool true 调用成功,false 调用失败

异步回调见nim_vchat_cb_func中type为以下值时
    kNIMVideoChatSessionTypeControlRes,        //code=200成功,json返回 kNIMVChatType对应NIMVChatControlType

C++

void foo(int64_t channel_id)
{
    nim::VChat::Control(channel_id, nim::kNIMTagControlOpenAudio);
}

C#

void foo(long channel_id)
{
    NIM.VChatAPI.ChatControl(channel_id, NIM.NIMVideoChatMode.kNIMVideoChatModeAudio);
}

C

typedef bool(*nim_vchat_control)(__int64 channel_id, NIMVChatControlType type, const char* json_extension, const void *user_data);

void foo(int64_t channel_id)
{
    nim_vchat_control func = (nim_vchat_control) GetProcAddress(hInst, "nim_vchat_control");
    func(channel_id, nim::kNIMTagControlCloseAudio, "", nullptr);
}

对方通话控制的通知

收到对方的通话控制消息,由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeControlNotify,其中code无效,json返回 kNIMVChatUid为发起者,kNIMVChatType对应NIMVChatControlType, kNIMVChatAccept

通话中成员进出

通话中成员进出由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypePeopleStatus,其中code对应NIMVideoChatSessionStatus, json返回kNIMVChatUid,对方离开后应主动结束通话

通话中网络变化

通话中网络变化由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeNetStatus,其中code对应NIMVideoChatSessionNetStat, json返回kNIMVChatUid

观众模式

设置观众模式(多人模式下),全局有效(重新发起时也生效),观众模式能减少运行开销

void nim_vchat_set_viewer_mode(bool viewer)
    param[in] viewer 是否观众模式
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetViewerMode(true);
}

C#

void foo()
{
    NIM.VChatAPI.SetViewerMode(true);
}

C

typedef void(*nim_vchat_set_viewer_mode)(bool viewer);

void foo()
{
    nim_vchat_set_viewer_mode func = (nim_vchat_set_viewer_mode) GetProcAddress(hInst, "nim_vchat_set_viewer_mode");
    func(true);
}

获取当前是否是观众模式

bool nim_vchat_get_viewer_mode()
    return bool true 观众模式,false 非观众模式

C++

void foo()
{
    bool view = nim::VChat::GetViewerMode();
}

C#

void foo()
{
    bool view = NIM.VChatAPI.GetViewerMode();
}

C

typedef bool(*nim_vchat_get_viewer_mode)();

void foo()
{
    nim_vchat_get_viewer_mode func = (nim_vchat_get_viewer_mode) GetProcAddress(hInst, "nim_vchat_get_viewer_mode");
    bool view = func();
}

音频静音

设置音频静音,全局有效(重新发起时也生效)

void nim_vchat_set_audio_mute(bool muted)
    param[in] muted true 静音,false 不静音
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioMuted(true);
}

C#

void foo()
{
    NIM.VChatAPI.SetAudioMute(true);
}

C

typedef void(*nim_vchat_set_audio_mute)(bool muted);

void foo()
{
    nim_vchat_set_audio_mute func = (nim_vchat_set_audio_mute) GetProcAddress(hInst, "nim_vchat_set_audio_mute");
    return func(true);
}

获取音频静音状态

bool nim_vchat_audio_mute_enabled()
    return bool true 静音,false 不静音

C++

void foo()
{
    bool mute = nim::VChat::GetAudioMuteEnabled();
}

C#

void foo()
{
    bool mute = NIM.VChatAPI.GetAudioMuteEnabled();
}

C

typedef bool(*nim_vchat_audio_mute_enabled)();

void foo()
{
    nim_vchat_audio_mute_enabled func = (nim_vchat_audio_mute_enabled) GetProcAddress(hInst, "nim_vchat_audio_mute_enabled");
    bool mute = func();
}

对端视频自动旋转

设置不自动旋转对方画面,默认打开,全局有效(重新发起时也生效)

void nim_vchat_set_rotate_remote_video(bool rotate)
    param[in] rotate true 自动旋转,false 不旋转
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetRotateRemoteVideo(true);
}

C#

void foo()
{
    NIM.VChatAPI.SetRotateRemoteVideo(true);
}

C

typedef void(*nim_vchat_set_rotate_remote_video)(bool rotate);

void foo()
{
    nim_vchat_set_rotate_remote_video func = (nim_vchat_set_rotate_remote_video) GetProcAddress(hInst, "nim_vchat_set_rotate_remote_video");
    return func(true);
}

获取自动旋转对方画面设置状态

bool nim_vchat_rotate_remote_video_enabled()
    return bool true 自动旋转,false 不旋转

C++

void foo()
{
    bool rotate = nim::VChat::IsRotateRemoteVideo();
}

C#

void foo()
{
    bool rotate = NIM.VChatAPI.IsRotateRemoteVideo();
}

C

typedef bool(*nim_vchat_rotate_remote_video_enabled)();

void foo()
{
    nim_vchat_rotate_remote_video_enabled func = (nim_vchat_rotate_remote_video_enabled) GetProcAddress(hInst, "nim_vchat_rotate_remote_video_enabled");
    bool rotate = func();
}

成员音视频控制

设置单个成员的黑名单状态,即是否显示对方的音频或视频数据,当前通话有效(只能设置进入过房间的成员)

void nim_vchat_set_member_in_blacklist(const char *uid, bool add, bool audio, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] uid 成员account
    param[in] add true表示添加到黑名单,false表示从黑名单移除
    param[in] audio true表示音频黑名单,false表示视频黑名单
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void OnOptCallback(bool ret, int code, const char *json_extension)
{
    ···
}

void foo()
{
    nim::VChat::SetMemberBlacklist("uid", true, false, "", &OnOptCallback);
}

C#

nim_vchat_opt_cb_func _vediosetblacklistop = null;

_vediosetblacklistop = new nim_vchat_opt_cb_func(
(ret, code, json_extension, user_data) =>
  {
      if (ret)
      {
          ···
      }
  }

);

VChatAPI.SetMemberInBlackList(id, !muted, false, "",
                              _vediosetblacklistop,
                              IntPtr.Zero);

C

void OnOptCallback(bool ret, int code, const char *json_extension, const void *user_data)
{
    if (user_data)
    {
        ···
    }
}

typedef void(*nim_vchat_set_member_in_blacklist)(const char *uid, bool add, bool audio, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_member_in_blacklist func = (nim_vchat_set_member_in_blacklist) GetProcAddress(hInst, "nim_vchat_set_member_in_blacklist");
    func("uid", true, true, "", &OnOptCallback, nullptr);
}

开始录制MP4

开始录制MP4,一次只允许一个录制文件,在通话开始的时候才有实际数据。状态变化见nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeMp4Notify。

C++

void StartRecordCb(bool ret, int code, const std::string& file, __int64 time)
{
    if (ret)
    {

    }
    else
    {

    }
}

void foo()
{
    nim::VChat::StartRecord(path, &StartRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{
    if(ret)
    {
        MessageBox.Show("开始录制");
    }
    else
    {
        MessageBox.Show("录制失败-错误码:" + code.ToString());
    }
}

void foo()
{
    NIM.VChatAPI.StartRecord(path, "", VChatRecordStartCallback, IntPtr.Zero);
}

C

void mp4_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_start_record)(const char *path, const char *json_extension, nim_vchat_mp4_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_record func = (nim_vchat_start_record) GetProcAddress(hInst, "nim_vchat_start_record");
    func(path.c_str(), "", &mp4_record_opt_cb, nullptr);
}

停止录制MP4

停止录制MP4

C++

void StopRecordCb(bool ret, int code, const std::string& file, __int64 time)
{

}

void foo()
{
    nim::VChat::StopRecord(&StopRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{

}

void foo()
{
    NIM.VChatAPI.StopRecord("", VChatRecordStartCallback, IntPtr.Zero);
}

C

void mp4_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_stop_record)(const char *json_extension, nim_vchat_mp4_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_stop_record func = (nim_vchat_stop_record) GetProcAddress(hInst, "nim_vchat_stop_record");
    func("", &mp4_record_opt_cb, nullptr);
}

开始录制混音文件

开始录制混音文件,一次只允许一个录制文件,在通话开始的时候才有实际数据。状态变化见nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeAuRecordNotify。

C++

void StartRecordCb(bool ret, int code, const std::string& file, __int64 time)
{
    if (ret)
    {

    }
    else
    {

    }
}

void foo()
{
    nim::VChat::StartAudioRecord(path, &StartRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{
    if(ret)
    {
        MessageBox.Show("开始录制");
    }
    else
    {
        MessageBox.Show("录制失败-错误码:" + code.ToString());
    }
}

void foo()
{
    NIM.VChatAPI.StartAudioRecord(path, "", VChatRecordStartCallback, IntPtr.Zero);
}

C

void audio_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_start_audio_record)(const char *path, const char *json_extension, nim_vchat_audio_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_record func = (nim_vchat_start_audio_record) GetProcAddress(hInst, "nim_vchat_start_audio_record");
    func(path.c_str(), "", &audio_record_opt_cb, nullptr);
}

停止录制混音文件

停止录制混音文件

C++

void StopRecordCb(bool ret, int code, const std::string& file, __int64 time)
{

}

void foo()
{
    nim::VChat::StopAudioRecord(&StopRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{

}

void foo()
{
    NIM.VChatAPI.StopAudioRecord("", VChatRecordStartCallback, IntPtr.Zero);
}

C

void audio_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_stop_audio_record)(const char *json_extension, nim_vchat_audio_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_stop_record func = (nim_vchat_stop_audio_record) GetProcAddress(hInst, "nim_vchat_stop_audio_record");
    func("", &audio_record_opt_cb, nullptr);
}

结束通话

需要在通话结束后调用,用于底层挂断和清理数据,如果是自己主动结束会通知对方

C++

void foo()
{
    nim::VChat::End("");
}

C#

void foo()
{
    NIM.VChatAPI.End();
}

C

typedef void(*nim_vchat_end)(const char* json_extension);

void foo()
{
    nim_vchat_end func = (nim_vchat_end) GetProcAddress(hInst, "nim_vchat_end");
    func("");
}

收到对方结束通知

收到对方结束由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeHangupNotify,其中code无效,json无效

C++

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{

}

void foo()
{
    nim::VChat::SetCbFunc(&VChatCb);
}

C#

private NIM.NIMVChatSessionStatus _vchatHandlers;

void foo()
{
    _vchatHandlers.onSessionConnectNotify = (channel_id, code, record_addr, record_file) =>
    {
        if (code == 200)
        {

        }
        else
        {

        }
    };


    NIM.VChatAPI.SetSessionStatusCb(_vchatHandlers);
}

C

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{

}

typedef void(*nim_vchat_set_cb_func)(nim_vchat_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_cb_func func = (nim_vchat_set_cb_func) GetProcAddress(hInst, "nim_vchat_set_cb_func");
    func(&VChatCb, nullptr);
}

多人音视频

创建一个多人房间(后续需要主动调用加入接口进入房间)

void nim_vchat_create_room(const char *room_name, const char *custom_info, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data)
    param[in] room_name 房间名
    param[in] custom_info 自定义的房间信息(加入房间的时候会返回)
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void CreateRoomCb(int code, __int64 channel_id, const std::string& json_extension)
{

}

void foo()
{
    nim::VChat::CreateRoom("room_name", "custom_info", "", CreateRoomCb);
}

C#

void CreateMultiVChatRoomCallback(bool ret, int code,string json_extension,IntPtr user_data)
{
    if (ret)
    {

    }
    else
    {
        Action action = () =>
        {
            MessageBox.Show("创建房间失败-错误码:" + code.ToString());
        };
        this.BeginInvoke(action);

    }
}

void foo()
{
    string custom_info = "custom_info";
    string json_extension = "";
    room_name = Guid.NewGuid().ToString("N");

    VChatAPI.CreateRoom(room_name, custom_info, json_extension, CreateMultiVChatRoomCallback, IntPtr.Zero);
}

C

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_vchat_create_room)(const char *room_name, const char *custom_info, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_create_room func = (nim_vchat_create_room) GetProcAddress(hInst, "nim_vchat_create_room");
    func("room_name", "custom_info", "", OnOpt2Callback, nullptr);
}

加入一个多人房间(进入房间后成员变化等,等同点对点nim_vchat_cb_func)

bool nim_vchat_join_room(NIMVideoChatMode mode, const char *room_name, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data)
    param[in] mode NIMVideoChatMode 音视频通话类型 见nim_vchat_def.h
    param[in] room_name 房间名
    param[in] json_extension 可选 如{"custom_video":0, "custom_audio":0, "video_quality":0, "session_id":"1231sda"}
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension扩展字段中包含 kNIMVChatCustomInfo,kNIMVChatSessionId
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return bool true 调用成功,false 调用失败可能有正在进行的通话

C++

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

void foo()
{
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    nim::VChat::JoinRoom(nim::kNIMVideoChatModeAudio, "room_name", json_value, OnOpt2Callback);
}

C#

void JoinMultiVChatRoomCallback(int code, Int64 channel_id, string json_extension,IntPtr user_data)
{
    if (code==200)
    {
    }
    else
    {
        Action action = () =>
        {
            MessageBox.Show("加入房间失败-错误码:" + code.ToString());
        };
        this.BeginInvoke(action);

    }
}

void foo()
{
    json_extension = "{\"session_id\":\"leewp\"}";
    VChatAPI.JoinRoom(NIMVideoChatMode.kNIMVideoChatModeVideo, "room_name", json_extension, JoinMultiVChatRoomCallback, IntPtr.Zero)
}

C

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

typedef bool(*nim_vchat_join_room)(NIMVideoChatMode mode, const char *room_name, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_join_room func = (nim_vchat_join_room) GetProcAddress(hInst, "nim_vchat_join_room");

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    func(nim::kNIMVideoChatModeAudio, "room_name", json_value, cb);
}

修改分辨率

通话中修改发送画面分辨率,在底层发送的时候发现数据源大于此设置则会缩小后发送,如果数据源小则不作处理。点对点只允许在对方解码能力内调节

void nim_vchat_set_video_quality(int video_quality, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] video_quality 分辨率模式,见NIMVChatVideoQuality定义
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

void foo()
{
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    nim::VChat::JoinRoom(nim::kNIMVideoChatModeAudio, "room_name", json_value, OnOpt2Callback);
}

C#

void JoinMultiVChatRoomCallback(int code, Int64 channel_id, string json_extension,IntPtr user_data)
{
    if (code==200)
    {
    }
    else
    {
        Action action = () =>
        {
            MessageBox.Show("加入房间失败-错误码:" + code.ToString());
        };
        this.BeginInvoke(action);

    }
}

void foo()
{
    json_extension = "{\"session_id\":\"leewp\"}";
    VChatAPI.JoinRoom(NIMVideoChatMode.kNIMVideoChatModeVideo, "room_name", json_extension, JoinMultiVChatRoomCallback, IntPtr.Zero)
}

C

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

typedef bool(*nim_vchat_join_room)(NIMVideoChatMode mode, const char *room_name, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_join_room func = (nim_vchat_join_room) GetProcAddress(hInst, "nim_vchat_join_room");

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    func(nim::kNIMVideoChatModeAudio, "room_name", json_value, cb);
}

设置视频帧率

实时设置视频发送帧率上限

C++

void foo()
{
    nim::VChat::SetFrameRate(nim::kNIMVChatVideoFrameRateNormal);
}

C#

void foo()
{
    NIM.VChatAPI.SetFrameRate(NIMVChatVideoFrameRate.kNIMVChatVideoFrameRateNormal, "", null, IntPtr.Zero);
}

C

typedef void(*nim_vchat_set_frame_rate)(NIMVChatVideoFrameRate frame_rate, const char* json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_frame_rate func = (nim_vchat_set_frame_rate) GetProcAddress(hInst, "nim_vchat_set_frame_rate");

    func(nim::kNIMVChatVideoFrameRateNormal, "", nullptr, nullptr);
}

修改视频码率

通话中修改视频码率,有效区间[100kb,2000kb],如果设置video_bitrate为0则取默认码率

void nim_vchat_set_video_bitrate(int video_bitrate, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] video_bitrate 视频码率值
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetVideoBitrate(1000);
}

C#

暂无

C

typedef void(*nim_vchat_set_video_bitrate)(int video_bitrate, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_video_bitrate func = (nim_vchat_set_video_bitrate) GetProcAddress(hInst, "nim_vchat_set_video_bitrate");

    func(1000, "", nullptr, nullptr);
}

修改数据模式

通话中修改自定义音视频数据模式。如果设置了自定义数据模式,对应的数据底层将不直接使用采集数据直接发送,而需要用户主动调用自定义数据接口来发送数据。用户可灵活的使用此功能,在数据采集处理后,通过自定义数据接口发送,实现变声、美颜等功能,或直接使用自定义数据接口实现屏幕共享、音乐播放等功能。

void nim_vchat_set_custom_data(bool custom_audio, bool custom_video, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] custom_audio true表示使用自定义的音频数据,false表示不使用
    param[in] custom_video true表示使用自定义的视频数据,false表示不使用
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetCustomData(false, true);
}

C#

private nim_vchat_opt_cb_func _set_custom_videocb = null;

void foo()
{
       if (_set_custom_videocb == null)
    {
        _set_custom_videocb = new nim_vchat_opt_cb_func((ret, code, json, intptr) =>
        {
            if (ret)
            {

            }
        });
    }
    NIM.VChatAPI.SetCustomData(false, true, "", _set_custom_videocb, IntPtr.Zero);
}

C

typedef void(*nim_vchat_set_custom_data)(bool custom_audio, bool custom_video, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_custom_data func = (nim_vchat_set_custom_data) GetProcAddress(hInst, "nim_vchat_set_custom_data");

    func(false, true, "", nullptr, nullptr);
}

互动直播

云信提供音视频互动直播推流和连麦功能(多人音视频的扩展):

互动直播相关概念

互动直播房间与云信音视频多人会议的房间概念一致,以房间名称为唯一标识。

互动直播房间需要先创建成功后才能加入,当所有用户都离开房间后,可以复用该房间名重新创建。

互动直播房间的主用户,推流地址的指定者,直播的主画面源。需要首先加入房间,连麦者才能加入。

互动直播房间的次用户,直播辅画面源,与主播加入同一房间,即能实现实时连麦互动。需要主播在房间时才能加入。

互动直播中除了主播和连麦者,观看直播画面的其他用户。可以通过停止播放直播并加入互动房间转变为连麦者。

云信互动直播基于音视频多人会议开发,通过将多人会议中用户的音视频数据处理后推送给视频流服务器实现直播和实时连麦。

在功能的提供上,互动直播复用多人音视频接口,增加互动开关、推流地址指定与切换、直播角色指定等扩展设置

互动直播流程

云信提供简单的互动推流和连麦接口,只需要创建并加入互动房间即可以实现直播推流;连麦者以相同的房间名加入即可以实现实时连麦互动。

调用nim_vchat_create_room接口创建一个互动直播房间,以房间名称为唯一标识。

主播首先加入已创建的互动房间,需要指定推流地址kNIMVChatRtmpUrl(指定推流地址的一端被认为是互动的主播),kNIMVChatBypassRtmp用于设置是否开启直播推流。调用加入接口nim_vchat_join_room进入房间,其中kNIMVChatRtmpUrl等扩展字段填写在参数json_extension中。其中NIMVChatVideoSplitMode的定义说明见图文介绍

static const char *kNIMVChatMeetingMode        = "meeting_mode";    /**< int 是否使用多人模式 >0表示是 */
static const char *kNIMVChatRtmpUrl            = "rtmp_url";        /**< string 直播推流地址(加入多人时有效),非空代表主播旁路直播, kNIMVChatBypassRtmp决定是否开始推流 */
static const char *kNIMVChatBypassRtmp        = "bypass_rtmp";    /**< int 是否旁路推流(如果rtmpurl为空是连麦观众,非空是主播的推流控制), >0表示是 */
static const char *kNIMVChatRtmpRecord        = "rtmp_record";    /**< int 是否开启服务器对直播推流录制(需要开启服务器能力), >0表示是 */
static const char *kNIMVChatSplitMode        = "split_mode";        /**< int 主播控制的直播推流时的分屏模式,见NIMVChatVideoSplitMode */

主播收到成功加入房间的反馈(nim_vchat_cb_func回调中对应的kNIMVideoChatSessionTypeConnect类型)后,即可以在观众端使用拉流播放器观看主播直播。

连麦者使用主播相同的房间名加入房间,需要打开互动直播推流开关kNIMVChatBypassRtmp(大于0标识打开),不要指定推流地址,调用加入接口nim_vchat_join_room进入房间;连麦者成功加入房间后,观众端就可以观看到双人连麦互动直播。

通过监听视频数据回掉接口,我们可以得到本地采集和接收到的远端画面数据,然后在客户端绘制(可参考demo实现)

主播可以调用nim_vchat_update_rtmp_url接口,实时切换推流地址,实现某些视频流平台的防盗链功能。

调用nim_vchat_set_streaming_mode接口函数修改实时的推流状态。

直播过程中可以使用nim_vchat_set_custom_data切换使用自定义的音频或视频数据。然后使用nim_vchat_custom_audio_data发送自定义音频数据,使用nim_vchat_custom_video_data发送自定义视频数据。

实现共享屏幕:应用上层获取屏幕数据后使用自定义视频数据接口发送即可。

当已经加入互动房间的用户收到其他用户进出的通知kNIMVideoChatSessionTypePeopleStatus,其中回调函数中code对应NIMVideoChatSessionStatus, json返回kNIMVChatUid。

kNIMVideoChatSessionStatusJoined表示连麦开始。

kNIMVideoChatSessionStatusLeaved表示连麦结束,此时连麦者应该主动退出房间,主播不要退出房间。

通过回掉函数nim_vchat_cb_func,可监听连接状态kNIMVideoChatSessionTypeConnect及对方的网络连接情况kNIMVideoChatSessionTypeNetStatus

调用nim_vchat_end接口退出互动直播房间。当所有加入改房间的用户都已离开,就可以复用该房间名重新创建了。

音视频设备

提供麦克风、扬声器(听筒)、摄像头等设备的遍历、启动关闭、监听的接口,使用相关功能前需要调用OleInitialize来初始化COM库。如果用户需要采集播放器音频,需要使用nim_audio_hook.dll,使用nim_vchat_start_device接口打开设备类型kNIMDeviceTypeAudioHook,device_path对应播放器本地全路径。

遍历设备

在启动设备前或者设备有变更后需要遍历设备,其中kNIMDeviceTypeAudioOut和kNIMDeviceTypeAudioOutChat都为听筒设备,遍历的时候等同

void nim_vchat_enum_device_devpath(NIMDeviceType type, const char *json_extension, nim_vchat_enum_device_devpath_sync_cb_func cb, const void *user_data) 
    param[in] type NIMDeviceType 见nim_device_def.h
    param[in] json_extension 无效的扩展字段
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data 无效的扩展字段
    return void 无返回值,设备信息由同步返回的cb通知

C++

void EnumDevCb(bool ret, nim::NIMDeviceType type, const char* json, const void*)
{
    if (ret)
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus) && valus.isArray())
        {
            int num = valus.size();
            for (int i=0;i<num;++i)
            {
                Json::Value device;
                device = valus.get(i, device);
                MEDIA_DEVICE_DRIVE_INFO info;
                info.device_path_ = device[nim::kNIMDevicePath].asString();
                info.friendly_name_ = device[nim::kNIMDeviceName].asString();
            }
        }
    }
}

void foo()
{
    nim::VChat::EnumDeviceDevpath(nim::kNIMDeviceTypeAudioIn, &EnumDevCb);
}

C#

void foo()
{
    NIMDeviceInfoList cameraDeviceinfolist = NIM.DeviceAPI.GetDeviceList(NIM.NIMDeviceType.kNIMDeviceTypeVideo);
}

C

void EnumDevCb(bool ret, nim::NIMDeviceType type, const char* json, const void*)
{
    if (ret)
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus) && valus.isArray())
        {
            int num = valus.size();
            for (int i=0;i<num;++i)
            {
                Json::Value device;
                device = valus.get(i, device);
                MEDIA_DEVICE_DRIVE_INFO info;
                info.device_path_ = device[nim::kNIMDevicePath].asString();
                info.friendly_name_ = device[nim::kNIMDeviceName].asString();
            }
        }
    }
}

typedef void(*nim_vchat_enum_device_devpath)(nim::NIMDeviceType type, const char *json_extension, nim_vchat_enum_device_devpath_sync_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_enum_device_devpath func = (nim_vchat_enum_device_devpath) GetProcAddress(hInst, "nim_vchat_enum_device_devpath");

    func(nim::kNIMDeviceTypeAudioIn, "", &EnumDevCb, nullptr);
}

启动设备

可以启动麦克风、摄像头、本地采集音频播放器、对方语音播放器等,设备路径填空的时候会启动系统首选项

启动设备,同一NIMDeviceType下设备将不重复启动,不同的设备会先关闭前一个设备开启新设备
void nim_vchat_start_device(NIMDeviceType type, const char* device_path, unsigned fps, const char *json_extension, nim_vchat_start_device_cb_func cb, const void *user_data)
    param[in] type NIMDeviceType 见nim_device_def.h
    param[in] device_path 设备路径对应kNIMDevicePath,如果是kNIMDeviceTypeAudioHook,对应播放器本地全路径
    param[in] fps 摄像头为采样频率(一般取30),其他NIMDeviceType无效(麦克风采样频率由底层控制,播放器采样频率也由底层控制)
    param[in] json_extension 无效的扩展字段
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void StartDeviceCb(nim::NIMDeviceType type, bool ret, const char *json, const void*)
{

}

void foo()
{
    nim::VChat::StartDevice(nim::kNIMDeviceTypeVideo, device_path, 50, width, height, &StartDeviceCb);
}

C#

void foo()
{
   NIM.DeviceAPI.StartDeviceResultHandler handle = (type, ret) =>
    {
        Action action = () =>
        {
            if(ret)
            {

            }
            else
            {
                MessageBox.Show("启动摄像头设备失败");
            }

        };
        this.Invoke(action);
    };
    NIM.DeviceAPI.StartDevice(NIM.NIMDeviceType.kNIMDeviceTypeVideo, camera_device_path, 50, handle);//开启摄像头
}

C

void StartDeviceCb(nim::NIMDeviceType type, bool ret, const char *json, const void*)
{

}

typedef void(*nim_vchat_start_device)(nim::NIMDeviceType type, const char* device_path, unsigned fps, const char* json_extension, nim_vchat_start_device_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_device func = (nim_vchat_start_device) GetProcAddress(hInst, "nim_vchat_start_device");

    std::string json_value;
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMDeviceWidth] = width;
    value[nim::kNIMDeviceHeight] = height;
    json_value = fs.write(value);

    fun(nim::kNIMDeviceTypeVideo, device_path.c_str(), 50, json_value.c_str(), &StartDeviceCb, nullptr);
}

结束设备

void nim_vchat_end_device(NIMDeviceType type, const char *json_extension)
    param[in] type NIMDeviceTypenim_device_def.h
    param[in] json_extension 无效的扩展字段
    return void 无返回值

C++

void foo()
{
    nim::VChat::EndDevice(nim::kNIMDeviceTypeVideo);
}

C#

void foo()
{
    NIM.DeviceAPI.EndDevice(NIM.NIMDeviceType.kNIMDeviceTypeAudioOut);
}

C

typedef void(*nim_vchat_end_device)(nim::NIMDeviceType type, const char *json_extension);

void foo()
{
    nim_vchat_end_device func = (nim_vchat_end_device) GetProcAddress(hInst, "nim_vchat_end_device");

    fun(nim::kNIMDeviceTypeVideo);
}

监听设备状态

通过设置设备监听,可以定时获知设备状态并启动非主动结束的设备,这里只对麦克风和摄像头、及伴音hook有效

void nim_vchat_add_device_status_cb(NIMDeviceType type, nim_vchat_device_status_cb_func cb)
    param[in] type NIMDeviceTypekNIMDeviceTypeAudioInkNIMDeviceTypeVideo有效) 见nim_device_def.h
    param[in] cb 结果回调见nim_device_def.h
    return void 无返回值

C++

void DeviceStatusCb(nim::NIMDeviceType type, UINT status, const char* path, const char *json, const void *)
{

}

void foo()
{
    nim::VChat::AddDeviceStatusCb(nim::kNIMDeviceTypeVideo, &DeviceStatusCb);
}

C#

void foo()
{
   NIM.DeviceAPI.AddDeviceStatusCb(NIM.NIMDeviceType.kNIMDeviceTypeVideo, (type, status, devicePath) =>
    {
    });
}

C

void DeviceStatusCb(nim::NIMDeviceType type, UINT status, const char* path, const char *json, const void *)
{

}

typedef void(*nim_vchat_add_device_status_cb)(nim::NIMDeviceType type, nim_vchat_device_status_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_add_device_status_cb func = (nim_vchat_add_device_status_cb) GetProcAddress(hInst, "nim_vchat_add_device_status_cb");

    fun(nim::kNIMDeviceTypeVideo, &DeviceStatusCb);
}

由于开启设备监听后会定时检查设备状态,在不需要时结束监听

void nim_vchat_remove_device_status_cb(NIMDeviceType type)
    param[in] type DeviceTypekNIMDeviceTypeAudioInkNIMDeviceTypeVideo有效) 见nim_device_def.h
    return void 无返回值

C++

void foo()
{
    nim::VChat::RemoveDeviceStatusCb(nim::kNIMDeviceTypeVideo);
}

C#

void foo()
{
    NIM.DeviceAPI.RemoveDeviceStatusCb(NIM.NIMDeviceType.kNIMDeviceTypeVideo);
}

C

typedef void(*nim_vchat_remove_device_status_cb)(nim::NIMDeviceType type);

void foo()
{
    nim_vchat_remove_device_status_cb func = (nim_vchat_remove_device_status_cb) GetProcAddress(hInst, "nim_vchat_remove_device_status_cb");

    fun(nim::kNIMDeviceTypeVideo);
}

辅助的摄像头

启动辅助的摄像头,摄像头数据通过nim_vchat_set_video_data_cb设置采集回调返回(json返回kNIMDeviceId),不直接通过视频通话发送给对方,并且不参与设备监听检测

void nim_vchat_start_extend_camera(const char *id, const char *device_path, unsigned fps, const char *json_extension, nim_vchat_start_device_cb_func cb, const void *user_data)
    param[in] id 摄像头标识,用于开关及数据回调时的对应,不能为空。(同一id下设备将不重复启动,如果设备device_path不同会先关闭前一个设备开启新设备)
    param[in] device_path 设备路径对应kNIMDevicePath
    param[in] fps 摄像头为采样频率
    param[in] json_extension 打开摄像头是允许设置 kNIMDeviceWidth 和 kNIMDeviceHeight,并取最接近设置值的画面模式
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

...

C#

...

C

void StartDeviceCb(nim::NIMDeviceType type, bool ret, const char *json, const void*)
{

}

typedef void(*nim_vchat_start_extend_camera)(const char *id, const char *device_path, unsigned fps, const char *json_extension, nim_vchat_start_device_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_extend_camera func = (nim_vchat_start_extend_camera) GetProcAddress(hInst, "nim_vchat_start_extend_camera");

    std::string json_value;
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMDeviceWidth] = width;
    value[nim::kNIMDeviceHeight] = height;
    json_value = fs.write(value);

    fun("video_extend", device_path.c_str(), 50, json_value.c_str(), cb, nullptr);
}

结束辅助摄像头

void nim_vchat_stop_extend_camera(const char *id, const char *json_extension)
    param[in] id 摄像头标识id,如果为空,则关闭所有辅助摄像头
    param[in] json_extension 无效的扩展字段
    return void 无返回值

C++

...

C#

...

C

typedef void(*nim_vchat_stop_extend_camera)(const char *id, const char *json_extension);

void foo()
{
    nim_vchat_stop_extend_camera func = (nim_vchat_stop_extend_camera) GetProcAddress(hInst, "nim_vchat_stop_extend_camera");

    fun("video_extend", "");
}

监听音频数据

通过设置监听,可以获得音频的pcm数据包,通过启动设备kDeviceTypeAudioOut和kDeviceTypeAudioOutChat由底层播放,可以不监听

void nim_vchat_set_audio_data_cb(bool capture, const char *json_extension, nim_vchat_audio_data_cb_func cb, const void *user_data)
    param[in] capture true 标识监听麦克风采集数据,false 标识监听通话中对方音频数据
    param[in] json_extension 扩展Json string:kNIMDeviceSampleRate(要求返回的音频数据为指定的采样频,缺省为0使用默认采样频)
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, const char *json_extension, const void *user_data)
{

}

void foo()
{
    nim::VChat::SetAudioDataCb(true, &SetDataCb);
}

C#

void AudioDataCaptureHandler(UInt64 time, IntPtr data, uint size, int rate)
{

}

void foo()
{
   NIM.DeviceAPI.SetAudioCaptureDataCb(AudioDataCaptureHandler);
}

C

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_vchat_set_audio_data_cb)(bool capture, const char* json_extension, nim_vchat_audio_data_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_audio_data_cb func = (nim_vchat_set_audio_data_cb) GetProcAddress(hInst, "nim_vchat_set_audio_data_cb");

    fun(true, "", &SetDataCb, nullptr);
}

监听视频数据

通过设置监听,可以获得视频的ARGB数据包

void nim_vchat_set_video_data_cb(bool capture, const char *json_extension, nim_vchat_video_data_cb_func cb, const void *user_data)
    param[in] capture true 标识监听采集数据,false 标识监听通话中对方视频数据
    param[in] json_extension Json string 返回kNIMVideoSubType(缺省为kNIMVideoSubTypeARGB)
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension, const void *user_data)
{

}

void foo()
{
    nim::VChat::SetVideoDataCb(true, &SetDataCb);
}

C#

void VideoDataCaptureHandler(UInt64 time, IntPtr data, UInt32 size, UInt32 width, UInt32 height, string json_extension, IntPtr user_data)
{

}

void foo()
{
    JObject jo = new JObject();
    jo.Add(new JProperty("subtype", NIMVideoSubType.kNIMVideoSubTypeI420));
    string json_extention = jo.ToString();
   NIM.DeviceAPI.SetVideoCaptureDataCb(VideoDataCaptureHandler,json_extention);
}

C

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_vchat_set_video_data_cb)(bool capture, const char* json_extension, nim_vchat_video_data_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_video_data_cb func = (nim_vchat_set_video_data_cb) GetProcAddress(hInst, "nim_vchat_set_video_data_cb");

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVideoSubType] = kNIMVideoSubTypeI420;
    std::string json_value = fs.write(value);
    fun(true, json_value.c_str(), &SetDataCb, nullptr);
}

音频设置

设置音频音量,默认255,且音量均由软件换算得出,设置麦克风音量自动调节后麦克风音量参数无效

void nim_vchat_set_audio_volumn(unsigned char volumn, bool capture)
    param[in] volumn 结果回调见nim_device_def.h
    param[in] capture true 标识设置麦克风音量,false 标识设置播放音量
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioVolumn(200, true);
}

C#

void foo()
{
    NIM.DeviceAPI.AudioCaptureVolumn = 200;
}

C

typedef void(*nim_vchat_set_audio_volumn)(unsigned char volumn, bool capture);

void foo()
{
    nim_vchat_set_audio_volumn func = (nim_vchat_set_audio_volumn) GetProcAddress(hInst, "nim_vchat_set_audio_volumn");

    func(200, true);
}

获取音频音量

unsigned char nim_vchat_get_audio_volumn(bool capture)
    param[in] capture true 标识获取麦克风音量,false 标识获取播放音量
    return unsigned char 音量值

C++

void foo()
{
    nim::VChat::GetAudioVolumn(true);
}

C#

void foo()
{
    byte value = NIM.DeviceAPI.AudioCaptureVolumn;
}

C

typedef unsigned char(*nim_vchat_get_audio_volumn)(bool capture);

void foo()
{
    nim_vchat_get_audio_volumn func = (nim_vchat_get_audio_volumn) GetProcAddress(hInst, "nim_vchat_get_audio_volumn");

    func(true);
}

设置麦克风音量自动调节

void nim_vchat_set_audio_input_auto_volumn(bool auto_volumn)
    param[in] auto_volumn true 标识麦克风音量自动调节,false 标识麦克风音量不调节,这时nim_vchat_set_audio_volumn中麦克风音量参数起效
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioInputAutoVolumn(true);
}

C#

void foo()
{
    NIM.DeviceAPI.AudioCaptureAutoVolumn = true;
}

C

typedef void(*nim_vchat_set_audio_input_auto_volumn)(bool auto_volumn);

void foo()
{
    nim_vchat_set_audio_input_auto_volumn func = (nim_vchat_set_audio_input_auto_volumn) GetProcAddress(hInst, "nim_vchat_set_audio_input_auto_volumn");

    func(true);
}

获取是否自动调节麦克风音量

bool nim_vchat_get_audio_input_auto_volumn()
    return bool true 标识麦克风音量自动调节,false 标识麦克风音量不调节

C++

void foo()
{
    bool value = nim::VChat::GetAudioInputAutoVolumn();
}

C#

void foo()
{
    bool value = NIM.DeviceAPI.AudioCaptureAutoVolumn;
}

C

typedef bool(*nim_vchat_get_audio_input_auto_volumn)();

void foo()
{
    nim_vchat_get_audio_input_auto_volumn func = (nim_vchat_get_audio_input_auto_volumn) GetProcAddress(hInst, "nim_vchat_get_audio_input_auto_volumn");

    bool is_auto = func();
}

自定义数据接口

自定义音频数据接口, 采样位深只支持16或32, kNIMDeviceSampleRate支持8000,16000,32000,44100

bool nim_vchat_custom_audio_data(unsigned __int64 time, const char *data, unsigned int size, const char *json_extension);
    param[in] time 时间毫秒级
    param[in] data 音频数据pcm格式
    param[in] size data的数据长度
    param[in] json_extension 扩展Json string kNIMDeviceSampleRate采样频和kNIMDeviceSampleBit采样位深 默认如{"sample_rate":16000, "sample_bit":16}
    return bool true 调用成功,false 调用失败

C++

(参见IM demo)
...

C#

(参见IM demo)
...

C

暂无

自定义视频数据接口

bool nim_vchat_custom_video_data(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension);
    param[in] time 时间毫秒级
    param[in] data 视频数据, 默认为yuv420格式
    param[in] size data的数据长度
    param[in] width  画面宽度,必须是偶数
    param[in] height  画面高度,必须是偶数
    param[in] json_extension  扩展Json string,kNIMVideoSubType视频数据格式(缺省为kNIMVideoSubTypeI420)
    return bool true 调用成功,false 调用失败

C++

//参见IM demo
void foo()
{
    static int64_t timestamp = 0;
    std::string data;
    data.resize(1280 * 720 * 4);
    int32_t w, h;
    w = 0;
    h = 0;
    bool ret = nim_comp::VideoManager::GetInstance()->video_frame_mng_.GetVideoFrame("", timestamp, (char*)data.c_str(), w, h, false, false);
    if (ret)
    {
        int32_t width = w;
        int32_t height = h;
        int32_t wxh = width*height;
        int32_t data_size = wxh * 3 / 2;

        //均方差滤波
        std::string beauty_src_data;
        beauty_src_data.resize(wxh * 4);
        nim_comp::YUV420ToARGB((char*)data.c_str(), (char*)beauty_src_data.c_str(), width, height);

        //采用色彩平衡算法进行美白
        nim_comp::colorbalance_rgb_u8((unsigned char*)beauty_src_data.c_str(), wxh, (size_t)(wxh * 2 / 100), (size_t)(wxh * 8 / 100), 4);

        nim_comp::ARGBToYUV420((char*)beauty_src_data.c_str(), (char*)data.c_str(), width, height);

        //采用均方差滤波进行磨皮
        nim_comp::smooth_process((uint8_t*)data.c_str(), width, height, 10, 0, 200);

        //保存用于预览
        std::string json;
        video_frame_mng_.AddVideoFrame(true, 0, data.c_str(), data_size, w, h, json, nim_comp::VideoFrameMng::Ft_I420);
        //发送
        nim::VChat::CustomVideoData(0, data.c_str(), data_size, w, h, nullptr);
    }
}

C#

//参见IM demo
void foo(object sender, VideoEventAgrs e)
{

    uint size=Convert.ToUInt32(e.Frame.Width*e.Frame.Height*3/2);
    //处理数据
    byte[] i420=NIMDemo.LivingStreamSDK.YUVHelper.ARGBToI420(e.Frame.Data,e.Frame.Width,e.Frame.Height);
    Beauty.Smooth.smooth_process(i420,e.Frame.Width,e.Frame.Height,10,0,200);
    e.Frame.Data = NIMDemo.LivingStreamSDK.YUVHelper.I420ToARGB(i420, e.Frame.Width, e.Frame.Height);

    //发送自定义数据
    TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
    ulong time = Convert.ToUInt64(ts.TotalMilliseconds);
    NIMDemo.LivingStreamSDK.YUVHelper.i420Revert(ref i420,e.Frame.Width,e.Frame.Height);
    IntPtr unmanagedPointer = Marshal.AllocHGlobal(i420.Length);
    Marshal.Copy(i420, 0, unmanagedPointer, i420.Length);
    NIM.DeviceAPI.CustomVideoData(time, unmanagedPointer, size, (uint)e.Frame.Width, (uint)e.Frame.Height);
    Marshal.FreeHGlobal(unmanagedPointer);
}

C

typedef bool(*nim_vchat_custom_video_data)(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension);

void foo()
{
    nim_vchat_custom_video_data func = (nim_vchat_custom_video_data) GetProcAddress(hInst, "nim_vchat_custom_video_data");

    func(time, data, size, width, height, "");
}

音频采集数据处理开关

设置底层针对麦克风采集数据处理开关接口,默认全开(此接口是全局接口,在sdk初始化后设置一直有效)。aec 标识回音消除功能,ns标识降噪功能,vid标识人言检测功能。

void nim_vchat_set_audio_process_info(bool aec, bool ns, bool vid)
    param[in] aec true 标识打开回音消除功能,false 标识关闭
    param[in] ns true 标识打开降噪功能,false 标识关闭
    param[in] vid true 标识打开人言检测功能,false 标识关闭
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioProcess(false, false, false);
}

C#

(参见IM demo)
...

C

typedef bool(*nim_vchat_set_audio_process_info)(bool aec, bool ns, bool vid);

void foo()
{
    nim_vchat_set_audio_process_info func = (nim_vchat_set_audio_process_info) GetProcAddress(hInst, "nim_vchat_set_audio_process_info");

    func(false, false, false);
}

音视频通话示例

对所有异步回调接口,在实现时post到ui线程处理

//本地设备遍历
struct MEDIA_DEVICE_DRIVE_INFO
{
    std::string device_path_;
    std::string friendly_name_;
};
std::list<MEDIA_DEVICE_DRIVE_INFO> list_audioin_;
std::list<MEDIA_DEVICE_DRIVE_INFO> list_audioout_;
std::list<MEDIA_DEVICE_DRIVE_INFO> list_video_;
void GetDeviceByJson(std::list<MEDIA_DEVICE_DRIVE_INFO>& list, const char* json)
{
    Json::Value valus;
    Json::Reader reader;
    if (reader.parse(json, valus) && valus.isArray())
    {
        int num = valus.size();
        for (int i=0;i<num;++i)
        {
            Json::Value device;
            device = valus.get(i, device);
            MEDIA_DEVICE_DRIVE_INFO info;
            info.device_path_ = device[kNIMDevicePath].asString();
            info.friendly_name_ = device[kNIMDeviceName].asString();
            list.push_back(info);
        }
    }
}
//遍历设备的回调为同步接口,不需要post
void EnumDevCb(bool ret, NIMDeviceType type, const char* json, const void*)
{
    Print("EnumDevCb: ret=%d, NIMDeviceType=%d\r\njson: %s", ret, type, json);
    if (ret)
    {
        switch (type)
        {
        case kNIMDeviceTypeAudioIn:
            GetDeviceByJson(list_audioin_, json);
            break;
        case kNIMDeviceTypeAudioOut:
            GetDeviceByJson(list_audioout_, json);
            break;
        case kNIMDeviceTypeVideo:
            GetDeviceByJson(list_video_, json);
            break;
        }
    }
}
void EnumDev()
{
    nim_vchat_enum_device_devpath(kNIMDeviceTypeVideo, "", EnumDevCb, nullptr);
    nim_vchat_enum_device_devpath(kNIMDeviceTypeAudioIn, "", EnumDevCb, nullptr);
    nim_vchat_enum_device_devpath(kNIMDeviceTypeAudioOut, "", EnumDevCb, nullptr);
}

//监听设备回调
void AudioStatus(NIMDeviceType type, UINT status, const char* path, const char*)
{
    //
}
void VideoStatus(NIMDeviceType type, UINT status, const char* path, const char*)
{
    //
}

//监听设备,建议在需要的时候开启,不需要的时候调用停止监听
void AddDeviceStatusCb()
{
    nim_vchat_add_device_status_cb(kNIMDeviceTypeVideo, VideoStatus);
    nim_vchat_add_device_status_cb(kNIMDeviceTypeAudioIn, AudioStatus);
}
//停止监听设备
void RemoveDeviceStatusCb()
{
    nim_vchat_remove_device_status_cb(kNIMDeviceTypeVideo);
    nim_vchat_remove_device_status_cb(kNIMDeviceTypeAudioIn);
}

//音视频通话
//通话回调函数
void OnVChatEvent(NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json_extension, const void *user_data)
{
    std::string json = json_extension;
    switch (type)
    {
    case nim::kNIMVideoChatSessionTypeStartRes:{
        if (code == 200)
        {
            //发起通话成功
        }
        else
        {
            EndVChat();            
        }
    }break;
    case nim::kNIMVideoChatSessionTypeInviteNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            std::string uid = valus[nim::kNIMVChatUid].asString();
            int mode = valus[nim::kNIMVChatType].asInt();
            bool exist = false;//是否已经有音视频通话存在
            if (exist)
            {
                //通知对方忙碌
                VChatControl(channel_id, nim::kNIMTagControlBusyLine);
                //拒绝接听,可由用户在ui触发
                VChatCalleeAck(channel_id, false);
            }
            else
            {
                //通知对方收到邀请
                VChatControl(channel_id, nim::kNIMTagControlReceiveStartNotifyFeedback);
                //接听,可由用户在ui触发
                VChatCalleeAck(channel_id, true);
            }
        }
    }break;
    case nim::kNIMVideoChatSessionTypeCalleeAckRes:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            //std::string uid = valus[nim::kNIMVChatUid].asString();
            bool accept = valus[nim::kNIMVChatAccept].asInt() > 0;
            if (accept && code != 200)
            {
                //接听失败,结束当前通话
                EndVChat();    
            }
        }
    }break;
    case nim::kNIMVideoChatSessionTypeCalleeAckNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            //std::string uid = valus[nim::kNIMVChatUid].asString();
            bool accept = valus[nim::kNIMVChatAccept].asInt() > 0;
            if (!accept)
            {
                //对方拒绝,结束当前通话
                EndVChat();    
            }
        }
    }break;
    case nim::kNIMVideoChatSessionTypeControlRes:{
        //控制命令发送结果
    }break;
    case nim::kNIMVideoChatSessionTypeControlNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            //std::string uid = valus[nim::kNIMVChatUid].asString();
            int type = valus[nim::kNIMVChatType].asInt();
            //收到一个控制通知
        }
    }break;
    case nim::kNIMVideoChatSessionTypeConnect:{
        if (code != 200)
        {
            //连接音视频服务器失败
        }
        else
        {
            //连接音视频服务器成功,启动麦克风、听筒、摄像头
            StartDevice();
        }
    }break;
    case nim::kNIMVideoChatSessionTypePeopleStatus:{
        if (code == nim::kNIMVideoChatSessionStatusJoined)
        {
            //对方进入
        }
        else if (code == nim::kNIMVideoChatSessionStatusLeaved)
        {
            //对方离开,结束
            EndVChat();    
        }
    }break;
    case nim::kNIMVideoChatSessionTypeNetStatus:{
        //网络状况变化
    }break;
    case nim::kNIMVideoChatSessionTypeHangupRes:{
        //挂断结果
    }break;
    case nim::kNIMVideoChatSessionTypeHangupNotify:{
        //收到对方挂断
    }break;
    case nim::kNIMVideoChatSessionTypeSyncAckNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            bool accept = valus[nim::kNIMVChatAccept].asInt() > 0;
            //在其他端处理
        }
    }break;
    }
}
//一个简单的视频数据绘制
void DrawPic(bool left, unsigned int width, unsigned int height, const char* data)
{
    if (!IsWindow(m_hWnd))
    {
        return;
    }
    int pos = 10;
    const char* scr_data = data;
    int w = 0;
    int top = 30;
    if (!left)
    {
        RECT rect;
        GetWindowRect(m_hWnd, &rect);
        w = rect.right - rect.left;
        pos = w - width - 20;
    }
    // 构造位图信息头
    BITMAPINFOHEADER bih;
    memset(&bih, 0, sizeof(BITMAPINFOHEADER));
    bih.biSize = sizeof(BITMAPINFOHEADER);
    bih.biPlanes = 1;
    bih.biBitCount = 32;
    bih.biWidth = width;
    bih.biHeight = height;
    bih.biCompression = BI_RGB;
    bih.biSizeImage = width * height * 4;

    // 开始绘制
    HDC    hdc = ::GetDC(m_hWnd);
    PAINTSTRUCT ps;
    ::BeginPaint(m_hWnd, &ps);
    // 使用DIB位图和颜色数据对与目标设备环境相关的设备上的指定矩形中的像素进行设置
    SetDIBitsToDevice(
        hdc, pos, top,
        bih.biWidth, bih.biHeight,
        0, 0, 0, bih.biWidth,
        scr_data,
        (BITMAPINFO*)&bih,
        DIB_RGB_COLORS);
    ::EndPaint(m_hWnd, &ps);
    ::ReleaseDC(m_hWnd, hdc);
}
//本地视频数据监听
void VideoCaptureData(unsigned __int64 time, const char* data, unsigned int size, unsigned int width, unsigned int height, const char*)
{
    static int capture_video_num = 0;
    capture_video_num++;
    if (capture_video_num % 2 == 0)
    {
        return;
    }
    DrawPic(true, width, height, data);
}
//接收视频数据监听
void VideoRecData(unsigned __int64 time, const char* data, unsigned int size, unsigned int width, unsigned int height, const char*)
{
    DrawPic(false, width, height, data);
}
//初始化回调接口
void InitVChatCb()
{
    nim_vchat_set_cb_func(OnVChatEvent);
    nim_vchat_set_video_data_cb(true, "", VideoCaptureData, nullptr);
    nim_vchat_set_video_data_cb(false, "", VideoRecData, nullptr);
}
//启动设备
void StartDevice()
{
    nim_vchat_start_device(kNIMDeviceTypeAudioIn, "", 0, "", nullptr, nullptr);
    nim_vchat_start_device(kNIMDeviceTypeAudioOutChat, "", 0, "", nullptr, nullptr);
    nim_vchat_start_device(kNIMDeviceTypeVideo, "", 50, "", nullptr, nullptr);
}
//停用设备
void EndDevice()
{
    nim_vchat_end_device(kNIMDeviceTypeVideo, "");
    nim_vchat_end_device(kNIMDeviceTypeAudioIn, "");
    nim_vchat_end_device(kNIMDeviceTypeAudioOutChat, "");
}
//发起邀请
void StartVChat()
{
    Json::FastWriter fs;
    Json::Value value;
    value[kNIMVChatUids].append("uid");
    std::string json_value = fs.write(value);
    nim_vchat_start(kNIMVideoChatModeVideo, json_value.c_str(), nullptr);
}
//结束通话
void EndVChat()
{
    EndDevice();
    //当结束当前音视频会话时都可以调用nim_vchat_end,sdk底层会对通话进行判断清理
    nim_vchat_end("");
}

实时会话(白板)

说明

点对点实时会话提供了一个音视频和一个tcp的通道,允许用户同时发起这2个通道,实现数据和音视频的会话功能,详见nim_rts.h和nim_rts_def.h。多人实时会话只发起一个多人的tcp通道,多人音视频通道需要用户使用多人音视频接口来创建。 其中需要注意的是,如果用户需要使用音视频通道,请使用音视频网络通话中的初始化和清理接口(nim_vchat.h),并在会话中使用nim_device.h中的设备相关接口,设置启动设备。并且,音视频通话具有唯一性,和音视频网络通话互斥,一个客户端只允许同时存在一个音视频连接。

服务器tcp数据录制说明

服务器会将用户发送的数据,每个成员录制到一个文件中。云信sdk3.2版本之前,录制数据是纯裸数据录制;sdk3.2版本开始,针对用户发的每条数据前追加8字节数据,包括uint32的长度(4+4+数据实际长度)和uint32的时间戳(多人会话开始计算的毫秒级时间)。

发起会话

发起者通过nim_rts_start将创建一个rts会话。其中可以通过传入json_extension,来修改通话的设置,参数使用见nim_rts_def.h。发起会话的结果将由nim_rts_start_cb_func,如果成功将得到一个唯一的session_id来标识此次会话。

收到会话邀请

被邀请者,通过nim_rts_set_start_notify_cb_func接口来设置回调实现,由nim_rts_start_notify_cb_func通知。

回复收到的邀请

被邀请者回复邀请接口为nim_rts_ack,可以通过传入json_extension,来修改通话的设置,参数使用见nim_rts_def.h。

发起者接收对方的响应

发起者通过设置nim_rts_set_ack_notify_cb_func接口,从回调函数nim_rts_ack_notify_cb_func得到被邀请者的回复。

被邀请者的多端同步

被邀请通过设置nim_rts_set_sync_ack_notify_cb_func接口,可以知道自己其他端在收到邀请后的回复,同时sdk将会清理本次邀请。

创建多人tcp通道(多人白板)

创建者通过nim_rts_create_conf接口向服务器创建一个多人白板会话。如果长时间无人进入或所有人离开后此会话失效。

加入多人tcp通道(多人白板)

创建者或参与者通过nim_rts_join_conf接口加入多人白板通道。

监听通道连接状态

当被邀请者同意邀请后,会话双方将连接服务器进入数据通道。监听通道状态,通过nim_rts_set_connect_notify_cb_func接口实现。如果会话中有多个通道,则各个通道的状态将分别通过nim_rts_connect_notify_cb_func通知。其中只有返回回调中code=200时,标识通道连接成功,其他均为失败,并且sdk会清理失败的通道,但不会主动结束会话。同时如果有音视频通道,则可以在通道连接成功的时候,启动相关音视频设备。

监听通道内成员变化

在通道连接成功后,通过由nim_rts_set_member_change_cb_func设置提供的nim_rts_member_change_cb_func回调,将通知通道内其他成员的状态。当通道内有其他成员时,数据通道才允许发送数据,否则发送数据将失败。

会话控制通知

通过nim_rts_control接口,用户可以在会话过程中,向对方发送一些自定义的通知。接收通知由nim_rts_set_control_notify_cb_func接口中的nim_rts_control_notify_cb_func回调返回。

音视频通话模式切换

用户可以通过nim_rts_set_vchat_mode接口切换音频或视频模式。模式切换的作用是,在非视频模式时用户的视频数据将不会发送给对方。注意,音频数据如果在设备打开的时候会通过音视频通道发送给对方,如果用户需要静音,则需要关闭麦克风设备,或者在发起会话时使用custom_audio模式,自主控制音频数据。

主动结束会话

通过nim_rts_hangup接口,用户可以主动结束会话。并通过nim_rts_set_hangup_notify_cb_func接口的nim_rts_hangup_notify_cb_func回调,监听对方的结束通知。注意,对于一个成功发起的会话,需要主动hangup结束或者收到对方结束通知,sdk才会结束清理本次会话。多人模式下此接口只做本地挂断、清理,其他参与人不会受到挂断通知。

数据发送与接收

发送数据由nim_rts_send_data实现,接收数据由nim_rts_set_rec_data_cb_func的nim_rts_rec_data_cb_func回调监听。发送数据接口需要在通道成功建立并有2个或以上成员(包括自己)时才能发送数据。

rts简单示例

下面是一个tcp通道的简单示例,所有回调接口建议post到UI线程处理

static std::string session_id_;//会话id
//会话发起结果回调
void RtsStartCb(int code, const char *session_id, int channel_type, const char* uid, const char *json_extension, const void *user_data)
{
    if (code == 200)
    {
        session_id_ = session_id;
    }
}
//一个会话邀请通知
void RtsStartNotifyCb(const char *session_id, int channel_type, const char* uid, const char *json_extension, const void *user_data)
{
    if (session_id_.empty())
    {
        session_id_ = session_id;
        nim_rts_ack(session_id, channel_type, true, "", nullptr, nullptr);
    }
    else
    {
        nim_rts_ack(session_id, channel_type, false, "", nullptr, nullptr);
    }
}
//收到被邀请方回复的通知回调
void RtsAckNotifyCb(const char *session_id, int channel_type, bool accept, const char *uid, const char *json_extension, const void *user_data)
{
    if (session_id == session_id_)
    {
        if (accept)
        {
            //对方同意,sdk底层开始连接
        }
        else
        {
            session_id_.clear();
        }
    }
}
//连接状态的回调
void RtsConnectNotifyCb(const char *session_id, int channel_type, int code, const char *json_extension, const void *user_data)
{
    if (session_id == session_id_)
    {
        if (code != 200)//连接异常,挂断
        {
            nim_rts_hangup(session_id_.c_str(), "", nullptr, nullptr);
            session_id_.clear();
        }
    }
}
//成员变化回调
void RtsMemberChangeCb(const char *session_id, int channel_type, int type, const char *uid, const char *json_extension, const void *user_data)
{
    if (session_id == session_id_)
    {
        if (type == kNIMRtsMemberStatusJoined)
        {
            //成员进入,此时可以在tcp通道发送数据
        }
    }
}
void RtsHangupNotifyCb(const char *session_id, const char *uid, const char *json_extension, const void *user_data)
{
    if (session_id == session_id_)
    {
        session_id_.clear();
    }
}
//收到会话中的数据
void RtsAppDataCb(const char *session_id, int channel_type, const char* uid, const char* data, unsigned int size, const char *json_extension, const void *user_data)
{
}
//初始化
void init()
{
    nim_rts_set_start_notify_cb_func(RtsStartNotifyCb, nullptr);
    nim_rts_set_ack_notify_cb_func(RtsAckNotifyCb, nullptr);
    nim_rts_set_connect_notify_cb_func(RtsConnectNotifyCb, nullptr);
    nim_rts_set_member_change_cb_func(RtsMemberChangeCb, nullptr);
    nim_rts_hangup(RtsHangupNotifyCb, nullptr);
    nim_rts_set_rec_data_cb_func(RtsAppDataCb, nullptr);
}
//发起一个会话
nim_rts_start(kNIMRtsChannelTypeTcp, "account", "", RtsStartCb, nullptr);
//发送数据
std::string data = "test 123";
nim_rts_send_data(session_id_.c_str(), kNIMRtsChannelTypeTcp, data.c_str(), data.size(), "");
//挂断
nim_rts_hangup(session_id_.c_str(), "", nullptr, nullptr);
session_id_.clear();

系统通知

除消息通道外,SDK 还提供系统通知这种通道用于消息之外的通知分发。目前有两种类型:内置系统通知和自定义系统通知。

所有的系统通知(包括内置系统通知和自定义系统通知)都是通过

C++

void UIReceiveSysmsgCallback(nim::SysMessage& msg)
{
    if (msg.type_ == nim::kNIMSysMsgTypeCustomP2PMsg || msg.type_ == nim::kNIMSysMsgTypeCustomTeamMsg)
    {

    }
    else
    {

    }
}

void foo()
{
    nim::SystemMsg::RegSysmsgCb(&OnReceiveSysmsgCallback);
}

C#

void OnReceivedSysNotification(object sender, NIMSysMsgEventArgs e)
{
    if (e.Message == null || e.Message.Content == null)
        return;

    if (e.Message.Content.MsgType == NIMSysMsgType.kNIMSysMsgTypeTeamInvite)
    {

    }
}

void foo()
{
    NIM.SysMessage.SysMsgAPI.ReceiveSysMsgHandler += OnReceivedSysNotification;
}

C

void CallbackSysmsgChange(const char *result, const char *json_extension, const void *callback)
{
    //解析result
}

typedef void(*nim_sysmsg_reg_sysmsg_cb)(const char *json_extension, nim_sysmsg_receive_cb_func cb, const void* user_data);

void foo()
{
    nim_sysmsg_reg_sysmsg_cb func = (nim_sysmsg_reg_sysmsg_cb) GetProcAddress(hInst, "nim_sysmsg_reg_sysmsg_cb");
    func("", &CallbackSysmsgChange, nullptr);
}

注册后的回调通知给APP。为了保证整个程序逻辑的一致性,APP 需要针对不同类型的系统通知进行相应的操作。

内置系统通知

这是由SDK 预定义的通知类型,目前仅支持几种群操作的通知,如被邀请入群,SDK 负责这些通知的持久化。

此外,SDK 提供了以下接口来获取和维护内置系统通知记录:

查询系统消息列表(按时间逆序查询,逆序排列):

void nim_sysmsg_query_msg_async(int limit_count, __int64 last_time, const char *json_extension, nim_sysmsg_query_cb_func cb, const void *user_data);

C++

void LoadEventsCb(int count, int unread, const std::list<nim::SysMessage> &result)
{

}

void foo()
{
    nim::SystemMsg::QueryMsgAsync(20, 0, &LoadEventsCb);
}

C#

NIM.SysMessage.SysMsgAPI.QueryMessage(100, 0, (r) =>
{

});

C

void CallbackSysmsgChange(int count, const char *result, const char *json_extension, const void *callback)
{
    //解析result
}

typedef void(*nim_sysmsg_query_msg_async)(int limit_count, __int64 last_time, const char *json_extension, nim_sysmsg_query_cb_func cb, const void* user_data);

void foo()
{
    nim_sysmsg_query_msg_async func = (nim_sysmsg_query_msg_async) GetProcAddress(hInst, "nim_sysmsg_query_msg_async");
    func(20, 0, "", &CallbackSysmsgChange, nullptr);
}

查询未读消息数:

void nim_sysmsg_query_unread_count(const char *json_extension, nim_sysmsg_res_cb_func cb, const void *user_data);

C++

void OnQuerySysmsgUnreadCb(nim::NIMResCode res_code, int unread_count)
{
    if (res_code == 200)
        ···
}

void foo()
{
    nim::SystemMsg::QueryUnreadCount(&OnQuerySysmsgUnreadCb);
}

C#

NIM.SysMessage.SysMsgAPI.QueryUnreadCount((response, count) =>
{

});

C

void CallbackNotifySysmsgRes(int res_code, int unread_count, const char *json_extension, const void *callback)
{

}

typedef void(*nim_sysmsg_query_unread_count)(const char *json_extension, nim_sysmsg_res_cb_func cb, const void *user_data);

void foo()
{
    nim_sysmsg_query_unread_count func = (nim_sysmsg_query_unread_count) GetProcAddress(hInst, "nim_sysmsg_query_unread_count");
    func("", &CallbackNotifySysmsgRes, nullptr);
}

设置消息状态:

void nim_sysmsg_set_status_async(__int64 msg_id, NIMSysMsgStatus status, const char *json_extension, nim_sysmsg_res_ex_cb_func cb, const void *user_data);

C++

void SetStatusCb(nim::NIMResCode code, __int64 msg_id, int unread)
{

}

void foo(__int64 msg_id)
{
    nim::SystemMsg::SetStatusAsync(msg_id_, nim::kNIMSysMsgStatusInvalid, &SetStatusCb);
}

C#

void foo(long msg_id)
{
    NIM.SysMessage.SysMsgAPI.SetMsgStatus(msg_id, NIM.SysMessage.NIMSysMsgStatus.kNIMSysMsgStatusRead, 
        (res_code, _msg_id, unread_count, json_extension, user_data) =>
    {

    });
}

C

void CallbackNotifySingleSysmsg(int res_code, __int64 msg_id, int unread_count, const char *json_extension, const void *callback)
{

}

typedef void(*nim_sysmsg_set_status_async)(__int64 msg_id, nim::NIMSysMsgStatus status, const char *json_extension, nim_sysmsg_res_ex_cb_func cb, const void* user_data);

void foo(__int64 msg_id, nim::NIMSysMsgStatus status)
{
    nim_sysmsg_set_status_async func = (nim_sysmsg_set_status_async) GetProcAddress(hInst, "nim_sysmsg_set_status_async");
    func(msg_d, status, "", &CallbackNotifySingleSysmsg, nullptr);
}

删除单条消息:

void nim_sysmsg_delete_async(__int64 msg_id, const char *json_extension, nim_sysmsg_res_ex_cb_func cb, const void *user_data);

C++

void DeleteCb(nim::NIMResCode code, __int64 msg_id, int unread)
{

}

void foo(__int64 msg_id)
{
    nim::SystemMsg::DeleteAsync(msg_id, &DeleteCb);
}

C#

void foo(long msg_id)
{
    NIM.SysMessage.SysMsgAPI.DeleteByMsgId(msg_id,
        (res_code, _msg_id, unread_count, json_extension, user_data) =>
    {

    });
}

C

void CallbackNotifySingleSysmsg(int res_code, __int64 msg_id, int unread_count, const char *json_extension, const void *callback)
{

}

typedef void(*nim_sysmsg_delete_async)(__int64 msg_id, const char *json_extension, nim_sysmsg_res_ex_cb_func cb, const void *user_data);

void foo(__int64 msg_id)
{
    nim_sysmsg_delete_async func = (nim_sysmsg_delete_async) GetProcAddress(hInst, "nim_sysmsg_delete_async");
    func(msg_d, "", &CallbackNotifySingleSysmsg, nullptr);
}

设置全部消息为已读:

void nim_sysmsg_read_all_async(const char *json_extension, nim_sysmsg_res_cb_func cb, const void *user_data);

C++

void SysMsgReadAllCb(nim::NIMResCode code, int unread)
{

}

void foo()
{
    nim::SystemMsg::ReadAllAsync(&SysMsgReadAllCb);
}

C#

void foo()
{
    NIM.SysMessage.SysMsgAPI.SetAllMsgRead(
        (res_code, unread_count, json_extension, user_data) =>
    {

    });
}

C

void CallbackNotifySysmsgRes(int res_code, int unread_count, const char *json_extension, const void *callback)
{

}

typedef void(*nim_sysmsg_read_all_async)(const char *json_extension, nim_sysmsg_res_cb_func cb, const void* user_data);

void foo()
{
    nim_sysmsg_read_all_async func = (nim_sysmsg_read_all_async) GetProcAddress(hInst, "nim_sysmsg_read_all_async");
    func("", &CallbackNotifySysmsgRes, nullptr);
}

删除全部消息:

void nim_sysmsg_delete_all_async(const char *json_extension, nim_sysmsg_res_cb_func cb, const void *user_data);

C++

void DeleteAllCb(nim::NIMResCode res_code, int unread)
{

}

void foo()
{
    nim::SystemMsg::DeleteAllAsync(&DeleteAllCb);
}

C#

void foo()
{
    NIM.SysMessage.SysMsgAPI.DeleteAll(
        (res_code, unread_count, json_extension, user_data) =>
    {

    });
}

C

void CallbackNotifySysmsgRes(int res_code, int unread_count, const char *json_extension, const void *callback)
{

}

typedef void(*nim_sysmsg_delete_all_async)(const char *json_extension, nim_sysmsg_res_cb_func cb, const void *user_data);

void foo()
{
    nim_sysmsg_delete_all_async func = (nim_sysmsg_delete_all_async) GetProcAddress(hInst, "nim_sysmsg_delete_all_async");
    func("", &CallbackNotifySysmsgRes, nullptr);
}

自定义系统通知

除了内置系统通知外,SDK 也额外提供了自定义系统给开发者,方便开发者进行业务逻辑的通知。这个通知既可以由客户端发起也可以由开发者服务器发起。

客户端发起的自定义通知,该类型通知格式由开发者自定义(kNIMSysMsgKeyAttach 里),SDK 仅负责发送、接收,支持在线或离线发送,且支持点对点通知和群通知,不做任何解析,也不会存储,因此也不会在聊天记录中体现,可以使用的场景例如发送正在输入的状态等。代码示例如下:

C++

void foo()
{
    Json::Value json;
    Json::FastWriter writer;
    json["id"] = "1";

    nim::SysMessage msg;
    msg.receiver_accid_ = ;    //接收者id
    msg.sender_accid_ = ;     //自己id
    msg.client_msg_id_ = QString::GetGUID();    //本地定义的消息id
    msg.attach_ = writer.write(json);            //通知附件内容
    msg.type_ = nim::kNIMSysMsgTypeCustomP2PMsg; //通知类型

    nim::SystemMsg::SendCustomNotificationMsg(msg.ToJsonString());
}

C#

void foo()
{
    NIM.SysMessage.NIMSysMessageContent content = new NIM.SysMessage.NIMSysMessageContent();
    content.ClientMsgId = Guid.NewGuid().ToString();
    content.ReceiverId = ""; //接收者id
    content.SenderId = ""; //自己id
    if (_sessionType == NIM.Session.NIMSessionType.kNIMSessionTypeP2P)
        content.MsgType = NIM.SysMessage.NIMSysMsgType.kNIMSysMsgTypeCustomP2PMsg; //通知类型
    else if (_sessionType == NIM.Session.NIMSessionType.kNIMSessionTypeTeam)
        content.MsgType = NIM.SysMessage.NIMSysMsgType.kNIMSysMsgTypeCustomTeamMsg;
    content.Attachment = ""; //通知附件内容
    NIM.SysMessage.SysMsgAPI.SendCustomMessage(content);
}

C

typedef void(*nim_sysmsg_send_custom_notification)(const char *json_msg, const char *json_extension);

void foo()
{
    //json_msg:key的定义详见系统消息字段nim_sysmsg_def.h
    Json::Value json_msg;
    json_msg[kNIMSysMsgKeyToAccount] = ; //接收者id
    json_msg[kNIMSysMsgKeyFromAccount] = ; //自己id
    json_msg[kNIMSysMsgKeyLocalClientMsgId] = 本地定义的消息id; 
    json_msg[kNIMSysMsgKeyTime] = ; //时间戳
    json_msg[kNIMSysMsgKeyAttach] = ; //通知附件内容
    json_msg[kNIMSysMsgKeyType] = ; //通知类型

    nim_sysmsg_send_custom_notification func = (nim_sysmsg_send_custom_notification) GetProcAddress(hInst, "nim_sysmsg_send_custom_notification");
    func(json_msg.toStyledString().c_str(), nullptr);
}

客户端发起的自定义通知的回执结果通过APP 预先通过

void nim_sysmsg_reg_sysmsg_cb(const char *json_extension, nim_sysmsg_receive_cb_func cb, const void *user_data);

注册的回调告知开发者。代码实力如下:

C++

void OnReceiveSysmsgCallback( const nim::SysMessage& msg )
{

}

void foo()
{
    nim::SystemMsg::RegSysmsgCb(&OnReceiveSysmsgCallback);
}

C#

void OnReceivedSysNotification(object sender, NIMSysMsgEventArgs e)
{
    if (e.Message == null || e.Message.Content == null)
        return;

    if (e.Message.Content.MsgType == NIMSysMsgType.kNIMSysMsgTypeTeamInvite)
    {
        ···
    }
}

void foo()
{
    NIM.SysMessage.SysMsgAPI.ReceiveSysMsgHandler += OnReceivedSysNotification;
}

C

void CallbackSysmsgChange(const char *result, const char *json_extension, const void *callback)
{
    // 解析result
}

typedef void(*nim_sysmsg_reg_sysmsg_cb)(const char *json_extension, nim_sysmsg_receive_cb_func cb, const void* user_data);

void foo()
{
    nim_sysmsg_reg_sysmsg_cb func = (nim_sysmsg_reg_sysmsg_cb) GetProcAddress(hInst, "nim_sysmsg_reg_sysmsg_cb");
    func("", &CallbackSysmsgChange, nullptr);
}

此外,自定义系统通知还提供了属性设置如下:

SDK 并不负责自定义通知的持久化,APP 需要根据自己的业务逻辑按需进行解析和持久化的工作。

用户名片

概述

SDK 提供了用户帐号资料管理。以下几个接口仅当选择云信托管用户资料时有效,如果开发者不希望云信获取自己的用户数据,则需自行维护用户资料。

nim_user_def.h 里定义了用户信息的字段 kUInfoKeyXXX。

用户信息变更会通过注册的全用户信息变更通知回调告知APP:

C++

void OnUserInfoChange(const std::list<nim::UserNameCard> &uinfo_list)
{
    for (auto& info : uinfo_list)
    {

        if (info.ExistValue(nim::kUserNameCardKeyName) || info.ExistValue(nim::kUserNameCardKeyIconUrl)) //用户名或头像变化了
            ···;
        if (info.ExistValue((nim::UserNameCardValueKey)(nim::kUserNameCardKeyAll - nim::kUserNameCardKeyName - nim::kUserNameCardKeyIconUrl))) //用户其他信息变化了
            ···;
    }
}

void foo()
{
    //向SDK注册监听用户名片变化
    nim::User::RegUserNameCardChangedCb(&OnUserInfoChange);
}

C#

void OnUserNameCardChanged(object sender, UserNameCardChangedArgs e)
{

}

void foo()
{
    NIM.User.UserAPI.UserNameCardChangedHandler += OnUserNameCardChanged;
}

C

void CallbackUserNameCardChange(const char *result_json, const char *json_extension, const void *callback)
{
    // 解析result_json
}

typedef void (*nim_user_reg_user_name_card_changed_cb)(const char *json_extension, nim_user_name_card_change_cb_func cb, const void *user_data);

void foo()
{
    nim_user_reg_user_name_card_changed_cb func = (nim_user_reg_user_name_card_changed_cb) GetProcAddress(hInst, "nim_user_reg_user_name_card_changed_cb");
    func("", &CallbackUserNameCardChange, nullptr);
}

获取本地用户信息

C++

void OnGetUserCard(const std::list<nim::UserNameCard> &json_result)
{
    for (auto& info : json_result)
    {
        ···
    }
}

void foo()
{
    std::list<std::string> account_list;
    account_list.push_back("test1");
    account_list.push_back("test2");
    nim::User::GetUserNameCard(account_list, &OnGetUserCard);
}

C#

UserAPI.GetUserNameCard(new List<string>() {"test1", "test2"}, (ret) =>
{
    if (ret.Any())
    {

    }
});

C

void CallbackGetUserNameCard(const char *result_json, const char *json_extension, const void *callback)
{
    // 解析result_json
}

typedef void (*nim_user_get_user_name_card)(const char *accids, const char *json_extension, nim_user_get_user_name_card_cb_func cb, const void *user_data);

void foo(const std::list<std::string>& accids)
{
    nim_user_get_user_name_card func = (nim_user_get_user_name_card) GetProcAddress(hInst, "nim_user_get_user_name_card");

    Json::Value values;
    for (auto iter = accids.cbegin(); iter != accids.cend(); ++iter)
        values.append(*iter);

    Json::FastWriter fw;

    func(fw.write(values).c_str(), "", &CallbackGetUserNameCard, nullptr);
}

获取服务器用户信息

单次查询限制150人。

C++

void OnGetUserCard(const std::list<nim::UserNameCard> &json_result)
{
    for (auto& info : json_result)
    {
        ···
    }
}

void foo()
{
    std::list<std::string> account_list;
    account_list.push_back("test1");
    account_list.push_back("test2");
    nim::User::GetUserNameCardOnline(account_list, &OnGetUserCard);
}

C#

NIM.User.UserAPI.QueryUserNameCardOnline(new List<string>() {"test1", "test2"}, (ret) =>
{
    if (ret == null || !ret.Any())
    {
        return;
    }

});

C

void CallbackGetUserNameCard(const char *result_json, const char *json_extension, const void *callback)
{
    // 解析result_json
}

typedef void (*nim_user_get_user_name_card_online)(const char *accids, const char *json_extension, nim_user_get_user_name_card_cb_func cb, const void *user_data);

void foo(const std::list<std::string>& accids)
{
    nim_user_get_user_name_card_online func = (nim_user_get_user_name_card_online) GetProcAddress(hInst, "nim_user_get_user_name_card_online");

    Json::Value values;
    for (auto iter = accids.cbegin(); iter != accids.cend(); ++iter)
        values.append(*iter);

    Json::FastWriter fw;

    func(fw.write(values).c_str(), "", &CallbackGetUserNameCard, nullptr);
}

编辑用户资料

例:

C++

 void OnUpdateMyInfo(nim::NIMResCode res) 
{
    if (res == nim::kNIMResSuccess)
    {

    }
}

void foo()
{
    nim::UserNameCard info;
    info.SetName("new_name");
    nim::User::UpdateMyUserNameCard(info, &OnUpdateMyInfo);
}

C#

void foo()
{
    NIM.User.UserNameCard user_card = new NIM.User.UserNameCard;
    user_card.NickName = "new_name";
    NIM.User.UserAPI.UpdateMyCard(user_card, (a) =>
    {
        if (a == NIM.ResponseCode.kNIMResSuccess)
        {
            MessageBox.Show("修改成功");
        }
        else
        {
            MessageBox.Show("修改失败");
        }
    });
}

C

void CallbackUpdateNameCard(int res_code, const char *json_extension, const void *callback)
{
    if (res_code == kNIMResSuccess)
    ...
    else
    ...
}

typedef void (*nim_user_update_my_user_name_card)(const char *info_json, const char *json_extension, nim_user_update_my_name_card_cb_func cb, const void *user_data);

void foo()
{
    //修改昵称
    Json::Value values;
    values[kUInfoKeyAccid] = "litianyi02";
    values[kUInfoKeyName] = "修改后的大意的昵称";

    nim_user_update_my_user_name_card func = (nim_user_update_my_user_name_card) GetProcAddress(hInst, "nim_user_update_my_user_name_card");
    func(values.toStyledString().c_str(), nullptr, &CallbackUpdateNameCard, nullptr);
}

用户关系托管

SDK 提供了用户好友关系管理,以及对用户会话的消息设置。在云信中,不是好友也允许聊天。好友关系如果不托管给云信,开发者需要自己在应用服务器维护。

添加/被添加,请求/被请求,删除/被删除的通知以及多端同步等通知通过注册的好友数据变更通知回调告知APP:

例:

C++

void OnFriendListChange(const nim::FriendChangeEvent& change_event)
{
    switch (change_event.type_)
    {
    case nim::kNIMFriendChangeTypeDel:
    {
        break;
    }
    case nim::kNIMFriendChangeTypeRequest:
    {
        break;
    }
    case nim::kNIMFriendChangeTypeSyncList:
    {
        break;
    }
    case nim::kNIMFriendChangeTypeUpdate:
    {
        break;
    }
    default:
        break;
    }
}

void foo()
{
    //向SDK注册监听好友列表变化
    nim::Friend::RegChangeCb(&OnFriendListChange);
}

C#

void OnFriendChanged(object sender, NIM.Friend.NIMFriendProfileChangedArgs args)
{
    if (args.ChangedInfo == null)
        return;

    if (args.ChangedInfo.ChangedType == NIM.Friend.NIMFriendChangeType.kNIMFriendChangeTypeDel)
    {

    }
    if (args.ChangedInfo.ChangedType == NIM.Friend.NIMFriendChangeType.kNIMFriendChangeTypeRequest)
    {

    }
    if (args.ChangedInfo.ChangedType == NIM.Friend.NIMFriendChangeType.kNIMFriendChangeTypeSyncList)
    {

    }
    if (args.ChangedInfo.ChangedType == NIM.Friend.NIMFriendChangeType.kNIMFriendChangeTypeUpdate)
    {

    }
}

void foo()
{
    NIM.Friend.FriendAPI.FriendProfileChangedHandler += OnFriendChanged;
}

C

void CallbackFriendChange(NIMFriendChangeType type, const char *result_json, const char *json_extension, const void *user_data)
{
    switch (type)
    {
        case kNIMFriendChangeTypeRequest:
            // 解析result_json
            break;
        case kNIMFriendChangeTypeDel:
            // 解析result_json
            break;
        ...
}

typedef void(*nim_friend_reg_changed_cb)(const char *json_extension, nim_friend_change_cb_func cb, const void *user_data);

void foo()
{
    nim_friend_reg_changed_cb func = (nim_friend_reg_changed_cb) GetProcAddress(hInst, "nim_friend_reg_changed_cb");
    func("", &CallbackFriendChange, nullptr);
}

好友关系

黑名单

云信中,黑名单和用户关系是互相独立的,即修改用户关系不会影响黑名单关系,同时,修改黑名单也不会对用户关系进行操作。

黑名单列表有本地缓存,缓存会在手动/自动登录后与服务器自动进行同步更新。通过注册用户关系变更通知回调获取当前数据变化:

例:

C++

void OnMuteBlackEventCallback(const nim::SpecialRelationshipChangeEvent& change_event)
{
    switch (change_event.type_)
    {
    case nim::NIMUserSpecialRelationshipChangeType::kNIMUserSpecialRelationshipChangeTypeMarkBlack:
    {
        break;
    }
    case nim::NIMUserSpecialRelationshipChangeType::kNIMUserSpecialRelationshipChangeTypeMarkMute:
    {
        break;
    }
    case nim::NIMUserSpecialRelationshipChangeType::kNIMUserSpecialRelationshipChangeTypeSyncMuteAndBlackList:
    {
        break;
    }
    }
}

void foo()
{
    nim::User::RegSpecialRelationshipChangedCb(&OnMuteBlackEventCallback);
}

C#

void OnUserRelationshipSync(object sender, UserRelationshipSyncArgs e)
{
    if (e.Items == null)
        return;
}

void OnUserRelationshipChanged(object sender, UserRelationshipChangedArgs e)
{
    if (e.ChangedType == NIMUserRelationshipChangeType.AddRemoveBlacklist)
    {
    }
}

void foo()
{
    NIM.User.UserAPI.UserRelationshipListSyncHander += OnUserRelationshipSync;
    NIM.User.UserAPI.UserRelationshipChangedHandler += OnUserRelationshipChanged;
}

C

void CallbackUserRelationshipChanged(NIMUserSpecialRelationshipChangeType type, const char *result_json ,const char *json_extension, const void *user_data)
{
    switch (type)
    {
        case kNIMUserSpecialRelationshipChangeTypeMarkBlack:
            //解析result_json
            break;
        case kNIMUserSpecialRelationshipChangeTypeMarkMute:
            //解析result_json
            break;
        ...
    }
}

typedef    void (*nim_user_reg_special_relationship_changed_cb)(const char *json_extension, nim_user_special_relationship_change_cb_func cb, const void *user_data);

void foo()
{
    nim_user_reg_special_relationship_changed_cb func = (nim_user_reg_special_relationship_changed_cb) GetProcAddress(hInst, "nim_user_reg_special_relationship_changed_cb");
    func(nullptr, &CallbackUserRelationshipChanged, nullptr);
}

消息提醒

云信中,可以单独设置是否开启某个用户的消息提醒,即对某个用户静音。静音关系和用户关系是互相独立的,修改用户关系不会影响静音关系,同时,修改静音关系也不会对用户关系进行操作。

静音名单列表有本地缓存,缓存会在手动/自动登录后与服务器自动进行同步更新。通过注册用户关系变更通知回调获取当前数据变化:

C++

void OnMuteBlackEventCallback(const nim::SpecialRelationshipChangeEvent& change_event)
{
    switch (change_event.type_)
    {
    case nim::NIMUserSpecialRelationshipChangeType::kNIMUserSpecialRelationshipChangeTypeMarkBlack:
    {
        break;
    }
    case nim::NIMUserSpecialRelationshipChangeType::kNIMUserSpecialRelationshipChangeTypeMarkMute:
    {
        break;
    }
    case nim::NIMUserSpecialRelationshipChangeType::kNIMUserSpecialRelationshipChangeTypeSyncMuteAndBlackList:
    {
        break;
    }
    }
}

void foo()
{
    nim::User::RegSpecialRelationshipChangedCb(&OnMuteBlackEventCallback);
}

C#

void OnUserRelationshipSync(object sender, UserRelationshipSyncArgs e)
{
    if (e.Items == null)
        return;
}

void OnUserRelationshipChanged(object sender, UserRelationshipChangedArgs e)
{
    if (e.ChangedType == NIMUserRelationshipChangeType.AddRemoveBlacklist)
    {
    }
}

void foo()
{
    NIM.User.UserAPI.UserRelationshipListSyncHander += OnUserRelationshipSync;
    NIM.User.UserAPI.UserRelationshipChangedHandler += OnUserRelationshipChanged;
}

C

void CallbackUserRelationshipChanged(NIMUserSpecialRelationshipChangeType type, const char *result_json ,const char *json_extension, const void *user_data)
{
    switch (type)
    {
        case kNIMUserSpecialRelationshipChangeTypeMarkBlack:
            //解析result_json
            break;
        case kNIMUserSpecialRelationshipChangeTypeMarkMute:
            //解析result_json
            break;
        ...
    }
}

typedef    void (*nim_user_reg_special_relationship_changed_cb)(const char *json_extension, nim_user_special_relationship_change_cb_func cb, const void *user_data);

void foo()
{
    nim_user_reg_special_relationship_changed_cb func = (nim_user_reg_special_relationship_changed_cb) GetProcAddress(hInst, "nim_user_reg_special_relationship_changed_cb");
    func(nullptr, &CallbackUserRelationshipChanged, nullptr);
}

NOS云存储服务

下载

下载资源,回调函数包括了下载结果以及下载进度

下载结果回调函数
@param rescode:200表示下载成功
@param file_path:下载文件的完整路径
@param call_id:对话id
@param res_id:消息id,与对话id一起可定位到具体的消息,然后根据rescode调整UI呈现
typedef void(*nim_nos_download_cb_func)(int rescode, const char *file_path, const char *call_id, const char *res_id, const char *json_extension, const void *user_data);

下载进度回调函数
@param downloaded_size:已下载大小
@param file_size:文件大小,单位都是字节
typedef void(*nim_nos_download_prg_cb_func)(__int64 downloaded_size, __int64 file_size, const char *json_extension, const void *user_data);

示例代码如下:

C++

void DownloadResourceCallback(nim::NIMResCode res_code, const std::string& file_path, const std::string& call_id, const std::string& res_id)
{

}

void DownloadResourceProgressCallback(__int64 downloaded_size, __int64 file_size)
{

}

void foo(const IMMessage& msg)
{
    nim::NOS::FetchMedia(msg, &DownloadResourceCallback, &DownloadResourceProgressCallback);
}

C#

void foo(NIM.NIMIMMessage message)
{
    NIM.Nos.NosAPI.DownloadMedia(message, (rescode, filePath, callId, resId) =>
    {
        if (rescode == 200)
        {

        }
    },
    (prgData) =>
    {

    });
}

C

void CallbackDownload(int res_code, const char *file_path, const char *call_id, const char *res_id, const char *json_extension, const void *user_data)
{
    ···
}

void CallbackProgress(__int64 completed_size, __int64 total_size, const char *json_extension, const void *callback)
{
    ···
}

typedef void(*nim_nos_download_media)(const char *json_msg, nim_nos_download_cb_func callback_result, const void *download_user_data, nim_nos_download_prg_cb_func prg_cb, const void *prg_user_data);

void foo(const IMMessage& msg)
{
    nim_nos_download_media func = (nim_nos_download_media) GetProcAddress(hInst, "nim_nos_download_media");
    func(msg.ToJsonString(false).c_str(), &CallbackDownload, nullptr, &CallbackProgress, nullptr);
}

下载与暂停

从3.3.0开始SDK新增了下载的扩展类接口,支持断点续传,开发者可以通过该接口设置自定义的超时时间,下载文件的本地存放路径,以及获取下载瞬时速度、下载平均速度等信息。

C++

//下载结果回调
void CallbackDownload(nim::NIMResCode res_code, const nim::DownloadMediaResult& result)
{
    if (res_code == kNIMResSuccess)
    {
        //下载成功
    }
    else if (res_code == kNIMLocalResMsgNosDownloadCancel)
    {
        //下载暂停
    }
    else
    {
        //下载失败
    }
}

//下载过程回调,参数:当前已下载文件大小, 文件大小, 下载相关信息
void CallbackHttpPro(__int64 completed_size, __int64 file_size, const ProgressData& data)
{
    ...
}

//下载瞬时速度回调
void CallbackSpeed(__int64 speed)
{
    if (speed > 0)
    {
        char res[1024];
        sprintf_s(res, "speed : %lld B/s(%lf KB/s)", speed, (static_cast<double>(speed))/1024);
    }
}

static __int64 actual_size_ = 0;
//下载结束后信息回调,参数:此次下载文件的大小, 平均速度
void CallbackTransferInfo(__int64 actual_size, __int64 speed)
{    
    actual_size_ += actual_size;
    char res[1024];
    sprintf_s(res, "average speed : %lld(%lf KB/s)", speed, (static_cast<double>(speed))/1024);

    sprintf_s(res, "actual size : %lld, block size : %lld", actual_size_, actual_size);
}

//发起下载任务
void foo_start_http_download(const std::string& task_uuid)
{
    Json::FastWriter writer;
    Json::Value value;
    value[kNIMNosLowLimit] = ; //int(选填) HTTP通用配置,传输速度,每秒字节数(默认10)
    value[kNIMNosLowTime] = ;  //int(选填) HTTP通用配置,传输过程中当low_time秒时间内传输速度小于low_limit时(字节每秒),下载任务会返回超时而取消(默认60)
    value[kNIMNosTimeout] = ;  //int(选填) HTTP通用配置,超时时间,单位ms,下载时最小10000,上传时最小30000,不设置默认30000
    value[kNIMNosSaveAsFilePath] = ; //string(选填) HTTP下载任务的文件存放本地路径,不填则默认路径回调中返回
    value[kNIMNosFileSize] = ; //int64(断点续传必填) HTTP下载任务的文件大小,需要续传功能必填,单位Byte
    value[kNIMNosTaskId] = task_uuid;   //string(断点续传必填) HTTP通用配置,任务ID,如果传入的ID是曾经未完成的传输任务,则会开始续传(用户需要保证ID的唯一性)
    nim::NOS::DownloadResourceEx(url, writer.write(value), &CallbackDownload, &CallbackHttpPro, &CallbackSpeed, &CallbackTransferInfo);
}

//暂停下载任务
void foo_pause_http_download(const std::string& task_uuid)
{
    nim::NOS::StopDownloadResourceEx(task_uuid);
}

C#

...

C

//下载结果回调
static void CallbackDownloadEx(int res_code, const char *file_path, const char *call_id, const char *res_id, const char *json_extension, const void *user_data)
{
    if (res_code == kNIMResSuccess)
    {
        //下载成功
    }
    else if (res_code == kNIMLocalResMsgNosDownloadCancel)
    {
        //下载暂停
    }
    else
    {
        //下载失败
    }
}

//下载过程回调,参数:当前已下载文件大小, 文件大小, 下载相关信息
static void CallbackProgressEx(int64_t completed_size, int64_t total_size, const char *json_extension, const void *callback)
{
    ···
}

//下载瞬时速度回调
static void CallbackSpeed(int64_t speed, const char *json_extension, const void *callback)
{
    if (speed > 0)
    {
        char res[1024];
        sprintf_s(res, "speed : %lld B/s(%lf KB/s)", speed, (static_cast<double>(speed))/1024);
    }
}

static __int64 actual_size_ = 0;
//下载结束后信息回调,参数:此次下载文件的大小, 平均速度
static void CallbackTransferInfo(int64_t actual_size, int64_t speed, const char *json_extension, const void *callback)
{
    actual_size_ += actual_size;
    char res[1024];
    sprintf_s(res, "average speed : %lld(%lf KB/s)", speed, (static_cast<double>(speed))/1024);

    sprintf_s(res, "actual size : %lld, block size : %lld", actual_size_, actual_size);
}

typedef void(*nim_nos_download_ex)(const char *nos_url, const char *json_extension, nim_nos_download_cb_func callback_result, const void *res_user_data, nim_nos_download_prg_cb_func prg_cb, const void *prg_user_data, nim_nos_download_speed_cb_func speed_cb, const void *speed_user_data, nim_nos_download_info_cb_func info_cb, const void *info_user_data);

typedef void(*nim_nos_stop_download_ex)(const char *task_id, const char *json_extension);

//发起下载任务
void foo_start_http_download(const std::string& task_uuid)
{
    Json::FastWriter writer;
    Json::Value value;
    value[kNIMNosLowLimit] = ; //int(选填) HTTP通用配置,传输速度,每秒字节数(默认10)
    value[kNIMNosLowTime] = ;  //int(选填) HTTP通用配置,传输过程中当low_time秒时间内传输速度小于low_limit时(字节每秒),下载任务会返回超时而取消(默认60)
    value[kNIMNosTimeout] = ;  //int(选填) HTTP通用配置,超时时间,单位ms,下载时最小10000,上传时最小30000,不设置默认30000
    value[kNIMNosSaveAsFilePath] = ; //string(选填) HTTP下载任务的文件存放本地路径,不填则默认路径回调中返回
    value[kNIMNosFileSize] = ; //int64(断点续传必填) HTTP下载任务的文件大小,需要续传功能必填,单位Byte
    value[kNIMNosTaskId] = task_uuid;   //string(断点续传必填) HTTP通用配置,任务ID,如果传入的ID是曾经未完成的传输任务,则会开始续传(用户需要保证ID的唯一性)

    nim_nos_download_ex func = (nim_nos_download_ex) GetProcAddress(hInst, "nim_nos_download_ex");
    func(nos_url.c_str(), writer.write(value).c_str(), &CallbackDownloadEx, callback_result_userdata, &CallbackProgressEx, callback_progress_pointer, &CallbackSpeed, callback_speed_pointer, &CallbackTransferInfo, callback_transfer_pointer);
}    

//暂停下载任务
void foo_pause_http_download(const std::string& task_uuid)
{
    nim_nos_stop_download_ex func = (nim_nos_stop_download_ex) GetProcAddress(hInst, "nim_nos_stop_download_ex");
    func(task_uuid.c_str(), nullptr);
}

上传

上传资源,回调函数包括了上传结果以及上传进度

上传结果回调函数
@param rescode:错误码,200表示成功
@param url:上传成功后返回的url
typedef void(*nim_nos_upload_cb_func)(int rescode, const char *url, const char *json_extension, const void *user_data);

上传进度回调函数
@uploaded_size:已上传大小
@file_size:文件大小
typedef void(*nim_nos_upload_prg_cb_func)(__int64 uploaded_size, __int64 file_size, const char *json_extension, const void *user_data);    

示例代码如下:

C++

void OnUploadCallback(int res_code, const std::string& url)
{

}

void foo()
{
    nim::NOS::UploadResource("D://test_file.txt", &OnUploadCallback);
}

C#

void foo()
{
    NIM.Nos.NosAPI.Upload("D://test_file.txt", (res_code, url) =>
    {
    }, null);
}

C

void CallbackUpload(int res_code, const char *url, const char *json_extension, const void *user_data)
{
    ···
}

void CallbackProgress(__int64 completed_size, __int64 total_size, const char *json_extension, const void *callback)
{
    ···
}

typedef void(*nim_nos_upload)(const char *local_file, nim_nos_upload_cb_func callback_result, const void *res_user_data, nim_nos_upload_prg_cb_func prg_cb, const void *prg_user_data);

void foo()
{
    nim_nos_upload func = (nim_nos_upload) GetProcAddress(hInst, "nim_nos_upload");
    func("D://test_file.txt", &CallbackUpload, nullptr, &CallbackProgress, nullptr);
}

上传与暂停

从3.3.0开始SDK新增了上传的扩展类接口,支持断点续传,开发者可以通过该接口设置自定义的超时时间,以及获取上传瞬时速度、上传平均速度等信息。

C++

//上传结果回调
void CallbackUpload(nim::NIMResCode res_code, const nim::DownloadMediaResult& result)
{
    if (res_code == kNIMResSuccess)
    {
        //上传成功
    }
    else if (res_code == kNIMLocalResMsgNosUploadCancel)
    {
        //上传暂停
    }
    else
    {
        //上传失败
    }
}

//上传过程回调,参数:当前已上传文件大小, 文件大小, 上传相关信息
void CallbackHttpPro(__int64 completed_size, __int64 file_size, const ProgressData& data)
{
    ...
}

//上传瞬时速度回调
void CallbackSpeed(__int64 speed)
{
    if (speed > 0)
    {
        char res[1024];
        sprintf_s(res, "speed : %lld B/s(%lf KB/s)", speed, (static_cast<double>(speed))/1024);
    }
}

static __int64 actual_size_ = 0;
//上传结束后信息回调,参数:此次上传文件的大小, 平均速度
void CallbackTransferInfo(__int64 actual_size, __int64 speed)
{    
    actual_size_ += actual_size;
    char res[1024];
    sprintf_s(res, "average speed : %lld(%lf KB/s)", speed, (static_cast<double>(speed))/1024);

    sprintf_s(res, "actual size : %lld, block size : %lld", actual_size_, actual_size);
}

//发起上传任务
void foo_start_http_upload(const std::string& task_uuid)
{
    Json::FastWriter writer;
    Json::Value value;
    value[kNIMNosLowLimit] = ; //int(选填) HTTP通用配置,传输速度,每秒字节数(默认10)
    value[kNIMNosLowTime] = ;  //int(选填) HTTP通用配置,传输过程中当low_time秒时间内传输速度小于low_limit时(字节每秒),下载任务会返回超时而取消(默认60)
    value[kNIMNosTimeout] = ;  //int(选填) HTTP通用配置,超时时间,单位ms,下载时最小10000,上传时最小30000,不设置默认30000
    value[kNIMNosNeedContinueTrans] = ; //bool 需要支持断点续传True
    value[kNIMNosTaskId] = task_uuid;   //string(断点续传必填) HTTP通用配置,任务ID,如果传入的ID是曾经未完成的传输任务,则会开始续传(用户需要保证ID的唯一性)
    nim::NOS::UploadResourceEx(file_path, writer.write(value), &CallbackUpload, &CallbackHttpPro, &CallbackSpeed, &CallbackTransferInfo);
}

//暂停上传任务
void foo_pause_http_upload(const std::string& task_uuid)
{
    nim::NOS::StopUploadResourceEx(task_uuid);
}

C#

...

C

//上传结果回调
static void CallbackUploadEx(int res_code, const char *url, const char *json_extension, const void *user_data)
{
    if (res_code == kNIMResSuccess)
    {
        //下载成功
    }
    else if (res_code == kNIMLocalResMsgNosDownloadCancel)
    {
        //下载暂停
    }
    else
    {
        //下载失败
    }
}

//上传过程回调,参数:当前已上传文件大小, 文件大小, 上传相关信息
static void CallbackProgressEx(int64_t completed_size, int64_t total_size, const char *json_extension, const void *callback)
{
    ···
}

//上传瞬时速度回调
static void CallbackSpeed(int64_t speed, const char *json_extension, const void *callback)
{
    if (speed > 0)
    {
        char res[1024];
        sprintf_s(res, "speed : %lld B/s(%lf KB/s)", speed, (static_cast<double>(speed))/1024);
    }
}

static __int64 actual_size_ = 0;
//上传结束后信息回调,参数:此次上传文件的大小, 平均速度
static void CallbackTransferInfo(int64_t actual_size, int64_t speed, const char *json_extension, const void *callback)
{
    actual_size_ += actual_size;
    char res[1024];
    sprintf_s(res, "average speed : %lld(%lf KB/s)", speed, (static_cast<double>(speed))/1024);

    sprintf_s(res, "actual size : %lld, block size : %lld", actual_size_, actual_size);
}

typedef void(*nim_nos_upload_ex)(const char *local_file, const char *json_extension, nim_nos_upload_cb_func callback_result, const void *res_user_data, nim_nos_upload_prg_cb_func prg_cb, const void *prg_user_data, nim_nos_upload_speed_cb_func speed_cb, const void *speed_user_data, nim_nos_upload_info_cb_func info_cb, const void *info_user_data);

typedef void(*nim_nos_stop_upload_ex)(const char *task_id, const char *json_extension);


//发起上传任务
void foo_start_http_download(const std::string& task_uuid)
{
    Json::FastWriter writer;
    Json::Value value;
    value[kNIMNosLowLimit] = ; //int(选填) HTTP通用配置,传输速度,每秒字节数(默认10)
    value[kNIMNosLowTime] = ;  //int(选填) HTTP通用配置,传输过程中当low_time秒时间内传输速度小于low_limit时(字节每秒),下载任务会返回超时而取消(默认60)
    value[kNIMNosTimeout] = ;  //int(选填) HTTP通用配置,超时时间,单位ms,下载时最小10000,上传时最小30000,不设置默认30000
    value[kNIMNosNeedContinueTrans] = ; //bool 需要支持断点续传True
    value[kNIMNosTaskId] = task_uuid;   //string(断点续传必填) HTTP通用配置,任务ID,如果传入的ID是曾经未完成的传输任务,则会开始续传(用户需要保证ID的唯一性)

    nim_nos_upload_ex func = (nim_nos_upload_ex) GetProcAddress(hInst, "nim_nos_upload_ex");
    func(local_file.c_str(), writer.write(value).c_str(), &CallbackUploadEx, callback_result_userdata, &CallbackProgressEx, callback_progress_pointer, &CallbackSpeed, callback_speed_pointer, &CallbackTransferInfo, callback_transfer_pointer);
}    

//暂停上传任务
void foo_pause_http_download(const std::string& task_uuid)
{
    nim_nos_stop_upload_ex func = (nim_nos_stop_upload_ex) GetProcAddress(hInst, "nim_nos_stop_upload_ex");
    func(task_uuid.c_str(), nullptr);
}

文档转换

文档转换,允许用户上传ppt、pptx、pdf等文档到服务器,服务器提供文档转换成图片、保存用户文档列表、删除、查询文档等功能。其中文件的上传下载使用nos的服务实现nim_nos_upload_ex/nim_nos_download/nim_nos_download_ex。

注册转换结果通知回调

注册文档转换的结果的回调通知(服务器异步转换,客户端需要等待通知才知道转换结果)

@param[in] json_extension 无效扩展字段
@param[in] cb 结果回调见nim_doc_trans_def.h,成功返回的json_extension中带有一条记录
@param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
@return void 无返回值
typedef void(*nim_doctrans_reg_notify_cb)(const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

示例代码如下:

C++

void OnDocInfoCallback(int32_t code, const DocTransInfo& doc_info)
{

}

void foo()
{
    nim::DocTrans::RegNotifyCb(&OnDocInfoCallback);
}

C#

暂无

C

void CallbackDocTrans(int32_t code, const char *json_extension, const void *user_data)
{
    ···
}

typedef void(*void nim_doctrans_reg_notify_cb)(const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_doctrans_reg_notify_cb func = (nim_doctrans_reg_notify_cb) GetProcAddress(hInst, "nim_doctrans_reg_notify_cb");
    func("", &CallbackDocTrans, nullptr);
}

查询文档信息

根据文档id查询文档信息

@param[in] id 文档id
@param[in] json_extension 无效扩展字段
@param[in] cb 结果回调见nim_doc_trans_def.h,成功返回的json_extension中带有一条记录
@param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
@return void 无返回值
typedef void(*nim_doctrans_get_info)(const char *id, const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

示例代码如下:

C++

void OnDocInfoCallback(int32_t code, const DocTransInfo& doc_info)
{

}

void foo()
{
    nim::DocTrans::GetInfo("id...", "", const DocInfoCallback& cb);
}

C#

暂无

C

void CallbackDocTrans(int32_t code, const char *json_extension, const void *user_data)
{
    ···
}

typedef void(*nim_doctrans_get_info)(const char *id, const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_doctrans_get_info func = (nim_doctrans_get_info) GetProcAddress(hInst, "nim_doctrans_get_info");
    func("id...", "", &CallbackDocTrans, nullptr);
}

分页查询文档信息

查询文档信息列表

@param[in] id        查询的起始docId,若为空,表示从头开始查找,按照文档转码的发起时间降序排列
@param[in] limit    查询的文档的最大数目,有最大值限制,目前为30
@param[in] json_extension 无效扩展字段
@param[in] cb        结果回调见nim_doc_trans_def.h,成功返回的json_extension中带有记录列表
@param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
@return void 无返回值
typedef void(*nim_doctrans_get_info_list)(const char *id, int32_t limit, const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

示例代码如下:

C++

void OnDocInfosCallback(int32_t code, int32_t count, const std::list<DocTransInfo>& doc_infos)
{

}

void foo()
{
    nim::DocTrans::GetInfoList("", 30, "", const OnDocInfosCallback& cb);
}

C#

暂无

C

void CallbackDocTrans(int32_t code, const char *json_extension, const void *user_data)
{
    ···
}

typedef void(*nim_doctrans_get_info_list)(const char *id, int32_t limit, const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_doctrans_get_info_list func = (nim_doctrans_get_info_list) GetProcAddress(hInst, "nim_doctrans_get_info_list");
    func("", 30, "", &CallbackDocTrans, nullptr);
}

删除文档信息

根据文档id删除服务器记录,对于正在转码中的文档,删除后将不会收到转码结果的通知

@param[in] id 文档id
@param[in] json_extension 无效扩展字段
@param[in] cb 结果回调见nim_doc_trans_def.h,返回的json_extension无效
@param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
@return void 无返回值
typedef void(*nim_doctrans_del_info)(const char *id, const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

示例代码如下:

C++

void OnDocInfoCallback(int32_t code, const DocTransInfo& doc_info)
{

}

void foo()
{
    nim::DocTrans::DeleteInfo("id...", "", const OnDocInfoCallback& cb);
}

C#

暂无

C

void CallbackDocTrans(int32_t code, const char *json_extension, const void *user_data)
{
    ···
}

typedef void(*nim_doctrans_del_info)(const char *id, const char *json_extension, nim_doctrans_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_doctrans_del_info func = (nim_doctrans_del_info) GetProcAddress(hInst, "nim_doctrans_del_info");
    func("id...", "", &CallbackDocTrans, nullptr);
}

文档源的下载地址

拼接文档源的下载地址

@param[in] url_prefix    文档信息中的url前缀
@param[in] file_type    文档源类型
@return char * 返回文档源的下载地址,需要上层调用nim_global.h提供的内存释放接口释放。
typedef char *(*nim_doctrans_get_source_file_url)(const char *url_prefix, NIMDocTranscodingFileType file_type);

示例代码如下:

C++

std::string foo()
{
    return nim::DocTrans::GetSourceFileUrl("http....", kNIMDocTranscodingFileTypePPT);
}

C#

暂无

C

typedef char *(*nim_doctrans_get_source_file_url)(const char *url_prefix, NIMDocTranscodingFileType file_type);
typedef    void (*nim_global_free_buf)(void *data);

void foo()
{
    nim_doctrans_get_source_file_url func = (nim_doctrans_get_source_file_url) GetProcAddress(hInst, "nim_doctrans_get_source_file_url");
    nim_global_free_buf free_func = (nim_global_free_buf) GetProcAddress(hInst, "nim_global_free_buf");
    const char *url = func("http....", kNIMDocTranscodingFileTypePPT);

    ....

    free_func((void *)url);
}

文档图片的下载地址

拼接文档图片的下载地址

@param[in] url_prefix    文档信息中的url前缀
@param[in] img_type        文档转换的图片类型
@param[in] quality        需要的图片清晰度
@param[in] page_num        图片页码(从1开始计算)
@return char * 返回文档图片的下载地址,需要上层调用nim_global.h提供的内存释放接口释放。
typedef char *(*nim_doctrans_get_page_url)(const char *url_prefix, NIMDocTranscodingImageType img_type, NIMDocTranscodingQuality quality, int32_t page_num);

示例代码如下:

C++

std::string foo()
{
    return nim::DocTrans::GetPageUrl("http....", kNIMDocTranscodingImageTypeJPG, kNIMDocTranscodingQualityHigh, 1);
}

C#

暂无

C

typedef char *(*nim_doctrans_get_page_url)(const char *url_prefix, NIMDocTranscodingImageType img_type, NIMDocTranscodingQuality quality, int32_t page_num);
typedef    void (*nim_global_free_buf)(void *data);

void foo()
{
    nim_doctrans_get_page_url func = (nim_doctrans_get_page_url) GetProcAddress(hInst, "nim_doctrans_get_page_url");
    nim_global_free_buf free_func = (nim_global_free_buf) GetProcAddress(hInst, "nim_global_free_buf");
    const char *url = func("http....", kNIMDocTranscodingImageTypeJPG, kNIMDocTranscodingQualityHigh, 1);

    ....

    free_func((void *)url);
}

初始化

SDK 开发示例

#include "stdafx.h"
#include <windows.h>
#include <cassert>
#include <iostream>
#include <string>
#include "json/json.h"
#include "nim_res_code_def.h"
#include "nim_client_def.h"

//初始化和卸载
typedef bool (*nim_client_init)(const char *app_data_dir, const char *app_install_dir, const char *json_extension);
typedef void (*nim_client_cleanup)(const char *json_extension);

//登录和退出
typedef void (*nim_client_login)(const char *app_key, const char *account, const char *password, const char *json_extension, nim_json_transport_cb_func cb, const void *user_data);
typedef void (*nim_client_logout)(NIMLogoutType logout_type, const char *json_extension, nim_json_transport_cb_func cb, const void *user_data);

//加载模块和接口
class SdkNim
{
public:
    template <typename F>
    static F Function(const char* function_name)
    {
        F f = (F) ::GetProcAddress(nim_sdk_, function_name);
        assert(f);
        return f;
    }
public:
    static bool Load()
    {
        nim_sdk_ = ::LoadLibraryW(L"nim_sdk_dll.dll");
        if( nim_sdk_ == NULL )
        {
            wprintf_s(L"Load nim_sdk_dll.dll Fail: %d", ::GetLastError());
            return false;
        }
        else
        {
            return true;
        }
    }
    static void UnLoad()
    {
        assert(nim_sdk_ != NULL);
        if( nim_sdk_ != NULL )
        {
            ::FreeLibrary(nim_sdk_);
            nim_sdk_ = NULL;
        }
    }
    static bool Init()
    {
        nim_client_init f_init = Function<nim_client_init>("nim_client_init");
        return f_init("Netease", "", "");
    }
    static void UnInit()
    {
        assert(nim_sdk_ != NULL);
        if( nim_sdk_ != NULL )
        {
            nim_client_cleanup f_cleanup = Function<nim_client_cleanup>("nim_client_cleanup");
            f_cleanup("");
        }
    }
private:
    static HINSTANCE nim_sdk_;
};

HINSTANCE SdkNim::nim_sdk_ = NULL;

//设置简单的标记
bool wait_for_login = false, wait_for_logout = false;
bool login_success = false;

//回调函数
void OnLoginCallback(const char *json_params, const void *user_data)
{
    printf_s("OnLoginCallback:%s", json_params);

    Json::Value json;
    Json::Reader reader;
    if( reader.parse(json_params, json) )
    {
        int code = json[kNIMErrorCode].asInt();
        int step = json[kNIMLoginStep].asInt();
        if( code == kNIMResSuccess )
        {
            if( step == kNIMLoginStepLogin )
            {
                login_success = true;
                wait_for_login = false;
            }
        }
        else
        {
            wait_for_login = false;
        }
    }
    else
    {
        assert(0);
        wait_for_login = false;
    }
}

void OnLogoutCallback(const char *json_params, const void *user_data)
{
    printf_s("OnLogoutCallback:%s", json_params);

    wait_for_logout = false;
}

//登录
void NimLogin()
{
    nim_client_login f_login = SdkNim::Function<nim_client_login>("nim_client_login");
    f_login("app_key", "account", "password", "", &OnLoginCallback, NULL);
}

//退出
void NimLogout()
{
    nim_client_logout f_logout = SdkNim::Function<nim_client_logout>("nim_client_logout");
    f_logout(kNIMLogoutAppExit, "", &OnLogoutCallback, NULL);
}

int main(int, char**)
{
    if( SdkNim::Load() )
    {
        if( SdkNim::Init() )
        {
            wait_for_login = true;
            NimLogin();

            while( wait_for_login )
            {
                ::Sleep(1000);
            }

            if( login_success )
            {
                ::Sleep(3000);

                wait_for_logout = true;
                NimLogout();

                while( wait_for_logout )
                {
                    ::Sleep(1000);
                }
            }

            SdkNim::UnInit();
        }
        else
        {
            wprintf_s(L"SdkNim::Init Fail");
        }
        SdkNim::UnLoad();
    }
    system("pause");
    return 0;
}

聊天室

集成聊天室必读

聊天室功能概述

聊天室特点:

PC端聊天室SDK提供了不同于IMSDK的动态链接库(nim_chatroom.dll),因此加载使用聊天室功能有以下准备工作:

将SDK 相关的dll 文件(nim_chatroom.dll)放到App 的运行目录下。SDK 基于vs2010 开发,如果App 没有对应的运行时库文件(msvcp100.dll和msvcr100.dll),请将其放到App 的运行目录下。

准备工作完成后,在程序启动时,初始化SDK;在程序退出前,清理SDK。示例代码如下:

C++

void Init()
{
    nim_chatroom::ChatRoom::Init("");
}

void Cleanup()
{
    nim_chatroom::ChatRoom::Cleanup();
}

C#

void Init()
{
    NIMChatRoom.ChatRoomApi.Init();
}

void Cleanup()
{
    NIMChatRoom.ChatRoomApi.Cleanup();
}

C

typedef void(*nim_chatroom_init)(const char *json_extension);
typedef    void(*nim_chatroom_cleanup)(const char *json_extension);

void Init()
{
    //获取SDK初始化接口
    HINSTANCE hInst = LoadLibraryW(L"nim_chatroom.dll");
    nim_chatroom_init func = (nim_chatroom_init) GetProcAddress(hInst, "nim_chatroom_init");

    //初始化SDK
    func("");
}

void Cleanup()
{
    nim_chatroom_cleanup func = (nim_chatroom_cleanup) GetProcAddress(hInst, "nim_chatroom_cleanup");
    func("");
    FreeLibrary(hInst);
}

进入聊天室

进入聊天室前,需要通过调用IM模块接口获取进入聊天室的权限,注意:这里调用的是IMSDK模块的接口

C++

void OnChatRoomRequestEnterCallback(int error_code, const std::string& result)
{
}

void foo()
{
    //获取聊天室登录信息
    nim::PluginIn::ChatRoomRequestEnterAsync(room_id, &OnChatRoomRequestEnterCallback);
}

C#

void foo()
{
    NIM.Plugin.ChatRoom.RequestLoginInfo(roomId, (response, authResult) =>
    {
        if (response == NIM.ResponseCode.kNIMResSuccess)
        {

        }
    });
}

C

typedef void(*nim_plugin_chatroom_request_enter_async)(const __int64 room_id, const char *json_extension, nim_plugin_chatroom_request_enter_cb_func cb, const void *user_data);

void CallbackRequestChatRoomEnter(int error_code, const char *result, const char *json_extension, const void *user_data)
{    
    //result为权限信息
}

void foo()
{
    HINSTANCE hInst = LoadLibraryW(L"nim.dll");
    nim_plugin_chatroom_request_enter_async func = (nim_plugin_chatroom_request_enter_async) GetProcAddress(hInst, "nim_plugin_chatroom_request_enter_async");

    func(room_id, null, &CallbackRequestChatRoomEnter, null);
}

获取权限成功后会得到权限信息(回调函数第二个参数result),接下去再调用进入聊天室的接口。

C++

void foo()
{
    ChatRoomEnterInfo info;
    ChatRoom::Enter(room_id_, room_enter_token, info);
}

C#

void foo()
{
    NIMChatRoom.LoginData data = new NIMChatRoom.LoginData();
    data.Nick = "【C# Client】";
    NIMChatRoom.ChatRoomApi.Login(roomId, authResult, data);
}

C typedef bool(nim_chatroom_enter)(const __int64 room_id, const char request_enter_data, const char enter_info, const char json_extension);

void foo()
{
    //获取进入聊天室接口
    HINSTANCE hInst = LoadLibraryW(L"nim_chatroom.dll");
    nim_chatroom_enter func = (nim_chatroom_enter) GetProcAddress(hInst, "nim_chatroom_enter");

    //聊天室可选参数
    Json::Value info_value;
    info_value[kNIMChatRoomEnterKeyNick] = "my_nickname_of_room";//设置聊天室内的昵称
    info_value[kNIMChatRoomEnterKeyAvatar] = "http://my_avatar_url";//设置聊天室内的头像地址
    info_value[kNIMChatRoomEnterKeyExt] = GetJsonStringWithNoStyled(JSON_OBJECT);//设置扩展字段
    info_value[kNIMChatRoomEnterKeyNotifyExt] = GetJsonStringWithNoStyled(JSON_OBJECT);//设置扩展字段用于通知

    //这里直接将权限信息作为参数传入
    func(room_id, result, GetJsonStringWithNoStyled(info_value).c_str(), "");
}

进入聊天室前需要提前注册一些全局回调(具体说明请参阅API 文档),以下以注册进入聊天室结果通知回调为例:

C++

void OnEnterCallback(__int64 room_id, const NIMChatRoomEnterStep step, int error_code, const ChatRoomInfo& info, const ChatRoomMemberInfo& my_info)
{
    if (step != kNIMChatRoomEnterStepRoomAuthOver)
        return;

        if (error_code != nim::kNIMResSuccess && error_code != nim::kNIMResTimeoutError)
        {
            std::wstring kick_tip_str;
            switch (error_code)
            {
            case nim::kNIMResNotExist:
                kick_tip_str = L"聊天室不存在";
                break;
            case nim::kNIMResForbidden:
                kick_tip_str = L"权限问题";
                break;
            case nim::kNIMResRoomLinkError:
            case nim::kNIMResRoomError:
                kick_tip_str = L"聊天室异常";
                break;
            case nim::kNIMResRoomBlackBeOut:
                kick_tip_str = L"黑名单用户禁止进入聊天室";
                break;
            case nim::kNIMResFrequently:
                kick_tip_str = L"操作太频繁,稍后重试";
                break;
            default:
                return;
            }
        }
        else
        {

        }
}

void foo()
{
    ChatRoom::RegEnterCb(&OnEnterCallback);
}

C#

void ChatRoomApi_LoginHandler(NIMChatRoom.NIMChatRoomLoginStep loginStep, NIM.ResponseCode errorCode, NIMChatRoom.ChatRoomInfo roomInfo, NIMChatRoom.MemberInfo memberInfo)
{
    if (loginStep == NIMChatRoom.NIMChatRoomLoginStep.kNIMChatRoomLoginStepRoomAuthOver && errorCode == NIM.ResponseCode.kNIMResSuccess)
    {
    }
    if (errorCode != NIM.ResponseCode.kNIMResSuccess)
    {
        MessageBox.Show(loginStep.ToString() + " " + errorCode.ToString(), "进入聊天室出错");
    }
}

void foo()
{
    NIMChatRoom.ChatRoomApi.LoginHandler += ChatRoomApi_LoginHandler;
}

C

typedef void(*nim_chatroom_reg_enter_cb)(const char *json_extension, nim_chatroom_enter_cb_func cb, const void *user_data);

static void CallbackEnter(__int64 room_id, int step, int error_code, const char *result, const char *json_extension, const void *user_data)
{

}

void foo()
{
    nim_chatroom_reg_enter_cb func = (nim_chatroom_reg_enter_cb)GetProcAddress(hInst, "nim_chatroom_reg_enter_cb");
    func(nullptr, &CallbackEnter, nullptr);    
}

离开聊天室(被踢)

调用接口nim_chatroom_exit 离开聊天室。

C++

void foo()
{
    ChatRoom::Exit(room_id);
}

C#

void foo()
{
    NIMChatRoom.ChatRoomApi.Exit(roomId);
}

C typedef void(nim_chatroom_exit)(const __int64 room_id, const char json_extension);

void foo()
{
    nim_chatroom_exit func = (nim_chatroom_exit) GetProcAddress(hInst, "nim_chatroom_exit");
    func(room_id, nullptr);
}

被踢出聊天室时,开发者可以通过接口nim_chatroom_reg_exit_cb注册全局通知回调函数获取离开聊天室的原因。

C++

void OnEnterCallback(__int64 room_id, int error_code, NIMChatRoomExitReason exit_reason)
{

}

void foo()
{
    ChatRoom::RegExitCb(&OnExitCallback);
}

C#

void ChatRoomApi_ExitHandler(long roomId, NIM.ResponseCode errorCode, NIMChatRoom.NIMChatRoomExitReason reason)
{
    if (errorCode == NIM.ResponseCode.kNIMResSuccess)
    {
    }
}

void foo()
{
    NIMChatRoom.ChatRoomApi.ExitHandler += ChatRoomApi_ExitHandler;
}

C

static void CallbackExit(__int64 room_id, int error_code, int exit_reason, const char *json_extension, const void *user_data)
{
    ...
}

typedef void(*nim_chatroom_reg_exit_cb)(const char *json_extension, nim_chatroom_exit_cb_func cb, const void *user_data);

void foo()
{
    nim_chatroom_reg_exit_cb func = (nim_chatroom_reg_exit_cb) GetProcAddress(hInst, "nim_chatroom_reg_exit_cb");
    func("", &CallbackExit, "");
}    

除了被多端、主播和管理员踢出以外,聊天室被关闭或者被解散也会收到离开聊天室的通知。

聊天室连接情况通知

通过接口nim_chatroom_reg_link_condition_cb 来当前连接情况变更回调函数,开发者可以通过回调函数获取当前连接情况。

C++

void OnRegLinkConditionCallback(__int64 room_id, const NIMChatRoomLinkCondition condition)
{

}

void foo()
{
    ChatRoom::RegLinkConditionCb(&OnRegLinkConditionCallback);
}

C#

void ChatRoomApi_LinkStateChanged(long roomId, NIMChatRoomLinkCondition state)
{

}

void foo()
{
    NIMChatRoom.ChatRoomApi.LinkStateChanged += ChatRoomApi_LinkStateChanged;
}

C

static void CallbackLinkCondition(__int64 room_id, int condition, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_chatroom_reg_link_condition_cb)(const char *json_extension, nim_chatroom_link_condition_cb_func cb, const void *user_data);

void foo()
{
    nim_chatroom_reg_link_condition_cb func = (nim_chatroom_reg_link_condition_cb) GetProcAddress(hInst, "nim_chatroom_reg_link_condition_cb");
    func(nullptr, &CallbackLinkCondition, nullptr);
}

获取/更新聊天室信息

进入聊天室的回调函数中会有聊天室的信息,此外还能通过单独的接口获取聊天室信息。SDK 本地不缓存聊天室信息,只提供向服务器查询的接口,因此接口调用遵循服务器接口使用频率限制。

C++

void OnGetChatRoomInfoCallback(__int64 room_id, int error_code, const ChatRoomInfo& info)
{
    if (error_code != nim::kNIMResSuccess || room_id != room_id_)
    {
        return;
    }
}

void foo()
{
    ChatRoom::GetInfoAsync(room_id, &OnGetChatRoomInfoCallback);
}

C#

void foo()
{
    NIMChatRoom.ChatRoomApi.GetRoomInfo(roomId, (room_Id, errorCode, info) =>
    {
    });
}

C

static void CallbackGetChatRoomInfo(__int64 room_id, int error_code, const char *result, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_chatroom_get_info_async)(const __int64 room_id, const char *json_extension, nim_chatroom_get_info_cb_func cb, const void *user_data);

void foo()
{
    nim_chatroom_get_info_async func = (nim_chatroom_get_info_async) GetProcAddress(hInst, "nim_chatroom_get_info_async");
    func(room_id, nullptr, &CallbackGetChatRoomInfo, nullptr);
}

更新聊天室信息(需要管理员权限),目前只支持更新kNIMChatRoomInfoKeyName,kNIMChatRoomInfoKeyAnnouncement,kNIMChatRoomInfoKeyBroadcastUrl,kNIMChatRoomInfoKeyExt四个字段:

C++

void UpdateRoomCallback(int64_t room_id, int error_code)
{

}

void foo()
{
    ChatRoomInfo info;
    info.announcement_ = ; //聊天室公告
    info.name_ = ; //聊天室名称
    ChatRoom::UpdateRoomInfoAsync(room_id, info, true, "广播通知", &UpdateRoomCallback);
}

C#

暂无

C

void CallbackUpdateRoomInfo(int64_t room_id, int error_code, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_chatroom_update_room_info_async)(const int64_t room_id, const char *room_info_json_str, bool need_notify, const char *notify_ext, const char *json_extension, nim_chatroom_update_room_info_cb_func cb, const void *user_data);

void foo()
{
    nim_chatroom_update_room_info_async func = (nim_chatroom_update_room_info_async) GetProcAddress(hInst, "nim_chatroom_update_room_info_async");

    Json::Value values;
    values[kNIMChatRoomInfoKeyName] = name_; //聊天室名称
    values[kNIMChatRoomInfoKeyAnnouncement] = announcement_; //聊天室公告
    values[kNIMChatRoomInfoKeyBroadcastUrl] = broadcast_url_; //视频直播拉流地址
    values[kNIMChatRoomInfoKeyExt] = ext_; //第三方扩展字段
    Json::FastWriter fw;

    func(room_id, fw.write(values).c_str(), true, "广播通知", nullptr, &CallbackUpdateRoomInfo, nullptr);
}

更新自己的信息

目前只支持更新kNIMChatRoomMemberInfoKeyNick,kNIMChatRoomMemberInfoKeyAvatar,kNIMChatRoomMemberInfoKeyExt三个字段:

C++

void UpdateMyRoleCallback(int64_t room_id, int error_code)
{

}

void foo()
{
    ChatRoomMemberInfo info;
    info.nick_ = ; //聊天室内的昵称字段
    info.avatar_ = ; //聊天室内的头像
    info.ext_ = ;  //开发者扩展字段
    ChatRoom::UpdateMyRoomRoleAsync(room_id, info, true, "广播通知", &UpdateMyRoleCallback);
}

C#

暂无

C

void CallbackUpdateMyRoomRole(int64_t room_id, int error_code, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_chatroom_update_my_role_async)(const int64_t room_id, const char *role_info_json_str, bool need_notify, const char *notify_ext, const char *json_extension, nim_chatroom_update_my_role_cb_func cb, const void *user_data);

void foo()
{
    nim_chatroom_update_my_role_async func = (nim_chatroom_update_my_role_async) GetProcAddress(hInst, "nim_chatroom_update_my_role_async");

    Json::Value values;
    values[kNIMChatRoomMemberInfoKeyNick] = id_; //聊天室内的昵称字段
    values[kNIMChatRoomMemberInfoKeyAvatar] = name_; //聊天室内的头像
    values[kNIMChatRoomMemberInfoKeyExt] = announcement_; //开发者扩展字段
    Json::FastWriter fw;

    func(room_id, fw.write(values).c_str(), true, "广播通知", nullptr, &CallbackUpdateMyRoomRole, nullptr);
}

聊天室消息收发

聊天室SDK 支持文本、图片、音频、视频、地理位置、通知消息、提醒消息、文件消息和自定义消息等多种种类型消息。

查询消息历史

SDK 本地不缓存任何消息历史,只提供向服务器查询消息历史的接口,因此接口调用遵循服务器接口使用频率限制。

C++

void GetMsgHistoryCallback(__int64 room_id, int error_code, const std::list<ChatRoomMessage>& msgs)
{
    if (error_code != nim::kNIMResSuccess)
        return;
}

void foo()
{
    ChatRoomGetMsgHistoryParameters history_param;
    history_param.limit_ = 10;
    history_param.start_timetag_ = 0;
    ChatRoom::GetMessageHistoryOnlineAsync(room_id, history_param, &GetMsgHistoryCallback);
}

C#

void foo()
{
    NIMChatRoom.ChatRoomApi.QueryMessageHistoryOnline(roomId, 0, 50, (room_Id, errorCode, messages) =>
    {
        if (errorCode == NIM.ResponseCode.kNIMResSuccess)

    });
}

C

typedef void(*nim_chatroom_get_msg_history_online_async)(const __int64 room_id, const char *parameters_json_str, const char *json_extension, nim_chatroom_get_msg_cb_func cb, const void *user_data);

static void CallbackGetMsgHistoryOnline(__int64 room_id, int error_code, const char *result, const char *json_extension, const void *user_data)
{

}

void foo()
{
    nim_chatroom_get_msg_history_online_async func = (nim_chatroom_get_msg_history_online_async) GetProcAddress(hInst, "nim_chatroom_get_msg_history_online_async");

    Json::Value values;
    values[kNIMChatRoomGetMsgHistoryKeyStartTime] = start_timetag_;    //开始时间,单位毫秒
    values[kNIMChatRoomGetMsgHistoryKeyLimit] = limit_;                //本次返回的消息数量

    Json::FastWriter fw;

    func(room_id, fw.write(values).c_str(), nullptr, &CallbackGetMsgHistoryOnline, nullptr);
}

获取聊天室在线成员

SDK 本地不缓存聊天室成员信息,只提供向服务器查询的接口,因此接口调用遵循服务器接口使用频率限制。

SDK 提供了两种获取聊天室在线成员信息的接口:

踢出在线成员

C++

void KickMemberCallback(__int64 room_id, int error_code)
{
    if (error_code != nim::kNIMResSuccess)
        return;
}

void foo()
{
    ChatRoom::KickMemberAsync(room_id, user_account, "", &KickMemberCallback);
}

C#

void foo()
{
    NIMChatRoom.ChatRoomApi.RemoveMember(roomId, "userId", "踢出提示文本", (room_Id, errorCode) =>
    {
    });
}

C

typedef void(*nim_chatroom_kick_member_async)(const __int64 room_id, const char *id, const char *notify_ext, const char *json_extension, nim_chatroom_kick_member_cb_func cb, const void *user_data);

static void CallbackKickMember(__int64 room_id, int error_code, const char *json_extension, const void *user_data)
{

}

void foo()
{
    nim_chatroom_kick_member_async func = (nim_chatroom_kick_member_async) GetProcAddress(hInst, "nim_chatroom_kick_member_async");

    func(room_id, id, nullptr, nullptr, &CallbackKickMember, nullptr);
}

聊天室权限设置

设置管理员、设置普通成员、设置黑名单、禁言通过调用同一个接口nim_chatroom_set_member_attribute_async实现。

聊天室队列服务

C++ SDK开发手册

C# SDK开发手册