カスタマイズ

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

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

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

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

コンポーネントを挿入してプレーヤーをカスタマイズする一般的な例を、以降のセクションで説明します。

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

ExoPlayer で使用されるネットワーク スタックのカスタマイズに関するページをご覧ください。

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

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

サーバーとのやり取りのカスタマイズ

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

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

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: LoadErrorHandlingPolicy.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(LoadErrorHandlingPolicy.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);

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();

レンダラを直接インスタンス化する場合は、new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() を MediaCodecVideoRenderer コンストラクタと MediaCodecAudioRenderer コンストラクタに渡します。

ForwardingSimpleBasePlayer を使用したオペレーションのカスタマイズ

Player インスタンスの動作の一部は、ForwardingSimpleBasePlayer のサブクラスでラップすることでカスタマイズできます。このクラスを使用すると、Player メソッドを直接実装するのではなく、特定の「オペレーション」をインターセプトできます。これにより、play()、pause()、setPlayWhenReady(boolean) などの動作の一貫性が確保されます。また、すべての状態変更が登録済みの Player.Listener インスタンスに正しく伝播されるようにします。ほとんどのカスタマイズ ユースケースでは、整合性の保証により、エラーが発生しやすい ForwardingPlayer よりも ForwardingSimpleBasePlayer を優先する必要があります。

たとえば、再生の開始時または停止時にカスタム ロジックを追加するには、次のようにします。

Kotlin

class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady)
  }
}

Java

public static final class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer {

  public PlayerWithCustomPlay(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady);
  }
}

または、SEEK_TO_NEXT コマンドを禁止する(Player.seekToNext が何も行わないようにする)には、次のようにします。

Kotlin

class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun getState(): State {
    val state = super.getState()
    return state
      .buildUpon()
      .setAvailableCommands(
        state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()
      )
      .build()
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

Java

public static final class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer {

  public PlayerWithoutSeekToNext(Player player) {
    super(player);
  }

  @Override
  protected State getState() {
    State state = super.getState();
    return state
        .buildUpon()
        .setAvailableCommands(
            state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build())
        .build();
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

MediaSource のカスタマイズ

上記の例では、プレーヤーに渡されるすべての MediaItem オブジェクトの再生中に使用するカスタマイズされたコンポーネントを挿入しています。きめ細かいカスタマイズが必要な場合は、カスタマイズしたコンポーネントを個々の MediaSource インスタンスに挿入して、プレーヤーに直接渡すこともできます。次の例は、カスタムの DataSource.Factory、ExtractorsFactory、LoadErrorHandlingPolicy を使用するように 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 を使用してコンポーネントに送信することで、構成の変更を渡す必要があります。再生スレッドで配信されるメッセージを送信すると、プレーヤーで実行されている他のオペレーションとともに、メッセージが順番に実行されます。