Пользователи мобильных устройств тратят много времени на просмотр медиаконтента, поэтому воспроизведение должно быть безупречным, эффективным и не должно быстро разряжать батарею. Кроме того, пользователи ожидают, что смогут легко переключаться между устройствами, например телефонами и телевизорами.
Узнайте, как изменения в архитектуре Media3 и ExoPlayer обеспечивают бесперебойное воспроизведение на устройствах разных типов. Мы обсудим, как создавать адаптивные интерфейсы с помощью набора инструментов Media3 Compose UI, устранять буферизацию в лентах с помощью нового PreloadManager и расширять возможности приложения для экранов телевизоров и Android XR с помощью CastPlayer и пространственного звука.
Расшифровка
Окей.
Здравствуйте!
Меня зовут Тони.
Я инженер в команде Android Media.
Вместе с Кристиной и Донованом мы поговорим о том, как Jetpack Media3 может помочь вам со всеми вашими медиа-потребностями на Android.
Media3 – это библиотека поддержки Android для всего, что связано с медиаконтентом, будь то воспроизведение с помощью ExoPlayer, обмен медиафайлами с помощью сеанса мультимедиа и Cast, редактирование медиафайлов с помощью преобразователя или проверка медиафайлов, например извлечение кадров или метаданных.
Media3 охватывает все эти возможности в одной группе библиотек.
Поскольку это библиотека поддержки, она встроена в ваше приложение.
Это позволяет нам обеспечивать согласованность, чтобы один и тот же код работал на всех версиях ОС и всех устройствах.
Это также помогает с обновляемостью.
Каждое исправление ошибок, которое мы делаем, доходит до API 24 и не привязано к какому-либо циклу выпуска ОС.
А поскольку это библиотека поддержки, мы можем предоставить очень широкие возможности настройки, которые были бы невозможны в платформе Android.
А поскольку это библиотека на GitHub, мы в первую очередь ориентируемся на открытый исходный код.
Это означает, что каждое изменение, которое мы делаем, сразу же доступно на GitHub.
У нас есть активная система отслеживания ошибок, и мы принимаем pull-запросы.
Главное, что нужно подчеркнуть здесь, во всех этих случаях использования, мы действительно заботимся обо всех уровнях детализации, от высокоуровневых элементов пользовательского интерфейса до простых готовых API, а также о возможностях глубокой настройки, широкой поддержке различных форматов и стандартов, а также о производительности и эффективности, лежащих в основе всего этого.
Media3 охватывает все эти области.
Сегодня мы постараемся глубже погрузиться в некоторые из этих тем.
Мы поговорим о новом модуле Compose UI, новом CastPlayer, который упрощает обмен данными с медиасеансом, обновленном конвейере редактирования с новыми расширенными эффектами, новых инструментах для получения метаданных и кадров, некоторых новых технологиях потоковой передачи, в частности, видео и аудио Eclipsa, а также о ряде утилит для повышения качества работы, предназначенных для предзагрузки, прокрутки и экономии заряда батареи.
Теперь давайте перейдем к первой теме и узнаем, как Media3 помогает создавать элементы интерфейса для медиа.
Недавно мы опубликовали два новых модуля: Media3 UI Compose и Media3 UI Compose Material 3.
Здесь вы видите высокоуровневую composable-функцию проигрывателя, как она была запущена в Media3 1.10 без дальнейших изменений.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Она работает по умолчанию.
Для этого нужен только проигрыватель Media3.
В этом примере используется ExoPlayer, который обычно находится в модели представления.
Но это может быть и что-то другое, например CastPlayer, контроллер мультимедиа или любой другой проигрыватель, который есть в вашем приложении.
Стоит подчеркнуть, что ExoPlayer здесь является источником истины.
Он содержит всю информацию, а composable-функция проигрывателя отвечает за рендеринг пользовательского интерфейса и подключение всего.
Но что, если вы хотите настроить этот интерфейс, добавить новые кнопки, изменить их положение, стиль или наложить текст?
Составной проигрыватель состоит из строительных блоков.
Вы можете заменить их.
Вы можете настроить каждый отдельный блок или использовать их по отдельности в других местах приложения.
Например, вы можете отдельно настроить верхнюю, центральную и bottomControls части.
Вы также можете настроить внешний вид заставки, которая показывается, когда видео не воспроизводится, или оверлея с сообщением об ошибке.
Но это только первый уровень.
В центре и на bottomControls есть дополнительные слоты, которые можно настроить.
Например, вы можете заменить центральную кнопку, оставив все остальное без изменений, или добавить элементы над или под ползунком.
Media3 Compose предоставляет все необходимые для этого строительные блоки.
Например, у нас есть кнопки воспроизведения и паузы, повтора, случайного порядка, отключения звука и другие.
Также есть индикаторы выполнения, например ползунок и текст с позицией, показанные на экране.
Также есть несколько контейнеров и оверлеев.
Высокоуровневая composable-функция проигрывателя, которую я вам показал, – это один из примеров. Есть и другие, например оверлеи ошибок или субтитры.
Кроме того, у нас есть другие полезные компоненты, например этот мини-контроллер.
Вы можете разместить его на нижнем экране, пока пользователь перемещается по приложению.
И это ещё не всё.
Мы активно расширяем эту библиотеку, например, скоро появятся новые элементы управления редактированием, такие как ползунок обрезки.
Теперь давайте посмотрим, как эти блоки используются на практике.
Player(viewModel.exoPlayer)Допустим, мы хотим переместить текст с позицией и продолжительностью, который сейчас находится рядом с ползунком, над ним.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Поэтому мы можем переопределить эти bottomControls.
Мы можем использовать компонент по умолчанию, но задать для левого и правого слотов пустые значения, чтобы они не отображались.
Вместо этого мы помещаем текст с позицией и продолжительностью в верхний слот с настраиваемым разделителем, чтобы он выглядел интереснее.
Теперь предположим, что мы хотим полностью удалить оверлей centerControls и вместо него разместить кнопку воспроизведения прямо над ползунком с регулятором скорости и кнопкой отключения звука.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}И это точно такая же идея.
Мы можем сделать centerControls пустым, чтобы он исчез, и расширить рекламное место над ползунком, добавив немного свободного пространства и три кнопки, которые мы хотим показать.
Обратите внимание, что эти кнопки относятся к самой библиотеке, поэтому вам не нужно добавлять в нее дополнительный код.
Вот и все.
Вы можете продолжить работу в этом направлении и настроить стили для этих элементов.
Если вы хотите изменить значок кнопки отключения микрофона, но сохранить ее функциональность, это работает точно так же.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)Как видите, вы можете определить собственный класс Painter для кнопки и выбрать значки.
Компонент предоставляет некоторые метаданные, поэтому все, что вам нужно сделать, это посмотреть на состояние, например showMuted в этом примере, чтобы выбрать правильный значок.
И это действительно главное преимущество Media3 Compose.
Вы можете сосредоточиться на внешнем виде и удобстве приложения, а Media3 позаботится обо всем остальном.
Он считывает состояние проигрывателя, взаимодействует с ним и обрабатывает все переходы и анимации.
Теперь перейдем от пользовательского интерфейса к обмену медиафайлами.
Вы можете делиться статусом воспроизведения с другими приложениями на том же устройстве или подключенными к удаленным устройствам.
Основной способ публикации текущего воспроизведения – использование MediaSession.
Оно позволяет другим пользователям видеть статус и метаданные воспроизведения, а также управлять им.
Именно так Gemini, уведомления о медиаконтенте в системном интерфейсе, Cast, Wear OS и сопутствующие приложения Android Auto взаимодействуют с вашим приложением.
MediaSession – это оболочка для проигрывателя Media3, которую очень легко создать, если у вас есть проигрыватель.
MediaSession.Builder(context, player).build()Как и в случае с Compose UI, это может быть ExoPlayer или любой другой проигрыватель, который вы используете в приложении.
Если вы воспроизводите контент в фоновом режиме, то обычно размещаете его в MediaSessionService или MediaLibraryService, которые автоматически обрабатывают весь жизненный цикл, а также такие вещи, как уведомления.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}Но если у вас есть мультимедийное приложение, вы, скорее всего, уже знаете все это.
Сегодня я хочу показать вам обновленный CastPlayer.
Google Cast позволяет переносить воспроизведение на удаленное устройство, например Android TV, Chromecast или умную колонку.
Раньше мы публиковали отдельный Cast SDK, который сейчас находится в режиме обслуживания.
Вместо этого Media3 Cast полностью заменяет его, поскольку обеспечивает гораздо более простую и оптимизированную интеграцию.
Самым важным элементом здесь является CastPlayer, который реализует тот же стандартный интерфейс проигрывателя, что и ExoPlayer.
ExoPlayer предназначен для локального воспроизведения, а CastPlayer – это делегирующий экземпляр, который может воспроизводить контент локально через ExoPlayer или удаленно через протокол Cast.
и он сделает все за вас.
Он отслеживает изменения устройства вывода, устанавливает подключение, синхронизирует состояние и при необходимости переносит воспроизведение.
Поэтому интегрировать CastPlayer в приложение очень просто.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Вместо того чтобы создавать ExoPlayer и передавать его в MediaSession, вы используете CastPlayer.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Вам нужно только инициализировать систему Cast перед ее использованием в приложении.
Обратите внимание, что CastPlayer создает ExoPlayer для локального воспроизведения. Чтобы настроить локальный проигрыватель, вы можете создать собственный ExoPlayer и передать его в CastPlayer.
Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()И это снова тот же принцип Media3.
Мы предоставляем простые готовые блоки API, а также все необходимые параметры для их настройки.
И вам может быть интересно, как пользователь на самом деле переключается между локальным и удаленным воспроизведением в этой настройке.
Она уже работает в диалоговом окне переключателя вывода звука в уведомлении о воспроизведении медиаконтента.
Это маленькая кнопка в углу.
Но что, если вы хотите, чтобы кнопка трансляции была на экране воспроизведения и ее было проще найти?
Помните этот настроенный интерфейс Compose, который мы только что создали несколько минут назад?
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Media3 Cast также предоставляет готовую кнопку, которую вы можете там разместить.
Вам нужно просто добавить готовый MediaRouteButton в topControls. Никаких дополнительных параметров настраивать не нужно.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)Нажав на эту кнопку, вы откроете то же диалоговое окно переключателя системного вывода, и CastPlayer перенесет воспроизведение.
Вы также можете заметить, что я подключил CastPlayer к интерфейсу, чтобы все, что происходит удаленно, отражалось в локальном интерфейсе.
А теперь я передаю слово Кристине, которая расскажет о редактировании.
Спасибо!
Здравствуйте! Теперь давайте перейдем к редактированию и расширенным эффектам.
Media3 API состоит из нескольких компонентов.
Во-первых, Transformer – это основная точка входа для наших API редактирования.
Transformer обрабатывает, перекодирует и экспортирует медиаконтент в файл.
Далее идет CompositionPlayer, который в реальном времени показывает, как будет выглядеть медиакомпозиция.
Это позволяет просматривать и настраивать изменения перед окончательным экспортом.
Затем у нас есть эффекты.
Вы можете полностью контролировать покадровую обработку, когда применяете простые визуальные эффекты или добавляете графические оверлеи.
Наконец, у нас есть Muxer.
Это последний этап обработки, на котором аудио, видео и метаданные записываются в стандартные контейнеры MP4.
В течение последнего года мы переосмысливали и перестраивали процесс редактирования видео с нуля, и сегодня я рад поделиться с вами основными улучшениями.
Зачем мы перестроили конвейер?
Таким образом, устаревшая архитектура создавала несколько проблем.
Во-первых, она не могла распространять метаданные на всех этапах обработки.
Он был тесно связан с OpenGL, что ограничивало гибкость современных графических инструментов.
а сложный API затруднял настройку и обслуживание.
Чтобы решить эти проблемы, мы создали совершенно новый конвейер обработки.
Во-первых, теперь гораздо проще собирать сложные композиции из нескольких объектов.
Во-вторых, отказ от OpenGL позволяет использовать современные графические фреймворки, например WebGPU.
Наконец, Media3 теперь напрямую управляет декодером и кодером, обеспечивая большую согласованность и эффективность на всех устройствах.
Давайте посмотрим, как работает этот конвейер.
Декодеры выводят кадры непосредственно в аппаратные буферы, управляемые Media3.
Агрегатор кадров координирует кадры в нескольких последовательностях, предоставляя вашему пользовательскому процессору кадров прямой доступ к чтению и обработке этих буферов перед их отправкой в кодировщик.
Интерфейс FrameProcessor служит основной точкой расширения для обработки на уровне приложения.
public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}Через интерфейс прослушивателя приложение уведомляет Media3 о том, что кадр обработан, и передает барьеры синхронизации. Таким образом Media3 точно знает, когда можно повторно использовать буферы.
Примечание об унифицированной памяти. Если Media3 берет на себя управление аппаратными буферами, ваше приложение может читать и изменять данные непосредственно на месте.
Такой подход позволяет снизить нагрузку на память и ускорить обработку.
Одним из разработчиков, использующих новые API для редактирования, является Adobe.
Команда Adobe Premiere использовала API Media3 для редактирования в качестве основы своей инфраструктуры редактирования медиафайлов, чтобы обеспечить лучшую совместимость с экосистемой Android, сохраняя при этом гибкость, необходимую для использования собственного конвейера и WebGPU.
В Media3 1.12 мы добавили новые эффекты.
Начнем с оптимизации видео.
Наша архитектура основана на модульной структуре.
Ваше приложение интегрировано с сервисами Google Play для эффектов Media3, которые взаимодействуют с модулем улучшения сервисов Google Play.
Модуль улучшения видео включает тональную компрессию, шумоподавление, восстановление резкости и суперразрешение.
Чтобы интегрировать Video Enhance в конвейер, потребуется всего несколько строк кода.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)С помощью EnhancementSessionDecorator.Builder вы можете выборочно включать такие возможности, как тональная компрессия, восстановление резкости или апскейлинг, и передавать полученные frameProcessorFactory и вспомогательные средства аппаратного буфера в Transformer.Builder.
Ещё один интересный эффект, который появится в Media3 1.12, – студийный звук.
Функция "Студийный звук" автоматически анализирует звуковую дорожку видео и разделяет ее на отдельные элементы, такие как речь, окружающий шум и музыка.
Авторы могут легко убрать шум ветра или толпы, чтобы на передний план вышли голоса и музыка.
Чтобы интегрировать Studio Sound, передайте аудиопроцессор Studio Sound в объект эффекта.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()Аудиопроцессор будет прикреплен к вашему мультимедийному объекту.
При создании аудиопроцессора Studio Sound прикрепите обратный вызов прослушивателя, чтобы получить сопоставление проанализированных аудиоканалов через onResult.
val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)Перебирайте каждый канал, чтобы извлечь индекс канала, идентифицированный тип аудио и читаемую метку, когда результат операции доступен.
Давайте послушаем, как работает студийный звук.
Итак, первый эффект. Детка, не плачь.
Мы режем для вашего ужина.
На фоне слышен плач ребенка, а теперь с примененным эффектом.
Да, как вы слышите, на фоне не было плача ребенка благодаря эффекту студийного звука.
Теперь давайте перейдем к получению метаданных.
Мы рассмотрим, как Media3 упрощает чтение метаданных контейнера и извлечение кадров.
Начиная с версии MetadataRetriever, Room легко интегрируется с сопрограммами Kotlin, что позволяет писать чистый неблокирующий код.
Он имеет более чистый и менее подверженный ошибкам дизайн, в котором используется один неизменяемый ретривер для каждого мультимедийного объекта.
Она работает одинаково во всех версиях API и на всех устройствах.
и поддерживает множество форматов медиаконтента.
Вот пример использования MetadataRetriever.
MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}Используя сопрограммы Kotlin, вы можете асинхронно получать длительность, проверять форматы треков и метаданные в одном чистом блоке.
Теперь поговорим о FrameExtractor.
Это позволяет быстро и эффективно извлекать декодированные кадры видео из мультимедийного объекта.
Он автоматически управляет базовыми ресурсами, такими как декодеры, позволяет применять визуальные эффекты или сопоставление тонов HDR, а также обеспечивает точный контроль над поиском, чтобы вы могли получить нужный кадр.
Вот как выглядит извлечение кадров в коде.
FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}Вы создаете экземпляр с такими параметрами, как параметры поиска, уменьшение масштаба графического процессора, эффекты и поддержка HDR, а затем асинхронно ожидаете миниатюры и определенные кадры, используя чистые приостанавливающие вызовы.
А теперь перейдем к Доновану.
С уважением, Кристина
Теперь я хочу рассказать о некоторых технологиях потоковой передачи, которые мы добавили в Media3, включая поддержку таких форматов, как Eclipsa Video и Eclipsa Audio.
Поэтому одна из основных задач Media3 – опережать стандарты потоковой передачи, чтобы вам не приходилось переписывать медиаконвейер каждый раз, когда появляется новая спецификация.
Вот несколько примеров того, что мы недавно выпустили.
У нас есть управление контентом, которое позволяет динамически переключать пути CDN во время трансляции.
Мы поддерживаем вставку объявлений под управлением сервера, которая позволяет легко добавлять персонализированную рекламу.
Мы также добавили поддержку AV1 в нескольких точках входа, включая полную поддержку AV1 HDR.
Мы добавили поддержку профессионального видео на Android для высококачественной съемки и редактирования.
Мы также добавляем поддержку фото с движением в форматах JPEG и HEIC.
И, конечно, мы добавляем поддержку Eclipsa Video и Eclipsa Audio, о которых я расскажу подробнее чуть позже.
Хорошая новость заключается в том, что все это поддерживается автоматически по мере того, как устройства получают поддержку.
Теперь давайте посмотрим на следующий этап развития HDR, Eclipsa Video, и основную проблему, которую он пытается решить.
Предыдущие форматы HDR предоставляли описательные метаданные, то есть определяли, как должна выглядеть сцена, но не указывали дисплею, как ее отображать.
Давайте сначала разберемся с этим.
Для разных условий освещения рекомендуются разные уровни яркости экрана, чтобы вам было удобно просматривать контент.
В тускло освещенной комнате этот диапазон будет составлять от 100 до 300 нит.
И наоборот, чем ярче свет вокруг, тем ярче должен быть экран, чтобы вы могли четко видеть контент. Например, если вы смотрите на телефон под прямыми солнечными лучами, яркость экрана может достигать 1000, 2000 или даже больше нит.
Традиционный SDR работает в диапазоне от 100 до 300.
И, конечно, HDR не предназначен для глобального увеличения яркости экрана.
Вы хотите расширить динамический диапазон, чтобы локализовать блики от огня и солнечного света.
Однако со смартфонами все немного сложнее, потому что мы берем их с собой повсюду: и в темную комнату, и на солнечный пляж.
Кроме того, во многих приложениях элементы интерфейса SDR сочетаются с контентом SDR и HDR в смешанных фидах, что может привести к неприятному, ослепляющему эффекту, особенно если вы прокручиваете ленту поздно ночью.
Теперь вы понимаете, почему HDR стоит того, верно?
Это позволяет нашему контенту выглядеть отлично в разных средах.
Мы даже заметили, что при включенном HDR вовлеченность пользователей возрастает.
Но проблема в том, что существующие форматы HDR просто не учитывают эти условия.
Именно в этом заключается главное преимущество Eclipsa Video.
Пауза для смеха.
Спасибо!
Спасибо!
Поэтому вместо описательных метаданных Eclipsa Video предоставляет предписывающие метаданные.
И это действительно построено на трех основных столпах.
Первый столп – это опорная точка белого цвета.
Это позволяет зафиксировать стандартную яркость на уровне точки белого SDR, скорректированной с учетом окружающего освещения, и избежать резких скачков яркости.
Второй столп – это базовый уровень SDR для авторов.
Это позволяет сохранить точную кривую сопоставления тонов, заданную автором, при преобразовании в SDR.
В-третьих, у нас есть адаптивные кривые усиления запаса, которые детерминированно масштабируют светлые участки в соответствии с запасом HDR дисплея в реальном времени, чтобы контент соответствовал среде, в которой он просматривается.
Эта функция доступна в ExoPlayer в Media3 1.11 на устройствах с Android 17 и более поздними версиями.
Eclipsa Video основана на открытых стандартах, что обеспечивает совместимость с другими отраслями.
Сейчас самое время начать поддерживать HDR.
Что касается аудио, мы предлагаем Eclipsa Audio – бесплатный формат пространственного 3D-звука с открытым исходным кодом.
Он основан на спецификации IAMF от Alliance for Open Media.
Он объединяет амбифонию на основе сцен и аудио на основе каналов в один поток с несколькими микшированными презентациями.
Это позволяет манипулировать объектами, например улучшать диалоги, и будет работать на всем, от стереонаушников до систем объемного звучания.
В Media3 есть два пути воспроизведения.
На устройствах с Android 17 и более поздних версий ExoPlayer и Android Audio Framework будут автоматически воспроизводить Eclipsa Audio.
Для Android 16 и более ранних версий у вас по-прежнему есть выбор.
Вы можете включить расширение IAMF через дополнительную библиотеку дополнений Media3.
А для создания аудиодорожек IAMF можно использовать мультиплексоры Media3.
Но поддержка высококачественных форматов – это только половина дела.
Мы должны эффективно обеспечивать это качество.
Теперь я хочу рассказать о том, как Media3 делает наши конвейеры быстрее и эффективнее.
Как и многие другие функции, о которых мы сегодня говорили, большинство этих улучшений производительности доступны по умолчанию. Вам нужно лишь поддерживать актуальную версию Media3.
Например, функция разгрузки аудио PCM позволяет направлять воспроизведение аудио на блок обработки с низким энергопотреблением.
Наш первоначальный анализ показал, что при воспроизведении аудио с выключенным экраном экономия энергии может достигать 20 %.
Помимо разгрузки аудио PCM, у нас есть динамическое планирование, которое согласовывает пробуждения внутреннего цикла рендеринга ExoPlayer с VSYNC и доступностью буфера декодера.
Также у нас есть предварительный прогрев рендерера, который позволяет предварительно прогреть вторичный рендерер видео, что позволит мгновенно переключать плейлисты и упростит вставку рекламы.
Кроме того, у нас есть функция обнаружения зависания проигрывателя и предварительного кеширования, которая автоматически обнаруживает зависшие буферы и средства визуализации и очищает предварительно кешированную память.
Ещё один оптимальный режим – режим прокрутки.
Это касается приложений, в которых пользователи часто прокручивают контент.
Когда вы включаете режим прокрутки, перемещать ползунок становится намного проще и удобнее, как вы можете видеть здесь.
Кстати, режим прокрутки отключен.
А справа – с включенным.
Спасибо!
Спасибо!
Теперь
Теперь поговорим о стабильности работы приложения на Android.
Нам нужно бережно относиться к общим ресурсам ОЗУ, ЦП и батареи. Поэтому, когда Android ужесточает правила в отношении усиления защиты аудио и активных служб, приложения, которые управляют собственными необработанными службами и битовыми картами, часто могут сталкиваться с ошибками.
Например, вы могли видеть исключения, связанные с запретом на запуск активной службы или нехваткой памяти в Binder.
Поэтому мы усилили защиту сеанса медиасервиса и менеджера уведомлений о медиаконтенте, а также наших конвейеров растровых изображений несколькими способами, включая предотвращение устаревших интентов активных служб и ограничение памяти для обложек и тому подобное.
Но если вы используете Media3 и следуете стандартным рекомендациям, все эти ограничения будут обрабатываться автоматически и не повлияют на работу приложения.
И наконец, я хочу рассказать о функции, которая скоро появится в Media3.
При кодировании или экспорте видео разнообразие аппаратного обеспечения может стать одной из самых больших проблем.
Во многих приложениях используется универсальная конфигурация кодека, что приводит к неоптимальному качеству и производительности.
Поэтому мы создали CodecDB.
CodecDB основана на обширных измерениях реального видео с использованием стандартизированных показателей качества и аппаратного тестирования на устройствах Android.
Давайте посмотрим, как это работает.
Слева показан кадр из видео, снятого на телефон с использованием стандартных настроек, которые подходят для многих устройств.
а справа – оптимальную конфигурацию на уровне устройства, полученную из CodecDB.
В чем же разница?
Разница в качестве будет наиболее заметна на участках с высокой детализацией.
Посмотрите на четкость ветвей деревьев и кустарников или на резкость края здания.
Таким образом, CodecDB позволяет улучшить качество просмотра, используя возможности конкретного устройства.
Использовать CodecDB в коде довольно просто.
val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()Вы будете использовать шаблон построителя этапов, который поможет вам настроить каждый обязательный параметр.
Поэтому вы укажете свой вариант использования.
В данном случае мы используем его для редактирования.
Это сообщает CodecDB, что вы используете конвейер экспорта или перекодирования Media3.
Например, можно настроить съемку на камеру, если вы используете ее.
Также нужно будет указать целевое разрешение и частоту кадров.
Затем, вместо того чтобы угадывать, нужно ли для видео 1080p 8 или 15 Мбит/с, вы указываете CodecDB, какой кодек хотите использовать, и запрашиваете целевое качество восприятия.
В этом случае мы используем уровень качества "Очень хорошее".
CodecDB находит эмпирические кривые производительности для этой однокристальной системы и возвращает идеальные параметры кодека и кодировщика.
Мы планируем запустить CodecDB в Media3 в ближайшее время. В этой библиотеке будут использоваться нативные типы Media3.
Поэтому интегрировать его с существующим кодом Media3 будет очень просто.
Окей.
Сегодня мы рассмотрели множество потрясающих функций Media3. Давайте подведем итоги.
Сегодня вы узнали о новых модулях Compose UI, CastPlayer, обновленном конвейере редактирования и множестве новых эффектов, которые можно использовать в Media3.
Также появились новые инструменты для извлечения метаданных и кадров.
Мы также рассмотрели поддержку новых технологий потоковой передачи, повышение производительности и утилиты.
Media3 – это библиотека с открытым исходным кодом, поэтому вы можете посмотреть, как мы реализовали все эти функции, в нашем репозитории GitHub.
На сегодняшний день последняя стабильная версия Media3 – 1.11.1.
Вы также можете ознакомиться с версией 1.12.0, которая сейчас находится на этапе альфа-тестирования.
Перейдите по короткой ссылке, чтобы открыть документацию для разработчиков по Media3.
Спасибо за внимание и приятного просмотра оставшейся части саммита.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Player(viewModel.exoPlayer)Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)MediaSession.Builder(context, player).build()class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()