17、ExoPlayer基础使用:播放本地/网络视频、自定义UI、事件监听
说实话,ExoPlayer 是我在 Android 音视频开发里用得最顺手的播放器。Google 官方出品,比 MediaPlayer 灵活太多。我记得第一次接手一个视频播放项目,产品经理要求支持多种格式、自定义控制栏、还要能监听各种播放状态。我当时第一反应就是:用 ExoPlayer 吧,省心。
这一章,我会带你从零开始,把 ExoPlayer 的基本用法捋一遍。包括怎么播本地视频、怎么播网络视频、怎么把 UI 改成你想要的样子,以及怎么监听播放器里那些关键事件。嗯,都是实战里天天要用的东西。
17.1 ExoPlayer 是什么?为什么选它?
ExoPlayer 是 Google 开源的媒体播放器库。它不像 MediaPlayer 那样是系统组件,而是跑在应用进程里的。这意味着你可以自己控制很多东西。
我个人习惯用 ExoPlayer 的原因有三:
- 支持格式多:DASH、HLS、SmoothStreaming、MP4、MKV、FLV……基本你能想到的它都支持。
- 可定制性强:UI、渲染器、解码器、加载策略,全都可以自己换。
- 更新快:Google 一直在维护,新特性、新格式支持都很及时。
我在项目中遇到过一个问题:用 MediaPlayer 播一个 HLS 流,总是卡在某个分片加载上。换成 ExoPlayer 后,调整一下加载策略,问题就解决了。说白了,ExoPlayer 给了你更多控制权。
17.2 快速集成 ExoPlayer
首先,在 build.gradle 里加上依赖:
implementation 'com.google.android.exoplayer:exoplayer:2.19.1'
如果你只需要核心功能,也可以按模块引入:
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.1'
我个人建议直接用全量依赖,省得后面缺模块再补。
17.3 播放本地视频
本地视频播放是最基础的场景。你想想看,用户从手机相册里选一个视频,或者 App 自带的 demo 视频,都需要本地播放能力。
代码其实很简单:
// 1. 创建播放器
SimpleExoPlayer player = new SimpleExoPlayer.Builder(context).build();
// 2. 创建媒体项(本地文件)
Uri uri = Uri.parse("file:///storage/emulated/0/Movies/sample.mp4");
MediaItem mediaItem = MediaItem.fromUri(uri);
// 3. 设置媒体项并准备播放
player.setMediaItem(mediaItem);
player.prepare();
player.play();
嗯,这里要注意:本地文件路径一定要有读取权限。Android 6.0 以上需要动态申请 READ_EXTERNAL_STORAGE 权限。我曾经踩过这个坑,用户反馈说视频播不了,结果发现是权限没给。
17.4 播放网络视频
网络视频播放和本地几乎一样,只是把 URI 换成网络地址:
Uri uri = Uri.parse("https://example.com/video.mp4");
MediaItem mediaItem = MediaItem.fromUri(uri);
player.setMediaItem(mediaItem);
player.prepare();
player.play();
但这里有几个坑,我帮你列一下:
- 网络权限:别忘了在 AndroidManifest 里加
INTERNET权限。 - 网络变化:WiFi 切到移动网络时,播放可能会中断。建议监听网络状态,做自动暂停或提示。
- 缓存策略:如果视频比较大,建议用
CacheDataSourceFactory做缓存,避免重复下载。 - HTTPS 问题:有些服务器证书有问题,ExoPlayer 默认会拒绝连接。这时候需要自定义
HttpDataSource.Factory。
我在项目中遇到过一个问题:用户用 4G 网络播一个 1080p 的视频,卡得不行。后来我加了码率自适应(AdaptiveTrackSelection),根据网络状况自动切换清晰度,体验就好多了。
17.5 自定义 UI
ExoPlayer 默认带了一套 UI,叫 PlayerView。但说实话,大部分项目都需要改 UI。比如换播放按钮的图标、加个倍速切换、改进度条颜色等等。
自定义 UI 的核心思路是:用 PlayerView 作为容器,然后通过 setControllerLayoutId() 替换掉默认的控制栏布局。
举个例子:
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/custom_controller" />
然后在 custom_controller.xml 里,你可以自由设计控制栏:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<ImageButton
android:id="@+id/play_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_custom_play" />
<TextView
android:id="@+id/speed_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1.0x"
android:padding="8dp" />
<SeekBar
android:id="@+id/progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
然后通过代码绑定这些控件:
PlayerView playerView = findViewById(R.id.playerView);
playerView.setPlayer(player);
// 获取自定义控制栏里的按钮
ImageButton playButton = playerView.findViewById(R.id.play_button);
playButton.setOnClickListener(v -> {
if (player.isPlaying()) {
player.pause();
} else {
player.play();
}
});
app:controller_show_timeout_ms 控制控制栏显示时长,或者用 app:controller_show_rewind_button 隐藏快退按钮。说白了,能不改布局就别改,省事。
17.6 事件监听
事件监听是播放器开发里很重要的一环。你需要知道播放器什么时候准备好了、什么时候出错了、缓冲进度是多少等等。
ExoPlayer 提供了 Player.Listener 接口,你可以重写里面的方法:
player.addListener(new Player.Listener() {
@Override
public void onPlaybackStateChanged(int playbackState) {
switch (playbackState) {
case Player.STATE_IDLE:
// 空闲状态
break;
case Player.STATE_BUFFERING:
// 缓冲中
showBuffering();
break;
case Player.STATE_READY:
// 准备就绪
hideBuffering();
break;
case Player.STATE_ENDED:
// 播放结束
onVideoEnded();
break;
}
}
@Override
public void onPlayerError(PlaybackException error) {
// 播放出错
handleError(error);
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
// 播放/暂停状态变化
updatePlayButton(isPlaying);
}
});
我在项目中遇到过一个问题:用户反馈说视频播到一半突然停了,但没报错。后来我加了 onIsPlayingChanged 监听,发现是网络波动导致播放器自动暂停了。嗯,这个监听真的很实用。
17.7 核心知识体系
下面这张图帮你梳理了 ExoPlayer 基础使用的核心逻辑:
17.8 完整示例代码
最后,给你一个完整的 Activity 示例,把上面讲的东西串起来:
public class PlayerActivity extends AppCompatActivity {
private SimpleExoPlayer player;
private PlayerView playerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
playerView = findViewById(R.id.playerView);
// 创建播放器
player = new SimpleExoPlayer.Builder(this).build();
playerView.setPlayer(player);
// 设置媒体项(网络视频)
Uri uri = Uri.parse("https://example.com/video.mp4");
MediaItem mediaItem = MediaItem.fromUri(uri);
player.setMediaItem(mediaItem);
// 添加事件监听
player.addListener(new Player.Listener() {
@Override
public void onPlaybackStateChanged(int state) {
if (state == Player.STATE_READY) {
// 准备完成,可以播放了
}
}
@Override
public void onPlayerError(PlaybackException error) {
Toast.makeText(PlayerActivity.this,
"播放出错:" + error.getMessage(), Toast.LENGTH_SHORT).show();
}
});
// 开始播放
player.prepare();
player.play();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (player != null) {
player.release();
player = null;
}
}
}
📌 核心要点回顾:
- ExoPlayer 比 MediaPlayer 更灵活,适合复杂播放场景。
- 本地和网络播放的代码几乎一样,只是 URI 不同。
- 自定义 UI 通过替换
controller_layout_id实现。 - 事件监听用
Player.Listener,可以监听状态、错误、播放变化等。 - 别忘了在
onDestroy里释放播放器,避免内存泄漏。
好了,这一章的内容就到这里。ExoPlayer 的基础用法其实不难,但细节很多。你动手写一遍,遇到问题再回来翻翻,慢慢就熟了。