產品最新消息

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

閱讀時間:8 分鐘
查看 Mayuri Khinvasara Khabya 的個人資料
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 必須使用相同的建構工具,確保兩者底層的元件能正確共用。

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

使用 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,從預先載入管理工具擷取 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 的自訂共用元件。

歡迎提供意見。期待收到您的回覆。

敬請期待,並著手提升應用程式速度!🚀

繼續閱讀