Konsumsi media menyumbang sebagian besar waktu yang dihabiskan pengguna di perangkat seluler, yang memerlukan pengalaman pemutaran yang sempurna, sangat efisien, dan memperhatikan masa pakai baterai. Selain itu, pengguna mengharapkan pengalaman ini dapat beralih dengan lancar di seluruh perangkat mereka, mulai dari ponsel hingga TV di ruang keluarga dan lainnya.
Pelajari cara update arsitektur di Media3 dan ExoPlayer mendukung pemutaran yang lancar di berbagai faktor bentuk. Diskusi ini berfokus pada pembuatan antarmuka responsif dengan toolkit Media3 Compose UI, menghilangkan buffering di feed menggunakan PreloadManager baru, dan memperluas aplikasi Anda ke Living Room dan Android XR dengan CastPlayer dan audio spasial.
Transkrip
Oke.
Selamat datang semuanya.
Nama saya Toni.
Saya adalah engineer di tim media Android.
Bersama Kristina dan Donovan, kami akan membahas cara Jetpack Media3 dapat membantu Anda memenuhi semua kebutuhan media di Android.
Media3 adalah support library Android untuk semua hal yang terkait dengan media, baik itu pemutaran dengan ExoPlayer, berbagi media dengan sesi media dan Cast, atau mengedit media dengan transformer, atau memeriksa media, seperti mengekstrak frame atau metadata.
Media3 mencakup seluruh ruang ini dalam satu grup library.
Sebagai library dukungan, library ini dipaketkan ke dalam aplikasi Anda.
Artinya, kami dapat memastikan konsistensi, sehingga kode yang sama berjalan di semua versi OS dan semua perangkat.
Hal ini juga membantu kapasitas update.
Setiap perbaikan bug yang kami lakukan menjangkau hingga API 24, dan tidak terkait dengan siklus rilis OS apa pun.
Dan sebagai library dukungan, kita dapat mengizinkan opsi penyesuaian yang sangat beragam, yang tidak mungkin dilakukan di framework Android.
Sebagai library di GitHub, kami mengutamakan open source.
Artinya, setiap perubahan yang kita lakukan akan langsung tersedia di GitHub.
Kami memiliki issue tracker yang aktif, dan kami menerima permintaan pull.
Hal utama yang perlu ditekankan di sini, di semua kasus penggunaan ini, kami sangat memperhatikan semua tingkat detail, mulai dari elemen UI tingkat tinggi hingga API siap pakai yang sederhana, tetapi juga opsi penyesuaian yang mendalam, dukungan yang luas untuk berbagai format dan standar, serta performa dan efisiensi yang mendasari semuanya.
Media3 mencakup seluruh ruang ini.
Hari ini, kami mencoba membahas beberapa topik ini secara lebih mendalam.
Kita akan membahas modul Compose UI baru, CastPlayer baru yang memudahkan berbagi dengan sesi media, pipeline pengeditan yang diperbarui dengan efek lanjutan baru, alat baru untuk mengambil metadata dan frame, beberapa teknologi streaming baru, khususnya, video dan audio Eclipsa, serta sejumlah utilitas kualitas pengalaman untuk pramuat, scrubbing, dan penghematan baterai.
Sekarang, mari kita langsung membahas topik pertama, yaitu cara Media3 dapat membantu membangun elemen UI untuk media.
Kami baru-baru ini memublikasikan dua modul baru: Media3 UI Compose dan Media3 UI Compose Material 3.
Di sini, Anda dapat melihat composable pemutar tingkat tinggi, seperti yang diluncurkan di Media3 1.10 tanpa modifikasi lebih lanjut.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Dapat langsung digunakan.
Hanya memerlukan pemutar Media3.
Dalam contoh di sini, ExoPlayer, yang biasanya ada dalam model tampilan Anda.
Namun, bisa juga berupa hal lain, seperti CastPlayer, pengontrol media, atau pemutar lain yang Anda miliki di aplikasi.
Perlu ditekankan bahwa ExoPlayer di sini adalah sumber kebenaran.
Composable ini berisi semua informasi, dan composable pemutar menangani rendering UI dan menghubungkan semuanya.
Namun, bagaimana jika Anda ingin menyesuaikan UI ini, tombol baru, posisi yang berbeda, gaya yang berbeda, overlay teks?
Composable pemutar terdiri dari elemen penyusun.
Anda dapat menggantinya.
Anda dapat menyesuaikan setiap blok, atau menggunakannya secara mandiri di tempat lain dalam aplikasi Anda.
Misalnya, Anda dapat menyesuaikan bagian atas, tengah, dan bottomControls secara terpisah.
Anda juga dapat menyesuaikan tampilan shutter, yang ditampilkan saat video tidak diputar, atau overlay error.
Namun, ini baru lapisan pertama.
Slot tengah default dan bottomControls juga memiliki slot yang lebih mendetail yang dapat Anda sesuaikan.
Misalnya, Anda dapat menukar tombol tengah sambil membiarkan elemen lainnya tetap seperti semula, atau menambahkan elemen di atas atau di bawah penggeser.
Media3 Compose menyediakan semua elemen penyusun yang Anda perlukan untuk melakukannya.
Misalnya, kita memiliki sejumlah tombol, tombol putar/jeda, mode ulang, mode acak, tombol bisukan, dan lainnya.
Kemudian, ada indikator progres, seperti penggeser dan teks posisi yang ditampilkan di layar.
Ada juga sejumlah penampung dan overlay.
Composable pemutar tingkat tinggi yang telah kami tunjukkan adalah salah satu contohnya, tetapi ada lebih banyak contoh, seperti overlay error atau subtitel, dan banyak lagi.
Selain itu, kami memiliki komponen berguna lainnya, seperti pengontrol mini ini.
Anda dapat menempatkannya di panel bawah saat pengguna menjelajahi aplikasi Anda.
Dan kami belum selesai.
Kami secara aktif memperluas koleksi ini lebih lanjut, misalnya, dengan kontrol pengeditan baru seperti penggeser klip yang akan segera hadir.
Sekarang, mari kita lihat penerapan penyesuaian ini untuk melihat cara penggunaan elemen penyusun ini.
Player(viewModel.exoPlayer)Misalnya, kita ingin memindahkan teks posisi dan durasi, yang saat ini berada di samping penggeser, ke atasnya.
Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Jadi, kita dapat mengganti bottomControls ini.
Kita masih dapat menggunakan komponen default, tetapi kita menetapkan slot kiri dan kanan ke kosong untuk membuatnya menghilang.
Sebagai gantinya, kita menempatkan teks posisi dan durasi di atasnya di slot atas dengan pemisah kustom agar terlihat lebih menarik.
Sekarang, misalnya kita ingin menghapus overlay centerControls sepenuhnya dan, sebagai gantinya, menempatkan tombol putar tepat di atas penggeser dengan kontrol kecepatan dan tombol bisukan.
Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Dan itu adalah ide yang sama persis.
Kita dapat menyetel centerControls ke kosong untuk menghilangkannya, dan kita dapat memperluas slot di atas penggeser dengan beberapa ruang tambahan dan tiga tombol yang ingin kita tampilkan.
Perhatikan bahwa tombol ini berasal dari library itu sendiri, jadi Anda tidak perlu memasukkan kode tambahan ke dalamnya.
Itu saja yang Anda perlukan.
Anda juga dapat melanjutkan langkah ini untuk menata gaya elemen-elemen tersebut.
Jika Anda ingin mengubah ikon tombol bisukan, misalnya, tetapi tetap mempertahankan fungsinya, cara kerjanya juga sama.
Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)Seperti yang Anda lihat di sini, Anda dapat menentukan painter kustom untuk tombol dan memilih ikonnya sendiri.
Komponen ini menyediakan beberapa metadata, jadi Anda hanya perlu melihat status, seperti showMuted dalam contoh ini, untuk memilih ikon yang tepat.
Dan ini adalah manfaat utama Media3 Compose.
Anda dapat berfokus pada tampilan dan nuansa aplikasi, dan Media3 akan menangani sisanya.
Class ini membaca status dari pemutar, berinteraksi dengan pemutar, dan menangani semua transisi dan animasi di antaranya.
Sekarang, mari kita beralih dari UI ke berbagi media.
Anda dapat membagikan status pemutaran kepada aplikasi lain di perangkat yang sama atau yang terhubung ke perangkat jarak jauh.
Cara utama untuk memublikasikan pemutaran yang sedang berlangsung adalah dengan MediaSession.
Hal ini membuat status dan metadata pemutaran Anda tersedia untuk orang lain dan memungkinkan mereka mengontrol pemutaran Anda.
Dengan cara inilah Gemini, notifikasi media UI sistem, Cast, Wear OS, atau aplikasi pendamping Android Auto berkomunikasi dengan aplikasi Anda.
MediaSession menggabungkan pemutar Media3, dan sangat mudah untuk membuatnya setelah Anda memiliki pemutar.
MediaSession.Builder(context, player).build()Sekali lagi, ini bisa berupa ExoPlayer atau pemutar lain yang Anda miliki di aplikasi, sama seperti Compose UI.
Jika Anda memutar di latar belakang, Anda biasanya akan menghostingnya di dalam MediaSessionService atau MediaLibraryService, yang juga otomatis menangani semua siklus proses untuk Anda dan hal-hal seperti notifikasi.
class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}Namun, jika Anda memiliki aplikasi media, Anda mungkin sudah mengetahui semua ini.
Hal baru yang menarik yang ingin kami tunjukkan kepada Anda adalah CastPlayer yang baru ditulis ulang.
Google Cast memungkinkan Anda mentransfer pemutaran lokal ke perangkat jarak jauh, seperti Android TV, Chromecast, atau smart speaker.
Kami biasa memublikasikan Cast SDK terpisah, yang kini dalam mode pemeliharaan.
Sebagai gantinya, Media3 Cast menggantikannya sepenuhnya karena menyediakan integrasi yang jauh lebih sederhana dan efisien.
Elemen terpenting di sini adalah CastPlayer, yang mengimplementasikan antarmuka pemutar standar yang sama seperti ExoPlayer.
Meskipun ExoPlayer ditujukan untuk pemutaran lokal, CastPlayer adalah instance delegasi yang dapat diputar secara lokal melalui ExoPlayer atau dari jarak jauh melalui protokol Cast.
Dan semuanya akan diurus.
API ini memproses perubahan perangkat output, membuat koneksi, menyinkronkan status, dan mentransfer pemutaran untuk Anda saat diperlukan.
Oleh karena itu, mengintegrasikan CastPlayer ke dalam aplikasi Anda sangatlah mudah.
val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Daripada membuat ExoPlayer dan membagikannya dengan MediaSession seperti ini, Anda menggunakan CastPlayer, dan selesai.
Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Satu-satunya hal lain yang perlu Anda lakukan adalah melakukan inisialisasi sistem Cast sebelum digunakan di suatu tempat di aplikasi Anda.
Perhatikan bahwa CastPlayer ini membangun ExoPlayer secara internal untuk pemutaran lokal, jadi untuk menyesuaikan pemutar lokal lebih lanjut, Anda juga dapat membangun ExoPlayer Anda sendiri dan meneruskannya sebagai pemutar lokal ke CastPlayer ini.
Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Dan ini adalah prinsip Media3 yang sama.
Kami menyediakan elemen penyusun API siap pakai yang sederhana, tetapi juga semua opsi penyesuaian yang Anda perlukan untuk menyesuaikannya lebih lanjut.
Anda mungkin bertanya-tanya bagaimana pengguna beralih antara pemutaran lokal dan jarak jauh dalam penyiapan ini.
Fitur ini sudah langsung berfungsi dengan dialog pengalih output sistem di notifikasi media.
Tombol kecil di pojok itu.
Namun, bagaimana jika Anda ingin menampilkan tombol transmisi di layar pemutaran agar lebih mudah ditemukan?
Ingat UI Compose yang disesuaikan ini yang baru saja kita buat beberapa menit yang lalu?
Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Media3 Cast juga menyediakan tombol siap pakai yang dapat Anda tempatkan di sana.
Caranya semudah menambahkan MediaRouteButton yang sudah siap pakai ke topControls, misalnya, dan kita tidak perlu menyiapkan parameter lebih lanjut, itu saja.
Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)Dan mengklik tombol ini akan membuka dialog pengalih output sistem yang sama, lalu CastPlayer akan mentransfer pemutaran untuk Anda.
Dan Anda juga perlu memperhatikan bahwa saya menghubungkan CastPlayer ke UI di sini untuk memastikan apa pun yang terjadi dari jarak jauh akan ditampilkan di UI lokal Anda.
Selanjutnya, saya akan menyerahkan kepada Kristina untuk membahas kasus penggunaan pengeditan.
Terima kasih.
Halo, sekarang mari kita beralih ke pengeditan dan efek lanjutan.
API Media3 terdiri dari beberapa komponen.
Pertama, Transformer adalah titik entri utama untuk API pengeditan kami.
Transformer memproses, melakukan transcoding, dan mengekspor media ke file.
Berikutnya adalah CompositionPlayer, yang merender pratinjau real-time komposisi media Anda.
Dengan begitu, Anda dapat melihat pratinjau dan menyesuaikan hasil edit sebelum ekspor akhir.
Kemudian, kita memiliki efek.
Fitur ini memberi Anda kontrol penuh atas pemrosesan frame demi frame setiap kali Anda menerapkan efek visual sederhana atau menambahkan overlay grafis.
Terakhir, kita memiliki Muxer.
Layanan ini berfungsi sebagai tahap output akhir dalam pipeline kami, yang mengambil trek audio, video, dan metadata yang telah diproses, lalu menulisnya ke dalam container MP4 standar.
Selama setahun terakhir, kami telah memikirkan ulang dan membangun kembali pipeline pengeditan video kami dari awal, dan hari ini, kami sangat senang dapat membagikan peningkatan besar ini.
Mengapa kami membangun ulang pipeline?
Jadi, arsitektur lama menghadirkan beberapa tantangan.
Pertama, sistem ini tidak dapat menyebarkan metadata di semua langkah pemrosesan.
Sistem ini sangat terkait dengan OpenGL, yang membatasi fleksibilitas untuk alat grafis modern.
Selain itu, API-nya yang kompleks membuat penyesuaian dan pemeliharaan menjadi tantangan berat bagi developer.
Untuk mengatasi tantangan ini, kami membangun pipeline pemrosesan yang benar-benar baru.
Pertama, menyusun komposisi multi-aset yang kompleks kini jauh lebih mudah.
Kedua, dengan memisahkan dari OpenGL, Anda memiliki kebebasan penuh untuk menggunakan framework grafis modern, seperti WebGPU, misalnya.
Terakhir, Media3 kini mengelola dekoder dan encoder secara langsung, sehingga memastikan konsistensi dan efisiensi yang lebih baik di semua perangkat.
Mari kita lihat cara kerja pipeline ini.
Decoder menghasilkan frame langsung ke buffer hardware yang dikelola oleh Media3.
Frame Aggregator mengoordinasikan frame di beberapa urutan, sehingga memberikan akses langsung kepada pemroses frame kustom Anda untuk membaca dan memproses buffer tersebut sebelum mengirimkannya ke encoder.
Antarmuka FrameProcessor berfungsi sebagai titik ekstensi utama untuk pemrosesan tingkat aplikasi.
public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}Melalui antarmuka pemroses, aplikasi Anda memberi tahu Media3 setiap kali frame telah diproses, dengan meneruskan fence sinkronisasi, sehingga Media3 tahu persis kapan buffer dapat digunakan kembali dengan aman.
Catatan tentang memori terpadu: dengan meminta Media3 mengambil kepemilikan buffer hardware, aplikasi Anda dapat membaca dan mengubah data secara langsung di tempat.
Pendekatan zero-copy ini mengurangi overhead memori dan menjaga pemrosesan tetap sangat cepat.
Salah satu developer yang memanfaatkan API pengeditan baru adalah Adobe.
Tim Adobe Premiere menggunakan API pengeditan Media3 sebagai fondasi infrastruktur pengeditan media mereka untuk memastikan kompatibilitas yang lebih baik di seluruh ekosistem Android, sekaligus mempertahankan fleksibilitas yang mereka perlukan untuk menggunakan pipeline kustom dan WebGPU mereka sendiri.
Selanjutnya, dengan senang hati kami umumkan bahwa kami merilis efek baru sebagai bagian dari rilis Media3 1.12.
Mulai dengan peningkatan kualitas video.
Di balik layar, arsitektur kami mengandalkan penyiapan modular.
Aplikasi Anda terintegrasi dengan Layanan Play efek Media3, yang berinteraksi dengan modul Peningkatan Layanan Play.
Modul Video Enhance mencakup kemampuan pemetaan warna, penghilang noise, penghilang blur, dan resolusi super.
Mengintegrasikan Video Enhance ke dalam pipeline Anda hanya memerlukan beberapa baris kode.
val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)Dengan menggunakan EnhancementSessionDecorator.Builder, Anda dapat mengaktifkan kemampuan secara selektif seperti pemetaan warna, penghilang blur, atau peningkatan skala, dan meneruskan frameProcessorFactory dan helper buffering hardware yang dihasilkan ke Transformer.Builder.
Efek menarik lainnya yang akan hadir di Media3 1.12 adalah Suara Studio.
Studio Sound otomatis menganalisis trek audio video Anda dan memisahkannya menjadi beberapa stem, seperti ucapan, suara bising di sekitar, dan musik.
Kreator dapat dengan mudah mengurangi gangguan angin atau keramaian sambil menonjolkan dialog dan musik yang jelas.
Untuk mengintegrasikan Suara Studio, teruskan pemroses audio Suara Studio di dalam objek efek.
val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()Tindakan ini akan melampirkan pemroses audio ke item media Anda.
Saat membuat pemroses audio Suara Studio, lampirkan callback pemroses untuk menerima pemetaan saluran audio yang dianalisis melalui onResult.
val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)Lakukan iterasi pada setiap saluran untuk mengekstrak indeks saluran, jenis audio yang diidentifikasi, dan label yang dapat dibaca saat hasil operasi tersedia.
Jadi, mari kita dengarkan Suara Studio dalam tindakan.
Jadi, efek pertama. Sayang, jangan menangis.
Kami akan memotongnya untuk makan malam Anda.
Anda dapat mendengar suara bayi menangis di latar belakang, dan sekarang dengan efek yang diterapkan.
Ya, seperti yang Anda dengar, tidak ada suara bayi menangis di latar belakang berkat efek Suara Studio.
Sekarang, mari kita pelajari pengambilan metadata.
Kita akan membahas cara Media3 memudahkan Anda membaca metadata container dan mengekstrak frame.
Mulai dari MetadataRetriever, library ini terintegrasi lancar dengan coroutine Kotlin untuk kode yang bersih dan tidak memblokir.
Desainnya lebih bersih dan tidak rentan terhadap error, menggunakan satu pengambil yang tidak dapat diubah per item media.
Library ini berfungsi secara konsisten di semua versi API dan perangkat.
Dan mendukung berbagai format media secara langsung.
Berikut adalah contoh praktis menggunakan MetadataRetriever.
MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}Dengan coroutine Kotlin, Anda dapat mengambil durasi secara asinkron, memeriksa format trek dan metadata, semuanya dalam satu blok yang rapi.
Sekarang, mari kita bahas FrameExtractor.
Fitur ini membuat pengambilan frame video yang didekode dari item media menjadi cepat dan efisien.
Transformer mengelola resource pokok seperti decoder secara otomatis, memungkinkan Anda menerapkan efek visual atau pemetaan nuansa HDR, dan memberi Anda kontrol pencarian yang mendetail sehingga Anda bisa mendapatkan frame yang tepat sesuai kebutuhan.
Berikut adalah tampilan ekstraksi frame dalam kode.
FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}Anda membuat instance dengan opsi seperti parameter pencarian, downscaling GPU, efek, dan dukungan HDR, lalu menunggu thumbnail dan frame tertentu secara asinkron menggunakan panggilan penangguhan yang bersih.
Sekarang, giliran Donovan.
Terima kasih, Kristina.
Selanjutnya, kami ingin membahas beberapa dukungan teknologi streaming dasar yang telah kami tambahkan di Media3, termasuk dukungan untuk format seperti Eclipsa Video dan Eclipsa Audio.
Jadi, upaya inti di Media3 adalah untuk tetap mengikuti standar streaming sehingga setiap kali ada spesifikasi baru, Anda tidak perlu menulis ulang pipeline media.
Berikut beberapa contoh hal yang baru-baru ini kami luncurkan.
Kami memiliki pengarahan konten, yang memungkinkan peralihan jalur CDN backend secara dinamis di tengah streaming.
Kami memiliki dukungan untuk penyisipan iklan yang dipandu server untuk penggabungan iklan yang dipersonalisasi dengan lancar.
Kami juga telah memperkenalkan dukungan AV1 di beberapa titik entri, termasuk dukungan HDR AV1 penuh.
Kami memiliki dukungan untuk video profesional Android untuk perekaman dan pengeditan fidelitas tinggi.
Kami juga menambahkan dukungan untuk foto motion (foto dengan gambar bergerak) dalam format JPEG dan HEIC.
Dan tentu saja, kami menambahkan dukungan untuk Eclipsa Video dan Eclipsa Audio, yang akan saya bahas lebih lanjut nanti.
Kabar baiknya, semua ini didukung secara otomatis seiring peluncuran dukungan perangkat.
Sekarang, mari kita lihat evolusi HDR berikutnya, Eclipsa Video, dan masalah inti yang ingin dipecahkannya.
Jadi, format HDR sebelumnya menyediakan metadata deskriptif, sehingga menentukan tampilan adegan, tetapi tidak benar-benar memberi tahu layar cara merendernya.
Jadi, mari kita jelaskan hal ini terlebih dahulu.
Untuk lingkungan yang berbeda, ada rekomendasi yang berbeda untuk kecerahan layar rata-rata agar Anda dapat melihat konten dengan jelas dan nyaman.
Jadi, di ruangan redup, rentang tersebut akan berada di antara 100 dan 300 nit.
Kemudian, tentu saja, saat Anda berada di lingkungan yang makin terang, layar harus mengimbanginya dan menjadi lebih terang agar Anda dapat melihat konten dengan jelas, hingga saat Anda melihat ponsel di bawah sinar matahari langsung, layar mungkin perlu mencapai 1.000, 2.000, atau bahkan lebih banyak nit agar Anda dapat melihat konten.
SDR tradisional beroperasi dalam rentang 100 hingga 300.
Tentu saja, HDR tidak dimaksudkan untuk mencerahkan layar secara global.
Anda ingin memperluas rentang dinamis, sehingga Anda memiliki sorotan yang dilokalkan untuk hal-hal seperti api dan sinar matahari.
Namun, smartphone menimbulkan beberapa kerumitan, karena kita membawanya ke mana saja, ke ruangan gelap di rumah, atau ke pantai yang cerah.
Hal itu, ditambah dengan fakta bahwa banyak aplikasi menggabungkan elemen UI SDR dengan konten SDR dan konten HDR dalam feed campuran, dapat menyebabkan efek yang mengganggu dan menyilaukan yang terkadang Anda dapatkan, terutama jika Anda men-scroll di malam hari.
Jadi, kita bisa melihat mengapa HDR sangat bermanfaat, bukan?
Hal ini membuat konten kita terlihat bagus di lebih banyak lingkungan.
Kami bahkan melihat peningkatan engagement pengguna saat HDR diaktifkan.
Namun, masalahnya adalah format HDR yang ada tidak mempertimbangkan lingkungan ini.
Di sinilah Eclipsa Video benar-benar unggul.
Menunggu tawa.
Terima kasih.
Terima kasih.
Jadi, alih-alih metadata deskriptif, Eclipsa Video menyediakan metadata preskriptif.
Dan hal itu benar-benar dibangun di atas tiga pilar utama.
Pilar pertama adalah anchor putih referensi.
Jadi, setelan ini mengunci kecerahan standar ke titik putih SDR yang disesuaikan dengan kondisi ruangan, sehingga menghilangkan lonjakan kecerahan yang tiba-tiba.
Pilar kedua adalah dasar SDR kreator.
Hal ini mempertahankan kurva pemetaan nuansa yang tepat dari kreator kembali ke SDR.
Ketiga, kami memiliki kurva penguatan adaptif headroom, yang secara deterministik menskalakan highlight agar sesuai dengan headroom HDR real-time layar sehingga konten sesuai dengan lingkungan tempat konten dilihat.
Fitur ini tersedia di ExoPlayer secara default di Media3 1.11 pada perangkat yang menjalankan Android 17 dan yang lebih baru.
Dan Eclipsa Video dibangun di atas standar terbuka untuk kompatibilitas lintas industri.
Jadi, sekarang adalah waktu yang tepat untuk mendukung HDR.
Di sisi audio, kami memiliki Eclipsa Audio, yang merupakan format audio spasial 3D open source dan bebas royalti.
Format ini didasarkan pada spesifikasi IAMF Alliance for Open Media.
Format ini menggabungkan Ambisonics berbasis adegan dengan audio berbasis saluran ke dalam satu streaming dengan beberapa presentasi campuran.
Jadi, hal ini memungkinkan manipulasi seperti objek, untuk hal-hal seperti peningkatan dialog, dan akan berfungsi pada semua hal, mulai dari headphone stereo hingga penyiapan suara surround.
Sebenarnya ada dua jalur pemutaran di Media3.
Untuk Android 17 dan yang lebih baru, ExoPlayer dan Android Audio Framework akan otomatis merender Eclipsa Audio.
Dan untuk Android 16 dan versi sebelumnya, Anda masih memiliki opsi.
Anda dapat mengaktifkan ekstensi IAMF melalui library add-on Media3 opsional.
Dan di sisi pembuatan, Anda dapat menulis trek audio IAMF menggunakan muxer Media3.
Namun, mendukung format berkualitas tinggi hanyalah sebagian dari keseluruhan cerita.
Kita harus memberikan kualitas tersebut secara efisien.
Sekarang, kami ingin membahas cara Media3 membuat pipeline kami lebih cepat dan efisien.
Seperti banyak hal yang telah kita bahas hari ini, sebagian besar peningkatan performa ini tersedia secara langsung hanya dengan terus mengupdate versi Media3 Anda.
Salah satu contoh fitur yang kami miliki adalah PCM audio offload, yang dapat mengarahkan pemutaran audio ke unit pemrosesan berdaya rendah.
Dalam analisis awal, kami melihat penghematan daya hingga 20% untuk pemutaran audio saat layar nonaktif.
Selain offload audio PCM, kami memiliki penjadwalan dinamis, yang menyelaraskan pengaktifan loop rendering internal ExoPlayer dengan VSYNC dan ketersediaan buffer dekoder.
Kami juga memiliki pra-pemanasan perender, dan ini memungkinkan Anda melakukan pra-pemanasan perender video sekunder, yang akan memungkinkan transisi playlist instan dan penyambungan iklan yang lebih mudah.
Kami juga memiliki deteksi pemutar macet dan pra-cache, yang otomatis mendeteksi buffer dan perender yang terhenti serta membatasi memori yang di-pra-cache dengan rapi.
Mode optimal lain yang kami miliki adalah mode scrubbing.
Dan ini untuk aplikasi tempat pengguna Anda sering men-scrub konten.
Jadi, saat Anda mengaktifkan mode scrubbing, menarik seekbar akan jauh lebih responsif dan lancar, seperti yang dapat Anda lihat di sini.
Ngomong-ngomong, ini dengan mode scrubbing nonaktif.
Dan di sebelah kanan, kita melihat mode scrubbing aktif.
Terima kasih.
Terima kasih.
Oke.
Sekarang, mari kita bahas Kesehatan Sistem Android.
Kita harus menghormati resource RAM, CPU, dan baterai bersama. Jadi, saat Android memperketat aturannya terkait Penguatan Audio dan layanan latar depan, aplikasi yang mengelola layanan mentah dan bitmap-nya sendiri sering kali mengalami beberapa error.
Beberapa hal yang mungkin pernah Anda lihat adalah pengecualian layanan latar depan yang tidak diizinkan untuk dimulai atau pengecualian binder kehabisan memori.
Jadi, kami telah memperkuat sesi layanan media dan pengelola notifikasi media serta pipeline bitmap kami dalam beberapa cara, termasuk mencegah intent layanan latar depan yang usang dan membatasi memori gambar album dan hal-hal semacam itu.
Namun, kesimpulannya adalah jika Anda menggunakan Media3 dan mengikuti panduan standar, semua batasan kesehatan sistem ini akan ditangani untuk Anda tanpa dampak.
Terakhir, saya ingin membahas fitur yang akan segera hadir di Media3.
Saat tiba waktunya untuk mengenkode atau mengekspor video, keragaman hardware dapat menjadi salah satu tantangan terbesar.
Banyak aplikasi menggunakan konfigurasi codec standar, yang menyebabkan kualitas dan performa yang tidak optimal.
Jadi, itulah alasan kami membuat CodecDB.
CodecDB dibuat berdasarkan pengukuran video dunia nyata yang ekstensif menggunakan metrik kualitas standar dan tolok ukur hardware di seluruh perangkat Android.
Mari kita lihat penerapannya.
Di sebelah kiri, kita memiliki frame video yang diambil di ponsel menggunakan konfigurasi video yang sangat umum yang mungkin kita gunakan di banyak perangkat.
Dan di sebelah kanan, kami menggunakan konfigurasi optimal tingkat perangkat yang kami dapatkan dari CodecDB.
Jadi, apa perbedaannya?
Jika Anda melihat area yang sangat mendetail, Anda akan melihat perbedaan kualitas terbesar.
Jadi, lihat kejelasan pada cabang pohon dan semak atau ketajaman tepi bangunan.
Jadi, yang kita dapatkan di sini adalah pengalaman menonton yang jauh lebih baik yang memanfaatkan kemampuan perangkat tertentu dengan memanfaatkan CodecDB.
Dan menggunakan CodecDB dalam kode cukup sederhana.
val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()Anda akan menggunakan pola builder tahap yang lancar yang memandu Anda melalui setiap parameter yang diperlukan.
Jadi, Anda akan menentukan kasus penggunaan Anda.
Di sini kita menggunakan Gemini untuk mengedit.
Hal ini memberi tahu CodecDB bahwa Anda menggunakan pipeline ekspor atau transcoding Media3.
Beberapa kasus penggunaan lain yang dapat Anda tetapkan, misalnya, adalah pengambilan gambar kamera jika itu yang Anda gunakan.
Anda juga akan meneruskan resolusi dan kecepatan frame target.
Kemudian, daripada menebak apakah video 1080p tersebut memerlukan 8 megabit per detik atau 15 megabit per detik, Anda memberi tahu CodecDB codec mana yang ingin Anda gunakan dan Anda meminta target kualitas persepsi.
Dalam hal ini, kita menggunakan tingkat kualitas sangat baik.
Di balik layar, CodecDB mencari kurva benchmark empiris untuk SOC yang sama persis dan menampilkan parameter codec dan encoder yang ideal.
Kami berharap CodecDB akan segera diluncurkan di Media3, dan saat diluncurkan, CodecDB akan menggunakan jenis Media3 native.
Jadi, mengintegrasikannya dengan kode Media3 yang ada akan sangat mudah.
Oke.
Ada banyak fungsi luar biasa yang telah kita lihat hari ini di Media3, jadi mari kita rekap.
Hari ini, Anda telah mempelajari modul Compose UI baru, CastPlayer baru, pipeline pengeditan yang diperbarui, dan banyak efek baru yang dapat Anda gunakan di Media3.
Kita juga telah melihat alat baru untuk mengambil metadata dan frame.
Kami juga membahas dukungan teknologi streaming baru serta peningkatan performa dan utilitas.
Media3 adalah library open source, jadi Anda dapat melihat cara kami menerapkan semua fitur ini di repositori GitHub kami.
Saat ini, rilis stabil terbaru Media3 adalah 1.11.1.
Anda juga dapat melihat 1.12.0, yang saat ini dalam versi alfa.
Dan ikuti link singkat ini, yang akan mengarahkan Anda ke dokumentasi developer kami tentang Media3.
Terima kasih banyak, dan nikmati sisa acara ADS.
@Composable
fun videoScreen() {
Player(viewModel.exoPlayer)
}Player(viewModel.exoPlayer)Player(
player = viewModel.exoPlayer,
bottomControls = {
PlayerDefaults.BottomControls(
player,
left = {}, right = {},
above = {
PositionAndDurationText(
player, separator = " • ")
},
)
},
)Player(
player = viewModel.exoPlayer,
centerControls = {},
bottomControls = { ...
above = {
Row(Modifier.fillMaxWidth()) {
PositionAndDurationText(...)
Spacer(Modifier.weight(1f))
PlaybackSpeedToggleButton(player)
MuteButton(player)
PlayPauseButton(player)
}
},
)
}Player(
...
bottomControls = { ...
above = {
...
MuteButton(player,
painter = { painterResource(
if (showMuted)
R.drawable.muted
else
R.drawable.unmuted )})
}
},
)MediaSession.Builder(context, player).build()class PlaybackService : MediaSessionService() {
private lateinit var mediaSession:
MediaSession
override fun onGetSession(
info: ControllerInfo) : MediaSession?
= mediaSession
}val player = ExoPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val player = CastPlayer.Builder(context).build()
mediaSession = MediaSession.Builder(context,player)
.build()Cast.getSingletonInstance(context).initialize()
val exoPlayer = ExoPlayer.Builder(context).build()
val player = CastPlayer.Builder(context)
.setLocalPlayer(exoPlayer).build()
mediaSession = MediaSession.Builder(context,player)
.build()Player(
exoPlayer,
centerControls = {}
bottomControls = { /* customized */ },
)Player(
castPlayer,
centerControls = {}
bottomControls = { /* customized */ },
topControls = {
PlayerDefaults.TopControls(castPlayer) {
MediaRouteButton(
Modifier.align(Alignment.TopEnd))
}
},
)public interface FrameProcessor
extends AutoCloseable {
interface Listener {
void onWakeup();
void onError(
VideoFrameProcessingException exception);
void onFrameProcessed(
Frame frame,
@Nullable SyncFenceWrapper onCompleteFence);
}
interface Factory {
FrameProcessor create(
FrameWriter output,
Executor listenerExecutor,
Listener listener);
}
boolean queue(List<AsyncFrame> frames);
void signalEndOfStream();
}val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory)
.setTonemappingEnabled(true)
.setDeblurAndDenoiseEnabled(true)
.setUpscaleVideoEnabled(true)
.build()
val transformerBuilder = Transformer.Builder(/* context = */ this)
.setFrameProcessorFactory(frameProcessorFactory)val editedMediaItem =
EditedMediaItem.Builder(MediaItem.fromUri(media.uri))
.setEffects(
Effects(
listOf(
soundSeparationAudioProcessor
),
emptyList(), // video effects
)
)
.build()val soundSeparationAudioProcessor =
SoundSeparationAudioProcessor(
getApplication(),
mainExecutor,
object : SoundSeparationAudioProcessor.Listener {
override fun onResult(channelToAudioType: SparseIntArray) {
for (i in 0 until channelToAudioType.size()) {
val channelIndex = channelToAudioType.keyAt(i)
val audioType = channelToAudioType.valueAt(i)
val label = toAudioTypeName(audioType)
detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label))
}
}
},
)MetadataRetriever.Builder(context, mediaItem).build().use { retriever ->
// 1. Duration (in microseconds)
val durationUs = retriever.retrieveDurationUs().await()
// 2. Track formats & container metadata (reuses the same preparation pass)
val trackGroups = retriever.retrieveTrackGroups().await()
for (i in 0 until trackGroups.length) {
val format = trackGroups[i].getFormat(0)
// Video/Audio properties: format.width, format.height, format.colorInfo …
// Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData …
val metadata = format.metadata
}
// 3. Stream structure & manifest info
val timeline = retriever.retrieveTimeline().await()
}FrameExtractor.Builder(context, mediaItem)
.setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking
.setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling
.setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR
.build()
.use { extractor ->
// Representative thumbnail
val thumbnail = extractor.getThumbnail().await()
// Frame at specific timestamp (bitmap + actual presentationTimeMs)
val frame = extractor.getFrame(positionMs = 5_000L).await()
}val config = CodecDb.onDevice(context)
.forEncodingFallingBackTo {
constraints -> createFallbackConfig(constraints)
}
.findOptimalSettings()
.forEditing()
.withResolution(1920, 1080, /* frameRate= */ 30.0)
.withAnyCodec()
.targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD)
.get()