实现语聊房(基于NERoom)
更新时间: 2024/11/26 15:44:05
本文档为您展示通过 NERoom房间组件实现语聊房场景的相关步骤,帮助您在业务中实现麦位管理、低延时语音互动、文字聊天等能力。
开发环境要求
开发环境要求如下:
环境要求 | 说明 |
---|---|
iOS 版本 | 11.0 及以上的 iPhone 或者 iPad 真机 |
CPU 架构 | ARM64、ARMV7 |
IDE | XCode |
其他 | 安装 CocoaPods |
前提条件
方案架构
方案架构说明如下:
- 主播、连麦者和观众加入同一个音视频房间。
- 主播和连麦者在音视频房间内进行音频流的实时发布和订阅。
- 观众在音视频房间内仅订阅音频流,不发布音频流。
API调用时序图
sequenceDiagram
participant hostClientA as 主播A client
participant NERoomA as NERoom
participant app_server as VoiceRoom_Server
participant NERoomB as NERoom
participant audienceB as 观众B client
note over hostClientA, audienceB: 主播开播
hostClientA ->> NERoomA: createRoom
NERoomA ->> app_server: createRoom
app_server -->> hostClientA: 房间信息
hostClientA ->> NERoomA: joinRoom
NERoomA ->> app_server: joinRoom
app_server -->> hostClientA: 加入成功
hostClientA ->> NERoomA: unmuteMyAudio
NERoomA ->> app_server: unmuteMyAudio
note over hostClientA, audienceB: 观众进入房间
audienceB ->> NERoomB: joinRoom
NERoomB ->> app_server: joinRoom
app_server -->> audienceB: 房间信息
audienceB ->> NERoomB: unmuteMyAudio
NERoomB ->> app_server: unmuteMyAudio
sequenceDiagram
participant hostClientA as 主播A client
participant NERoomA as NERoom
participant app_server as VoiceRoom_Server
participant NERoomB as NERoom
participant audienceB as 观众B client
note over hostClientA, audienceB: 抱观众上麦
hostClientA ->> NERoomA: sendSeatInvitation <br>(邀请上麦)
NERoomA ->> app_server: sendSeatInvitation <br>(邀请上麦)
app_server -->> NERoomB: onSeatInvitationAccepted <br>(自动同意上麦的回调)
NERoomB -->> audienceB: onSeatInvitationAccepted <br>(自动同意上麦的回调)
app_server -->> NERoomB: onSeatListChanged <br>(麦位变更的回调)
NERoomB -->> audienceB: onSeatListChanged <br>(麦位变更的回调)
app_server -->> NERoomA: onSeatInvitationAccepted <br>(自动同意上麦的回调)
NERoomA -->> hostClientA: onSeatInvitationAccepted <br>(自动同意上麦的回调)
app_server -->> NERoomA: onSeatListChanged <br> (麦位变更的回调)
NERoomA -->> hostClientA: onSeatListChanged <br>(麦位变更的回调)
audienceB ->> audienceB: 刷新UI
hostClientA ->> hostClientA: 刷新UI
note over hostClientA, audienceB: 将观众移出麦位
hostClientA ->> NERoomA: kickSeat <br>(主播踢观众B下麦)
NERoomA ->> app_server: kickSeat <br>(主播踢观众B下麦)
app_server -->> NERoomB: onSeatKicked <br>(主播踢观众B下麦的回调)
NERoomB -->> audienceB: onSeatKicked <br>(主播踢观众B下麦的回调)
app_server -->> NERoomB: onSeatListChanged <br>(麦位变更的回调)
NERoomB -->> audienceB: onSeatListChanged <br>(麦位变更的回调)
app_server -->> NERoomA: onSeatKicked <br>(主播踢观众B下麦的回调)
NERoomA -->> hostClientA: onSeatKicked <br>(主播踢观众B下麦的回调)
app_server -->> NERoomA: onSeatListChanged <br>(麦位变更的回调)
NERoomA -->> hostClientA: onSeatListChanged <br>(麦位变更的回调)
audienceB ->> audienceB: 刷新UI
hostClientA ->> hostClientA: 刷新UI
sequenceDiagram
participant hostClientA as 主播A client
participant NERoomA as NERoom
participant app_server as VoiceRoom_Server
participant NERoomB as NERoom
participant audienceB as 观众B client
note over hostClientA, audienceB: 观众申请上麦
audienceB ->> NERoomB: submitSeatRequest <br>(申请上麦)
NERoomB ->> app_server: submitSeatRequest <br>(申请上麦)
app_server ->> NERoomB: onSeatListChanged <br>(麦位变更的回调)
NERoomB ->> audienceB: onSeatListChanged <br>(麦位变更的回调)
app_server ->> NERoomA: onSeatRequestSubmitted <br>(观众B申请上麦的回调)
NERoomA ->> hostClientA: onSeatRequestSubmitted <br>(观众B申请上麦的回调)
app_server ->> NERoomA: onSeatListChanged <br>(麦位变更的回调)
NERoomA ->> hostClientA: onSeatListChanged <br>(麦位变更的回调)
audienceB ->> audienceB: 刷新UI
hostClientA ->> hostClientA: 刷新UI
alt 主播同意上麦申请
hostClientA ->> NERoomA: approveSeatRequest <br>(主播同意上麦申请)
NERoomA ->> app_server: approveSeatRequest <br>(主播同意上麦申请)
app_server ->> NERoomA: onSeatRequestApproved <br>(主播同意上麦申请的回调)
NERoomA ->> hostClientA: onSeatRequestApproved <br>(主播同意上麦申请的回调)
app_server ->> NERoomA: onSeatListChanged <br>(麦位变更的回调)
NERoomA ->> hostClientA: onSeatListChanged <br>(麦位变更的回调)
app_server ->> NERoomB: onSeatRequestApproved <br>(主播同意上麦申请的回调)
NERoomB ->> audienceB: onSeatRequestApproved <br> (主播同意上麦申请的回调)
app_server ->> NERoomB: onSeatListChanged <br>(麦位变更的回调)
NERoomB ->> audienceB: onSeatListChanged <br> (麦位变更的回调)
audienceB ->> audienceB: 刷新UI
hostClientA ->> hostClientA: 刷新UI
else 主播拒绝上麦申请
hostClientA ->> NERoomA: rejectRequestSeat <br>(主播不同意上麦申请)
NERoomA ->> app_server: rejectRequestSeat <br>(主播不同意上麦申请)
app_server -->> NERoomA: onSeatRequestRejected <br>(主播拒绝观众B上麦的回调)
NERoomA -->> hostClientA: onSeatRequestRejected <br>(主播拒绝观众B上麦的回调)
app_server -->> NERoomA: onSeatListChanged <br>(麦位变更的回调)
NERoomA -->> hostClientA: onSeatListChanged <br>(麦位变更的回调)
app_server -->> NERoomB: onSeatRequestRejected <br>(主播拒绝观众B上麦的回调)
NERoomB -->> audienceB: onSeatRequestRejected <br>(主播拒绝观众B上麦的回调)
app_server -->> NERoomB: onSeatListChanged <br>(麦位变更的回调)
NERoomB -->> audienceB: onSeatListChanged <br>(麦位变更的回调)
audienceB ->> audienceB: 刷新UI
hostClientA ->> hostClientA: 刷新UI
end
sequenceDiagram
participant hostClientA as 主播A client
participant NERoomA as NERoom
participant app_server as VoiceRoom_Server
participant NERoomB as NERoom
participant audienceB as 观众B client
note over hostClientA, audienceB: 观众取消申请上麦
audienceB ->> NERoomB: cancelSeatRequest<br> (取消申请上麦)
NERoomB ->> app_server: cancelSeatRequest <br> (取消申请上麦)
app_server -->> NERoomA: onSeatRequestCancelled <br>(观众B取消上麦的回调)
NERoomA -->> hostClientA: onSeatRequestCancelled <br>(观众B取消上麦的回调)
app_server -->> NERoomA: onSeatListChanged <br>(麦位变更的回调)
NERoomA -->> hostClientA: onSeatListChanged <br>(麦位变更的回调)
app_server -->> NERoomB: onSeatRequestCancelled <br>(观众B取消上麦的回调)
NERoomB -->> audienceB: onSeatRequestCancelled <br> (观众B取消上麦的回调)
app_server -->> NERoomB: onSeatListChanged <br>(麦位变更的回调)
NERoomB -->> audienceB: onSeatListChanged <br>(麦位变更的回调)
audienceB ->> audienceB: 刷新UI
hostClientA ->> hostClientA: 刷新UI
note over hostClientA, audienceB: 观众下麦
audienceB ->> NERoomB: leaveSeat (下麦)
NERoomB ->> app_server: leaveSeat (下麦)
app_server -->> NERoomA: onSeatLeave <br>(观众B成功下麦的回调)
NERoomA -->> hostClientA: onSeatLeave <br>(观众B成功下麦的回调)
app_server -->> NERoomA: onSeatListChanged <br>(麦位变更的回调)
NERoomA -->> hostClientA: onSeatListChanged <br>(麦位变更的回调)
app_server -->> NERoomB: onSeatLeave <br>(观众B成功下麦的回调)
NERoomB -->> audienceB: onSeatLeave <br>(观众B成功下麦的回调)
app_server -->> NERoomB: onSeatListChanged <br>(麦位变更的回调)
NERoomB -->> audienceB: onSeatListChanged <br>(麦位变更的回调)
audienceB ->> audienceB: 刷新UI
hostClientA ->> hostClientA: 刷新UI
登录鉴权
调用 NEAuthService
实例中的login
接口进行账号登录。
let account:String = "your account"
let token:String = "your token"
NERoomKit.shared().authService.login(account: account,
token: token) { code, str, _ in
if code == 0 {
// 登陆成功
} else {
// 登陆失败
}
}
示例代码中的your account
和 your token
是您的应用服务器向 NERoom 服务器申请创建账号时,返回的 userUuid
和 userToken
的值,具体请参见创建账号。
主播开播
-
主播调用
createRoom
接口创建房间。创建房间麦位
NESeatInitParams
参数说明:参数 类型 描述 seatCount int 麦位数量 seatRequestApprovalMode int 从 NESeatRequestApprovalMode
内获取麦位申请审批模式。OFF
:关闭审批模式,即成员提交申请后不需要管理员同意,可直接上麦。ON
:开启审批模式,即成员提交申请后需要管理员同意才能上麦。
seatInvitationConfirmMode int 麦位邀请确认模式。 OFF
: 关闭确认模式,即管理员邀请其他成员上麦,不需要被邀请的成员确认,可直接上麦。ON
:开启确认模式,即管理员邀请其他成员上麦,需要被邀请的成员确认,接受后方可上麦。
示例代码如下:
NECreateRoomParams *param = [[NECreateRoomParams alloc] init]; param.roomUuid = uuid; param.roomName = name; param.templateId = config.integerValue; NESeatInitParams *seatParams = [[NESeatInitParams alloc] init]; seatParams.seatCount = 8; seatParams.seatRequestApprovalMode = NESeatRequestApprovalModeOn; param.seatInitParams = seatParams; NECreateRoomOptions *options = [[NECreateRoomOptions alloc] init]; options.enableRtc = true; options.enableChatroom = true; options.enableWhiteboard = true; [[[NERoomKit shared] roomService] createRoomWithParams:param options:options callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable obj) { }];
-
主播调用
joinRoom
接口加入房间。示例代码如下:
NEJoinRoomParams *param = [NEJoinRoomParams new]; param.roomUuid = roomUuid; param.userName = userName; param.role = role; NEJoinRoomOptions *options = [[NEJoinRoomOptions alloc] init]; [[[NERoomKit shared] roomService] joinRoomWithParams:param options:options callback:^(NSInteger code, NSString *_Nullable string, NERoomContext *_Nullable context) { }];
-
调用
joinRtcChannel
接口加入音视频房间。示例代码如下:
[self.context.rtcController joinRtcChannel:^(NSInteger code, NSString *_Nullable msg, id _Nullable obj) { }];
-
调用
addRoomListener
接口监听房间里的事件。详细的事件列表请参见
监听房间事件
。示例代码如下:
[self.context addRoomListenerWithListener:self];
-
收到加入成功的
onMemberJoinRoom
回调后,调用unmuteMyAudio
方法开启本地音频采集。示例代码如下:
[self.context.rtcController unmuteMyAudio:^(NSInteger code, NSString *_Nullable msg, id _Nullable obj) { }];
观众进入房间
-
观众调用
joinRoom
接口加入房间。 -
观众调用
addRoomListener
接口监听房间里的事件。 -
观众调用
muteMyAudio
方法关闭本地音频采集。等到上麦后,再开启本地音频采集。
麦位管理
添加麦位事件监听
调用 addSeatListener
接口,监听麦位相关的事件。
详细的事件列表请参见NESeatEventListener
。
示例代码如下:
[self.context.seatController addSeatListener:self];
主播邀请观众上麦
-
主播调用
sendSeatInvitation
接口邀请观众上麦。[self.context.seatController sendSeatInvitation:userId callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable objc) { }];
-
主播也可以在邀请观众上麦时指定麦位。
[self.context.seatController sendSeatInvitation:self.index user:self.userId callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable obj) { }];
观众申请上麦
观众调用 submitSeatRequest
接口申请上麦。
-
观众不指定麦位
[self.context.seatController submitSeatRequest:^(NSInteger code, NSString *_Nullable msg, id _Nullable objc) { }];
-
观众指定麦位
[self.context.seatController submitSeatRequest:index callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable obj) { }];
主播同意观众上麦
主播调用 approveSeatRequest
接口同意观众上麦。
[self.context.seatController
approveSeatRequest:userId
callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable objc) {
}];
主播拒绝观众上麦
主播调用 rejectSeatRequest
接口拒绝观众上麦。
[self.context.seatController
rejectSeatRequest:self.userId
callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable objc) {
}];
主播将观众移出麦位
主播调用 kickSeat
接口将观众移出麦位。
[self.context.seatController
kickSeat:userId
callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable objc) {
}];
观众取消申请上麦
观众调用 cancelSeatRequest
接口取消申请上麦。
[self.context.seatController
cancelSeatRequest:^(NSInteger code, NSString *_Nullable msg, id _Nullable objc) {
}];
观众主动下麦
观众调用 leaveSeat
接口下麦。
[self.context.seatController leaveSeat:^(NSInteger code, NSString *_Nullable msg,
id _Nullable objc) {
}];
离开房间
-
调用
leaveRoom
接口离开房间。示例代码如下:
[self.context leaveRoomWithCallback:^(NSInteger code, NSString *_Nullable msg, id _Nullable obj) { }];
-
离开房间成功后,会触发
NERoomListener.onMemberLeaveRoom
回调,通知房间内所有成员。 -
主播调用
endRoom
接口删除房间。示例代码如下:
[self.context endRoomWithIsForce:true callback:^(NSInteger code, NSString *_Nullable msg, id _Nullable obj) { }];
-
删除房间成功后,会触发
NERoomListener
协议中的onRoomEnded
回调方法。
进阶功能
伴音
-
调用
RtcController
的startAudioMixing
方法播放伴音。NERoomCreateAudioMixingOption
参数说明如下表所示。参数 类型 描述 path String 待播放的音乐文件的绝对路径或 URL 地址,支持本地 SD 卡中的绝对路径或 URL 地址 loopCount int 伴音循环播放的次数。1:(默认)播放一次。≤ 0:无限循环播放 sendEnabled boolean 是否将伴音发送远端,默认为 true,即远端用户可以听到该伴音。 sendVolume int 音乐文件的发送音量,取值范围为 0~200。默认为 100,表示使用文件的原始音量。 playbackEnabled boolean 是否本地播放伴音。默认为 true,即本地用户可以听到该伴音。 playbackVolume int 音乐文件的播放音量,取值范围为 0~200。默认为 100,表示使用文件的原始音量。 startTimeStamp long 音乐文件开始播放的时间,UTC 时间戳,即从1970 年 1 月 1 日 0 点 0 分 0 秒开始到事件发生时的毫秒数。默认值为 0,表示立即播放。 sendWithAudioType NERoomAudioStreamType 伴音跟随音频主流还是辅流,默认跟随主流。 - NERoomAudioStreamTypeMain:伴音跟随主流
- NERoomAudioStreamTypeSub:伴音跟随辅流
-
设置伴音的音量。
- 通过
setAudioMixingSendVolume
设置伴音的发送音量 - 通过
setAudioMixingPlaybackVolume
设置伴音的播放音量。
- 通过
-
在离开房间前调用
stopAudioMixing
结束伴音。
示例代码如下:
// 开始伴音
NERoomCreateAudioMixingOption *option = [[NERoomCreateAudioMixingOption alloc] init];
option.path = @"";
option.loopCount = -1;
option.sendEnabled = true;
option.sendVolume = 100;
option.playbackEnabled = true;
option.playbackVolume = 100;
option.startTimeStamp = 0;
option.sendWithAudioType = NERoomAudioStreamTypeMain;
[self.context.rtcController startAudioMixingWithOption:option];
// 设置伴音音量
[self.context.rtcController setAudioMixingSendVolumeWithVolume:90];
[self.context.rtcController setAudioMixingPlaybackVolumeWithVolume:90];
// 结束伴音
[self.context.rtcController stopAudioMixing];
音效
-
调用
RtcController
的playEffect
方法播放音效。NERoomCreateAudioEffectOption
参数说明如下表所示。参数 类型 描述 path String 待播放的音乐文件的绝对路径或 URL 地址,支持本地 SD 卡中的绝对路径或 URL 地址 loopCount int 音效循环播放的次数。1:(默认)播放一次。≤ 0:无限循环播放 sendEnabled boolean 是否将音效发送远端,默认为 true,即远端用户可以听到该音效。 sendVolume int 音效文件的发送音量,取值范围为 0~200。默认为 100,表示使用文件的原始音量。 playbackEnabled boolean 是否本地播放音效。默认为 true,即本地用户可以听到该音效。 playbackVolume int 音乐文件的播放音量,取值范围为 0~200。默认为 100,表示使用文件的原始音量。 startTimeStamp long 音乐文件开始播放的时间,UTC 时间戳,即从1970 年 1 月 1 日 0 点 0 分 0 秒开始到事件发生时的毫秒数。默认值为 0,表示立即播放。 sendWithAudioType NERoomAudioStreamType 伴音跟随音频主流还是辅流,默认跟随主流。 - NERoomAudioStreamTypeMain:伴音跟随主
- NERoomAudioStreamTypeSub:伴音跟随辅流
-
设置音效的音量。
-
调用
setEffectSendVolume
方法设置音效文件发送音量。 -
调用
setEffectPlaybackVolume
setEffectPlaybackVolume
方法设置音效文件播放音量。
-
-
在离开房间前,调用
stopAllEffects
方法停止播放所有音效文件。
示例代码如下:
int effectId = 1;
// 开始音效
NERoomCreateAudioEffectOption *option = [[NERoomCreateAudioEffectOption alloc] init];
option.path = @"";
option.loopCount = -1;
option.sendEnabled = true;
option.sendVolume = 100;
option.playbackEnabled = true;
option.playbackVolume = 100;
option.startTimeStamp = 0;
option.sendWithAudioType = NERoomAudioStreamTypeMain;
[self.context.rtcController playEffectWithEffectId:effectId option:option];
// 设置音效音量
[self.context.rtcController setEffectPlaybackVolumeWithEffectId:effectId volume:90];
[self.context.rtcController setEffectSendVolumeWithEffectId:effectId volume:90];
// 停止指定音效
[self.context.rtcController stopEffectWithEffectId:effectId];
// 停止所有音效
[self.context.rtcController stopAllEffects];
耳返
- 通过
onRtcAudioOutputDeviceChanged
监听播放设备的变化,当监听到播放设备切换为耳机时才开启耳返。- (void)onRtcAudioOutputDeviceChangedWithDevice:(enum NEAudioOutputDevice)device { switch (device) { case NEAudioOutputDeviceSpeakerPhone: // 扬声器 break; case NEAudioOutputDeviceWiredHeadset: // 有线耳机,可开启耳返 break; case NEAudioOutputDeviceEarpiece: // 听筒 break; case NEAudioOutputDeviceBluetoothHeadset: // 无线耳机,可开启耳返 break; default: break; } }
- 调用
enableEarBack
方法开启耳返。[self.context.rtcController enableEarbackWithVolume:100];
- 调用
disableEarBack
方法关闭耳返。[self.context.rtcController disableEarback];