メディアのダウンロード

ExoPlayer には、オフライン再生用にメディアをダウンロードする機能が用意されています。ほとんどのユースケースでは、アプリがバックグラウンドにある場合でもダウンロードを継続することが望ましいです。このようなユースケースでは、アプリは DownloadService をサブクラス化し、サービスにコマンドを送信してダウンロードの追加、削除、制御を行う必要があります。次の図は、関連するメインクラスを示しています。

メディアをダウンロードするためのクラス。矢印の方向は、データの流れを示します。

  • DownloadService: DownloadManager をラップし、コマンドを転送します。このサービスにより、アプリがバックグラウンドにある場合でも DownloadManager を実行し続けることができます。
  • DownloadManager: 複数のダウンロードを管理し、DownloadIndex から(および DownloadIndex に)状態を読み込んで(および保存して)、ネットワーク接続などの要件に基づいてダウンロードを開始および停止します。コンテンツをダウンロードするために、マネージャーは通常、ダウンロードされるデータを HttpDataSource から読み取り、Cache に書き込みます。
  • DownloadIndex: ダウンロードの状態を保持します。

DownloadService の作成

DownloadService を作成するには、サブクラス化して抽象メソッドを実装します。

  • getDownloadManager(): 使用する DownloadManager を返します。
  • getScheduler(): オプションの Scheduler を返します。これは、保留中のダウンロードを進めるために必要な要件が満たされたときにサービスを再起動できます。ExoPlayer は次の実装を提供します。
    • PlatformSchedulerJobScheduler を使用します(最小 API は 21)。アプリの権限要件については、PlatformScheduler の javadoc をご覧ください。
    • WorkManagerSchedulerWorkManager を使用)。
  • getForegroundNotification(): サービスがフォアグラウンドで実行されているときに表示される通知を返します。DownloadNotificationHelper.buildProgressNotification を使用して、デフォルト スタイルの通知を作成できます。

最後に、AndroidManifest.xml ファイルでサービスを定義します。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<application>
  <service android:name="com.myapp.MyDownloadService"
      android:exported="false"
      android:foregroundServiceType="dataSync">
    <!-- This is needed for Scheduler -->
    <intent-filter>
      <action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
      <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
  </service>
</application>

具体的な例については、ExoPlayer デモアプリの DemoDownloadServiceAndroidManifest.xml をご覧ください。

DownloadManager の作成

次のコード スニペットは、DownloadManager をインスタンス化する方法を示しています。これは、DownloadServicegetDownloadManager() によって返される可能性があります。

Kotlin

// Note: This should be a singleton in your app.
val databaseProvider = StandaloneDatabaseProvider(context)

// A download cache should not evict media, so should use a NoopCacheEvictor.
val downloadCache = SimpleCache(downloadDirectory, NoOpCacheEvictor(), databaseProvider)

// Create a factory for reading the data from the network.
val dataSourceFactory = DefaultHttpDataSource.Factory()

// Choose an executor for downloading data. Using Runnable::run will cause each download task to
// download data on its own thread. Passing an executor that uses multiple threads will speed up
// download tasks that can be split into smaller parts for parallel execution. Applications that
// already have an executor for background downloads may wish to reuse their existing executor.
val downloadExecutor = Executor(Runnable::run)

// Create the download manager.
val downloadManager =
  DownloadManager(context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor)

// Optionally, properties can be assigned to configure the download manager.
downloadManager.requirements = requirements
downloadManager.maxParallelDownloads = 3

Java

// Note: This should be a singleton in your app.
databaseProvider = new StandaloneDatabaseProvider(context);

// A download cache should not evict media, so should use a NoopCacheEvictor.
downloadCache = new SimpleCache(downloadDirectory, new NoOpCacheEvictor(), databaseProvider);

// Create a factory for reading the data from the network.
dataSourceFactory = new DefaultHttpDataSource.Factory();

// Choose an executor for downloading data. Using Runnable::run will cause each download task to
// download data on its own thread. Passing an executor that uses multiple threads will speed up
// download tasks that can be split into smaller parts for parallel execution. Applications that
// already have an executor for background downloads may wish to reuse their existing executor.
Executor downloadExecutor = Runnable::run;

// Create the download manager.
downloadManager =
    new DownloadManager(
        context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor);

// Optionally, setters can be called to configure the download manager.
downloadManager.setRequirements(requirements);
downloadManager.setMaxParallelDownloads(3);

具体的な例については、デモアプリの DemoUtil をご覧ください。

ダウンロードを追加する

ダウンロードを追加するには、DownloadRequest を作成して DownloadService に送信します。アダプティブ ストリームの場合、DownloadHelper を使用して DownloadRequest の構築を支援します。次の例は、ダウンロード リクエストを作成する方法を示しています。

Kotlin

val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()

Java

DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();

この例では、contentId はコンテンツの一意の識別子です。シンプルなケースでは、contentUricontentId として使用できることがよくありますが、アプリはユースケースに最適な ID スキームを自由に使用できます。DownloadRequest.Builder には、省略可能なセッターもあります。たとえば、setKeySetIdsetData を使用して、アプリがダウンロードに関連付けたい DRM とカスタムデータをそれぞれ設定できます。contentUri からコンテンツ タイプを推測できない場合、ヒントとして setMimeType を使用してコンテンツの MIME タイプを指定することもできます。

作成したリクエストは、DownloadService に送信してダウンロードを追加できます。

Kotlin

DownloadService.sendAddDownload(
  context,
  MyDownloadService::class.java,
  downloadRequest,
  /* foreground= */ false,
)

Java

DownloadService.sendAddDownload(
    context, MyDownloadService.class, downloadRequest, /* foreground= */ false);

この例では、MyDownloadService はアプリの DownloadService サブクラスであり、foreground パラメータはサービスがフォアグラウンドで開始されるかどうかを制御します。アプリがすでにフォアグラウンドにある場合、DownloadService は実行する作業があると判断するとフォアグラウンドに移行するため、通常は foreground パラメータを false に設定する必要があります。

ダウンロードを削除しています

ダウンロードを削除するには、DownloadService に削除コマンドを送信します。ここで、contentId は削除するダウンロードを識別します。

Kotlin

DownloadService.sendRemoveDownload(
  context,
  MyDownloadService::class.java,
  contentId,
  /* foreground= */ false,
)

Java

DownloadService.sendRemoveDownload(
    context, MyDownloadService.class, contentId, /* foreground= */ false);

DownloadService.sendRemoveAllDownloads を使用して、ダウンロードしたすべてのデータを削除することもできます。

ダウンロードの開始と停止

ダウンロードは、次の 4 つの条件が満たされた場合にのみ進行します。

  • ダウンロードに停止理由がありません。
  • ダウンロードは一時停止されません。
  • ダウンロードを進めるための要件が満たされている。要件では、許可されるネットワーク タイプに関する制約や、デバイスがアイドル状態であるか、充電器に接続されているかを指定できます。
  • 並列ダウンロードの最大数を超えていない。

これらの条件はすべて、DownloadService にコマンドを送信することで制御できます。

ダウンロード停止理由の設定とクリア

1 つまたはすべてのダウンロードが停止した理由を設定できます。

Kotlin

// Set the stop reason for a single download.
DownloadService.sendSetStopReason(
  context,
  MyDownloadService::class.java,
  contentId,
  stopReason,
  /* foreground= */ false,
)

// Clear the stop reason for a single download.
DownloadService.sendSetStopReason(
  context,
  MyDownloadService::class.java,
  contentId,
  Download.STOP_REASON_NONE,
  /* foreground= */ false,
)

Java

// Set the stop reason for a single download.
DownloadService.sendSetStopReason(
    context, MyDownloadService.class, contentId, stopReason, /* foreground= */ false);

// Clear the stop reason for a single download.
DownloadService.sendSetStopReason(
    context,
    MyDownloadService.class,
    contentId,
    Download.STOP_REASON_NONE,
    /* foreground= */ false);

stopReason には 0 以外の任意の値を指定できます(Download.STOP_REASON_NONE = 0 は、ダウンロードが停止しないことを意味する特別な値です)。ダウンロードを停止する理由が複数あるアプリは、さまざまな値を使用して、各ダウンロードが停止した理由を追跡できます。すべてのダウンロードの停止理由の設定とクリアは、単一のダウンロードの停止理由の設定とクリアと同じように機能しますが、contentIdnull に設定する必要があります。

ダウンロードにゼロ以外の停止理由がある場合、Download.STATE_STOPPED 状態になります。停止理由は DownloadIndex に保持されるため、アプリプロセスが強制終了されて後で再起動された場合でも保持されます。

すべてのダウンロードを一時停止、再開する

すべてのダウンロードは、次のように一時停止と再開が可能です。

Kotlin

// Pause all downloads.
DownloadService.sendPauseDownloads(
  context,
  MyDownloadService::class.java,
  /* foreground= */ false,
)

// Resume all downloads.
DownloadService.sendResumeDownloads(
  context,
  MyDownloadService::class.java,
  /* foreground= */ false,
)

Java

// Pause all downloads.
DownloadService.sendPauseDownloads(context, MyDownloadService.class, /* foreground= */ false);

// Resume all downloads.
DownloadService.sendResumeDownloads(context, MyDownloadService.class, /* foreground= */ false);

ダウンロードが一時停止されると、Download.STATE_QUEUED 状態になります。停止理由を設定する場合とは異なり、このアプローチでは状態の変更は保持されません。これは DownloadManager の実行時の状態にのみ影響します。

ダウンロードを進めるための要件を設定する

Requirements を使用すると、ダウンロードを続行するために満たす必要のある制約を指定できます。要件は、上記の例のように、DownloadManager の作成時に DownloadManager.setRequirements() を呼び出すことで設定できます。また、DownloadService にコマンドを送信して動的に変更することもできます。

Kotlin

// Set the download requirements.
DownloadService.sendSetRequirements(
  context,
  MyDownloadService::class.java,
  requirements,
  /* foreground= */ false,
)

Java

// Set the download requirements.
DownloadService.sendSetRequirements(
    context, MyDownloadService.class, requirements, /* foreground= */ false);

要件が満たされていないためにダウンロードを続行できない場合、ダウンロードは Download.STATE_QUEUED 状態になります。満たされていない要件は DownloadManager.getNotMetRequirements() でクエリできます。

並列ダウンロードの最大数を設定する

並列ダウンロードの最大数は、DownloadManager.setMaxParallelDownloads() を呼び出すことで設定できます。通常、これは DownloadManager の作成時に行われます(上記の例を参照)。

並行ダウンロードの最大数がすでに進行中のためダウンロードを続行できない場合、状態は Download.STATE_QUEUED になります。

ダウンロードのクエリ

DownloadManagerDownloadIndex をクエリして、完了または失敗したダウンロードを含む、すべてのダウンロードの状態を取得できます。DownloadIndex は、DownloadManager.getDownloadIndex() を呼び出すことで取得できます。DownloadIndex.getDownloads() を呼び出すことで、すべてのダウンロードを反復処理するカーソルを取得できます。また、DownloadIndex.getDownload() を呼び出すことで、単一のダウンロードの状態をクエリすることもできます。

DownloadManagerDownloadManager.getCurrentDownloads() も提供します。これは、現在の(完了または失敗していない)ダウンロードの状態のみを返します。このメソッドは、現在のダウンロードの進行状況とステータスを表示する通知やその他の UI コンポーネントを更新するのに便利です。

ダウンロードを聴く

現在のダウンロードの状態が変化したときに通知を受け取るように、DownloadManager にリスナーを追加できます。

Kotlin

downloadManager.addListener(
  object : DownloadManager.Listener { // Override methods of interest here.
  }
)

Java

downloadManager.addListener(
    new DownloadManager.Listener() {
      // Override methods of interest here.
    });

具体的な例については、デモアプリの DownloadTracker クラスの DownloadManagerListener をご覧ください。

ダウンロードしたコンテンツの再生

ダウンロードしたコンテンツの再生は、ネットワーク経由ではなくダウンロード Cache からデータを読み取る点を除き、オンライン コンテンツの再生と似ています。

ダウンロードしたコンテンツを再生するには、ダウンロードに使用したのと同じ Cache インスタンスを使用して CacheDataSource.Factory を作成し、プレーヤーのビルド時に DefaultMediaSourceFactory に挿入します。

Kotlin

// Create a read-only cache data source factory using the download cache.
val cacheDataSourceFactory: DataSource.Factory =
  CacheDataSource.Factory()
    .setCache(downloadCache)
    .setUpstreamDataSourceFactory(httpDataSourceFactory)
    .setCacheWriteDataSinkFactory(null) // Disable writing.

val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory)
    )
    .build()

Java

// Create a read-only cache data source factory using the download cache.
DataSource.Factory cacheDataSourceFactory =
    new CacheDataSource.Factory()
        .setCache(downloadCache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory)
        .setCacheWriteDataSinkFactory(null); // Disable writing.

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

同じプレーヤー インスタンスを使用してダウンロードされていないコンテンツも再生する場合は、再生中にそのコンテンツもダウンロードされないように、CacheDataSource.Factory を読み取り専用として構成する必要があります。

プレーヤーが CacheDataSource.Factory で構成されると、ダウンロードされたコンテンツにアクセスして再生できるようになります。ダウンロードを再生するには、対応する MediaItem をプレーヤーに渡すだけです。MediaItem は、Download.request.toMediaItem を使用して Download から取得することも、DownloadRequest.toMediaItem を使用して DownloadRequest から直接取得することもできます。

MediaSource の構成

上記の例では、すべての MediaItem の再生でダウンロード キャッシュを使用できるようにしています。ダウンロード キャッシュを個々の MediaSource インスタンスで使用できるようにすることもできます。このインスタンスはプレーヤーに直接渡すことができます。

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(cacheDataSourceFactory)
    .createMediaSource(MediaItem.fromUri(contentUri))
player.setMediaSource(mediaSource)
player.prepare()

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
        .createMediaSource(MediaItem.fromUri(contentUri));
player.setMediaSource(mediaSource);
player.prepare();

アダプティブ ストリームのダウンロードと再生

アダプティブ ストリーム(DASH、SmoothStreaming、HLS など)には通常、複数のメディア トラックが含まれています。同じコンテンツを異なる品質(SD、HD、4K の動画トラックなど)で含むトラックが複数あることがよくあります。同じタイプのトラックが複数あり、それぞれ異なるコンテンツが含まれている場合もあります(異なる言語の音声トラックが複数あるなど)。

ストリーミング再生の場合、トラック セレクタを使用して、どのトラックを再生するかを選択できます。同様に、ダウンロードの場合、DownloadHelper を使用して、どのトラックをダウンロードするかを選択できます。DownloadHelper の一般的な使用方法は次のとおりです。

  1. DownloadHelper.Factory インスタンスを使用して DownloadHelper を構築します。ヘルパーを準備して、コールバックを待ちます。
  2. 必要に応じて、getMappedTrackInfogetTrackSelections を使用してデフォルトで選択されているトラックを検査し、clearTrackSelectionsreplaceTrackSelectionsaddTrackSelection を使用して調整します。
  3. getDownloadRequest を呼び出して、選択したトラックの DownloadRequest を作成します。リクエストは、上記のようにダウンロードを追加するために DownloadService に渡すことができます。
  4. release() を使用してヘルパーを解放します。

Kotlin

val downloadHelper =
  DownloadHelper.Factory()
    .setRenderersFactory(DefaultRenderersFactory(context))
    .setDataSourceFactory(dataSourceFactory)
    .create(MediaItem.fromUri(contentUri))
downloadHelper.prepare(callback)

Java

DownloadHelper downloadHelper =
    new DownloadHelper.Factory()
        .setRenderersFactory(new DefaultRenderersFactory(context))
        .setDataSourceFactory(dataSourceFactory)
        .create(MediaItem.fromUri(contentUri));
downloadHelper.prepare(callback);

ダウンロードしたアダプティブ コンテンツを再生するには、上記のようにプレーヤーを構成し、対応する MediaItem を渡す必要があります。

MediaItem をビルドするときは、MediaItem.localConfiguration.streamKeysDownloadRequest の値と一致するように設定して、プレーヤーがダウンロードされたトラックのサブセットのみを再生するようにする必要があります。Download.request.toMediaItemDownloadRequest.toMediaItem を使用して MediaItem をビルドすると、この処理が自動的に行われます。