Engenheiro de relações com desenvolvedores
O consumo de mídia representa uma grande parte do tempo que os usuários passam em dispositivos móveis, o que exige que a experiência de reprodução seja impecável, altamente eficiente e que não consuma muita bateria. Além disso, os usuários esperam que essas experiências sejam transferidas de forma integrada entre os dispositivos, de smartphones a TVs de sala de estar e muito mais.
Saiba como as atualizações de arquitetura no Media3 e no ExoPlayer proporcionam uma reprodução perfeita em vários formatos. A discussão se concentra na criação de interfaces responsivas com o kit de ferramentas de interface do Media3 Compose, na eliminação do armazenamento em buffer em feeds usando o novo PreloadManager e na extensão do app para telas de sala de estar e Android XR com o CastPlayer e o áudio espacial.
Transcrição
Ok.
Olá, pessoal!
Meu nome é Toni.
Sou engenheiro na equipe de mídia do Android.
E, junto com a Kristina e o Donovan, vamos falar sobre como o Jetpack Media3 pode ajudar você com todas as suas necessidades de mídia no Android.
A Media3 é a biblioteca de suporte do Android para tudo relacionado a mídia, seja reprodução com o ExoPlayer, compartilhamento com a sessão de mídia e o Cast, edição com o Transformer ou inspeção, como extração de frames ou metadados.
A Media3 abrange todo esse espaço em um único grupo de bibliotecas.
Como uma biblioteca de suporte, ela é incluída no app.
Isso significa que podemos garantir a consistência, para que o mesmo código seja executado em todas as versões do SO e em todos os dispositivos.
Isso também ajuda na capacidade de atualização.
Cada correção de bug que fazemos chega até a API 24 e não está vinculada a nenhum ciclo de lançamento do SO.
Como uma biblioteca de suporte, podemos permitir opções de personalização muito ricas, o que não seria possível no framework do Android.
E, como uma biblioteca no GitHub, somos de código aberto.
Isso significa que todas as mudanças que fazemos ficam disponíveis imediatamente no GitHub.
Temos um Issue Tracker ativo e aceitamos solicitações de pull.
O principal destaque aqui, em todos esses casos de uso, é que nos preocupamos com todos os níveis de detalhe, desde elementos da interface de alto nível até APIs simples e prontas para uso, mas também opções de personalização profunda, um amplo suporte para vários formatos e padrões e o desempenho e a eficiência que sustentam tudo isso.
A Media3 abrange todo esse espaço.
Hoje, vamos nos aprofundar em alguns desses tópicos.
Vamos falar sobre o novo módulo de interface do Compose, o novo CastPlayer que facilita ainda mais o compartilhamento com sessões de mídia, um pipeline de edição reformulado com novos efeitos avançados, novas ferramentas para recuperar metadados e frames, algumas novas tecnologias de streaming, em especial, vídeo e áudio Eclipsa, e vários utilitários de qualidade de experiência para pré-carregamento, arrastar e economia de bateria.
Agora, vamos direto ao primeiro tópico: como a Media3 pode ajudar a criar elementos da interface para mídia.
Recentemente, publicamos dois novos módulos: Media3 UI Compose e Media3 UI Compose Material 3.
Aqui, você pode ver um player combinável de alto nível, como foi lançado no Media3 1.10 sem outras modificações.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Ele funciona imediatamente.
Ele só precisa de um player Media3.
No exemplo aqui, é um ExoPlayer, que geralmente fica no modelo de visualização.
Mas pode ser outra coisa, como um CastPlayer, um controlador de mídia ou qualquer outro player que você tenha no app.
Vale destacar que o ExoPlayer é a fonte de verdade.
Ele contém todas as informações, e o elemento combinável do player cuida da renderização da IU e da conexão de tudo.
Mas e se você quiser personalizar essa interface, novos botões, posições diferentes, estilos diferentes, sobreposições de texto?
O player combinável é feito de elementos básicos.
Você pode substituí-los.
Você pode personalizar cada bloco individual ou usá-los de forma independente em outro lugar do app.
Por exemplo, é possível personalizar a parte de cima, o centro e o bottomControls separadamente.
Também é possível personalizar a aparência do obturador, que é mostrado quando os vídeos não estão sendo reproduzidos, ou uma sobreposição de erro.
Mas essa é apenas a primeira camada.
O centro padrão e o bottomControls também têm espaços mais detalhados que você pode personalizar.
Por exemplo, você pode trocar o botão central e deixar o restante como está ou adicionar elementos acima ou abaixo do controle deslizante.
O Media3 Compose oferece todos os elementos básicos necessários para isso.
Por exemplo, temos vários botões, como reproduzir/pausar, modo de repetição, modo aleatório, botão de desativar microfone e outros.
Depois, há indicadores de progresso, como o controle deslizante e o texto de posição mostrados na tela.
Há também vários contêineres e sobreposições.
O player combinável de alto nível que mostrei é um exemplo, mas há outros, como sobreposições de erro ou legendas, e muito mais.
Além disso, temos outros componentes úteis, como este minicontrolador aqui.
Você pode colocar em uma página inferior enquanto o usuário navega pelo app.
E ainda não acabou.
Estamos ampliando essa biblioteca, por exemplo, com novos controles de edição, como um controle deslizante de corte que será lançado em breve.
Agora, vamos ver essa personalização em ação para entender como esses elementos básicos são usados.
Player(viewModel.exoPlayer)Digamos que queremos mover o texto de posição e duração, que atualmente estão ao lado do controle deslizante, para cima dele.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Então, o que podemos fazer é substituir esses bottomControls.
Ainda podemos usar o componente padrão, mas definimos os slots esquerdo e direito como vazios para que eles desapareçam.
Em vez disso, colocamos o texto de posição e duração acima dele no slot superior com um separador personalizado para torná-lo mais interessante.
Agora, digamos que queremos remover totalmente a sobreposição centerControls e, em vez disso, colocar um botão de reprodução logo acima do controle deslizante com um controle de velocidade e um botão de desativar som.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}E essa é exatamente a mesma ideia de novo.
Podemos definir o centerControls como vazio para que eles desapareçam e expandir o espaço acima do controle deslizante com um pouco de espaço extra e os três botões que queremos mostrar.
Esses botões são da própria biblioteca, então você não precisa colocar nenhum código extra nela.
Isso é tudo o que você precisa.
E você pode continuar seguindo esse caminho para estilizar esses elementos também.
Se você quiser mudar o ícone do botão de desativar o som, por exemplo, mas manter a funcionalidade, o processo é o mesmo.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)Como você pode ver aqui, é possível definir um pintor personalizado para o botão e escolher os ícones.
O componente fornece alguns metadados. Então, basta analisar o estado, como showMuted neste exemplo, para escolher o ícone certo.
Esse é o principal benefício do Media3 Compose.
Você pode se concentrar na aparência do app, e o Media3 cuida do resto.
Ele lê o estado do player, interage com ele e processa todas as transições e animações.
Agora vamos passar da interface para o compartilhamento de mídia.
Você pode compartilhar o estado de reprodução com outros apps no mesmo dispositivo ou conectados a dispositivos remotos.
A principal maneira de publicar a reprodução em andamento é com o MediaSession.
Ela disponibiliza o estado e os metadados da reprodução para outras pessoas e permite que elas controlem a reprodução.
É assim que o Gemini, a notificação de mídia da interface do sistema, o Cast, o Wear OS ou os apps complementares do Android Auto se comunicam com seu app.
O MediaSession envolve um player do Media3, e é muito fácil criar um depois que você tem um player.
MediaSession.Builder(context, player).build()Novamente, pode ser um ExoPlayer ou qualquer outro player que você tenha no app, assim como na interface do Compose.
Se você estiver reproduzindo em segundo plano, geralmente hospedará isso em um MediaSessionService ou em um MediaLibraryService, que também processa automaticamente todo o ciclo de vida para você, além de itens como uma notificação.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}Mas se você tem um app de música, provavelmente já sabe de tudo isso.
A novidade que quero mostrar é o CastPlayer, que foi reescrito recentemente.
Com o Google Cast, você pode transferir a reprodução local para um dispositivo remoto, como Android TVs, Chromecast ou alto-falantes inteligentes.
Costumávamos publicar um SDK do Cast separado, que agora está no modo de manutenção.
Em vez disso, o Media3 Cast o substitui totalmente, porque oferece uma integração muito mais simples e otimizada.
O elemento mais importante aqui é o CastPlayer, que implementa a mesma interface de player padrão do ExoPlayer.
Enquanto o ExoPlayer é para reprodução local, o CastPlayer é uma instância de delegação que pode ser reproduzida localmente pelo ExoPlayer ou remotamente pelo protocolo Cast.
E ele cuida de tudo.
Ele detecta as mudanças no dispositivo de saída, estabelece a conexão, sincroniza o estado e transfere a reprodução para você quando necessário.
Por isso, é muito simples integrar o CastPlayer ao seu app.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Em vez de criar um ExoPlayer e compartilhá-lo com MediaSession, use um CastPlayer.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()A única outra coisa que você precisa fazer é inicializar o sistema do Cast antes de usá-lo em algum lugar do app.
Esse CastPlayer cria um ExoPlayer internamente para a reprodução local. Portanto, para personalizar ainda mais o player local, você também pode criar seu próprio ExoPlayer e transmiti-lo como um player local para esse 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()E esse é o mesmo princípio da Media3.
Oferecemos os componentes básicos da API prontos para uso, mas também todas as opções de personalização necessárias para você fazer mais ajustes.
E você pode estar se perguntando como o usuário realmente alterna entre a reprodução local e remota nessa configuração.
Ele já funciona imediatamente com a caixa de diálogo do seletor de saída do sistema na notificação de mídia.
É o botão pequeno no canto.
Mas e se você quiser mostrar um botão Transmitir na tela de reprodução para facilitar a descoberta?
Lembra da interface personalizada do Compose que criamos há alguns minutos?
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)O Media3 Cast também oferece um botão pronto para uso que pode ser colocado lá.
É tão simples quanto adicionar o MediaRouteButton pronto ao topControls, por exemplo, e não precisamos de mais parâmetros para configurar. É só isso.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)Ao clicar nesse botão, a mesma caixa de diálogo do seletor de saída do sistema é aberta, e o CastPlayer transfere a reprodução para você.
E você também pode notar que conectei o CastPlayer à IU aqui para garantir que o que acontecer remotamente seja refletido na IU local.
Agora, passo a palavra para a Kristina, que vai falar sobre casos de uso de edição.
Nossa equipe agradece.
Olá! Agora vamos falar sobre edição e efeitos avançados.
As APIs Media3 consistem em vários componentes.
Primeiro, o Transformer é o principal ponto de entrada para nossas APIs de edição.
O Transformer processa, transcodifica e exporta mídia para um arquivo.
Em seguida, temos o CompositionPlayer, que renderiza prévias em tempo real das suas composições de mídia.
Isso permite que você visualize e ajuste suas edições antes da exportação final.
Depois, temos os efeitos.
Ele oferece controle total sobre o processamento quadro a quadro ao aplicar efeitos visuais simples ou adicionar sobreposições gráficas.
Por fim, temos o Muxer.
Ele serve como o estágio de saída final no pipeline, recebendo faixas de áudio, vídeo e metadados processadas e gravando-as em contêineres MP4 padronizados.
Passamos o último ano repensando e reconstruindo nosso pipeline de edição de vídeo do zero e, hoje, estou feliz em compartilhar as principais melhorias.
Por que recriamos o pipeline?
Portanto, a arquitetura legada apresentava vários desafios.
Primeiro, ele não conseguia propagar metadados em todas as etapas de processamento.
Ele era profundamente acoplado ao OpenGL, o que limitava a flexibilidade para ferramentas gráficas modernas.
Além disso, a API complexa dificultava a personalização e a manutenção para os desenvolvedores.
Para resolver esses desafios, criamos um pipeline de processamento totalmente novo.
Primeiro, agora é muito mais simples montar composições complexas com vários recursos.
Em segundo lugar, ao se desvincular do OpenGL, você tem total liberdade para usar frameworks gráficos modernos, como a WebGPU, por exemplo.
Por fim, a Media3 agora gerencia o decodificador e o codificador diretamente, garantindo maior consistência e eficiência em todos os dispositivos.
Vamos analisar como esse pipeline funciona.
Os decodificadores mostram frames diretamente em buffers de hardware gerenciados pelo Media3.
O agregador de frames coordena frames em várias sequências, dando ao processador de frames personalizado acesso direto para ler e processar esses buffers antes de enviá-los ao codificador.
A interface FrameProcessor serve como o principal ponto de extensão para o processamento no nível do app.
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();
}Com a interface do listener, o aplicativo notifica o Media3 sempre que um frame é processado, transmitindo as barreiras de sincronização para que o Media3 saiba exatamente quando os buffers podem ser reutilizados com segurança.
Uma observação sobre a memória unificada: ao permitir que o Media3 assuma o controle dos buffers de hardware, seu app pode ler e modificar dados diretamente no local.
Essa abordagem de cópia zero reduz a sobrecarga de memória e mantém o processamento ultrarrápido.
A Adobe é um dos desenvolvedores que estão aproveitando as novas APIs de edição.
A equipe do Adobe Premiere usou as APIs de edição do Media3 como base para a infraestrutura de edição de mídia, garantindo maior compatibilidade com o ecossistema Android e mantendo a flexibilidade necessária para usar o próprio pipeline personalizado e a WebGPU.
Em seguida, tenho o prazer de anunciar que estamos lançando novos efeitos como parte da versão 1.12 do Media3.
Começando com as melhorias de vídeo.
Em segundo plano, nossa arquitetura depende de uma configuração modular.
Seu aplicativo se integra ao Media3 Effect Play Services, que faz interface com o módulo de melhoria do Play Services.
O módulo de melhoria de vídeo inclui recursos de mapeamento de tons, remoção de ruído, remoção de desfoque e super resolução.
A integração do Video Enhance ao seu pipeline requer apenas algumas linhas de código.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)Usando EnhancementSessionDecorator.Builder, é possível ativar seletivamente recursos como mapeamento de tons, remoção do desfoque ou aumento de resolução e transmitir os auxiliares de buffer de hardware e frameProcessorFactory resultantes para Transformer.Builder.
Outro efeito interessante que será lançado no Media3 1.12 é o som de estúdio.
O Studio Sound analisa automaticamente a faixa de áudio do vídeo e a separa em elementos individuais, como fala, ruído ambiente e música.
Os criadores de conteúdo podem reduzir facilmente as distrações de vento ou multidão, ao mesmo tempo em que destacam diálogos e músicas.
Para integrar a Acústica de Estúdio, transmita o processador de áudio da Acústica de Estúdio dentro de um objeto de efeito.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()Isso anexa o processador de áudio ao item de mídia.
Ao criar o processador de áudio da Acústica de Estúdio, anexe o callback do listener para receber o mapeamento do canal de áudio analisado usando 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))
}
}
},
)Itere em cada canal para extrair o índice do canal, o tipo de áudio identificado e o rótulo legível quando o resultado da operação estiver disponível.
Vamos ouvir a Acústica em ação.
Então, o primeiro efeito. Amor, não chore, por favor.
Estamos cortando para o seu jantar.
Dá para ouvir o bebê chorando ao fundo, e agora com o efeito aplicado.
Sim, como você pode ouvir, não havia nenhum bebê chorando no fundo, graças ao efeito de som de estúdio.
Agora, vamos falar sobre a recuperação de metadados.
Vamos conferir como o Media3 facilita a leitura de metadados de contêineres e a extração de frames.
A partir do MetadataRetriever, ele se integra perfeitamente às corrotinas do Kotlin para um código limpo e sem bloqueios.
Ele tem um design mais limpo e menos propenso a erros, usando um recuperador imutável por item de mídia.
Ele funciona de maneira consistente em todas as versões de API e dispositivos.
Além disso, ele é compatível com uma ampla variedade de formatos de mídia.
Veja um exemplo prático usando 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()
}Usando corrotinas do Kotlin, é possível recuperar a duração de forma assíncrona, inspecionar formatos de faixa e metadados, tudo em um único bloco limpo.
Agora, vamos falar sobre o FrameExtractor.
Ela torna a extração de frames de vídeo decodificados do item de mídia rápida e eficiente.
Ele gerencia recursos subjacentes, como decodificadores, de forma automática, permite aplicar efeitos visuais ou mapeamento de tons HDR e oferece controle de busca refinado para que você possa acessar o frame exato que precisa.
Veja como a extração de frames funciona no código.
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()
}Você cria uma instância com opções como parâmetros de busca, redução de escala de GPU, efeitos e suporte a HDR. Em seguida, aguarda de forma assíncrona as miniaturas e os frames específicos usando chamadas de suspensão limpas.
Agora, vamos para o Donovan.
Obrigada, Kristina.
Agora, vamos falar sobre o suporte à tecnologia de streaming que adicionamos ao Media3, incluindo suporte a formatos como Eclipsa Video e Eclipsa Audio.
Por isso, um esforço central na Media3 é se manter à frente dos padrões de streaming para que, sempre que uma nova especificação for lançada, você não precise reescrever seu pipeline de mídia.
Confira alguns exemplos de recursos que lançamos recentemente.
Temos o direcionamento de conteúdo, que permite alternar dinamicamente os caminhos de CDN de back-end no meio da transmissão.
Oferecemos suporte à inserção de anúncios guiada pelo servidor para uma junção de anúncios personalizada e integrada.
Também introduzimos o suporte ao AV1 em vários pontos de entrada, incluindo suporte completo ao AV1 HDR.
Temos suporte para vídeo profissional do Android para captura e edição de alta fidelidade.
Também estamos adicionando suporte para fotos com movimento nos formatos JPEG e HEIC.
E, claro, estamos adicionando suporte para o Eclipsa Video e o Eclipsa Audio, sobre os quais falarei mais em breve.
A boa notícia é que tudo isso é compatível automaticamente à medida que o suporte ao dispositivo é lançado.
Agora, vamos dar uma olhada na próxima evolução do HDR, o Eclipsa Video, e no principal problema que ele está tentando resolver.
Os formatos HDR anteriores forneciam metadados descritivos, ou seja, determinavam como a cena deveria ser, mas não informavam à tela como renderizá-la.
Vamos começar com isso.
Para ambientes diferentes, há recomendações diferentes para o brilho médio da tela, para que você possa ver o conteúdo com clareza e conforto.
Em um ambiente escuro, esse intervalo fica entre 100 e 300 nits.
E, claro, conforme você entra em ambientes cada vez mais claros, a tela precisa acompanhar e ficar mais brilhante para que você possa ver o conteúdo com clareza. Quando você está usando o smartphone sob luz solar direta, a tela pode precisar chegar a 1.000, 2.000 ou até mais nits para que você possa ver o conteúdo.
O SDR tradicional opera nessa faixa de 100 a 300.
E, claro, o HDR não serve para aumentar o brilho da tela como um todo.
Você quer expandir o intervalo dinâmico para ter destaques localizados para elementos como fogo e luz do sol.
O smartphone, no entanto, introduz algumas complicações, porque nós o levamos para todos os lugares, de um quarto escuro em casa a uma praia ensolarada.
Além disso, muitos apps misturam elementos da interface SDR com conteúdo SDR e HDR em feeds mistos, o que pode causar aquele efeito ofuscante e incômodo, especialmente se você estiver navegando tarde da noite.
E agora podemos ver por que o HDR vale a pena, certo?
Isso faz com que nosso conteúdo fique ótimo em mais ambientes.
E até notamos um aumento no engajamento do usuário quando o HDR está ativado.
Mas o problema é que os formatos HDR atuais não levam esses ambientes em consideração.
É aí que o Eclipsa Video realmente se destaca.
Pausa para risadas.
Nossa equipe agradece.
Nossa equipe agradece.
Portanto, em vez de metadados descritivos, a Eclipsa Video fornece metadados prescritivos.
E isso se baseia em três pilares principais.
O primeiro pilar é uma âncora de referência branca.
Isso bloqueia o brilho padrão no ponto branco SDR ajustado ao ambiente da tela, o que elimina picos repentinos de brilho.
O segundo pilar é o valor de referência da SDR do criador de conteúdo.
Isso preserva a curva de mapeamento de tons exata do criador de volta para SDR.
Em terceiro lugar, temos as curvas de ganho adaptáveis de headroom, que dimensionam de forma determinística os destaques para corresponder ao headroom de HDR em tempo real da tela, de modo que o conteúdo corresponda ao ambiente em que é visualizado.
Isso está disponível no ExoPlayer pronto para uso no Media3 1.11 em dispositivos com o Android 17 e versões mais recentes.
Além disso, o Eclipsa Video é baseado em padrões abertos para compatibilidade entre setores.
Por isso, nunca houve um momento melhor para oferecer suporte ao HDR.
Na parte de áudio, temos o Eclipsa Audio, um formato de áudio espacial 3D de código aberto e sem royalties.
Ele é baseado na especificação IAMF da Alliance for Open Media.
Ele combina áudio ambisônico baseado em cena com áudio baseado em canal em um único stream com várias apresentações mistas.
Isso permite manipulações semelhantes a objetos, como melhoria de diálogos, e funciona em tudo, desde fones de ouvido estéreo até configurações de som surround.
Na verdade, há dois caminhos de reprodução no Media3.
No Android 17 e versões mais recentes, o ExoPlayer e o Android Audio Framework renderizam o Eclipsa Audio automaticamente.
E para o Android 16 e versões anteriores, você ainda tem uma opção.
É possível ativar a extensão IAMF usando uma biblioteca de complementos opcional do Media3.
E, para criar, você pode gravar faixas de áudio IAMF usando os muxers do Media3.
Mas oferecer suporte a formatos de alta qualidade é apenas metade da história.
Precisamos oferecer essa qualidade de forma eficiente.
Agora, quero falar sobre como a Media3 está tornando nossos pipelines mais rápidos e eficientes.
Como muitas coisas que discutimos hoje, a maioria dessas melhorias de desempenho está disponível de forma imediata, basta manter sua versão do Media3 atualizada.
Um exemplo de recurso que temos é o descarregamento de áudio PCM, que pode rotear a reprodução de áudio para uma unidade de processamento de baixa potência.
Em nossa análise inicial, observamos uma economia de energia de até 20% na reprodução de áudio com a tela desligada.
Além do descarregamento de áudio PCM, temos a programação dinâmica, que alinha as ativações do loop de renderização interna do ExoPlayer com o VSYNC e a disponibilidade do buffer do decodificador.
Também temos o pré-aquecimento do renderizador, que permite pré-aquecer um renderizador de vídeo secundário, o que possibilita transições instantâneas de playlists e uma inserção de anúncios mais fácil.
Também temos detecção de player travado e pré-armazenamento em cache, que detecta automaticamente buffers e renderizadores travados e limita a memória pré-armazenada em cache de forma limpa.
Outro modo ideal é o de arrastar.
Isso é para apps em que os usuários arrastam o conteúdo com frequência.
Quando você ativa o modo de arrastar, fica muito mais fácil e rápido arrastar a barra de busca, como você pode ver aqui.
Isso é com o modo de arrastar desativado, aliás.
À direita, o modo de arrastar está ativado.
Nossa equipe agradece.
Nossa equipe agradece.
Certo.
Agora vamos falar sobre a integridade do sistema Android.
Precisamos respeitar os recursos compartilhados de RAM, CPU e bateria. Por isso, à medida que o Android reforça as regras de proteção de áudio e serviços em primeiro plano, os apps que gerenciam os próprios serviços brutos e bitmaps podem apresentar alguns erros.
Algumas coisas que você pode ter visto são exceções de início de serviço em primeiro plano não permitido ou exceções de falta de memória do binder.
Por isso, reforçamos a sessão do serviço de mídia e o gerenciador de notificações de mídia, além dos nossos pipelines de bitmap de várias maneiras, incluindo a prevenção de intents de serviço em primeiro plano desatualizadas e a limitação da memória de arte, entre outras coisas.
Mas o importante é que, se você estiver usando o Media3 e seguindo as orientações padrão, todas essas restrições de integridade do sistema serão processadas sem impacto.
Por fim, quero falar sobre um recurso que será lançado em breve no Media3.
Na hora de codificar ou exportar vídeos, a diversidade de hardware pode ser um dos maiores desafios.
Muitos apps usam uma configuração de codec única, o que, como você sabe, leva a uma qualidade e desempenho abaixo do ideal.
Por isso, criamos o CodecDB.
O CodecDB é baseado em medições de vídeo reais e extensas, usando métricas de qualidade padronizadas e comparativos de hardware em dispositivos Android.
Vamos ver como isso funciona.
À esquerda, temos um frame de um vídeo que gravamos em um smartphone usando uma configuração de vídeo muito geral que pode ser usada em muitos dispositivos.
À direita, usamos uma configuração ideal no nível do dispositivo que recebemos do CodecDB.
Então, onde estão as diferenças?
Se você olhar para as áreas com muitos detalhes, vai notar a maior diferença de qualidade.
Observe a nitidez nos galhos das árvores e nos arbustos ou a nitidez da borda do prédio.
O que temos aqui é uma experiência de visualização muito melhor que aproveita os recursos de um dispositivo específico usando o CodecDB.
E usar o CodecDB no código é bem simples.
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()Você vai usar um padrão de builder de estágio fluente que orienta você em todos os parâmetros necessários.
Portanto, você vai especificar seu caso de uso.
Aqui, estamos usando para edição.
Isso informa ao CodecDB que você está usando o pipeline de exportação ou transcodificação do Media3.
Outros casos de uso que você pode definir, por exemplo, são a captura de câmera, se for para isso que você está usando.
Você também vai transmitir a resolução e a taxa de quadros desejadas.
Em vez de adivinhar se o vídeo de 1080p precisa de 8 ou 15 megabits por segundo, você informa ao CodecDB qual codec quer usar e pede uma meta de qualidade perceptiva.
Nesse caso, estamos usando o nível de qualidade "muito bom".
Nos bastidores, o CodecDB procura curvas de comparativo de mercado empíricas para esse SOC exato e retorna os parâmetros ideais de codec e codificador.
Esperamos que o CodecDB seja lançado no Media3 em breve e, quando isso acontecer, ele vai usar tipos nativos do Media3.
Por isso, a integração com o código Media3 atual é muito simples.
Ok.
Hoje vimos muitas funcionalidades incríveis na Media3. Vamos recapitular.
Hoje, você aprendeu sobre nossos novos módulos de interface do Compose, o novo CastPlayer, o pipeline de edição reformulado e muitos novos efeitos que podem ser usados no Media3.
Também vimos novas ferramentas para recuperar metadados e frames.
Também analisamos o suporte a novas tecnologias de streaming, além de utilitários e melhorias de desempenho.
A Media3 é uma biblioteca de código aberto, então você pode ver como implementamos todos esses recursos no nosso repositório do GitHub.
Até o momento, a versão estável mais recente do Media3 é a 1.11.1.
Confira também a versão 1.12.0, que está em Alfa.
E siga este link curto, que vai levar você à nossa documentação para desenvolvedores sobre o Media3.
Muito obrigado e aproveite o restante do ADS.
@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()