UI 自訂

Media3 提供預設的 PlayerView,其中包含一些自訂選項。

覆寫可繪項目

PlayerView 會使用 PlayerControlView 顯示播放控制項和進度列。PlayerControlView 使用的可繪項目可由應用程式中定義的同名可繪項目覆寫。如需可覆寫的控制項可繪項目清單,請參閱 PlayerControlView 說明文件。

如需進一步自訂,應用程式開發人員應自行實作 UI 元件。不過,以下提供一些最佳做法,協助您著手進行。

最佳做法

實作連線至 Media3 Player 的媒體 UI 時 (例如 ExoPlayer、MediaController 或自訂 Player 實作),建議應用程式遵循下列最佳做法,以獲得最佳 UI 體驗。

播放/暫停按鈕

播放和暫停按鈕不會直接對應到單一播放器狀態。舉例來說,即使播放器未暫停,使用者也應能在播放結束或失敗後重新開始播放。

為簡化實作程序,Media3 提供公用程式方法,可決定要顯示哪個按鈕 (Util.shouldShowPlayButton),以及處理按下按鈕的動作 (Util.handlePlayPauseButtonAction):

Kotlin

val shouldShowPlayButton: Boolean = Util.shouldShowPlayButton(player)
playPauseButton.setImageDrawable(if (shouldShowPlayButton) playDrawable else pauseDrawable)
playPauseButton.setOnClickListener { Util.handlePlayPauseButtonAction(player) }

Java

boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
playPauseButton.setImageDrawable(shouldShowPlayButton ? playDrawable : pauseDrawable);
playPauseButton.setOnClickListener(view -> Util.handlePlayPauseButtonAction(player));

監聽狀態更新

UI 元件需要新增 Player.Listener,才能在狀態變更時收到通知,並據此更新 UI。詳情請參閱「監聽播放事件」。

重新整理 UI 的成本可能很高,而且多個播放器事件通常會同時發生。為避免在短時間內過於頻繁地重新整理 UI,通常最好只監聽 onEvents,並從該處觸發 UI 更新:

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onEvents(player: Player, events: Player.Events) {
      if (
        events.containsAny(
          Player.EVENT_PLAY_WHEN_READY_CHANGED,
          Player.EVENT_PLAYBACK_STATE_CHANGED,
          Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
        )
      ) {
        updatePlayPauseButton()
      }
      if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) {
        updateRepeatModeButton()
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onEvents(Player player, Player.Events events) {
        if (events.containsAny(
            Player.EVENT_PLAY_WHEN_READY_CHANGED,
            Player.EVENT_PLAYBACK_STATE_CHANGED,
            Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) {
          updatePlayPauseButton();
        }
        if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) {
          updateRepeatModeButton();
        }
      }
    });

處理可用的指令

一般用途的 UI 元件可能需要與不同的 Player 實作搭配使用,因此應檢查可用的播放器指令,以顯示或隱藏按鈕,並避免呼叫不支援的方法:

Kotlin

nextButton.isEnabled = player.isCommandAvailable(COMMAND_SEEK_TO_NEXT)

Java

nextButton.setEnabled(player.isCommandAvailable(COMMAND_SEEK_TO_NEXT));

第一個影格快門和圖片顯示

當 UI 元件顯示影片或圖片時,通常會使用預留位置快門檢視畫面,直到實際的第一個影格或圖片可用為止。此外,混合影片和圖片播放時,必須在適當時間隱藏及顯示圖片檢視畫面。

處理這些更新的常見模式是監聽 Player.Listener.onEvents(),瞭解所選曲目 (EVENT_TRACKS_CHANGED) 是否有任何變更,以及第一個影片影格是否已算繪 (EVENT_RENDERED_FIRST_FRAME),並監聽 ImageOutput.onImageAvailable(),瞭解是否有新圖片可用:

Kotlin

override fun onEvents(player: Player, events: Player.Events) {
  if (events.contains(Player.EVENT_TRACKS_CHANGED)) {
    // If no video or image track: show shutter, hide image view.
    // Otherwise: do nothing to wait for first frame or image.
  }
  if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) {
    // Hide shutter, hide image view.
  }
}

override fun onImageAvailable(presentationTimeUs: Long, bitmap: Bitmap) {
  // Show shutter, set image and show image view.
}

Java

@Override
public void onEvents(Player player, Player.Events events) {
  if (events.contains(Player.EVENT_TRACKS_CHANGED)) {
    // If no video or image track: show shutter, hide image view.
    // Otherwise: do nothing to wait for first frame or image.
  }
  if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) {
    // Hide shutter, hide image view.
  }
}

@Override
public void onImageAvailable(long presentationTimeUs, Bitmap bitmap) {
  // Show shutter, set image and show image view.
}

拖曳桿和跳轉滑桿控制項

實作連結至 ExoPlayer 執行個體的自訂跳轉滑桿或拖曳桿時,請在使用者開始拖曳拖曳桿時,使用 ExoPlayer.setScrubbingModeEnabled 啟用拖曳模式,在拖曳期間調度 Player.seekTo 更新,並在手勢完成或取消時立即停用拖曳模式。請避免在正常播放或單次跳轉期間啟用拖曳模式,因為這會抑制播放並增加解碼器資源用量。

我們也建議使用 ScrubbingModeParameters.Builder.setFractionalSeekTolerance,根據 UI 拖曳桿的寬度,設定以 dp 為基礎的部分跳轉容許值,讓 ExoPlayer 貼齊拖曳桿觸控精確度內的附近主要畫面格。如需程式碼範例和詳細資料,請參閱「拖曳模式」。