通过 MediaSession 控制媒体

上次更新日期:2020 年 9 月 9 日

为视频播放添加 MediaSession 支持有什么好处?

媒体会话是连接 Android 平台和媒体应用的重要桥梁。它不仅会告知 Android 媒体内容正在播放,以便平台将媒体操作转发到正确的会话,还可以告知平台正在播放什么内容以及如何对其进行控制。

在您的应用中提供 MediaSession 可为用户带来诸多好处。以下是一些很好的示例。

Google 助理

用户可以在您的应用中,通过语音指令(例如“暂停”、“继续”和“下一个”)轻松与媒体内容互动。系统还可借助媒体内容的元数据弄清当前正在播放什么内容。

Android TV

在大屏幕上,对于使用支持 HDMI-CEC 的电视的用户,您的 Android TV 应用支持使用传统遥控器。由播放/暂停、停止、下一个和上一个按钮发出的命令将传送至您的应用。

屏幕媒体控件

从 Android 4.0(API 级别 14)开始,系统可以访问媒体会话的播放状态和元数据。此功能可让锁定屏幕显示媒体控件和海报图片。此行为因 Android 版本而异。

背景媒体

即使播放媒体内容的应用在后台运行,也可以在上述任意场景中控制媒体。

环境计算

提供媒体相关数据(即正在播放什么内容、如何对其进行控制)可在各个设备之间架起桥梁,以便用户能够以他们喜欢的方式与媒体互动。

您将构建的内容

在本 Codelab 中,您将扩展现有的 Exoplayer 示例,为其添加媒体会话支持。您的应用将:

  • 正确反映媒体会话的活跃状态
  • 将媒体控件传送到 ExoPlayer
  • 将队列中媒体内容的元数据传递到媒体会话中

学习内容

  • 媒体会话为何能够为用户提供更丰富的体验
  • 如何创建媒体会话并管理其状态
  • 如何将媒体会话连接到 ExoPlayer
  • 如何在媒体会话的播放队列中添加媒体内容的元数据
  • 如何添加其他(自定义)操作

本 Codelab 重点介绍 MediaSession SDK,这里不讨论无关的概念和代码块(包括与 ExoPlayer 实现相关的详细信息),但提供这些内容供您简单复制和粘贴。

所需条件

  • 最新版 Android Studio(3.5 或更高版本)
  • 开发 Android 应用的基础知识

我们从何处入手?

我们着手点是 ExoPlayer 中的主演示。此演示包含带有屏幕播放控件的视频,但不在一开始就使用媒体会话,非常适合我们将其作为起点并添加媒体会话!

获取 ExoPlayer 示例

我们先从 ExoPlayer 示例开始。通过以下链接从 GitHub 代码库中克隆该示例。

克隆 ExoPlayer 示例

打开演示

在 Android Studio 中,打开位于 demos/main 下的主演示项目。

Android Studio 将提示您设置 SDK 路径。如果遇到任何问题,可以按照更新 IDE 和 SDK 工具的建议进行操作。

10e3b5c652186d57.png

如果系统要求您使用最新版 Gradle,请进行更新。

请花点时间大致了解应用的设计方式。请注意,涉及的 Activity 有两个:SampleChooserActivity 和 PlayerActivity。Codelab 的剩余部分将主要探讨实际播放媒体内容的类 PlayerActivity,因此,请打开这个类,然后转到下一部分。

创建媒体会话

打开 PlayerActivity.java。这个类可创建 ExoPlayer 并管理其函数,例如将视频呈现到屏幕上。在本 Activity 中,我们将把 ExoPlayer 连接到媒体会话。

在类的顶部声明以下两个字段。本部分会用到这些字段。

private MediaSessionCompat mediaSession;
private MediaSessionConnector mediaSessionConnector;

您将需要把“extension-mediasession”项目依赖项添加到“Module: demo”的模块级 build.gradle 中:

implementation project(path: ':extension-mediasession')

请注意,如果您将鼠标悬停在解析 MediaSessionConnector 时发生的错误上,Android Studio 可帮助您自动添加此依赖项:

60055e4ad54fbb97.png

最后,请通过添加以下内容来解析类导入:

import android.support.v4.media.session.MediaSessionCompat;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;

创建 activity 后,我们将创建一个媒体会话和一个媒体会话连接器,后者充当媒体会话和 ExoPlayer 之间的中间层。

插入上述代码的理想位置也是创建 ExoPlayer 的位置。在我们的演示应用中,我们可以将代码附加到 initializePlayer() 的末尾。请务必在播放器实例化之后添加此逻辑!

private void initializePlayer() {
  if (player == null) {
    ...
    player = ...
    ...
    mediaSession = new MediaSessionCompat(this, "sample");
    mediaSessionConnector = new MediaSessionConnector(mediaSession);
    mediaSessionConnector.setPlayer(player);
  }
  ...
}

释放媒体会话

不再需要媒体会话时,请将其释放。我们在 releasePlayer() 中释放 ExoPlayer 时,还添加了以下代码:

private void releasePlayer() {
  if (mediaSession != null) {
    mediaSession.release();
  }
  ...
}

管理媒体会话状态

现在,我们已实例化媒体会话,接下来需要确保在用户与 Activity 交互时正确反映媒体会话的状态。

用户启动 Activity 后,媒体会话应变为活动状态:

@Override
public void onStart() {
  ...
  if (mediaSession != null) {
    mediaSession.setActive(true);
  }
}

由于我们的应用不在后台播放媒体内容,因此请务必确保用户离开 Activity 后媒体会话变为非活动状态:

@Override
public void onStop() {
  super.onStop();
  if (mediaSession != null) {
    mediaSession.setActive(false);
  }
  ...
}

运行演示

  1. 连接 Android 设备或启动模拟器。
  2. 请确保选择“演示”,以便从 Android Studio 工具栏运行演示。cb1ec4e50886874f.png
  3. 点击 Android Studio 工具栏中的 9d8fb3a9ddf12827.png
  4. 应用在您的设备上启动后,请选择要播放的视频串流。
  5. 开始播放后,尝试使用以下 adb 命令来控制媒体会话:adb shell media dispatch pauseadb shell media dispatch playadb shell media dispatch play-pauseadb shell media dispatch fast-forwardadb shell media dispatch rewind
  1. 此外,您还可以通过执行以下命令了解 Android 如何访问您的媒体会话:adb shell dumpsys media_session
  2. 如果您使用的是带有麦克风的实体设备,请尝试调用 Google 助理并发出语音指令,例如:“暂停”、“继续”、“快进 1 分钟”。

b8dda02a6fb0f6a4.png在 Android TV 上运行的 ExoPlayer 示例。

现在,我们可以在刚才在 initializePlayer() 中创建 MediaSessionConnector 的位置,扩展媒体会话支持的功能。

添加 TimelineQueueNavigator

ExoPlayer 以时间轴的形式表示媒体内容的结构。如需详细了解相关工作原理,请花点时间了解 ExoPlayer 的 Timeline 对象。通过了解此结构,我们可以在内容变化时收到通知,并在收到请求时显示当前正在播放的内容的元数据。

为此,我们将指定一个 TimelineQueueNavigator。在 initializePlayer() 中找到 MediaSessionConnector 的实例化代码段,并在初始化 mediaSession 之后添加 TimelineQueueNavigator 的实现。

mediaSessionConnector.setQueueNavigator(new TimelineQueueNavigator(mediaSession) {
  @Override
  public MediaDescriptionCompat getMediaDescription(Player player, int windowIndex) {
    return new MediaDescriptionCompat.Builder()
            .setTitle("MediaDescription title")
            .setDescription("MediaDescription description for " + windowIndex)
            .setSubtitle("MediaDescription subtitle")
            .build();
  }
});

通过添加以下内容解析类导入:

import android.support.v4.media.MediaDescriptionCompat;
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;

注意观察 windowIndex 参数是否与播放队列中该索引的媒体内容相对应。

现在,您已经添加了一些元数据,接下来可以测试 Google 助理是否理解当前正在播放什么内容。在 Android TV 上播放视频时,调用 Google 助理并询问“正在播放的是什么?”

6c7fc0cb853cbc38.png

您的播放器可能不支持某些操作,或者您希望添加对更多操作的支持?现在,我们来深入了解一下刚才我们在 initializePlayer() 中创建 MediaSessionConnector 所在的媒体会话。

声明支持的操作

尝试使用 mediaSessionConnector.setEnabledPlaybackActions() 自定义您希望媒体会话支持哪些操作。

请注意,完整的集合如下所示:

mediaSessionConnector.setEnabledPlaybackActions(
        PlaybackStateCompat.ACTION_PLAY_PAUSE
                | PlaybackStateCompat.ACTION_PLAY
                | PlaybackStateCompat.ACTION_PAUSE
                | PlaybackStateCompat.ACTION_SEEK_TO
                | PlaybackStateCompat.ACTION_FAST_FORWARD
                | PlaybackStateCompat.ACTION_REWIND
                | PlaybackStateCompat.ACTION_STOP
                | PlaybackStateCompat.ACTION_SET_REPEAT_MODE
                | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
);

我们再次看一下此数据在平台上的显示方式:

  1. 和之前一样,播放视频。
  2. 通过执行以下命令,了解 Android 如何从您的媒体会话访问元数据:adb shell dumpsys media_session
  3. 找到包含元数据的行,并注意观察其中包含的与 com.google.android.exoplayer2.demo/sample 关联的标题和说明。

添加其他操作

我们扩展我们的视频会话,使其支持更多操作。在本部分中,我们仅添加字幕支持。

字幕支持

为媒体会话添加字幕支持可让用户通过语音打开/关闭字幕。找到初始化媒体会话连接器的位置,添加以下内容:

mediaSessionConnector.setCaptionCallback(new MediaSessionConnector.CaptionCallback() {
      @Override
      public void onSetCaptioningEnabled(Player player, boolean enabled) {
        Log.d("MediaSession", "onSetCaptioningEnabled: enabled=" + enabled);
      }

      @Override
      public boolean hasCaptions(Player player) {
        return true;
      }

      @Override
      public boolean onCommand(Player player, ControlDispatcher controlDispatcher, String command, Bundle extras, ResultReceiver cb) {
        return false;
      }
    }
);

最后,解析缺失的任意导入。

如需测试此功能,您可以调用 Android TV 上的 Google 助理,然后说“启用字幕”。检查 Logcat 中的消息,查看此功能如何调用您的代码。

恭喜,您已在示例中成功添加了媒体会话!

您可以通过以下方式实现大量功能:

  • 添加媒体会话;
  • 将媒体会话连接到 ExoPlayer 的实例;
  • 添加元数据和其他操作。

现在您已了解丰富媒体应用所需的关键步骤,快试着为用户提供更丰富多样的体验!

本 Codelab 是基于 ExoPlayer 源代码中的示例构建的。您无需通过源代码使用 ExoPlayer,建议您提取 ExoPlayer 和 MediaSessionConnector 的依赖项,以便轻松获取最新版本。

为此,只需替换项目依赖项即可,例如:

implementation project(modulePrefix + 'library-core')
implementation project(path: ':extension-mediasession')

并从 Maven 代码库中提取数据,例如:

implementation 'com.google.android.exoplayer:exoplayer-core:2.+'
implementation 'com.google.android.exoplayer:extension-mediasession:2.+'

参考文档