Berita Produk
Meningkatkan kualitas pemutaran media: Mempelajari PreloadManager Media3 secara mendalam - Bagian 2
Waktu baca: 9 menit
Selamat datang di bagian kedua dari seri tiga bagian kami tentang pra-pemuatan media dengan Media3. Seri ini dirancang untuk memandu Anda melalui proses membangun pengalaman media yang sangat responsif dan latensi rendah di aplikasi Android Anda.
- Bagian 1: Memperkenalkan Pemuatan Awal dengan Media3 membahas dasar-dasarnya. Kita telah mempelajari perbedaan antara PreloadConfiguration untuk playlist sederhana dan DefaultPreloadManager yang lebih canggih untuk antarmuka pengguna dinamis. Anda telah mempelajari cara menerapkan siklus proses API dasar: menambahkan media dengan add(), mengambil MediaSource yang telah disiapkan dengan getMediaSource(), mengelola prioritas dengan setCurrentPlayingIndex() dan invalidate(), serta melepaskan resource dengan remove() dan release().
- Bagian 2 (Postingan ini): Dalam blog ini, kita akan mempelajari kemampuan lanjutan DefaultPreloadManager. Kita akan membahas cara mendapatkan insight dengan PreloadManagerListener, menerapkan praktik terbaik yang siap produksi seperti berbagi komponen inti dengan ExoPlayer, dan menguasai pola jendela geser untuk mengelola memori secara efektif.
- Bagian 3: Bagian terakhir dari seri ini akan membahas integrasi PreloadManager dengan cache disk persisten, sehingga Anda dapat mengurangi penggunaan data dengan pengelolaan resource dan memberikan pengalaman yang lancar.
Jika Anda baru mengenal pra-pemuatan di Media3, sebaiknya baca Bagian 1 sebelum melanjutkan. Bagi Anda yang siap melampaui dasar-dasar, mari pelajari cara meningkatkan kualitas penerapan pemutaran media Anda.
Mendengarkan: Mengambil analisis dengan PreloadManagerListener
Saat ingin meluncurkan fitur dalam produksi, sebagai developer aplikasi, Anda juga ingin memahami dan merekam analisis di baliknya. Bagaimana Anda bisa memastikan bahwa strategi pemuatan awal Anda efektif di lingkungan dunia nyata? Untuk menjawab pertanyaan ini, diperlukan data tentang tingkat keberhasilan, kegagalan, dan performa. Antarmuka PreloadManagerListener adalah mekanisme utama untuk mengumpulkan data ini.
PreloadManagerListener menyediakan dua callback penting yang menawarkan insight penting tentang proses dan status pramuat.
- onCompleted(MediaItem mediaItem): Callback ini dipanggil setelah berhasil menyelesaikan permintaan pra-muat, sebagaimana ditentukan oleh TargetPreloadStatusControl Anda.
- onError(error PreloadException): Callback ini dapat berguna untuk proses debug dan pemantauan. Metode ini dipanggil saat pramuat gagal, dengan memberikan pengecualian terkait.
Anda dapat mendaftarkan pemroses dengan satu panggilan metode seperti yang ditunjukkan dalam contoh kode berikut:
val preloadManagerListener = object : PreloadManagerListener { override fun onCompleted(mediaItem: MediaItem) { // Log success for analytics. Log.d("PreloadAnalytics", "Preload completed for $mediaItem") } override fun onError( preloadError: PreloadException) { // Log the specific error for debugging and monitoring. Log.e("PreloadAnalytics", "Preload error ", preloadError) } } preloadManager.addListener(preloadManagerListener)
Mengekstrak insight dari pendengar
Callback pemroses ini dapat dikaitkan ke pipeline analisis Anda. Dengan meneruskan peristiwa ini ke mesin analisis, Anda dapat menjawab pertanyaan penting seperti:
- Berapa tingkat keberhasilan pra-muat kami? (rasio peristiwa onCompleted terhadap total upaya pra-muat)
- CDN atau format video mana yang menunjukkan rasio error tertinggi? (Dengan mengurai pengecualian dari onError)
- Berapa tingkat error pra-muat kita? (rasio peristiwa onError terhadap total upaya pra-pemuatan)
Data ini dapat memberi Anda masukan kuantitatif tentang strategi pemuatan awal, sehingga memungkinkan pengujian A/B dan peningkatan pengalaman pengguna berbasis data. Data ini dapat lebih lanjut membantu Anda menyesuaikan durasi pra-muat secara cerdas dan jumlah video yang ingin Anda pra-muat serta buffer yang Anda alokasikan.
Selain proses debug: Menggunakan onError untuk penggantian UI yang lancar
Pemuatan awal yang gagal adalah indikator kuat dari peristiwa buffering yang akan datang bagi pengguna. Callback onError memungkinkan Anda merespons secara reaktif. Daripada hanya mencatat error, Anda dapat menyesuaikan UI. Misalnya, jika video berikutnya gagal dimuat sebelumnya, aplikasi Anda dapat menonaktifkan putar otomatis untuk geseran berikutnya, sehingga pengguna perlu mengetuk untuk memulai pemutaran.
Selain itu, dengan memeriksa jenis PreloadException, Anda dapat menentukan strategi percobaan ulang yang lebih cerdas. Aplikasi dapat memilih untuk segera menghapus sumber yang gagal dari pengelola berdasarkan pesan error atau kode status HTTP. Item harus dihapus dari aliran UI dengan tepat agar masalah pemuatan tidak memengaruhi pengalaman pengguna. Anda juga bisa mendapatkan data yang lebih terperinci dari PreloadException seperti HttpDataSourceException untuk menyelidiki error lebih lanjut. Baca selengkapnya tentang pemecahan masalah ExoPlayer.
Sistem pendamping: Mengapa berbagi komponen dengan ExoPlayer diperlukan?
DefaultPreloadManager dan ExoPlayer dirancang untuk bekerja sama. Untuk memastikan stabilitas dan efisiensi, mereka harus berbagi beberapa komponen inti. Jika beroperasi dengan komponen yang terpisah dan tidak terkoordinasi, hal ini dapat memengaruhi keamanan thread dan kegunaan trek yang sudah dimuat sebelumnya di pemutar karena kita perlu memastikan bahwa trek yang sudah dimuat sebelumnya harus diputar di pemutar yang benar. Komponen terpisah juga dapat bersaing untuk mendapatkan resource terbatas seperti bandwidth jaringan dan memori, yang dapat menyebabkan penurunan performa. Bagian penting dari siklus proses adalah menangani pembuangan yang tepat. Urutan pembuangan yang direkomendasikan adalah merilis PreloadManager terlebih dahulu, diikuti dengan ExoPlayer.
DefaultPreloadManager.Builder dirancang untuk memfasilitasi berbagi ini dan memiliki API untuk meng-instance PreloadManager dan instance pemain tertaut. Mari kita lihat alasan komponen seperti BandwidthMeter, LoadControl, TrackSelector, Looper harus dibagikan. Periksa representasi visual tentang cara komponen ini berinteraksi dengan Pemutaran ExoPlayer.
Mencegah konflik bandwidth dengan BandwidthMeter bersama
BandwidthMeter memberikan estimasi bandwidth jaringan yang tersedia berdasarkan kecepatan transfer historis. Jika PreloadManager dan pemutar menggunakan instance terpisah, keduanya tidak mengetahui aktivitas jaringan masing-masing, yang dapat menyebabkan skenario kegagalan. Misalnya, pertimbangkan skenario saat pengguna sedang menonton video, koneksi jaringannya menurun, dan MediaSource yang melakukan pra-pemuatan secara bersamaan memulai download agresif untuk video mendatang. Aktivitas MediaSource yang memuat sebelumnya akan menggunakan bandwidth yang diperlukan oleh pemutar aktif, sehingga menyebabkan video saat ini terhenti. Penghentian selama pemutaran adalah kegagalan pengalaman pengguna yang signifikan.
Dengan membagikan satu BandwidthMeter, TrackSelector dapat memilih trek dengan kualitas tertinggi berdasarkan kondisi jaringan saat ini dan status buffer, selama pra-pemuatan atau pemutaran. Kemudian, sistem dapat membuat keputusan cerdas untuk melindungi sesi pemutaran aktif dan memastikan pengalaman yang lancar.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
Memastikan konsistensi dengan komponen LoadControl, TrackSelector, Renderer bersama ExoPlayer
- LoadControl: Komponen ini menentukan kebijakan buffering, seperti jumlah data yang akan di-buffer sebelum memulai pemutaran dan kapan harus memulai atau berhenti memuat lebih banyak data. Berbagi LoadControl memastikan bahwa konsumsi memori pemutar dan PreloadManager dipandu oleh satu strategi buffering yang terkoordinasi di seluruh media yang dimuat sebelumnya dan yang sedang diputar secara aktif, sehingga mencegah pertentangan resource. Anda harus mengalokasikan ukuran buffer secara cerdas dengan mengoordinasikan jumlah item yang Anda muat sebelumnya dan durasinya, untuk memastikan konsistensi. Saat terjadi persaingan, pemutar akan memprioritaskan pemutaran item saat ini yang ditampilkan di layar. Dengan LoadControl bersama, pengelola pramuat akan terus melakukan pramuat selama byte buffer target yang dialokasikan untuk pramuat belum mencapai batas atas, dan tidak menunggu hingga pemuatan untuk pemutaran selesai.
Catatan: Berbagi LoadControl di Media3 (1.8) versi terbaru memastikan bahwa Alokatornya dapat dibagikan dengan benar ke PreloadManager dan pemutar. Penggunaan LoadControl untuk mengontrol pra-pemuatan secara efektif adalah fitur yang akan tersedia di rilis Media3 1.9 mendatang.
preloadManagerBuilder.setLoadControl(customLoadControl)
- TrackSelector: Komponen ini bertanggung jawab untuk memilih trek mana (misalnya, video dengan resolusi tertentu, audio dalam bahasa tertentu) yang akan dimuat dan diputar. Berbagi memastikan bahwa trek yang dipilih selama pra-pemuatan adalah trek yang sama dengan yang akan digunakan pemutar. Hal ini menghindari skenario yang tidak efisien saat trek video 480p dimuat sebelumnya, hanya agar pemutar segera menghapusnya dan mengambil trek 720p saat pemutaran.< br /> Pengelola pramuat TIDAK boleh membagikan instance yang sama dari TrackSelector dengan pemutar. Sebagai gantinya, mereka harus menggunakan instance TrackSelector yang berbeda, tetapi dengan implementasi yang sama. Itulah sebabnya kami menyetel TrackSelectorFactory, bukan TrackSelector di DefaultPreloadManager.Builder.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- Renderer: Komponen ini bertanggung jawab untuk memahami kemampuan pemutar tanpa membuat perender lengkap. Alat ini memeriksa cetak biru ini untuk melihat format video, audio, dan teks yang akan didukung oleh pemutar akhir. Hal ini memungkinkan pemilihan dan download trek media yang kompatibel secara cerdas dan mencegah pemborosan bandwidth pada konten yang sebenarnya tidak dapat diputar oleh pemutar.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
Baca komponen Exoplayer lainnya.
Aturan penting: Satu Playback Looper umum untuk semuanya
Thread tempat instance ExoPlayer dapat diakses dapat ditentukan secara eksplisit dengan meneruskan Looper saat membuat pemutar. Looper thread tempat pemain harus diakses dapat dikueri menggunakan Player.getApplicationLooper. Dengan mempertahankan Looper bersama antara pemutar dan PreloadManager, semua operasi pada objek media bersama ini dijamin diserialkan ke antrean pesan satu thread. Hal ini dapat mengurangi bug konkurensi.
Semua interaksi antara PreloadManager dan pemutar dengan sumber media yang akan dimuat atau dimuat sebelumnya harus terjadi di thread pemutaran yang sama. Berbagi Looper adalah hal yang wajib untuk keamanan thread, sehingga kita harus membagikan PlaybackLooper antara PreloadManager dan pemutar.
PreloadManager menyiapkan objek MediaSource stateful di latar belakang. Saat kode UI Anda memanggil player.setMediaSource(mediaSource), Anda melakukan pengalihan objek kompleks dan berstatus ini dari MediaSource yang telah di-preload ke pemutar. Dalam skenario ini, seluruh PreloadMediaSource dipindahkan dari pengelola ke pemutar. Semua interaksi dan pengalihan ini harus terjadi di PlaybackLooper yang sama.
Jika PreloadManager dan ExoPlayer beroperasi pada thread yang berbeda, kondisi persaingan dapat terjadi. Thread PreloadManager dapat mengubah status internal MediaSource (misalnya, menulis data baru ke dalam buffer) tepat pada saat thread pemutar mencoba membacanya. Hal ini menyebabkan perilaku yang tidak dapat diprediksi, IllegalStateException yang sulit di-debug.
preloadManagerBuilder.setPreloadLooper(playbackLooper)
Mari kita lihat cara Anda dapat membagikan semua komponen di atas antara ExoPlayer dan DefaultPreloadManager dalam penyiapan itu sendiri.
val preloadManagerBuilder = DefaultPreloadManager.Builder(context, targetPreloadStatusControl) // Optional - Share components between ExoPlayer and DefaultPreloadManager preloadManagerBuilder .setBandwidthMeter(customBandwidthMeter) .setLoadControl(customLoadControl) .setMediaSourceFactory(customMediaSourceFactory) .setTrackSelectorFactory(customTrackSelectorFactory) .setRenderersFactory(customRenderersFactory) .setPreloadLooper(playbackLooper) val preloadManager = val preloadManagerBuilder.build()
Tips: Jika Anda menggunakan komponen Default di ExoPlayer seperti DefaultLoadControl, dll., Anda tidak perlu membagikannya secara eksplisit dengan DefaultPreloadManager. Saat Anda membuat instance ExoPlayer melalui buildExoPlayer dari DefaultPreloadManager.Builder, komponen ini akan otomatis direferensikan satu sama lain, jika Anda menggunakan implementasi default dengan konfigurasi default. Namun, jika Anda menggunakan komponen kustom atau konfigurasi kustom, Anda harus secara eksplisit memberi tahu DefaultPreloadManager tentang komponen atau konfigurasi tersebut melalui API di atas.
Pemuatan awal yang siap produksi: Pola jendela geser
Dalam feed dinamis, pengguna dapat men-scroll konten dalam jumlah yang hampir tak terbatas. Jika Anda terus menambahkan video ke DefaultPreloadManager tanpa strategi penghapusan yang sesuai, Anda pasti akan menyebabkan OutOfMemoryError. Setiap MediaSource yang sudah dimuat sebelumnya menyimpan SampleQueue, yang mengalokasikan buffer memori. Saat menumpuk, hal ini dapat menghabiskan ruang heap aplikasi. Solusinya adalah algoritma yang mungkin sudah Anda kenal, yang disebut jendela geser. Pola jendela geser mempertahankan sekumpulan kecil item yang mudah dikelola dalam memori yang secara logis berdekatan dengan posisi pengguna saat ini di feed. Saat pengguna men-scroll, "jendela" item terkelola ini akan bergeser bersama mereka, menambahkan item baru yang terlihat, dan juga menghapus item yang kini jauh.
Menerapkan pola jendela geser
Penting untuk dipahami bahwa PreloadManager tidak menyediakan metode setWindowSize() bawaan. Jendela geser adalah pola desain yang harus Anda, sebagai developer, terapkan menggunakan metode add() dan remove() primitif. Logika aplikasi Anda harus menghubungkan peristiwa UI, seperti scroll atau perubahan halaman, ke panggilan API ini. Jika Anda menginginkan referensi kode untuk hal ini, kami telah menerapkan pola jendela geser ini dalam contoh socialite yang juga menyertakan PreloadManagerWrapper yang meniru jendela geser.
Jangan lupa menambahkan preloadManager.remove(mediaItem) dalam penerapan Anda saat item tidak mungkin muncul lagi dalam waktu dekat di penayangan pengguna. Gagal menghapus item yang tidak lagi dekat dengan pengguna adalah penyebab utama masalah memori dalam penerapan pramuat. Panggilan remove() memastikan resource dilepaskan yang membantu Anda menjaga penggunaan memori aplikasi tetap terikat dan stabil.
Menyesuaikan strategi pra-pemuatan yang dikategorikan dengan TargetPreloadStatusControl
Setelah menentukan apa yang akan di-preload (item di jendela), kita dapat menerapkan strategi yang ditentukan dengan baik untuk jumlah yang akan di-preload untuk setiap item. Kita telah melihat cara mencapai perincian ini dengan penyiapan TargetPreloadStatusControl di Bagian 1.
Sebagai pengingat, item pada posisi +/- 1 dapat memiliki probabilitas yang lebih tinggi untuk diputar daripada item pada posisi +/- 4. Anda dapat mengalokasikan lebih banyak resource (jaringan, CPU, memori) ke item yang kemungkinan besar akan dilihat pengguna berikutnya. Hal ini akan membuat strategi "pemuatan awal" berdasarkan kedekatan, yang merupakan kunci untuk menyeimbangkan pemutaran langsung dengan penggunaan resource yang efisien.
Anda dapat menggunakan data analisis melalui PreloadManagerListener seperti yang dibahas di bagian sebelumnya untuk menentukan strategi durasi pramuat.
Kesimpulan dan langkah selanjutnya
Sekarang Anda telah dilengkapi dengan pengetahuan lanjutan untuk membuat feed media yang cepat, stabil, dan hemat resource menggunakan DefaultPreloadManager Media3.
Mari kita rangkum poin-poin penting:
- Gunakan PreloadManagerListener untuk mengumpulkan insight analisis dan menerapkan penanganan error yang andal.
- Selalu gunakan satu DefaultPreloadManager.Builder untuk membuat instance pengelola dan pemutar guna memastikan komponen penting dibagikan.
- Terapkan pola jendela geser dengan mengelola panggilan add() dan remove() secara aktif untuk mencegah OutOfMemoryError.
- Gunakan TargetPreloadStatusControl untuk membuat strategi pra-pemuatan bertingkat yang cerdas dan menyeimbangkan performa dan konsumsi resource.
Langkah selanjutnya di Bagian 3: Membuat cache dengan media yang telah dimuat sebelumnya
Memuat data sebelumnya ke dalam memori memberikan manfaat performa langsung, tetapi dapat menimbulkan kompromi. Setelah aplikasi ditutup atau media yang telah dimuat sebelumnya dihapus dari pengelola, data akan hilang. Untuk mencapai tingkat pengoptimalan yang lebih persisten, kita dapat menggabungkan pemuatan awal dengan caching disk. Fitur ini masih dalam pengembangan aktif dan akan segera hadir dalam beberapa bulan ke depan.
Apakah ada masukan yang ingin Anda sampaikan? Kami ingin mendengar pendapat Anda.
Nantikan info selengkapnya, dan percepat pemutaran video Anda. 🚀
Lanjutkan membaca
-
Berita Produk
Dalam aplikasi yang berfokus pada media saat ini, memberikan pengalaman pemutaran yang lancar dan tanpa gangguan adalah kunci untuk pengalaman pengguna yang menyenangkan. Pengguna berharap video mereka dimulai secara instan dan diputar dengan lancar tanpa jeda.
Mayuri Khinvasara Khabya • Waktu baca: 8 menit
-
Berita Produk
Setiap developer memiliki alur kerja dan kebutuhan AI yang unik, dan penting untuk dapat memilih cara AI membantu pengembangan Anda. Pada bulan Januari, kami memperkenalkan kemampuan untuk memilih model AI lokal atau jarak jauh guna mendukung fungsi AI di Android Studio
Matthew Warner • Waktu baca: 2 menit
-
Berita Produk
Android Studio Panda 3 kini stabil dan siap digunakan dalam produksi. Rilis ini memberi Anda lebih banyak kontrol dan penyesuaian atas alur kerja yang didukung AI, sehingga mempermudah pembuatan aplikasi Android berkualitas tinggi.
Matt Dyor • Waktu baca 3 menit
Terus dapatkan informasi
Dapatkan insight pengembangan Android terbaru yang dikirim ke kotak masuk Anda setiap minggu.