Personalizzazione

L'interfaccia Player è il fulcro della libreria ExoPlayer. Player espone le funzionalità tradizionali di media player di alto livello, come la capacità eseguire il buffering di contenuti multimediali, riprodurre, mettere in pausa e cercare contenuti. L'implementazione predefinita ExoPlayer è progettato per fare poche ipotesi (e quindi per imporre restrizioni) il tipo di contenuti multimediali riprodotti, come e dove sono archiviati e come eseguire il rendering. Invece di implementare direttamente il caricamento e il rendering dei contenuti multimediali, ExoPlayer implementazioni delegano questo lavoro ai componenti che vengono inseriti quando viene creato un player o quando vengono trasmesse a quest'ultimo nuove fonti multimediali. I componenti comuni a tutte le implementazioni di ExoPlayer sono:

  • MediaSource di istanze che definiscono i contenuti multimediali da riprodurre, caricano i contenuti multimediali e da cui è possibile leggere i contenuti multimediali caricati. Viene creata un'istanza MediaSource da MediaItem di un MediaSource.Factory all'interno del player. Possono anche Deve essere passata direttamente al player tramite l'API per la playlist basata su origine multimediale.
  • Un'istanza MediaSource.Factory che converte un MediaItem in MediaSource. La L'elemento MediaSource.Factory viene inserito alla creazione del player.
  • Renderer di istanze che mostrano i singoli componenti del contenuto multimediale. Si tratta di viene inserito quando viene creato il player.
  • Un TrackSelector che seleziona le tracce fornite da MediaSource consumato da ogni Renderer disponibile. È stato inserito un TrackSelector quando viene creato il player.
  • Un LoadControl che controlla quando MediaSource memorizza più contenuti multimediali nel buffer, la quantità di contenuti multimediali memorizzati nel buffer. Viene inserito un LoadControl quando il player è stato creato.
  • Un LivePlaybackSpeedControl che controlla la velocità di riproduzione durante le live per consentire al player di rimanere vicino a un offset dal vivo configurato. R L'elemento LivePlaybackSpeedControl viene inserito alla creazione del player.

Concetto di inserire componenti che implementano elementi del player in tutta la libreria. Le implementazioni predefinite alcuni componenti delegano il lavoro a ulteriori componenti inseriti. Ciò consente a molti singoli sottocomponenti da sostituire singolarmente con implementazioni configurati in modo personalizzato.

Personalizzazione del player

Alcuni esempi comuni di personalizzazione del player tramite l'inserimento di componenti sono: descritti di seguito.

Configurazione dello stack di rete

È disponibile una pagina relativa alla personalizzazione dello stack di rete utilizzato da ExoPlayer.

Memorizzazione nella cache dei dati caricati dalla rete

Consulta le guide per memorizzazione nella cache temporanea e il download di 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 richieste URI 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, il campo HttpDataSource inserito include l'intestazione "Header: Value" in ogni richiesta HTTP. Questo comportamento è corretto per ogni un'interazione con un'origine HTTP.

Per un approccio più granulare, puoi inserire il comportamento just-in-time utilizzando un ResolvingDataSource. Il seguente snippet di codice mostra come inserire 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 usare un ResolvingDataSource anche per eseguire modifiche just-in-time dell'URI, come mostrato nello snippet seguente:

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 può decidere di avere un errore rapido anziché riprovare più volte, o potrebbe essere opportuno personalizzare la logica di backoff controlla l'intervallo di tempo che deve trascorrere 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 in personalizzare la logica in base al tipo di errore o alla richiesta non riuscita.

Personalizzazione dei flag degli estrattori

I flag dell'estrattore possono essere usati per personalizzare le modalità di estrazione dei singoli formati dai media progressivi. Possono essere impostate sul DefaultExtractorsFactory forniti al DefaultMediaSourceFactory. L'esempio seguente trasmette un flag che consente la ricerca basata su 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 con velocità in bit costante

Per gli stream MP3, ADTS e AMR, puoi attivare la ricerca approssimativa utilizzando un ipotesi costante della velocità in bit con FLAG_ENABLE_CONSTANT_BITRATE_SEEKING flag. Questi flag possono essere impostati per singoli estrattori usando l'impostazione DefaultExtractorsFactory.setXyzExtractorFlags come descritto sopra. A per abilitare la ricerca a velocità in bit costante per tutti gli estrattori che la supportano, usa DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

ExtractorsFactory può quindi essere inserito tramite DefaultMediaSourceFactory come descritti sopra per personalizzare i flag degli estrattori.

Attivazione dell'accodamento del buffer asincrono

L'accodamento nel buffer asincrono è un miglioramento del rendering di ExoPlayer che gestisce MediaCodec istanze in modalità asincrona e utilizza thread aggiuntivi per pianificare la decodifica e il rendering dei dati. Abilitazione può ridurre i frame interrotti e le inadeguatezze audio.

L'accodamento con buffer asincrono è attivo per impostazione predefinita sui dispositivi con Android 12 (livello API 31) e versioni successive, e possono essere attivate manualmente a partire da Android 6.0 (livello API 23). Potresti attivare la funzionalità per i dispositivi specifici su cui riscontri un calo fotogrammi o l'audio insufficiente, in particolare durante la riproduzione con protezione DRM o con una frequenza fotogrammi contenuti.

Nel caso più semplice, devi inserire un'istruzione DefaultRenderersFactory nella 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 stai creando un'istanza direttamente per renderer, passa un'istruzione AsynchronousMediaCodecAdapter.Factory per MediaCodecVideoRenderer e MediaCodecAudioRenderer costruttori.

Intercettazione delle chiamate al metodo con ForwardingPlayer

Puoi personalizzare parte del comportamento di un'istanza Player eseguendone il wrapping in una sottoclasse ForwardingPlayer e i metodi di override per eseguire qualsiasi le seguenti:

  • Accedi ai parametri prima di trasmetterli al delegato Player.
  • Accedi al valore restituito dal delegato Player prima di restituirlo.
  • Implementa nuovamente il metodo.

Quando esegui l'override dei metodi ForwardingPlayer, è importante assicurarsi che l'implementazione rimane autocoerente e conforme alle Player dell'interfaccia utente, in particolare quando si utilizzano metodi che mirano a con un comportamento identico o correlato. Ad esempio:

  • Se vuoi sostituire ogni "riproduzione" devi eseguire l'override ForwardingPlayer.play e ForwardingPlayer.setPlayWhenReady, perché si aspetta che il comportamento di questi metodi sia identico playWhenReady = true.
  • Per modificare l'incremento in avanti, devi sostituire entrambi ForwardingPlayer.seekForward per eseguire una ricerca con il tuo incrementare e ForwardingPlayer.getSeekForwardIncrement per generare report l'incremento personalizzato corretto al chiamante.
  • Se vuoi controllare quali contenuti Player.Commands vengono pubblicizzati da un giocatore istanza, devi eseguire l'override di Player.getAvailableCommands() e Player.isCommandAvailable() e ascolta anche callback di Player.Listener.onAvailableCommandsChanged() per ricevere una notifica modifiche provenienti dal player sottostante.

Personalizzazione di MediaSource

Gli esempi precedenti inseriscono componenti personalizzati da utilizzare durante la riproduzione di tutti MediaItem oggetti passati al player. mentre la personalizzazione granulare è è anche possibile inserire componenti personalizzati MediaSource istanze, che possono essere trasmesse direttamente al player. L'esempio di seguito mostra come personalizzare un ProgressiveMediaSource per l'utilizzo di un DataSource.Factory, ExtractorsFactory e LoadErrorHandlingPolicy:

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 in alto di questa pagina per i casi d'uso comuni. Un ExoPlayer può utilizzare questi componenti, ma possono anche essere create per utilizzare implementazioni personalizzate se vengono rilevati comportamenti non standard obbligatorio. Di seguito sono riportati alcuni casi d'uso per le implementazioni personalizzate:

  • Renderer - Potresti voler implementare un Renderer personalizzato per gestire una tipo di media non supportato dalle implementazioni predefinite fornite dalla libreria.
  • TrackSelector: l'implementazione di un valore TrackSelector personalizzato consente a un'app sviluppatore di modificare il modo in cui le tracce esposte da un MediaSource vengono selezionati per il consumo da parte di ciascuno dei Renderer disponibili.
  • LoadControl: l'implementazione di un valore LoadControl personalizzato consente a un'app sviluppatore di modificare il criterio di buffering del player.
  • Extractor: se devi supportare un formato contenitore al momento non disponibile supportata dalla libreria, valuta la possibilità di implementare una classe Extractor personalizzata.
  • MediaSource: l'implementazione di una classe MediaSource personalizzata può essere appropriato se desideri ottenere campioni multimediali da inviare ai renderer in una in modo personalizzato o se vuoi implementare una composizione MediaSource personalizzata comportamento degli utenti.
  • MediaSource.Factory – Implementazione di un MediaSource.Factory personalizzato consente a un'applicazione di personalizzare il modo in cui viene creato un MediaSource da un MediaItem.
  • DataSource - Il pacchetto upstream di ExoPlayer contiene già un certo numero di DataSource implementazioni per diversi casi d'uso. Potresti voler implementa la tua classe DataSource per caricare i dati in un altro modo, ad esempio oltre da un protocollo personalizzato, utilizzando uno stack HTTP personalizzato o da un .

Quando crei componenti personalizzati, segui questi consigli:

  • Se un componente personalizzato deve segnalare gli eventi all'app, ti consigliamo mediante lo stesso modello dei componenti ExoPlayer esistenti, ad esempio esempio utilizzando classi EventDispatcher o passando un Handler insieme a un listener per il costruttore del componente.
  • Consigliamo per i componenti personalizzati di utilizzare lo stesso modello dell'ExoPlayer esistente per consentire la riconfigurazione da parte dell'app durante la riproduzione. Per farlo, i componenti personalizzati devono implementare PlayerMessage.Target e ricevere modifiche alla configurazione nel metodo handleMessage. Il codice dell'applicazione passa le modifiche alla configurazione chiamando il metodo createMessage di ExoPlayer, configurando il messaggio e inviandolo al componente utilizzando PlayerMessage.send. Invio di messaggi da recapitare nel thread di riproduzione in corso... ne assicura l'esecuzione in ordine, mentre qualsiasi altra operazione eseguita sul player.