Новости продуктов

Улучшение воспроизведения медиаконтента: предварительная загрузка с помощью Media3 (часть 1)

Время на чтение: 8 минут
Посмотреть профиль пользователя Mayuri Khinvasara Khabya
Mayuri Khinvasara Khabya Developer Relations Engineer

В современных приложениях, ориентированных на медиаконтент, плавное воспроизведение без прерываний – это ключевой фактор удобства для пользователей. Пользователи ожидают, что видео будут запускаться мгновенно и воспроизводиться без пауз.

Основная сложность заключается в задержке. Обычно видеопроигрыватель начинает работу (подключается, скачивает, анализирует и буферизирует контент) только после того, как пользователь выбрал видео для воспроизведения. Такой подход не подходит для современных коротких видео. Решение – проявлять инициативу. Нам нужно предугадать, что пользователь захочет посмотреть дальше, и подготовить контент заранее. В этом и заключается суть предварительной загрузки.

Основные преимущества предварительной загрузки:

  • 🚀 Более быстрый запуск воспроизведения. Видео уже готовы к показу, поэтому переходы между элементами и запуск воспроизведения происходят быстрее.
  • 📉 Меньше буферизации. Благодаря упреждающей загрузке данных воспроизведение будет реже прерываться, например из-за проблем с сетью.
  • ✨ Более удобный интерфейс. Благодаря более быстрой загрузке и меньшему количеству буферизаций пользователи могут наслаждаться более плавным и непрерывным взаимодействием.

В этой серии из трех частей мы расскажем о мощных утилитах Media3 для предварительной загрузки компонентов.

  • В первой части мы рассмотрим основы: стратегии предзагрузки, доступные в Media3, включение PreloadConfiguration и настройку DefaultPreloadManager, а также предзагрузку контента в приложении. К концу этой статьи вы сможете предварительно загружать и воспроизводить медиаконтент с заданными вами рейтингом и продолжительностью.
  • Во второй части мы рассмотрим более сложные темы, связанные с DefaultPreloadManager: использование прослушивателей для аналитики, изучение готовых к использованию рекомендаций, таких как шаблон скользящего окна и пользовательские общие компоненты DefaultPreloadManager и ExoPlayer.
  • В третьей части мы подробно рассмотрим кеширование на диске с помощью DefaultPreloadManager.

Предзагрузка поможет! 🦸‍♀️

Основная идея предзагрузки проста: загружайте медиаконтент до того, как он вам понадобится. К тому моменту, когда пользователь переходит к следующему видео, первые сегменты уже скачаны и готовы к воспроизведению.

Представьте, что это ресторан. На кухне, где много заказов, не ждут, пока поступит заказ, чтобы начать резать лук. 🧅 Они заранее выполняют подготовительную работу. Предзагрузка – это подготовка видеопроигрывателя.

Если включить предварительную загрузку, можно уменьшить задержку при подключении, когда пользователь переходит к следующему объекту до того, как буфер воспроизведения достигнет этого объекта. Первый период следующего окна подготовлен, а образцы видео, аудио и текста буферизованы. Загруженный контент добавляется в очередь проигрывателя, и буферизованные фрагменты сразу же становятся доступны для декодера.

В Media3 есть два основных API для предварительной загрузки, каждый из которых подходит для разных вариантов использования. Первый шаг – выбрать подходящий API.

1. Предзагрузка объектов плейлиста с помощью PreloadConfiguration

Это простой подход, полезный для линейных, последовательных медиафайлов, таких как плейлисты, где порядок воспроизведения предсказуем (например, для серий). Вы передаете проигрывателю полный список медиаконтента с помощью API плейлиста ExoPlayer и задаете PreloadConfiguration для проигрывателя, после чего он автоматически предварительно загружает следующие элементы в последовательности в соответствии с настройками. Этот API пытается оптимизировать задержку подключения, когда пользователь переходит к следующему элементу до того, как буфер воспроизведения уже перекрывает следующий элемент.

Предварительная загрузка начинается только тогда, когда не загружается контент для текущего воспроизведения. Это позволяет избежать конкуренции за пропускную способность.

Если вы не уверены, нужно ли вам предварительное скачивание, этот API – отличный способ проверить его эффективность.

player.preloadConfiguration =
    PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)

Приведенная выше конфигурация PreloadConfiguration указывает, что проигрыватель должен попытаться предварительно загрузить пять секунд медиаконтента для следующего элемента в плейлисте.

Если вы включили предварительную загрузку плейлистов, ее можно отключить с помощью PreloadConfiguration.DEFAULT:

player.preloadConfiguration = PreloadConfiguration.DEFAULT

2. Предварительная загрузка динамических списков с помощью PreloadManager

Для динамических интерфейсов, таких как вертикальные фиды или карусели, где следующий элемент определяется взаимодействием с пользователем, подходит API PreloadManager. Это новый мощный автономный компонент библиотеки Media3 ExoPlayer, специально разработанный для упреждающей загрузки. Он управляет набором потенциальных источников медиаконтента, расставляя приоритеты на основе близости к текущему местоположению пользователя, и предлагает детальный контроль над тем, что нужно предварительно загрузить. Это подходит для сложных сценариев, например для динамических фидов коротких видео.

Настройка PreloadManager

DefaultPreloadManager – это каноническая реализация PreloadManager.

Создатель DefaultPreloadManager может создать как DefaultPreloadManager, так и любые экземпляры ExoPlayer, которые будут воспроизводить предварительно загруженный контент. Чтобы создать DefaultPreloadManager, вам нужно передать TargetPreloadStatusControl, который менеджер предзагрузки может запросить, чтобы узнать, сколько нужно загрузить для элемента. Ниже мы приведем пример использования TargetPreloadStatusControl.

val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
val preloadManager = val preloadManagerBuilder.build()

// Build ExoPlayer with DefaultPreloadManager.Builder
val player = preloadManagerBuilder.buildExoPlayer()

Для ExoPlayer и DefaultPreloadManager необходимо использовать один и тот же сборщик. Это обеспечит правильное совместное использование внутренних компонентов.

Вот и всё! Теперь у вас есть менеджер, готовый к получению инструкций.

Настройка продолжительности и ранжирования с помощью TargetPreloadStatusControl

Что делать, если вы хотите предварительно загрузить, например, 10 секунд видео? Вы можете указать позицию медиаконтента в карусели, и DefaultPreloadManager будет загружать контент в порядке приоритета в зависимости от того, насколько он близок к контенту, который сейчас воспроизводит пользователь.

Если вы хотите контролировать, сколько времени будет предзагружаться контент, укажите это в возвращаемом значении DefaultPreloadManager.PreloadStatus.

Например,

  • У объекта А самый высокий приоритет, поэтому загружается 5 секунд видео.
  • Объект Б имеет средний приоритет, но при его показе нужно загрузить три секунды видео.
  • Объект "В" имеет более низкий приоритет, поэтому загружаются только треки.
  • У задачи "Г" приоритет ещё ниже, поэтому просто подготовьтесь.
  • Другие объекты находятся далеко, поэтому ничего не нужно предварительно загружать.

Такая детализация позволяет оптимизировать использование ресурсов, что необходимо для бесперебойного воспроизведения.

import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus


class MyTargetPreloadStatusControl(
    currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {


    // The app is responsible for updating this based on UI state
    override fun getTargetPreloadStatus(index: Int): PreloadStatus? {

        val distance = index - currentPlayingIndex

        // Adjacent items (Next): preload 5 seconds
        if (distance == 1) { 
        // Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
                    return PreloadStatus.specifiedRangeLoaded(5000L)
                } 

        // Adjacent items (Previous): preload 3 seconds
        else if (distance == -1) { 
        // Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
                    return PreloadStatus.specifiedRangeLoaded(3000L)
                } 

        // Items two positions away: just select tracks
        else if (distance) == 2) {
        // Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
                    return PreloadStatus.TRACKS_SELECTED
                } 

        // Items four positions away: just select prepare
        else if (abs(distance) <= 4) {
        // Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
                    return PreloadStatus.SOURCE_PREPARED
                }

             // All other items are too far away
             return null
            }
}

Примечание. PreloadManager может хранить как предыдущие, так и следующие элементы, а PreloadConfiguration – только следующие.

Как управлять предзагруженными объектами

После создания менеджера вы можете начать давать ему задания. Когда пользователь прокручивает ленту, вы определяете следующие видео и добавляете их в менеджер. Взаимодействие с PreloadManager представляет собой разговор между вашим интерфейсом и механизмом предзагрузки, основанный на состояниях.

1. Как добавить медиаобъекты

При заполнении фида необходимо сообщить менеджеру, какие медиафайлы нужно отслеживать. Если вы только начинаете работу с предзагрузкой, добавьте весь список. После этого вы сможете добавлять в список по одному элементу. Вы полностью контролируете список предзагрузки, а значит, вам нужно управлять тем, что добавляется и удаляется из менеджера.

val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
    preloadManager.add(
        initialMediaItems.get(index),index)
    )
}

Менеджер начнет получать данные для этого MediaItem в фоновом режиме.

После добавления попросите менеджера переоценить новый список (подскажите, что что-то изменилось, например был добавлен или удален объект или пользователь перешел к воспроизведению нового объекта).

preloadManager.invalidate()

2. Как найти и воспроизвести объект

Здесь находится основная логика воспроизведения. Когда пользователь решит посмотреть видео, вам не нужно будет создавать новый объект MediaSource. Вместо этого вы можете попросить PreloadManager предоставить уже подготовленный ответ. Вы можете получить MediaSource из Preload Manager, используя MediaItem.

Если из PreloadManager возвращается нулевое значение, значит объект mediaItem ещё не загружен или не добавлен в PreloadManager. В этом случае вы можете задать mediaItem напрямую.

// When a media item is about to displ​​ay on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
  player.setMediaSource(mediaSource)
} else {
  // If mediaSource is null, that mediaItem hasn't been added yet.
  // So, send it directly to the player.
  player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()

Подготовив MediaSource, полученный от PreloadManager, вы сможете плавно перейти от предварительной загрузки к воспроизведению, используя данные, которые уже находятся в памяти. Благодаря этому время запуска сокращается.

3. Поддерживайте актуальность индекса в соответствии с интерфейсом

Поскольку фид или список может быть динамическим, важно сообщать PreloadManager текущий индекс воспроизведения, чтобы он всегда мог отдавать приоритет элементам, ближайшим к текущему индексу, для предварительной загрузки.

preloadManager.setCurrentPlayingIndex(currentIndex)
// Need to call invalidate() to update the priorities
preloadManager.invalidate()

4. Как удалить объект

Чтобы менеджер работал эффективно, удалите из него объекты, которые ему больше не нужно отслеживать, например те, которые находятся далеко от текущего местоположения пользователя.

// When an item is too far from the current playing index
preloadManager.remove(mediaItem)

Если вам нужно удалить все объекты сразу, вы можете вызвать метод preloadManager.reset().

5. Как отменить блокировку

Когда PreloadManager больше не нужен (например, при уничтожении интерфейса), его необходимо освободить, чтобы освободить его ресурсы. Лучше всего это делать там, где вы уже выпускаете ресурсы игрока. Рекомендуем сначала освободить менеджер, а затем проигрыватель, поскольку проигрыватель может продолжать воспроизведение, если вам больше не нужна предварительная загрузка.

// In your Activity's onDestroy() or Composable's onDispose
preloadManager.release()

Время демонстрации

Посмотрите, как это работает 👍

На демонстрации ниже справа показано , как PreloadManager влияет на скорость загрузки, а слева – как это происходит без него. Вы также можете посмотреть образец кода для демонстрации. (Бонус: также отображается задержка запуска для каждого видео)

Demo-PreloadManager_2.webp

Что дальше?

На этом первая часть завершается. Теперь у вас есть инструменты для создания системы динамической предварительной загрузки. Вы можете использовать PreloadConfiguration, чтобы предварительно загрузить следующий элемент плейлиста в ExoPlayer, или настроить DefaultPreloadManager, добавлять и удалять элементы на лету, задать целевой статус предварительной загрузки и правильно извлекать предварительно загруженный контент для воспроизведения.

Во второй части мы подробнее рассмотрим DefaultPreloadManager. Мы расскажем, как отслеживать события предварительной загрузки, обсудим рекомендации по использованию скользящего окна, чтобы избежать проблем с памятью, и рассмотрим внутренние механизмы работы пользовательских общих компонентов ExoPlayer и DefaultPreloadManager.

Хотите поделиться отзывом? Мы ждем вашего ответа.

Следите за новостями и ускоряйте свои приложения! 🚀

Автор:
Продолжить чтение