Penyesuaian

Inti dari library ExoPlayer adalah antarmuka Player. Player memaparkan fungsionalitas pemutar media tingkat tinggi tradisional seperti kemampuan untuk melakukan buffer media, memutar, menjeda dan mencari. Implementasi default ExoPlayer adalah dirancang untuk membuat sedikit asumsi (dan karenanya memberlakukan sedikit batasan) jenis media yang sedang diputar, bagaimana dan di mana media tersebut disimpan, serta bagaimana media tersebut disimpan dirender. Daripada menerapkan pemuatan dan rendering media secara langsung, Implementasi ExoPlayer mendelegasikan pekerjaan ini ke komponen yang dimasukkan saat pemutar dibuat atau saat sumber media baru diteruskan ke pemutar. Komponen yang umum untuk semua implementasi 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. Mereka juga dapat diteruskan langsung ke pemutar menggunakan API playlist berbasis sumber media.
  • Instance MediaSource.Factory yang mengonversi MediaItem menjadi MediaSource. Tujuan MediaSource.Factory dimasukkan saat pemutar dibuat.
  • Instance Renderer yang merender setiap komponen media. Berikut adalah yang dimasukkan saat pemutar dibuat.
  • TrackSelector yang memilih jalur yang disediakan oleh MediaSource untuk yang digunakan oleh setiap Renderer yang tersedia. TrackSelector dimasukkan saat pemutar dibuat.
  • LoadControl yang mengontrol kapan MediaSource mem-buffer lebih banyak media, dan berapa banyak media yang di-buffer. LoadControl dimasukkan saat pemutar dibuat.
  • LivePlaybackSpeedControl yang mengontrol kecepatan pemutaran selama live streaming pemutaran agar pemutar tetap dekat dengan offset live yang dikonfigurasi. J LivePlaybackSpeedControl dimasukkan saat pemutar dibuat.

Konsep memasukkan komponen yang mengimplementasikan potongan pemutar fungsionalitas ada di seluruh library. Implementasi {i>default<i} dari beberapa komponen mendelegasikan pekerjaan ke komponen yang dimasukkan lebih lanjut. Hal ini memungkinkan banyak sub-komponen untuk diganti satu per satu dengan penerapan yang dikonfigurasi dengan cara khusus.

Penyesuaian pemutar

Beberapa contoh umum penyesuaian pemutar dengan memasukkan komponen adalah yang dijelaskan di bawah.

Mengonfigurasi tumpukan jaringan

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

Menyimpan data ke dalam cache yang dimuat dari jaringan

Lihat panduan untuk cache dengan cepat untuk sementara dan mendownload media.

Menyesuaikan interaksi server

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

Contoh berikut menunjukkan cara menerapkan perilaku ini dengan dengan memasukkan 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 dimasukkan mencakup header "Header: Value" di setiap permintaan HTTP. Perilaku ini diperbaiki untuk setiap dengan sumber HTTP.

Untuk pendekatan yang lebih terperinci, Anda bisa menginjeksikan perilaku tepat waktu menggunakan ResolvingDataSource. Cuplikan kode berikut menunjukkan cara memasukkan 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 perubahan 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

Mengimplementasikan LoadErrorHandlingPolicy kustom memungkinkan aplikasi menyesuaikan cara ExoPlayer bereaksi terhadap error pemuatan. Misalnya, sebuah aplikasi mungkin ingin gagal dengan cepat alih-alih mencoba berkali-kali, atau mungkin ingin menyesuaikan logika {i>back-off<i} yang mengontrol berapa lama pemain menunggu di antara setiap percobaan ulang. Cuplikan berikut menunjukkan cara menerapkan logika back-off 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 selengkapnya tentang pemuatan yang gagal ke menyesuaikan logika berdasarkan jenis error atau permintaan yang gagal.

Menyesuaikan tanda ekstraktor

Tanda pengekstrak dapat digunakan untuk menyesuaikan cara mengekstrak setiap format dari media progresif. Parameter ini dapat disetel di DefaultExtractorsFactory yang yang diberikan ke DefaultMediaSourceFactory. Contoh berikut meneruskan flag yang memungkinkan 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 kecepatan bit konstan

Untuk streaming MP3, ADTS, dan AMR, Anda dapat mengaktifkan pencarian perkiraan menggunakan asumsi kecepatan bit konstan dengan tanda FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. Tanda ini dapat ditetapkan untuk masing-masing ekstraktor yang menggunakan DefaultExtractorsFactory.setXyzExtractorFlags seperti yang dijelaskan di atas. Kepada mengaktifkan pencarian kecepatan bit 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 dimasukkan melalui DefaultMediaSourceFactory sebagai yang dijelaskan untuk menyesuaikan flag ekstrak di atas.

Mengaktifkan antrean buffer asinkron

Antrean buffer asinkron adalah peningkatan dalam rendering ExoPlayer pipeline, yang mengoperasikan instance MediaCodec dalam mode asinkron dan menggunakan thread tambahan untuk menjadwalkan dekode dan rendering data. Mengaktifkannya dapat mengurangi penurunan {i>frame<i} dan {i>underrun<i} audio.

Antrean buffer asinkron diaktifkan secara default di perangkat yang menjalankan Android 12 (API level 31) dan yang lebih tinggi, serta dapat diaktifkan secara manual mulai dari Android 6.0 (API level 23). Pertimbangkan untuk mengaktifkan fitur untuk perangkat tertentu yang Anda amati mengalami penurunan {i>frame<i} atau {i>underrun<i} audio, khususnya saat memutar sinyal yang dilindungi DRM atau kecepatan frame tinggi saat ini.

Dalam kasus yang paling sederhana, Anda perlu memasukkan 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 AsynchronousMediaCodecAdapter.Factory ke MediaCodecVideoRenderer dan Konstruktor MediaCodecAudioRenderer.

Mengintersepsi panggilan metode dengan ForwardingPlayer

Anda dapat menyesuaikan beberapa perilaku instance Player dengan menggabungkannya subclass dari ForwardingPlayer dan mengganti metode untuk melakukan salah satu hal berikut:

  • Akses parameter sebelum meneruskannya ke Player delegasi.
  • Akses nilai yang ditampilkan dari Player delegasi sebelum menampilkannya.
  • Mengimplementasikan kembali metode ini sepenuhnya.

Saat mengganti metode ForwardingPlayer, penting untuk memastikan tetap konsisten mandiri dan mematuhi Player antarmuka, terutama ketika berhadapan dengan metode yang dimaksudkan untuk memiliki perilaku yang identik atau terkait. Contoh:

  • Jika Anda ingin mengganti setiap 'putar' operasi, Anda harus mengganti ForwardingPlayer.play dan ForwardingPlayer.setPlayWhenReady, karena pemanggil akan mengharapkan perilaku metode ini akan identik ketika playWhenReady = true.
  • Jika Anda ingin mengubah kenaikan pencarian, Anda perlu mengganti keduanya ForwardingPlayer.seekForward untuk melakukan pencarian dengan tambahan, dan ForwardingPlayer.getSeekForwardIncrement untuk melaporkan kenaikan yang disesuaikan secara benar kembali ke pemanggil.
  • Jika Anda ingin mengontrol apa Player.Commands yang diiklankan oleh pemain Anda harus mengganti Player.getAvailableCommands() dan Player.isCommandAvailable(), dan juga mendengarkan Callback Player.Listener.onAvailableCommandsChanged() untuk mendapatkan notifikasi perubahan dari pemain yang mendasarinya.

Penyesuaian MediaSource

Contoh di atas memasukkan komponen yang disesuaikan untuk digunakan selama pemutaran semua Objek MediaItem yang diteruskan ke pemutar. Di mana penyesuaian yang terperinci diperlukan, Anda juga dapat memasukkan komponen khusus ke dalam Instance MediaSource, yang dapat diteruskan langsung ke pemutar. Contoh di bawah ini menunjukkan cara menyesuaikan ProgressiveMediaSource untuk menggunakan DataSource.Factory, ExtractorsFactory, dan 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));

Membuat komponen kustom

Library menyediakan implementasi default dari 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 tidak diperlukan. Beberapa kasus penggunaan untuk penerapan kustom adalah:

  • Renderer – Anda mungkin ingin mengimplementasikan Renderer kustom untuk menangani jenis media ini tidak didukung oleh implementasi default yang disediakan oleh library.
  • TrackSelector – Mengimplementasikan TrackSelector kustom memungkinkan aplikasi developer untuk mengubah cara jalur yang diekspos oleh MediaSource dipilih untuk digunakan oleh masing-masing Renderer yang tersedia.
  • LoadControl – Mengimplementasikan LoadControl kustom memungkinkan aplikasi untuk mengubah kebijakan {i>buffering<i} pemutar.
  • Extractor – Jika Anda perlu mendukung format penampung yang saat ini tidak didukung didukung oleh library, sebaiknya implementasikan class Extractor kustom.
  • MediaSource – Mengimplementasikan class MediaSource kustom dapat berupa sesuai jika Anda ingin mendapatkan sampel media untuk dimasukkan ke perender dalam cara kustom, atau jika Anda ingin menerapkan pengomposisian MediaSource kustom perilaku model.
  • 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 mengimplementasikan class DataSource Anda sendiri untuk memuat data dengan cara lain, seperti melalui protokol kustom, menggunakan stack HTTP kustom, atau dari protokol di cache oleh pengguna.

Saat membuat komponen kustom, sebaiknya lakukan tindakan berikut:

  • Jika komponen kustom perlu melaporkan peristiwa kembali ke aplikasi, sebaiknya Anda melakukannya dengan menggunakan model yang sama seperti komponen ExoPlayer yang ada, untuk contoh menggunakan class EventDispatcher atau meneruskan Handler bersama dengan pemroses ke konstruktor komponen.
  • Kami merekomendasikan agar komponen kustom menggunakan model yang sama dengan ExoPlayer yang ada untuk memungkinkan konfigurasi ulang oleh aplikasi selama pemutaran. Untuk melakukannya: komponen kustom harus mengimplementasikan 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 yang akan dikirim di rangkaian pesan pemutaran memastikan bahwa operasi tersebut dijalankan dalam urutan operasi lain yang yang ditampilkan pada pemutar.