客户端实现
更新时间: 2024/08/02 11:38:27
本文档为您展示智慧云课堂在 1 对 1 互动教学场景下的常见功能,您可以参考智慧云课堂方案实现 1 对 1 互动教学场景,也可以在此基础上实现业务功能的灵活扩展。
技术架构
如果示例项目中默认实现的 UI 不符合您的预期,您可以按需实现自己的用户界面,即只使用我们封装好的组件所提供的音视频能力,自行实现 UI 部分。
教育组件功能模块:
- EduUI:教育组件的UI配置,包括一对一教学、多人小班课、互动大班课、直播大班课等场景的 ViewController、View 以及 model 部分。
- EduLogic:基于云信 NERTC SDK、IM SDK 以及白板 SDK 定制的,针对教育场景的逻辑实现,包括如下3个服务:
- NEEduRtcService 是音视频服务,提供可供 App 调用的音视频相关方法。
- NEEduIMService 是 IM 服务,提供可供 App 调用的即时通信、聊天室相关方法。
- NEEduBoardService 是互动白板服务,提供可供 App 调用的互动白板相关方法。
业务流程
下图展示了启动智慧云课堂的基本流程:
当 App 客户端请求加入云课堂时,业务流程如下:
- App 客户端向 App 服务端申请创建场景。根据创建场景的配置参数,查询房间信息,如果房间不存在,则生成房间信息;如果房间已存在,提示已存在。
- 加入课堂,先检测 IM 是否已经登录,没登录需要登录,登录失败返回加入失败。向服务端发送加入课堂接口,成功后进入课程页面, 根据状态加入音视频房间。
- 请求快照,获取聊天室 ID,加入聊天室。
- 初始化组件。加入白板房间,白板服务器会进行 IM 账号鉴权。更新本地流,开始拉流。
客户端实现方法
步骤一 集成到项目
-
新建 Android 工程。
- 运行 Android Sudio,在顶部菜单栏选择 File > New > New Project... 新建工程。
- 选择 Phone and Tablet > Empty Activity ,并单击 Next。
- 配置工程相关信息。
Android API 等级必须大于等于21。
- 单击 Finish,完成工程创建。
-
添加依赖模块。
-
复制示例项目中的
Modules
和config.gradle
、config.properties
等相关配置文件至当前目录。 -
settings.gradle
引入Modules
。implementation project(':edu-ui') implementation project(':recordplay-ui') implementation project(':viewbinding')
-
修改工程目录下的
app/build.gradle
文件,添加智慧云课堂 SDK 相关的依赖。allprojects { repositories { google() jcenter() maven{ url 'https://oss.sonatype.org/content/repositories/snapshots/' } } } // 若出现 More than one file was found with OS independent path 'lib/arm64-v8a/libc++_shared.so'. // 可以在主 module 的 build.gradle 文件中 android 闭包内追加如下 packageOptions 配置 android{ //...... packagingOptions { pickFirst 'lib/arm64-v8a/libc++_shared.so' pickFirst 'lib/armeabi-v7a/libc++_shared.so' } } dependencies { //...... // 添加EduUI依赖 implementation project(':edu-ui') }
-
在顶部菜单栏单击 Build > Make Project 构建工程,下载依赖。
下载完成后即可在代码中引入云课堂组件中的类和方法。
-
-
权限配置。
智慧云课堂 SDK 正常工作需要应用获取以下权限:
<!-- 网络相关 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 多媒体 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.Manifest.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
以上权限已经在SDK内部进行声明,开发者无需在
AndroidManifest.xml
文件中重新声明这些权限,但需要自己编码实现运行时的权限申请。运行时的权限可在应用首页中统一申请,详细信息请参考 Android 官方开发文档。
步骤二 初始化组件
-
进行全局配置。
首先创建
NEEduOptions
实例对 SDK 进行全局配置,然后调用config
方法传入该实例。NEEduOptions 相关参数说明如下表所示。
| 配置项 | 说明 | |---|---| | APP_KEY | 应用的 AppKey。可以在网易云信控制台中查看。 | | BASE_URL | 应用服务器地址。私有化配置时需替换为私有化部署地址 | | AUTHORIZATION | 调用服务端接口时,请求头中的校验参数。 | | reuseIM | 配置是否复用底层NIM-SDK的长连接通道,默认关闭。仅当应用中同时还需独立接入和使用NIM-SDK,才需要开启该配置,其他情况下请忽略该配置。 |
NEEduUiKit.config( this, NEEduOptions( BuildConfig.APP_KEY, BuildConfig.AUTHORIZATION, BuildConfig.API_BASE_URL, reuseIM ) )
-
初始化组件
创建 NEEduUiKit 实例,调用 init 方法进行初始化。相关参数说明如下表所示。
| 配置项 | 说明 | |---|---| | uuid | 用户鉴权userUuid。匿名登录时请设置为空字符串"" | | token | 用户鉴权userToken。匿名登录时请设置为空字符串"" |
示例代码:
NEEduUiKit.init(uuid, token).observeOnce(viewLifecycleOwner, initObserver)
-
(可选)私有云场景的初始化配置。
当部署场景为私有云时,您需要对云课堂用到的IM、RTC、互动白板和应用服务器,增加私有云相关配置。公有云请忽略该配置。
V1.9.2及之后的智慧云课堂版本,才支持私有云的配置。
-
应用服务器的私有云配置。
在config.properties 中,将BASE_URL的值改为私有云中应用服务的URL地址。
| 配置项 | 说明 | |---|---| | BASE_URL | 应用服务在私有云中的URL地址 |
-
IM的私有云配置。
- 联系网易云信技术支持获取 IM 私有云的配置文件,并将文件名改为server.conf。
- 将server.conf拷贝到项目的assets目录。
- 在build.gradle文件中添加assets资源目录的配置,示例代码如下:
Groovy sourceSets { main { assets.srcDirs = ['assets'] } }
- 在云课堂初始化时,打开 IM 私有云配置的开关。示例代码如下:
Kotlin val option = NEEduOptions( BuildConfig.APP_KEY, BuildConfig.AUTHORIZATION, BuildConfig.API_BASE_URL, PreferenceUtil.reuseIM ) option.useIMAssetServerAddressConfig = true // IM privatization NEEduUiKit.config( this, option )
-
RTC的私有云配置。
-
联系网易云信技术支持获取 RTC 私有云的配置文件,并将文件名改为rtc_server.conf。
-
将rtc_server.conf拷贝到项目的assets目录。
-
在build.gradle文件中添加assets资源目录的配置,样例如下:
如果之前已配置该内容,请忽略该步骤。
Groovy sourceSets { main { assets.srcDirs = ['assets'] } }
- 在云课堂初始化时,打开 RTC 私有云配置的开关。示例代码如下:
Kotlin val option = NEEduOptions( BuildConfig.APP_KEY, BuildConfig.AUTHORIZATION, BuildConfig.API_BASE_URL, PreferenceUtil.reuseIM ) option.useRtcAssetServerAddressConfig = true // Rtc privatization NEEduUiKit.config( this, option )
-
-
互动白板的私有云配置。
-
联系网易云信技术支持获取互动白板私有云的配置文件,并将文件名改为wb_server.conf。
-
将wb_server.conf拷贝到项目的assets目录。
-
在build.gradle文件中添加assets资源目录的配置,示例代码如下:
如果之前已配置该内容,请忽略该步骤。
Groovy sourceSets { main { assets.srcDirs = ['assets'] } }
- 在云课堂初始化时,打开互动白板私有云配置的开关。示例代码如下:
Kotlin val option = NEEduOptions( BuildConfig.APP_KEY, BuildConfig.AUTHORIZATION, BuildConfig.API_BASE_URL, PreferenceUtil.reuseIM ) option.useWbAssetServerAddressConfig = true // whiteboard privatization NEEduUiKit.config( this, option )
- 在WhiteboardManager.kt中,将如下示例代码中的互动白板webview地址,修改为您的私有云地址。
Kotlin private const val DEFAULT_URL = "https://yunxinent-demo.netease.im/xedu/webview/g2/webview.html" // 互动白板页面的私有云webview地址
- 在NERecordWhiteboardManager.kt中,将如下示例代码中的白板录像回放的webview地址,修改为您的私有云地址。
Kotlin private const val DEFAULT_URL = "https://yunxinent-demo.netease.im/xedu/webview/g2/webview.html" // 白板的录像回放页面的私有云webview地址
-
-
步骤三 学生或老师加入课堂
学生或老师加入课堂时,会使用 NEEduClassOptions
的实例创建课堂并且加入,如果对应课堂号的课堂已经存在就直接加入。NEEduClassOptions
相关参数说明如下表所示。
配置项 | 说明 |
---|---|
classId | 课程号,课堂唯一标识 |
className | 课程名称 |
nickName | 用户在课堂中的昵称 |
sceneType | 课堂类型,有4种类型: 一对一教学、多人小班课、互动大班课、直播大班课 |
roleType |
角色类型,包括如下两种角色:
|
示例代码:
eduManager.enterClass(neEduClassOptions).map {
if (it.success()) {
if (neEduClassOptions.roleType == NEEduRoleType.HOST) {
when (neEduClassOptions.sceneType) {
NEEduSceneType.ONE_TO_ONE -> {
OneToOneTeacherActivity.start(context)
}
NEEduSceneType.SMALL -> {
SmallClazzTeacherActivity.start(context)
}
NEEduSceneType.BIG -> {
BigClazzTeacherActivity.start(context)
}
}
} else {
when (neEduClassOptions.sceneType) {
NEEduSceneType.ONE_TO_ONE -> {
OneToOneStudentActivity.start(context)
}
NEEduSceneType.SMALL -> {
SmallClazzStudentActivity.start(context)
}
NEEduSceneType.BIG -> {
BigClazzStudentActivity.start(context)
}
}
}
}
it
}
步骤四 课堂互动
教学双方加入教室后,开启课程,开始课堂互动。智慧云课堂提供了丰富的课堂功能,如:互动白板、屏幕共享、举手、视频、语音等。
-
开始上课。
教师端开始上课。
示例代码:
// 教师端开始上课 eduManager.getRoomService().startClass(roomUuid = eduRoom.roomUuid) .observe(this@BaseClassActivity, { ALog.i(tag, "startClazz") })
-
学生管理。
-
教师端调用
remoteUserVideoEnable
、remoteUserAudioEnable
方法控制学生端的摄像头和麦克风。如果需要和学生互动,可以开启对方的麦克风。示例代码:
// 打开学生端的摄像头 eduManager.roomConfig.memberStreamsPermission()?.apply { val self = entryMember video?.let { it -> // 首先检查自己是否有权限 if (it.hasAllPermission(self.role)) { // 接着调用remoteUserVideoEnable打开指定userUuid学生的摄像头 eduManager.getRtcService().remoteUserVideoEnable(userUuid, true) .observe(this@BaseClassActivity, { // 最后处理结果回调 ALog.i(tag, "switchRemoteUserVideo") ToastUtil.showShort(R.string.operation_successful) }) } } } // 打开学生端的音频 eduManager.roomConfig.memberStreamsPermission()?.apply { val self = entryMember audio?.let { it -> // 首先检查教师是否有权限 if (it.hasAllPermission(self.role)) { // 接着调用remoteUserAudioEnable打开指定userUuid学生的音频 eduManager.getRtcService().remoteUserAudioEnable(member.userUuid, !member.hasAudio()) .observe(this@BaseClassActivity, { // 最后处理结果回调 ALog.i(tag, "switchRemoteUserAudio") toastOperateSuccess() }) } } }
-
教师端调用
grantPermission
方法授权学生使用白板或屏幕共享。示例代码:
// 授权学生使用白板 eduManager.roomConfig.memberPropertiesPermission()?.apply { val self = entryMember whiteboard?.let { it -> // 首先检查教师是否有权限 if (it.hasAllPermission(self.role)) { // 接着调用grantPermission授权学生使用白板 eduManager.getBoardService().grantPermission(member.userUuid, !member.isGrantedWhiteboard()) .observe(this@BaseClassActivity, { // 最后处理结果回调 ALog.i(tag, "grantWhiteboardPermission") }) } } }
-
-
屏幕共享。
教师端或学生端调用
startScreenCapture
发起屏幕共享,共享本端屏幕给其他人观看。示例代码:
// 发起屏幕共享 // 首先创建屏幕共享配置实例 val config = NERtcScreenConfig().apply { contentPrefer = NERtcScreenConfig.NERtcSubStreamContentPrefer.CONTENT_PREFER_DETAILS videoProfile = RTCVideoProfile.kVideoProfileHD1080p } // 接着发起本地屏幕共享 eduManager.getShareScreenService().startScreenCapture(config, data, object : MediaProjection.Callback() { override fun onStop() { // 最后处理结果回调 runOnUiThread { stopLocalShareScreen() } } })
步骤五 录制回放
在线教育场景中,可实现上课过程中录制老师和学生的音视频、白板、屏幕共享,还原真实上课场景,供学生和教师回放复习。目前所有课堂模式都支持录制回放。录制功能包含在云端录制服务中,用户端只需要接入Recordplay
回放组件,回放功能依赖的代码包含在组件里面。
教师开始上课时,服务器会自动开始录制。课程结束时,需要等待服务端进行转码,预计 20 分钟左右。转码完成后,用户可以通过 Recordplay 回放组件观看录制内容。在示例项目中,展示了如何快速接入 Recordplay 组件。接入步骤如下:
-
复制示例项目中,
Recordplay
回放组件的recordplay-logic
、recordplay-model
、recordplay-ui
模块至当前项目目录。 -
添加模块依赖。
示例代码:
implementation project(':recordplay-ui')
-
课程结束后,回放观看端调用
fetchRecord
获取上一次的回放记录,并创建回放播放器实例。示例代码:
RecordPlayRepository.appKey = BuildConfig.APP_KEY RecordPlayRepository.baseUrl = BuildConfig.API_BASE_URL // 根据roomUuid和rtcCid查询回放记录,并创建回放播放器实例 NERecordPlayUiKit.fetchRecord(roomUuid, rtcCid) .observeOnce(viewLifecycleOwner, initRecordObserver) // 回放记录查询结果回调 private val initRecordObserver = { t: NEResult<NERecordPlayer> -> when { t.success() -> { // 调用成功后进入回放观看界面 enterRecordPlay() } // 音视频未完成转码,需要等待服务端20分钟左右进行转码 t.code == NEEduHttpCode.NO_CONTENT.code -> { ALog.i("init record failed, result $t") ToastUtil.showLong(getString(R.string.course_playback_file_is_being_transcoded)) } else -> { // 获取回放记录失败 ALog.i("init record failed, result $t") val tip = context?.let { NEEduErrorCode.tipsWithErrorCode(it, t.code) } if (!TextUtils.isEmpty(tip)) { ToastUtil.showLong(tip!!) } else { ToastUtil.showLong(getString(R.string.open_recordplay_fail_try_again)) } } } }
-
如果成功,那么跳转到
Recordplay
模块中的NERecordActivity
回放界面。示例代码:
NERecordActivity.start(requireActivity()) <!-- 回放播放界面 --> <activity android:name=".NERecordActivity" android:configChanges="keyboardHidden|screenSize|orientation" android:launchMode="singleTop" android:screenOrientation="sensorLandscape" android:theme="@style/Theme.EduApp.Landscape" />
-
初始化播放器。
示例代码:
var recordPlayer = NERecordPlayer.instance recordPlayer.init(application, this)
-
开始播放。
示例代码:
// 判断播放状态 when (recordPlayer.getState()) { // 处于播放就绪状态,开始播放 NERecordPlayState.PREPARED, NERecordPlayState.PAUSED -> recordPlayer.start() ... }
-
暂停播放。
示例代码:
// 判断播放状态 when (recordPlayer.getState()) { // 处于播放暂停状态,暂停播放 NERecordPlayState.PLAYING -> recordPlayer.pause() ... }
-
拖动进度。
示例代码:
// 根据progress改变播放进度,progress介于 0 ~ 100 之间 recordPlayer.seek(recordPlayer.getDuration() * progress / 100)
步骤六 结束课堂
调用 NEEduUiKit.destroy()
结束课程。
示例代码:
eduManager.getRtcService().leave() // 离开rtc房间
eduManager.getMemberService().getLocalUser().let {
if (it != null && it.hasSubVideo()) {
stopLocalShareScreen { // 如果打开了屏幕,那么先关闭屏幕共享
NEEduUiKit.destroy() // 结束课堂
finish()
}
} else {
NEEduUiKit.destroy() // 如果没有打开屏幕,那么直接结束课程
finish()
}
}