深入解析Android ExoPlayer:从基础集成到高级定制开发

深入解析Android ExoPlayer:从基础集成到高级定制开发 1. ExoPlayer基础入门为什么选择它如果你正在开发一个需要播放音视频的Android应用系统自带的MediaPlayer可能第一个跳进你的脑海。但用过的人都知道它就像个固执的老头——功能有限还很难扩展。这时候ExoPlayer就像个全能选手走进球场支持DASH/HLS自适应流媒体、可以动态加载字幕、能循环播放视频片段甚至还能处理DRM加密内容。我第一次用ExoPlayer是在做一个在线教育项目时需要实现视频变速播放但保持音调不变的功能。MediaPlayer完全无能为力而ExoPlayer只需要几行代码就搞定了// 设置播放速度1.5倍速并保持音调 mPlayer.setPlaybackParameters(new PlaybackParameters(1.5f, 1f));更让人惊喜的是它的模块化设计。就像搭积木一样你可以自由组合需要的功能。比如只需要核心播放功能时引入exoplayer-core就够了能省下近200KB的体积。这是它的典型依赖配置// 基础核心库必选 implementation com.google.android.exoplayer:exoplayer-core:2.18.1 // DASH流媒体支持 implementation com.google.android.exoplayer:exoplayer-dash:2.18.1 // 默认UI控件 implementation com.google.android.exoplayer:exoplayer-ui:2.18.1注意从2.13.0版本开始包名从com.google.android.exoplayer2变回了com.google.android.exoplayer迁移时要注意修改导入语句2. 从零开始集成ExoPlayer2.1 基础播放器搭建创建一个最简单的播放器只需要三步。首先初始化PlayerView它继承了FrameLayoutcom.google.android.exoplayer2.ui.PlayerView android:idid/player_view android:layout_widthmatch_parent android:layout_height200dp/然后在Activity中配置播放器。这里有个坑要注意必须正确处理生命周期否则会导致音频后台播放异常Override protected void onStart() { super.onStart(); if (Util.SDK_INT 24) { initializePlayer(); } } Override protected void onResume() { super.onResume(); if (Util.SDK_INT 24 || player null) { initializePlayer(); } } private void initializePlayer() { SimpleExoPlayer player new SimpleExoPlayer.Builder(this).build(); playerView.setPlayer(player); // 创建媒体源这里以MP4文件为例 MediaSource mediaSource new ProgressiveMediaSource.Factory( new DefaultDataSourceFactory(this, your-user-agent)) .createMediaSource(Uri.parse(asset:///sample.mp4)); player.prepare(mediaSource); player.setPlayWhenReady(true); }2.2 处理不同视频格式ExoPlayer的强大之处在于对各种流媒体协议的支持。以下是常见格式的适配方案HLS直播流MediaSource hlsSource new HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(true) // 优化首帧速度 .createMediaSource(hlsUri);DASH自适应码率DashMediaSource dashSource new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(dataSourceFactory), dataSourceFactory) .createMediaSource(dashUri);RTMP直播需要额外扩展库implementation com.google.android.exoplayer:extension-rtmp:2.18.1实测发现对于低延迟直播3s建议开启低延迟模式DefaultLoadControl loadControl new DefaultLoadControl.Builder() .setBufferDurationsMs( 500, // 最小缓冲 3000, // 最大缓冲 500, // 开始播放缓冲 500 // 恢复播放缓冲 ).build();3. 高级定制开发技巧3.1 自定义MediaSource实现当遇到特殊协议时可以继承MediaSource实现自定义逻辑。比如我曾经对接过一个私有加密协议需要先解密才能播放public class EncryptedMediaSource extends BaseMediaSource { Override protected void prepareSourceInternal(Nullable TransferListener mediaTransferListener) { // 实现解密逻辑 byte[] decryptedData CryptoUtils.decrypt(encryptedUri); // 创建可播放的媒体源 MediaSource innerSource new ProgressiveMediaSource.Factory(...) .createMediaSource(decryptedData); // 代理给实际媒体源 innerSource.prepareSource(new DelegatingMediaSourceCaller(), mediaTransferListener); } }3.2 播放器性能优化在列表中使用多个播放器实例时内存管理尤为重要。推荐使用ExoPlayer的缓存池// 全局配置 DefaultRenderersFactory renderersFactory new DefaultRenderersFactory(this) .setEnableDecoderFallback(true); // 启用解码器降级 // 复用播放器实例 ExoPlayer player new ExoPlayer.Builder(context, renderersFactory) .setLoadControl(loadControl) .setTrackSelector(trackSelector) .build();对于4K视频播放建议开启硬件加速MediaCodecVideoRenderer videoRenderer new MediaCodecVideoRenderer( context, MediaCodecSelector.DEFAULT, allowedJoiningTimeMs, enableDecoderFallback, eventHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY) { Override protected boolean supportsFormat(MediaCodecSelector codecSelector, Format format) { // 优先使用硬件解码 return super.supportsFormat(codecSelector, format) || isHardwareAccelerated(format); } };4. 疑难问题解决方案4.1 首帧加载慢问题优化通过分析源码发现默认的DefaultLoadControl会缓冲至少500ms数据才开始播放。我们可以通过自定义LoadControl来优化DefaultLoadControl loadControl new DefaultLoadControl.Builder() .setPrioritizeTimeOverSizeThresholds(true) // 时间优先 .setBackBuffer(1000, true) // 启用回退缓冲 .setBufferDurationsMs( 100, // 最小缓冲时间原500ms 5000, 100, 100 ).build();配合预加载策略效果更佳// 在滑动列表时预加载下一个视频 recyclerView.addOnScrollListener(new OnScrollListener() { Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState SCROLL_STATE_IDLE) { int position layoutManager.findFirstVisibleItemPosition(); preloadVideo(position 1); } } });4.2 内存泄漏排查ExoPlayer如果不正确释放会导致严重的内存问题。推荐使用LifecycleObserver自动管理public class PlayerHolder implements LifecycleObserver { private SimpleExoPlayer player; OnLifecycleEvent(Lifecycle.Event.ON_STOP) void releasePlayer() { player.release(); } OnLifecycleEvent(Lifecycle.Event.ON_START) void initPlayer() { if (player null) { player new SimpleExoPlayer.Builder(context).build(); } } }对于视频封面加载建议使用ExoPlayer自带的DefaultMediaMetadataProvider替代Glide可以避免内存抖动player.addListener(new Player.Listener() { Override public void onMediaMetadataChanged(MediaMetadata metadata) { Bitmap artwork metadata.artworkData ! null ? BitmapFactory.decodeByteArray(metadata.artworkData, 0, metadata.artworkData.length) : null; coverImageView.setImageBitmap(artwork); } });