You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

ExoPlayer直播流:如何检测直播状态并自定义进度条时间显示?

我来帮你搞定ExoPlayer直播流进度条的时间显示问题,针对你提到的两个核心需求,咱们一步步实现:

1. 检测当前媒体是否为直播流

ExoPlayer的TimelineWindow类已经提供了判断直播流的属性,咱们可以通过Player.ListeneronTimelineChanged回调来实时检测:

首先,在你的类里定义一个完整的播放状态监听器,替换原来的playbackStateListener

private val playbackStateListener = object : Player.Listener {
    override fun onTimelineChanged(timeline: Timeline, reason: Int) {
        super.onTimelineChanged(timeline, reason)
        // 判断是否为直播流:检查Timeline的第一个Window是否标记为Live
        val isLive = timeline.windowCount > 0 && timeline.getWindow(0, Window()).isLive
        // 触发进度条时间显示更新
        updateProgressBarTimeDisplay(isLive)
    }

    override fun onPlaybackStateChanged(playbackState: Int) {
        // 控制时间更新的定时任务启停
        when (playbackState) {
            Player.STATE_READY -> updateHandler.post(updateRunnable)
            Player.STATE_ENDED, Player.STATE_IDLE -> updateHandler.removeCallbacks(updateRunnable)
        }
    }
}

这个逻辑的核心是Window.isLive属性,MPD直播流的元数据会被ExoPlayer解析并标记这个属性,所以能准确判断当前是否为直播场景。

2. 自定义直播流的进度条时间显示逻辑

你的需求是:直播时进度条左端显示负数值形式的缓存时长(比如-00:59:51),右端显示直播节点(比如LIVE)。咱们需要自定义时间格式,并定时更新进度条的文本。

步骤2.1:编写时间格式化工具函数

先写一个把毫秒转换成时分秒格式的函数,支持负数显示:

private fun formatTimeMs(timeMs: Long): String {
    val isNegative = timeMs < 0
    val absTimeMs = kotlin.math.abs(timeMs)
    val hours = absTimeMs / 3600000
    val minutes = (absTimeMs % 3600000) / 60000
    val seconds = (absTimeMs % 60000) / 1000
    return if (isNegative) {
        String.format("-%02d:%02d:%02d", hours, minutes, seconds)
    } else {
        String.format("%02d:%02d:%02d", hours, minutes, seconds)
    }
}

步骤2.2:实现时间更新逻辑

接下来写更新进度条左右文本的方法,区分直播和非直播场景:

private fun updateProgressBarTimeDisplay(isLive: Boolean) {
    playerView?.let { safePlayerView ->
        // 获取默认进度条的左右时间TextView(ExoPlayer默认id是exo_position和exo_duration)
        val positionTextView = safePlayerView.findViewById<TextView>(R.id.exo_position)
        val durationTextView = safePlayerView.findViewById<TextView>(R.id.exo_duration)
        
        player?.let { safePlayer ->
            if (isLive) {
                val window = Window()
                safePlayer.currentTimeline.getWindow(safePlayer.currentWindowIndex, window)
                // 直播节点时间(live edge)
                val liveEdgeMs = window.liveEdgeMs
                // 当前播放位置
                val currentPositionMs = safePlayer.currentPosition
                // 缓存时长 = 直播节点 - 当前位置,左端显示负的这个值
                val cacheDurationMs = liveEdgeMs - currentPositionMs
                positionTextView.text = formatTimeMs(-cacheDurationMs)
                // 右端显示直播标识
                durationTextView.text = "LIVE"
            } else {
                // 非直播场景用默认逻辑:显示当前位置和总时长
                positionTextView.text = formatTimeMs(safePlayer.currentPosition)
                durationTextView.text = formatTimeMs(safePlayer.duration)
            }
        }
    }
}

步骤2.3:定时更新时间

因为直播流的播放位置和直播节点一直在变化,咱们需要定时触发更新:

private val updateHandler = Handler(Looper.getMainLooper())
private val updateRunnable = object : Runnable {
    override fun run() {
        player?.let { safePlayer ->
            val timeline = safePlayer.currentTimeline
            if (timeline.windowCount > 0) {
                val window = Window()
                timeline.getWindow(safePlayer.currentWindowIndex, window)
                updateProgressBarTimeDisplay(window.isLive)
            }
            // 每秒更新一次
            updateHandler.postDelayed(this, 1000)
        }
    }
}

步骤2.4:清理资源

最后,记得在页面销毁时停止定时任务,避免内存泄漏:

override fun onDestroy() {
    super.onDestroy()
    updateHandler.removeCallbacks(updateRunnable)
    player?.release()
    player = null
}
整合到你的初始化代码里

把上面定义的playbackStateListener替换你原来的监听器,确保在初始化播放器时添加:

// 替换你原有的监听器添加代码
player!!.addListener(playbackStateListener)

这样就完成了!现在当播放MPD直播流时,进度条会自动切换到你想要的时间显示模式,非直播流则保持默认逻辑。

内容的提问来源于stack exchange,提问作者Caner Güneş

火山引擎 最新活动