產品新訊

提升媒體播放體驗:推出 Media3 預先載入功能 - 第 1 部分

8 分鐘閱讀
Mayuri Khinvasara Khabya
開發人員關係工程師

在以媒體為中心的應用程式中,提供流暢無間斷的播放體驗,是打造優質使用者體驗的關鍵。使用者希望影片能立即開始播放,且過程流暢無間斷。

核心挑戰是延遲。傳統上,使用者選擇要播放的項目後,影片播放器才會開始運作,包括連線、下載、剖析及緩衝處理。在當今短片盛行的環境下,這種被動做法速度太慢。解決方法是主動出擊。我們需要預測使用者接下來會觀看的內容,並預先準備好。這就是預先載入的本質。

預先載入的主要優點包括:

  • 🚀 更快開始播放:影片已準備就緒,因此項目之間的轉場效果更快,且能立即開始播放。
  • 📉 減少緩衝:主動載入資料可大幅減少播放停頓的機率,例如因網路不穩而停頓。
  • ✨ 提升使用者體驗:啟動速度更快,緩衝時間更短,使用者互動更流暢。

在這個三部曲系列中,我們將介紹並深入探討 Media3 強大的公用程式,瞭解如何 (預先) 載入元件。

  • 在第 1 部分中,我們將介紹基礎知識:瞭解 Media3 中提供的不同預先載入策略、啟用 PreloadConfiguration 和設定 DefaultPreloadManager,以及讓應用程式預先載入項目。看完這篇網誌後,您應該就能預先載入及播放媒體項目,並設定排名和時間長度。
  • 第 2 部分中,我們將深入探討 DefaultPreloadManager 的進階主題:使用監聽器進行數據分析,以及探索適用於可部署於正式環境的最佳做法,例如滑動視窗模式,以及 DefaultPreloadManager 和 ExoPlayer 的自訂共用元件。
  • 在第 3 部分,我們將深入探討如何使用 DefaultPreloadManager 進行磁碟快取。

預先載入功能可派上用場!🦸‍♀️

預先載入背後的概念很簡單:在需要媒體內容之前先載入。使用者滑動到下一部影片時,系統已下載並提供影片的前幾段內容,可立即播放。

這就像餐廳一樣,忙碌的廚房不會等到訂單來了才開始切洋蔥。🧅 他們會預先做好準備工作。預先載入是影片播放器的準備工作。

啟用預先載入功能後,如果使用者在播放緩衝區到達下一個項目之前跳至下一個項目,預先載入功能有助於縮短加入延遲時間。系統會準備下一個時段的第一個週期,並緩衝處理影片、音訊和文字樣本。預先載入的期間稍後會排入播放器佇列,緩衝的樣本會立即提供,並準備好饋送至轉碼器進行算繪。

Media3 提供兩種主要預先載入 API,分別適用於不同用途。選擇合適的 API 是第一步。

1. 使用 PreloadConfiguration 預先載入播放清單項目

這是簡單的做法,適用於播放順序可預測的線性連續媒體,例如播放清單 (如一系列劇集)。您可以使用 ExoPlayer 的播放清單 API,將完整媒體項目清單提供給播放器,並為播放器設定 PreloadConfiguration,然後播放器就會自動預先載入序列中的下一個項目 (如設定)。如果使用者在播放緩衝區已重疊到下一個項目之前跳至下一個項目,這項 API 會嘗試縮短加入延遲時間。

只有在目前播放作業未載入任何媒體時,系統才會開始預先載入,避免與主要播放作業爭用頻寬。

如果您仍不確定是否需要預先載入,這個 API 是絕佳的低負擔選項,可供您試用!

player.preloadConfiguration =
    PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)

使用上述 PreloadConfiguration,播放器會嘗試預先載入播放清單中下一個項目五秒的媒體。

加入後,如要再次關閉播放清單預先載入功能,請使用 PreloadConfiguration.DEFAULT 停用播放清單預先載入功能:

player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. 使用 PreloadManager 預先載入動態清單

如果是直向動態饋給或輪播等動態 UI,其中「下一個」項目是由使用者互動決定,則適合使用 PreloadManager API。這是 Media3 ExoPlayer 程式庫中功能強大的獨立元件,專為主動預先載入而設計。這項工具會管理一系列可能的 MediaSource,並根據與使用者目前位置的距離設定優先順序,還能精細控管要預先載入的內容,適用於動態短片動態饋給等複雜情境。

設定 PreloadManager

DefaultPreloadManager 是 PreloadManager 的標準實作。

DefaultPreloadManager 的建構工具可以建構 DefaultPreloadManager 和任何 ExoPlayer 執行個體,這些執行個體會播放預先載入的內容。如要建立 DefaultPreloadManager,您需要傳遞 TargetPreloadStatusControl,預先載入管理工具可查詢這項控制項,瞭解要為項目載入多少內容。我們將在下節說明並定義 TargetPreloadStatusControl 的範例。

val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
val preloadManager = val preloadManagerBuilder.build()

// Build ExoPlayer with DefaultPreloadManager.Builder
val player = preloadManagerBuilder.buildExoPlayer()

ExoPlayer 和 DefaultPreloadManager 必須使用相同的 builder,確保兩者底層的元件正確共用。

就是這麼簡單,現在管理員已準備好接收指示。

使用 TargetPreloadStatusControl 設定時間長度和排名

如果想預先載入 10 秒的影片,您可以提供輪播介面中媒體項目的位置,DefaultPreloadManager 會根據項目與使用者目前播放項目的距離,優先載入項目。

如要控制預先載入項目的時間長度,可以透過 DefaultPreloadManager.PreloadStatus 回傳。

例如:

  • 項目「A」的優先順序最高,載入 5 秒的影片。
  • 項目「B」的優先順序為中等,但當您處理到這個項目時,請載入 3 秒的影片。
  • 項目「C」的優先順序較低,只追蹤載入。
  • 項目「D」的優先順序更低,只要準備即可。
  • 其他項目距離很遠,請勿預先載入任何內容。

這種精細控管機制有助於提高資源使用率,建議您採用此做法,確保影片順暢播放。

import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus


class MyTargetPreloadStatusControl(
    currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {


    // The app is responsible for updating this based on UI state
    override fun getTargetPreloadStatus(index: Int): PreloadStatus? {

        val distance = index - currentPlayingIndex

        // Adjacent items (Next): preload 5 seconds
        if (distance == 1) { 
        // Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
                    return PreloadStatus.specifiedRangeLoaded(5000L)
                } 

        // Adjacent items (Previous): preload 3 seconds
        else if (distance == -1) { 
        // Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
                    return PreloadStatus.specifiedRangeLoaded(3000L)
                } 

        // Items two positions away: just select tracks
        else if (distance) == 2) {
        // Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
                    return PreloadStatus.TRACKS_SELECTED
                } 

        // Items four positions away: just select prepare
        else if (abs(distance) <= 4) {
        // Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
                    return PreloadStatus.SOURCE_PREPARED
                }

             // All other items are too far away
             return null
            }
}

提示:PreloadManager 可以預先載入前一個和下一個項目,但 PreloadConfiguration 只會預先載入下一個項目。

管理預先載入項目

建立管理員後,即可開始告知管理員要處理的工作。當使用者捲動瀏覽動態消息時,您會找出即將播放的影片,並將這些影片加入管理員。與 PreloadManager 的互動是 UI 和預先載入引擎之間以狀態為導向的對話。

1. 新增媒體項目

填入動態饋給時,請務必告知管理員需要追蹤的媒體。如果您是新手,可以加入要預先載入的完整清單。之後可以視需要繼續在清單中新增單一項目。您可以完全掌控預先載入清單中的項目,也就是說,您也必須管理管理工具中新增和移除的項目。

val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
    preloadManager.add(
        initialMediaItems.get(index),index)
    )
}

管理員現在會在背景開始擷取這項 MediaItem 的資料。

新增後,請告知管理員重新評估新清單 (提示有變更,例如新增/ 移除項目,或使用者改為播放新項目)。

preloadManager.invalidate()

2. 擷取及播放項目

接下來是主要的播放邏輯。使用者決定播放該影片時,您不需要建立新的 MediaSource。而是要求 PreloadManager 提供已準備好的內容。您可以使用 MediaItem 從 Preload Manager 擷取 MediaSource。

如果從 PreloadManager 擷取的項目為空值,表示 mediaItem 尚未預先載入或新增至 PreloadManager,因此您可以選擇直接設定 mediaItem。

// When a media item is about to displ​​ay on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
  player.setMediaSource(mediaSource)
} else {
  // If mediaSource is null, that mediaItem hasn't been added yet.
  // So, send it directly to the player.
  player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()

準備從 PreloadManager 擷取的 MediaSource,即可使用記憶體中已有的資料,從預先載入無縫轉換至播放。這就是啟動時間縮短的原因。

3. 讓目前的索引與 UI 保持同步

由於動態消息 / 清單可能會變動,請務必將目前的播放索引通知 PreloadManager,這樣系統才能一律優先預先載入最接近目前索引的項目。

preloadManager.setCurrentPlayingIndex(currentIndex)
// Need to call invalidate() to update the priorities
preloadManager.invalidate()

4. 移除項目

為確保管理員效率,您應移除不再需要追蹤的項目,例如遠離使用者目前位置的項目。

// When an item is too far from the current playing index
preloadManager.remove(mediaItem)

如要一次清除所有項目,可以呼叫 preloadManager.reset()

5. 釋放管理員

不再需要 PreloadManager 時 (例如 UI 遭到毀損時),請務必釋出該管理工具,以釋放資源。建議您在發布 Player 資源的位置執行這項操作。建議先釋出管理員,再釋出播放器,因為如果不需要預先載入任何內容,播放器可以繼續播放。

// In your Activity's onDestroy() or Composable's onDispose
preloadManager.release()

示範時間

立即查看實際運作情況 👍

在下方的示範中,我們可以看到 PreloadManager 的影響:右側的載入速度較快,左側則顯示現有的體驗。您也可以查看示範的程式碼範例。(額外功能:還會顯示每個影片的啟動延遲時間)

Demo-PreloadManager_2.webp

後續步驟

第 1 部分到此結束!您現在已具備建構動態預先載入系統的工具。您可以使用 PreloadConfiguration 在 ExoPlayer 中預先載入播放清單的下一個項目,也可以設定 DefaultPreloadManager、即時新增及移除項目、設定目標預先載入狀態,並正確擷取預先載入的內容以供播放。

第 2 部分,我們將深入探討 DefaultPreloadManager。我們將探討如何監聽預先載入事件、討論最佳做法 (例如使用滑動視窗來避免記憶體問題),並深入瞭解 ExoPlayer 和 DefaultPreloadManager 的自訂共用元件。

有任何意見想分享嗎?期待收到您的回覆。

敬請期待,並開始加快應用程式速度!🚀

撰寫者:

繼續閱讀