播放器界面

播放器是应用中用于促进媒体项播放的组件。Media3 Player 接口为播放器通常处理的功能设置了概要。这包括

  • 影响播放控制,例如播放、暂停和搜索
  • 查询当前正在播放媒体的属性,例如播放位置
  • 管理媒体项目的播放列表/队列
  • 配置播放属性,例如随机播放、重复、速度和音量
  • 将视频渲染到屏幕上

Media3 还提供了一个 Player 接口的实现,称为 ExoPlayer.

组件之间的通用接口

Media3 中的几个组件实现了 Player 接口,例如

组件 描述和行为说明
ExoPlayer 一个媒体播放器 API,也是 Player 接口的默认实现。
MediaController MediaSession 交互以发送播放命令。如果你的 PlayerMediaSession 位于一个与你的播放器 UI 所在的 ActivityFragment 分开的 Service 中,你可以将你的 MediaController 指定为你的 PlayerView UI 的播放器。播放和播放列表方法调用通过你的 MediaSession 发送到你的 Player
MediaBrowser 除了 MediaController 提供的功能外,它还与 MediaLibrarySession 交互以浏览可用的媒体内容。
ForwardingPlayer 一个 Player 实现,它将方法调用转发到另一个 Player。使用此类来通过覆盖相应方法来抑制或修改特定操作。
SimpleBasePlayer 一个 Player 实现,它将要实现的方法数量减少到最少。当使用你想要连接到 MediaSession 的自定义播放器时很有用。
CastPlayer 一个 Player 实现,它与 Cast 接收器应用程序通信。行为取决于底层的 Cast 会话。

虽然 MediaSession 没有实现 Player 接口,但它在创建时需要一个 Player。它的目的是从其他进程或线程提供对 Player 的访问。

Media3 播放架构

如果你可以访问 Player,你应该直接调用其方法来发出播放命令。你可以通过实现 MediaSession 来宣传你的播放并授予外部来源播放控制权。这些外部来源实现了一个 MediaController,它有助于连接到媒体会话并发出播放命令请求。

在后台播放媒体时,你需要将你的媒体会话和播放器放在一个作为前台服务运行的 MediaSessionServiceMediaLibraryService 中。如果你这样做,你就可以将你的播放器与包含播放控制 UI 的应用程序中的 Activity 分开。这可能需要你使用媒体控制器。

A diagram showing how Media3 playback components fit into a media app architecture.
图 1Player 接口在 Media3 的架构中起着关键作用。

播放器状态

实现 Player 接口的媒体播放器的状态主要包含 4 类信息

  1. 播放状态
  2. 媒体项目的播放列表
  3. 播放/暂停属性,例如
    • playWhenReady:指示用户是否希望媒体在可能的情况下播放或保持暂停状态
    • 播放抑制原因:指示播放被抑制的原因(如果适用),即使 playWhenReadytrue
    • isPlaying:指示播放器当前是否正在播放,如果播放状态为 STATE_READYplayWhenReadytrue 且播放未被抑制,则此值仅为 true
  4. 播放位置,包括

此外,Player 接口允许访问 可用轨道媒体元数据播放速度音量 和播放的其他辅助属性。

监听更改

使用 Player.Listener 监听 Player 中的更改。有关如何创建和使用侦听器的详细信息,请参阅 ExoPlayer 文档中的 播放器事件 部分。

请注意,侦听器接口不包含任何用于跟踪正常播放进度的回调。要持续监视播放进度(例如设置进度条 UI),你应该定期查询当前位置。

Kotlin

val handler = Handler(Looper.getMainLooper())
fun checkPlaybackPosition(delayMs: Long): Boolean =
  handler.postDelayed(
    {
      val currentPosition = player.currentPosition
      // Update UI based on currentPosition
      checkPlaybackPosition(delayMs)
    },
    delayMs)

Java

Handler handler = new Handler(Looper.getMainLooper());
boolean checkPlaybackPosition(long delayMs) {
    return handler.postDelayed(() -> {
        long currentPosition = player.getCurrentPosition();
        // Update UI based on currentPosition
        checkPlaybackPosition(delayMs);
    }, delayMs);
}

控制播放

Player 接口提供了多种方法来操作状态和控制播放

自定义 Player 实现

要创建一个自定义播放器,你可以扩展 Media3 中包含的 SimpleBasePlayer。此类提供 Player 接口的基本实现,以将你必须实现的方法数量减少到最少。

首先覆盖 getState() 方法。此方法应在调用时填充当前播放器状态,包括

  • 可用命令集
  • 播放属性,例如播放器在播放状态为 STATE_READY 时是否应开始播放、当前播放媒体项的索引以及当前项目中的播放位置

Kotlin

class CustomPlayer : SimpleBasePlayer(looper) {
  override fun getState(): State {
    return State.Builder()
      .setAvailableCommands(...) // Set which playback commands the player can handle
      // Configure additional playback properties
      .setPlayWhenReady(true, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
      .setCurrentMediaItemIndex(0)
      .setContentPositionMs(0)
      .build()
  }
}

Java

public class CustomPlayer extends SimpleBasePlayer {
  public CustomPlayer(Looper looper) {
    super(looper);
  }

  @Override
  protected State getState() {
    return new State.Builder()
      .setAvailableCommands(...) // Set which playback commands the player can handle
      // Configure additional playback properties
      .setPlayWhenReady(true, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
      .setCurrentMediaItemIndex(0)
      .setContentPositionMs(0)
      .build();
  }
}

SimpleBasePlayer 将强制使用有效的状态值组合创建 State。它还将处理侦听器并告知侦听器状态更改。如果你需要手动触发状态更新,请调用 invalidateState()

除了 getState() 方法外,你只需要实现用于你的播放器声明为可用的命令的方法。找到与要实现的功能相对应的可覆盖处理程序方法。例如,覆盖 handleSeek() 方法以支持 COMMAND_SEEK_IN_CURRENT_MEDIA_ITEMCOMMAND_SEEK_TO_NEXT_MEDIA_ITEM 等操作。