在线教育 Web Demo 源码导读
工程概述
在线教育 Demo 是网易云信的一款针对目前市场比较热门的在线教育场景推出的解决方案。在方案中结合了网易云信 IM 能力的聊天室和文档转码、音视频能力的多人会议模型和互动白板的多人白板模型。适用于pc浏览器 chrome 64.0、65.0版本和firefox 58.0、59.0版本。
在使用本解决方案之前请务必了解
总体逻辑
使用聊天室进行会议管理和 IM 通信,使用音视频多人会议进行实时音视频沟通,使用互动白板的多人实时会话进行白板沟通,Demo 限制了有发言权限的人数为 4 人(主播和 3 个发言者)
为了展示完整的方案逻辑,我们在服务端部署了 Demo Server,通过 http 接口进行聊天室的创建和销毁。开发者需要和自己的应用服务器沟通实现该逻辑
多人会议和多人实时会话使用聊天室 id 作为房间名,保证同一个聊天室里的人在同一个多人会议房间
房间的创建者负责和 Demo Server 沟通以创建和销毁聊天室,并负责多人会议和多人实时会话房间的创建。其他参与者通过创建者产生的聊天室 id 进入聊天室、多人会议房间和多人白板
老师可通过Web上传文档(可支持类型有:pdf/ppt/pptx),并通过文档转码能力拉取转为分页图片后的文档信息,从而实现在线文档的同步教学
老师可通过chrome插件实现屏幕共享
- 由于 SDK 不是直接针对在线的方案模型,我们在应用上层补充一些控制指令来保证业务逻辑。
- 控制指令分为两套:
- 多人会议和多人白板的权限控制消息。主要使用用聊天室自定义消息和点对点自定义系统通知
- 多人白板笔迹协议。使用多人实时会话的数据传输通道
目录结构
- build: 构建时使用的辅助工具类
- env: 不同环境的基础配置
- src: 源码目录(具体描述见工程根目录下的readme.md文件)
- ssh: 开发环境运行http-server时用到的key与cert文件
权限控制消息
- 消息体格式
封装为 json,放在消息的 attach 中,格式示例如下:
{
“type”:10,
“data”: { "room_id":123,"command”:3,"uids":["a","b"]}
}
type 10 用于将权限控制自定义消息类型和其他自定义消息区分开来。权限控制消息体在 data 中:
room_id
是一通在线教育会话的标识 id,即聊天室 id;
command
是具体的控制指令类型
uids
是指令相关的用户 id 信息
控制和同步流程
成员申请和取消申请发言,发送点对点消息给主持人
- 主持人反馈申请结果,发送点对点消息给成员,如果通过,主持人更新状态(全量)发送一条聊天室消息
- 有新成员进入聊天室,主持人发送点对点消息给成员
- 成员进入聊天室后,如果没有收到聊天室的发言权限通知,主动发送聊天室消息给所有成员请求发言权限,有权限的成员发送点对点消息(自己的权限状态)给请求者 ,此类消息新成员在收到主持人的状态通知前有效
成员退出聊天室,所有人更新权限列表(把退出的人从权限列表中删掉),主持人更新状态(全量)发送一条聊天室消息
指令解释
command | 解释 | data 举例 |
---|---|---|
1 | 主持人通知有权限的成员列表 | {"type":10,"data":{"room_id":"123","command":1,"uids":["a","b"]}} |
2 | 成员向所有人请求有权限的成员 | {"type":10,"data":{"room_id":"123","command":2}} |
3 | 有权限的成员向请求者返回自己有权限的通知 | {"type":10,"data":{"room_id":"123","command":3,"uids":["myid"]}} |
10 | 成员向主持人请求连麦权限 | {"type":10,"data":{"room_id":"123","command":10}} |
11 | 主持人同意连麦请求,主持人同时发送群发1消息 | {"type":10,"data":{"room_id":"123","command":11}} |
12 | 主持人关闭某成员发言权限 | {"type":10,"data":{"room_id":"123","command":12}} |
13 | 成员向主持人取消申请发言权限 | {"type":10,"data":{"room_id":"123","command":13}} |
白板绘制插件接入
接入步骤
// step1 初始化白板实例
this.wb = WhiteBoard.getInstance({
nim: nim, //nim实例
debug: true
});
// step2 初始化绘制插件
this.drawPlugin = new DrawPlugin(container, {
UID: this.wb.getAccount(),
nim: nim // nim实例
})
this.drawPlugin.enableDraw(true)
// step3 注册绘制插件的data事件,并交由白板转发数据
this.drawPlugin.on('data', (obj) => {
let { toAccount = 0, data } = obj
this.wb.sendData({ toAccount, data })
})
// step4 注册白板data事件,并交由绘制插件执行
this.wb.on('data', (obj) => {
this.drawPlugin.act({ account: obj.account, data: obj.data })
});
以上四步即可完成绘制插件的数据接收与发送,由绘制插件接管白板绘制。
白板绘制插件接口介绍
// 结束白板时调用,做内存释放。
this.drawPlugin.destroy()
// 设置容器
this.drawPlugin.setContainer(node)
// 设置颜色
this.drawPlugin.setColor(color)
// 观众权限:禁止绘图
this.drawPlugin.changeRoleToAudience()
// 互动者权限:允许绘图
this.drawPlugin.changeRoleToPlayer()
// 设置绘图模式: 激光笔
this.drawPlugin.setDrawMode('flag')
// 设置绘图模式: 自由绘图模式
this.drawPlugin.setDrawMode('free');
// 撤销
this.drawPlugin.undo();
// 反撤销
this.drawPlugin.redo();
// 清除
this.drawPlugin.clear();
// 同步准备,用于主播重连后调用,清除学生端数据
this.drawPlugin.syncBegin();
// 同步请求,用于学生进入房间后,向主播请求历史数据
this.drawPlugin.syncRequest(account);
// 设置背景
this.drawPlugin.image({
url,
docId: item.docId,
pageCount: item.pageCount,
currentPage: index
});
// 取消背景设置
this.drawPlugin.clearImage();
多人白板笔迹协议
包
调用SDK发送或者从SDK接收到的一条多人实时会话数据,包
是一个字符串,由一条或多条命令
组成,命令
之间用分号隔开
命令1;命令2;命令3;
命令
多人白板的原子协议,每个命令
必须遵守以下格式:
type:value,value,value,…
每一个type
对应明确数量的value
,value
之间用逗号隔开
type
命令
的类型,整数
type
和value
:
类型 | type | value,value,value,… |
---|---|---|
起始点 | 1 | x,y,rgb |
移动点 | 2 | x,y,rgb |
结束点 | 3 | x,y,rgb |
上一步 | 4 | |
包序号 | 5 | id |
清空 | 6 | |
清空响应 | 7 | |
同步请求 | 8 | |
同步 | 9 | uid,end |
同步准备 | 10 | |
同步准备响应 | 11 | |
标记 | 12 | x,y,rgb |
标记结束 | 13 | |
文档分享 | 14 | docId,pageCount,currentPage,type |
说明
x y: 浮点型的相对坐标,值为相对于画板的长宽,例如下图的白板示意中 x 点的坐标为 x= 2/6 = 0.3333 y = 3/4 = 0.75
o | o | o | o | o | o |
---|---|---|---|---|---|
o | o | o | o | o | o |
o | x | o | o | o | o |
o | o | o | o | o | o |
rgb:整型的颜色值,例如一个颜色的RGB是 0xF3F6F9,rgb=15988473
上图的坐标点如果是一个移动点
,那这个点的命令
为:
2:0.3333,0.75,15988473
包序号:可选,主要用来调试是否有丢包
docId:文档id
pageCount:文档总页数
currentPage:文档当前页
type:文档操作类型
清空:只能主播发,发广播包;主播清空所有笔画,同时发该命令,非主播收到该命令后,清空所有笔画;
清空响应:参与者收到清空命令后发清空响应
广播包;其他人收到该响应后,清空该参与者的笔画。该步骤用来避免清空过程中同时有人在画图可能会导致的数据不一致
同步请求: 非主播加入多人实时会话成功以后,发送该命令给主播
同步准备: 主播B多人白板登录成功(包含断网重新登录成功)后,发送该指令给所有其他人,然后再发同步
。其他人收到该消息后清空本地所有数据
同步准备响应: 非主播收到同步准备
后,回该消息给主播。该消息用于适配服务端录制回放功能,主播收到以后无需处理
同步:1. 主播B收到A同步请求
后,主播B把所有的用户ABCD的笔画的数据单播发送给用户A,每个用户的数据单独通过该协议发送;2.主播B发出同步准备
后,同步所有用户的数据给所有人
end=1表示该用户的数据发完了,例如C的数据可以一次发完:
8:uid_C,1;1:x,y,rgb;2:x,y,rgb;…;3:x,y,rgb;
end=0表示没发完,后面还有数据。例如C的数据量太大,超过SDK接口的发送限制,可以分多条发送,比如分3个包
发送:
包1,end=0
8:uid_C,0;1:x,y,rgb;2:x,y,rgb;…;3:x,y,rgb;
包2,end=0
8:uid_C,0;1:x,y,rgb;2:x,y,rgb;…;3:x,y,rgb;
包3,end=1
8:uid_C,1;1:x,y,rgb;2:x,y,rgb;…;3:x,y,rgb;
A在收完C所有的数据以后,清空C当前的数据,用同步的数据代替之。
标记: 主播发送标记消息,非主播收到后在界面显示标记图标,标记图标没有轨迹
标记结束: 主播发送标记结束后,非主播清除界面之前显示的标记
Chrome屏幕共享插件安装
在 Chrome 浏览器内,Web端教育demo已经实现屏幕共享功能,支持整个桌面共享、应用共享的能力,第一次使用Web端屏幕共享功能时,点击屏幕共享按钮,显示的窗口如下:
点击下载Chrome插件按钮,将插件下载到本地,点击下载完的插件,选择“在文件夹中显示”菜单,找到下载文件所在目录,然后点击“CHROME屏幕共享插件安装教程”按钮,阅读插件安装指导。
打开 Chrome 浏览器右上角,找到『更多工具』-『扩展程序』;
将下载完的插件拖拽上传到扩展程序页面
点击 按钮,安装屏幕共享插件,当Chrome浏览器器的工具栏右上角显示图标,则下载安装完成。 插件安装成功之后,再点击屏幕共享按钮,选择其中一个共享窗口,点击分享,则可以将窗口分享出去。
备注:
1、如果插件拖拽安转失败,请重新启动浏览器再尝试安装,如果此种方式失败,再重启电脑尝试安装
2、如果上述安装方式失败,且可以正常访问Chrome应用商店,请搜索yunxin web screeensharing 插件
点击 按钮,安装屏幕共享插件。
点击 按钮,安装屏幕共享插件,当Chrome浏览器器的工具栏右上角显示 图标,则下载安装完成,屏幕共享功能可以使用。
修改代码为己用
- 在remoteTrack事件的回调中,回调参数为
{account,uid,track}
,媒体类型判断为obj.track.kind
,值是 video 或 audio - 开启一通会话请确保先调用
startRtc()
,resolve
后再去调用开启设备的流程 - 调用
leaveChannel
即可完成退出房间的逻辑,无需再关闭摄像头、麦克风等,SDK内部会自动处理