O ExoPlayer oferece a funcionalidade de fazer o download de mídia para reprodução off-line. Na maioria
dos casos de uso, é desejável que os downloads continuem mesmo quando o app estiver em
segundo plano. Para esses casos de uso, seu app precisa criar uma subclasse DownloadService
e
enviar comandos ao serviço para adicionar, remover e controlar os downloads. O diagrama a seguir mostra as principais classes envolvidas.
DownloadService
: encapsula umaDownloadManager
e encaminha comandos para ela. O serviço permite que oDownloadManager
continue em execução mesmo quando o app estiver em segundo plano.DownloadManager
: gerencia vários downloads, carregando (e armazenando) os estados de (e até) umaDownloadIndex
, iniciando e interrompendo downloads com base em requisitos como conectividade de rede e assim por diante. Para fazer o download do conteúdo, o gerenciador normalmente lê os dados que estão sendo transferidos por download de umHttpDataSource
e os grava em umCache
.DownloadIndex
: mantém os estados dos downloads.
Como criar um DownloadService
Para criar uma DownloadService
, crie uma subclasse e implemente os
métodos abstratos dela:
getDownloadManager()
: retorna oDownloadManager
a ser usado.getScheduler()
: retorna umScheduler
opcional, que poderá reiniciar o serviço quando os requisitos necessários para que os downloads pendentes sejam atendidos. O ExoPlayer oferece estas implementações:PlatformScheduler
, que usa JobScheduler (a API mínima é 21). Consulte os javadocs do PlatformScheduler para ver os requisitos de permissão do app.WorkManagerScheduler
, que usa o WorkManager.
getForegroundNotification()
: retorna uma notificação que será mostrada quando o serviço estiver em execução em primeiro plano. Você pode usarDownloadNotificationHelper.buildProgressNotification
para criar uma notificação no estilo padrão.
Por fim, defina o serviço no arquivo 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>
Consulte DemoDownloadService
e AndroidManifest.xml
no app de demonstração
do ExoPlayer para ver um exemplo concreto.
Como criar um DownloadManager
O snippet de código abaixo demonstra como instanciar um DownloadManager
,
que pode ser retornado por getDownloadManager()
no DownloadService
:
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);
Consulte DemoUtil
no app de demonstração para conferir um exemplo concreto.
Como adicionar um download
Para adicionar um download, crie um DownloadRequest
e envie-o para seu
DownloadService
. Para streams adaptáveis, use DownloadHelper
para ajudar
a criar um DownloadRequest
. O exemplo
a seguir mostra como criar uma solicitação de download:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
Neste exemplo, contentId
é um identificador exclusivo para o conteúdo. Em casos simples, o
contentUri
geralmente pode ser usado como o contentId
. No entanto, os apps são livres para usar
o esquema de ID mais adequado ao caso de uso. DownloadRequest.Builder
também tem
alguns setters opcionais. Por exemplo, setKeySetId
e setData
podem ser usados para
definir o DRM e dados personalizados que o app quer associar ao download,
respectivamente. O tipo MIME do conteúdo também pode ser especificado usando setMimeType
,
como uma dica para casos em que o tipo de conteúdo não pode ser inferido de contentUri
.
Depois de criada, a solicitação pode ser enviada ao DownloadService
para adicionar o
download:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
Nesse exemplo, MyDownloadService
é a subclasse DownloadService
do app, e o
parâmetro foreground
controla se o serviço será iniciado em
primeiro plano. Se o app já estiver em primeiro plano, o parâmetro foreground
normalmente será definido como false
, porque DownloadService
vai se
colocar em primeiro plano se determinar que tem trabalho a fazer.
Removendo downloads
Um download pode ser removido enviando um comando de remoção para DownloadService
,
em que contentId
identifica o download a ser removido:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
Também é possível remover todos os dados transferidos por download com
DownloadService.sendRemoveAllDownloads
.
Como iniciar e interromper downloads
Um download só será realizado se quatro condições forem atendidas:
- O download não tem um motivo para interromper.
- Os downloads não são pausados.
- Os requisitos para o progresso dos downloads foram atendidos. Os requisitos podem especificar restrições nos tipos de rede permitidos, bem como se o dispositivo deve estar inativo ou conectado a um carregador.
- O número máximo de downloads paralelos não foi excedido.
Todas essas condições podem ser controladas pelo envio de comandos para a
DownloadService
.
Como definir e limpar os motivos das interrupções de download
É possível definir um motivo para a interrupção de um ou todos os downloads:
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
pode ser qualquer valor diferente de zero, sendo Download.STOP_REASON_NONE = 0
um valor especial que significa que o download não é interrompido. Apps que têm
vários motivos para interromper downloads podem usar valores diferentes para acompanhar
por que cada download foi interrompido. Definir e limpar o motivo da interrupção de todos
os downloads funciona da mesma forma que definir e limpar o motivo da interrupção de um
único download, mas com a exceção de que contentId
precisa ser definido como null
.
Quando um download tem um motivo de parada diferente de zero, ele fica no
estado Download.STATE_STOPPED
. Os motivos de parada são mantidos no
DownloadIndex
e, portanto, serão mantidos se o processo do aplicativo for encerrado e
reiniciado posteriormente.
Pausar e retomar todos os downloads
Todos os downloads podem ser pausados e retomados da seguinte maneira:
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);
Quando os downloads são pausados, eles ficam no estado Download.STATE_QUEUED
.
Ao contrário da definição de motivos de parada, essa abordagem não mantém nenhuma mudança
de estado. Isso afeta apenas o estado do ambiente de execução da DownloadManager
.
Como definir os requisitos para andamento dos downloads
Requirements
pode ser usado para especificar restrições que precisam ser atendidas para
que os downloads prossigam. Os requisitos podem ser definidos chamando
DownloadManager.setRequirements()
ao criar o DownloadManager
, como no
exemplo acima. Eles também podem ser alterados dinamicamente, enviando um comando
para 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);
Quando um download não puder continuar porque os requisitos não foram atendidos, ele
ficará no estado Download.STATE_QUEUED
. É possível consultar os requisitos
não atendidos com DownloadManager.getNotMetRequirements()
.
Como definir o número máximo de downloads paralelos
O número máximo de downloads paralelos pode ser definido chamando
DownloadManager.setMaxParallelDownloads()
. Isso normalmente seria feito durante a criação do DownloadManager
, como no exemplo acima.
Quando o download não pode continuar porque o número máximo de downloads paralelos já está em andamento, ele fica no estado Download.STATE_QUEUED
.
Como consultar downloads
O DownloadIndex
de um DownloadManager
pode ser consultado para ver o estado de todos
os downloads, incluindo os que foram concluídos ou falharam. O DownloadIndex
pode ser obtido chamando DownloadManager.getDownloadIndex()
. Um cursor que
itera em todos os downloads pode ser recebido chamando
DownloadIndex.getDownloads()
. Como alternativa, o estado de um único download
pode ser consultado chamando DownloadIndex.getDownload()
.
DownloadManager
também fornece DownloadManager.getCurrentDownloads()
, que
retorna o estado apenas dos downloads atuais (ou seja, não concluídos ou com falha). Esse
método é útil para atualizar notificações e outros componentes da interface que mostram
o progresso e o status dos downloads atuais.
Como ouvir downloads
Você pode adicionar um listener a DownloadManager
para ser informado quando os downloads
atuais mudarem de estado:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
Consulte DownloadManagerListener
na classe DownloadTracker
do app de demonstração para
ver um exemplo concreto.
Exibindo conteúdo transferido por download
Reproduzir conteúdo salvo é semelhante a reproduzir conteúdo on-line. A diferença é que
os dados são lidos pela Cache
de download, e não pela rede.
Para abrir conteúdo transferido por download, crie um CacheDataSource.Factory
usando a mesma
instância de Cache
que foi usada para download e injete-a em
DefaultMediaSourceFactory
ao criar o player:
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();
Se a mesma instância do player também for usada para reproduzir conteúdo não transferido,
o CacheDataSource.Factory
precisará ser configurado como somente leitura para evitar
o download desse conteúdo durante a reprodução.
Depois de configurar o player com CacheDataSource.Factory
, ele
terá acesso ao conteúdo salvo para reprodução. Reproduzir um download é
tão simples quanto transmitir o MediaItem
correspondente ao player. Um MediaItem
pode ser recebido de um Download
usando Download.request.toMediaItem
ou
diretamente de um DownloadRequest
usando DownloadRequest.toMediaItem
.
Configuração do MediaSource
O exemplo anterior disponibiliza o cache de download para a reprodução de todos os
MediaItem
s. Você também pode disponibilizar o cache de download para
instâncias de MediaSource
individuais, que podem ser transmitidas diretamente ao player:
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();
Fazer o download e reproduzir streams adaptáveis
Os streams adaptáveis (por exemplo, DASH, SmoothStreaming e HLS) normalmente contêm várias faixas de mídia. Muitas vezes, há várias faixas com o mesmo conteúdo em diferentes qualidades (por exemplo, faixas de vídeo em SD, HD e 4K). Também pode haver várias faixas do mesmo tipo com conteúdos diferentes (por exemplo, várias faixas de áudio em idiomas diferentes).
Para reproduções de streaming, um seletor de faixa pode ser usado para escolher quais das
faixas são tocadas. Da mesma forma, para fazer o download, um DownloadHelper
pode ser usado para
escolher quais das faixas serão transferidas por download. O uso típico de um DownloadHelper
segue estas etapas:
- Crie um
DownloadHelper
usando um dos métodosDownloadHelper.forMediaItem
. Prepare o auxiliar e aguarde o callback.Kotlin
val downloadHelper = DownloadHelper.forMediaItem( context, MediaItem.fromUri(contentUri), DefaultRenderersFactory(context), dataSourceFactory ) downloadHelper.prepare(callback)
Java
DownloadHelper downloadHelper = DownloadHelper.forMediaItem( context, MediaItem.fromUri(contentUri), new DefaultRenderersFactory(context), dataSourceFactory); downloadHelper.prepare(callback);
- Se quiser, inspecione as faixas padrão selecionadas usando
getMappedTrackInfo
egetTrackSelections
e faça ajustes usandoclearTrackSelections
,replaceTrackSelections
eaddTrackSelection
. - Crie um
DownloadRequest
para as faixas selecionadas chamandogetDownloadRequest
. A solicitação pode ser transmitida ao seuDownloadService
para adicionar o download, conforme descrito acima. - Libere o auxiliar usando
release()
.
A reprodução de conteúdo adaptável transferido por download requer a configuração do player e
a transmissão do MediaItem
correspondente, conforme descrito acima.
Ao criar o MediaItem
, MediaItem.localConfiguration.streamKeys
precisa ser
definido para corresponder aos do DownloadRequest
para que o jogador tente
apenas tocar o subconjunto de faixas que foram transferidas por download. Use
Download.request.toMediaItem
e DownloadRequest.toMediaItem
para criar o
MediaItem
para fazer isso.