You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin中ExoPlayer进度监听及时段贴纸显示实现方案

在ExoPlayer中实现定时显示的视频贴纸/图片

针对你需要在视频预览层上方添加指定时段显示的贴纸需求,PlayerMessage确实不适合——它仅能在特定播放位置触发单次操作,无法处理持续的区间检测和状态同步。以下是几种更合适的实现方案:


方案一:自定义Overlay + 播放进度监听(推荐,适合大多数场景)

这是最直接且易维护的方案,核心思路是将贴纸View放在ExoPlayer的渲染层上方,通过实时监听播放进度来切换贴纸可见性。

实现步骤:

  1. 布局层级:在XML中将贴纸View(如ImageView)放在ExoPlayer的SurfaceView/TextureView同级且上方,初始设为GONE。
  2. 监听播放进度:通过ExoPlayer的AnalyticsListener或Player.Listener实时获取当前播放位置。
  3. 区间判断与状态切换:每次进度更新时,判断当前位置是否在贴纸的显示区间内,动态调整View的可见性。

代码示例(Kotlin):

// 初始化贴纸View
val stickerView = findViewById<ImageView>(R.id.sticker_view)
stickerView.visibility = View.GONE

// 定义贴纸的时间区间(单位:毫秒)
val stickerStartMs = 2000L
val stickerEndMs = 6000L

// 监听播放进度,实时更新贴纸状态
player.addAnalyticsListener(object : AnalyticsListener {
    override fun onPositionChanged(
        eventTime: AnalyticsListener.EventTime,
        positionMs: Long,
        bufferedPositionMs: Long,
        totalDurationMs: Long
    ) {
        val isInDisplayRange = positionMs in stickerStartMs..stickerEndMs
        stickerView.visibility = if (isInDisplayRange) View.VISIBLE else View.GONE
    }
})

扩展:多贴纸场景

如果需要添加多个贴纸,可将贴纸信息封装为数据类,遍历所有贴纸进行区间判断:

data class Sticker(
    val view: View,
    val startMs: Long,
    val endMs: Long
)

val stickers = listOf(
    Sticker(findViewById(R.id.sticker1), 2000L, 6000L),
    Sticker(findViewById(R.id.sticker2), 4000L, 8000L)
)

// 在进度监听中遍历更新
stickers.forEach { sticker ->
    val isInRange = positionMs in sticker.startMs..sticker.endMs
    sticker.view.visibility = if (isInRange) View.VISIBLE else View.GONE
}

方案二:视频帧同步绘制(适合高精度需求)

如果需要贴纸与视频帧精确同步(如随视频缩放、旋转,或添加动画贴纸),可通过ExoPlayer的VideoListener在视频帧渲染时直接绘制贴纸到Surface上。

代码示例(Kotlin):

// 自定义贴纸数据类,包含位置、时间区间和位图
data class FrameSticker(
    val bitmap: Bitmap,
    val startMs: Long,
    val endMs: Long,
    val x: Float,
    val y: Float
)

val stickerList = listOf(
    FrameSticker(
        BitmapFactory.decodeResource(resources, R.drawable.sticker),
        2000L,
        6000L,
        100f,
        100f
    )
)

// 监听视频帧渲染事件,直接绘制贴纸
player.addVideoListener(object : VideoListener {
    override fun onVideoFrameRendered(
        player: Player,
        timestampMs: Long,
        frame: Bitmap?
    ) {
        val surfaceHolder = (player.videoSurfaceView as SurfaceView).holder
        val canvas = surfaceHolder.lockCanvas()
        canvas?.let {
            // 遍历贴纸,在对应时间区间绘制
            stickerList.forEach { sticker ->
                if (timestampMs in sticker.startMs..sticker.endMs) {
                    it.drawBitmap(sticker.bitmap, sticker.x, sticker.y, null)
                }
            }
            surfaceHolder.unlockCanvasAndPost(it)
        }
    }
})

注意事项:

  • 避免在主线程执行复杂绘制逻辑,可通过子线程处理位图预处理。
  • 开启硬件加速提升绘制性能,避免卡顿。

方案三:PlayerMessage结合进度拖动监听(仅适合简单单次场景)

如果坚持使用PlayerMessage,可通过两次消息分别触发显示和隐藏,同时添加进度拖动监听处理用户手动跳转的情况:

// 触发显示贴纸
val showMsg = PlayerMessage(
    player,
    Runnable { stickerView.visibility = View.VISIBLE },
    2000L
)
showMsg.send()

// 触发隐藏贴纸
val hideMsg = PlayerMessage(
    player,
    Runnable { stickerView.visibility = View.GONE },
    6000L
)
hideMsg.send()

// 处理进度拖动后的状态同步
player.addListener(object : Player.Listener {
    override fun onPlaybackStateChanged(playbackState: Int) {
        if (playbackState == Player.STATE_READY) {
            val currentPos = player.currentPosition
            stickerView.visibility = if (currentPos in 2000L..6000L) View.VISIBLE else View.GONE
        }
    }
})

方案选型建议:

  • 普通贴纸需求:优先选方案一,实现成本低、稳定性高。
  • 高精度/动画贴纸需求:选方案二,保证帧级同步。
  • 单次触发类需求:可考虑方案三,但需额外处理进度拖动的边界情况。

内容的提问来源于stack exchange,提问作者Dhina17

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 21:15:33