十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在 Android TV 上集成 ExoPlayer Leanback 扩展:LeanbackPlayerAdapter 原理与实战

在 Android TV 上集成 ExoPlayer Leanback 扩展:LeanbackPlayerAdapter 原理与实战 在 Android TV 上集成 ExoPlayer Leanback 扩展LeanbackPlayerAdapter 原理与实战【免费下载链接】SmartTubeBrowse media content with your own rules on Android TV项目地址: https://gitcode.com/GitHub_Trending/smar/SmartTube本指南以 ExoPlayer 2.10.6Amazon 移植版自带的extension-leanback模块为研究对象讲解如何通过LeanbackPlayerAdapter将 ExoPlayer 无缝接入 Android TV 的 Leanbackandroidx.leanback媒体播放框架。文章将覆盖依赖引入方式、核心 API 逐个剖析、底层实现原理表面绑定、进度轮询、状态桥接、错误上报并展示 SmartTube 实际项目中的集成与内存泄漏修复经验帮助读者在自有 Android TV 应用中快速搭建可播放、可控、可追踪进度的 Leanback 播放器。一、模块概览Leanback 扩展在项目中的定位在exoplayer-amzn-2.10.6仓库中Leanback 扩展位于 extensions/leanback是一个独立的 Android Library 模块其包名为com.google.android.exoplayer2.ext.leanback对应 AndroidManifest 可见 src/main/AndroidManifest.xml。该模块的核心价值在于Leanback 的媒体播放体系PlayerAdapter、PlaybackGlueHost、VideoPlayerGlue等并不认识 ExoPlayer 的Player接口而 ExoPlayer 也没有原生适配 Leanback 播放控制 UI。扩展提供的LeanbackPlayerAdapter正是两者之间的桥梁——它实现 Leanback 的PlayerAdapter抽象类内部持有并驱动一个 ExoPlayerPlayer实例从而让 Leanback 的播放控制行、播放器片段PlaybackFragment可以直接操作 ExoPlayer 完成播放、暂停、seek、进度上报等全部交互。从模块依赖关系看见 extensions/leanback/build.gradle它同时依赖:library-coreExoPlayer 核心库androidx.leanback:leanback:1.0.0Leanback 框架本身本仓库还额外注入了:leanback-1.0.0本地源码模块位于仓库根目录 leanback-1.0.0是 Leanback 源码的本地副本。换言之这个扩展只做一件事把 ExoPlayer 的播放能力适配成 Leanback 期望的PlayerAdapter形态它是Leanback UI ExoPlayer 内核这一组合的关键粘合层。二、获取与引入扩展两种接入方式2.1 通过 Gradle 依赖引入推荐扩展的官方使用方式是作为 Gradle 依赖引入implementation com.google.android.exoplayer:extension-leanback:2.X.X其中2.X.X必须与项目所用的 ExoPlayer 核心库版本完全一致。在本仓库中对应版本为 2.10.6见 constants.gradle 的releaseVersion 2.10.6。需要特别说明exoplayer-amzn-2.10.6是 ExoPlayer 的 Amazon 设备移植版见 exoplayer-amzn-2.10.6/README.md因此其发布坐标与 Google 官方版本不同。在 SmartTube 的 smarttubetv/build.gradle 中可以看到三种候选方式// 方式一本地源码模块当前实际采用 implementation project(:exoplayer-extension-leanback) // 方式二Amazon 移植版的 Maven 坐标被注释 // implementation com.amazon.android:extension-leanback: amazonExoplayerVersion // 方式三Google 官方 Maven 坐标被注释 // implementation com.google.android.exoplayer:extension-leanback: exoplayerVersion对于普通开发者的理解要点是依赖坐标必须与 ExoPlayer 核心库来源一致混用不同发行渠道的坐标会导致类版本冲突。2.2 通过本地源码模块引入仓库内做法如果你拿到的是本仓库这样的 ExoPlayer 源码工程可以不做任何远程依赖直接在settings.gradle中把模块纳入构建再以project()方式引用settings.gradle 的include列表中已包含:exoplayer-extension-leanback模块名前缀由core_settings.gradle中的modulePrefix机制拼接见 core_settings.gradle 与 core_settings.gradle后者将模块映射到extensions/leanback目录应用模块中implementation project(:exoplayer-extension-leanback)即可直接使用。这种本地模块方式便于针对源码做定制修改也是 SmartTube 实际采用的方式。三、核心类 LeanbackPlayerAdapter一次完整的源码走读扩展模块的全部业务逻辑集中在唯一一个公开类中LeanbackPlayerAdapter.java。它继承androidx.leanback.media.PlayerAdapter并实现Runnable内部通过一个ComponentListener同时实现Player.EventListener、SurfaceHolder.Callback、VideoListener监听 ExoPlayer 与 Surface 的全部事件。3.1 构造与三要素Context、Player、updatePeriodMspublic LeanbackPlayerAdapter(Context context, Player player, final int updatePeriodMs)构造函数接收三个参数context当前 Activity 上下文player需要接入 Leanback 的 ExoPlayer 实例updatePeriodMs播放控制 UI 的进度刷新周期毫秒。源码注释明确强调PlayerAdapter不管理Player的生命周期释放 Player 的责任仍在调用方LeanbackPlayerAdapter.java。这一点在集成时容易被忽略——销毁播放器时若只释放 Adapter 而不释放 Player会造成泄漏。3.2 可选配置项三个 Setter除构造外Adapter 还提供三个可选配置入口setPlaybackPreparer(PlaybackPreparer)设置播放准备器。当播放器处于STATE_IDLE且用户按下播放键时play()会回调playbackPreparer.preparePlayback()来触发媒体准备见下文play()分析setControlDispatcher(ControlDispatcher)替换控制分发器默认使用DefaultControlDispatcher传null也会回落到默认实现LeanbackPlayerAdapter.java。ControlDispatcher是 ExoPlayer 2.x 中拦截并重新分发播放控制命令的扩展点可用于拦截 seek、播放暂停等指令setErrorMessageProvider(ErrorMessageProvider? super ExoPlaybackException)设置错误信息提供器将ExoPlaybackException映射为(错误码, 错误描述)二元组。不设置时使用 Leanback 默认文案R.string.lb_media_player_errorLeanbackPlayerAdapter.java。3.3 状态桥接把 ExoPlayer 状态翻译成 Leanback 状态PlayerAdapter要求子类实现一组状态查询方法这些方法全部直接委托给内部PlayerAdapter 方法委托逻辑说明isPlaying()playbackState ! IDLE playbackState ! ENDED getPlayWhenReady()是否正在播放getDuration()player.getDuration()TIME_UNSET时返回-1媒体总时长getCurrentPosition()处于IDLE返回-1否则返回当前位置当前进度getBufferedPosition()player.getBufferedPosition()缓冲位置isPrepared()非IDLE且无 Surface 宿主或已有 Surface是否可播放isPrepared()的实现细节值得注意它不仅要求播放器离开IDLE状态还要求Surface 已经就绪surfaceHolderGlueHost null || hasSurface。也就是说在 Leanback 体系里已准备 数据就绪 渲染表面就绪避免出现有声音无画面的播放状态。3.4 播放控制play / pause / seekToOverride public void play() { if (player.getPlaybackState() Player.STATE_IDLE) { if (playbackPreparer ! null) { playbackPreparer.preparePlayback(); } } else if (player.getPlaybackState() Player.STATE_ENDED) { controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); } if (controlDispatcher.dispatchSetPlayWhenReady(player, true)) { getCallback().onPlayStateChanged(this); } }play()覆盖了三种场景从未播放IDLE通过PlaybackPreparer触发preparePlayback()即先准备后播放的懒加载模式已播完ENDED先 seek 回起点C.TIME_UNSET表示播放窗口起点再播放实现重播正常暂停中直接置playWhenReady true。所有播放/暂停/seek 操作都经由ControlDispatcher分发而非直接调用player方法这正是前文提到的控制拦截扩展点的生效位置。pause()和seekTo()同理分别调用dispatchSetPlayWhenReady(player, false)与dispatchSeekTo(...)LeanbackPlayerAdapter.java。3.5 生命周期挂钩attach / detachonAttachedToHost()在 Adapter 挂到 Leanback 的PlaybackGlueHost时被调用若宿主实现了SurfaceHolderGlueHost则把ComponentListener注册为SurfaceHolder.Callback接管视频表面向player.addListener(componentListener)注册播放器事件监听若 Player 存在VideoComponent再注册VideoListener用于视频尺寸与首帧回调。onDetachedFromHost()做完全对称的清理移除所有监听器、解除 Surface 回调、重置hasSurface false并回调 Leanback 侧重置缓冲与播放状态LeanbackPlayerAdapter.java。四、底层机制详解三个关键实现点4.1 Surface 绑定SurfaceHolder.Callback 驱动视频渲染表面通过SurfaceHolderGlueHost注入Override public void surfaceCreated(SurfaceHolder surfaceHolder) { setVideoSurface(surfaceHolder.getSurface()); } Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { setVideoSurface(null); }setVideoSurface()内部维护hasSurface标志并把 Surface 交给player.getVideoComponent().setVideoSurface(surface)同时触发maybeNotifyPreparedStateChanged()LeanbackPlayerAdapter.java。这正是 3.3 节isPrepared()依赖hasSurface的原因——Surface 创建完成才会通知 Leanback 已准备。4.2 进度轮询Runnable Handler 的定时刷新Leanback 的播放控制行需要周期性刷新进度条而 ExoPlayer 不会主动推送位置变化因此 Adapter 采用定时轮询策略Override public void run() { if (!hasSurface) { // MOD: mem leak fix (SegmentTimelineElement) return; } Callback callback getCallback(); callback.onCurrentPositionChanged(this); callback.onBufferedPositionChanged(this); handler.postDelayed(this, updatePeriodMs); }setProgressUpdatingEnabled(true)时启动轮询handler.post(this)每次run()回调 Leanback 的当前位置变化与缓冲位置变化然后用postDelayed安排下一次刷新周期即构造参数updatePeriodMssetProgressUpdatingEnabled(false)时调用handler.removeCallbacks(this)停止轮询。注意此处源码中带有MOD: mem leak fix注释的一行——当 Surface 已被销毁如播放器重建时直接跳过刷新与重排避免在不可见状态下继续积累消息这是 SmartTube 对原版 ExoPlayer 的定制改动痕迹LeanbackPlayerAdapter.java。4.3 事件桥接ComponentListener 的三重身份ComponentListener同时扮演三个角色把三类事件统一翻译为PlayerAdapter.Callback回调Player.EventListeneronPlayerStateChanged触发notifyStateChanged()统一通知准备状态、播放状态、缓冲状态若播放结束还通知onPlayCompletedonTimelineChanged通知时长与位置变化onPositionDiscontinuity通知位置跳变onPlayerError经ErrorMessageProvider或默认文案上报错误LeanbackPlayerAdapter.javaSurfaceHolder.Callback负责表面创建/销毁见 4.1VideoListeneronVideoSizeChanged通知 Leanback 视频尺寸变化用于画面宽高比适配LeanbackPlayerAdapter.java。notifyStateChanged()还承担播完状态的通知当播放状态进入STATE_ENDED时调用callback.onPlayCompleted(this)让 Leanback 播放控制行正确显示重播按钮等结束态 UI。五、SmartTube 实战集成从 Adapter 到 PlayerGlue 的完整链路SmartTubeAndroid TV 上的媒体浏览应用主模块为 smarttubetv在 PlaybackFragment.java 中完整演示了该扩展的生产级用法。5.1 组装播放器与 Adapter// createPlayerGlue() PlayerAdapter playerAdapter new LeanbackPlayerAdapter(getContext(), mPlayer, UPDATE_DELAY_MS); OnActionClickedListener playerActionListener new PlayerActionListener(); mPlayerGlue new VideoPlayerGlue(getContext(), playerAdapter, playerActionListener); mPlayerGlue.setHost(new SurfacePlaybackFragmentGlueHost(this)); mPlayerGlue.setSeekEnabled(true); mPlayerGlue.setControlsOverlayAutoHideEnabled(false);链路如下PlaybackFragment.java构造LeanbackPlayerAdapter传入 ExoPlayermPlayer与刷新周期UPDATE_DELAY_MS将 Adapter 包装进自研的VideoPlayerGlue继承自仓库内 MaxControlsVideoPlayerGlue后者对播放控制行的按键与焦点做了定制通过SurfacePlaybackFragmentGlueHost继承 Leanback 的PlaybackSupportFragmentGlueHost见 SurfacePlaybackFragmentGlueHost.java把控制 UI 与视频 Surface 一并挂载到播放 Fragment。5.2 内存泄漏与重建问题的实战修复SmartTube 的集成代码中反复出现针对 Adapter 生命周期的修复注释这是真实项目中极易踩坑的地方值得重点吸收修复一销毁时先断开 Adapter 再释放 Player// destroyPlayerObjects() setAdapter(null); // PlayerGlue-LeanbackPlayerAdapter-Context memory leak fix ... if (mPlayerGlue ! null) { mPlayerGlue.getPlayerAdapter().onDetachedFromHost(); mPlayerGlue.setHost(null); mPlayerGlue null; }如 PlaybackFragment.java 所示销毁顺序严格为先setAdapter(null)摘除 Adapter → 再显式调用onDetachedFromHost()→ 清空宿主引用 → 最后释放 Player 与其他对象。由于 Adapter 持有Context且内部 Handler 会持续 post 消息若不按此顺序清理会导致PlayerGlue → LeanbackPlayerAdapter → Context链路的泄漏。修复二Surface 销毁后停止进度轮询LeanbackPlayerAdapter.run()中if (!hasSurface) return;的定制逻辑见 4.2 节正是针对播放器重建后 SegmentTimelineElement 累积数万次分配这一线上问题的修复Surface 不存在时直接终止轮询链避免无意义的回调与消息堆积。修复三正确管理 Player 生命周期再次强调LeanbackPlayerAdapter不负责释放 Player。SmartTube 在destroyPlayerObjects()中通过mPlayerInitializer.release()、mExoPlayerController.release()等专门逻辑释放 Player正是对这一约定的落实。六、注意事项与最佳实践小结版本严格匹配extension-leanback的版本必须与 ExoPlayer 核心库一致且来源Google 官方 / Amazon 移植版 / 本地源码也要一致避免类冲突谁创建谁释放Player 的生命周期归调用方管理销毁播放页面时务必在释放 Player 前调用onDetachedFromHost()并断开 Glue 与 Adapter 的引用善用 ControlDispatcher如需拦截或改造播放控制行为如广告插播时禁用 seek通过setControlDispatcher实现而不是子类化 Adapter错误文案定制通过setErrorMessageProvider把ExoPlaybackException映射为符合自己产品语境的错误提示否则使用 Leanback 默认文案进度刷新周期取舍updatePeriodMs决定控制条进度精度与刷新开销的平衡Android TV 遥控器交互场景下宜选择 100~500ms 量级的适中值SmartTube 以UPDATE_DELAY_MS常量统一管理源码是最好的老师若需要深度定制可直接在仓库内阅读 LeanbackPlayerAdapter.java 全文仅 325 行结合 LeanbackPlayerAdapter 的调用示例 理解每个回调的真实触发时机。七、小结extension-leanback通过一个约三百行的LeanbackPlayerAdapter完整解决了ExoPlayer 内核 × Leanback UI之间的适配问题状态查询、播放控制、Surface 绑定、进度轮询、错误上报五大职责全部收敛于此类中。对于 Android TV 应用开发者掌握其构造三要素、三个可选配置点以及attach/detach生命周期契约再参考 SmartTube 的生产级集成与泄漏修复经验即可在自己项目中稳定地构建出基于 ExoPlayer 的 Leanback 播放体验。【免费下载链接】SmartTubeBrowse media content with your own rules on Android TV项目地址: https://gitcode.com/GitHub_Trending/smar/SmartTube创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表