实现秀场直播
本文介绍如何实现秀场直播。
示例项目
声网在 agora-ent-scenarios
仓库中提供秀场直播源代码供你参考:
业务流程图
本节展示秀场直播中常见的业务流程。
创建直播间
下图展示房主预览、创建、进入、退出直播的流程。
进入/退出直播间
下图展示用户进入房主已创建好的直播间的流程。这里的用户可以有两种角色:
- 观众:可以观看房主直播或与主播连麦。
- 虚假主播:通过声网输入在线媒体流服务在房间内创建的一路视频流,是伪直播。观众和房主可以观看虚假主播,但是不能与其连麦。
主播 PK 连麦
下图展示主播 PK 连麦的流程。在这个流程中,房主邀请另一个房间的房主开始 PK 连麦。两个房间内的观众都可以看到两个房主 PK 连麦直播的画面。
观众连麦
下图展示观众与主播连麦的流程。观众与主播连麦有两种方式:
- 主播邀请观众连麦。展示在下图左侧的流程中。
- 观众申请与主播连麦。展示在下图右侧的流程中。
准备开发环境
前提条件
开始前,请确保前提条件如下:
- Android Studio 4.1 及以上。
- Android 手机,版本 Android 5.0(API Level 21)及以上。
- 可以访问互联网的计算机。确保你的网络环境没有部署防火墙,否则无法正常使用声网服务。
- 开通服务并从声网控制台获取以下信息:
- App ID:声网随机生成的字符串,用于识别你的 App。
- 临时 Token:你的 App 客户端加入频道时会使用 Token 对用户进行鉴权。临时 Token 的有效期为 24 小时。
- 频道名称:用于标识直播频道的字符串。
创建项目
按照如下步骤准备开发环境:
-
如需创建新项目,在 Android Studio 里,依次选择 Phone and Tablet > Empty Activity,创建 Android 项目。
注意创建项目后,Android Studio 会自动开始同步 gradle, 稍等片刻至同步成功后再进行下一步操作。
-
使用 Maven Central 将声网 RTC SDK 集成到你的项目中。
-
在
/Gradle Scripts/build.gradle(Project: <projectname>)
文件中添加如下代码,添加 Maven Central 依赖:Kotlinbuildscript {
repositories {
...
mavenCentral()
}
...
}
allprojects {
repositories {
...
mavenCentral()
}
}注意如果你的 Android 项目设置了 dependencyResolutionManagement,添加 Maven Central 依赖的方式可能存在差异。
-
在
/Gradle Scripts/build.gradle(Module: <projectname>.app)
文件中添加如下代码,将声网 RTC SDK 集成到你的 Android 项目中:Kotlin...
dependencies {
...
// x.y.z,请填写具体的 SDK 版本号,如:4.0.0 或 4.1.0-1
// 通过实时互动产品发版说明获取最新版本号
implementation 'io.agora.rtc:full-sdk:x.y.z'
}
-
-
将商汤美颜 SDK 复制到你的项目中:请联系商汤技术支持获取美颜 SDK、测试证书、集成步骤。
-
添加网络及设备权限。
在
/app/Manifests/AndroidManifest.xml
文件中,在</application>
后面添加如下权限:XML<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!-- 对于 Android 12.0 及以上且集成 v4.1.0 以下声网 SDK 的设备,还需要添加以下权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<!-- 对于 Android 12.0 及以上设备,还需要添加以下权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> -
在
/Gradle Scripts/proguard-rules.pro
文件中添加如下行,以防止声网 SDK 的代码被混淆:Kotlin-keep class io.agora.**{*;}
-dontwarn javax.**
-dontwarn com.google.devtools.build.android.**
实现秀场直播
时序图展示如何创建直播间、进入直播间、PK 连麦、观众连麦、退出直播间。
- 声网 RTC SDK:承担实时音视频的业务。本节会详细介绍如何调用 RTC SDK 的 API 完成这些音视频逻辑。
- 应用业务服务器:承担信令消息和数据存储的业务。需要你自行实现,声网提供时序图和示例项目供你参考。
1. 创建直播间
创建直播间时,你需要初始化 RTC 引擎、注册原始视频数据以设置商汤美颜、为主播设置本地视图并进行预览。本节展示初始化 RTC 引擎和注册原始视频数据的示例代码。
你需要调用 create
方法创建 RTC 引擎,并在 config
参数中配置上下文 Context、项目的 App ID、注册事件回调。调用 registerVideoFrameObserver
注册原始视频数据 beautyProcessor
,用于后续设置商汤美颜。再调用 enableVideo
开启视频。
val rtcEngine: RtcEngineEx
get() {
if (innerRtcEngine == null) {
val config = RtcEngineConfig()
config.mContext = AgoraApplication.the()
config.mAppId = io.agora.scene.base.BuildConfig.AGORA_APP_ID
config.mEventHandler = object : IRtcEngineEventHandler() {
override fun onError(err: Int) {
super.onError(err)
ToastUtils.showToast(
"Rtc Error code:$err, msg:" + RtcEngine.getErrorDescription(err)
)
}
}
innerRtcEngine = (RtcEngine.create(config) as RtcEngineEx).apply {
registerVideoFrameObserver(beautyProcessor)
enableVideo()
}
}
return innerRtcEngine!!
}
2. 进入直播间
进入直播间时,你需要在主播和观众端都设置并渲染主播视频,再加入频道。本节展示加入频道的示例代码。
调用 joinChannelEx
加入频道。频道用于传输直播间中的音视频流,应用业务服务用于传输直播间中的信令消息和存储数据。用户在频道内可以进行实时音视频互动。频道内的用户有两种角色:
- 主播:可以发送和接收音视频流。直播间的房主即为主播。
- 观众:只可以接收音视频流。
private fun joinChannel() {
val channelMediaOptions = ChannelMediaOptions()
channelMediaOptions.clientRoleType =
if (isRoomOwner) Constants.CLIENT_ROLE_BROADCASTER else Constants.CLIENT_ROLE_AUDIENCE
channelMediaOptions.autoSubscribeVideo = true
channelMediaOptions.autoSubscribeAudio = true
// 对于房主,发布音视频流
// 对于观众,不发布音视频流
channelMediaOptions.publishCameraTrack = isRoomOwner
channelMediaOptions.publishMicrophoneTrack = isRoomOwner
// 对于观众,把延时等级设置为 LOW_LATENCY,以便体验低延时的音视频互动
if (!isRoomOwner) {
channelMediaOptions.audienceLatencyLevel = AUDIENCE_LATENCY_LEVEL_LOW_LATENCY
}
rtcEngine.joinChannel(
token,
rtcConnection,
channelMediaOptions,
object: IRtcEventHandler() {}
)
}
3. 主播设置本地视图
进入直播间时,你需要在主播和观众端都设置并渲染主播视频,再加入频道。本节展示调用 setupLocalVideo
在主播端设置并渲染主播视频的示例代码。
mRtcEngine.setupLocalVideo(
VideoCanvas(
view, // 需要渲染的视图
RENDER_MODE_HIDDEN, // 渲染模式
)
)
4. 观众渲染主播视频
进入直播间时,你需要在主播和观众端都设置并渲染主播视频,再加入频道。本节展示调用 setupRemoteVideoEx
在观众端渲染远端视频(即主播的视频)的示例代码。
mRtcEngine.setupRemoteVideoEx(
VideoCanvas(
view, // 需要渲染的视图
RENDER_MODE_HIDDEN, // 渲染模式
uid // 主播 UID(用户 ID)
),
rtcConnection
)
5. 主播 PK 连麦
房主跨直播间 PK 连麦意味着不同频道内的主播加入对方频道进行连麦。当房间内用户收到房主 PK 连麦的信令消息后,房间内用户的代码逻辑如下:
- 房间 A:
- 房主 A 通过
joinChannelEx
加入频道 B,并且设置订阅频道 B 内音视频流,但不发送音视频流。同时通过setupRemoteVideoEx
渲染频道 B 中主播的视频。 - 观众通过
joinChannelEx
加入频道 B,并且设置订阅频道 B 内音视频流,但不发送音视频流。同时通过setupRemoteVideoEx
渲染频道 B 中主播的视频。
- 房主 A 通过
- 房间 B:
- 房主 B 通过
joinChannelEx
加入频道 A,并且设置订阅频道 A 内音视频流,但不发送音视频流。同时通过setupRemoteVideoEx
渲染频道 A 中主播的视频。 - 观众通过
joinChannelEx
加入频道 A,并且设置订阅频道 A 内音视频流,但不发送音视频流。同时通过setupRemoteVideoEx
渲染频道 A 中主播的视频。
- 房主 B 通过
完成这些逻辑后,观众可以同时接收频道 A 和 B 的音视频流,因此可以同时看到两个房间的房主。房主仅在自己的频道发流,在对方的频道内不发流仅收流,因此,房主可以(在对方频道)看到对方,达到连麦的效果。
为了加快 PK 连麦时视频出图速度,在发起 PK 连麦的过程中就让用户加入对方频道并设置远端视图,但此时无需订阅音频流。等 PK 连麦建立成功后,直接将远端视频显示出来并订阅对方主播的音频流。请注意,提前加入频道会产生额外的费用,因此建议你根据实际业务需要调整集成逻辑。
结束 PK 连麦时,房间内用户都需要调用 leaveChannelEx
离开对方频道。
本节展示用户加入对方频道和离开对方频道的示例代码,如需查阅 setupRemoteVideoEx
方法调用逻辑,请参考 GitHub 示例项目。
// 主播加入对方频道的设置
val channelMediaOptions = ChannelMediaOptions()
channelMediaOptions.publishCameraTrack = false
channelMediaOptions.publishMicrophoneTrack = false
channelMediaOptions.publishCustomAudioTrack = false
channelMediaOptions.autoSubscribeVideo = true
channelMediaOptions.autoSubscribeAudio = true
channelMediaOptions.clientRoleType = Constants.CLIENT_ROLE_AUDIENCE
channelMediaOptions.audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY
channelMediaOptions.isInteractiveAudience = true
// 观众加入对方频道的设置
val channelMediaOptions = ChannelMediaOptions()
channelMediaOptions.publishCameraTrack = false
channelMediaOptions.publishMicrophoneTrack = false
channelMediaOptions.publishCustomAudioTrack = false
channelMediaOptions.autoSubscribeVideo = true
channelMediaOptions.autoSubscribeAudio = true
channelMediaOptions.clientRoleType = Constants.CLIENT_ROLE_AUDIENCE
channelMediaOptions.audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_ULTRA_LOW_LATENCY
channelMediaOptions.isInteractiveAudience = true
// 加入对方频道
// 例如房间 A 的用户加入频道 B,或者房间 B 的用户加入频道 A
rtcEngine.joinChannelEx(
token,
pkRtcConnection,
channelMediaOptions,
object: IRtcEventHandler() {}
)
// 退出对方频道
// 例如房间 A 的用户退出频道 B,或者房间 B 的用户退出频道 A
rtcEngine.leaveChannelEx(pkRtcConnection)
6. 观众连麦
观众与主播连麦时,你可以通过信令让主播邀请观众连麦,或观众向主播申请连麦。让待上麦观众更新频道媒体选项、预览并设置本地视图。让其他用户收到观众连麦通知后,渲染该连麦观众的视频。完成这些逻辑后,直播间内观众可以看到主播和上麦观众的连麦直播。
结束连麦时,你需要让待下麦观众更新频道媒体选项、停止预览并取消本地试图。让其他用户收到该观众下麦通知后,取消渲染该观众的视频。完成这些逻辑后,直播间观众可以看到仅有主播的直播画面。
本节展示观众连麦和结束连麦时更新频道媒体选项的示例代码。通过 updateChannelMediaOptionsEx
方法在观众加入频道后更新频道媒体选项,例如是否开启本地音频采集,是否发布本地音频流等。观众的用户角色为 CLIENT_ROLE_AUDIENCE
,因此无法在频道内发布音频流。如果观众想与主播连麦,需要将用户角色修改为 CLIENT_ROLE_BROADCASTER
。
// 观众上麦时,用户角色从 AUDIENCE 切换成 BROADCASTER
val channelMediaOptions = ChannelMediaOptions()
// 发布音视频流
channelMediaOptions.publishCameraTrack = true
channelMediaOptions.publishMicrophoneTrack = true
channelMediaOptions.publishCustomAudioTrack = false
channelMediaOptions.enableAudioRecordingOrPlayout = true
channelMediaOptions.autoSubscribeVideo = true
channelMediaOptions.autoSubscribeAudio = true
channelMediaOptions.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
mRtcEngine.updateChannelMediaOptionsEx(channelMediaOptions, rtcConnection)
// 连麦观众下麦时,用户角色从 BROADCASTER 切换成 AUDIENCE
val channelMediaOptions = ChannelMediaOptions()
val rtcConnection = mMainRtcConnection
// 不发布音视频流
channelMediaOptions.publishCameraTrack = false
channelMediaOptions.publishMicrophoneTrack = false
channelMediaOptions.publishCustomAudioTrack = false
channelMediaOptions.enableAudioRecordingOrPlayout = true
channelMediaOptions.autoSubscribeVideo = true
channelMediaOptions.autoSubscribeAudio = true
// 注意:角色为观众时,即使 publishCameraTrack 和 publishMicrophoneTrack 设为 true,也无法发音视频流。如需发布音视频流,必须将角色设为主播。
channelMediaOptions.clientRoleType = Constants.CLIENT_ROLE_AUDIENCE
channelMediaOptions.audienceLatencyLevel = Constants.AUDIENCE_LATENCY_LEVEL_LOW_LATENCY
mRtcEngine.updateChannelMediaOptionsEx(channelMediaOptions, rtcConnection)
7. 离开并销毁房间
直播结束时,主播和观众离开房间,你可以离开频道并销毁 RTC 引擎。
本节展示调用 destroy
销毁 RTC 引擎的示例代码。
fun destroy() {
// 移除所有消息和定时任务
innerVideoSwitcher?.let {
it.unloadConnections()
innerVideoSwitcher = null
}
// 销毁 RTC 引擎
innerRtcEngine?.let {
workingExecutor.execute { RtcEngine.destroy() }
innerRtcEngine = null
}
// 释放 beautyProcessor
innerBeautyProcessor?.let { processor ->
processor.release()
innerBeautyProcessor = null
}
}
// innerVideoSwitcher
// class VideoSwitcherImpl
// unloadConnections 函数中执行的具体操作
override fun unloadConnections() {
mainHandler.removeCallbacksAndMessages(null)
connectionsJoined.forEach {
leaveRtcChannel(it)
}
connectionsPreloaded.forEach {
leaveRtcChannel(it)
}
localVideoCanvas?.release()
connectionsForPreloading.clear()
connectionsPreloaded.clear()
connectionsJoined.clear()
ShowLogger.d(tag, "unloadConnections")
}
API 时序图
下图展示实现本文全部流程所需的 API 调用时序。