Personalizzazione

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'istanza MediaSource viene creata da un elemento MediaItem da un elemento MediaSource.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 un MediaItem in un MediaSource. Il parametro MediaSource.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 da MediaSource da utilizzare per ogni Renderer disponibile. Viene inserito un elemento TrackSelector quando viene creato il player.
  • Un LoadControl che controlla quando MediaSource esegue il buffering di più contenuti multimediali e la quantità di contenuti multimediali inclusi nel buffer. Viene inserito un valore LoadControl 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 elemento LivePlaybackSpeedControl 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 e ForwardingPlayer.setPlayWhenReady, poiché un chiamante si aspetta che il comportamento di questi metodi sia identico quando playWhenReady = true.
  • Se vuoi modificare l'incremento di richiesta in avanti, devi sostituire sia ForwardingPlayer.seekForward per eseguire una ricerca con il tuo incremento personalizzato sia ForwardingPlayer.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 valori Player.getAvailableCommands() e Player.isCommandAvailable() e ascoltare il callback Player.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 elemento Renderer personalizzato per gestire un tipo di media non supportato dalle implementazioni predefinite fornite dalla libreria.
  • TrackSelector: l'implementazione di un TrackSelector personalizzato consente a uno sviluppatore di app di cambiare il modo in cui i canali esposti da un MediaSource vengono selezionati per il consumo da parte di ciascuno dei Renderer disponibili.
  • LoadControl - L'implementazione di un elemento LoadControl 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 classe Extractor personalizzata.
  • MediaSource - L'implementazione di una classe MediaSource 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 composizione MediaSource personalizzato.
  • MediaSource.Factory: l'implementazione di un elemento MediaSource.Factory personalizzato consente a un'applicazione di personalizzare la modalità di creazione di un MediaSource da un elemento MediaItem.
  • DataSource - Il pacchetto upstream di ExoPlayer contiene già una serie di implementazioni DataSource per diversi casi d'uso. Potresti voler implementare la tua classe DataSource 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 un Handler 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 metodo handleMessage. Il codice dell'applicazione deve passare le modifiche alla configurazione chiamando il metodo createMessage di ExoPlayer, configurando il messaggio e inviandolo al componente utilizzando PlayerMessage.send. L'invio di messaggi da consegnare nel thread di riproduzione garantisce che questi vengano eseguiti in ordine di altra operazione sul player.