调整视频直播挂件 (Android)

最近更新时间:2026-04-08 23:35:11

我的收藏
LiveCoreView 是一个跨平台的视频直播核心组件,提供开播、观看、连麦和主播 PK 等关键能力。该组件通过挂件机制,支持在视频区域实时显示用户名、等级、PK 进度条等自定义信息。本文档指导开发者通过实现接口,在 Android 平台上快速定制专属的视频挂件 UI

效果展示

连线视频挂件
PK 视频挂件



准备工作

在开始调整视频挂件前,请先参考 主播开播观众观看 完成主流程的搭建。

核心原理

LiveCoreView 通过 VideoViewAdapter 代理支持自定义视图渲染。当业务场景发生变化(例如有人上麦、开始 PK )时,LiveCoreView 会调用代理方法询问应该显示的视图。开发者实现对应的接口方法并返回自定义的 View 实例即可。
方法
描述
对应业务场景
createCoGuestView
创建观众连麦的挂件视图。
观众连麦、邀请上麦
createCoHostView
创建跨房连麦(主播连线)的挂件视图。
主播连线
createBattleView
创建 PK 场景中单个用户的挂件视图(例如头像、分数)。
主播 PK
createBattleContainerView
创建 PK 场景的整体容器视图(例如背景、分数 PK 条)。
主播 PK

调整连线场景挂件

当观众与主播进行连线或者主播间跨房连线时,直播间界面会从单人模式切换为多人布局。此时,需要在连线的视频窗口上展示特定的用户信息,例如昵称、等级或“静音”状态提示,以便区分不同麦位。

适用场景

主播和观众视频连麦时,修改麦位上的用户信息(例如昵称、等级、静音图标)。
主播与主播跨房连线时,修改对方主播的显示样式(例如昵称、等级、静音图标)。
修改无视频画面时的默认背景(例如占位头像)。
修改空麦位时的视图实现。

视图层级示意



实现步骤

说明:
您也可以参考 TUILiveKit 开源项目中的 CoGuestWidgetsCoHostWidgets 目录下文件来了解完整的实现逻辑。

步骤 1. 准备自定义挂件 View

定义三个基础视图类,分别用于展示用户信息、空麦位提示和无视频流时的背景。
创建自定义的用户信息视图:
import android.content.Context
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView

class CustomInfoView(context: Context, name: String, isMuted: Boolean) : FrameLayout(context) {
private val nameTextView = TextView(context)
private val muteIcon = ImageView(context)
init {
nameTextView.text = name
muteIcon.visibility = if (isMuted) View.VISIBLE else View.GONE
addView(nameTextView)
addView(muteIcon)
// 此处省略具体布局参数设置代码
}
}
创建自定义的空麦位视图:
import android.content.Context
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView

class EmptySeatView(context: Context) : FrameLayout(context) {
private val addIcon = ImageView(context)
private val addLabel = TextView(context)
init {
addLabel.text = "邀请连线"
// 此处省略图片资源加载和布局参数设置代码
}
}
创建自定义无视频流时的头像占位视图:
import android.content.Context
import android.widget.FrameLayout
import android.widget.ImageView

class CustomAvatarView(context: Context, avatarURL: String) : FrameLayout(context) {
private val avatarImageView = ImageView(context)
init {
// 实际项目中可使用 Glide 等图片加载库加载 avatarURL
addView(avatarImageView)
// 此处省略具体布局参数设置代码
}
}

步骤 2. 实现 Adapter 逻辑

创建一个适配器类,实现 VideoViewAdapter 接口的方法 createCoGuestView (观众连线)和 createCoHostView (主播连线),返回刚才创建的自定义视图。
实现 VideoViewAdapter 中的 createCoGuestView 方法,返回观众连线视频挂件。
import android.content.Context
import android.view.View
import io.trtc.tuikit.atomicxcore.api.device.DeviceStatus
import io.trtc.tuikit.atomicxcore.api.live.SeatInfo
import io.trtc.tuikit.atomicxcore.api.view.VideoViewAdapter
import io.trtc.tuikit.atomicxcore.api.view.ViewLayer


class VideoWidgetAdapter(private val context: Context) : VideoViewAdapter {
override fun createCoGuestView(seatInfo: SeatInfo, viewLayer: ViewLayer): View? {
val isUserOnSeat = seatInfo.userInfo.userID.isNotEmpty()

return when (viewLayer) {
ViewLayer.FOREGROUND -> {
if (isUserOnSeat) {
// 非空麦位,返回自定义的前景视图
CustomInfoView(context, seatInfo.userInfo.userName, seatInfo.userInfo.microphoneStatus == DeviceStatus.OFF)
} else {
// 空麦位,返回自定义空麦位视图
EmptySeatView(context)
}
}
ViewLayer.BACKGROUND -> {
if (isUserOnSeat) {
// 返回自定义的背景视图(未开摄像头时显示)
CustomAvatarView(context, seatInfo.userInfo.avatarURL)
} else {
null
}
}
}
}
}
实现 VideoViewAdapter 中的 createCoHostView 方法,返回主播连线的视频挂件。
import android.content.Context
import android.view.View
import io.trtc.tuikit.atomicxcore.api.device.DeviceStatus
import io.trtc.tuikit.atomicxcore.api.live.SeatInfo
import io.trtc.tuikit.atomicxcore.api.view.VideoViewAdapter
import io.trtc.tuikit.atomicxcore.api.view.ViewLayer

class VideoWidgetAdapter(private val context: Context) : VideoViewAdapter {

override fun createCoHostView(seatInfo: SeatInfo, viewLayer: ViewLayer): View? {
val isUserOnSeat = seatInfo.userInfo.userID.isNotEmpty()

return when (viewLayer) {
ViewLayer.FOREGROUND -> {
if (isUserOnSeat) {
// 返回自定义的前景视图,可以返回与观众连线不同的样式
CustomInfoView(context, seatInfo.userInfo.userName, seatInfo.userInfo.microphoneStatus == DeviceStatus.OFF)
} else {
// 返回自定义空麦位视图,可以返回与观众连线不同的样式
EmptySeatView(context)
}
}
ViewLayer.BACKGROUND -> {
if (isUserOnSeat) {
// 返回自定义的背景视图(未开摄像头时显示),可以返回与观众连线不同的样式
CustomAvatarView(context, seatInfo.userInfo.avatarURL)
} else {
null
}
}
}
}
}

参数说明

参数
类型
说明
seatInfo
SeatInfo
麦位信息对象,包含麦上用户的详细信息。
seatInfo.userInfo.userName
String
麦上用户的昵称。
seatInfo.userInfo.avatarURL
String
麦上用户的头像 URL。
seatInfo.userInfo.microphoneStatus
DeviceStatus
麦上用户的麦克风状态。
seatInfo.userInfo.cameraStatus
DeviceStatus
麦上用户的摄像头状态。
viewLayer
ViewLayer
视图层级枚举。
.foreground 表示前景挂件视图,始终显示在视频画面的最上层。
.background 表示背景挂件视图,位于前景视图下层,仅在对应用户没有视频流(例例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图。

调整主播 PK 场景挂件

PK 是直播中互动性最强的环节。在 PK 过程中,画面通常根据参与人数被分割为几部分。开发者需要在画面上方或中间区域添加 PK 专用的 UI ,分数 PK 条、每个麦位上的分数显示、倒计时动画或“VS”特效图标,以营造紧张的竞技氛围。

视图层级示意


说明:
PK 功能依赖于连线功能,所以需要先在主播连线的情况下才能发起 PK 请求

实现步骤

步骤 1. 准备自定义 UI 组件

PK 场景通常需要两类视图:
单人挂件:显示在每个主播窗口上(例如分数胶囊)。
全局容器:覆盖在整体画面上(例如 VS 动画、倒计时)。
说明:
您也可以参考 TUILiveKit 开源项目中的 BattleWidgets 目录下文件来了解完整的实现逻辑。
import android.content.Context
import android.widget.FrameLayout
import android.widget.ImageView

// 示例:单人分数条
class MyBattleScoreView(context: Context) : FrameLayout(context) {
// 内部实现分数显示逻辑
}

// 示例:全局 VS 面板
class MyBattleContainer(context: Context) : FrameLayout(context) {
private val battleTimeView = ImageView(context)
// 内部实现倒计时和 VS 动画逻辑
}


步骤 2. 实现 Adapter 逻辑

在之前创建的 VideoWidgetAdapter 中,实现剩余的 PK 视图方法。
class VideoWidgetAdapter(private val context: Context) : VideoViewAdapter {
// 省略 createCoGuestView / createCoHostView 实现...

// 1. 创建【PK 单人信息】挂件 (显示在每个主播视频上方)
override fun createBattleView(seatInfo: SeatInfo): View? {
return MyBattleScoreView(context)
}

// 2. 创建【PK 全局容器】挂件 (显示在整个视频区域上方)
override fun createBattleContainerView(): View? {
return MyBattleContainer(context)
}
}

集成与生效(重要)

这是最关键的一步。您需要将实现了代理逻辑的 VideoWidgetAdapter 注入到直播核心流程中。
主播端集成:需在初始化外层容器前,先初始化 LiveCoreView 并通过 setVideoViewAdapter 设置适配器。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.ViewGroup
import com.trtc.uikit.livekit.features.anchorview.AnchorView
import io.trtc.tuikit.atomicxcore.api.live.LiveInfo
import io.trtc.tuikit.atomicxcore.api.view.LiveCoreView


class AnchorActivity : AppCompatActivity() {
private lateinit var anchorView: AnchorView
private lateinit var widgetAdapter: VideoWidgetAdapter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 初始化适配器对象
widgetAdapter = VideoWidgetAdapter(this)

// 假设此处已获取 liveInfo
val liveInfo = LiveInfo()

// 1. 仅传入 Context 初始化 LiveCoreView
val videoView = LiveCoreView(this)
// 2. 调用 setVideoViewAdapter 传入自定义适配器
videoView.setVideoViewAdapter(widgetAdapter)

// 3. 将核心视频组件传入外层容器
anchorView = AnchorView(this, liveInfo, videoView)

setContentView(anchorView, ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
))
}
}
观众端集成:观众端通过 AudienceContainerViewDelegate 来回调核心视图的创建时机。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import io.trtc.tuikit.atomicxcore.api.live.LiveInfo
import io.trtc.tuikit.atomicxcore.api.view.LiveCoreView

class AudienceActivity : AppCompatActivity(), AudienceContainerViewDelegate {
private lateinit var audienceView: AudienceContainerView
private lateinit var widgetAdapter: VideoWidgetAdapter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

widgetAdapter = VideoWidgetAdapter(this)

val roomId = "your_room_id"
audienceView = AudienceContainerView(this, roomId)
audienceView.setDelegate(this)

setContentView(audienceView)
}

override fun onCreateCoreView(liveInfo: LiveInfo): LiveCoreView? {
val view = LiveCoreView(this)
// 注入适配器
view.setVideoViewAdapter(widgetAdapter)
return view
}
}

进阶:获取实时业务数据

在开发 PK 等复杂场景时,您会发现 SeatInfo 只提供了基础麦位信息。如果您需要获取实时倒计时、PK 分数等业务数据,就需要在您的自定义视图中对接 AtomicXCore 中的核心数据。
Store/Component
功能描述
API 文档
CoGuestStore
观众连线数据:已连线用户列表、邀请列表,申请列表等。
CoHostStore
主播连线数据:已连线用户列表、邀请列表,申请列表等。
BattleStore
PK 数据:当前 PK 信息、PK 用户列表、PK 分数列表。

常见问题

设置了适配器,但自定义视图没有显示?

请检查设置时机是否太晚。在 Android 的组件装载链路中,LiveCoreView 的适配器赋值必须在它被传入 AnchorView 之前完成。如果 AnchorView 已经完成了初始化,它内部会直接加载 SDK 的默认挂件视图。
解决:请务必遵循严格的初始化顺序:先 new LiveCoreView > 再调用 setVideoViewAdapter 注入自定义 UI > 最后实例化 AnchorView 并传入配置好的 CoreView

我只想修改“连麦”视图,“PK”视图想保留默认的,怎么办?

VideoViewAdapter 是一种完全接管模式。一旦您设置了自定义的代理,SDK 默认的 AnchorView/AudienceView 内部实现的代理逻辑就会完全失效。
解决:您需要在您的代理类中实现所有相关方法。对于您不想修改的部分(例如 PK 视图),您可以:
1. 复制源码:找到 LiveKit 源码中默认的实现类,在您的代理中返回这些默认类的实例。
2. 手动实现:参考默认样式,快速写一个类似的简单视图返回。

自定义挂件显示了,但无法点击?

这是 Android 经典的事件分发冲突问题。由于前景视图(.FOREGROUND)叠加在视频渲染层之上:
1. 请确保您返回的 CustomView 设置了 isClickable = true 或注册了 setOnClickListener
2. 如果您的挂件是一个嵌套的 ViewGroup(例如 FrameLayout),请检查其父容器是否错误地拦截了触摸事件(例如被外层控件触发了 onInterceptTouchEvent 且返回了 true)。