Al centro della libreria ExoPlayer c'è l'interfaccia Player
. Un Player
espone funzionalità tradizionali di lettore multimediale di alto livello, come la possibilità di
memorizzare nella cache i contenuti multimediali, riprodurli, metterli in pausa e cercarli. L'implementazione predefinita ExoPlayer
è
progettata per fare poche ipotesi (e quindi imporre poche limitazioni) sul tipo di contenuti multimediali riprodotti, su come e dove vengono archiviati e su come vengono
renderizzati. Anziché implementare direttamente il caricamento e il rendering dei contenuti multimediali,
le implementazioni di ExoPlayer
delegano questo lavoro a componenti inseriti
quando viene creato un player o quando vengono passate nuove origini multimediali al player.
I componenti comuni a tutte le implementazioni di ExoPlayer
sono:
- Istanze
MediaSource
che definiscono i contenuti multimediali da riprodurre, caricano i contenuti multimediali e da cui possono essere letti i contenuti multimediali caricati. Un'istanzaMediaSource
viene creata da unMediaItem
da unMediaSource.Factory
all'interno del player. Possono anche essere passati direttamente al player utilizzando l'API per le playlist basate su origini media. - Un'istanza
MediaSource.Factory
che converte unMediaItem
in unMediaSource
. IlMediaSource.Factory
viene inserito quando viene creato il player. - istanze
Renderer
che eseguono il rendering dei singoli componenti dei contenuti multimediali. Questi vengono inseriti quando viene creato il giocatore. - Un
TrackSelector
che seleziona le tracce fornite daMediaSource
da utilizzare in ogniRenderer
disponibile. UnTrackSelector
viene inserito quando viene creato il giocatore. - Un
LoadControl
che controlla quandoMediaSource
memorizza nella cache altri contenuti multimediali e la quantità di contenuti multimediali memorizzati nella cache. UnLoadControl
viene inserito quando viene creato il player. - Un
LivePlaybackSpeedControl
che controlla la velocità di riproduzione durante le riproduzioni live per consentire al player di rimanere vicino a un offset live configurato. UnLivePlaybackSpeedControl
viene inserito quando viene creato il player.
Il concetto di inserimento di componenti che implementano parti della funzionalità del player è presente in tutta la libreria. Le implementazioni predefinite di alcuni componenti delegano il lavoro ad altri componenti inseriti. Ciò consente di sostituire individualmente molti sottocomponenti con implementazioni configurate in modo personalizzato.
Personalizzazione del giocatore
Di seguito sono descritti alcuni esempi comuni di personalizzazione del player tramite l'inserimento di componenti.
Configurazione dello stack di rete
Abbiamo 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 temporanea nella cache al volo e il download dei contenuti multimediali.
Personalizzazione delle interazioni con il server
Alcune app potrebbero voler intercettare richieste e risposte HTTP. Potresti voler inserire intestazioni di richiesta personalizzate, leggere le intestazioni di risposta del server, modificare gli URI delle richieste e così via. Ad esempio, la tua app potrebbe autenticarsi inserendo un token come intestazione quando richiede i segmenti multimediali.
L'esempio seguente mostra come implementare questi comportamenti inserendo un 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, il 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 appena 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 anche utilizzare un ResolvingDataSource
per eseguire
modifiche just-in-time dell'URI, come mostrato 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 un LoadErrorHandlingPolicy
personalizzato consente alle app di personalizzare il modo in cui ExoPlayer reagisce agli errori di caricamento. Ad esempio, un'app potrebbe voler interrompere rapidamente
il tentativo anziché riprovarlo molte volte o potrebbe voler personalizzare la logica di backoff che
controlla il tempo di attesa del player tra un 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.
Personalizzare i flag dell'estrattore
I flag di estrazione possono essere utilizzati per personalizzare la modalità di estrazione dei singoli formati
dai contenuti multimediali progressivi. Possono essere impostati sul DefaultExtractorsFactory
fornito al DefaultMediaSourceFactory
. L'esempio seguente passa un flag
che consente la ricerca basata sull'indice per gli stream 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 bitrate costante
Per i flussi MP3, ADTS e AMR, puoi attivare la ricerca approssimativa utilizzando un'ipotesi di bitrate costante con i flag FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
.
Questi flag possono essere impostati per i singoli estrattori utilizzando i singoli
metodi DefaultExtractorsFactory.setXyzExtractorFlags
descritti sopra. Per
attivare la ricerca del bitrate costante per tutti gli estrattori che lo supportano, utilizza
DefaultExtractorsFactory.setConstantBitrateSeekingEnabled
.
Kotlin
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
Java
DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
Il ExtractorsFactory
può quindi essere inserito tramite DefaultMediaSourceFactory
come
descritto sopra per la personalizzazione dei flag dell'estrattore.
Attivazione dell'accodamento asincrono dei buffer
La gestione in coda asincrona dei buffer è un miglioramento della pipeline di rendering di ExoPlayer, che opera su istanze MediaCodec
in modalità asincrona e utilizza thread aggiuntivi per pianificare la decodifica e il rendering dei dati. L'attivazione
può ridurre i frame persi e gli underrun audio.
L'accodamento asincrono dei buffer è attivato per impostazione predefinita sui dispositivi con Android 12 (livello API 31) e versioni successive e può essere attivato manualmente a partire da Android 6.0 (livello API 23). Valuta la possibilità di attivare la funzionalità per dispositivi specifici su cui noti frame persi o sottocarichi audio, in particolare durante la riproduzione di contenuti protetti da DRM o con frame rate elevato.
Nel caso più semplice, devi inserire un DefaultRenderersFactory
nel
player come segue:
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 crei istanze dei renderer direttamente, passa
new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous()
ai costruttori
MediaCodecVideoRenderer
e MediaCodecAudioRenderer
.
Personalizzazione delle operazioni con ForwardingSimpleBasePlayer
Puoi personalizzare parte del comportamento di un'istanza Player
racchiudendola in
una sottoclasse di ForwardingSimpleBasePlayer
. Questa classe ti consente di intercettare
"operazioni" specifiche, anziché dover implementare direttamente i metodi Player
. In questo modo viene garantito un comportamento coerente di, ad esempio, play()
, pause()
e setPlayWhenReady(boolean)
. Inoltre, garantisce che tutte le modifiche dello stato vengano propagate correttamente alle istanze Player.Listener
registrate. Per la maggior parte dei casi d'uso di personalizzazione, ForwardingSimpleBasePlayer
è preferibile a ForwardingPlayer
, più soggetto a errori, grazie a queste garanzie di coerenza.
Ad esempio, per aggiungere una logica personalizzata all'avvio o all'interruzione della riproduzione:
Kotlin
class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) { override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady) } }
Java
class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer { public PlayerWithCustomPlay(Player player) { super(player); } @Override protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) { // Add custom logic return super.handleSetPlayWhenReady(playWhenReady); } }
Oppure per non consentire il comando SEEK_TO_NEXT
(e assicurarsi che Player.seekToNext
sia un'operazione
che non ha effetto):
Kotlin
class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) { override fun getState(): State { val state = super.getState() return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build() ) .build() } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Java
class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer { public PlayerWithoutSeekToNext(Player player) { super(player); } @Override protected State getState() { State state = super.getState(); return state .buildUpon() .setAvailableCommands( state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()) .build(); } // We don't need to override handleSeek, because it is guaranteed not to be called for // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable. }
Personalizzazione di MediaSource
Gli esempi precedenti inseriscono componenti personalizzati da utilizzare durante la riproduzione di tutti gli oggetti MediaItem
passati al player. Se è necessaria una personalizzazione granulare, è anche possibile inserire componenti personalizzati in singole istanze di MediaSource
, che possono essere passate direttamente al player. L'esempio
di seguito mostra come personalizzare un ProgressiveMediaSource
per utilizzare un DataSource.Factory
, un ExtractorsFactory
e un 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 i casi d'uso comuni. Un ExoPlayer
può utilizzare questi componenti, ma
può anche essere creato per utilizzare implementazioni personalizzate se sono necessari comportamenti non standard. Ecco alcuni casi d'uso per le implementazioni personalizzate:
Renderer
: potresti voler implementare unRenderer
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 modificare il modo in cui le tracce esposte da unMediaSource
vengono selezionate per il consumo da parte di ciascuno deiRenderer
disponibili.LoadControl
: l'implementazione di unLoadControl
personalizzato consente a uno sviluppatore di app di modificare la policy di buffering del player.Extractor
: se devi supportare un formato contenitore non attualmente supportato dalla libreria, valuta la possibilità di implementare una classeExtractor
personalizzata.MediaSource
: l'implementazione di una classeMediaSource
personalizzata potrebbe essere appropriata se vuoi ottenere campioni multimediali da fornire ai renderer in modo personalizzato o se vuoi implementare un comportamento di composizioneMediaSource
personalizzato.MediaSource.Factory
: l'implementazione di unMediaSource.Factory
personalizzato consente a un'applicazione di personalizzare il modo in cui viene creato unMediaSource
da unMediaItem
.DataSource
: il pacchetto upstream di ExoPlayer contiene già diverse implementazioni diDataSource
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, utilizzando uno stack HTTP personalizzato o da una cache persistente personalizzata.
Quando crei componenti personalizzati, ti consigliamo di:
- Se un componente personalizzato deve segnalare 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. - Abbiamo consigliato ai componenti personalizzati di utilizzare lo stesso modello dei componenti ExoPlayer esistenti per consentire la riconfigurazione da parte dell'app durante la riproduzione. Per farlo,
i componenti personalizzati devono implementare
PlayerMessage.Target
e ricevere le modifiche alla configurazione nel metodohandleMessage
. Il codice dell'applicazione deve trasmettere le modifiche alla configurazione chiamando il metodocreateMessage
di ExoPlayer, configurando il messaggio e inviandolo al componente utilizzandoPlayerMessage.send
. L'invio di messaggi da recapitare nel thread di riproduzione garantisce che vengano eseguiti in ordine con qualsiasi altra operazione eseguita sul player.