互动直播 PC Demo 源码导读
概述
互动直播是网易云信的一款针对目前市场比较热门的直播解决方案。在方案中结合了网易云信 IM 能力的聊天室模型和网易云信音视频能力的多人会议模型。.Demo使用Visual Studio 2013 Update5开发(必须使用Update5版本)。
Demo源码中目录结构中的C++封装层文件夹名字尾部携带_vs2010的为vs2010创建的工程项目,对应的不携带_vs2010的为vs2013创建的工程项目,即Demo目前加载的工程,该工程依赖Demo工程。
Demo的主要功能由Demo工程本身以及UI组件
工程共同完成,Demo工程和UI组件
工程具有相同的目录结构。UI组件
位于tool_kits\ui_component\ui_kit
目录,UI组件的
相关文档详见:云信UI组件
Demo源码版本历史
界面开发资料
网易云信Duilib使用说明
控件属性
界面布局介绍
会话多窗口实现介绍
从3.0.0版本开始,Demo对会话窗口的实现进行了升级,方便开发者快速开发类似QQ,微信等多窗口模式。
CEF开发指南
从3.4.0版本开始,Demo增加了Cef控件,方便开发者开发Web相关的功能模块。
Duillib高分屏(高DPI)支持
从3.5.0版本开始,Duilib增加了对高分屏的支持,方便在用户设置了DPI后保持软件界面的清晰效果。
目录结构
callback:注册到SDK的一些回调的处理
gui:所有功能的界面相关实现
module:所有功能的逻辑相关实现
util:一些公用的工具类
打包说明
开发者在打包自己的应用时,应确保将以下云信SDK相关文件打包进去。
nim.dll:云信SDK主要功能库文件。
nim_chatroom.dll:云信聊天室SDK主要功能库文件
nim_audio.dll:语音消息功能库文件。
nrtc.dll:音视频通话功能库文件。
nim_tools_http.dll:http功能库文件。
nim_audio_hook.dll:负责辅助采集播放器音频,由nrtc.dll调用;放在用户程序目录下,x64位暂时不提供该Dll。
msvcr100.dll:SDK依赖的VS2010动态链接库。
msvcp100.dll:SDK依赖的VS2010动态链接库。
nim_conf:云信SDK配置文件目录,包含SDK版本控制等。
nim_player:播放器SDK依赖库的文件夹
其他的文件及目录是应用程序相关的,开发者根据自己程序的使用情况选择是否打包。
image_ole.dll:图像显示库文件,支持在RichEdit组件中插入和显示图片。
translation.bin:中文翻译成拼音依赖的文件。
app_ver.dll:云信Demo应用程序版本控制,开发者请勿打包到自己的应用。
lang:Demo界面文案对照表,可支持多国语言。
res:Demo资源文件目录。
themes:Demo皮肤目录,包含XML配置文件和图片文件。
功能点指引
SDK C++封装层
因为SDK所有接口都是C接口,为了方便使用C++的同学使用,我们提供了nim_cpp_sdk
和nim_chatroom_cpp_sdk
静态库。静态库位于libs\nim_sdk_desktop\nim_cpp_sdk
、libs\nim_sdk_desktop\nim_chatroom_cpp_sdk
和libs\nim_sdk_desktop\nim_player_cpp_sdk
目录,它将C接口SDK封装为C++代码,demo和UI组件
都直接使用nim_cpp_sdk
、nim_chatroom_cpp_sdk
和 nim_player_cpp_sdk
静态库的C++封装层代码。开发者可以直接在解决方案中导入nim_cpp_sdk
工程、nim_chatroom_cpp_sdk
和 nim_player_cpp_sdk
工程。
封装层提供的包装文件如下:
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_client.h: 全局管理功能;主要包括SDK初始化/清理、客户端登录/退出等功能
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_data_sync.h: 数据同步相关接口
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_friend.h: 好友功能,包含添加好友、删除好友、监听好友变化
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_global.h: NIM SDK提供的一些全局接口;释放从SDK申请的内存
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_msglog.h: 消息历史接口
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_nos.h: NOS云存储服务接口;上传或下载文件资源
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_rts.h: 白板功能
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_session.h: 会话列表管理功能;主要包括查询会话列表、删除会话列表等功能
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_sysmsg.h: 系统消息接口;主要包括查询系统消息、删除系统消息等功能
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_talk.h: 聊天功能;主要包括发送消息、接收消息等功能
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_team.h: 群组功能;主要包括查询群信息、查询群成员信息、加人、踢人等功能
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_tool.h: 提供的一些工具接口,主要包括获取SDK里app account对应的app data目录,计算md5、语音转文字等
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_vchat.h: 音视频(包括设备)相关接口
nim_sdk_cpp\nim_sdk_cpp\api\nim_cpp_user.h: 用户功能,包含黑名单设置、个人消息提醒设置、状态设置
nim_chatroom_cpp_sdk\nim_chatroom_cpp\api\nim_chatroom_cpp.h :聊天室操作的相关接口
nim_player_cpp_sdk\nim_player_cpp\api\nim_player_cpp.h :播放器操作的相关接口
除了
nim_cpp_sdk
、nim_chatroom_cpp_sdk
和nim_player_cpp_sdk
静态库,另外还提供了负责语音和http传输的dll的C++封装类:libs\nim_sdk_desktop\nim_cpp_sdk\nim_audio_cpp\nim_tools_audio_cpp_wrapper.h:提供的语音录制和播放接口
libs\nim_sdk_desktop\nim_cpp_sdk\nim_http_cpp\nim_tools_http_cpp_wrapper.h:提供的http传输相关接口
SDK 初始化
SDK的初始化在main.cpp中InitNim方法进行。
示例:
//sdk能力参数(必填)
//string(db key必填,目前只支持最多32个字符的加密密钥!建议使用32个字符)
config.database_encrypt_key_ = "Netease";
std::string app_key = GetConfigValueAppKey();
// 载入云信sdk,初始化安装目录和用户目录
bool ret = nim::Client::Init(app_key, "NIM_LIVE", "", config);
assert(ret);
//聊天室初始化
ret = nim_chatroom::ChatRoom::Init("","");
assert(ret);
nim_ui::InitManager::GetInstance()->InitUiKit();
nim_chatroom::ChatroomCallback::InitChatroomCallback();
界面开发
云信PC demo以及UI组件的界面开发都依赖云信DuiLib库
,关于云信DuiLib库
的使用方法和注意事项,请参考:云信Duilib
登录
登录相关界面代码在gui/login
目录下,登录相关的控制逻辑已经封装到了UI组件的
,逻辑代码在callback\login\login_callback.cpp
文件中。登录界面可以直接调用UI组件
的登录函数,示例如下:
std::string username = "123456789";
std::string password = "123456789";
nim_ui::LoginManager::GetInstance()->DoLogin(username, password);
主窗体功能
Demo登录进去之后有两种模式可供用户选择:观众模式和主播模式。Demo提供网络检测功能,可直观的观测到当前网络状态,示例如下:
void LoginForm::NetDetect()
{
nim::VChat::NetDetect(nbase::Bind(&LoginForm::NetDetectCb, this, std::placeholders::_1, std::placeholders::_2));
}
void LoginForm::NetDetectCb(int code, nim::NetDetectCbInfo info)
{
if (code == 200)
{
nim_chatroom::BypassLiveFontPage::SetNetDetectInfo(info);
}
}
观众模式
当以观众模式进行互动时,采用播放器SDK拉流,直接看到主播的直播画面。当与主播成功连麦后,停止拉流,进入音视频房间,和主播互动。互动模式有两种:音频互动和视频互动。当观众向主播申请连麦时,观众将自己加入到连麦队列中,加入成功后发送自定义通知给主播。示例如下:
value_info["nick"] = nbase::UTF16ToUTF8(nick);
value_info["avatar"] = "avatar_default";
string info = fw.write(value_info);
Json::Value value;
value["style"] = kAudio;
value["state"] = NTESLiveMicStateConnecting;
value["info"] = info;
string ext = fw.write(value);
RequestPushMicLink(nbase::Int64ToString(room_id_), uid, ext,kAudio);
主播模式
当以主播模式进行互动时,主播开启直播后,能正常收到观众的连麦请求,主播可以同意观众的连麦请求。主播同意连麦后,发送自定义通知给用户id,如果此时已有连麦者,应该断开已有的连接,然后再更新连麦者的状态。示例如下:
if ((!bypassingaccout_.empty())&&bypassingaccout_ != bypass_inact_account_)
{
RequestPopMicLink(nbase::Int64ToString(room_id_), bypassingaccout_);
SendSysNotifyMsg(kDisConnectingMIC, bypassingaccout_);
}
QLOG_APP(L"BypassAgreeMenuItemClick-bypass_inact_account:{0}") << bypass_inact_account_;
SendSysNotifyMsg(kConnectingMIC, bypass_inact_account_);
bypassingaccout_ = bypass_inact_account_;
string nick = nbase::UTF16ToUTF8(nim_ui::UserManager::GetInstance()->GetUserNameW(bypass_inact_account_));
UpdateBypassMembersState(bypass_inact_account_, NTESLiveMicStateConnecting, bypass_inact_type_, kUpdate, nick);
互动直播的逻辑说明
- 主播开始直播(在多人会议中推流),观众可以用播放器拉流观看。
- 观众申请连麦,主播端显示连麦队列。
- 主播选择连麦队列中的某一个观众,观众结束播放器拉流并进入主播的多人会议。此时旁路直播自动开启,其他观众会看到主播和连麦观众合并的画面;主播和连麦观众和收到对方的画面数据,用特定控件(BitmapControl)渲染显示。
- 主播结束连麦,连麦观众自动退出多人会议,开启播放器拉流观看。
由于聊天室和多人会议都不是直接针对直播的方案模型,所以需要在应用上层补充一些控制指令来保证直播业务逻辑。 控制指令分为两套:
- 点对点自定义系统通知,用于主播和连麦者的控制交互,用于保证连麦者的上下麦时机。
- 聊天室广播消息,用于全局通知所有观众当前的连麦状态,观众需要根据连麦状态显示或隐藏一些控件。
观众端推拉流切换逻辑说明
- 观众进入聊天室成功之后,通过播放器拉流观看主播画面。
- 观众进入音视频房间成功时,停止拉流,进行互动。
- 观众退出音视频房间时,开始拉流观看主播视频画面。
void ChatroomForm::SwitchPullStreamAndInteract(bool pull_stream)
{
if (master_)
{
assert(false);
return;
}
if (!pull_stream) //切换为互动
{
if (livestreaming_) //已经在互动
{
assert(false);
return;
//停止拉流
pull_stream_stopped_cb_.reset(new nbase::WeakCallback<StdClosure>(ToWeakCallback([this]()
{
//开始互动
StartLiveStream();
pull_stream_stopped_cb_.reset();
})));
nim_comp::PlayerManager* player_manager = nim_comp::PlayerManager::GetInstance();
if (!player_manager->StopPlay()) //当前没在拉流
{
(*pull_stream_stopped_cb_.get())();
}
else //切换为拉流
{
interact_stopped_cb_.reset(new nbase::WeakCallback<StdClosure>(ToWeakCallback([this]()
{
//开始拉流
nim_comp::PlayerManager* player_manager = nim_comp::PlayerManager::GetInstance();
player_manager->StartPlay(nbase::UTF16ToUTF8(rtmp_pull_url_), creater_id_);
video_show_ctrl_->SetAccount(creater_id_);
interact_stopped_cb_.reset();
})));
if (livestreaming_) //如果正在互动,停止互动
StopLiveStream();
else if (nim_comp::PlayerManager::GetInstance()->GetPlayerState() =EN_PLAYER_STATE::EN_PLAYER_STOPPING)
{
pull_stream_stopped_cb_.reset(new nbase::WeakCallback<StdClosure>(ToWeakCallback([this]()
{
//开始拉流
nim_comp::PlayerManager* player_manager = nim_comp::PlayerManager::GetInstance();
player_manager->StartPlay(nbase::UTF16ToUTF8(rtmp_pull_url_), creater_id_);
video_show_ctrl_->SetAccount(creater_id_);
pull_stream_stopped_cb_.reset();
})));
}
else
(*interact_stopped_cb_.get())();
}
}
}
点对点系统通知
*进入麦序队列
参数 | 说明 | 值 |
---|---|---|
type | 自定义系统通知类型:加入连麦队列通知 | kJoinQueue |
roomid | 房间ID | 聊天室ID |
style | 网络通话类型 | InactionType枚举 |
info | 进入聊天队列用户信息 | {"nick" : "","avatar" : ""} 字典 |
*退出麦序队列
参数 | 说明 | 值 |
---|---|---|
type | 自定义系统通知类型:退出连麦队列通知 | kExitQueue |
roomid | 房间ID | 聊天室ID |
*主播同意连麦
参数 | 说明 | 值 |
---|---|---|
type | 自定义系统通知类型:主播同意连麦通知 | kConnectingMIC |
roomid | 房间ID | 聊天室ID |
style | 连麦者连麦方式 | InactionType枚举 |
*连麦者拒绝连麦
当连麦者受到主播同意连麦通知时,会检查自身的连麦状态,如果连麦状态过期则需要发送一条拒绝消息告诉主播
参数 | 说明 | 值 |
---|---|---|
type | 自定义系统通知类型:连麦者拒绝连麦通知 | kRejectConnecting |
roomid | 房间ID | 聊天室ID |
*主播强制连麦者断开
参数 | 说明 | 值 |
---|---|---|
type | 自定义系统通知类型:主播强制连麦者断开 | kDisConnectingMIC |
roomid | 房间ID | 聊天室ID |
聊天室广播消息
*连麦者已连麦
参数 | 说明 |
---|---|
uid | 连麦者的 accid |
nick | 连麦者的昵称 |
avatar | 连麦者的头像 |
style | 连麦者的连麦方式 |
*连麦者已断开
参数 | 说明 |
---|---|
uid | 连麦者的 accid |
其他说明
- 主播进入或者退出房间清空麦序队列
- 主播加入频道成功之后,发送一个断开连麦的全局通知