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

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

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

开始使用

若要开始,请添加 Jetpack Media3 的 ExoPlayer、UI 和 Common 模块的依赖项

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

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

请确保将 1.7.1 替换为您首选的库版本。您可以参考发布说明查看最新版本。

创建媒体播放器

使用 Media3,您可以使用随附的 Player 接口实现(即 ExoPlayer),也可以构建自己的自定义实现。

创建 ExoPlayer

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

Kotlin

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

Java

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

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

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

Media3 提供了一个 PlayerView UI 组件,您可以将其包含在应用布局文件中。此组件封装了用于播放控制的 PlayerControlView、用于显示字幕的 SubtitleView 以及用于渲染视频的 Surface

准备播放器

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

您不应在应用处于前台之前执行这些步骤。如果您的播放器位于 ActivityFragment 中,这意味着在 API 级别 24 及更高版本中,在 onStart() 生命周期方法中准备播放器;在 API 级别 23 及更低版本中,在 onResume() 生命周期方法中准备播放器。对于位于 Service 中的播放器,您可以在 onCreate() 中准备它。

控制播放器

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

当绑定到播放器时,PlayerViewPlayerControlView 等 UI 组件将相应更新。

释放播放器

播放可能需要有限的资源,例如视频解码器,因此在不再需要播放器时,调用播放器上的 release() 以释放资源非常重要。

如果您的播放器位于 ActivityFragment 中,请在 API 级别 24 及更高版本中,在 onStop() 生命周期方法中释放播放器;在 API 级别 23 及更低版本中,在 onPause() 方法中释放播放器。对于位于 Service 中的播放器,您可以在 onDestroy() 中释放它。

使用媒体会话管理播放

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

要使用媒体会话,请添加对 Media3 Session 模块的依赖

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

创建媒体会话

初始化播放器后,您可以按如下方式创建 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 Session 演示应用中的示例。

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

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

在后台播放媒体

为了在您的应用不在前台时继续播放媒体(例如,即使在用户未打开您的应用时也能播放音乐、有声读物或播客),您的 PlayerMediaSession 应该封装在前台服务中。Media3 为此提供了 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;
}

连接到您的 UI

现在,您的媒体会话位于一个与播放器 UI 所在的 ActivityFragment 分离的 Service 中,您可以使用 MediaController 将它们链接起来。在包含 UI 的 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 时(例如在 ActivityonStop() 生命周期方法中),通过调用 MediaController.releaseFuture() 来释放它。

发布通知

前台服务在活动时必须发布通知。MediaSessionService 将自动为您创建 MediaStyle 通知,形式为 MediaNotification。要提供自定义通知,请使用 DefaultMediaNotificationProvider.Builder 创建一个 MediaNotification.Provider,或创建提供程序接口的自定义实现。使用 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 的 Intent 过滤器,以及为了向后兼容,旧版 MediaBrowserService 的 Intent 过滤器。额外的 Intent 过滤器使使用 MediaBrowserCompat API 的客户端应用能够识别您的 Service

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

创建 MediaLibrarySession

一个 MediaLibrarySession 扩展了 MediaSession API 以添加内容浏览 API。与 MediaSession 回调相比,MediaLibrarySession 回调添加了以下方法,例如

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