客户端实现

更新时间: 2022/01/20 03:53:24

本文档为您展示智慧云课堂在 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 客户端请求加入云课堂时,业务流程如下:

  1. App 客户端向 App 服务端申请创建场景。根据创建场景的配置参数,查询房间信息,如果房间不存在,则生成房间信息;如果房间已存在,提示已存在。
  2. 加入课堂,先检测 IM 是否已经登录,没登录需要登录,登录失败返回加入失败。向服务端发送加入课堂接口,成功后进入课程页面, 根据状态加入音视频房间。
  3. 请求快照,获取聊天室 ID,加入聊天室。
  4. 初始化组件。加入白板房间,白板服务器会进行 IM 账号鉴权。更新本地流,开始拉流。

客户端实现方法

步骤一 集成到项目

  1. 新建 Android 工程。

    1. 运行 Android Sudio,在顶部菜单栏选择 File > New > New Project... 新建工程。
    2. 选择 Phone and Tablet > Empty Activity ,并单击 Next
    3. 配置工程相关信息。

    Android API 等级必须大于等于21。

    1. 单击 Finish,完成工程创建。
  2. 添加依赖模块。

    1. 复制示例项目中的 Modulesconfig.gradleconfig.properties 等相关配置文件至当前目录。

    2. settings.gradle 引入 Modules

      implementation project(':edu-ui')
      implementation project(':recordplay-ui')
      implementation project(':viewbinding')
      
    3. 修改工程目录下的 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')
      }
      
    4. 在顶部菜单栏单击 Build > Make Project 构建工程,下载依赖。

      下载完成后即可在代码中引入云课堂组件中的类和方法。

  3. 权限配置。

    智慧云课堂 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 官方开发文档

步骤二 初始化组件

  1. 进行全局配置。

    首先创建 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
        )
    )
    
  2. 初始化组件

    创建 NEEduUiKit 实例,调用 init 方法进行初始化。相关参数说明如下表所示。

    | 配置项 | 说明 | |---|---| | uuid | 用户鉴权userUuid。匿名登录时请设置为空字符串"" | | token | 用户鉴权userToken。匿名登录时请设置为空字符串"" |

    示例代码:

    NEEduUiKit.init(uuid, token).observeOnce(viewLifecycleOwner, initObserver)
    
  3. (可选)私有云场景的初始化配置。

    当部署场景为私有云时,您需要对云课堂用到的IM、RTC、互动白板和应用服务器,增加私有云相关配置。公有云请忽略该配置

    V1.9.2及之后的智慧云课堂版本,才支持私有云的配置。

    • 应用服务器的私有云配置。

      config.properties 中,将BASE_URL的值改为私有云中应用服务的URL地址。

      | 配置项 | 说明 | |---|---| | BASE_URL | 应用服务在私有云中的URL地址 |

    • IM的私有云配置。

      1. 联系网易云信技术支持获取 IM 私有云的配置文件,并将文件名改为server.conf
      2. server.conf拷贝到项目的assets目录。
      3. build.gradle文件中添加assets资源目录的配置,示例代码如下:
      Groovy
      sourceSets {
          main {
              assets.srcDirs = ['assets']
          }
      }
      
      
      1. 在云课堂初始化时,打开 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的私有云配置。

      1. 联系网易云信技术支持获取 RTC 私有云的配置文件,并将文件名改为rtc_server.conf

      2. rtc_server.conf拷贝到项目的assets目录。

      3. build.gradle文件中添加assets资源目录的配置,样例如下:

        如果之前已配置该内容,请忽略该步骤。

      Groovy
      sourceSets {
          main {
              assets.srcDirs = ['assets']
          }
      }
      
      
      1. 在云课堂初始化时,打开 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
      )
      
    • 互动白板的私有云配置。

      1. 联系网易云信技术支持获取互动白板私有云的配置文件,并将文件名改为wb_server.conf

      2. wb_server.conf拷贝到项目的assets目录。

      3. build.gradle文件中添加assets资源目录的配置,示例代码如下:

        如果之前已配置该内容,请忽略该步骤。

      Groovy
      sourceSets {
          main {
              assets.srcDirs = ['assets']
          }
      }
      
      
      1. 在云课堂初始化时,打开互动白板私有云配置的开关。示例代码如下:
       Kotlin
      
      val option = NEEduOptions(
          BuildConfig.APP_KEY,
          BuildConfig.AUTHORIZATION,
          BuildConfig.API_BASE_URL,
          PreferenceUtil.reuseIM
      )
      option.useWbAssetServerAddressConfig = true // whiteboard privatization
      NEEduUiKit.config(
          this,
          option
      )
      
      
      1. WhiteboardManager.kt中,将如下示例代码中的互动白板webview地址,修改为您的私有云地址。
      Kotlin
      private const val DEFAULT_URL = "https://yunxinent-demo.netease.im/xedu/webview/g2/webview.html"  // 互动白板页面的私有云webview地址
      
      1. 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

角色类型,包括如下两种角色:

  • host:老师
  • broadcaster:学生

示例代码:

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
}

步骤四 课堂互动

教学双方加入教室后,开启课程,开始课堂互动。智慧云课堂提供了丰富的课堂功能,如:互动白板、屏幕共享、举手、视频、语音等。

  1. 开始上课。

    教师端开始上课。

    示例代码:

    // 教师端开始上课
    eduManager.getRoomService().startClass(roomUuid = eduRoom.roomUuid)
        .observe(this@BaseClassActivity, {
            ALog.i(tag, "startClazz")
        })
    
  2. 学生管理。

    1. 教师端调用 remoteUserVideoEnableremoteUserAudioEnable 方法控制学生端的摄像头和麦克风。如果需要和学生互动,可以开启对方的麦克风。

      示例代码:

      // 打开学生端的摄像头
      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()
                      })
              }
          }
      }
      
    2. 教师端调用 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")
                      })
              }
          }
      }
      
  3. 屏幕共享。

    教师端或学生端调用 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 组件。接入步骤如下:

  1. 复制示例项目中,Recordplay 回放组件的 recordplay-logicrecordplay-modelrecordplay-ui 模块至当前项目目录。

  2. 添加模块依赖。

    示例代码:

    implementation project(':recordplay-ui')
    
  3. 课程结束后,回放观看端调用 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))
                }
            }
        }
    }
    
  4. 如果成功,那么跳转到 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" />
    
  5. 初始化播放器。

    示例代码:

    var recordPlayer = NERecordPlayer.instance
    recordPlayer.init(application, this)
    
  6. 开始播放。

    示例代码:

    // 判断播放状态
    when (recordPlayer.getState()) {
       // 处于播放就绪状态,开始播放
       NERecordPlayState.PREPARED, NERecordPlayState.PAUSED -> recordPlayer.start()
        ...
    }
    
  7. 暂停播放。

    示例代码:

    // 判断播放状态
    when (recordPlayer.getState()) {
        // 处于播放暂停状态,暂停播放
        NERecordPlayState.PLAYING -> recordPlayer.pause()
        ...
    }
    
  8. 拖动进度。

    示例代码:

    // 根据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()
    }
}
此文档是否对你有帮助?
有帮助
去反馈
  • 技术架构
  • 业务流程
  • 客户端实现方法
  • 步骤一 集成到项目
  • 步骤二 初始化组件
  • 步骤三 学生或老师加入课堂
  • 步骤四 课堂互动
  • 步骤五 录制回放
  • 步骤六 结束课堂