ExoPlayer 2.18.0切换轨道后isTrackSelected始终返回默认轨道问题
问题现象
参考ExoPlayer官方示例实现音轨、字幕轨、视频清晰度轨切换逻辑后,切换操作本身可正常生效,但调用trackGroup.isTrackSelected(i)获取选中轨道时,始终返回默认轨道为选中状态,无法正确识别手动切换后的选中项。
官方给出的轨道切换示例代码如下:
player.setTrackSelectionParameters( player.getTrackSelectionParameters() .buildUpon() .setOverrideForType( new TrackSelectionOverride( audioTrackGroup.getMediaTrackGroup(), /* trackIndex= */ 0)) .build());
2.18.0版本前可正常工作的旧版字幕切换代码如下:
private fun setSubTrack( trackSelector: DefaultTrackSelector?, subLanguage: String ) { trackSelector?.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(C.TRACK_TYPE_VIDEO, false) .setPreferredTextLanguage(subLanguage)) }
问题根因
代码存在3个核心问题,导致选中状态识别异常:
- 轨道索引硬编码错误:
setAudioTrack、setSubTrack方法中构造TrackSelectionOverride时,第二个参数(轨道在组内的索引)写死为0,无论用户选了哪个轨道,实际都强制选中对应组的第一个轨道,和记录的selectedPosition完全不匹配。 - 轨道组索引映射错误:遍历轨道时把所有同类型TrackGroup下的轨道平铺到同一个列表里,但选中后直接用
selectedPosition -1取audioTrackGroups/textTrackGroups里的组,没有记录每个列表项对应的「轨道组+组内索引」映射,很容易取错组,导致override设置到错误的轨道组上。 - 状态读取时机+遍历逻辑错误:
currentTracks是播放器异步更新的,设置完trackSelectionParameters后不会立刻刷新;同时遍历同类型轨道组时,只要遇到选中的轨道就覆盖selectedXxxPosition变量,遇到多轨道组场景会被后续组的状态覆盖,拿到错误的位置。
修复方案
第一步:新增数据结构记录轨道映射关系
不要用纯String列表存轨道选项,每个选项要绑定对应的TrackGroup和组内索引,避免索引错位:
// 新增轨道项数据类,存每个选项对应的组和索引 private data class TrackOption( val group: Tracks.Group?, val trackIndex: Int, // -1表示禁用,-2表示自动(仅视频) val displayName: String )
第二步:修正轨道选择弹窗的遍历逻辑
遍历轨道时逐个构造TrackOption,同时正确判断选中状态,不要硬编码索引。以音轨选择为例,改造后的核心逻辑:
fun selectAudioTrack(context: Context, player: ExoPlayer?) { player ?: return val tracks = player.currentTracks val trackOptions = mutableListOf<TrackOption>() // 添加禁用选项 trackOptions.add(TrackOption(null, -1, "Disable")) // 遍历所有音轨组 for (trackGroup in tracks.groups) { if (trackGroup.type != C.TRACK_TYPE_AUDIO) continue for (i in 0 until trackGroup.length) { if (!trackGroup.isTrackSupported(i)) continue val format = trackGroup.getTrackFormat(i) // 生成显示名称 val label = format.label?.let { " - $it" } ?: "" val lang = Locale(format.language ?: "").displayLanguage val displayName = if (lang.contains("und") || lang.contains("null")) { "Audio track #${i + 1}$label" } else { "$lang$label" } trackOptions.add(TrackOption(trackGroup, i, displayName)) } } // 找到当前选中项的位置 var selectedPos = 0 val isAudioDisabled = player.trackSelectionParameters.isTrackTypeDisabled(C.TRACK_TYPE_AUDIO) if (!isAudioDisabled) { // 遍历找当前实际选中的轨道 for ((index, option) in trackOptions.withIndex()) { if (option.group == null) continue if (option.group.isTrackSelected(option.trackIndex)) { selectedPos = index break } } } val optionNames = trackOptions.map { it.displayName }.toTypedArray<CharSequence>() var tempSelected = selectedPos MaterialAlertDialogBuilder(context) .setTitle("Audio Track") .setNeutralButton(R.string.cancel) { _, _ -> } .setPositiveButton(R.string.ok) { _, _ -> val selectedOption = trackOptions[tempSelected] setAudioTrack(player, selectedOption) } .setSingleChoiceItems(optionNames, selectedPos) { _, pos -> tempSelected = pos } .show() }
第三步:修正轨道设置方法,去掉硬编码索引
对应的setAudioTrack方法改造,直接用传入的TrackOption设置参数,不要写死trackIndex=0:
private fun setAudioTrack(player: ExoPlayer, option: TrackOption) { val paramsBuilder = player.trackSelectionParameters.buildUpon() if (option.trackIndex == -1) { // 禁用音轨 paramsBuilder.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, true) paramsBuilder.clearOverridesOfType(C.TRACK_TYPE_AUDIO) } else { paramsBuilder.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, false) // 清除同类型旧的override,避免多个override冲突 paramsBuilder.clearOverridesOfType(C.TRACK_TYPE_AUDIO) // 用option里存的正确索引设置override paramsBuilder.setOverrideForType( TrackSelectionOverride(option.group!!.mediaTrackGroup, option.trackIndex) ) } player.trackSelectionParameters = paramsBuilder.build() }
第四步:补充状态刷新时机
如果需要在切换后立刻刷新UI/选中状态,不要设置完参数马上读currentTracks,注册Player.Listener监听onTracksChanged回调,在回调触发后再读取选中状态,此时播放器已经完成轨道切换,isTrackSelected会返回正确结果:
player.addListener(object : Player.Listener { override fun onTracksChanged(tracks: Tracks) { // 在这里更新记录的selectedXxxPosition变量,刷新UI状态 } })
字幕轨、视频轨的修复逻辑和音轨完全一致:不要硬编码索引、用绑定了组和索引的实体类存选项、设置前清除同类型旧的override、在onTracksChanged回调里读选中状态即可。视频轨的Auto选项对应TrackOption的trackIndex=-2,设置时清除所有视频类型的override,让播放器自适应选择即可,不要用
setMaxVideoSizeSd()这种限制清晰度的接口。
内容的提问来源于stack exchange,提问作者Mubashir P A

