ExoPlayer的ConcatenatingMediaSource是否为每个媒体源创建单独windowIndex?
问题核心原因
首先直接回答你的疑问:
ConcatenatingMediaSource是否为每个子媒体源创建独立Window,完全由你构造时传入的isAtomic参数决定:
- 你代码中传入的是
ConcatenatingMediaSource(true),isAtomic=true表示将整个拼接媒体源标记为原子单元,ExoPlayer会把所有子媒体源合并成单个Window暴露给上层,你无法通过windowIndex访问内部的子媒体源,这就是你seek时window对应不上的根本原因。 - 只有当
isAtomic=false时,每个子MediaSource才会对应独立的Window,支持通过windowIndex单独寻址。
你之前3个及以内媒体源时逻辑看起来正常,是因为本地存储的track.getLengthMillis()和ExoPlayer实际解析的媒体时长偏差较小,累计偏差没有触发索引错位,媒体源数量变多后偏差累计变大,就会出现跳转位置完全偏离的问题。
两种适配方案
根据你的需求(将所有音频作为单一流呈现,进度条显示总时长),更推荐第一种方案:
方案1:保留原子模式,直接使用单Window seek(最优)
因为你本来就需要把多个音频伪装成单一流,原子模式刚好匹配你的需求,完全不需要自定义复杂的window索引计算逻辑,直接修改seek代码即可:
exoPlayer.apply { pause() // 原子模式下整个拼接源是单个Window,直接传入总偏移即可,播放器内部自动处理跨轨道跳转 seekTo(newPositionMillis) play() }
这种方案无需修改媒体源构造逻辑,也不用维护本地轨道时长,完全由ExoPlayer内部处理跨轨道跳转,不会出现索引错位问题。
方案2:关闭原子模式,用Timeline计算跳转位置
如果你需要单独控制每个子轨道(比如获取当前播放的轨道信息),可以修改媒体源构造参数,同时基于ExoPlayer的Timeline计算跳转位置,避免本地元数据不准的问题:
- 修改媒体源构造,关闭原子模式:
private fun createMediaSource( tracks: List<Track> ): MediaSource = ConcatenatingMediaSource(false).apply { tracks.forEach { track -> val mediaSource = ProgressiveMediaSource .Factory(DefaultDataSourceFactory(context)) .createMediaSource(MediaItem.fromUri(track.getFullUri())) addMediaSource(mediaSource) } }
- 基于Timeline的seek逻辑:
exoPlayer.apply { val timeline = currentTimeline if (timeline.isEmpty) return@apply var remainingPosition = newPositionMillis var targetWindowIndex = 0 // 遍历Timeline中的Window获取实际时长,避免本地元数据偏差 for (i in 0 until timeline.windowCount) { val window = timeline.getWindow(i, Timeline.Window()) if (remainingPosition < window.durationMs) { targetWindowIndex = i break } remainingPosition -= window.durationMs } pause() seekTo(targetWindowIndex, remainingPosition) play() }
这种方案能保证你拿到的每个轨道时长都是ExoPlayer实际解析的结果,不会出现累计偏差导致的跳转错误。
内容的提问来源于stack exchange,提问作者Ayesh Qumhieh
相关产品推荐
相关产品推荐

