如何在ExoPlayer中正确播放每3秒动态更新的m3u8直播流地址?
核心问题原因
你当前使用ConcatenatingMediaSource的逻辑是按队列顺序播放媒体源,只有前一个媒体源播放完成后才会加载下一个,并不会自动切换到新添加的有效地址,所以旧地址过期后播放器仍在请求失效的旧m3u8地址,直接触发源错误。
正确实现方案
方案1:自定义DataSource动态替换请求地址(最优,无卡顿切换)
这是最适配你的场景的方案,不需要频繁更换MediaSource,只需要在Http请求发起前动态替换最新的有效流地址即可。
- 先自定义
HttpDataSource,重写open方法,每次打开请求的时候替换成最新的流地址:
public class DynamicUrlHttpDataSource extends DefaultHttpDataSource { private Supplier<String> latestUrlSupplier; public DynamicUrlHttpDataSource(int connectTimeoutMs, int readTimeoutMs, boolean allowCrossProtocolRedirects, Supplier<String> latestUrlSupplier) { super("ExoPlayer", connectTimeoutMs, readTimeoutMs, allowCrossProtocolRedirects, null); this.latestUrlSupplier = latestUrlSupplier; } @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { // 每次发起请求前替换成最新的有效地址 String latestUrl = latestUrlSupplier.get(); DataSpec newDataSpec = new DataSpec.Builder() .setUri(latestUrl) .setPosition(dataSpec.position) .setLength(dataSpec.length) .setKey(dataSpec.key) .setFlags(dataSpec.flags) .build(); return super.open(newDataSpec); } }
- 自定义DataSource.Factory返回上面的自定义DataSource:
public class DynamicUrlHttpDataSourceFactory implements DataSource.Factory { private Supplier<String> latestUrlSupplier; public DynamicUrlHttpDataSourceFactory(Supplier<String> latestUrlSupplier) { this.latestUrlSupplier = latestUrlSupplier; } @Override public DataSource createDataSource() { return new DynamicUrlHttpDataSource(15000, 15000, true, latestUrlSupplier); } }
- 初始化播放器,用全局变量保存最新的流地址:
// 全局变量保存最新流地址 private String latestStreamUrl; // 初始化播放器逻辑 latestStreamUrl = dataItem.getVideo(); DynamicUrlHttpDataSourceFactory dataSourceFactory = new DynamicUrlHttpDataSourceFactory(() -> latestStreamUrl); HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(latestStreamUrl)); player.setMediaSource(hlsMediaSource); player.prepare(); player.setPlayWhenReady(true);
- 定时拉取新地址的时候只需要更新
latestStreamUrl变量即可,不需要操作MediaSource:
private void setLiveStreamData(String id) { Call<LiveStreamData> liveStreamDataCall = RetrofitBuilder.newCreate().getStreamLive(id); liveStreamDataCall.enqueue(new Callback<LiveStreamData>() { @Override public void onResponse(@NotNull Call<LiveStreamData> call, @NotNull Response<LiveStreamData> response) { if (response.isSuccessful() && response.body() != null) { // 只需要更新最新地址变量即可 latestStreamUrl = response.body().getUrl(); } } @Override public void onFailure(@NotNull Call<LiveStreamData> call, @NotNull Throwable t) { Log.e(TAG, "onFailure: ", t); } }); }
方案2:替换MediaSource处理(适合地址变化不频繁的场景)
如果不想自定义DataSource,也可以在获取新地址后直接替换当前播放的MediaSource,开启无缝切换配置避免卡顿:
- 初始化ExoPlayer的时候开启无缝切换:
ExoPlayer player = new ExoPlayer.Builder(context) .setSeekBackIncrementMs(0) .setSeekForwardIncrementMs(0) .build();
- 获取新地址后直接替换MediaSource:
@Override public void onResponse(@NotNull Call<LiveStreamData> call, @NotNull Response<LiveStreamData> response) { if (response.isSuccessful() && response.body() != null) { HlsMediaSource newHlsMediaSource = new HlsMediaSource.Factory(new DefaultHttpDataSourceFactory()) .createMediaSource(MediaItem.fromUri(response.body().getUrl())); // 直接替换当前播放的媒体源,第二个参数传true表示保留当前播放位置无缝切换 player.setMediaSource(newHlsMediaSource, true); } }
额外注意事项
- 不需要使用
ConcatenatingMediaSource,这个组件是用来播放多个连续的媒体片段的,完全不适合你的动态流地址更新场景 - 建议给ExoPlayer添加错误监听,遇到源错误的时候可以自动重试,避免偶发的地址更新不及时导致播放终止
内容的提问来源于stack exchange,提问作者BroscR
相关产品推荐
相关产品推荐

