使用 ExoPlayer 流式传输媒体内容   您所在的位置:网站首页 exo读什么 使用 ExoPlayer 流式传输媒体内容  

使用 ExoPlayer 流式传输媒体内容  

2023-09-26 22:26| 来源: 网络整理| 查看: 265

1. 准备工作

注意:ExoPlayer 是在 Android 版 YouTube 应用中运行的视频播放器。

526b239733391e74.png

屏幕截图:YouTube Android 版应用

ExoPlayer 是一款基于 Android 中的低层级媒体 API 构建的应用级媒体播放器。与 Android 内置的 MediaPlayer 相比,ExoPlayer 具有多项优势。它支持 MediaPlayer 支持的许多媒体格式,还支持 DASH 和 SmoothStreaming 等自适应格式。ExoPlayer 具有高度的可定制性和可扩展性,因此能够用于许多高级用例。它是 Google 应用(包括 YouTube 和 Google Play 影视)使用的开源项目。

注意:如需详细了解相关优缺点,请参阅 ExoPlayer 开发者指南。

前提条件 适度了解 Android 开发和 Android Studio 实践内容 创建一个 SimpleExoPlayer 实例,用于准备和播放来自各种来源的媒体。 将 ExoPlayer 与应用的 activity 生命周期集成,以在单窗口或多窗口环境中支持后台运行、前台运行和继续播放媒体内容功能。 使用 MediaItem 创建播放列表。 播放自适应视频串流(根据可用带宽调整媒体质量)。 注册事件监听器,监听播放状态并展示如何使用监听器来衡量播放质量。 使用标准 ExoPlayer 界面组件,然后根据应用的样式对其进行自定义。 所需条件 最新的稳定版 Android Studio。 搭载 JellyBean (4.1) 或更高版本的 Android 设备(最好是 Nougat [7.1] 或更高版本,因为这些版本支持多窗口)。

注意:此 Codelab 主要介绍 ExoPlayer。对于不相关的概念,我们仅会略作介绍,但是会提供不相关的代码块供您复制和粘贴。

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 文件。

111b190903697765.png

屏幕截图:导入时的项目结构

构建完成后,您会看到 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") }

注意:您可以切换到此 Codelab 中任何步骤的起始状态,只需将 exoplayer-codelab-00 更改为 explayer-codelab-N 即可,其中 N 为步骤编号。完成后,请依次选择 File -> Sync Project With Gradle Files。

媒体播放器 activity 保存在 exoplayer-codelab-N 模块中。之所以将其保存在单独的库模块中,是为了让您可以在针对不同目标平台(例如,移动设备和 Android TV)的 APK 之间共享它。它还允许您充分利用各种功能,例如 Dynamic Delivery。使用 Dynamic Delivery 功能后,只有当用户需要时,系统才允许安装您的媒体播放功能。

部署并运行应用,以检查是否一切正常。应用应该以黑色为背景填充屏幕。

2dae13fed92e6c8c.png

屏幕截图:空白应用正在运行

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 元素内。 开始输入 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 文件并看到嵌入的海报图片。

d92917867ee23ef8.png

屏幕截图:应用正在播放一个曲目。

测试 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 会完成所有相关操作。视频(而非海报图片)会全屏呈现。

425c6c65f78e8d46.png

屏幕截图:应用正在播放视频。

真厉害!您刚刚制作了一款能够在 Android 上全屏流式传输媒体的应用,以及配套的生命周期管理、保存状态和界面控件!

4. 创建播放列表

注意:您可以继续更新当前项目,或从 exoplayer-intro/exoplayer-codelab-01 导入项目以返回某个已知的工作状态。

您当前的应用可以播放单个媒体文件,但如果您想让多个媒体文件相继播放,该怎么办呢?为此,您需要使用播放列表。

如需创建播放列表,您可以使用 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); [...] }

检查播放器控件的行为方式。您可以使用 1f79fee4d082870f.png39627002c03ce320.png 在媒体项序列中跳转。

7b5c034dafabe1bd.png

屏幕截图:显示“下一个”和“上一个”按钮的播放控件

您可以通过直接在播放器上调用 addMediaItem(MediaItem item)、moveMediaItem(int fromIndex, int toIndex) 和 removeMediaItem(int index) 来修改播放列表。播放列表修改可在播放器处于任何状态时执行,包括播放器做好准备之前以及当前正在播放媒体时。

这很方便!如需了解详情,请参阅有关媒体项和播放列表的开发者文档,以及有关 Playlist API 的这篇文章。

5. 自适应流式传输

注意:您可以继续更新当前项目,或从 exoplayer-intro/exoplayer-codelab-02 导入项目以返回某个已知的工作状态。

自适应流式传输是一种媒体流式传输技术,可根据可用网络带宽改变串流质量。借助该技术,用户可体验其带宽支持的最高质量的媒体内容。

通常,同一媒体内容会分为多个轨道,其质量(比特率和分辨率)各有不同。播放器会根据可用的网络带宽来选择轨道。

每个轨道会分成给定时长的数据块,其时长通常在 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 类型。

借助 MediaItem.Builder,您可以创建 MediaItem 并为其设置多种附加属性,其中包括:

媒体内容的 MIME 类型。 受保护的内容属性,其中包括 DRM 类型、许可服务器 URI 和许可请求标头。 在播放期间使用的旁加载字幕文件。 剪辑开始和结束位置。 用于广告插入的广告标签 URI。

如需了解详情,请参阅开发者指南。

更新 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,您可以轻松实现这项操作!

注意:现在,您正在流式传输 DASH,并使用可用带宽来选择轨道。若要查看其实际效果,您可以执行以下操作:

点按两次 Shift 键打开 Search Everywhere。 查找 AdaptiveTrackSelection 类。 在该类中,查找 updateSelectedTrack(当轨道更新时,系统会调用此方法)。 在 if (selectedIndex == currentSelectedIndex 行中添加断点。 在调试模式下启动应用。

观察每当轨道选择器需要决定使用哪个轨道时,端点是如何遇到的。

其他自适应流式传输格式

其他常用的自适应流式传输格式为 HLS (MimeTypes.APPLICATION_M3U8) 和 SmoothStreaming (MimeTypes.APPLICATION_SS),二者均受 ExoPlayer 支持。如需详细了解其他自适应媒体来源的构造方式,请参阅 ExoPlayer 演示版应用。

6. 监听事件

注意:您可以继续更新当前项目,或从 exoplayer-intro/exoplayer-codelab-03 导入项目以返回某个已知的工作状态。

在前面的步骤中,您学习了如何流式传输渐进式媒体串流和自适应媒体串流。ExoPlayer 会在幕后为您处理大量工作,其中包括以下各项:

分配内存 下载容器文件 从容器中提取元数据 解码数据 向屏幕和扬声器呈现视频、音频和文本

有时,了解 ExoPlayer 在运行时执行的操作有助于了解和改进用户的播放体验。

例如,您可能想要通过执行以下操作来反映界面中的播放状态变化:

在播放器进入缓冲状态时显示“正在加载”旋转图标 在轨道播放结束后显示叠加层和“接下来观看”选项

ExoPlayer 提供了几个监听器接口,用于提供对有用事件的回调。您可以使用监听器来记录播放器所处的状态。

监听

注意:在实现接口时,很容易将所有接口方法都放在 PlayerActivity 类中。不过,这可能会导致代码冗长,难以阅读和维护。更好的解决方案是使用工厂函数生成接口的匿名类以监听事件。这样可以让 API 保持简洁。

在 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 种状态之一:

状态

说明

ExoPlayer.STATE_IDLE

播放器已实例化,但尚未准备就绪。

ExoPlayer.STATE_BUFFERING

播放器无法从当前位置开始播放,因为已缓冲的数据不足。

ExoPlayer.STATE_READY

播放器可以立即从当前位置开始播放。这意味着如果播放器的 playWhenReady 属性为 true,播放器将自动开始播放媒体。如果该属性为 false,播放器会暂停播放。

ExoPlayer.STATE_ENDED

播放器已完成媒体播放。

注意:如何确定播放器是否真的在播放媒体?必须满足以下所有条件:

播放状态为 STATE_READY. playWhenReady 为 true. 播放未因某些其他原因(例如,音频焦点丢失)而停止。

幸运的是,ExoPlayer 专门为此提供了一个便捷方法 ExoPlayer.isPlaying。或者,如果您想在 isPlaying 发生变化时收到通知,可以监听 onIsPlayingChanged。

注册监听器

如需调用您的回调,您需要向播放器注册 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-intro/exoplayer-codelab-04 导入项目以返回某个已知的工作状态。

到目前为止,您一直在使用 ExoPlayer 的 PlayerControlView 向用户显示播放控制器。

bcfe17eebcad9e13.png

屏幕截图:默认播放控制器

如果您想更改这些控件的功能或外观,该怎么办?幸运的是,这些控件是高度可定制的。

第一项简单的自定义是完全不使用控制器。这很容易实现,只需在 activity_player.xml 中的 PlayerView 元素上使用 use_controller 属性即可。

将 use_controller 设为 false,控件就不会再显示了: activity_player.xml 将以下命名空间添加到 FrameLayout: activity_player.xml

现在试一下吧。

自定义行为

PlayerControlView 包含几个对其行为有影响的属性。例如,您可以使用 show_timeout 自定义从用户最后一次与控件互动到控件隐藏之间的延迟(以毫秒为单位)。为此,请执行以下操作:

移除 app:use_controller="false"。 更改播放器视图以使用 show_timeout: activity_player.xml

PlayerControlView 的属性也可通过编程方式设置。

自定义外观

好了,初步设置进展不错。但是,如果您想更改 PlayerControlView 的外观或显示的按钮,该怎么办?PlayerControlView 的实现并未假设任何按钮存在,因此您可以轻松地删除按钮并添加新按钮。

我们来看看如何自定义 PlayerControlView。

在文件夹 player-lib/res/layout/ 中创建新的布局文件 custom_player_control_view.xml。 从布局文件夹的上下文菜单中,依次选择 New - Layout resource file 并将文件命名为 custom_player_control_view.xml。

ae1e3795726d4e4e.png

屏幕截图:播放器控件视图的布局文件已创建。

将原始布局文件从此处复制到 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 重新启动应用。此时,播放器控件视图不再具有“上一个”和“下一个”按钮。

89e6535a22c8e321.png

屏幕截图:没有“上一个”和“下一个”按钮的自定义播放器控件视图

注意:PlayerControlView 根据 ID 标识它使用的界面元素。因此在自定义布局文件时,请勿更改标准元素的 ID(例如 @id/exo_play 和 @id/exo_pause,),否则 PlayerControlView 将无法找到它们。

您可以视需要在布局文件中应用任何更改。默认情况下,系统会选择 Android 主题的颜色。您可以替换此设置以匹配您应用的设计。

向每个 ImageButton 元素分别添加一个 android:tint 属性: custom_player_control_view.xml 将您在自定义文件中找到的所有 android:textColor 属性更改为同一颜色:#FF00A6FF。 custom_player_control_view.xml 运行应用。现在,您就得到了漂亮的彩色界面组件!

e9835d65d6dd0634.png

屏幕截图:彩色按钮和文本视图

注意:对于您的真实应用,您可能需要使用 Android 样式,而不是直接在布局文件的元素中设置属性。

替换默认样式

您刚刚创建了一个自定义布局文件,并在 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 在库模块 player-lib 的 res/layout 文件夹中创建一个名为 exo_player_control_view.xml 的文件。 将以下代码插入 exo_player_control_view.xml 以添加播放按钮、暂停按钮和带有徽标的 ImageView: exo_player**_control_view.xml**

以上操作演示了如何在此处添加您自己的元素,并将它们与标准控件元素融为一体。现在,当与控件的互动保持不变时,ExoPlayerView 会使用您的自定义控件以及所有隐藏和显示逻辑。

8. 恭喜

恭喜!您已经学习了很多关于如何将 ExoPlayer 与您的应用集成的知识。

了解详情

如需详细了解 ExoPlayer,请查看开发者指南和源代码,并订阅 ExoPlayer 博客。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有