最近很多学生已经在线开启了新的一学期。很多教育巨头为老师与学生搭建的在线教学场景,都是通过声网 Agora SDK 实现的。为了方便更多用户可以基于 Agora SDK 快速实现多种在线教学场景,我们现已开源声网云课堂 Demo,大家可在文末获取源码。
图:iOS 版声网云课堂
除了 demo 开源,我们也提供了Web、Android、iOS 应用供大家体验。推荐老师使用 Web 端应用,学生可使用以上任一版本。
Web(推荐用Chrome访问):https://solutions.agora.io/education/web/
iOS:App Store 搜索“声网云课堂”
Android:华为应用市场搜索“声网云课堂”,或扫码下载(密码:123)
声网云课堂 Demo 实现的教学场景包括:
1 对 1 互动教学:1 位老师对 1 名学生进行专属线上辅导教学,老师和学生能实时音视频互动。
1 对 N 在线小班课:1 位教师对多名学生进行在线辅导教学,最多支持 16 名学生。
低延迟大班课:1 位老师进行教学,多名学生实时观看和收听,学生人数无上限。与此同时,学生可以“举手”发言,与老师进行实时音视频互动。
除了支持实时音视频的互动,本示例项目还支持在线课堂中许多必不可少的功能。我们仅以 Web 端来介绍一下。
教学白板:在以上三个场景中,都支持白板功能。1 对 1 互动教学中,老师和学生都可以操作白板;1 对 1 在线小班课、低延迟大班课中,可以授权学生操作白板。
图:小班课场景下的白板
图:小班课场景下,可授权学生用白板
共享PPT等文件:老师可以在授课的同时,共享 PPT、PDF、word、图片、mp4、mp3 等多媒体文件。学生端只能观看,不能上传。
图:在线演示 PPT 等文件
屏幕共享:老师可以发起屏幕共享,展示桌面、网页或其他应用窗口。
图:共享屏幕、应用窗口或 Web 页面
文字消息:除了连麦以外,学生还可以通过文字消息在课堂中提问交流。
图:右侧可以发送文本消息
录制回放:老师登录 Web端,可以进行课程录制。在白板的下方,有录制按钮,制完成后,在右侧的消息窗口中会自动弹出回放地址。
图:录制按钮在屏幕下方
另外,老师端还支持一些控制学生端音视频流的权限,比如在 1 对 N 在线小班课中,老师还可以访问学生列表,开启或关闭学生的麦克风,比如在希望某个学生发言,那么就可以直接开启对方的麦克风。接下来,我们来通过 Demo 中的代码来看一下如何实现以上各个功能。
以下功能我们会主要基于Java 代码来进行讲解。
这个示例中,课程的直播、师生的连麦,都是基于 Agora SDK 实现的。我们通过以下代码可以让用户加入RTC频道,实现音视频的互通。
@Override
public void joinChannel(Map data) {
sdk.joinChannel(data.get(TOKEN), data.get(CHANNEL_ID), null, Integer.valueOf(data.get(USER_ID)));
}
在课堂中的文字消息、控制指令(比如学生发出申请使用白板)等,都是基于 Agora 实时消息RTM SDK 实现的。在这里我们集成 RTM SDK 后,通过以下代码让用户加入 RTM 频道。
public void joinChannel(String rtcToken) {
RtmManager.instance().joinChannel(new HashMap() {{
put(SdkManager.CHANNEL_ID, getChannelId());
}});
RtcManager.instance().joinChannel(new HashMap() {{
put(SdkManager.TOKEN, rtcToken);
put(SdkManager.CHANNEL_ID, getChannelId());
put(SdkManager.USER_ID, getLocal().getUserId());
}});
}
大家可以参考 Demo 中的白板部分的代码来实现白板功能,如下所示:
public void initBoard(String uuid, String token) {
if (TextUtils.isEmpty(uuid)) return;
boardManager.getRoomPhase(new Promise() {
@Override
public void then(RoomPhase phase) {
if (phase != RoomPhase.connected) {
pb_loading.setVisibility(View.VISIBLE);
boardManager.roomJoin(uuid, token, new Callback() {
@Override
public void onSuccess(RoomJoin res) {
RoomParams params = new RoomParams(uuid, res.roomToken);
boardManager.init(whiteSdk, params);
}
@Override
public void onFailure(Throwable throwable) {
ToastManager.showShort(throwable.getMessage());
}
});
}
}
@Override
public void catchEx(SDKError t) {
ToastManager.showShort(t.getMessage());
}
});
}
老师端的屏幕共享,仅支持在 Web 端发起,以下是 Web 代码是老师端的实现逻辑。
async startScreenShare (token: string) {
this.shareClient = new AgoraRTCClient();
await this.shareClient.createLocalStream({
video: false,
audio: false,
screen: true,
screenAudio: true,
streamID: SHARE_ID,
microphoneId: '',
cameraId: ''
})
await this.shareClient.createClient(APP_ID);
await this.shareClient.join(SHARE_ID, this.channel, token);
await this.shareClient.publish();
this.shared = true;
}
以下 Java 代码是学生端处理屏幕共享流(仅支持观看屏幕共享)的代码。
public void onScreenShareJoined(int uid) {
if (surface_share_video == null) {
surface_share_video = RtcManager.instance().createRendererView(this);
}
layout_whiteboard.setVisibility(View.GONE);
layout_share_video.setVisibility(View.VISIBLE);
removeFromParent(surface_share_video);
surface_share_video.setTag(uid);
layout_share_video.addView(surface_share_video, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
RtcManager.instance().setupRemoteVideo(surface_share_video, VideoCanvas.RENDER_MODE_FIT, uid);
}
@Override
public void onScreenShareOffline(int uid) {
Object tag = surface_share_video.getTag();
if (tag instanceof Integer) {
if ((int) tag == uid) {
layout_whiteboard.setVisibility(View.VISIBLE);
layout_share_video.setVisibility(View.GONE);
removeFromParent(surface_share_video);
surface_share_video = null;
}
}
}
在线录制只有使用 Web 端的老师才可以发起,以下是 Web 老师端开启云端录制的代码逻辑:
public async start(): Promise {
if (this.resourceId === undefined) {
throw {
recordingErr: {
message: 'start recording failed',
},
reason: 'resourceId is undefined',
}
}
const response = await AgoraFetch(`${PREFIX}/v1/apps/${this.agoraAppId}/cloud_recording/resourceid/${this.resourceId}/mode/${this.mode}/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: this.basicAuthorization(this.customerId, this.customerCertificate),
},
body: JSON.stringify({
cname: this.channelName,
uid: this.uid,
clientRequest: {
token: this.token,
recordingConfig: this.recordingConfig,
storageConfig: this.storageConfig,
},
})
});
const res = await response.json();
if (typeof res.sid === "string") {
this.recordId = res.sid;
} else {
throw {
recordingErr: {
message: 'start recording failed',
},
reason: 'recordId is invalid',
}
}
return res;
}
本示例项目支持以下平台和版本:
iOS 10 及以上。
Android 4.4 及以上。
Web Chrome 72 及以上。
注意,目前示例 Demo 还未适配 iPad,所以部分 iPad 运行 Demo 时会出现黑边,需要你根据自己的业务需求在代码中进行调整、适配。
这是由于本示例项目没有用户系统,无法及时查询在线人数。本示例项目使用 Agora RTM SDK 查询在线人数。在用户异常退出后,RTM 无法及时知晓该用户状态。一般情况下,需要等待 10 秒左右 RTM 才能更新当前用户状态。如果你有用户系统,就可以不需要使用 RTM 查询在线人数。
这很有可能是因为 Agora RTC SDK 的 token 配置存在问题。请检查你的 token 配置。请在声网文档中心搜索「校验用户权限」,参考相关文档。
请注意,Agora RTC SDK 的 token 和 channelId 需要对应。如果 channelId 变了,配置的 token 也需要变。
Demo 中的白板采用的是合作伙伴 Netless 的接口,如果发现连接异常,可能是 token 配置有问题。白板的 token 分为 sdkToken 和 roomToken,请在https://developer.netless.link/docs/blog/term/ 搜索这两个关键词,检查 token 的设置是否存在问题。
如果你的团队有后端开发能力,我们建议使用声网本地服务端录制 SDK,将录制功能集成在后端。
如果没有后端开发团队,也可以参考声网文档中心的「云端录制快速开始」,实现云端录制功能。要注意,本示例项目将录制集成在了前端,存在暴露 OSS 访问操作的安全隐患,部署云录制业务也可能存在困难。 如遇到问题可以访问 RTC 开发者社区(rtcdeveloper.com),发帖寻求帮助。
声网云端录制服务支持合流录制和单流录制。合流录制是比较简单的集成方案,具体可参考声网文档中心教程。
本示例项目中,白板课件管理部署在前端。建议你实现排课业务后,在课程里预先上传课件,Web 端只读取课件即可。
本示例项目中涉及的 OSS 相关的接入,建议参考 stsToken 的上传方案集成(https://help.aliyun.com/document_detail/32077.html)。建议不要使用示例项目中的 accessKey 和 secretKey 的上传方案集成,可能存在安全隐患。
本示例源码中,屏幕共享的 UID 固定为 7,你需要根据自己的业务进行调整。
本示例项目中的 channelId 是 MD5 后的,你需要对接自己的环境进行修改。
推荐你使用自己的业务后台管理频道属性,将 Agora RTM SDK 作为指令收发器,配合 Agora Native/Web SDK 实现具体业务逻辑。
获取 Demo 源码,请访问Github:https://github.com/AgoraIO-Community/eEducation
欢迎在测试源码的同时,提出更多关于 Demo 优化、改进的更多想法。我们会对每位提出有效建议的开发者送上丰厚奖励。