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

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

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

快速入门

首先,添加对 ExoPlayer、界面和 Jetpack Media3 的常用模块的依赖项:

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

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

请务必将 1.3.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 界面组件,您可以将其添加到应用的布局文件中。该组件封装了用于播放控件的 PlayerControlView、用于显示字幕的 SubtitleView 和用于呈现视频的 Surface

准备播放器

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

您不应在应用处于前台之前执行这些步骤。如果播放器处于 ActivityFragment 状态,则意味着,对于 API 级别 24 及更高级别,应使用 onStart() 生命周期方法或 onResume() 生命周期方法(对于 API 级别 23 及更低级别)为播放器做好准备。对于 Service 中的播放器,您可以在 onCreate() 中准备该播放器。

控制播放器

播放器准备就绪后,您可以通过对播放器调用如下方法来控制播放:

界面组件(如 PlayerViewPlayerControlView)会在绑定到播放器时相应地更新。

释放播放器

播放可能需要供应量有限的资源(例如视频解码器),因此在不再需要播放器时,请务必对播放器调用 release() 以释放资源。

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

使用媒体会话管理播放

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

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

implementation "androidx.media3:media3-session:1.3.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();
    }
}

在您的清单中,带有 MediaSessionService intent 过滤器的 Service 类,并请求 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;
}

连接到界面

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

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.releaseFuture() 释放不再需要的 MediaController(例如 ActivityonStop() 生命周期方法)。

发布通知

需要前台服务才能发布处于活跃状态的通知。MediaSessionService 会自动为您创建 MediaNotification 形式的 MediaStyle 通知。如需提供自定义通知,请使用 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 和旧版 MediaBrowserService(为了向后兼容)的 intent 过滤器。借助额外的 intent 过滤器,使用 MediaBrowserCompat API 的客户端应用可以识别您的 Service

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

创建 MediaLibrarySession

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

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