プロダクト ニュース

メディア再生の進化:Media3 によるプリロード機能の導入 - パート 1

8 分読了
Mayuri Khinvasara Khabya のプロフィールを表示
Mayuri Khinvasara Khabya Developer Relations Engineer

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

根本的な課題はレイテンシー(遅延時間)である。従来、ビデオプレーヤーは、ユーザーが再生する項目を選択してから初めて、接続、ダウンロード、解析、バッファリングといった処理を開始する。このような受動的なアプローチは、今日の短尺動画の文脈においては遅すぎる。解決策は、先手を打つことだ。ユーザーが次に何を見るかを予測し、事前にコンテンツを準備しておく必要がある。これがプリロードの本質です。

プリロードの主な利点は以下のとおりです。

  • 🚀 再生開始が速くなりました:動画は既に準備が整っているため、アイテム間の切り替えがスムーズになり、より迅速に開始できます。
  • 📉 バッファリングの削減: データを事前に読み込むことで、ネットワークの不具合などによる再生の停止がはるかに少なくなります。
  • ✨ 結果としてよりスムーズなユーザーエクスペリエンスが実現: 起動が速くなり、バッファリングが少なくなるため、ユーザーはよりスムーズでシームレスな操作を楽しむことができます。

この 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 ライブラリ内の新しい強力なスタンドアロンコンポーネントであり、特にプロアクティブなプリロードを行うように設計されています。潜在的なメディアソースのコレクションを管理し、ユーザーの現在位置からの近さに基づいて優先順位を付け、粒状プリロードする内容を制御できるため、短尺動画の動的なフィードなど、複雑なシナリオに適しています。

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の両方に同じ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. メディアアイテムを追加

フィードにコンテンツを追加する際には、managerに追跡する必要のあるメディアを通知する必要があります。初めて使用する場合は、プリロードしたいリスト全体を追加できます。その後は、必要に応じてリストに項目を 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 から取得したアイテムが null の場合、それは 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 は常に現在のインデックスに最も近いアイテムを優先的にプリロードできるようになります。

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 の効果(読み込み時間の短縮)が示されており、左側は既存のユーザーエクスペリエンスを示しています。デモ用のコードsampleもご覧いただけます。(おまけ:すべての動画の起動遅延時間も表示されます)

Demo-PreloadManager_2.webp

次のステップ

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

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

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

今後の情報にご期待ください。そして、あなたのアプリをより高速化しましょう!🚀

作成者:
続きを読む