使用AndroidX Media3的ExoPlayer播放网络视频
AndroidX Media3是Google最新推出的媒体播放库,ExoPlayer是其核心播放引擎。下面详细介绍如何使用Media3库中的ExoPlayer播放网络视频。
一、环境配置
1. 添加Gradle依赖
dependencies {
// Media3 ExoPlayer核心库
implementation 'androidx.media3:media3-exoplayer:1.1.1'
// UI组件
implementation 'androidx.media3:media3-ui:1.1.1'
// HLS支持
implementation 'androidx.media3:media3-exoplayer-hls:1.1.1'
// Dash支持
implementation 'androidx.media3:media3-exoplayer-dash:1.1.1'
}
2. 添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
二、基本播放器实现
1. 布局文件 - activity_player.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:surface_type="texture_view"
app:controller_layout_id="@layout/custom_controller"
app:use_controller="true"
app:show_buffering="always" />
</androidx.constraintlayout.widget.ConstraintLayout>
2. Java代码 - PlayerActivity.java
public class PlayerActivity extends AppCompatActivity {
private PlayerView playerView;
private ExoPlayer player;
private String videoUrl = "https://your-domain.com/video.mp4";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
playerView = findViewById(R.id.player_view);
}
@Override
protected void onStart() {
super.onStart();
initializePlayer();
}
@Override
protected void onStop() {
super.onStop();
releasePlayer();
}
@Override
protected void onPause() {
super.onPause();
if (player != null) {
player.pause();
}
}
private void initializePlayer() {
// 创建ExoPlayer实例
player = new ExoPlayer.Builder(this)
.setSeekForwardIncrementMs(10000) // 前进10秒
.setSeekBackIncrementMs(10000) // 后退10秒
.setHandleAudioBecomingNoisy(true) // 音频失去焦点时暂停
.build();
// 绑定播放器到视图
playerView.setPlayer(player);
// 设置媒体源
MediaItem mediaItem = new MediaItem.Builder()
.setUri(videoUrl)
.setMimeType(MimeTypes.APPLICATION_MP4) // 可省略,自动检测
.build();
player.setMediaItem(mediaItem);
player.prepare();
player.play(); // 开始播放
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
}
}
}
三、高级功能实现
1. 自定义控制器布局
创建res/layout/custom_controller.xml
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.media3.ui.PlayerControlView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:button_fastforward="@drawable/exo_icon_fastforward"
app:button_rewind="@drawable/exo_icon_rewind"
app:show_timeout="3000">
<TextView
android:id="@+id/custom_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放中..."
android:textColor="@color/white"
android:padding="8dp"/>
</androidx.media3.ui.PlayerControlView>
2. 自适应码率支持
private void createAdaptivePlayer() {
// 创建自适应渲染器工厂
RenderersFactory renderersFactory = new DefaultRenderersFactory(this)
.forceEnableMediaCodecAdaptiveWorkaround();
// 创建带宽计估算器
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(this).build();
// 创建自适应数据源工厂
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory()
.setBandwidthMeter(bandwidthMeter);
// 创建播放器
player = new ExoPlayer.Builder(this, renderersFactory)
.setBandwidthMeter(bandwidthMeter)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(this)
.setDataSourceFactory(dataSourceFactory)
)
.build();
}
3. 播放列表支持
private void playMultipleVideos() {
List<MediaItem> mediaItems = new ArrayList<>();
mediaItems.add(new MediaItem.Builder()
.setUri("https://.../video1.mp4")
.setMediaMetadata(new MediaMetadata.Builder()
.setTitle("视频一")
.build())
.build());
mediaItems.add(new MediaItem.Builder()
.setUri("https://.../video2.mp4")
.setMediaMetadata(new MediaMetadata.Builder()
.setTitle("视频二")
.build())
.build());
player.setMediaItems(mediaItems);
player.prepare();
player.play();
// 设置自动播放下一个
player.setRepeatMode(Player.REPEAT_MODE_ALL);
}
4. HLS直播流播放
private void playHlsLiveStream() {
MediaItem mediaItem = new MediaItem.Builder()
.setUri("https://.../live.m3u8")
.setLiveConfiguration(new MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(30000) // 延迟30秒
.build())
.build();
player.setMediaItem(mediaItem);
player.prepare();
player.play();
}
5. DRM保护内容播放
private void playDrmProtectedContent() {
MediaItem mediaItem = new MediaItem.Builder()
.setUri("https://.../drm_video.mp4")
.setDrmConfiguration(new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri("https://.../license_server")
.setMultiSession(true)
.build())
.build();
player.setMediaItem(mediaItem);
player.prepare();
player.play();
}
四、监听播放事件
1. 播放状态监听
player.addListener(new Player.Listener() {
@Override
public void onPlaybackStateChanged(int playbackState) {
switch (playbackState) {
case Player.STATE_IDLE:
// 播放器空闲
break;
case Player.STATE_BUFFERING:
// 缓冲中
break;
case Player.STATE_READY:
// 准备完成,可播放
break;
case Player.STATE_ENDED:
// 播放结束
break;
}
}
@Override
public void onPlayerError(PlaybackException error) {
// 处理播放错误
if (error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED) {
showError("网络连接失败");
}
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
// 播放状态变化
}
});
2. 缓冲事件监听
player.addListener(new Player.Listener() {
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
// 播放速度变化
}
@Override
public void onPositionDiscontinuity(PositionInfo oldPosition, PositionInfo newPosition, int reason) {
// 播放位置不连续
}
});
3. 播放器统计信息
AnalyticsListener analyticsListener = new AnalyticsListener() {
@Override
public void onBandwidthEstimate(
EventTime eventTime,
int totalLoadTimeMs,
long totalBytesLoaded,
long bitrateEstimate
) {
Log.d("PlayerStats", "带宽估计: " + bitrateEstimate / 1000 + " kbps");
}
@Override
public void onVideoSizeChanged(EventTime eventTime, VideoSize videoSize) {
Log.d("PlayerStats", "视频尺寸: " + videoSize.width + "x" + videoSize.height);
}
};
player.addAnalyticsListener(analyticsListener);
五、播放器配置优化
1. 低延迟配置
private ExoPlayer createLowLatencyPlayer() {
DefaultTrackSelector trackSelector = new DefaultTrackSelector(this);
trackSelector.setParameters(
trackSelector.buildUponParameters()
.setMaxVideoSizeSd()
.setPreferredVideoMimeTypes(MimeTypes.VIDEO_H264)
.setForceLowLatency(true)
);
LoadControl loadControl = new DefaultLoadControl.Builder()
.setBufferDurationsMs(500, 2000, 500, 500) // 最小缓冲500ms
.build();
return new ExoPlayer.Builder(this)
.setTrackSelector(trackSelector)
.setLoadControl(loadControl)
.build();
}
2. 背景音频播放配置
<service
android:name="androidx.media3.session.MediaSessionService"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
private void setupMediaSession() {
MediaSession mediaSession = new MediaSession.Builder(
this, player)
.setCallback(new MediaSession.Callback() {})
.build();
player.addListener(new Player.Listener() {
@Override
public void onEvents(Player player, Player.Events events) {
if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
if (player.getPlaybackState() == Player.STATE_READY) {
mediaSession.setAvailableCommands(
mediaSession.getAvailableCommands()
.buildUpon()
.add(COMMAND_PLAY_PAUSE)
.add(COMMAND_STOP)
.build());
}
}
}
});
}
六、高级播放控制
1. 播放速度控制
// 设置播放速度
player.setPlaybackParameters(new PlaybackParameters(1.5f)); // 1.5倍速
// 显示速度控制UI
playerView.setShowFastForwardButton(true);
playerView.setShowPreviousButton(false);
playerView.setShowNextButton(false);
playerView.setShowFastForwardButton(true);
playerView.setShowRewindButton(true);
2. 视频质量选择
TrackSelectionDialogBuilder builder =
new TrackSelectionDialogBuilder(
this,
"选择视频质量",
player,
C.TRACK_TYPE_VIDEO);
builder.setTrackNameProvider(format -> {
if (format.height == Format.NO_VALUE) return "未知";
return format.height + "p | " + (format.bitrate / 1000) + "kbps";
});
builder.build().show();
3. 自定义重试策略
private void setupRetryPolicy() {
player.setRetryHandler((handler, error) -> {
int errorCode = PlaybackException.getErrorCode(error);
if (errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED) {
// 网络问题,延迟重试
return new RetryHandler.Result(true, 2000, "等待网络恢复");
} else {
// 其他错误,重试3次
if (handler.getRetryCount() < 3) {
return new RetryHandler.Result(true, 3000, "重试: " + (handler.getRetryCount() + 1));
}
}
return new RetryHandler.Result(false, "无法播放");
});
}
七、性能优化
1. 内存优化配置
private ExoPlayer createOptimizedPlayer() {
return new ExoPlayer.Builder(this)
.setUseLazyPreparation(false) // 及时准备
.setHandleAudioBecomingNoisy(true)
.setPauseAtEndOfMediaItems(true) // 播放结束时暂停
.setLoadControl(new DefaultLoadControl.Builder()
.setAllocator(new DefaultAllocator(true, 1024*1024)) // 减少内存分配
.setBufferDurationsMs(
500, 5000, // 最小缓冲
500, 500) // 最大缓冲
.build())
.build();
}
2. 后台播放优化
@Override
public void onResume() {
super.onResume();
if (player != null) {
player.setPlayWhenReady(true);
}
}
@Override
public void onStop() {
super.onStop();
if (!isChangingConfigurations()) {
releasePlayer();
}
}
八、常见问题解决
播放错误处理
错误代码 | 含义 | 解决方案 |
---|---|---|
ERROR_CODE_IO_NETWORK_CONNECTION_FAILED | 网络连接失败 | 检查网络,重试播放 |
ERROR_CODE_DECODING_FAILED | 解码失败 | 切换渲染器,尝试硬件加速 |
ERROR_CODE_PARSING_CONTAINER_MALFORMED | 格式错误 | 验证媒体格式 |
ERROR_CODE_DECODER_INIT_FAILED | 解码器初始化失败 | 检查设备支持的格式 |
ERROR_CODE_TIMEOUT | 操作超时 | 增加超时设置 |
缓存控制
// 自定义缓存实现
CacheDataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory()
.setCache(downloadCache)
.setUpstreamDataSourceFactory(httpDataSourceFactory);
// 创建带缓存的播放器
player = new ExoPlayer.Builder(this)
.setMediaSourceFactory(
new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
).build();
九、完整示例代码
高级播放器实现
public class EnhancedPlayerActivity extends AppCompatActivity {
private PlayerView playerView;
private ExoPlayer player;
private Button qualityButton;
private Button speedButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_enhanced_player);
playerView = findViewById(R.id.player_view);
qualityButton = findViewById(R.id.quality_button);
speedButton = findViewById(R.id.speed_button);
setupPlayer();
setupListeners();
}
private void setupPlayer() {
// 创建带宽计
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(this).build();
// 创建数据源工厂
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory()
.setBandwidthMeter(bandwidthMeter);
// 创建自适应渲染器工厂
RenderersFactory renderersFactory = new DefaultRenderersFactory(this)
.setEnableDecoderFallback(true);
// 创建播放器
player = new ExoPlayer.Builder(this, renderersFactory)
.setBandwidthMeter(bandwidthMeter)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(this)
.setDataSourceFactory(dataSourceFactory)
)
.build();
// 设置媒体项
MediaItem mediaItem = new MediaItem.Builder()
.setUri("https://.../video.mp4")
.setLiveConfiguration(
new MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(10000)
.build())
.build();
player.setMediaItem(mediaItem);
player.setPlayWhenReady(true);
player.prepare();
// 绑定播放器到视图
playerView.setPlayer(player);
}
private void setupListeners() {
// 视频质量选择
qualityButton.setOnClickListener(v -> {
TrackSelectionDialogBuilder builder =
new TrackSelectionDialogBuilder(this, "选择画质", player, C.TRACK_TYPE_VIDEO);
builder.build().show();
});
// 播放速度选择
speedButton.setOnClickListener(v -> {
new AlertDialog.Builder(this)
.setTitle("选择播放速度")
.setItems(new String[]{"0.5x", "1.0x", "1.5x", "2.0x"}, (dialog, which) -> {
float speed = 0.5f + 0.5f * which;
player.setPlaybackParameters(new PlaybackParameters(speed));
})
.show();
});
// 播放错误处理
player.addListener(new Player.Listener() {
@Override
public void onPlayerError(PlaybackException error) {
showErrorDialog(error);
}
});
}
private void showErrorDialog(PlaybackException error) {
int errorCode = PlaybackException.getErrorCode(error);
String message;
switch (errorCode) {
case PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED:
message = "网络连接失败,请检查网络设置";
break;
case PlaybackException.ERROR_CODE_DECODING_FAILED:
message = "视频解码失败,尝试更换播放模式";
break;
default:
message = "播放错误: " + error.getMessage();
}
new AlertDialog.Builder(this)
.setTitle("播放失败")
.setMessage(message)
.setPositiveButton("重试", (d, i) -> player.retry())
.setNegativeButton("取消", null)
.show();
}
@Override
protected void onDestroy() {
super.onDestroy();
releasePlayer();
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
}
}
}
总结
通过AndroidX Media3的ExoPlayer实现视频播放具有以下优势:
- 现代化架构:基于最新的AndroidX Media3库
- 强大功能:支持HLS/DASH/DRM等高级特性
- 高度可定制:UI、播放控制、缓存等均可定制
- 高性能:硬件解码和渲染优化
- 良好兼容性:支持广泛的媒体格式
最佳实践建议:
- 使用PlayerView作为播放器容器
- 合理管理播放器生命周期
- 实现错误处理和重试机制
- 根据应用场景优化播放配置
- 支持后台播放时使用MediaSession
通过本指南,您应该能够实现一个功能完善的网络视频播放器,支持主流的流媒体协议和高级播放功能。