カスタマイズ

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

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

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

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

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

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

ExoPlayer が使用するネットワーク スタックのカスタマイズに関するページを用意しています。

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

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

サーバー操作のカスタマイズ

アプリによっては、HTTP リクエストと 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 メソッドを使用して、個々の extractor に設定できます。サポートするすべての extractor で固定ビットレート シークを有効にするには、DefaultExtractorsFactory.setConstantBitrateSeekingEnabled を使用します。

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

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

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

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

非同期バッファキューイングは、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 実装がすでに含まれています。カスタム プロトコル、カスタム HTTP スタック、カスタム永続キャッシュなど、別の方法でデータを読み込むために、独自の DataSource クラスを実装することもできます。

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

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