O ExoPlayer oferece suporte a HLS com vários formatos de contêiner. Os formatos de amostra de áudio e vídeo contidos também precisam ser aceitos (consulte a seção de formatos de amostra para mais detalhes). Incentivamos os produtores de conteúdo HLS a gerar transmissões HLS de alta qualidade, conforme descrito neste post do blog.
| Recurso | Compatível | Comentários |
|---|---|---|
| Contêineres | ||
| MPEG-TS | SIM | |
| FMP4/CMAF | SIM | |
| ADTS (AAC) | SIM | |
| MP3 | SIM | |
| Legendas ocultas / subtítulos | ||
| CEA-608 | SIM | |
| CEA-708 | SIM | |
| WebVTT | SIM | |
| Metadados | ||
| ID3 | SIM | |
| SCTE-35 | NÃO | |
| Proteção de conteúdo | ||
| AES-128 | SIM | |
| Amostra AES-128 | NÃO | |
| Widevine | SIM | API 19 ou mais recente (esquema "cenc") e 25 ou mais recente (esquema "cbcs") |
| PlayReady SL2000 | SIM | Somente no Android TV |
| Controle do servidor | ||
| Atualizações delta | SIM | |
| Bloqueio da recarga da playlist | SIM | |
| Bloqueio do carregamento de dicas de pré-carregamento | SIM | Exceto para intervalos de bytes com comprimentos indefinidos |
| Inserção de anúncios | ||
| Inserção de anúncios guiada pelo servidor (intersticiais) | Parcialmente | Somente VOD com X-ASSET-URI.
Transmissões ao vivo e X-ASSET-LIST serão adicionadas mais tarde. |
| Anúncios do IMA do lado do servidor e do lado do cliente | SIM | Guia de inserção de anúncios |
| Reprodução ao vivo | ||
| Reprodução ao vivo normal | SIM | |
| HLS de baixa latência (Apple) | SIM | |
| HLS de baixa latência (comunidade) | NÃO | |
| Dados comuns do cliente de mídia CMCD | SIM | Guia de integração do CMCD |
Como usar MediaItem
Para reproduzir uma transmissão HLS, é necessário depender do módulo HLS.
Kotlin
implementation("androidx.media3:media3-exoplayer-hls:1.10.0")
Groovy
implementation "androidx.media3:media3-exoplayer-hls:1.10.0"
Em seguida, crie um MediaItem para um URI de playlist HLS e transmita-o ao player.
Kotlin
// Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the media item to be played. player.setMediaItem(MediaItem.fromUri(hlsUri)) // Prepare the player. player.prepare()
Java
// Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the media item to be played. player.setMediaItem(MediaItem.fromUri(hlsUri)); // Prepare the player. player.prepare();
Se o URI não terminar com .m3u8, transmita MimeTypes.APPLICATION_M3U8 para setMimeType de MediaItem.Builder para indicar explicitamente o tipo de conteúdo.
O URI do item de mídia pode apontar para uma playlist de mídia ou uma playlist multivariante. Se o URI apontar para uma playlist multivariante que declara várias tags #EXT-X-STREAM-INF, o ExoPlayer vai se adaptar automaticamente entre as variantes, considerando a largura de banda disponível e os recursos do dispositivo.
Como usar HlsMediaSource
Para mais opções de personalização, crie um HlsMediaSource e transmita-o diretamente ao player em vez de um MediaItem.
Kotlin
// Create a data source factory. val dataSourceFactory: DataSource.Factory = DefaultHttpDataSource.Factory() // Create a HLS media source pointing to a playlist uri. val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri)) // Create a player instance. val player = ExoPlayer.Builder(context).build() // Set the HLS media source as the playlist with a single media item. player.setMediaSource(hlsMediaSource) // Prepare the player. player.prepare()
Java
// Create a data source factory. DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory(); // Create a HLS media source pointing to a playlist uri. HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(hlsUri)); // Create a player instance. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Set the HLS media source as the playlist with a single media item. player.setMediaSource(hlsMediaSource); // Prepare the player. player.prepare();
Como acessar o manifesto
Você pode recuperar o manifesto atual chamando Player.getCurrentManifest.
Para HLS, converta o objeto retornado para HlsManifest. O callback onTimelineChanged de Player.Listener também é chamado sempre que o manifesto é carregado. Isso acontece uma vez para conteúdo on demand e possivelmente várias vezes para conteúdo ao vivo. O snippet de código a seguir mostra como um app pode fazer algo sempre que o manifesto é carregado.
Kotlin
player.addListener( object : Player.Listener { override fun onTimelineChanged( timeline: Timeline, @Player.TimelineChangeReason reason: Int, ) { val manifest = player.currentManifest if (manifest is HlsManifest) { // Do something with the manifest. } } } )
Java
player.addListener( new Player.Listener() { @Override public void onTimelineChanged( Timeline timeline, @Player.TimelineChangeReason int reason) { Object manifest = player.getCurrentManifest(); if (manifest != null) { HlsManifest hlsManifest = (HlsManifest) manifest; // Do something with the manifest. } } });
Reproduzir transmissões HLS com intersticiais
A especificação HLS define intersticiais HLS que podem ser usados para incluir informações intersticiais em uma playlist de mídia. O ExoPlayer ignora esses intersticiais por padrão. O suporte pode ser adicionado usando HlsInterstitialsAdsLoader. Não oferecemos suporte a todos os recursos da especificação desde o início. Se você não tiver suporte para
sua transmissão, registre um problema no GitHub e envie o
URI da transmissão para que possamos adicionar suporte a ela.
Usar um MediaItem com a API Playlist
A maneira mais conveniente de reproduzir transmissões HLS com intersticiais é criar uma instância do ExoPlayer com uma HlsInterstitialsAdsLoader.AdsMediaSourceFactory.
Isso permite usar a API MediaItem baseada em playlist da interface Player
para reproduzir intersticiais HLS.
O MediaSource.Factory do ExoPlayer pode ser injetado no builder ao criar a instância do player:
Kotlin
val hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context) // Create a MediaSource.Factory for HLS streams with interstitials. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, DefaultMediaSourceFactory(context), ) // Build player with interstitials media source factory val player = ExoPlayer.Builder(context).setMediaSourceFactory(hlsMediaSourceFactory).build() // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player) playerView.setPlayer(player)
Java
HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context); // Create a MediaSource.Factory for HLS streams with interstitials. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, new DefaultMediaSourceFactory(context)); // Build player with interstitials media source factory ExoPlayer player = new ExoPlayer.Builder(context).setMediaSourceFactory(hlsMediaSourceFactory).build(); // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player); playerView.setPlayer(player);
Com essa configuração do player, a reprodução de intersticiais HLS é apenas sobre a configuração de um item de mídia com uma AdsConfiguration no player:
Kotlin
// Build an HLS media item with ads configuration to be played. player.setMediaItem( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") // must be unique within playlist .build() ) .build() ) player.prepare() player.play()
Java
// Build an HLS media item with ads configuration to be played. player.setMediaItem( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( new AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") // must be unique within playlist .build()) .build()); player.prepare(); player.play();
Usar a API baseada na origem de mídia
Como alternativa, a instância do ExoPlayer pode ser criada sem substituir a fábrica de origem de mídia padrão. Para oferecer suporte a intersticiais, um app pode então
usar HlsInterstitialsAdsLoader.AdsMediaSourceFactory diretamente para criar um
MediaSource e fornecê-lo ao ExoPlayer usando a API Playlist baseada na origem de mídia:
Kotlin
val hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(context) // Create a MediaSource.Factory for HLS streams with interstitials. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, context, ) // Build player with default media source factory. val player = ExoPlayer.Builder(context).build() // Create an media source from an HLS media item with ads configuration. val mediaSource = hlsMediaSourceFactory.createMediaSource( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") .build() ) .build() ) // Set the media source on the player. player.setMediaSource(mediaSource) player.prepare() player.play()
Java
HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(context); // Create a MediaSource.Factory for HLS streams with interstitials. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( hlsInterstitialsAdsLoader, playerView, context); // Build player with default media source factory. ExoPlayer player = new ExoPlayer.Builder(context).build(); // Create an media source from an HLS media item with ads configuration. MediaSource mediaSource = hlsMediaSourceFactory.createMediaSource( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") .build()) .build()); // Set the media source on the player. player.setMediaSource(mediaSource); player.prepare(); player.play();
Detectar eventos de anúncios
Um Listener pode ser adicionado ao HlsInterstitialsAdsLoader para monitorar eventos relacionados a mudanças de status na reprodução de intersticiais HLS. Isso permite que um app ou SDK rastreie anúncios reproduzidos, listas de recursos carregadas, origens de mídia de anúncios sendo preparadas ou detecte erros de carregamento de listas de recursos e preparação de anúncios. Além disso, os metadados emitidos por origens de mídia de anúncios podem ser recebidos para verificação detalhada da reprodução de anúncios ou para acompanhar o progresso da reprodução de anúncios.
Kotlin
class AdsLoaderListener : HlsInterstitialsAdsLoader.Listener { override fun onStart(mediaItem: MediaItem, adsId: Any, adViewProvider: AdViewProvider) { // Do something when HLS media item with interstitials is started. } override fun onMetadata( mediaItem: MediaItem, adsId: Any, adGroupIndex: Int, adIndexInAdGroup: Int, metadata: Metadata, ) { // Do something with metadata that is emitted by the ad media source of the given ad. } override fun onAdCompleted( mediaItem: MediaItem, adsId: Any, adGroupIndex: Int, adIndexInAdGroup: Int, ) { // Do something when ad completed playback. } // ... See JavaDoc for further callbacks of HlsInterstitialsAdsLoader.Listener. override fun onStop(mediaItem: MediaItem, adsId: Any, adPlaybackState: AdPlaybackState) { // Do something with the resulting ad playback state when stopped. } }
Java
@OptIn(markerClass = UnstableApi.class) private static class AdsLoaderListener implements HlsInterstitialsAdsLoader.Listener { // implement HlsInterstitialsAdsLoader.Listener @Override public void onStart(MediaItem mediaItem, Object adsId, AdViewProvider adViewProvider) { // Do something when HLS media item with interstitials is started. } @Override public void onMetadata( MediaItem mediaItem, Object adsId, int adGroupIndex, int adIndexInAdGroup, Metadata metadata) { // Do something with metadata that is emitted by the ad media source of the given ad. } @Override public void onAdCompleted( MediaItem mediaItem, Object adsId, int adGroupIndex, int adIndexInAdGroup) { // Do something when ad completed playback. } // ... See JavaDoc for further callbacks @Override public void onStop(MediaItem mediaItem, Object adsId, AdPlaybackState adPlaybackState) { // Do something with the resulting ad playback state when stopped. } }
Consulte o JavaDoc de HlsInterstitialsAdsLoader.Listener para conferir a documentação detalhada
de todos os callbacks disponíveis.
O listener pode ser adicionado ao carregador de anúncios:
Kotlin
val listener = AdsLoaderListener() // Add the listener to the ads loader to receive ad loader events. hlsInterstitialsAdsLoader.addListener(listener)
Java
AdsLoaderListener listener = new AdsLoaderListener(); // Add the listener to the ads loader to receive ad loader events. hlsInterstitialsAdsLoader.addListener(listener);
Ciclo de vida do HlsInterstitialsAdsLoader
Uma instância de HlsInterstitialsAdsLoader ou
HlsInterstitialsAdsLoader.AdsMediaSourceFactory pode ser reutilizada para várias
instâncias de player que criam várias origens de mídia para as quais os anúncios precisam ser
carregados.
Uma instância pode ser criada, por exemplo, no método onCreate de uma Activity e reutilizada para várias instâncias de player. Isso funciona desde que esteja em uso por uma única instância de um jogador ao mesmo tempo. Isso é útil para o caso de uso comum quando o app é colocado em segundo plano, a instância do player é destruída e uma nova instância é criada quando o app é colocado em primeiro plano novamente.
Retomada da reprodução com um estado de reprodução de anúncios
Para evitar que os usuários precisem assistir anúncios novamente, o estado de reprodução de anúncios pode ser salvo e restaurado quando o player é recriado. Isso é feito chamando
getAdsResumptionStates() quando o player está prestes a ser lançado e armazenando
os objetos AdsResumptionState retornados. Quando o player é recriado, o estado pode ser restaurado chamando addAdResumptionState() na instância do carregador de anúncios. AdsResumptionState pode ser agrupado, então ele pode ser armazenado em um pacote onSaveInstanceState de uma Activity. A retomada de anúncios só é aceita para transmissões VOD.
Kotlin
class HlsInterstitialsActivity : Activity() { companion object { const val ADS_RESUMPTION_STATE_KEY = "ads_resumption_state" } private var hlsInterstitialsAdsLoader: HlsInterstitialsAdsLoader? = null private var playerView: PlayerView? = null private var player: ExoPlayer? = null private var adsResumptionStates: List<HlsInterstitialsAdsLoader.AdsResumptionState>? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Create the ads loader instance. hlsInterstitialsAdsLoader = HlsInterstitialsAdsLoader(this) // Restore ad resumption states. savedInstanceState?.getParcelableArrayList<Bundle>(ADS_RESUMPTION_STATE_KEY)?.let { bundles -> adsResumptionStates = bundles.map { HlsInterstitialsAdsLoader.AdsResumptionState.fromBundle(it) } } } override fun onStart() { super.onStart() // Build a player and set it on the ads loader. initializePlayer() hlsInterstitialsAdsLoader?.setPlayer(player) // Add any stored ad resumption states to the ads loader. adsResumptionStates?.forEach { hlsInterstitialsAdsLoader?.addAdResumptionState(it) } adsResumptionStates = null // Consume the states } override fun onStop() { super.onStop() // Get ad resumption states. adsResumptionStates = hlsInterstitialsAdsLoader?.adsResumptionStates releasePlayer() } override fun onDestroy() { // Release the ads loader when not used anymore. hlsInterstitialsAdsLoader?.release() hlsInterstitialsAdsLoader = null super.onDestroy() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // Store the ad resumption states. adsResumptionStates?.let { outState.putParcelableArrayList( ADS_RESUMPTION_STATE_KEY, ArrayList(it.map(HlsInterstitialsAdsLoader.AdsResumptionState::toBundle)), ) } } fun initializePlayer() { if (player == null) { // Create a media source factory for HLS streams. val hlsMediaSourceFactory = HlsInterstitialsAdsLoader.AdsMediaSourceFactory( checkNotNull(hlsInterstitialsAdsLoader), playerView!!, /* context= */ this, ) // Build player with interstitials media source player = ExoPlayer.Builder(/* context= */ this) .setMediaSourceFactory(hlsMediaSourceFactory) .build() // Set the player on the ads loader. hlsInterstitialsAdsLoader?.setPlayer(player) playerView?.player = player } // Use a media item with an HLS stream URI, an ad tag URI and ads ID. player?.setMediaItem( MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setMimeType(MimeTypes.APPLICATION_M3U8) .setAdsConfiguration( MediaItem.AdsConfiguration.Builder("hls://interstitials".toUri()) .setAdsId("ad-tag-0") // must be unique within ExoPlayer playlist .build() ) .build() ) player?.prepare() player?.play() } fun releasePlayer() { player?.release() player = null hlsInterstitialsAdsLoader?.setPlayer(null) playerView?.player = null } }
Java
@OptIn(markerClass = UnstableApi.class) public static class HlsInterstitialsActivity extends Activity { public static final String ADS_RESUMPTION_STATE_KEY = "ads_resumption_state"; @Nullable private HlsInterstitialsAdsLoader hlsInterstitialsAdsLoader; @Nullable private PlayerView playerView; @Nullable private ExoPlayer player; private List<HlsInterstitialsAdsLoader.AdsResumptionState> adsResumptionStates; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create the ads loader instance. hlsInterstitialsAdsLoader = new HlsInterstitialsAdsLoader(this); // Restore ad resumption states. if (savedInstanceState != null) { ArrayList<Bundle> bundles = savedInstanceState.getParcelableArrayList(ADS_RESUMPTION_STATE_KEY); if (bundles != null) { adsResumptionStates = new ArrayList<>(); for (Bundle bundle : bundles) { adsResumptionStates.add( HlsInterstitialsAdsLoader.AdsResumptionState.fromBundle(bundle)); } } } } @Override protected void onStart() { super.onStart(); // Build a player and set it on the ads loader. initializePlayer(); // Add any stored ad resumption states to the ads loader. if (adsResumptionStates != null) { for (HlsInterstitialsAdsLoader.AdsResumptionState state : adsResumptionStates) { hlsInterstitialsAdsLoader.addAdResumptionState(state); } adsResumptionStates = null; // Consume the states } } @Override protected void onStop() { super.onStop(); // Get ad resumption states before releasing the player. if (hlsInterstitialsAdsLoader != null) { adsResumptionStates = hlsInterstitialsAdsLoader.getAdsResumptionStates(); } releasePlayer(); } @Override protected void onDestroy() { // Release the ads loader when not used anymore. if (hlsInterstitialsAdsLoader != null) { hlsInterstitialsAdsLoader.release(); hlsInterstitialsAdsLoader = null; } super.onDestroy(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Store the ad resumption states. if (adsResumptionStates != null) { ArrayList<Bundle> bundles = new ArrayList<>(); for (HlsInterstitialsAdsLoader.AdsResumptionState state : adsResumptionStates) { bundles.add(state.toBundle()); } outState.putParcelableArrayList(ADS_RESUMPTION_STATE_KEY, bundles); } } private void initializePlayer() { if (player == null) { // Create a media source factory for HLS streams. MediaSource.Factory hlsMediaSourceFactory = new HlsInterstitialsAdsLoader.AdsMediaSourceFactory( checkNotNull(hlsInterstitialsAdsLoader), playerView, /* context= */ this); // Build player with interstitials media source player = new ExoPlayer.Builder(/* context= */ this) .setMediaSourceFactory(hlsMediaSourceFactory) .build(); // Set the player on the ads loader. hlsInterstitialsAdsLoader.setPlayer(player); playerView.setPlayer(player); } // Use a media item with an HLS stream URI, an ad tag URI and ads ID. player.setMediaItem( new MediaItem.Builder() .setUri("https://www.example.com/media.m3u8") .setMimeType(MimeTypes.APPLICATION_M3U8) .setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse("hls://interstitials")) .setAdsId("ad-tag-0") // must be unique within ExoPlayer playlist .build()) .build()); player.prepare(); player.play(); } private void releasePlayer() { if (player != null) { player.release(); player = null; } if (hlsInterstitialsAdsLoader != null) { hlsInterstitialsAdsLoader.setPlayer(null); } if (playerView != null) { playerView.setPlayer(null); } } }
Como personalizar a reprodução
O ExoPlayer oferece várias maneiras de personalizar a experiência de reprodução de acordo com as necessidades do seu app. Consulte a página de personalização para conferir exemplos.
Como desativar a preparação sem blocos
Por padrão, o ExoPlayer usa a preparação sem blocos. Isso significa que o ExoPlayer só vai usar as informações na playlist multivariante para preparar a transmissão, o que funciona se as tags #EXT-X-STREAM-INF contiverem o atributo CODECS.
Talvez seja necessário desativar esse recurso se os segmentos de mídia contiverem faixas de legendas ocultas muxadas que não são declaradas na playlist multivariante com uma tag #EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS. Caso contrário, essas faixas de legendas ocultas não serão detectadas e reproduzidas. Você pode desativar a preparação sem blocos na HlsMediaSource.Factory, conforme mostrado no snippet a seguir. Isso vai aumentar o tempo de inicialização, já que o ExoPlayer precisa fazer o download de um segmento de mídia para descobrir essas faixas adicionais. É preferível declarar as faixas de legendas ocultas na playlist multivariante.
Kotlin
val hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(false) .createMediaSource(MediaItem.fromUri(hlsUri))
Java
HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(false) .createMediaSource(MediaItem.fromUri(hlsUri));
Como criar conteúdo HLS de alta qualidade
Para aproveitar ao máximo o ExoPlayer, siga algumas diretrizes para melhorar seu conteúdo HLS. Leia nosso post do Medium sobre a reprodução de HLS no ExoPlayer para conferir uma explicação completa. Os principais pontos são:
- Use durações de segmento precisas.
- Use uma transmissão de mídia contínua. Evite mudanças na estrutura de mídia em segmentos.
- Use a tag
#EXT-X-INDEPENDENT-SEGMENTS. - Prefira transmissões demuxadas, em vez de arquivos que incluem vídeo e áudio.
- Inclua todas as informações possíveis na playlist multivariante.
As diretrizes a seguir se aplicam especificamente a transmissões ao vivo:
- Use a tag
#EXT-X-PROGRAM-DATE-TIME. - Use a tag
#EXT-X-DISCONTINUITY-SEQUENCE. - Forneça uma janela ao vivo longa. Um minuto ou mais é ótimo.