客户端实现
更新时间: 2024/08/02 11:38:27
本文档为您展示智慧云课堂在 1 对 1 互动教学场景下的常见功能,您可以参考智慧云课堂方案实现 1 对 1 互动教学场景,也可以在此基础上实现业务功能的灵活扩展。
技术架构
如果示例项目中默认实现的 UI 不符合您的预期,您可以按需实现自己的用户界面,即只使用我们封装好的组件所提供的音视频能力,自行实现 UI 部分。
教育组件的架构如下图所示。
- EduUI:教育组件的UI配置,包括一对一教学、多人小班课、互动大班课、直播大班课等场景的 ViewController、View 以及 model 部分。
- EduLogic:基于云信 NERTC SDK、IM SDK 定制的,针对教育场景的逻辑实现,包括如下2个服务:
- NEEduRtcService 是音视频服务,提供可供 App 调用的音视频相关方法。
- NEEduIMService 是 IM 服务,提供可供 App 调用的即时通信、聊天室相关方法。
- NEWhiteBoard:云信的互动白板服务,提供可供 App 调用的互动白板相关方法。
业务流程
智慧云课堂的启动流程如下图所示。
当 App 客户端请求加入云课堂时,业务流程如下:
- App 客户端向 App 服务端申请创建场景。根据创建场景的配置参数,查询房间信息,如果房间不存在,则生成房间信息;如果房间已存在,提示已存在。
- 加入课堂,先检测 IM 是否已经登录,没登录需要登录,登录失败返回加入失败。向服务端发送加入课堂接口,成功后进入课程页面, 根据状态加入音视频房间。
- 请求快照,获取聊天室 ID,加入聊天室。
- 初始化组件。加入白板房间,白板服务器会进行 IM 账号鉴权。更新本地流,开始拉流。
客户端实现方法
步骤一 集成组件
-
新建 iOS 工程。
- 运行 XCode,单击 Create a new Xcode project。
- 选择 Single View App,单击 Next。
- 配置工程相关信息,单击 Next。
- 选择合适的工程本地路径,并单击 Create 创建工程。
-
集成 SDK。
-
复制示例项目中的
Modules
文件夹到项目目录下。 -
进入工程目录,创建
Podfile
文件。pod
pod init
-
添加 pod 本地依赖,复制以下代码到
Podfile
文件。pod
platform :ios, '10.0' target '工程名' do use_frameworks! pod 'EduLogic', :path => 'Modules/EduLogic/EduLogic.podspec' pod 'NEWhiteBoard', :path => 'Modules/NEWhiteBoard/NEWhiteBoard.podspec' pod 'NEScreenShareBroadcaster', '~> 0.1.0'
-
执行以下命令安装组件。
pod
pod install
-
-
添加权限。
智慧云课堂 SDK 正常工作需要摄像头、麦克风权限。
-
在工程中的
Info.list
文件中配置相关的权限信息。Privacy - Microphone Usage Description Privacy - Camera Usage Description
-
在工程的 Signing&Capabilities 添加 Background Modes,并勾选 Audio、Airplay、and Picture in Picture。
-
步骤二 初始化组件
-
在使用组件其他功能之前,首先需要通过
setupAppKey
方法完成组件初始化。示例代码:
objective-c
NEEduKitOptions *option = [[NEEduKitOptions alloc] init]; option.authorization = [KeyCenter authorization]; option.baseURL = [KeyCenter baseURL]; [[EduManager shared] setupAppKey:[KeyCenter appKey] options:option];
-
(可选)私有云场景的初始化配置。
当部署场景为私有云时,您需要对云课堂用到的IM、RTC、互动白板和应用服务器,增加私有云相关配置。公有云请忽略该配置。
V1.9.2及之后的智慧云课堂版本,才支持私有云的配置。
-
应用服务器的私有云配置。
在
KeyCenter
类中,将baseUrl的值改为私有云中应用服务的URL地址。| 配置项 | 说明 | |---|---| | baseUrl | 应用服务在私有云中的URL地址 |
-
IM的私有云配置。
- 联系网易云信技术支持获取 IM 私有云的配置文件,并将文件名改为server.conf。
- 将server.conf拷贝到项目的根目录。
-
RTC的私有云配置。
- 联系网易云信技术支持获取 RTC 私有云的配置文件,并将文件名改为rtc_server.conf。
- 将rtc_server.conf拷贝到项目的根目录。
- 在云课堂初始化时,打开 RTC 私有云配置的开关。示例代码如下:
Objective-C NEEduKitOptions *option = [[NEEduKitOptions alloc] init]; option.authorization = [KeyCenter authorization]; option.baseURL = [KeyCenter baseURL]; // 读取私有云文件开关 option.configRead = YES; [[EduManager shared] setupAppKey:[KeyCenter appKey] options:option];
-
互动白板的私有云配置。
-
联系网易云信技术支持获取互动白板私有云的配置文件,并将文件名改为wb_server.conf。
-
将wb_server.conf拷贝到项目的根目录。
-
在云课堂初始化时,打开互动白板私有云配置的开关。示例代码如下:
Objective-C [NMCWhiteboardManager sharedManager].configRead = YES;
-
修改互动白板webView的URL地址。即在
NMCWhiteboardManager
类的.m
文件中,将NMCWhiteboardUrl的值设置为您的私有云地址。示例代码如下:Objective-C NSString *const NMCWhiteboardUrl = @”您的私有云地址“;
-
修改互动白板录像回放webView的URL地址。即在
NEWBRecordPlayer
类的.m
文件中,将NMCWhiteboardRecordUrl的值设置为您的私有云地址,示例代码如下:Objective-C NSString *const NMCWhiteboardRecordUrl = @”您的私有云地址“;
-
-
步骤三 教师端开启课堂
-
教师端登陆智慧云课堂。
调用
login
登录课堂,登录课堂成功后,自动创建对应课堂。示例代码:
objective-c
__weak typeof(self)weakSelf = self; [[EduManager shared] login:nil success:^(NEEduUser * _Nonnull user) { __strong typeof(self)strongSelf = weakSelf; //登录成功,创建房间 [strongSelf createRoom]; } failure:^(NSError * _Nonnull error) { //登录失败处理逻辑 }];
-
选择课堂类型。
课堂类型分为 1 对 1 教学、多人小班课、互动大班课和直播大班课,您可以在开课的逻辑中自定义相关 UI 显示、菜单、行为等,并配置课堂最大容纳人数。
示例代码:
objective-c
NEEduRoom *room = [[NEEduRoom alloc] init]; room.roomName = [NSString stringWithFormat:@"%@的课堂",self.nicknameView.text]; room.sceneType = self.lessonType; switch (room.sceneType) { case NEEduSceneType1V1: { room.configId = 5; } break; case NEEduSceneTypeSmall: { room.configId = 6; } break; case NEEduSceneTypeBig: { room.configId = 7; } break; default: break; } room.roomUuid = [NSString stringWithFormat:@"%@%d",self.lessonIdView.text,room.configId]; __weak typeof(self)weakSelf = self; [[EduManager shared].roomService createRoom:room completion:^(NEEduCreateRoomRequest *result,NSError * _Nonnull error) { // 创建结果处理 }];
步骤四 学生端进入课堂
学生端通过 enterClassroom
加入已创建的课堂。
objective-cNEEduEnterRoomParam *room = [[NEEduEnterRoomParam alloc] init];
room.autoPublish = YES;
room.autoSubscribeVideo = YES;
room.autoSubscribeAudio = YES;
room.roomUuid = resRoom.roomUuid;
room.roomName = resRoom.roomName;
room.sceneType = self.lessonType;
room.role = NEEduRoleTypeStudent;
room.userName = @"";
__weak typeof(self)weakSelf = self;
[[EduManager shared] enterClassroom:room success:^(NEEduRoomProfile * _Nonnull roomProfile) {
// 进入成功处理
} failure:^(NSError * _Nonnull error) {
// 进入失败处理
}
步骤五 课堂互动
教学双方加入教室后,开启课程,可以进行课堂互动。智慧云课堂提供了丰富的课堂功能,如:互动白板、屏幕共享、举手、视频、语音等。
-
开始上课。
教师端开始上课。
示例代码:
objective-c
[[NEEduManager shared].roomService startLesson:state completion:^(NSError * _Nonnull error, NEEduPropertyItem * _Nonnull item) { button.userInteractionEnabled = YES; if (error) { [weakSelf.view makeToast:error.localizedDescription]; }else { [weakSelf.maskView selectButton:!selected]; } }];
-
学生管理。
-
教师端调用
remoteUserVideoEnable:userID:result:
、remoteUserAudioEnable:userID:result:
方法控制学生端的摄像头和麦克风。如果需要和学生互动,可以开启对方的麦克风。示例代码:
objective-c
[[NEEduManager shared].userService remoteUserAudioEnable:!member.streams.audio.value userID:member.userUuid result:^(NSError * _Nonnull error) { if (error) { [weakSelf.view makeToast:error.description]; }else { [weakSelf.view makeToast:@"操作成功"]; } }];
-
教师端调用
whiteboardDrawable:userID:result:
、screenShareAuthorization:userID:result:
方法授权学生使用白板或屏幕共享。示例代码:
objective-c
__weak typeof(self)weakSelf = self; [[NEEduManager shared].userService localShareScreenEnable:NO result:^(NSError * _Nonnull error) { if (error) { [self.view makeToast:error.localizedDescription]; }else { weakSelf.isSharing = NO; weakSelf.shareScreenMask.hidden = YES; [weakSelf updateShareItemWithSelected:NO]; } }];
-
-
屏幕共享。
教师端或学生端调用
localShareScreenEnable:result:
发起屏幕共享,共享本端屏幕给其他人观看。示例代码:
objective-c
//这里只是同步共享消息,前置条件:发起 NERtcEngine 屏幕共享,详细参考项目源码。 __weak typeof(self)weakSelf = self; [[NEEduManager shared].userService localShareScreenEnable:NO result:^(NSError * _Nonnull error) { if (error) { [self.view makeToast:error.localizedDescription]; }else { weakSelf.isSharing = NO; weakSelf.shareScreenMask.hidden = YES; [weakSelf updateShareItemWithSelected:NO]; } }];
步骤六 举手上台
在互动大班课场景中,学生需要通过上台才能进行互动白板、屏幕共享和音视频互动。
-
学生举手申请上台。
示例代码:
__weak typeof(self) weakSelf = self; [[NEEduManager shared].userService handsupStateChange:NEEduHandsupStateApply userID:[NEEduManager shared].localUser.userUuid result:^(NSError * _Nonnull error) { if (!error) { weakSelf.handsupState = NEEduHandsupStateApply; weakSelf.handsupItem.selectTitle = @"举手中"; weakSelf.handsupItem.isSelected = YES; }else { [weakSelf.view makeToast:error.localizedDescription]; } }];
-
学生取消举手上台申请。
示例代码:
if (self.handsupState == NEEduHandsupStateApply) { __weak typeof(self)weakSelf = self; [[NEEduManager shared].userService handsupStateChange:NEEduHandsupStateStuCancel userID:[NEEduManager shared].localUser.userUuid result:^(NSError * _Nonnull error) { if (!error) { weakSelf.handsupState = NEEduHandsupStateIdle; weakSelf.handsupItem.isSelected = NO; } }]; }
-
老师同意举手上台申请。
示例代码:
[[NEEduManager shared].userService handsupStateChange:NEEduHandsupStateTeaAccept userID:member.userUuid result:^(NSError * _Nonnull error) { if (!error) { [self.view makeToast:@"操作成功"]; member.properties.avHandsUp.value = NEEduHandsupStateTeaAccept; [self.applyStudents removeObject:member]; [self.tableView reloadData]; if (self.delegate && [self.delegate respondsToSelector:@selector(didAgreeWithMember:)]) { [self.delegate didAgreeWithMember:member]; } }else { [self.view makeToast:error.localizedDescription]; } }];
-
老师拒绝举手上台申请。
示例代码:
[[NEEduManager shared].userService handsupStateChange:NEEduHandsupStateTeaReject userID:member.userUuid result:^(NSError * _Nonnull error) { if (!error) { member.properties.avHandsUp.value = NEEduHandsupStateTeaReject; [self.applyStudents removeObject:member]; [self.tableView reloadData]; if (self.delegate && [self.delegate respondsToSelector:@selector(didDisAgreeWithMember:)]) { [self.delegate didDisAgreeWithMember:member]; } }else { [self.view makeToast:error.localizedDescription]; } }];
-
老师让学生下台。
示例代码:
[[NEEduManager shared].userService handsupStateChange:NEEduHandsupStateTeaOffStage userID:member.userID result:^(NSError * _Nonnull error) { if (!error) { [weakSelf.view makeToast:@"操作成功"]; }else { [weakSelf.view makeToast:error.localizedDescription]; } }];
-
监听学生上台状态变化。
示例代码:
- (void)onHandsupStateChange:(NEEduHandsupState)state user:(NEEduHttpUser *)user { self.handsupState = state; switch (state) { case NEEduHandsupStateIdle: //学生关闭 [self handleHandsupClose:user]; break; case NEEduHandsupStateApply: //学生申请 [self handleHandsupAppy:user]; break; case NEEduHandsupStateTeaAccept: [self handleHandsupAccept:user]; break; case NEEduHandsupStateStuCancel: //学生取消 [self handleHandsupCancel:user]; break; case NEEduHandsupStateTeaOffStage: [self handleHandsupTeacherOffStage:user]; break; default: break; } }
步骤七 录制回放
在线教育场景中,可实现上课过程中老师和学生的音视频、白板、屏幕共享录制,还原真实上课场景,供学生和教师回放复习。目前所有课堂模式都支持录制回放。录制功能包含在云端录制服务中,用户端只需要接入Recordplay回放组件,回放功能依赖的代码包含在组件里面。
教师开始上课时,服务器会自动开始录制。课程结束时,需要等待服务端 20 分钟左右进行转码。转码完成后,用户可以通过 Recordplay 回放组件观看录制内容。在示例项目中,展示了如何快速接入Recordplay 组件。
接入步骤如下:
-
Recordplay 回放组件包含 NERecordPlay、NERecordPlayUI 2 个依赖模块,podfile 中添加依赖到自己的项目中:
pod
pod 'NERecordPlay', :path => 'Modules/NERecordPlay/NERecordPlay.podspec' pod 'NERecordPlayUI', :path => 'Modules/NERecordPlayUI/NERecordPlayUI.podspec'
-
课程结束后,回放观看端调
getRecordListWithRoomUuid:rtcCid:success:failure:
获取上一次的回放记录,并创建回放播放器实例。示例代码:
objective-c
- (void)recordPlayEvent:(UIButton *)button { NSString *lastRoomUuid = [[NSUserDefaults standardUserDefaults] objectForKey:kLastRoomUuid]; NSString *lastRtcCid = [[NSUserDefaults standardUserDefaults] objectForKey:kLastRtcCid]; NSString *lastUserUuid = [[NSUserDefaults standardUserDefaults] objectForKey:kLastUserUuid]; NSString *lastUserToken = [[NSUserDefaults standardUserDefaults] objectForKey:kLastUserToken]; NERecordRequest *request = [[NERecordRequest alloc] initWithAppKey:[KeyCenter appKey] authorization:[KeyCenter authorization] baseUrl:[KeyCenter baseURL] userUuid:lastUserUuid token:lastUserToken]; [request getRecordListWithRoomUuid:lastRoomUuid rtcCid:lastRtcCid success:^(id _Nonnull data) { if (!data) { [self.view makeToast:@"课程结束后,需进行文件转码,预计20分钟后可观看回放"]; return; } NERecordViewController *vc = [[NERecordViewController alloc] init]; vc.modalPresentationStyle = UIModalPresentationOverFullScreen; vc.recordData = data; [self presentViewController:vc animated:YES completion:nil]; } failure:^(NSError * _Nonnull error) { [self.view makeToast:error.localizedDescription]; }]; }
-
如果成功,那么跳转到 Recordplay 模块中的 NERecordViewController 回放界面。
示例代码:
objective-c
NERecordViewController *vc = [[NERecordViewController alloc] init]; vc.modalPresentationStyle = UIModalPresentationOverFullScreen; vc.recordData = data; [self presentViewController:vc animated:YES completion:nil];
-
初始化播放器。
示例代码:
objective-c
func createPlayer() { guard let data = recordData else { print("error:recordData = nil") return } player = NEEduRecorderPlayerManager(data: data, view: contentView) player?.delegate = self recordList = player?.playingRecordItems player?.prepareToPlay() } func addNetListen() { NetworkReachabilityManager.default?.startListening(onUpdatePerforming: { state in print("state:\(state)") if state == .notReachable { self.navView.netState.image = UIImage.ne_imageName(name: "net_0") }else { self.navView.netState.image = UIImage.ne_imageName(name: "net_3") } }) }
-
开始播放。
示例代码:
objective-c
@objc func playButtonEvent(button: UIButton) { print(#function,button) button.isSelected = !button.isSelected button.isSelected ? player?.play() : player?.pause() }
-
暂停播放。
示例代码:
objective-c
@objc func playButtonEvent(button: UIButton) { print(#function,button) button.isSelected = !button.isSelected button.isSelected ? player?.play() : player?.pause() }
-
拖动进度。
示例代码:
objective-c
@objc func sliderEvent(slider: UISlider, event: UIEvent) { guard let touch: UITouch = event.allTouches?.first else { return } switch touch.phase { case .began,.moved: seeking = true case .ended,.cancelled: seeking = false player?.seekTo(time: Double(slider.value) * player!.duration) default: return } }
步骤八 结束课堂
调用 player?.stop()
结束课程。
示例代码:
objective-c@objc func backButtonEvent(button: UIButton) {
player?.stop()
self.dismiss(animated: true, completion: nil)
}