ExoPlayer は、オフライン再生用にメディアをダウンロードする機能を提供します。ほとんどのユースケースでは、アプリがバックグラウンドにある場合でもダウンロードを続行することが望まれます。このようなユースケースでは、アプリで DownloadService
のサブクラスを作成し、サービスにコマンドを送信してダウンロードの追加、削除、制御を行う必要があります。次の図は、関連する主なクラスを示しています。
DownloadService
:DownloadManager
をラップし、コマンドを転送します。このサービスにより、アプリがバックグラウンドにある場合でもDownloadManager
の実行を継続できます。DownloadManager
: 複数のダウンロード、DownloadIndex
との間での状態の読み込み(および保存)、ネットワーク接続などの要件に基づくダウンロードの開始と停止を管理します。コンテンツをダウンロードするために、マネージャーは通常、HttpDataSource
からダウンロードされるデータを読み取り、Cache
に書き込みます。DownloadIndex
: ダウンロードの状態を保持します。
DownloadService の作成
DownloadService
を作成するには、サブクラスを作成し、抽象メソッドを実装します。
getDownloadManager()
: 使用するDownloadManager
を返します。getScheduler()
: オプションのScheduler
を返します。このScheduler
は、保留中のダウンロードの進行に必要な要件が満たされたときにサービスを再起動できます。ExoPlayer には、次の実装が用意されています。PlatformScheduler
: JobScheduler を使用します(最小 API は 21)。アプリの権限の要件については、PlatformScheduler の javadocs をご覧ください。WorkManagerScheduler
: WorkManager を使用します。
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 デモアプリの DemoDownloadService
と AndroidManifest.xml
をご覧ください。
DownloadManager を作成する
次のコード スニペットは、DownloadService
の getDownloadManager()
によって返される DownloadManager
をインスタンス化する方法を示しています。
// 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
// 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
をビルドします。次の例は、ダウンロード リクエストを作成する方法を示しています。
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
この例で、contentId
はコンテンツの一意の識別子です。単純なケースでは、多くの場合、contentUri
を contentId
として使用できますが、アプリはユースケースに最適な ID スキームを自由に使用できます。DownloadRequest.Builder
には、オプションのセッターもあります。たとえば、setKeySetId
と setData
を使用して、アプリがダウンロードに関連付ける DRM とカスタムデータをそれぞれ設定できます。コンテンツの MIME タイプは、contentUri
からコンテンツ タイプを推測できない場合にヒントとして setMimeType
を使用して指定することもできます。
作成したら、リクエストを DownloadService
に送信してダウンロードを追加できます。
DownloadService.sendAddDownload(
context,
MyDownloadService::class.java,
downloadRequest,
/* foreground= */ false
)
DownloadService.sendAddDownload(
context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
この例では、MyDownloadService
はアプリの DownloadService
サブクラスであり、foreground
パラメータはサービスがフォアグラウンドで開始されるかどうかを制御します。アプリがすでにフォアグラウンドにある場合は、通常、foreground
パラメータを false
に設定する必要があります。これは、DownloadService
が処理する必要があると判断すると、自身をフォアグラウンドに配置するためです。
ダウンロードを削除しています
ダウンロードを削除するには、Remove コマンドを DownloadService
に送信します。ここで、contentId
には削除するダウンロードを指定します。
DownloadService.sendRemoveDownload(
context,
MyDownloadService::class.java,
contentId,
/* foreground= */ false
)
DownloadService.sendRemoveDownload(
context, MyDownloadService.class, contentId, /* foreground= */ false);
DownloadService.sendRemoveAllDownloads
を使用して、ダウンロードしたデータをすべて削除することもできます。
ダウンロードの開始と停止
ダウンロードが開始されるのは、次の 4 つの条件が満たされた場合のみです。
- ダウンロードに停止の理由がない。
- ダウンロードが一時停止されていない。
- ダウンロードを続行するための要件が満たされている。要件では、許可されるネットワーク タイプに関する制約や、デバイスがアイドル状態か充電器に接続されているかを指定できます。
- 並列ダウンロードの最大数を超えていません。
これらの条件はすべて、DownloadService
にコマンドを送信することで制御できます。
ダウンロード停止の理由の設定と消去
1 つまたは複数のダウンロードを停止する理由を設定できます。
// 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
)
// 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
には、ゼロ以外の任意の値を指定できます(Download.STOP_REASON_NONE = 0
は、ダウンロードが停止されていないことを意味する特別な値です)。ダウンロードを停止する理由が複数あるアプリでは、さまざまな値を使用して、各ダウンロードが停止した理由を追跡できます。すべてのダウンロードの停止理由の設定と消去は、単一のダウンロードの停止理由の設定と消去と同じ方法で行えます。ただし、contentId
は null
に設定する必要があります。
ダウンロードの停止理由がゼロ以外の場合は、Download.STATE_STOPPED
状態になります。停止理由は DownloadIndex
に保持されるため、アプリプロセスが強制終了されてから再起動された場合でも保持されます。
すべてのダウンロードを一時停止、再開する
すべてのダウンロードは一時停止と再開が可能です。手順は次のとおりです。
// Pause all downloads.
DownloadService.sendPauseDownloads(
context,
MyDownloadService::class.java,
/* foreground= */ false
)
// Resume all downloads.
DownloadService.sendResumeDownloads(
context,
MyDownloadService::class.java,
/* foreground= */ false
)
// 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
にコマンドを送信して動的に変更することもできます。
// Set the download requirements.
DownloadService.sendSetRequirements(
context, MyDownloadService::class.java, requirements, /* foreground= */ false)
// Set the download requirements.
DownloadService.sendSetRequirements(
context,
MyDownloadService.class,
requirements,
/* foreground= */ false);
要件が満たされていないためにダウンロードを続行できない場合、ダウンロードは Download.STATE_QUEUED
状態になります。満たされていない要件は DownloadManager.getNotMetRequirements()
でクエリできます。
並列ダウンロードの最大数の設定
並列ダウンロードの最大数は、DownloadManager.setMaxParallelDownloads()
を呼び出すことで設定できます。これは通常、上記の例のように、DownloadManager
の作成時に行われます。
並列ダウンロードの最大数がすでに進行中であるためにダウンロードを続行できない場合は、ダウンロードが Download.STATE_QUEUED
状態になります。
ダウンロードのクエリ
DownloadManager
の DownloadIndex
に対してクエリを実行すると、完了したダウンロードや失敗したダウンロードなど、すべてのダウンロードのステータスを確認できます。DownloadIndex
は DownloadManager.getDownloadIndex()
を呼び出すことで取得できます。すべてのダウンロードに対して反復処理を行うカーソルは、DownloadIndex.getDownloads()
を呼び出すことによって取得できます。また、DownloadIndex.getDownload()
を呼び出して 1 回のダウンロードの状態を照会することもできます。
DownloadManager
には DownloadManager.getCurrentDownloads()
も用意されています。これは、現在のダウンロード(完了していないダウンロードや失敗したダウンロード)のステータスのみを返します。このメソッドは、現在のダウンロードの進行状況とステータスを表示する通知やその他の UI コンポーネントを更新する場合に便利です。
ダウンロードしたコンテンツの再生
DownloadManager
にリスナーを追加すると、現在のダウンロードの状態が変更されたときに通知を受け取ることができます。
downloadManager.addListener(
object : DownloadManager.Listener { // Override methods of interest here.
}
)
downloadManager.addListener(
new DownloadManager.Listener() {
// Override methods of interest here.
});
具体的な例については、デモアプリの DownloadTracker
クラスの DownloadManagerListener
をご覧ください。
ダウンロードしたコンテンツの再生
ダウンロードされたコンテンツの再生は、オンライン コンテンツの再生と似ていますが、データがネットワーク経由ではなくダウンロード Cache
から読み取られる点が異なります。
ダウンロードしたコンテンツを再生するには、ダウンロードに使用したのと同じ Cache
インスタンスを使用して CacheDataSource.Factory
を作成し、プレーヤーのビルド時に DefaultMediaSourceFactory
に挿入します。
// 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()
// 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
インスタンスでダウンロード キャッシュを利用できるようにし、これをプレーヤーに直接渡すこともできます。
val mediaSource =
ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(MediaItem.fromUri(contentUri))
player.setMediaSource(mediaSource)
player.prepare()
ProgressiveMediaSource mediaSource =
new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(MediaItem.fromUri(contentUri));
player.setMediaSource(mediaSource);
player.prepare();
アダプティブ ストリームのダウンロードと再生
アダプティブ ストリーム(DASH、SmoothStreaming、HLS など)には通常、複数のメディア トラックが含まれています。多くの場合、同じコンテンツが異なる品質(SD、HD、4K の動画トラックなど)で複数のトラックに含まれています。同じタイプのトラックに異なるコンテンツが含まれている場合もあります(異なる言語の複数のオーディオ トラックなど)。
ストリーミング再生では、トラックセレクタを使用して再生するトラックを選択できます。同様に、ダウンロードでは DownloadHelper
を使用して、ダウンロードするトラックを選択できます。DownloadHelper
の一般的な使用方法は次のとおりです。
DownloadHelper.forMediaItem
メソッドのいずれかを使用してDownloadHelper
を作成します。ヘルパーを準備し、コールバックを待ちます。val downloadHelper =
DownloadHelper.forMediaItem(
context,
MediaItem.fromUri(contentUri),
DefaultRenderersFactory(context),
dataSourceFactory
)
downloadHelper.prepare(callback)DownloadHelper downloadHelper =
DownloadHelper.forMediaItem(
context,
MediaItem.fromUri(contentUri),
new DefaultRenderersFactory(context),
dataSourceFactory);
downloadHelper.prepare(callback);- 必要に応じて、
getMappedTrackInfo
とgetTrackSelections
を使用して、デフォルトで選択されているトラックを調べ、clearTrackSelections
、replaceTrackSelections
、addTrackSelection
を使用して調整します。 getDownloadRequest
を呼び出して、選択したトラックのDownloadRequest
を作成します。上記のように、リクエストをDownloadService
に渡してダウンロードを追加できます。release()
を使用してヘルパーを解放します。
ダウンロードしたアダプティブ コンテンツを再生するには、前述のように、プレーヤーを設定して、対応する MediaItem
を渡す必要があります。
MediaItem
をビルドするときに、MediaItem.localConfiguration.streamKeys
を DownloadRequest
のものと一致するように設定して、プレーヤーがダウンロードされたトラックのサブセットのみを再生するようにする必要があります。これは、Download.request.toMediaItem
と DownloadRequest.toMediaItem
を使用して MediaItem
をビルドすることで解決します。