1. 准备工作
屏幕截图:YouTube Android 版应用
ExoPlayer 是一款基于 Android 中的低层级媒体 API 构建的应用级媒体播放器。与 Android 内置的 MediaPlayer 相比,ExoPlayer 具有多项优势。它支持 MediaPlayer 支持的许多媒体格式,还支持 DASH 和 SmoothStreaming 等自适应格式。ExoPlayer 具有高度的可定制性和可扩展性,因此能够用于许多高级用例。它是 Google 应用(包括 YouTube 和 Google Play 影视)使用的开源项目。
前提条件
- 适度了解 Android 开发和 Android Studio
实践内容
- 创建一个
SimpleExoPlayer
实例,用于准备和播放来自各种来源的媒体。 - 将 ExoPlayer 与应用的 activity 生命周期集成,以在单窗口或多窗口环境中支持后台运行、前台运行和继续播放媒体内容功能。
- 使用
MediaItem
创建播放列表。 - 播放自适应视频串流(根据可用带宽调整媒体质量)。
- 注册事件监听器,监听播放状态并展示如何使用监听器来衡量播放质量。
- 使用标准 ExoPlayer 界面组件,然后根据应用的样式对其进行自定义。
所需条件
- 最新的稳定版 Android Studio。
- 搭载 JellyBean (4.1) 或更高版本的 Android 设备(最好是 Nougat [7.1] 或更高版本,因为这些版本支持多窗口)。
2. 进行设置
获取代码
首先,请下载 Android Studio 项目:
或者,您也可以克隆 GitHub 代码库:
git clone https://github.com/googlecodelabs/exoplayer-intro.git
目录结构
通过克隆或解压缩,您会得到一个根文件夹 (exoplayer-intro
),其中包含一个 Gradle 项目以及多个模块(一个应用模块,以及此 Codelab 的每个步骤的模块),还有您需要的所有资源。
导入项目
- 启动 Android Studio。
- 依次选择 File > New > Import Project。
- 选择根
build.gradle
文件。
屏幕截图:导入时的项目结构
构建完成后,您会看到 6 个模块:app
模块(类型为“application”),以及 5 个名为 exoplayer-codelab-N
的模块(其中的 N
为 00
到 04,
,类型均为“library”)。app
模块实际上是空的,其中只包含一个清单。当使用 app/build.gradle
中的 Gradle 依赖项构建应用时,当前指定的 exoplayer-codelab-N
模块中的所有内容都会被合并。
app/build.gradle
dependencies {
implementation project(":exoplayer-codelab-00")
}
媒体播放器 activity 保存在 exoplayer-codelab-N
模块中。之所以将其保存在单独的库模块中,是为了让您可以在针对不同目标平台(例如,移动设备和 Android TV)的 APK 之间共享它。它还允许您充分利用各种功能,例如 Dynamic Delivery。使用 Dynamic Delivery 功能后,只有当用户需要时,系统才允许安装您的媒体播放功能。
- 部署并运行应用,以检查是否一切正常。应用应该以黑色为背景填充屏幕。
屏幕截图:空白应用正在运行
3. 流式传输!
添加 ExoPlayer 依赖项
ExoPlayer 是一个托管在 GitHub 上的开源项目。每个版本均通过 Google Maven 进行分发,后者是 Android Studio 和 Gradle 使用的默认软件包代码库之一。每个版本均以采用如下格式的字符串作为唯一标识:
com.google.android.exoplayer:exoplayer:X.X.X
您只需导入 ExoPlayer 的类和界面组件,即可将 ExoPlayer 添加到您的项目。它非常小巧,其收缩占用空间约为 70-300 kB,具体大小取决于包含的功能和支持的格式。ExoPlayer 库分为不同的模块,开发者只需导入自己需要的功能。如需详细了解 ExoPlayer 的模块化结构,请参阅添加 ExoPlayer 模块。
- 打开
player-lib
模块的build.gradle
文件。 - 将以下代码行添加到
dependencies
部分并同步项目。
exoplayer-codelab-00/build.gradle
dependencies {
[...]
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'
}
添加 PlayerView element
- 打开
exoplayer-codelab-00
模块中的布局资源文件activity_player.xml
。 - 将光标放在
FrameLayout
元素内。 - 开始输入
<PlayerView
,并让 Android Studio 自动补全PlayerView
元素。 - 对
width
和height
使用match_parent
。 - 将 ID 声明为
video_view
。
activity_player.xml
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
从现在起,您要引用该界面元素作为视频视图。
- 在
PlayerActivity
中,您现在可以获取对在您刚才修改的 XML 文件中创建的视图树的引用。
PlayerActivity.kt
private val viewBinding by lazy(LazyThreadSafetyMode.NONE) {
ActivityPlayerBinding.inflate(layoutInflater)
}
- 将视图树的根目录设为您的 activity 的内容视图。此外,还要检查
viewBinding
引用上的videoView
属性是否可见,以及其类型是否为PlayerView
。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)
}
创建 ExoPlayer
若要播放流媒体,您需要一个 ExoPlayer
对象。若要创建该对象,最简单的方法是使用 SimpleExoPlayer.Builder
类。顾名思义,就是使用构建器模式来构建 SimpleExoPlayer
实例。
SimpleExoPlayer
是 ExoPlayer
接口的一个方便、通用的实现。
添加私有方法 initializePlayer
来创建您的 SimpleExoPlayer
。
PlayerActivity.kt
private var player: SimpleExoPlayer? = null
[...]
private fun initializePlayer() {
player = SimpleExoPlayer.Builder(this)
.build()
.also { exoPlayer ->
viewBinding.videoView.player = exoPlayer
}
}
根据您的上下文创建 SimpleExoPlayer.Builder
,然后调用 build
来创建 SimpleExoPlayer
对象。然后,系统会将其分配给 player
,您需要将其声明为成员字段。然后,您可以使用 viewBinding.videoView.player
可变属性将 player
绑定到其对应视图。
创建媒体项
现在,player
需要一些可以播放的内容。为此,您要创建一个 MediaItem
。有许多不同类型的 MediaItem
,但您首先要针对互联网上的 MP3 文件创建一个。
若要创建 MediaItem
,最简单的方法是使用 MediaItem.fromUri
,后者会接受媒体文件的 URI。使用 player.setMediaItem
将 MediaItem
添加到 player
。
- 将以下代码添加到
also
块中的initializePlayer
:
PlayerActivity.kt
private fun initializePlayer() {
[...]
.also { exoPlayer ->
[...]
val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
exoPlayer.setMediaItem(mediaItem)
}
}
请注意,R.string.media_url_mp3
在 strings.xml
中定义为 https://storage.googleapis.com/exoplayer-test-media-0/play.mp3。
根据 activity 生命周期实现精细播放
我们的 player
可能会占用大量资源,包括内存、CPU、网络连接和硬件编解码器。其中许多资源都很短缺,尤其是对于硬件编解码器(可能只有一个)来说更是如此。当您不使用这些资源时(例如,当您的应用已置于后台时),请务必释放这些资源以供其他应用使用。
换句话说,播放器的生命周期应该与应用的生命周期相关联。若要实现这一点,您需要替换 PlayerActivity
的 4 个方法:onStart
、onResume
、onPause
和 onStop
。
- 在
PlayerActivity
处于打开状态时,依次点击 Code menu > Override methods…。 - 选择
onStart
、onResume
、onPause
和onStop
。 - 根据 API 级别,在
onStart
或onResume
回调中初始化播放器。
PlayerActivity.kt
public override fun onStart() {
super.onStart()
if (Util.SDK_INT >= 24) {
initializePlayer()
}
}
public override fun onResume() {
super.onResume()
hideSystemUi()
if ((Util.SDK_INT < 24 || player == null)) {
initializePlayer()
}
}
Android API 级别 24 及更高版本支持多窗口。由于您的应用在分屏模式下可见,但不处于活动状态,因此您需要在 onStart
中初始化播放器。由于 Android API 级别 24 及更低版本要求您尽可能多等些时间,直到您获取到资源为止,因此您要等到 onResume
再初始化播放器。
- 添加
hideSystemUi
方法。
PlayerActivity.kt
@SuppressLint("InlinedApi")
private fun hideSystemUi() {
viewBinding.videoView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}
hideSystemUi
是在 onResume
中调用的辅助方法,借助该方法,您可以实现全屏体验。
- 在
onPause
和onStop
中使用releasePlayer
(稍后就会创建)释放资源。
PlayerActivity.kt
public override fun onPause() {
super.onPause()
if (Util.SDK_INT < 24) {
releasePlayer()
}
}
public override fun onStop() {
super.onStop()
if (Util.SDK_INT >= 24) {
releasePlayer()
}
}
对于 API 级别 24 及更低版本,系统并不一定会调用 onStop
,因此您必须尽早在 onPause
中释放播放器。对于 API 级别 24 及更高版本(具备多窗口模式和分屏模式),系统一定会调用 onStop
。在暂停状态下,您的 activity 仍然可见,因此您要等到 onStop
再释放播放器。
您现在需要创建一个 releasePlayer
方法,用于释放播放器的资源并销毁播放器。
- 将以下代码添加到相应 activity:
PlayerActivity.kt
private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition = 0L
[...]
private fun releasePlayer() {
player?.run {
playbackPosition = this.currentPosition
currentWindow = this.currentWindowIndex
playWhenReady = this.playWhenReady
release()
}
player = null
}
在释放和销毁播放器之前,请存储以下信息:
- 使用
playWhenReady
存储播放/暂停状态。 - 使用
currentPosition
存储当前播放位置。 - 使用
currentWindowIndex
存储当前窗口索引。如需详细了解相关窗口,请参阅时间轴。
这样一来,您即可从用户停止播放的位置继续播放。您需要做的就是在初始化播放器时提供这些状态信息。
最终准备
现在,您要做的就是在初始化期间将您保存在 releasePlayer
中的状态信息提供给播放器。
- 将以下代码添加到
initializePlayer
中:
PlayerActivity.kt
private fun initializePlayer() {
[...]
exoPlayer.playWhenReady = playWhenReady
exoPlayer.seekTo(currentWindow, playbackPosition)
exoPlayer.prepare()
}
此时会发生下列情况:
playWhenReady
告知播放器是否在获取所有播放资源后立即开始播放。由于playWhenReady
最初为true
,因此在第一次运行应用时,播放会自动开始。seekTo
告知播放器在特定窗口中寻找某个位置。currentWindow
和playbackPosition
都初始化为零,以便在应用第一次运行时从头开始播放。prepare
告知播放器获取播放所需的所有资源。
播放音频
最后,一切就绪!启动应用即可播放 MP3 文件并看到嵌入的海报图片。
屏幕截图:应用正在播放一个曲目。
测试 activity 生命周期
测试应用在 activity 生命周期的所有不同状态下是否都能正常运行。
- 启动另一个应用,然后重新将您的应用置于前台。您的应用是否在正确的位置继续播放?
- 暂停应用,将其移到后台,然后重新移到前台。在移到后台进入暂停状态后,它是否始终保持暂停状态?
- 旋转应用。如果您将屏幕方向从竖屏改为横屏,然后再改回来,它的行为是怎样的?
播放视频
如果要播放视频,只需将媒体项 URI 修改为 MP4 文件即可。
- 将
initializePlayer
中的 URI 更改为R.string.media_url_mp4
。 - 重新启动应用,同样在视频播放中将应用移到后台,并测试这之后的行为。
PlayerActivity.kt
private fun initializePlayer() {
[...]
val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));
[...]
}
PlayerView
会完成所有相关操作。视频(而非海报图片)会全屏呈现。
屏幕截图:应用正在播放视频。
真厉害!您刚刚制作了一款能够在 Android 上全屏流式传输媒体的应用,以及配套的生命周期管理、保存状态和界面控件!
4. 创建播放列表
您当前的应用可以播放单个媒体文件,但如果您想让多个媒体文件相继播放,该怎么办呢?为此,您需要使用播放列表。
如需创建播放列表,您可以使用 addMediaItem
向 player
添加多个 MediaItem
。这样可以实现无缝播放,并且系统会在后台处理缓冲,因此在更改媒体项时,用户不会看到“正在缓冲”旋转图标。
- 将以下代码添加到
initializePlayer
:
PlayerActivity.kt
private void initializePlayer() {
[...]
exoPlayer.addMediaItem(mediaItem) // Existing code
val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3));
exoPlayer.addMediaItem(secondMediaItem);
[...]
}
检查播放器控件的行为方式。您可以使用 和 在媒体项序列中跳转。
屏幕截图:显示“下一个”和“上一个”按钮的播放控件
5. 自适应流式传输
自适应流式传输是一种媒体流式传输技术,可根据可用网络带宽改变串流质量。借助该技术,用户可体验其带宽支持的最高质量的媒体内容。
通常,同一媒体内容会分为多个轨道,其质量(比特率和分辨率)各有不同。播放器会根据可用的网络带宽来选择轨道。
每个轨道会分成给定时长的数据块,其时长通常在 2 到 10 秒之间。这样一来,播放器就可以随着可用带宽的变化快速切换不同轨道。播放器负责将这些数据块拼接在一起以实现无缝播放。
自适应轨道选择
自适应流式传输的核心是为当前环境选择最合适的轨道。您可以使用自适应轨道选择功能将您的应用更新为播放流媒体。
- 请使用以下代码更新
initializePlayer
:
PlayerActivity.kt
private fun initializePlayer() {
val trackSelector = DefaultTrackSelector(this).apply {
setParameters(buildUponParameters().setMaxVideoSizeSd())
}
player = SimpleExoPlayer.Builder(this)
.setTrackSelector(trackSelector)
.build()
[...]
}
首先,创建一个 DefaultTrackSelector
,它将负责选择媒体项中的轨道。然后,告知 trackSelector
只选择标准清晰度或更低清晰度的轨道,这是以牺牲质量为代价节省用户流量的好方法。最后,将 trackSelector
传递给构建器,以便在构建 SimpleExoPlayer
实例时使用。
构建自适应 MediaItem
DASH 是一种广泛使用的自适应流式传输格式。如需流式传输 DASH 内容,您需要像以前一样创建 MediaItem
。不过,这一次,我们必须使用 MediaItem.Builder
,而不是 fromUri
。
这是因为 fromUri
使用文件扩展名来确定底层媒体格式,但 DASH URI 没有文件扩展名,因此我们在构造 MediaItem
时必须提供 APPLICATION_MPD
的 MIME 类型。
- 更新
initializePlayer
,具体代码如下所示:
PlayerActivity.kt
private void initializePlayer() {
[...]
// Replace this line
val mediaItem = MediaItem.fromUri(getString(R.string.media_url_mp4));
// With this
val mediaItem = MediaItem.Builder()
.setUri(getString(R.string.media_url_dash))
.setMimeType(MimeTypes.APPLICATION_MPD)
.build()
// Also remove the following lines
val secondMediaItem = MediaItem.fromUri(getString(R.string.media_url_mp3))
exoPlayer.addMediaItem(secondMediaItem)
}
- 重启应用,并查看包含 DASH 的自适应视频串流的实际效果。借助 ExoPlayer,您可以轻松实现这项操作!
其他自适应流式传输格式
其他常用的自适应流式传输格式为 HLS (MimeTypes.APPLICATION_M3U8
) 和 SmoothStreaming (MimeTypes.APPLICATION_SS
),二者均受 ExoPlayer 支持。如需详细了解其他自适应媒体来源的构造方式,请参阅 ExoPlayer 演示版应用。
6. 监听事件
在前面的步骤中,您学习了如何流式传输渐进式媒体串流和自适应媒体串流。ExoPlayer 会在幕后为您处理大量工作,其中包括以下各项:
- 分配内存
- 下载容器文件
- 从容器中提取元数据
- 解码数据
- 向屏幕和扬声器呈现视频、音频和文本
有时,了解 ExoPlayer 在运行时执行的操作有助于了解和改进用户的播放体验。
例如,您可能想要通过执行以下操作来反映界面中的播放状态变化:
- 在播放器进入缓冲状态时显示“正在加载”旋转图标
- 在轨道播放结束后显示叠加层和“接下来观看”选项
ExoPlayer 提供了几个监听器接口,用于提供对有用事件的回调。您可以使用监听器来记录播放器所处的状态。
监听
- 在
PlayerActivity
类之外创建一个TAG
常量(后面您将使用该常量进行日志记录)。
PlayerActivity.kt
private const val TAG = "PlayerActivity"
- 在
PlayerActivity
类之外的工厂函数中实现Player.EventListener
接口。这用于通知您重要的播放器事件,其中包括错误和播放状态变化。 - 添加以下代码,替换
onPlaybackStateChanged
:
PlayerActivity.kt
private fun playbackStateListener() = object : Player.EventListener {
override fun onPlaybackStateChanged(playbackState: Int) {
val stateString: String = when (playbackState) {
ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE -"
ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY -"
ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED -"
else -> "UNKNOWN_STATE -"
}
Log.d(TAG, "changed state to $stateString")
}
}
- 在
PlayerActivity
中声明一个Player.EventListener
类型的私有成员。
PlayerActivity.kt
class PlayerActivity : AppCompatActivity() {
[...]
private val playbackStateListener: Player.EventListener = playbackStateListener()
}
当播放状态发生变化时,系统会调用 onPlaybackStateChanged
。新状态由 playbackState
参数提供。
播放器可能会处于以下 4 种状态之一:
状态 | 说明 |
| 播放器已实例化,但尚未准备就绪。 |
| 播放器无法从当前位置开始播放,因为已缓冲的数据不足。 |
| 播放器可以立即从当前位置开始播放。这意味着如果播放器的 playWhenReady 属性为 |
| 播放器已完成媒体播放。 |
注册监听器
如需调用您的回调,您需要向播放器注册 playbackStateListener
。该操作可在 initializePlayer
中完成。
- 请在播放准备就绪之前注册该监听器。
PlayerActivity.kt
private void initializePlayer() {
[...]
exoPlayer.seekTo(currentWindow, playbackPosition)
exoPlayer.addListener(playbackStateListener)
[...]
}
同样,您需要进行整理以避免来自播放器的悬空引用,这可能会导致内存泄漏。
- 在
releasePlayer
中移除该监听器:
PlayerActivity.kt
private void releasePlayer() {
player?.run {
[...]
removeListener(playbackStateListener)
release()
}
player = null
}
- 打开 logcat 并运行应用。
- 使用界面控件对播放进行跳转、暂停和恢复。您应该会在日志中看到播放状态的变化。
继续深入
ExoPlayer 提供了许多其他监听器,有助于您了解用户的播放体验。其中包括音频和视频的监听器,以及 AnalyticsListener
(其中包含来自所有监听器的回调)。以下是一些最重要的方法:
- 当视频的第一帧呈现时,系统会调用
onRenderedFirstFrame
。根据这项信息,您可以计算用户必须等待多长时间才能在屏幕上看到有意义的内容。 - 当视频丢帧时,系统会调用
onDroppedVideoFrames
。丢帧表示播放不流畅,且用户体验可能很差。 - 当发生音频欠载时,系统会调用
onAudioUnderrun
。欠载会导致出现声音故障,并且比视频丢帧更明显。
可以使用 addAnalyticsListener
将 AnalyticsListener
添加到 player
。音频和视频的监听器也有对应的方法。
您需要考虑哪些事件对您的应用和用户很重要。如需了解详情,请参阅监听播放器事件。事件监听器就讲到这里!
7. 自定义界面
到目前为止,您一直在使用 ExoPlayer 的 PlayerControlView
向用户显示播放控制器。
屏幕截图:默认播放控制器
如果您想更改这些控件的功能或外观,该怎么办?幸运的是,这些控件是高度可定制的。
第一项简单的自定义是完全不使用控制器。这很容易实现,只需在 activity_player.xml
中的 PlayerView
元素上使用 use_controller
属性即可。
- 将
use_controller
设为false
,控件就不会再显示了:
activity_player.xml
<com.google.android.exoplayer2.ui.PlayerView
[...]
app:use_controller="false"/>
- 将以下命名空间添加到
FrameLayout
:
activity_player.xml
<FrameLayout
[...]
xmlns:app="http://schemas.android.com/apk/res-auto">
现在试一下吧。
自定义行为
PlayerControlView
包含几个对其行为有影响的属性。例如,您可以使用 show_timeout
自定义从用户最后一次与控件互动到控件隐藏之间的延迟(以毫秒为单位)。为此,请执行以下操作:
- 移除
app:use_controller="false"
。 - 更改播放器视图以使用
show_timeout
:
activity_player.xml
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_timeout="10000"/>
PlayerControlView
的属性也可通过编程方式设置。
自定义外观
好了,初步设置进展不错。但是,如果您想更改 PlayerControlView
的外观或显示的按钮,该怎么办?PlayerControlView
的实现并未假设任何按钮存在,因此您可以轻松地删除按钮并添加新按钮。
我们来看看如何自定义 PlayerControlView
。
- 在文件夹
player-lib/res/layout/
中创建新的布局文件custom_player_control_view.xml
。 - 从布局文件夹的上下文菜单中,依次选择 New - Layout resource file 并将文件命名为
custom_player_control_view.xml
。
屏幕截图:播放器控件视图的布局文件已创建。
- 将原始布局文件从此处复制到
custom_player_control_view.xml
。 - 移除 ID 为
@id/exo_prev
和@id/exo_next
的ImageButton
元素。
如需使用自定义布局,您需要在 activity_player.xml
文件中设置 PlayerView
元素的属性 app:controller_layout_id
。
- 使用自定义文件的布局 ID,如以下代码段所示:
activity_player.xml
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/custom_player_control_view"/>
- 重新启动应用。此时,播放器控件视图不再具有“上一个”和“下一个”按钮。
屏幕截图:没有“上一个”和“下一个”按钮的自定义播放器控件视图
您可以视需要在布局文件中应用任何更改。默认情况下,系统会选择 Android 主题的颜色。您可以替换此设置以匹配您应用的设计。
- 向每个
ImageButton
元素分别添加一个android:tint
属性:
custom_player_control_view.xml
<ImageButton android:id="@id/exo_rew"
android:tint="#FF00A6FF"
style="@style/ExoMediaButton.Rewind"/>
- 将您在自定义文件中找到的所有
android:textColor
属性更改为同一颜色:#FF00A6FF
。
custom_player_control_view.xml
<TextView android:id="@id/exo_position"
[...]
android:textColor="#FF00A6FF"/>
<TextView android:id="@id/exo_duration"
[...]
android:textColor="#FF00A6FF"/>
- 运行应用。现在,您就得到了漂亮的彩色界面组件!
屏幕截图:彩色按钮和文本视图
替换默认样式
您刚刚创建了一个自定义布局文件,并在 activity_player.xml
中使用 controller_layout_id
引用了它。
另一种方法是替换 PlayerControlView
使用的默认布局文件。从 PlayerControlView
的源代码可以看出,它使用 R.layout.exo_player_control_view
进行布局。如果您使用相同的文件名创建自己的布局文件,PlayerControlView
就会改用您的文件。
- 移除刚刚添加的
controller_layout_id
属性。 - 删除文件
custom_player_control_view.xml
。
现在,activity_player.xml
中的 PlayerView
方法应如下所示:
activity_player.xml
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- 在库模块
player-lib
的res/layout
文件夹中创建一个名为exo_player_control_view.xml
的文件。 - 将以下代码插入
exo_player_control_view.xml
以添加播放按钮、暂停按钮和带有徽标的ImageView
:
exo_player**_control_view.xml**
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:background="#CC000000"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"/>
</LinearLayout>
<ImageView
android:contentDescription="@string/logo"
android:src="@drawable/google_logo"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
以上操作演示了如何在此处添加您自己的元素,并将它们与标准控件元素融为一体。现在,当与控件的互动保持不变时,ExoPlayerView
会使用您的自定义控件以及所有隐藏和显示逻辑。
8. 恭喜
恭喜!您已经学习了很多关于如何将 ExoPlayer 与您的应用集成的知识。
了解详情
如需详细了解 ExoPlayer,请查看开发者指南和源代码,并订阅 ExoPlayer 博客。