Al centro della libreria ExoPlayer c'è l'interfaccia Player
. Un elemento Player
espone le funzionalità tradizionali del media player di alto livello, ad esempio la capacità di eseguire il buffer dei contenuti multimediali, riprodurre, mettere in pausa e cercare. L'implementazione predefinita ExoPlayer
è progettata per fare poche ipotesi (e di conseguenza imporre poche restrizioni) sul tipo di contenuti multimediali riprodotti, su come e dove vengono archiviati e su come vengono visualizzati. Anziché implementare direttamente il caricamento e il rendering dei contenuti multimediali, le implementazioni di ExoPlayer
delegano questo lavoro ai componenti che vengono inseriti al momento della creazione di un player o quando vengono passate nuove origini multimediali al player.
I componenti comuni a tutte le implementazioni ExoPlayer
sono:
- Istanze
MediaSource
che definiscono i contenuti multimediali da riprodurre, caricano e da cui è possibile leggere i contenuti multimediali caricati. Un'istanzaMediaSource
viene creata da un elementoMediaItem
da un elementoMediaSource.Factory
all'interno del player. Inoltre, possono essere trasmessi direttamente al player utilizzando l'API Media Source basata su playlist. - Un'istanza
MediaSource.Factory
che converte unMediaItem
in unMediaSource
. Il parametroMediaSource.Factory
viene inserito al momento della creazione del player. Renderer
istanze che eseguono il rendering dei singoli componenti dell'elemento multimediale. che vengono inseriti al momento della creazione del player.- Un
TrackSelector
che seleziona i canali forniti daMediaSource
da utilizzare per ogniRenderer
disponibile. Viene inserito un elementoTrackSelector
quando viene creato il player. - Un
LoadControl
che controlla quandoMediaSource
esegue il buffering di più contenuti multimediali e la quantità di contenuti multimediali inclusi nel buffer. Viene inserito un valoreLoadControl
durante la creazione del player. - Un
LivePlaybackSpeedControl
che controlla la velocità di riproduzione durante le riproduzioni dal vivo per consentire al player di avvicinarsi a un offset dal vivo configurato. Viene inserito un elementoLivePlaybackSpeedControl
durante la creazione del player.
L'inserimento di componenti che implementano parti della funzionalità del player è presente in tutta la raccolta. Le implementazioni predefinite di alcuni componenti delegano il lavoro a componenti inseriti ulteriormente. Ciò consente di sostituire singolarmente molti sottocomponenti con implementazioni configurate in modo personalizzato.
Personalizzazione del player
Di seguito sono riportati alcuni esempi comuni di personalizzazione del player mediante l'inserimento di componenti.
Configurazione dello stack di rete
È disponibile una pagina sulla personalizzazione dello stack di rete utilizzato da ExoPlayer.
Memorizzazione nella cache dei dati caricati dalla rete
Consulta le guide per la memorizzazione nella cache temporanea e immediata e per il download dei contenuti multimediali.
Personalizzazione delle interazioni con il server
Alcune app potrebbero voler intercettare le richieste e le risposte HTTP. Potresti voler inserire intestazioni di richiesta personalizzate, leggere le intestazioni delle risposte del server, modificare gli URI delle richieste e così via. Ad esempio, la tua app potrebbe autenticarsi inserendo un token come intestazione quando richiedi i segmenti multimediali.
L'esempio seguente mostra come implementare questi comportamenti inserendo un valore DataSource.Factory
personalizzato in DefaultMediaSourceFactory
:
Kotlin
val dataSourceFactory = DataSource.Factory { val dataSource = httpDataSourceFactory.createDataSource() // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value") dataSource } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory) ) .build()
Java
DataSource.Factory dataSourceFactory = () -> { HttpDataSource dataSource = httpDataSourceFactory.createDataSource(); // Set a custom authentication request header. dataSource.setRequestProperty("Header", "Value"); return dataSource; }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)) .build();
Nello snippet di codice riportato sopra, l'elemento HttpDataSource
inserito include l'intestazione
"Header: Value"
in ogni richiesta HTTP. Questo comportamento è fisso per ogni interazione con un'origine HTTP.
Per un approccio più granulare, puoi inserire un comportamento just-in-time utilizzando un
ResolvingDataSource
. Il seguente snippet di codice mostra come inserire le intestazioni delle richieste immediatamente prima di interagire con un'origine HTTP:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time request headers. dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time request headers. dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));
Puoi utilizzare un elemento ResolvingDataSource
anche per eseguire modifiche just-in-time dell'URI, come illustrato nel seguente snippet:
Kotlin
val dataSourceFactory: DataSource.Factory = ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec -> // Provide just-in-time URI resolution logic. dataSpec.withUri(resolveUri(dataSpec.uri)) }
Java
DataSource.Factory dataSourceFactory = new ResolvingDataSource.Factory( httpDataSourceFactory, // Provide just-in-time URI resolution logic. dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));
Personalizzazione della gestione degli errori
L'implementazione di una LoadErrorHandlingPolicy
personalizzata consente alle app di personalizzare il modo in cui ExoPlayer reagisce agli errori di caricamento. Ad esempio, un'app potrebbe voler ottenere un errore rapido anziché riprovare molte volte oppure personalizzare la logica di backoff che controlla il tempo di attesa del player tra un nuovo tentativo e l'altro. Il seguente snippet mostra come implementare una logica di backoff personalizzata:
Kotlin
val loadErrorHandlingPolicy: LoadErrorHandlingPolicy = object : DefaultLoadErrorHandlingPolicy() { override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long { // Implement custom back-off logic here. return 0 } } val player = ExoPlayer.Builder(context) .setMediaSourceFactory( DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy) ) .build()
Java
LoadErrorHandlingPolicy loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy() { @Override public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { // Implement custom back-off logic here. return 0; } }; ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context) .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)) .build();
L'argomento LoadErrorInfo
contiene ulteriori informazioni sul caricamento non riuscito per personalizzare la logica in base al tipo di errore o alla richiesta non riuscita.
Personalizzazione dei flag degli estrattori
I flag degli estrattori possono essere usati per personalizzare il modo in cui i singoli formati vengono estratti dai media progressivi. Possono essere impostati sul DefaultExtractorsFactory
fornito a DefaultMediaSourceFactory
. L'esempio seguente passa un flag che abilita la ricerca basata su indice per i flussi MP3.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING) val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory)) .build()
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING); ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory)) .build();
Attivazione della ricerca a velocità in bit costante
Per gli stream MP3, ADTS e AMR, puoi attivare la ricerca approssimativa utilizzando
un'ipotesi a velocità in bit costante con flag FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
.
Questi flag possono essere impostati per singoli estrattori utilizzando i singoli
metodi DefaultExtractorsFactory.setXyzExtractorFlags
come descritto sopra. Per
abilitare la ricerca di una velocità in bit costante per tutti gli estrattori che la supportano, utilizza
DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
Il parametro ExtractorsFactory
può quindi essere inserito tramite DefaultMediaSourceFactory
come
descritto per personalizzare i flag degli estrattori in alto.
Abilitazione dell'accodamento del buffer asincrono
L'accodamento del buffer asincrono è un miglioramento della pipeline di rendering di ExoPlayer, che gestisce le istanze MediaCodec
in modalità asincrona e utilizza thread aggiuntivi per pianificare la decodifica e il rendering dei dati. Abilitarla può ridurre i frame eliminati e le sottocute dell'audio.
L'accodamento del buffer asincrono è abilitato per impostazione predefinita sui dispositivi con Android 12 (livello API 31) e versioni successive e può essere abilitato manualmente a partire da Android 6.0 (livello API 23). Valuta la possibilità di attivare la funzionalità per dispositivi specifici su cui noti frame interrotti o sottotipi audio, in particolare durante la riproduzione di contenuti protetti da DRM o con frequenza fotogrammi elevata.
Nel caso più semplice, devi inserire un valore DefaultRenderersFactory
nel player nel seguente modo:
Kotlin
val renderersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()
Java
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing(); ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
Se stai creando direttamente un'istanza dei renderer, passa un
AsynchronousMediaCodecAdapter.Factory
ai costruttori MediaCodecVideoRenderer
e
MediaCodecAudioRenderer
.
Chiamate ai metodi di intercettazione con ForwardingPlayer
Puoi personalizzare parte del comportamento di un'istanza Player
inserendola in una sottoclasse ForwardingPlayer
e sostituendo i metodi per eseguire una delle seguenti operazioni:
- Accedi ai parametri prima di passarli al delegato
Player
. - Accedi al valore restituito dal delegato
Player
prima di restituirlo. - Implementa nuovamente il metodo completamente.
Quando esegui l'override dei metodi ForwardingPlayer
, è importante garantire che l'implementazione rimanga autocoerente e conforme con l'interfaccia Player
, soprattutto quando si tratta di metodi che hanno un comportamento identico o correlato. Ecco alcuni esempi:
- Se vuoi eseguire l'override di ogni operazione di "riproduzione", devi sostituire entrambi i metodi
ForwardingPlayer.play
eForwardingPlayer.setPlayWhenReady
, poiché un chiamante si aspetta che il comportamento di questi metodi sia identico quandoplayWhenReady = true
. - Se vuoi modificare l'incremento di richiesta in avanti, devi sostituire sia
ForwardingPlayer.seekForward
per eseguire una ricerca con il tuo incremento personalizzato siaForwardingPlayer.getSeekForwardIncrement
per segnalare al chiamante l'incremento personalizzato corretto. - Se vuoi controllare quali
Player.Commands
vengono pubblicizzati da un'istanza del player, devi sostituire entrambi i valoriPlayer.getAvailableCommands()
ePlayer.isCommandAvailable()
e ascoltare il callbackPlayer.Listener.onAvailableCommandsChanged()
per ricevere una notifica in caso di modifiche apportate dal player sottostante.
Personalizzazione di MediaSource
Gli esempi precedenti contengono componenti personalizzati da utilizzare durante la riproduzione di tutti gli oggetti MediaItem
che vengono passati al player. Se è necessaria una personalizzazione granulare, è anche possibile inserire componenti personalizzati in singole istanze MediaSource
, che possono essere trasmesse direttamente al player. L'esempio riportato di seguito mostra come personalizzare un ProgressiveMediaSource
per utilizzare DataSource.Factory
, ExtractorsFactory
e LoadErrorHandlingPolicy
personalizzati:
Kotlin
val mediaSource = ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri))
Java
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory) .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy) .createMediaSource(MediaItem.fromUri(streamUri));
Creazione di componenti personalizzati
La libreria fornisce implementazioni predefinite dei componenti elencati nella parte superiore di questa pagina per casi d'uso comuni. Un ExoPlayer
può utilizzare questi componenti, ma
può anche essere creato per utilizzare implementazioni personalizzate se sono
obbligatori comportamenti non standard. Ecco alcuni casi d'uso per le implementazioni personalizzate:
Renderer
- Potresti voler implementare un elementoRenderer
personalizzato per gestire un tipo di media non supportato dalle implementazioni predefinite fornite dalla libreria.TrackSelector
: l'implementazione di unTrackSelector
personalizzato consente a uno sviluppatore di app di cambiare il modo in cui i canali esposti da unMediaSource
vengono selezionati per il consumo da parte di ciascuno deiRenderer
disponibili.LoadControl
- L'implementazione di un elementoLoadControl
personalizzato consente a uno sviluppatore di app di modificare la norma di buffering del player.Extractor
- Se devi supportare un formato di container non ancora supportato dalla libreria, valuta la possibilità di implementare una classeExtractor
personalizzata.MediaSource
- L'implementazione di una classeMediaSource
personalizzata può essere appropriata se vuoi ottenere esempi di contenuti multimediali da inviare ai renderer in modo personalizzato o se vuoi implementare un comportamento di composizioneMediaSource
personalizzato.MediaSource.Factory
: l'implementazione di un elementoMediaSource.Factory
personalizzato consente a un'applicazione di personalizzare la modalità di creazione di unMediaSource
da un elementoMediaItem
.DataSource
- Il pacchetto upstream di ExoPlayer contiene già una serie di implementazioniDataSource
per diversi casi d'uso. Potresti voler implementare la tua classeDataSource
per caricare i dati in un altro modo, ad esempio tramite un protocollo personalizzato, uno stack HTTP personalizzato o una cache permanente personalizzata.
Per la creazione di componenti personalizzati, consigliamo quanto segue:
- Se un componente personalizzato deve segnalare gli eventi all'app, ti consigliamo di farlo utilizzando lo stesso modello dei componenti ExoPlayer esistenti, ad esempio utilizzando le classi
EventDispatcher
o passando unHandler
insieme a un listener al costruttore del componente. - Consigliamo ai componenti personalizzati di utilizzare lo stesso modello dei componenti ExoPlayer esistenti per consentire la riconfigurazione da parte dell'app durante la riproduzione. A questo scopo, i componenti personalizzati devono implementare
PlayerMessage.Target
e ricevere le modifiche di configurazione nel metodohandleMessage
. Il codice dell'applicazione deve passare le modifiche alla configurazione chiamando il metodocreateMessage
di ExoPlayer, configurando il messaggio e inviandolo al componente utilizzandoPlayerMessage.send
. L'invio di messaggi da consegnare nel thread di riproduzione garantisce che questi vengano eseguiti in ordine di altra operazione sul player.