O ExoPlayer oferece funcionalidade para baixar 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, seu app precisa criar uma subclasse de 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 umDownloadManagere encaminha comandos para ele. O serviço permite que oDownloadManagercontinue sendo executado mesmo quando o app está em segundo plano.DownloadManager: gerencia vários downloads, carregando (e armazenando) os estados deles 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 baixados de umHttpDataSourcee os grava em umCache.DownloadIndex: persiste os estados dos downloads.
Como criar um DownloadService
Para criar uma DownloadService, crie uma subclasse e implemente os métodos abstratos dela:
getDownloadManager(): retorna oDownloadManagera ser usado.getScheduler(): retorna umScheduleropcional, que pode reiniciar o serviço quando os requisitos necessários para o progresso dos downloads pendentes forem atendidos. O ExoPlayer oferece estas implementações:PlatformScheduler, que usa o JobScheduler (a API mínima é a 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 a ser exibida quando o serviço está em execução em primeiro plano. É possível usarDownloadNotificationHelper.buildProgressNotificationpara 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 um exemplo concreto.
Como criar um DownloadManager
O snippet de código a seguir demonstra como instanciar um DownloadManager, que pode ser retornado por getDownloadManager() no seu 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.
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 do conteúdo. Em casos simples, o
contentUri pode ser usado como o 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 o DRM e os 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);
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 se colocará em primeiro plano se determinar que tem trabalho a fazer.
Removendo downloads
Para remover um download, envie um comando de remoção para o 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);
Você também pode remover todos os dados baixados com
DownloadService.sendRemoveAllDownloads.
Como iniciar e interromper downloads
Um download só vai progredir se quatro condições forem atendidas:
- O download não tem um motivo de interrupção.
- Os downloads não são pausados.
- Os requisitos para o andamento dos downloads são atendidos. Os requisitos podem especificar restrições nos tipos de rede permitidos, bem como se o dispositivo precisa estar ocioso 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 o seu
DownloadService.
Como definir e limpar motivos para interrupção de downloads
É possível definir um motivo para a interrupção de um ou de 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 é 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, 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.
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 interrupção, essa abordagem não mantém nenhuma mudança de estado. Isso afeta apenas o estado de execução do DownloadManager.
Definir os requisitos para o progresso dos downloads
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 mudadas 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 foram 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 é feito ao
criar o DownloadManager, como no exemplo acima.
Quando um download não pode ser concluído porque o número máximo de downloads paralelos já está em andamento, ele fica no estado Download.STATE_QUEUED.
Como consultar downloads
É possível consultar o DownloadIndex de um DownloadManager para saber 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 obtido 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 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 ser informado quando o estado dos downloads
atuais mudar:
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.
Reproduzir conteúdo baixado
A reprodução de conteúdo baixado é semelhante à reprodução de conteúdo on-line, exceto que
os dados são lidos do download Cache em vez de pela rede.
Para reproduzir o conteúdo baixado, crie um CacheDataSource.Factory usando a mesma
instância Cache usada para o 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 baixado,
o CacheDataSource.Factory precisa 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 baixado para reprodução. Para reproduzir um download, basta passar o MediaItem correspondente para o player. Um MediaItem pode ser obtido 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 reprodução de todos os
MediaItems. Também é possível disponibilizar o cache de download para
instâncias MediaSource individuais, 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 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 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 quais faixas serão tocadas. Da mesma forma, para fazer o download, um DownloadHelper pode ser usado para escolher quais músicas serão baixadas. O uso típico de um DownloadHelper
segue estas etapas:
- Crie um
DownloadHelperusando uma instânciaDownloadHelper.Factory. Prepare o auxiliar e aguarde o callback. - Se quiser, inspecione as faixas selecionadas por padrão usando
getMappedTrackInfoegetTrackSelectionse faça ajustes usandoclearTrackSelections,replaceTrackSelectionseaddTrackSelection. - Crie um
DownloadRequestpara as faixas selecionadas chamandogetDownloadRequest. A solicitação pode ser transmitida ao seuDownloadServicepara adicionar o download, conforme descrito acima. - Libere o auxiliar usando
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);
Para reproduzir conteúdo adaptável baixado, é necessário configurar o player e
transmitir o MediaItem correspondente, conforme descrito acima.
Ao criar o MediaItem, MediaItem.localConfiguration.streamKeys precisa ser
definido para corresponder aos do DownloadRequest. Assim, o player só tenta
reproduzir o subconjunto de faixas que foram baixadas. Usar
Download.request.toMediaItem e DownloadRequest.toMediaItem para criar o
MediaItem vai cuidar disso para você.