プロダクト ニュース

メディア再生の強化: 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 を解放する必要があります。これを行うのに適した場所は、プレーヤーのリソースをすでに解放している場所です。プリロードが不要になった場合でもプレーヤーは再生を続行できるため、プレーヤーの前にマネージャーを解放することをおすすめします。

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

デモの時間

実際の動作を確認する 👍

以下のデモでは、右側に PreloadManager の影響が表示され、読み込み時間が短縮されています。左側には既存のエクスペリエンスが表示されています。デモのコードサンプルもご覧いただけます。(ボーナス: 各動画の起動レイテンシも表示されます)

Demo-PreloadManager_2.webp

次のステップ

パート 1 は以上です。これで、動的なプリロード システムを構築するためのツールが揃いました。PreloadConfiguration を使用して ExoPlayer の再生リストの次のアイテムをプリロードするか、DefaultPreloadManager を設定して、アイテムを随時追加および削除し、ターゲットのプリロード ステータスを構成して、プリロードされたコンテンツを正しく取得して再生できます。

パート 2では、DefaultPreloadManagerについて詳しく説明します。プリロード イベントをリッスンする方法、スライディング ウィンドウを使用してメモリの問題を回避するなどのベスト プラクティスについて説明し、ExoPlayer と DefaultPreloadManager のカスタム共有コンポーネントについて詳しく説明します。

フィードバックをお寄せください。皆様からのご意見をお待ちしております。

今後の情報にご期待ください。アプリの高速化に取り組みましょう。🚀

作成者:

続きを読む