プロダクト ニュース

メディア再生の強化: Media3 でのプリロードの導入 - パート 1

所要時間: 8 分
Mayuri Khinvasara Khabya
デベロッパー リレーション エンジニア

メディア中心のアプリでは、スムーズで中断のない再生エクスペリエンスを提供することが、優れたユーザー エクスペリエンスを実現するうえで重要です。ユーザーは、動画がすぐに始まり、一時停止することなくシームレスに再生されることを期待しています。

主な課題はレイテンシです。従来、動画プレーヤーは、ユーザーが再生するアイテムを選択した後にのみ、接続、ダウンロード、解析、バッファリングなどの処理を開始していました。このリアクティブなアプローチは、今日のショート動画のコンテキストでは遅すぎます。解決策は、プロアクティブに対応することです。ユーザーが次に視聴するコンテンツを予測し、事前に準備しておく必要があります。これがプリロードの本質です。

プリロードの主なメリットは次のとおりです。

  • 🚀 再生開始の高速化: 動画の準備がすでに完了しているため、アイテム間の切り替えが速くなり、すぐに再生を開始できます。
  • 📉 バッファリングの減少: データを事前に読み込むことで、ネットワークの不具合などによる再生の停止が大幅に減少します。
  • ✨ ユーザー エクスペリエンスの向上: 起動時間の短縮とバッファリングの減少により、ユーザーはよりスムーズでシームレスな操作を楽しめるようになります。

この 3 部構成のシリーズでは、Media3 のコンポーネントの(事前)読み込み用の強力なユーティリティを紹介し、詳しく解説します。

  • パート 1 では、Media3 で利用可能なさまざまなプリロード戦略の理解、PreloadConfiguration の有効化と DefaultPreloadManager の設定、アプリでアイテムをプリロードできるようにする、といった基礎について説明します。このブログを読み終える頃には、設定したランキングと再生時間でメディア アイテムをプリロードして再生できるようになっているはずです。
  • パート 2 では、DefaultPreloadManager のより高度なトピックについて説明します。リスナーを分析に使用する方法、スライディング ウィンドウ パターンや DefaultPreloadManager と ExoPlayer のカスタム共有コンポーネントなどのプロダクション レディな効果的な手法について説明します。
  • パート 3 では、DefaultPreloadManager を使用したディスク キャッシュについて詳しく説明します。

プリロードを活用しましょう。🦸‍♀️

プリロードの基本的な考え方は、メディア コンテンツが必要になる前に読み込むというシンプルなものです。ユーザーが次の動画にスワイプするまでに、動画の最初のセグメントがすでにダウンロードされて利用可能になり、すぐに再生できるようになっています。

レストランに例えてみましょう。忙しいキッチンでは、注文を待ってから玉ねぎを切り始めることはありません。🧅 事前に準備作業を行います。プリロードは、動画プレーヤーの準備作業です。

プリロードを有効にすると、再生バッファが次のアイテムに達する前にユーザーが次のアイテムにスキップしたときに、結合のレイテンシを最小限に抑えることができます。次のウィンドウの導入部が準備され、動画、音声、テキストのサンプルがバッファリングされます。プリロード部分は後ほどプレーヤーのキューに追加されます。バッファリングされたサンプルは、すぐに利用可能になり、コーデックにフィードしてレンダリングをすることができます。

Media3 には、プリロード用の 2 つの主要な API があり、それぞれ異なるユースケースに適しています。適切な API を選択することが最初のステップです。

1. PreloadConfiguration を使用してプレイリスト アイテムをプリロードする

これは、再生順序が予測可能な(一連のエピソードなど)プレイリストのような線形シーケンシャル メディアに適したシンプルなアプローチです。ExoPlayer のプレイリスト API を使用してメディア アイテムの完全なリストをプレーヤーに渡し、プレーヤーの PreloadConfiguration を設定すると、構成された順序で次のアイテムが自動的にプリロードされます。この API は、再生バッファが次のアイテムにすでに重複している状態でユーザーが次のアイテムにスキップしたときに、結合のレイテンシを最適化しようとします。

プリロードは、進行中の再生に必要なメディアが読み込まれていない場合にのみ開始されます。これにより、プリロードとメインの再生が帯域幅を奪い合うことがなくなります。

プリロードが必要かどうかまだわからない場合は、この API を使用して簡単に試すことができます。

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

上記の PreloadConfiguration では、プレーヤーは再生リスト内の次のアイテムの 5 秒間のメディアをプリロードしようとします。

オプトインした後、プレイリスト プリロードを再び無効にするには、PreloadConfiguration.DEFAULT を使用します。

  player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. PreloadManager を使用して動的リストをプリロードする

垂直フィードやカルーセルなど、ユーザー操作によって「次」のアイテムが決まる動的 UI には、PreloadManager API が適しています。これは、Media3 ExoPlayer ライブラリ内の新しい強力なスタンドアロン コンポーネントで、プロアクティブなプリロード専用に設計されています。潜在的な MediaSource のコレクションを管理し、ユーザーの現在位置に近い順に優先順位を付けます。また、プリロードする対象を細かく制御できるため、ショート動画の動的フィードなどの複雑なシナリオに適しています。

PreloadManager の設定

DefaultPreloadManager は、PreloadManager の標準実装です。

DefaultPreloadManager のビルダーは、DefaultPreloadManager と、プリロードされたコンテンツを再生する ExoPlayer インスタンスの両方をビルドできます。DefaultPreloadManager を作成するには、TargetPreloadStatusControl を渡す必要があります。プリロード マネージャーは、この 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. メディア アイテムを追加

フィードにデータを入力する際は、トラッキングする必要があるメディアをマネージャーに通知する必要があります。開始する場合は、プリロードするリスト全体を追加できます。その後、必要に応じてリストに 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 から取得したアイテムが null の場合、mediaItem がまだプリロードされていないか、PreloadMamager に追加されていないことを意味します。そのため、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 が破棄された場合など)、リソースを解放するために PreloadManager を解放する必要があります。この処理は、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 のカスタム共有コンポーネントの内部構造について説明します。

共有するフィードバックはありますか?皆様からのご意見をお待ちしております。

今後の情報にご期待ください。アプリの高速化にぜひ取り組んでみてください。🚀

作成者:

続きを読む