为方便用户同时加入多个频道,接收多个频道的音视频流,声网 Unity SDK 自 v3.0.1 起新增支持多频道管理,且频道数量无限制。
该功能可应用于类似超级小班课的场景:将一个互动大班里的学生分到不同的小班,学生可以在小班内进行实时音视频互动。根据场景需要,你还可以给每个小班可以配备一名助教老师。
我们在 GitHub 提供一个实现了多频道功能的开源示例项目 AgoraMultiChannel,你可以前往下载体验,参考源代码。
Unity SDK 通过一个 AgoraChannel
类实现多频道控制。
你可以多次调用 CreateChannel
,通过不同的 channelId
创建多个 AgoraChannel
对象(对应多个频道),然后分别调用 AgoraChannel
中的 JoinChannel
方法加入对应的频道。
实现多频道功能的主要步骤如下:
调用 GetEngine
方法,初始化 IRtcEngine
。
调用 SetChannelProfile
方法,将频道场景设置为直播。
调用 IRtcEngine
类的 CreateChannel
方法,通过 channelId
创建一个 AgoraChannel
对象。
调用 AgoraChannel
类的 SetClientRole
设置用户角色。
调用 AgoraChannel
类中的 JoinChannel
方法加入频道。用户加入频道后,默认发布本地流并自动订阅频道内所有其他用户的流。你可以在加入频道时通过 ChannelMediaOptions
设置发布和订阅状态,也可以在加入频道后通过 AgoraChannel
类的 MuteLocal
和 MuteRemote
为前缀的方法修改发布和订阅状态。
如果需要加入更多的频道,重复步骤 3、4、5。
加入多个频道的 API 时序如下:
下面的示例代码演示了如何通过 AgoraChannel
类加入多频道,并在加入的第一个频道中发布本地流。
void InitEngine()
{
// 1. 初始化 IRtcEngine 实例。
mRtcEngine = IRtcEngine.GetEngine(APP_ID);
// 2. 启用视频模块。
mRtcEngine.EnableVideo();
// 如需接收和渲染多频道的视频,你需要在加入频道前调用 SetMultiChannelWant 为当前引擎开启多频道状态。
mRtcEngine.SetMultiChannelWant(true);
// 3. 将频道场景设置为直播。
mRtcEngine.SetChannelProfile(CHANNEL_PROFILE.CHANNEL_PROFILE_LIVE_BROADCASTING);
// 4. 创建并获取 AgoraChannel1 对象。
channel1 = mRtcEngine.CreateChannel(CHANNEL_NAME_1);
// 5. 监听加入、离开 AgoraChannel1 频道回调。
channel1.ChannelOnJoinChannelSuccess = Channel1OnJoinChannelSuccessHandler;
channel1.ChannelOnLeaveChannel = Channel1OnLeaveChannelHandler;
// 6. 设置用户角色为主播。
channel1.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
// 7. 加入 AgoraChannel1 对象的频道,SDK 会默认发布本地流并自动订阅所有远端流。
channel1.JoinChannel(TOKEN_1, "", 0, new ChannelMediaOptions(true, true));
// 8. 创建并获取 AgoraChannel2 对象。
channel2 = mRtcEngine.CreateChannel(CHANNEL_NAME_2);
// 9. 监听加入、离开 AgoraChannel2 频道回调。
channel2.ChannelOnJoinChannelSuccess = Channel2OnJoinChannelSuccessHandler;
channel2.ChannelOnLeaveChannel = Channel2OnLeaveChannelHandler;
// 10. 设置用户角色为观众,无发流权限。
channel2.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_AUDIENCE);
// 11. 加入 AgoraChannel2 对象的频道。你需要设置 publishLocalAudio 和 publishLocalVideo 为 false,否则加入频道会失败。
channel2.JoinChannel(TOKEN_2, "", 0, new ChannelMediaOptions(true, true, false, false));
}
void OnApplicationQuit()
{
Debug.Log("OnApplicationQuit");
if (mRtcEngine != null)
{
// 12. 离开并销毁 AgoraChannel2 对象的频道。
channel2.LeaveChannel()
channel2.ReleaseChannel();
// 13. 离开并销毁 AgoraChannel1 对象的频道。
channel1.LeaveChannel();
channel1.ReleaseChannel();
mRtcEngine.DisableVideoObserver();
// 14. 销毁 IRtcEngine 对象。
IRtcEngine.Destroy();
}
}
// 成功加入 AgoraChannel1 对象的频道回调。
void Channel1OnJoinChannelSuccessHandler(string channelId, uint uid, int elapsed)
{
logger.UpdateLog(string.Format("sdk version: ${0}", IRtcEngine.GetSdkVersion()));
logger.UpdateLog(string.Format("onJoinChannelSuccess channelId: {0}, uid: {1}, elapsed: {2}", channelId, uid, elapsed));
makeVideoView(channelId ,0);
}
// 成功加入 AgoraChannel2 对象的频道回调。
void Channel2OnJoinChannelSuccessHandler(string channelId, uint uid, int elapsed)
{
logger.UpdateLog(string.Format("sdk version: ${0}", IRtcEngine.GetSdkVersion()));
logger.UpdateLog(string.Format("onJoinChannelSuccess channelId: {0}, uid: {1}, elapsed: {2}", channelId, uid, elapsed));
makeVideoView(channelId, 0);
}
// 成功离开AgoraChannel1 对象的频道回调。
void Channel1OnLeaveChannelHandler(string channelId, RtcStats rtcStats)
{
logger.UpdateLog(string.Format("Channel1OnLeaveChannelHandler channelId: {0}", channelId));
}
// 成功离开AgoraChannel2 对象的频道回调。
void Channel2OnLeaveChannelHandler(string channelId, RtcStats rtcStats)
{
logger.UpdateLog(string.Format("Channel1OnLeaveChannelHandler channelId: {0}", channelId));
}
// 创建 GameObject 并与 VideoSurface 绑定。
VideoSurface videoSurface = makeImageSurface(objName);
if (!ReferenceEquals(videoSurface, null))
{
// 渲染本地或远端试图。
videoSurface.SetForMultiChannelUser(channelId, uid);
videoSurface.SetEnable(true);
videoSurface.SetVideoSurfaceType(AgoraVideoSurfaceType.RawImage);
videoSurface.SetGameFps(30);
}
public VideoSurface makeImageSurface(string goName)
{
GameObject go = new GameObject();
if (go == null)
{
return null;
}
go.name = goName;
go.AddComponent<UIElementDrag>();
go.AddComponent<RawImage>();
GameObject canvas = GameObject.Find("VideoCanvas");
if (canvas != null)
{
go.transform.parent = canvas.transform;
Debug.Log("add video view");
}
else
{
Debug.Log("Canvas is null video view");
}
go.transform.Rotate(0f, 0.0f, 180.0f);
float xPos = Random.Range(Offset - Screen.width / 2f, Screen.width / 2f - Offset);
float yPos = Random.Range(Offset, Screen.height / 2f - Offset);
Debug.Log("position x " + xPos + " y: " + yPos);
go.transform.localPosition = new Vector3(xPos, yPos, 0f);
go.transform.localScale = new Vector3(3f, 4f, 1f);
VideoSurface videoSurface = go.AddComponent<VideoSurface>();
return videoSurface;
}
AgoraChannel
中的 JoinChannel
方法提供媒体订阅选项设置(autoSubscribeAudio
和 autoSubscribeVideo
),可以控制在加入频道后是否自动订阅音频流和视频流,默认为自动订阅。在加入频道后,你也可以通过 MuteRemoteAudioStream
或 MuteRemoteAudioStream
方法修改订阅状态。
如果你需要在加入 AgoraChannel
的频道时仅订阅指定用户的音频流或视频流,声网建议使用以下方法:
JoinChannel
并在 ChannelMediaOptions
中设置 autoSubscribeAudio = false
或 autoSubscribeVideo = false
不订阅所有远端用户。MuteRemoteAudioStream(uid,false)
或 MuteRemoteVideoStream(uid,false)
订阅指定的远端用户。在视频场景中,如需接收多频道的视频流,你需要在加入频道前调用 SetMultiChannelWant
为当前引擎开启多频道状态。如需渲染远端视频画面,你需要在绑定 VideoSurface.cs
前调用 SetForMultiChannelUser
,并指定远端用户的 uid
及其所在频道的 channelId
。
SDK 仅支持用户同一时间在一个频道内发布媒体流。声网推荐以观众角色加入无需发流的频道并在加入频道时设置 ChannelMediaOptions
中的 publishLocalAudio
和 publishLocalVideo
为 false
。
在直播场景中,用户作为主播加入频道一后,SDK 默认发布本地流到频道一。如果用户需要加入频道二,则你需要根据实际场景修改发布状态:
SetClientRole(AUDIENCE)
)加入频道二。SetClientRole(AUDIENCE)
),再让用户作为主播(SetClientRole(BROADCASTER)
)加入频道二。如果用户已在频道一内发流,并且在频道二内调用如下方法,则该方法会调用失败并返回 -5(ERR_REFUSED)
错误码。
JoinChannel
方法加入频道二时使用 publishLocalAudio = true
和 publishLocalVideo = true
。MuteLocalAudioStream(false)
或 MuteLocalVideoStream(false)
。SetClientRole(BROADCASTER)
。自 3.4.5 版本起,AgoraChannel
类有如下变更:
Publish
和 Unpublish
,并新增 MuteLocalAudioStream
和 MuteLocalVideoStream
替代。加入频道后,你可以分别设置音频流和视频流的发布状态。IRtcEngine
类和 AgoraChannel
类下的 MuteLocalAudioStream
和 MuteLocalVideoStream
分别控制各自频道的发布状态。ChannelMediaOptions
中新增 publishLocalAudio
和 publishLocalVideo
成员,默认值为 true
。你可以调用 JoinChannel
加入频道并设置音视频流的发布状态。如果用户已在一个频道中发流,则不论用户是主播还是观众,都需要在加入其他频道时设置 publishLocalAudio
和 publishLocalVideo
为 false
。否则,加入频道会失败。SetClientRole(BROADCASTER)
默认发布本地流,无需再调用 Publish
。在 3.4.5 之前版本中:
IRtcEngine
类的 MuteLocalAudioStream(true)
或 MuteLocalVideoStream(true)
对 IRtcEngine
频道和 AgoraChannel
频道都会生效。MuteLocalAudioStream
和 MuteLocalVideoStream
在加入频道前后调用均可生效。JoinChannel
不可以设置本地流的发布状态。AgoraChannel
类的 SetClientRole(BROADCASTER)
不会发布本地流。你还需要调用 Publish
。