ExoPlayer позволяет скачивать медиафайлы для воспроизведения офлайн. В большинстве случаев желательно, чтобы скачивание продолжалось, даже когда приложение работает в фоновом режиме. В таких случаях приложению следует создать подкласс DownloadService и отправлять команды сервису, чтобы добавлять, удалять и контролировать скачивания. На приведенной ниже схеме показаны основные классы, которые используются в этом процессе.
DownloadService: обертка дляDownloadManager, которая пересылает ему команды. Сервис позволяет приложениюDownloadManagerработать в фоновом режиме.DownloadManager: управляет несколькими загрузками, загружая (и сохраняя) их состояния из (и в)DownloadIndex, а также запуская и останавливая загрузки в зависимости от требований, таких как подключение к сети. Чтобы скачать контент, менеджер обычно считывает данные изHttpDataSourceи записывает их вCache.DownloadIndex: сохраняет статусы скачивания.
Создание DownloadService
Чтобы создать DownloadService, создайте его подкласс и реализуйте его абстрактные методы:
getDownloadManager(): возвращает объектDownloadManager, который будет использоваться.getScheduler(): возвращает необязательный параметрScheduler, который может перезапустить сервис, когда будут выполнены требования, необходимые для продолжения скачивания. В ExoPlayer реализованы следующие функции:PlatformScheduler, в котором используетсяJobScheduler(минимальный уровень API – 21). Требования к разрешениям для приложений можно найти в документацииPlatformSchedulerJavadoc.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
В приведенном ниже фрагменте кода показано, как создать экземпляр DownloadManager, который может быть возвращен getDownloadManager() в вашем 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);
Пример можно найти в демонстрационном приложении в разделе DemoUtil.
Как добавить скачивание
Чтобы добавить скачивание, создайте DownloadRequest и отправьте его на DownloadService. Для адаптивных потоков используйте DownloadHelper, чтобы создать DownloadRequest. В примере ниже показано, как создать запрос на скачивание:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
В этом примере contentId – уникальный идентификатор контента. contentUri часто можно использовать в качестве contentId, однако приложения могут использовать любую схему идентификации, которая лучше всего подходит для их целей. У DownloadRequest.Builder также есть несколько необязательных методов установки. Например, setKeySetId и setData можно использовать для настройки DRM и специальных данных, которые приложение хочет связать со скачиванием, соответственно. MIME-тип контента также можно указать с помощью элемента setMimeType. Это может быть полезно, если MIME-тип нельзя определить по элементу contentUri.
После создания запроса его можно отправить в DownloadService, чтобы добавить возможность скачивания:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false, )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
В этом примере MyDownloadService – это подкласс DownloadService приложения, а параметр foreground определяет, будет ли служба запущена в активном режиме. Если приложение уже работает на переднем плане, параметр foreground обычно должен иметь значение false, поскольку DownloadService перейдет на передний план, если решит, что ему нужно выполнить какую-либо задачу.
Удаление скачанных файлов…
Чтобы удалить скачанный контент, отправьте команду remove в DownloadService, где contentId – идентификатор скачанного контента, который нужно удалить:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false, )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
Вы также можете удалить все скачанные данные с помощью DownloadService.sendRemoveAllDownloads.
Как начать и остановить скачивание
Скачивание будет выполняться, только если соблюдены четыре условия:
- У скачивания нет причины остановки.
- Скачивание не приостановлено.
- Выполнены требования для скачивания. В требованиях можно указать ограничения на допустимые типы сетей, а также то, должно ли устройство быть в режиме ожидания или подключено к зарядному устройству.
- Не превышено максимальное количество параллельных скачиваний.
Все эти условия можно контролировать, отправляя команды в DownloadService.
Как задавать и удалять причины остановки скачивания
Вы можете указать причину, по которой скачивание одного или нескольких файлов было остановлено.
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 может быть любым ненулевым значением (Download.STOP_REASON_NONE = 0 – специальное значение, означающее, что скачивание не остановлено). Приложения, в которых загрузка может быть остановлена по нескольким причинам, могут использовать разные значения, чтобы отслеживать, почему была остановлена каждая загрузка. Установка и удаление причины остановки для всех скачиваний выполняется так же, как и для одного скачивания, но для параметра contentId нужно задать значение null.
Если у скачивания есть причина остановки, отличная от нуля, оно будет иметь статус 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.setRequirements() при создании объекта DownloadManager, как показано в примере выше. Их также можно динамически изменять, отправляя команду в 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.
Как запрашивать скачанные файлы
У DownloadIndex объекта DownloadManager можно запросить статус всех скачиваний, в том числе завершенных и неудачных. DownloadIndex можно получить, вызвав метод DownloadManager.getDownloadIndex(). Курсор, который перебирает все скачанные файлы, можно получить, вызвав метод DownloadIndex.getDownloads(). Кроме того, статус отдельного скачивания можно запросить, вызвав метод DownloadIndex.getDownload().
DownloadManager также предоставляет DownloadManager.getCurrentDownloads(), который возвращает состояние только текущих (не завершенных и не неудачных) скачиваний. Этот метод полезен для обновления уведомлений и других компонентов интерфейса, в которых отображается прогресс и статус текущих скачиваний.
Прослушивание скачанных файлов
Вы можете добавить прослушиватель в 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, а не из сети.
Чтобы воспроизвести скачанный контент, создайте CacheDataSource.Factory, используя тот же экземпляр Cache, который использовался для скачивания, и добавьте его в 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 с помощью метода Download.request.toMediaItem или напрямую из объекта DownloadRequest с помощью метода DownloadRequest.toMediaItem.
Настройка 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
выглядит так:
- Создайте
DownloadHelper, используя экземплярDownloadHelper.Factory. Подготовьте помощника и дождитесь обратного звонка. - При необходимости проверьте выбранные по умолчанию дорожки с помощью значков
getMappedTrackInfoиgetTrackSelectionsи внесите изменения, используя значкиclearTrackSelections,replaceTrackSelectionsиaddTrackSelection. - Создайте
DownloadRequestдля выбранных треков, вызвав функциюgetDownloadRequest. Запрос можно передать вDownloadService, чтобы добавить скачивание, как описано выше. - Отпустите помощника, используя
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.streamKeys должно совпадать со значением в DownloadRequest, чтобы проигрыватель пытался воспроизвести только скачанные дорожки. При создании MediaItem с помощью Download.request.toMediaItem и DownloadRequest.toMediaItem эта проблема будет решена.