Berita Produk
Meningkatkan pemutaran media: Pembahasan mendalam tentang PreloadManager Media3 - Bagian 2
Waktu baca: 9 menit
Selamat datang di bagian kedua dari seri tiga bagian kami tentang pemuatan awal media dengan Media3. Seri ini dirancang untuk memandu Anda melalui proses pembuatan pengalaman media yang sangat responsif dan latensi rendah di aplikasi Android Anda.
- Bagian 1: Memperkenalkan Pemuatan Awal dengan Media3 membahas dasar-dasarnya. Kami membahas perbedaan antara PreloadConfiguration untuk playlist sederhana dan DefaultPreloadManager yang lebih canggih untuk antarmuka pengguna dinamis. Anda 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 merilis resource dengan remove() dan release().
- Bagian 2 (Postingan ini): Di blog ini, kita akan membahas kemampuan lanjutan DefaultPreloadManager. Kita akan membahas cara mendapatkan insight dengan PreloadManagerListener, menerapkan praktik terbaik 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, yang memungkinkan Anda mengurangi konsumsi data dengan pengelolaan resource dan memberikan pengalaman yang lancar.
Jika Anda baru menggunakan pemuatan awal di Media3, sebaiknya baca Bagian 1 sebelum melanjutkan. Bagi yang siap untuk melampaui dasar-dasarnya, mari kita bahas cara meningkatkan implementasi pemutaran media Anda.
Mendengarkan: Mengambil analisis dengan PreloadManagerListener
Saat ingin meluncurkan fitur dalam produksi, sebagai developer aplikasi, Anda juga ingin memahami dan mengambil 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 pemuatan awal.
- onCompleted(MediaItem mediaItem): Callback ini dipanggil setelah berhasil menyelesaikan permintaan pemuatan awal, seperti yang ditentukan oleh TargetPreloadStatusControl Anda.
- onError(PreloadException error): Callback ini dapat berguna untuk proses debug dan pemantauan. Callback ini dipanggil saat pemuatan awal gagal, dan 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 pemroses
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 pemuatan awal kita? (rasio peristiwa onCompleted terhadap total upaya pemuatan awal)
- CDN atau format video mana yang menunjukkan tingkat error tertinggi? (Dengan mengurai pengecualian dari onError)
- Berapa tingkat error pemuatan awal kita? (rasio peristiwa onError terhadap total upaya pemuatan awal)
Data ini dapat memberi Anda masukan kuantitatif tentang strategi pemuatan awal, yang memungkinkan pengujian A/B dan peningkatan berbasis data pada pengalaman pengguna Anda. Data ini dapat lebih membantu Anda untuk menyesuaikan durasi pemuatan awal dan jumlah video yang ingin Anda muat awal secara cerdas, 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 mengadaptasi UI. Misalnya, jika video yang akan datang gagal dimuat awal, aplikasi Anda dapat menonaktifkan putar otomatis untuk geser berikutnya, sehingga pengguna harus mengetuk untuk memulai pemutaran.
Selain itu, dengan memeriksa jenis PreloadException, Anda dapat menentukan strategi coba lagi 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 yang sesuai agar masalah pemuatan tidak bocor ke pengalaman pengguna. Anda juga bisa mendapatkan data yang lebih mendetail dari PreloadException seperti HttpDataSourceException untuk menyelidiki lebih lanjut error tersebut. Baca lebih lanjut tentang pemecahan masalah ExoPlayer.
Sistem pendukung: Mengapa berbagi komponen dengan ExoPlayer diperlukan?
DefaultPreloadManager dan ExoPlayer dirancang untuk bekerja sama. Untuk memastikan stabilitas dan efisiensi, keduanya harus berbagi beberapa komponen inti components. Jika beroperasi dengan komponen yang terpisah dan tidak terkoordinasi, hal ini dapat memengaruhi keamanan thread dan kegunaan trek yang dimuat awal di pemutar karena kita perlu memastikan bahwa trek yang dimuat awal 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 penghapusan yang sesuai. Urutan penghapusan yang direkomendasikan adalah merilis PreloadManager terlebih dahulu, diikuti oleh ExoPlayer.
DefaultPreloadManager.Builder dirancang untuk memfasilitasi berbagi ini dan memiliki API untuk membuat instance PreloadManager dan instance pemutar tertaut. Mari kita lihat mengapa komponen seperti BandwidthMeter, LoadControl, TrackSelector, Looper harus dibagikan. Lihat representasi visual tentang cara komponen ini berinteraksi dengan Pemutaran ExoPlayer.
Mencegah konflik bandwidth dengan BandwidthMeter bersama
BandwidthMeter memberikan perkiraan bandwidth jaringan yang tersedia berdasarkan kecepatan transfer historis. Jika PreloadManager dan pemutar menggunakan instance terpisah, keduanya tidak mengetahui aktivitas jaringan satu sama lain, yang dapat menyebabkan skenario kegagalan. Misalnya, pertimbangkan skenario saat pengguna menonton video, koneksi jaringannya menurun, dan MediaSource pemuatan awal secara bersamaan memulai download agresif untuk video mendatang. Aktivitas MediaSource pemuatan awal akan menggunakan bandwidth yang diperlukan oleh pemutar aktif, sehingga menyebabkan video saat ini terhenti. Penghentian selama pemutaran adalah kegagalan pengalaman pengguna yang signifikan.
Dengan berbagi satu BandwidthMeter, TrackSelector dapat memilih trek dengan kualitas tertinggi mengingat kondisi jaringan saat ini dan status buffer, selama pemuatan awal atau pemutaran. Kemudian, TrackSelector dapat membuat keputusan cerdas untuk melindungi sesi pemutaran aktif dan memastikan pengalaman yang lancar.
preloadManagerBuilder.setBandwidthMeter(customBandwidthMeter)
Memastikan konsistensi dengan komponen LoadControl, TrackSelector, Renderer ExoPlayer bersama
- 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 terkoordinasi di seluruh media yang dimuat awal dan yang diputar secara aktif, sehingga mencegah pertentangan resource. Anda harus mengalokasikan ukuran buffer secara cerdas dengan mengoordinasikan jumlah item yang Anda muat awal dan durasinya, untuk memastikan konsistensi. Saat terjadi pertentangan, pemutar akan memprioritaskan pemutaran item saat ini yang ditampilkan di layar. Dengan LoadControl bersama, pengelola pemuatan awal akan terus memuat awal selama byte buffer target yang dialokasikan untuk pemuatan awal belum mencapai batas atas, dan tidak menunggu hingga pemuatan untuk pemutaran selesai.
Catatan: Berbagi LoadControl di versi terbaru Media3 (1.8) memastikan bahwa Alokatornya dapat dibagikan dengan benar ke PreloadManager dan pemutar. Menggunakan LoadControl untuk mengontrol pemuatan awal 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 pemuatan awal adalah trek yang sama yang akan digunakan pemutar. Hal ini menghindari skenario yang boros saat trek video 480p dimuat awal, hanya agar pemutar segera menghapusnya dan mengambil trek 720p saat pemutaran.< br /> Pengelola pemuatan awal TIDAK boleh berbagi instance yang sama dari TrackSelector dengan pemutar. Sebagai gantinya, mereka harus menggunakan instance TrackSelector yang berbeda tetapi dari implementasi yang sama. Itulah sebabnya kita menetapkan TrackSelectorFactory, bukan TrackSelector di DefaultPreloadManager.Builder.
preloadManagerBuilder.setTrackSelectorFactory(customTrackSelectorFactory)
- Renderer: Komponen ini bertanggung jawab untuk memahami kemampuan pemutar tanpa membuat perender lengkap. Komponen ini memeriksa cetak biru ini untuk melihat format video, audio, dan teks mana yang akan didukung oleh pemutar akhir. Hal ini memungkinkan komponen tersebut memilih dan mendownload trek media yang kompatibel saja secara cerdas dan mencegah pemborosan bandwidth pada konten yang sebenarnya tidak dapat diputar oleh pemutar.
preloadManagerBuilder.setRenderersFactory(customRenderersFactory)
Baca komponen Exoplayer lainnya.
Aturan penting: Looper Pemutaran umum untuk menguasai semuanya
Thread tempat instance ExoPlayer dapat diakses dapat ditentukan secara eksplisit dengan meneruskan Looper saat membuat pemutar. Looper dari thread tempat pemutar harus diakses dapat dikueri menggunakan Player.getApplicationLooper. Dengan mempertahankan Looper bersama antara pemutar dan PreloadManager, dijamin bahwa semua operasi pada objek media bersama ini akan 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 awal harus terjadi pada thread pemutaran yang sama. Berbagi Looper adalah keharusan untuk keamanan thread, sehingga kita harus berbagi PlaybackLooper antara PreloadManager dan pemutar.
PreloadManager menyiapkan objek MediaSource stateful di latar belakang. Saat kode UI Anda memanggil player.setMediaSource(mediaSource), Anda melakukan serah terima objek stateful yang kompleks ini dari MediaSource pemuatan awal ke pemutar. Dalam skenario ini, seluruh PreloadMediaSource dipindahkan dari pengelola ke pemutar. Semua interaksi dan serah terima ini harus terjadi di PlaybackLooper yang sama.
Jika PreloadManager dan ExoPlayer beroperasi pada thread yang berbeda, kondisi race dapat terjadi. Thread PreloadManager dapat mengubah status internal MediaSource (misalnya, menulis data baru ke dalam buffer) pada saat yang sama 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 berbagi 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 ke 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 tersebut melalui API di atas.
Pemuatan awal 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 dimuat awal menyimpan SampleQueue, yang mengalokasikan buffer memori. Saat terakumulasi, buffer ini dapat menghabiskan ruang heap aplikasi. Solusinya adalah algoritma yang mungkin sudah Anda kenal, yang disebut jendela geser. Pola jendela geser mempertahankan kumpulan item kecil dan mudah dikelola dalam memori yang secara logis berdekatan dengan posisi pengguna saat ini dalam feed. Saat pengguna men-scroll, "jendela" item yang dikelola ini akan bergeser bersama pengguna, menambahkan item baru yang muncul, dan juga menghapus item yang sekarang jauh.
Mengimplementasikan pola jendela geser
Penting untuk memahami bahwa PreloadManager tidak menyediakan metode setWindowSize() bawaan. Jendela geser adalah pola desain yang harus Anda, sebagai developer, implementasikan 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 memiliki pola jendela geser ini yang diimplementasikan dalam socialite contoh yang juga menyertakan PreloadManagerWrapper yang meniru jendela geser.
Jangan lupa untuk menambahkan preloadManager.remove(mediaItem) dalam implementasi Anda jika item tersebut tidak mungkin muncul lagi dalam waktu dekat di tampilan pengguna. Kegagalan menghapus item yang tidak lagi dekat dengan pengguna adalah penyebab utama masalah memori dalam implementasi pemuatan awal. Panggilan remove() memastikan resource dirilis yang membantu Anda menjaga penggunaan memori aplikasi tetap terikat dan stabil.
Menyesuaikan strategi pemuatan awal yang dikategorikan dengan TargetPreloadStatusControl
Setelah menentukan apa yang akan dimuat awal (item di jendela kita), kita dapat menerapkan strategi yang ditentukan dengan baik untuk jumlah yang akan dimuat awal untuk setiap item. Kita telah melihat cara mencapai granularitas ini dengan penyiapan TargetPreloadStatusControl di Bagian 1.
Sebagai pengingat, item di posisi +/- 1 dapat memiliki probabilitas yang lebih tinggi untuk diputar daripada item di posisi +/- 4. Anda dapat mengalokasikan lebih banyak resource (jaringan, CPU, memori) ke item yang kemungkinan besar akan dilihat pengguna berikutnya. Hal ini 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 pemuatan awal.
Kesimpulan dan langkah selanjutnya
Anda kini memiliki pengetahuan lanjutan untuk membuat feed media yang cepat, stabil, dan hemat resource menggunakan DefaultPreloadManager Media3.
Mari kita rangkum poin-poin pentingnya:
- Gunakan PreloadManagerListener untuk mengumpulkan insight analisis dan menerapkan penanganan error yang kuat.
- Selalu gunakan satu DefaultPreloadManager.Builder untuk membuat instance pengelola dan pemutar Anda guna memastikan komponen penting dibagikan.
- Implementasikan pola jendela geser dengan mengelola panggilan add() dan remove() secara aktif untuk mencegah OutOfMemoryError.
- Gunakan TargetPreloadStatusControl untuk membuat strategi pemuatan awal bertingkat yang cerdas yang menyeimbangkan performa dan konsumsi resource.
Langkah selanjutnya di Bagian 3: Membuat cache dengan media yang dimuat awal
Memuat awal data ke dalam memori memberikan manfaat performa langsung, tetapi dapat menimbulkan kompromi. Setelah aplikasi ditutup atau media yang dimuat awal dihapus dari pengelola, data akan hilang. Untuk mencapai tingkat pengoptimalan yang lebih persisten, kita dapat menggabungkan pemuatan awal dengan pembuatan cache disk. Fitur ini sedang dalam pengembangan aktif dan akan segera hadir dalam beberapa bulan.
Apakah Anda memiliki masukan untuk dibagikan? Kami ingin mendengar masukan dari Anda.
Tetap ikuti perkembangan, dan buat pemutaran video Anda lebih cepat. 🚀
Lanjutkan membaca
-
Berita Produk
Di aplikasi yang berpusat 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
Dengan dasar ini, Jetpack Telecom v1.1.0 menghadirkan visibilitas dan kemudahan tingkat native ke aplikasi VoIP pihak ketiga.
Nataraj K R • Waktu baca: 2 menit
-
Berita Produk
Diumumkan hari ini selama The Android Show, Android bertransisi dari sistem operasi menjadi sistem kecerdasan, sehingga menciptakan lebih banyak peluang untuk interaksi dengan aplikasi Anda.
Matthew McCullough • Waktu baca: 4 menit
Terus dapatkan informasi
Dapatkan insight pengembangan Android terbaru yang dikirim ke kotak masuk Anda setiap minggu.