Новости о продуктах
Улучшение воспроизведения мультимедиа: подробный анализ PreloadManager от Media3 — Часть 2
9 минут чтения

Добро пожаловать во вторую часть нашей серии из трех статей о предварительной загрузке медиаконтента с помощью Media3. Эта серия призвана помочь вам в процессе создания высокоэффективных приложений для Android с низкой задержкой и быстрым откликом.
- Часть 1: Введение в предварительную загрузку с помощью Media3 охватила основы. Мы рассмотрели различие между PreloadConfiguration для простых плейлистов и более мощным DefaultPreloadManager для динамических пользовательских интерфейсов. Вы узнали, как реализовать базовый жизненный цикл API: добавление медиафайлов с помощью add(), получение подготовленного MediaSource с помощью getMediaSource(), управление приоритетами с помощью setCurrentPlayingIndex() и invalidate(), а также освобождение ресурсов с помощью remove() и release().
- Часть 2 (Эта статья): В этом блоге мы рассмотрим расширенные возможности DefaultPreloadManager. Мы расскажем, как получить ценную информацию с помощью PreloadManagerListener , внедрить лучшие практики, готовые к использованию в производственной среде, такие как совместное использование основных компонентов с ExoPlayer, и освоить шаблон скользящего окна для эффективного управления памятью.
- Часть 3: В заключительной части этой серии мы рассмотрим интеграцию PreloadManager с постоянным дисковым кэшем, что позволит вам сократить потребление данных за счет управления ресурсами и обеспечить бесперебойную работу.
Если вы новичок в предварительной загрузке в Media3, мы настоятельно рекомендуем прочитать Часть 1, прежде чем продолжить. А для тех, кто готов выйти за рамки основ, давайте рассмотрим, как улучшить реализацию воспроизведения мультимедиа.
Отслеживание: Получение аналитики с помощью PreloadManagerListener
Когда вы, как разработчик приложения, хотите запустить функцию в рабочую среду, вам также необходимо понимать и собирать аналитические данные, лежащие в её основе. Как вы можете быть уверены в эффективности вашей стратегии предварительной загрузки в реальных условиях? Для ответа на этот вопрос необходимы данные об успешности, неудачах и производительности. Интерфейс PreloadManagerListener является основным механизмом для сбора этих данных.
Объект PreloadManagerListener предоставляет две важные функции обратного вызова, которые позволяют получить критически важную информацию о процессе и состоянии предварительной загрузки.
- onCompleted (MediaItem mediaItem) : Этот обратный вызов вызывается после успешного завершения запроса предварительной загрузки, как определено в вашем TargetPreloadStatusControl.
- onError (ошибка PreloadException) : Этот коллбэк может быть полезен для отладки и мониторинга. Он вызывается при сбое предварительной загрузки, предоставляя соответствующее исключение.
Зарегистрировать слушатель можно с помощью одного вызова метода, как показано в следующем примере кода:
val preloadManagerListener = object : PreloadManagerListener {
override fun onCompleted(mediaItem: MediaItem) {
// Log success for analytics.
Log.d("PreloadAnalytics", "Preload completed for $mediaItem")
}
override fun onError( preloadError: PreloadException) {
// Log the specific error for debugging and monitoring.
Log.e("PreloadAnalytics", "Preload error ", preloadError)
}
}
preloadManager.addListener(preloadManagerListener)
Извлечение полезной информации из уст слушателя.
Эти обработчики событий можно подключить к вашему аналитическому конвейеру. Пересылая эти события в вашу аналитическую систему, вы можете ответить на ключевые вопросы, такие как:
- Каков наш показатель успешности предварительной загрузки? (отношение количества завершенных событий к общему числу попыток предварительной загрузки)
- Какие CDN или видеоформаты демонстрируют самый высокий уровень ошибок? (По результатам анализа исключений из функции onError)
- Каков наш показатель ошибок предварительной загрузки? (отношение числа событий onError к общему числу попыток предварительной загрузки)
Эти данные могут предоставить вам количественную обратную связь по вашей стратегии предварительной загрузки, что позволит проводить A/B-тестирование и вносить улучшения в пользовательский опыт на основе данных. Кроме того, эти данные помогут вам более точно настроить продолжительность предварительной загрузки , количество загружаемых видео и выделяемые буферы.
Помимо отладки: использование onError для корректного резервного варианта в пользовательском интерфейсе.
Неудачная предварительная загрузка — это явный признак предстоящей буферизации для пользователя. Коллбэк onError позволяет реагировать на это автоматически. Вместо простого логирования ошибки, вы можете адаптировать пользовательский интерфейс. Например, если предварительная загрузка предстоящего видео не удалась, ваше приложение может отключить автовоспроизведение для следующего свайпа, требуя от пользователя нажатия для начала воспроизведения.
Кроме того, анализируя тип PreloadException , можно определить более интеллектуальную стратегию повторных попыток. Приложение может выбрать немедленное удаление неисправного источника из менеджера на основе сообщения об ошибке или кода состояния HTTP. Соответствующим образом элемент необходимо будет удалить из потока пользовательского интерфейса, чтобы проблемы с загрузкой не влияли на пользовательский опыт. Также можно получить более подробные данные из PreloadException, например, из HttpDataSourceException, чтобы глубже изучить ошибки. Подробнее об устранении неполадок ExoPlayer читайте здесь.
Система взаимопомощи: зачем необходимо делиться компонентами с ExoPlayer?
DefaultPreloadManager и ExoPlayer предназначены для совместной работы. Для обеспечения стабильности и эффективности они должны использовать несколько общих основных компонентов . Если они работают с отдельными, нескоординированными компонентами, это может повлиять на потокобезопасность и удобство использования предварительно загруженных треков на плеере, поскольку необходимо гарантировать, что предварительно загруженные треки будут воспроизводиться на правильном плеере. Отдельные компоненты также могут конкурировать за ограниченные ресурсы, такие как пропускная способность сети и память, что может привести к снижению производительности. Важной частью жизненного цикла является правильная обработка освобождения ресурсов; рекомендуемый порядок освобождения ресурсов — сначала освободить PreloadManager, а затем ExoPlayer.
Компонент DefaultPreloadManager.Builder предназначен для упрощения такого совместного использования и имеет API для создания экземпляров как PreloadManager, так и связанного экземпляра проигрывателя. Давайте посмотрим, почему такие компоненты, как BandwidthMeter, LoadControl, TrackSelector, Looper, должны использоваться совместно. Ознакомьтесь с визуальным представлением того, как эти компоненты взаимодействуют с ExoPlayer Playback.

Предотвращение конфликтов полосы пропускания с помощью общего счетчика полосы пропускания.
Индикатор пропускной способности сети (BandwidthMeter) предоставляет оценку доступной пропускной способности сети на основе исторических данных о скорости передачи данных. Если PreloadManager и плеер используют отдельные экземпляры, они не знают о сетевой активности друг друга, что может привести к сбоям. Например, рассмотрим ситуацию, когда пользователь смотрит видео, его сетевое соединение ухудшается, и одновременно с этим MediaSource, осуществляющий предварительную загрузку, начинает интенсивную загрузку будущего видео. Активность MediaSource, осуществляющего предварительную загрузку, будет потреблять пропускную способность, необходимую активному плееру, что приведет к зависанию текущего видео. Зависание во время воспроизведения — это серьезный сбой в работе пользователя.
Благодаря использованию единого индикатора пропускной способности (BandwidthMeter), TrackSelector может выбирать треки наилучшего качества с учетом текущих сетевых условий и состояния буфера, как во время предварительной загрузки, так и во время воспроизведения. Затем он может принимать интеллектуальные решения для защиты активной сессии воспроизведения и обеспечения бесперебойной работы.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
Обеспечение согласованности с общими компонентами LoadControl, TrackSelector и Renderer в ExoPlayer.
- LoadControl : Этот компонент определяет политику буферизации, например, сколько данных буферизовать перед началом воспроизведения и когда начинать или останавливать загрузку дополнительных данных. Совместное использование LoadControl гарантирует, что потребление памяти проигрывателем и PreloadManager регулируется единой, скоординированной стратегией буферизации как для предварительно загруженных, так и для активно воспроизводимых медиафайлов, предотвращая конфликты ресурсов. Вам потребуется грамотно распределять размер буфера в зависимости от количества предварительно загружаемых элементов и их продолжительности, чтобы обеспечить согласованность. В случае конфликтов проигрыватель будет отдавать приоритет воспроизведению текущего элемента, отображаемого на экране. При совместном использовании LoadControl менеджер предварительной загрузки будет продолжать предварительную загрузку до тех пор, пока целевой объем буфера, выделенный для предварительной загрузки, не достигнет верхнего предела, он не будет ждать завершения загрузки для воспроизведения.
Примечание: Совместное использование LoadControl в последней версии Media3 (1.8) гарантирует корректное совместное использование его Allocator с PreloadManager и плеером. Использование LoadControl для эффективного управления предварительной загрузкой — это функция, которая будет доступна в предстоящем релизе Media3 1.9.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector : Этот компонент отвечает за выбор того, какие дорожки (например, видео определенного разрешения, аудио на определенном языке) загружать и воспроизводить. Совместное использование гарантирует, что дорожки, выбранные во время предварительной загрузки, будут совпадать с теми, которые будет использовать плеер. Это позволяет избежать неэффективной ситуации, когда предварительно загружается видеодорожка 480p, а плеер сразу же отбрасывает ее и при воспроизведении загружает дорожку 720p.<br /> Менеджер предварительной загрузки НЕ должен использовать один и тот же экземпляр TrackSelector с плеером. Вместо этого он должен использовать другой экземпляр TrackSelector, но с той же реализацией . Именно поэтому мы устанавливаем TrackSelectorFactory, а не TrackSelector в DefaultPreloadManager.Builder.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- Рендерер : Этот компонент отвечает за понимание возможностей проигрывателя без создания полных рендереров. Он проверяет этот шаблон, чтобы определить, какие видео, аудио и текстовые форматы будет поддерживать конечный проигрыватель. Это позволяет ему интеллектуально выбирать и загружать только совместимые медиафайлы и предотвращает трату полосы пропускания на контент, который проигрыватель фактически не может воспроизвести.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
Узнайте больше о компонентах Exoplayer .
Золотое правило: универсальный лупер воспроизведения , который подойдет для всех.
Поток, в котором можно получить доступ к экземпляру ExoPlayer, можно явно указать, передав Looper при создании проигрывателя. Looper потока, из которого необходимо получить доступ к проигрывателю, можно запросить с помощью метода Player.getApplicationLooper . Поддерживая общий Looper между проигрывателем и PreloadManager, гарантируется, что все операции с этими общими медиаобъектами сериализуются в очередь сообщений одного потока. Это может уменьшить количество ошибок, связанных с параллельным выполнением.
Все взаимодействия между PreloadManager и плеером с медиаисточниками, которые необходимо загрузить или предварительно загрузить, должны происходить в одном и том же потоке воспроизведения. Совместное использование Looper является обязательным условием потокобезопасности, поэтому мы должны совместно использовать PlaybackLooper между PreloadManager и плеером.
PreloadManager подготавливает объект MediaSource с сохранением состояния в фоновом режиме. Когда ваш код пользовательского интерфейса вызывает player.setMediaSource(mediaSource), вы выполняете передачу этого сложного объекта с сохранением состояния от предварительно загружаемого MediaSource к плееру. В этом сценарии весь PreloadMediaSource перемещается от менеджера к плееру. Все эти взаимодействия и передачи должны происходить в одном и том же PlaybackLooper.
Если PreloadManager и ExoPlayer работают в разных потоках, может возникнуть состояние гонки. Поток PreloadManager может изменять внутреннее состояние MediaSource (например, записывать новые данные в буфер) в тот самый момент, когда поток проигрывателя пытается прочитать из него данные. Это приводит к непредсказуемому поведению и исключению IllegalStateException, которое трудно отладить.
preloadManagerBuilder.setPreloadLooper(playbackLooper)
Давайте посмотрим, как можно совместно использовать все вышеперечисленные компоненты между ExoPlayer и DefaultPreloadManager в самом процессе настройки.
val preloadManagerBuilder =
DefaultPreloadManager.Builder(context, targetPreloadStatusControl)
// Optional - Share components between ExoPlayer and DefaultPreloadManager
preloadManagerBuilder
.setBandwidthMeter(customBandwidthMeter)
.setLoadControl(customLoadControl)
.setMediaSourceFactory(customMediaSourceFactory)
.setTrackSelectorFactory(customTrackSelectorFactory)
.setRenderersFactory(customRenderersFactory)
.setPreloadLooper(playbackLooper)
val preloadManager = val preloadManagerBuilder.build()
Совет: Если вы используете компоненты по умолчанию в ExoPlayer, такие как DefaultLoadControl и т. д., вам не нужно явно указывать их DefaultPreloadManager. При создании экземпляра ExoPlayer с помощью метода buildExoPlayer объекта DefaultPreloadManager.Builder эти компоненты автоматически связываются друг с другом, если вы используете реализации по умолчанию с конфигурациями по умолчанию. Но если вы используете пользовательские компоненты или пользовательские конфигурации, вам следует явно уведомить DefaultPreloadManager о них с помощью указанных выше API.
Предварительная загрузка, готовая к производству: схема скользящего окна.
В динамической ленте пользователь может прокручивать практически бесконечное количество контента. Если вы постоянно добавляете видео в DefaultPreloadManager без соответствующей стратегии удаления, это неизбежно приведет к ошибке OutOfMemoryError. Каждый предварительно загруженный MediaSource хранит SampleQueue , который выделяет буферы памяти. По мере их накопления может исчерпаться пространство кучи приложения. Решением является алгоритм, с которым вы, возможно, уже знакомы, называемый скользящим окном. Шаблон скользящего окна поддерживает в памяти небольшой, управляемый набор элементов, логически смежных с текущим положением пользователя в ленте. По мере прокрутки пользователем это «окно» управляемых элементов сдвигается вместе с ним, добавляя новые элементы, которые появляются в поле зрения, и удаляя элементы, которые теперь находятся далеко.

Реализация шаблона скользящего окна
Важно понимать, что PreloadManager не предоставляет встроенного метода setWindowSize(). Шаблон проектирования «скользящее окно» — это задача, которую вы, разработчик, должны реализовать, используя примитивные методы add() и remove(). Логика вашего приложения должна связывать события пользовательского интерфейса, такие как прокрутка или смена страницы, с этими вызовами API. Если вам нужен пример кода, у нас есть реализация шаблона «скользящее окно» в примере Socialite , который также включает PreloadManagerWrapper , имитирующий скользящее окно.
Не забудьте добавить preloadManager.remove(mediaItem) в вашу реализацию, если элемент вряд ли скоро появится в поле зрения пользователя. Неудаление элементов, которые больше не находятся рядом с пользователем, является основной причиной проблем с памятью в реализациях предварительной загрузки. Вызов remove() гарантирует освобождение ресурсов, что помогает поддерживать использование памяти вашим приложением на стабильном уровне.
Тонкая настройка стратегии предварительной загрузки по категориям с помощью TargetPreloadStatusControl
Теперь, когда мы определили, что именно нужно предварительно загрузить (элементы в нашем окне), мы можем применить четко определенную стратегию для определения объема предварительной загрузки каждого элемента. Мы уже видели, как добиться такой детализации с помощью настройки TargetPreloadStatusControl в части 1 .
Напомним, что элемент, находящийся на позиции +/- 1, может иметь более высокую вероятность быть воспроизведенным, чем элемент на позиции +/- 4. Вы можете выделить больше ресурсов (сети, процессора, памяти) для элементов, которые пользователь, скорее всего, увидит следующим. Это создает стратегию «предварительной загрузки», основанную на близости, что является ключом к балансу между немедленным воспроизведением и эффективным использованием ресурсов.
Как обсуждалось в предыдущих разделах, вы можете использовать аналитические данные через PreloadManagerListener для определения стратегии продолжительности предварительной загрузки.
Заключение и дальнейшие шаги
Теперь вы обладаете необходимыми знаниями для создания быстрых, стабильных и ресурсоэффективных медиапотоков с помощью DefaultPreloadManager от Media3.
Давайте подведем итоги основных выводов:
- Используйте PreloadManagerListener для сбора аналитических данных и реализации надежной обработки ошибок.
- Для создания экземпляров менеджера и игрока всегда используйте один и тот же DefaultPreloadManager.Builder, чтобы обеспечить совместное использование важных компонентов.
- Реализуйте шаблон скользящего окна, активно управляя вызовами add() и remove(), чтобы предотвратить ошибку OutOfMemoryError.
- Используйте TargetPreloadStatusControl для создания интеллектуальной многоуровневой стратегии предварительной загрузки, которая обеспечивает баланс между производительностью и потреблением ресурсов.
Что дальше в части 3: Кэширование с предварительно загруженными медиафайлами
Предварительная загрузка данных в память обеспечивает немедленный прирост производительности, но может сопровождаться компромиссами. После закрытия приложения или удаления предварительно загруженных медиафайлов из менеджера данные исчезают. Для достижения более стабильного уровня оптимизации можно комбинировать предварительную загрузку с дисковым кэшированием. Эта функция находится в активной разработке и появится в ближайшее время, через несколько месяцев.
У вас есть какие-либо отзывы, которыми вы хотели бы поделиться ? Мы будем рады их услышать.
Оставайтесь с нами и ускоряйте воспроизведение видео! 🚀
Продолжить чтение

Новости о продуктах
В современных приложениях, ориентированных на мультимедиа, обеспечение плавного и бесперебойного воспроизведения является ключом к приятному пользовательскому опыту. Пользователи ожидают, что их видео начнут воспроизводиться мгновенно и без пауз.
Mayuri Khinvasara Khabya • Чтение: 8 мин.

Новости о продуктах
Android Studio Panda 4 теперь стабильна и готова к использованию в продакшене. В этом релизе появились режим планирования, прогнозирование следующего изменения и многое другое, что делает создание высококачественных Android-приложений проще, чем когда-либо.
Matt Dyor • 5 мин чтения

Новости о продуктах
Если вы — разработчик Android-приложений, стремящийся внедрить в них инновационные функции искусственного интеллекта, то недавно мы выпустили новые мощные обновления.
Thomas Ezan • 3 мин чтения
Будьте в курсе событий
Получайте еженедельно самые свежие новости о разработке Android прямо на свою электронную почту.



