使用 Media3 ExoPlayer 创建基本的媒体播放器应用

Jetpack Media3 定义了一个 Player 接口,该接口概述了视频和音频文件播放的基本功能 。ExoPlayer 是 Media3 中此接口的默认实现 。我们建议使用 ExoPlayer,因为它提供了 一整套功能,涵盖了大多数播放用例,并且可以 自定义以处理您可能拥有的任何其他用例。ExoPlayer 还 抽象出了设备和操作系统碎片,因此您的代码可以在整个 Android 生态系统中保持一致 。ExoPlayer 包括:

本页将引导您完成构建播放 应用的一些关键步骤,如需了解更多详细信息,您可以前往我们的 Media3 ExoPlayer完整指南。

使用入门

如需开始使用,请添加对 Jetpack Media3 的 ExoPlayer、UI 和 Common 模块的依赖项:

implementation "androidx.media3:media3-exoplayer:1.9.2"
implementation "androidx.media3:media3-ui:1.9.2"
implementation "androidx.media3:media3-common:1.9.2"

根据您的用例,您可能还需要 Media3 中的其他模块, 例如 exoplayer-dash,用于播放 DASH 格式的流。

请务必将 1.9.2 替换为您偏好的 库版本。您可以参阅版本说明 了解最新版本。

创建媒体播放器

借助 Media3,您可以使用内置实现的 Player 接口,ExoPlayer,也可以构建自己的自定义实现。

创建 ExoPlayer

创建 ExoPlayer 实例的最简单方法如下:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

您可以在媒体播放器所在的 ActivityFragmentServiceonCreate()生命周期方法中创建媒体播放器。

Builder 包含 一系列您可能感兴趣的自定义选项,例如:

Media3 提供了一个 PlayerView 界面组件,您可以将其添加到应用的 布局文件中。此组件封装了用于播放 控件的 PlayerControlView、用于显示字幕的 SubtitleView 和用于渲染 视频的 Surface

准备播放器

使用 媒体项 将媒体项添加到播放列表以进行 播放,使用的方法包括 setMediaItem()addMediaItem()。 然后,调用prepare() 以 开始加载媒体并获取必要的资源。

您不应在应用位于前台之前执行这些步骤。如果您的 播放器位于 ActivityFragment 中,这意味着在 API 级别 24 及更高级别上,在 onStart() 生命周期方法中准备播放器;在 API 级别 23 及更低级别上,在 onResume() 生命周期方法中准备播放器。对于位于 Service 中的播放器, 您可以在 onCreate() 中准备它。如需查看如何实现生命周期方法的 示例,请参阅Exoplayer Codelab

控制播放器

准备好播放器后,您可以通过调用播放器上的方法 来控制播放,例如:

当绑定到播放器时,PlayerViewPlayerControlView 等界面组件会相应更新 。

释放播放器

播放可能需要资源(例如视频 解码器),而这些资源是有限的,因此请务必对播放器调用 release() 以释放资源。

如果您的播放器位于 ActivityFragment 中,请在 API 级别 24 及更高级别上,在 onStop()生命周期方法中释放播放器;在 API 级别 23 及更低级别上,在 onPause() 方法中释放播放器。对于位于 Service 中的播放器,您可以 在 onDestroy() 中释放它。如需查看如何实现生命周期方法的 示例,请参阅Exoplayer Codelab

使用媒体会话管理播放

在 Android 上,媒体会话提供了一种跨进程边界与媒体 播放器进行交互的标准化方式。将媒体会话连接到播放器后,您可以对外宣传您的媒体播放内容,并接收来自外部来源的播放命令,例如与系统媒体控件在移动设备和大型屏幕设备上集成。

如需使用媒体会话,请添加对 Media3 会话模块的依赖项:

implementation "androidx.media3:media3-session:1.9.2"

创建媒体会话

您可以在初始化播放器后创建 MediaSession,如下所示:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

Media3 会自动将 Player 的状态与 MediaSession 的状态同步。这适用于任何 Player 实现,包括 ExoPlayerCastPlayer 或 自定义实现。

向其他客户端授予控制权

客户端应用可以实现媒体控制器 来控制媒体会话的播放。如需接收这些请求,请在构建 MediaSession 时设置 回调对象。

当控制器即将连接到您的媒体会话时,系统会调用 onConnect() 方法。您可以使用提供的 ControllerInfo 来决定是接受 还是拒绝 该请求。如需查看相关示例,请参阅 Media3 会话演示应用

连接后,控制器可以向会话发送播放命令。然后,会话会将这些命令委托给播放器。会话会自动处理 Player 接口中定义的播放和播放列表 命令。

其他回调方法可让您处理自定义播放命令的请求和修改播放列表等。这些回调类似地包含 ControllerInfo 对象,因此您 可以根据每个请求确定访问控制。

在后台播放媒体内容

如需在应用不在前台时继续播放媒体内容(例如 即使在用户未打开应用的情况下播放音乐、有声读物或播客),您的 PlayerMediaSession 应封装在某个 前台服务中。Media3 provides MediaSessionService 接口为此目的。

实现 MediaSessionService

创建一个扩展 MediaSessionService 的类,并在 onCreate() 生命周期方法中实例化您的 MediaSession

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

在清单中,添加 Service 类并添加 MediaSessionService intent 过滤器,然后请求 FOREGROUND_SERVICE 权限以运行前台 服务:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

最后,在您创建的类中,替换 onGetSession() 方法以控制 客户端对媒体会话的访问。返回 MediaSession 以接受 连接请求,或返回 null 以拒绝该请求。

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

连接到界面

现在,您的媒体会话位于与ActivityFragment分开的Service中,您可以使用MediaController将 它们关联在一起。在包含界面的 ActivityFragmentonStart() 方法中,为 MediaSession 创建 SessionToken,然后使用 SessionToken 构建 MediaController。构建一个 MediaController 是 异步进行的。

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaController 实现了 Player 接口,因此您可以使用相同的 方法(例如 play()pause())来控制播放。与其他 组件类似,请记得在不再需要 MediaController 时释放它,例如通过调用 MediaController.releaseFuture()ActivityonStop() 生命周期方法中释放它。

发布通知

前台服务在处于活动状态时必须发布通知。A MediaSessionService 会自动以 MediaNotification 的形式为您创建 MediaStyle 通知。 如需提供自定义通知,请创建 MediaNotification.Provider ,方法是使用 DefaultMediaNotificationProvider.Builder 或通过创建提供程序接口的自定义实现。使用 setMediaNotificationProvider将提供程序添加到 MediaSession

宣传您的内容库

MediaLibraryService 基于 MediaSessionService 构建,允许客户端应用浏览您的应用提供的媒体内容。客户端应用实现MediaBrowser 以与 MediaLibraryService 互动。

实现 MediaLibraryService 与实现 MediaSessionService 类似,只不过在 onGetSession() 中,您应返回 MediaLibrarySession 而不是 MediaSession。与 MediaSession.Callback相比,MediaLibrarySession.Callback包含其他 方法,这些方法允许浏览器客户端浏览您的 库服务提供的内容。

MediaSessionService 类似,在 清单中声明 MediaLibraryService 并请求 FOREGROUND_SERVICE 权限以运行前台 服务:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

上面的示例包含 MediaLibraryService 和旧版 MediaBrowserService 的 intent 过滤器,以实现向后兼容性。其他 intent 过滤器使使用 MediaBrowserCompat API 的客户端应用能够识别您的 Service

MediaLibrarySession 可让您以树形结构提供内容库,其中包含单个根 MediaItem。树中的每个 MediaItem 都可以有 任意数量的子 MediaItem 节点。您可以根据客户端应用的请求提供不同的根或不同的 树。例如,您 返回给正在查找推荐媒体项列表的客户端的树可能仅 包含根 MediaItem 和单级子 MediaItem 节点, 而您返回给不同客户端应用的树可能代表更 完整的内容库。

创建 MediaLibrarySession

A MediaLibrarySession 扩展了 MediaSession API 以添加内容浏览 API。与 MediaSession 回调相比, MediaLibrarySession 回调 添加了以下方法:

相关回调方法将包含一个 LibraryParams 对象,其中包含有关客户端应用 感兴趣的内容树类型的其他信号。