Inti dari library ExoPlayer adalah 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 beberapa asumsi tentang (dan oleh karena itu memberlakukan beberapa batasan pada)
jenis media yang diputar, cara dan tempat penyimpanannya, serta cara
renderingnya. Alih-alih 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
MediaSourceyang menentukan media yang akan diputar, memuat media, dan tempat media yang dimuat dapat dibaca. InstanceMediaSourcedibuat dariMediaItemolehMediaSource.Factorydi dalam pemutar. Iklan juga dapat diteruskan langsung ke pemutar menggunakan API playlist berbasis sumber media. - Instance
MediaSource.Factoryyang mengonversiMediaItemmenjadiMediaSource.MediaSource.Factorydimasukkan saat pemutar dibuat. - Instance
Rendereryang merender setiap komponen media. Ini diinjeksikan saat pemutar dibuat. TrackSelectoryang memilih trek yang disediakan olehMediaSourceuntuk digunakan oleh setiapRendereryang tersedia.TrackSelectordimasukkan saat pemutar dibuat.LoadControlyang mengontrol kapanMediaSourcemelakukan buffering lebih banyak media, dan jumlah media yang di-buffer.LoadControldimasukkan saat pemutar dibuat.LivePlaybackSpeedControlyang mengontrol kecepatan pemutaran selama pemutaran live agar pemutar tetap dekat dengan offset live yang dikonfigurasi.LivePlaybackSpeedControldiinjeksikan saat pemutar dibuat.
Konsep memasukkan komponen yang mengimplementasikan bagian fungsi pemutar ada di seluruh library. Implementasi default beberapa komponen mendelegasikan pekerjaan ke komponen yang diinjeksikan lebih lanjut. Hal ini memungkinkan banyak subkomponen diganti satu per satu dengan implementasi yang dikonfigurasi secara kustom.
Penyesuaian pemutar
Beberapa contoh umum penyesuaian pemutar dengan memasukkan komponen dijelaskan di bagian berikut.
Mengonfigurasi stack jaringan
Kami memiliki halaman tentang menyesuaikan stack jaringan yang digunakan oleh ExoPlayer.
Menyimpan data yang dimuat dari jaringan ke dalam cache
Lihat panduan untuk caching on-the-fly sementara dan mendownload media.
Menyesuaikan interaksi server
Beberapa aplikasi mungkin ingin mengintersepsi permintaan dan respons HTTP. Anda mungkin ingin memasukkan header permintaan kustom, membaca header respons server, mengubah URI permintaan, 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 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 sebelumnya, HttpDataSource yang dimasukkan menyertakan header
"Header: Value" di setiap permintaan HTTP. Perilaku ini tetap untuk setiap
interaksi dengan sumber HTTP.
Untuk pendekatan yang lebih terperinci, Anda dapat memasukkan 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 modifikasi URI secara 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, aplikasi mungkin ingin gagal dengan cepat
daripada mencoba ulang berkali-kali, atau mungkin ingin menyesuaikan logika back-off yang
mengontrol berapa lama pemutar 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: LoadErrorHandlingPolicy.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(LoadErrorHandlingPolicy.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 untuk menyesuaikan logika berdasarkan jenis error atau permintaan yang gagal.
Menyesuaikan flag ekstraktor
Flag ekstraktor dapat digunakan untuk menyesuaikan cara setiap format diekstrak
dari media progresif. Properti ini dapat ditetapkan di DefaultExtractorsFactory yang
disediakan untuk DefaultMediaSourceFactory. Contoh berikut meneruskan flag
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 kecepatan bit konstan
Untuk streaming MP3, ADTS, dan AMR, Anda dapat mengaktifkan pencarian perkiraan menggunakan asumsi kecepatan bit konstan dengan flag FLAG_ENABLE_CONSTANT_BITRATE_SEEKING.
Flag ini dapat ditetapkan untuk setiap ekstraktor menggunakan masing-masing
metode DefaultExtractorsFactory.setXyzExtractorFlags seperti yang dijelaskan sebelumnya. Untuk
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 diinjeksikan menggunakan DefaultMediaSourceFactory
seperti yang dijelaskan untuk menyesuaikan flag ekstraktor sebelumnya.
Mengaktifkan antrean buffer asinkron
Antrean buffering 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 (API level 31) atau yang lebih tinggi, dan dapat diaktifkan secara manual mulai dari Android 6.0 (API level 23). Pertimbangkan untuk mengaktifkan fitur ini untuk perangkat tertentu yang mengalami penurunan frame atau underrun audio, terutama saat memutar konten yang dilindungi DRM atau konten dengan kecepatan frame tinggi.
Dalam kasus yang paling sederhana, Anda perlu memasukkan DefaultRenderersFactory ke
pemutar seperti 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 menggabungkannya dalam subclass ForwardingSimpleBasePlayer. Class ini memungkinkan Anda menangkap 'operasi' tertentu, tanpa harus langsung mengimplementasikan metode
Player. Hal ini memastikan perilaku yang konsisten, misalnya, play(), pause(),
dan setPlayWhenReady(boolean). Hal ini juga memastikan semua perubahan status diterapkan dengan benar ke instance Player.Listener yang terdaftar. Untuk sebagian besar kasus penggunaan penyesuaian, ForwardingSimpleBasePlayer harus lebih diutamakan 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
public static final 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
public static final 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 sebelumnya memasukkan komponen yang disesuaikan untuk digunakan selama pemutaran
semua objek MediaItem yang diteruskan ke pemutar. Jika diperlukan penyesuaian yang mendetail, Anda juga dapat memasukkan komponen yang disesuaikan ke dalam setiap instance MediaSource, yang dapat diteruskan langsung ke pemutar. Contoh berikut 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
Library 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 implementasi kustom jika diperlukan perilaku
non-standar. Beberapa kasus penggunaan untuk penerapan kustom adalah:
Renderer– Anda mungkin ingin menerapkanRendererkustom untuk menangani jenis media yang tidak didukung oleh penerapan default yang disediakan oleh library.TrackSelector– PenerapanTrackSelectorkustom memungkinkan developer aplikasi mengubah cara trek yang diekspos olehMediaSourcedipilih untuk digunakan oleh setiapRendereryang tersedia.LoadControl– PenerapanLoadControlkustom memungkinkan developer aplikasi mengubah kebijakan buffering pemutar.Extractor– Jika Anda perlu mendukung format penampung yang saat ini tidak didukung oleh library, pertimbangkan untuk mengimplementasikan classExtractorkustom.MediaSource– Mengimplementasikan classMediaSourcekustom mungkin sesuai jika Anda ingin mendapatkan sampel media untuk dimasukkan ke perender dengan cara kustom, atau jika Anda ingin mengimplementasikan perilaku penggabunganMediaSourcekustom.MediaSource.Factory– MengimplementasikanMediaSource.Factorykustom memungkinkan aplikasi menyesuaikan cara pembuatanMediaSourcedariMediaItem.DataSource– Paket upstream ExoPlayer sudah berisi sejumlah implementasiDataSourceuntuk berbagai kasus penggunaan. Anda mungkin ingin mengimplementasikan classDataSourceAnda 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
EventDispatcheratau meneruskanHandlerbersama 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 mengimplementasikan
PlayerMessage.Targetdan menerima perubahan konfigurasi dalam metodehandleMessage. Kode aplikasi harus meneruskan perubahan konfigurasi dengan memanggil metodecreateMessageExoPlayer, mengonfigurasi pesan, dan mengirimkannya ke komponen menggunakanPlayerMessage.send. Mengirim pesan untuk dikirim di thread pemutaran memastikan bahwa pesan tersebut dieksekusi secara berurutan dengan operasi lain yang dilakukan pada pemutar.