カスタマイズ

ExoPlayer ライブラリの中核は Player インターフェースです。Player は、メディアのバッファリング、再生、一時停止、シークなど、従来の高レベルのメディア プレーヤー機能を提供します。デフォルトの実装 ExoPlayer は、再生されるメディアの種類、保存方法と保存場所、レンダリング方法についてほとんど前提を持たないように設計されています(したがって、制限もほとんどありません)。ExoPlayer の実装では、メディアの読み込みとレンダリングを直接実装するのではなく、プレーヤーの作成時や新しいメディアソースがプレーヤーに渡されたときに挿入されるコンポーネントにこの処理を委任します。すべての ExoPlayer 実装に共通するコンポーネントは次のとおりです。

  • 再生するメディアを定義し、メディアを読み込んで、読み込まれたメディアを読み取り可能な MediaSource インスタンス。MediaSource インスタンスは、プレーヤー内の MediaSource.Factory によって MediaItem から作成されます。メディアソースベースのプレイリスト API を使用して、プレーヤーに直接渡すこともできます。
  • MediaItemMediaSource に変換する MediaSource.Factory インスタンス。MediaSource.Factory は、プレーヤーの作成時に挿入されます。
  • メディアの個々のコンポーネントをレンダリングする Renderer インスタンス。これらは、プレーヤーの作成時に挿入されます。
  • 利用可能な各 Renderer で使用される MediaSource から提供されるトラックを選択する TrackSelector。プレーヤーの作成時に TrackSelector が挿入されます。
  • MediaSource がメディアをバッファリングするタイミングと、バッファリングするメディアの量を制御する LoadControl。プレーヤーが作成されると、LoadControl が挿入されます。
  • ライブ再生中の再生速度を制御する LivePlaybackSpeedControl。これにより、プレーヤーは設定されたライブ オフセットに近づきます。LivePlaybackSpeedControl は、プレーヤーの作成時に挿入されます。

プレーヤー機能を実装するコンポーネントを注入するというコンセプトは、ライブラリ全体にあります。一部のコンポーネントのデフォルト実装では、挿入されたコンポーネントに作業を委任します。これにより、多くのサブコンポーネントを個別に、カスタム方法で構成された実装に置き換えることができます。

プレーヤーのカスタマイズ

コンポーネントを挿入してプレーヤーをカスタマイズする一般的な例を以下に示します。

ネットワーク スタックの構成

ExoPlayer で使用されるネットワーク スタックのカスタマイズに関するページがあります。

ネットワークから読み込まれたデータのキャッシュ保存

一時的なオンザフライ キャッシュメディアのダウンロードに関するガイドをご覧ください。

サーバー インタラクションのカスタマイズ

一部のアプリでは、HTTP リクエストとレスポンスをインターセプトする必要があります。カスタム リクエスト ヘッダーを挿入したり、サーバーのレスポンス ヘッダーを読み取ったり、リクエストの URI を変更したりする場合があります。たとえば、アプリは、メディア セグメントをリクエストするときにヘッダーとしてトークンを挿入することで、自身を認証できます。

次の例は、カスタム DataSource.FactoryDefaultMediaSourceFactory に挿入して、これらの動作を実装する方法を示しています。

Kotlin

val dataSourceFactory =
  DataSource.Factory {
    val dataSource = httpDataSourceFactory.createDataSource()
    // Set a custom authentication request header.
    dataSource.setRequestProperty("Header", "Value")
    dataSource
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

DataSource.Factory dataSourceFactory =
    () -> {
      HttpDataSource dataSource = httpDataSourceFactory.createDataSource();
      // Set a custom authentication request header.
      dataSource.setRequestProperty("Header", "Value");
      return dataSource;
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

上記のコード スニペットでは、挿入された HttpDataSource のすべての HTTP リクエストにヘッダー "Header: Value" が含まれています。この動作は、HTTP ソースとのやり取りごとに修正されます。

よりきめ細かいアプローチでは、ResolvingDataSource を使用してジャストインタイムの動作を挿入できます。次のコード スニペットは、HTTP ソースを操作する直前にリクエスト ヘッダーを挿入する方法を示しています。

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time request headers.
    dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri))
  }

Java

    DataSource.Factory dataSourceFactory =
        new ResolvingDataSource.Factory(
            httpDataSourceFactory,
            // Provide just-in-time request headers.
            dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));

次のスニペットに示すように、ResolvingDataSource を使用して URI のジャストインタイム変更を行うこともできます。

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time URI resolution logic.
    dataSpec.withUri(resolveUri(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time URI resolution logic.
        dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));

エラー処理のカスタマイズ

カスタム LoadErrorHandlingPolicy を実装すると、読み込みエラーに対する ExoPlayer の反応をアプリでカスタマイズできます。たとえば、アプリで何度も再試行するのではなく、早期に失敗させる場合や、プレーヤーが再試行するまでの待ち時間を制御するバックオフ ロジックをカスタマイズする場合などです。次のスニペットは、カスタム バックオフ ロジックを実装する方法を示しています。

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long {
      // Implement custom back-off logic here.
      return 0
    }
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
    )
    .build()

Java

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

LoadErrorInfo 引数には、失敗した読み込みに関する詳細情報が含まれ、エラータイプまたは失敗したリクエストに基づいてロジックをカスタマイズできます。

エクストラクタ フラグのカスタマイズ

エクストラクタのフラグを使用すると、プログレッシブ メディアから個々の形式を抽出する方法をカスタマイズできます。これらは、DefaultMediaSourceFactory に渡される DefaultExtractorsFactory に設定できます。次の例では、MP3 ストリームのインデックスベースのシーキングを有効にするフラグを渡します。

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
    .build()

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING);

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

固定ビットレートのシークを有効にする

MP3、ADTS、AMR ストリームの場合、FLAG_ENABLE_CONSTANT_BITRATE_SEEKING フラグを使用して一定のビットレートを想定して近似シークを有効にできます。これらのフラグは、上記のように個々の DefaultExtractorsFactory.setXyzExtractorFlags メソッドを使用して個々の抽出ツールに設定できます。これをサポートするすべてのエクストラクタで一定のビットレートのシークを有効にするには、DefaultExtractorsFactory.setConstantBitrateSeekingEnabled を使用します。

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

その後、上記の extractor フラグのカスタマイズで説明したように、DefaultMediaSourceFactory を介して ExtractorsFactory を挿入できます。

非同期バッファ キューイングを有効にする

非同期バッファ キューイングは、ExoPlayer のレンダリング パイプラインの機能強化です。MediaCodec インスタンスを非同期モードで動作させ、追加のスレッドを使用してデータのデコードとレンダリングをスケジュールします。これを有効にすると、フレーム落ちや音声アンダーランを減らすことができます。

非同期バッファ キューイングは、Android 12(API レベル 31)以降を搭載したデバイスではデフォルトで有効になっています。Android 6.0(API レベル 23)以降では、手動で有効にできます。フレーム落ちや音声の不足が発生している特定のデバイスで、この機能を有効にすることを検討してください。特に、DRM で保護されたコンテンツや高フレームレートのコンテンツを再生する場合は有効にすることをおすすめします。

最も単純なケースでは、次のようにプレーヤーに DefaultRenderersFactory を挿入する必要があります。

Kotlin

val renderersFactory = 
  DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing()
val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()

Java

DefaultRenderersFactory renderersFactory =
    new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();

レンダラを直接インスタンス化する場合は、AsynchronousMediaCodecAdapter.FactoryMediaCodecVideoRenderer コンストラクタと MediaCodecAudioRenderer コンストラクタに渡します。

ForwardingPlayer によるメソッド呼び出しのインターセプト

Player インスタンスの動作の一部は、次のいずれかを行うために ForwardingPlayer のサブクラスでラップし、メソッドをオーバーライドすることでカスタマイズできます。

  • パラメータにアクセスしてから、デリゲート Player に渡します。
  • デリゲート Player の戻り値にアクセスしてから、戻します。
  • メソッドを完全に再実装します。

ForwardingPlayer メソッドをオーバーライドする場合は、特に動作が同じまたは関連するメソッドを扱う場合は、実装が自己整合性を維持し、Player インターフェースに準拠していることを確認することが重要です。例:

  • すべての「再生」オペレーションをオーバーライドする場合は、ForwardingPlayer.playForwardingPlayer.setPlayWhenReady の両方をオーバーライドする必要があります。呼び出し元は、playWhenReady = true のときにこれらのメソッドの動作が同じであると期待するからです。
  • シーク フォワード インクリメントを変更するには、カスタマイズされたインクリメントでシークを実行する ForwardingPlayer.seekForward と、カスタマイズされた正しいインクリメントを呼び出し元に報告するために ForwardingPlayer.getSeekForwardIncrement の両方をオーバーライドする必要があります。
  • プレーヤー インスタンスによってアドバタイズされる Player.Commands を制御する場合は、Player.getAvailableCommands()Player.isCommandAvailable() の両方をオーバーライドし、Player.Listener.onAvailableCommandsChanged() コールバックをリッスンして、基盤となるプレーヤーからの変更を通知する必要があります。

MediaSource のカスタマイズ

上記の例では、プレーヤーに渡されるすべての MediaItem オブジェクトの再生中に使用するために、カスタマイズされたコンポーネントを挿入しています。きめ細かいカスタマイズが必要な場合は、カスタマイズされたコンポーネントを個々の MediaSource インスタンスに挿入することもできます。このインスタンスはプレーヤーに直接渡すことができます。次の例は、カスタム DataSource.FactoryExtractorsFactoryLoadErrorHandlingPolicy を使用するように ProgressiveMediaSource をカスタマイズする方法を示しています。

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
    .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
    .createMediaSource(MediaItem.fromUri(streamUri))

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
        .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
        .createMediaSource(MediaItem.fromUri(streamUri));

カスタム コンポーネントを作成する

このライブラリには、一般的なユースケース向けに、このページの上部に記載されているコンポーネントのデフォルト実装が用意されています。ExoPlayer はこれらのコンポーネントを使用できますが、標準以外の動作が必要な場合は、カスタム実装を使用するようにビルドすることもできます。カスタム実装のユースケースは次のとおりです。

  • Renderer - ライブラリで提供されるデフォルトの実装でサポートされていないメディアタイプを処理するために、カスタム Renderer を実装できます。
  • TrackSelector - カスタム TrackSelector を実装すると、アプリ デベロッパーは、MediaSource によって公開されたトラックが、利用可能な各 Renderer によって消費される方法を変更できます。
  • LoadControl - カスタム LoadControl を実装すると、アプリ デベロッパーがプレーヤーのバッファリング ポリシーを変更できるようになります。
  • Extractor - 現在ライブラリでサポートされていないコンテナ形式をサポートする必要がある場合は、カスタム Extractor クラスの実装を検討してください。
  • MediaSource - カスタム MediaSource クラスの実装は、レンダラにフィードするメディア サンプルをカスタム方法で取得する場合や、カスタム MediaSource 合成動作を実装する場合に適しています。
  • MediaSource.Factory - カスタム MediaSource.Factory を実装すると、アプリで MediaItem から MediaSource を作成する方法をカスタマイズできます。
  • DataSource - ExoPlayer のアップストリーム パッケージには、さまざまなユースケース向けの DataSource 実装がすでに含まれています。独自の DataSource クラスを実装して、カスタム プロトコル経由、カスタム HTTP スタックを使用、カスタム永続キャッシュからの読み込みなど、別の方法でデータを読み込むこともできます。

カスタム コンポーネントを作成する場合は、次のことをおすすめします。

  • カスタム コンポーネントがイベントをアプリに報告する必要がある場合は、既存の ExoPlayer コンポーネントと同じモデルを使用して報告することをおすすめします。たとえば、EventDispatcher クラスを使用するか、コンポーネントのコンストラクタに Handler をリスナーとともに渡します。
  • カスタム コンポーネントでは、既存の ExoPlayer コンポーネントと同じモデルを使用することをおすすめします。これにより、再生中にアプリによる再構成が可能になります。そのためには、カスタム コンポーネントで PlayerMessage.Target を実装し、handleMessage メソッドで構成変更を受け取る必要があります。アプリケーション コードは、ExoPlayer の createMessage メソッドを呼び出してメッセージを構成し、PlayerMessage.send を使用してコンポーネントに送信することで、構成変更を渡す必要があります。再生スレッドで配信されるメッセージを送信すると、プレーヤーで実行される他のオペレーションとともに順番に実行されます。