Penyesuaian

Di inti library ExoPlayer terdapat antarmuka Player. Player mengekspos fungsi pemutar media tingkat tinggi tradisional seperti kemampuan untuk melakukan buffering media, memutar, menjeda, dan mencari. Implementasi default ExoPlayer dirancang untuk membuat sedikit asumsi tentang (dan oleh karena itu memberlakukan sedikit batasan pada) jenis media yang diputar, cara dan tempat media tersebut disimpan, serta cara media tersebut dirender. Daripada menerapkan pemuatan dan rendering media secara langsung, penerapan ExoPlayer mendelegasikan tugas ini ke komponen yang disuntikkan saat pemutar dibuat atau saat sumber media baru diteruskan ke pemutar. Komponen umum untuk semua penerapan ExoPlayer adalah:

  • Instance MediaSource yang menentukan media yang akan diputar, memuat media, dan dari mana media yang dimuat dapat dibaca. Instance MediaSource dibuat dari MediaItem oleh MediaSource.Factory di dalam pemutar. URL ini juga dapat diteruskan langsung ke pemutar menggunakan API playlist berbasis sumber media.
  • Instance MediaSource.Factory yang mengonversi MediaItem menjadi MediaSource. MediaSource.Factory disisipkan saat pemutar dibuat.
  • Renderer instance yang merender komponen media tertentu. Setelan ini disisipkan saat pemutar dibuat.
  • TrackSelector yang memilih jalur yang disediakan oleh MediaSource untuk digunakan oleh setiap Renderer yang tersedia. TrackSelector disisipkan saat pemutar dibuat.
  • LoadControl yang mengontrol kapan MediaSource melakukan buffering lebih banyak media, dan seberapa banyak media yang di-buffer. LoadControl disisipkan saat pemutar dibuat.
  • LivePlaybackSpeedControl yang mengontrol kecepatan pemutaran selama pemutaran live agar pemutar tetap dekat dengan offset live yang dikonfigurasi. LivePlaybackSpeedControl disisipkan saat pemutar dibuat.

Konsep penyuntikan komponen yang mengimplementasikan bagian dari fungsi pemain ada di seluruh library. Implementasi default beberapa komponen mendelegasikan tugas ke komponen yang disuntikkan lebih lanjut. Hal ini memungkinkan banyak sub-komponen diganti satu per satu dengan implementasi yang dikonfigurasi secara kustom.

Penyesuaian pemain

Beberapa contoh umum penyesuaian pemutar dengan menyuntikkan komponen dijelaskan di bawah.

Mengonfigurasi stack jaringan

Kami memiliki halaman tentang menyesuaikan stack jaringan yang digunakan oleh ExoPlayer.

Meng-cache data yang dimuat dari jaringan

Lihat panduan untuk penyimpanan cache sementara secara langsung dan mendownload media.

Menyesuaikan interaksi server

Beberapa aplikasi mungkin ingin mencegat permintaan dan respons HTTP. Anda mungkin ingin menyisipkan header permintaan kustom, membaca header respons server, mengubah URI permintaan, dll. Misalnya, aplikasi Anda dapat mengautentikasi dirinya sendiri dengan menyisipkan token sebagai header saat meminta segmen media.

Contoh berikut menunjukkan cara menerapkan perilaku ini dengan menyuntikkan DataSource.Factory kustom ke dalam 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();

Dalam cuplikan kode di atas, HttpDataSource yang disuntikkan menyertakan header "Header: Value" di setiap permintaan HTTP. Perilaku ini diperbaiki untuk setiap interaksi dengan sumber HTTP.

Untuk pendekatan yang lebih terperinci, Anda dapat menyuntikkan perilaku tepat waktu menggunakan ResolvingDataSource. Cuplikan kode berikut menunjukkan cara menyuntikkan header permintaan tepat sebelum berinteraksi dengan sumber 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)));

Anda juga dapat menggunakan ResolvingDataSource untuk melakukan modifikasi URI tepat waktu, seperti yang ditunjukkan dalam cuplikan berikut:

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)));

Menyesuaikan penanganan error

Menerapkan LoadErrorHandlingPolicy kustom memungkinkan aplikasi menyesuaikan cara ExoPlayer bereaksi terhadap error pemuatan. Misalnya, aplikasi mungkin ingin gagal dengan cepat daripada mencoba lagi berkali-kali, atau mungkin ingin menyesuaikan logika backoff yang mengontrol durasi penantian pemutar di antara setiap percobaan ulang. Cuplikan berikut menunjukkan cara menerapkan logika penundaan kustom:

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();

Argumen LoadErrorInfo berisi informasi lebih lanjut tentang pemuatan yang gagal untuk menyesuaikan logika berdasarkan jenis error atau permintaan yang gagal.

Menyesuaikan tanda pengekstrak

Flag ekstraktor dapat digunakan untuk menyesuaikan cara setiap format diekstrak dari media progresif. Parameter ini dapat disetel di DefaultExtractorsFactory yang diberikan ke DefaultMediaSourceFactory. Contoh berikut meneruskan tanda yang mengaktifkan pencarian berbasis indeks untuk streaming 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();

Mengaktifkan pencarian bitrate konstan

Untuk streaming MP3, ADTS, dan AMR, Anda dapat mengaktifkan pencarian perkiraan menggunakan asumsi bitrate konstan dengan tanda FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. Flag ini dapat ditetapkan untuk setiap ekstraktor menggunakan metode DefaultExtractorsFactory.setXyzExtractorFlags individual seperti yang dijelaskan di atas. Untuk mengaktifkan pencarian bitrate konstan untuk semua ekstraktor yang mendukungnya, gunakan DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

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

ExtractorsFactory kemudian dapat disuntikkan melalui DefaultMediaSourceFactory seperti yang dijelaskan untuk menyesuaikan flag ekstraktor di atas.

Mengaktifkan antrean buffer asinkron

Pengantrean buffer asinkron adalah peningkatan dalam pipeline rendering ExoPlayer, yang mengoperasikan instance MediaCodec dalam mode asinkron dan menggunakan thread tambahan untuk menjadwalkan decoding dan rendering data. Mengaktifkannya dapat mengurangi frame yang drop dan underrun audio.

Pengantrean buffer asinkron diaktifkan secara default di perangkat yang menjalankan Android 12 (level API 31) dan yang lebih tinggi, dan dapat diaktifkan secara manual mulai dari Android 6.0 (level API 23). Pertimbangkan untuk mengaktifkan fitur ini untuk perangkat tertentu yang mengalami frame yang terputus atau kekurangan audio, terutama saat memutar konten yang dilindungi DRM atau konten dengan kecepatan frame tinggi.

Dalam kasus yang paling sederhana, Anda perlu menyuntikkan DefaultRenderersFactory ke pemutar sebagai berikut:

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();

Jika Anda membuat instance perender secara langsung, teruskan new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() ke konstruktor MediaCodecVideoRenderer dan MediaCodecAudioRenderer.

Menyesuaikan operasi dengan ForwardingSimpleBasePlayer

Anda dapat menyesuaikan beberapa perilaku instance Player dengan membungkusnya dalam subclass ForwardingSimpleBasePlayer. Class ini memungkinkan Anda mencegat 'operasi' tertentu, daripada harus mengimplementasikan metode Player secara langsung. Hal ini memastikan perilaku yang konsisten, misalnya, play(), pause() dan setPlayWhenReady(boolean). Hal ini juga memastikan semua perubahan status disebarkan dengan benar ke instance Player.Listener yang terdaftar. Untuk sebagian besar kasus penggunaan penyesuaian, ForwardingSimpleBasePlayer harus lebih disukai daripada ForwardingPlayer yang lebih rentan terhadap error karena jaminan konsistensi ini.

Misalnya, untuk menambahkan beberapa logika kustom saat pemutaran dimulai atau dihentikan:

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);
  }
}

Atau untuk melarang perintah SEEK_TO_NEXT (dan memastikan Player.seekToNext adalah no-op):

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.
}

Penyesuaian MediaSource

Contoh di atas menyuntikkan komponen yang disesuaikan untuk digunakan selama pemutaran semua objek MediaItem yang diteruskan ke pemutar. Jika penyesuaian terperinci diperlukan, Anda juga dapat menyuntikkan komponen yang disesuaikan ke dalam setiap instance MediaSource, yang dapat diteruskan langsung ke pemutar. Contoh di bawah menunjukkan cara menyesuaikan ProgressiveMediaSource untuk menggunakan DataSource.Factory, ExtractorsFactory, dan LoadErrorHandlingPolicy kustom:

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));

Membuat komponen kustom

Pustaka ini menyediakan implementasi default komponen yang tercantum di bagian atas halaman ini untuk kasus penggunaan umum. ExoPlayer dapat menggunakan komponen ini, tetapi juga dapat dibuat untuk menggunakan penerapan kustom jika perilaku non-standar diperlukan. Beberapa kasus penggunaan untuk penerapan kustom adalah:

  • Renderer – Anda mungkin ingin menerapkan Renderer kustom untuk menangani jenis media yang tidak didukung oleh penerapan default yang disediakan oleh library.
  • TrackSelector – Menerapkan TrackSelector kustom memungkinkan developer aplikasi mengubah cara trek yang diekspos oleh MediaSource dipilih untuk digunakan oleh setiap Renderer yang tersedia.
  • LoadControl – Dengan menerapkan LoadControl kustom, developer aplikasi dapat mengubah kebijakan buffering pemutar.
  • Extractor – Jika Anda perlu mendukung format penampung yang saat ini tidak didukung oleh library, pertimbangkan untuk menerapkan class Extractor kustom.
  • MediaSource – Menerapkan class MediaSource kustom mungkin sesuai jika Anda ingin mendapatkan sampel media untuk diumpankan ke perender dengan cara kustom, atau jika Anda ingin menerapkan perilaku komposit MediaSource kustom.
  • MediaSource.Factory – Menerapkan MediaSource.Factory kustom memungkinkan aplikasi menyesuaikan cara pembuatan MediaSource dari MediaItem.
  • DataSource – Paket upstream ExoPlayer sudah berisi sejumlah implementasi DataSource untuk berbagai kasus penggunaan. Anda mungkin ingin menerapkan class DataSource Anda sendiri untuk memuat data dengan cara lain, seperti melalui protokol kustom, menggunakan stack HTTP kustom, atau dari cache persisten kustom.

Saat membuat komponen kustom, sebaiknya lakukan hal berikut:

  • Jika komponen kustom perlu melaporkan peristiwa kembali ke aplikasi, sebaiknya lakukan hal tersebut menggunakan model yang sama dengan komponen ExoPlayer yang ada, misalnya menggunakan class EventDispatcher atau meneruskan Handler bersama dengan pemroses ke konstruktor komponen.
  • Sebaiknya komponen kustom menggunakan model yang sama dengan komponen ExoPlayer yang ada untuk memungkinkan konfigurasi ulang oleh aplikasi selama pemutaran. Untuk melakukannya, komponen kustom harus menerapkan PlayerMessage.Target dan menerima perubahan konfigurasi dalam metode handleMessage. Kode aplikasi harus meneruskan perubahan konfigurasi dengan memanggil metode createMessage ExoPlayer, mengonfigurasi pesan, dan mengirimkannya ke komponen menggunakan PlayerMessage.send. Mengirim pesan untuk dikirimkan di thread pemutaran memastikan bahwa pesan tersebut dijalankan secara berurutan dengan operasi lain yang dilakukan di pemutar.