O ExoPlayer oferece a funcionalidade de download de mídia para reprodução off-line. Na maioria
dos casos de uso, é recomendável que os downloads continuem mesmo quando o app está em
segundo plano. Para esses casos de uso, o app precisa da 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 umDownloadManager
e encaminha comandos para ele. O serviço permite que oDownloadManager
continue em execução mesmo quando o app está em segundo plano.DownloadManager
: gerencia vários downloads, carregando (e armazenando) os estados de (e para) umDownloadIndex
, 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 um DownloadService
, crie uma subclasse e implemente os
métodos abstratos:
getDownloadManager()
: retorna oDownloadManager
a ser usado.getScheduler()
: retorna umScheduler
opcional, que pode reiniciar o serviço quando os requisitos necessários para que os downloads pendentes sejam concluídos forem atendidos. O ExoPlayer oferece estas implementações:PlatformScheduler
, que usa JobScheduler (a API mínima é 21). Consulte os javadocs do PlatformScheduler para requisitos de permissão do app.WorkManagerScheduler
, que usa o WorkManager.
getForegroundNotification()
: retorna uma notificação para ser exibida quando o serviço está em execução em primeiro plano. É possível 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 conferir um exemplo concreto.
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 ver um exemplo concreto.
Como adicionar um download
Para adicionar um download, crie um DownloadRequest
e envie-o para o
DownloadService
. Para transmissões adaptativas, use DownloadHelper
para
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 do conteúdo. Em casos simples, o
contentUri
pode ser usado como contentId
. No entanto, os apps podem usar
qualquer esquema de ID que melhor se adapte ao caso de uso. DownloadRequest.Builder
também tem
alguns setters opcionais. Por exemplo, setKeySetId
e setData
podem ser usados para
definir dados personalizados e de DRM 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);
Neste 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 o DownloadService
vai
colocar em primeiro plano se determinar que há trabalho a realizar.
Removendo downloads
É possível remover um download 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
DownloadService.sendRemoveAllDownloads
.
Como iniciar e interromper downloads
Um download só vai avançar se quatro condições forem atendidas:
- O download não tem um motivo para ser interrompido.
- Os downloads não são pausados.
- Os requisitos para que os downloads sejam concluídos são atendidos. Os requisitos podem especificar restrições sobre os tipos de rede permitidos, bem como se o dispositivo precisa 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 enviando comandos para a
DownloadService
.
Como definir e limpar motivos de interrupção 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 (Download.STOP_REASON_NONE = 0
é
um valor especial que significa que o download não foi 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 para todos
os downloads funciona da mesma forma que definir e limpar o motivo da interrupção para um
único download, exceto que contentId
precisa ser definido como null
.
Quando um download tem um motivo de interrupção diferente de zero, ele fica no
estado Download.STATE_STOPPED
. Os motivos de interrupção são mantidos no
DownloadIndex
e, portanto, são retidos se o processo do aplicativo for encerrado e
reiniciado mais tarde.
Como 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 configuração dos motivos da parada, essa abordagem não mantém nenhuma mudança
de estado. Isso afeta apenas o estado de execução do DownloadManager
.
Como definir os requisitos para que os downloads sejam concluídos
Requirements
pode ser usado para especificar restrições que precisam ser atendidas para
que os downloads continuem. Os requisitos podem ser definidos chamando
DownloadManager.setRequirements()
ao criar o DownloadManager
, como no
exemplo acima. Elas também podem ser alteradas dinamicamente enviando um comando
para o 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 pode ser concluído porque os requisitos não são atendidos, ele
fica 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 ao
criar o DownloadManager
, como no exemplo acima.
Quando um download não pode ser feito porque o número máximo de downloads paralelos
já está em andamento, ele fica no estado Download.STATE_QUEUED
.
Consultar downloads
É possível consultar o DownloadIndex
de um DownloadManager
para saber o estado de todos
os downloads, incluindo os concluídos ou com falha. O DownloadIndex
pode ser acessado chamando DownloadManager.getDownloadIndex()
. Então, chame DownloadIndex.getDownloads()
para receber um cursor que faz iterações em todos os downloads. Como alternativa, o estado de um único download
pode ser consultado chamando DownloadIndex.getDownload()
.
DownloadManager
também fornece DownloadManager.getCurrentDownloads()
, que
retorna apenas o estado 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.
Ouvir downloads
Você pode adicionar um listener a DownloadManager
para receber uma notificação 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
um exemplo concreto.
Como reproduzir conteúdo salvo
A reprodução do conteúdo salvo é semelhante à reprodução do conteúdo on-line, exceto pelo fato de que
os dados são lidos do Cache
de download em vez de pela rede.
Para reproduzir conteúdo salvo, crie um CacheDataSource.Factory
usando a mesma
instância de Cache
usada para download e injete-o 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 por download,
o CacheDataSource.Factory
precisará ser configurado como somente leitura para evitar
o download desse conteúdo também durante a reprodução.
Depois que o player for configurado com o CacheDataSource.Factory
, ele terá
acesso ao conteúdo salvo para reprodução. A reprodução de 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. Também é possível disponibilizar o cache de download para
instâncias individuais de MediaSource
, que podem ser transmitidas diretamente para o 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();
Como fazer o download e reproduzir transmissões 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 que contêm o mesmo conteúdo em qualidades diferentes (por exemplo, faixas de vídeo SD, HD e 4K). Também pode haver várias faixas do mesmo tipo com conteúdo diferente (por exemplo, várias faixas de áudio em idiomas diferentes).
Para reproduções de streaming, um seletor de faixas pode ser usado para escolher qual das
faixas serão tocadas. Da mesma forma, para fazer o download, um DownloadHelper
pode ser usado para
escolher quais 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 selecionadas padrão usando
getMappedTrackInfo
egetTrackSelections
e faça ajustes usandoclearTrackSelections
,replaceTrackSelections
eaddTrackSelection
. - Crie um
DownloadRequest
para as faixas selecionadas chamandogetDownloadRequest
. A solicitação pode ser transmitida para oDownloadService
para adicionar o download, conforme descrito acima. - Libere o auxiliar usando
release()
.
A reprodução do 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
, o MediaItem.localConfiguration.streamKeys
precisa ser
definido para corresponder aos da DownloadRequest
, para que o player tente
reproduzir apenas o subconjunto de faixas que foram transferidas por download. O uso de
Download.request.toMediaItem
e DownloadRequest.toMediaItem
para criar o
MediaItem
vai cuidar disso para você.