
Di jalan, saya sering mendengarkan buku audio dan podcast dari telepon pintar. Ketika saya tiba di rumah, saya ingin terus mendengarkan mereka di TV Android atau Google Home. Tetapi tidak semua aplikasi mendukung Chromecast. Dan itu akan nyaman.
Menurut statistik Google selama 3 tahun terakhir, jumlah perangkat di Android TV telah meningkat 4 kali lipat, dan jumlah mitra manufaktur telah melebihi seratus: televisi, speaker, kotak set-top "pintar". Semuanya mendukung Chromecast. Tetapi masih ada banyak aplikasi di pasar yang jelas kurang integrasi dengan itu.
Pada artikel ini, saya ingin berbagi pengalaman mengintegrasikan Chromecast ke aplikasi Android untuk memutar konten media.
Bagaimana cara kerjanya
Jika ini pertama kalinya Anda mendengar kata "Chromecast," saya akan mencoba memberi tahu Anda secara singkat. Dalam hal penggunaan, tampilannya seperti ini:
- Pengguna mendengarkan musik atau menonton video melalui aplikasi atau situs web.
- Perangkat Chromecast muncul di jaringan lokal.
- Tombol yang sesuai akan muncul di antarmuka pemain.
- Dengan mengkliknya, pengguna memilih perangkat yang diinginkan dari daftar. Ini bisa menjadi Nexus Player, Android TV atau speaker pintar.
- Pemutaran lebih lanjut berlanjut dengan perangkat ini.

Secara teknis, sesuatu seperti yang berikut ini terjadi:
- Layanan Google memantau keberadaan perangkat Chromecast di jaringan lokal melalui Penyiaran.
- Jika MediaRouter terhubung ke aplikasi Anda, maka Anda akan menerima acara tentang ini.
- Saat pengguna memilih perangkat Cast dan menghubungkannya, sesi media baru (CastSession) terbuka.
- Sudah di sesi yang dibuat, kami akan mentransfer konten untuk diputar.
Kedengarannya sangat sederhana.
Integrasi
Google memiliki Chromecast SDK sendiri, tetapi tidak tercakup dalam dokumentasi dan kodenya dikaburkan. Karena itu, banyak hal yang harus diperiksa dengan mengetik. Mari kita bereskan semuanya.
Inisialisasi
Pertama, kita perlu menghubungkan Cast Application Framework dan MediaRouter:
implementation "com.google.android.gms:play-services-cast-framework:16.1.0" implementation "androidx.mediarouter:mediarouter:1.0.0"
Maka Cast Framework harus mendapatkan pengenal aplikasi (lebih lanjut tentang itu nanti), dan jenis konten media yang didukung. Artinya, jika aplikasi kita hanya memutar video, maka casting ke kolom Beranda Google tidak akan mungkin, dan itu tidak akan ada dalam daftar perangkat. Untuk melakukan ini, buat implementasi OptionsProvider:
class CastOptionsProvider: OptionsProvider { override fun getCastOptions(context: Context): CastOptions { return CastOptions.Builder() .setReceiverApplicationId(BuildConfig.CHROMECAST_APP_ID) .build() } override fun getAdditionalSessionProviders(context: Context): MutableList<SessionProvider>? { return null } }
Dan nyatakan dalam Manifest:
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:value="your.app.package.CastOptionsProvider" />
Daftarkan aplikasi
Agar Chromecast berfungsi dengan aplikasi kami, itu harus didaftarkan di Google Cast SDK Developers Console . Ini memerlukan akun pengembang Chromecast (jangan bingung dengan akun pengembang Google Play). Saat mendaftar, Anda harus membayar biaya satu kali sebesar $ 5. Setelah menerbitkan Aplikasi ChromeCast, Anda perlu menunggu sedikit.
Di konsol, Anda dapat mengubah tampilan pemain Cast untuk perangkat dengan layar dan melihat analitik casting dalam aplikasi.
MediaRouteFramework adalah mekanisme yang memungkinkan Anda menemukan semua perangkat pemutaran jarak jauh di dekat pengguna. Ini tidak hanya Chromecast, tetapi juga display jarak jauh dan speaker menggunakan protokol pihak ketiga. Tetapi yang menarik bagi kami adalah Chromecast.

MediaRouteFramework memiliki Tampilan yang mencerminkan keadaan skuter media. Ada dua cara untuk menghubungkannya:
1) Melalui menu:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> ... <item android:id="@+id/menu_media_route" android:title="@string/cast" app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider" app:showAsAction="always"/> ... </menu>
2) Melalui tata letak:
<androidx.mediarouter.app.MediaRouteButton android:id="@+id/mediaRouteButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:mediaRouteTypes="user"/>
Dan dari kode Anda hanya perlu mendaftarkan tombol di CastButtonFactory. maka keadaan saat ini dari skuter media akan dilemparkan ke dalamnya:
CastButtonFactory.setUpMediaRouteButton(applicationContext, view.mediaRouteButton)
Sekarang setelah aplikasi terdaftar dan MediaRouter dikonfigurasi, Anda dapat terhubung ke perangkat ChromeCast dan membuka sesi untuk mereka.
ChromeCast mendukung tiga jenis konten utama:
Bergantung pada pengaturan pemutar, seperti konten media dan perangkat transmisi, antarmuka pemain dapat bervariasi.
Castsession
Jadi, pengguna memilih perangkat yang diinginkan, CastFramework membuka sesi baru. Sekarang tugas kita adalah merespons ini dan meneruskan informasi ke perangkat untuk diputar.
Untuk mengetahui keadaan sesi saat ini dan mendaftar untuk memperbarui keadaan ini, gunakan objek SessionManager :
private val mediaSessionListener = object : SessionManagerListener<CastSession> { override fun onSessionStarted(session: CastSession, sessionId: String) { currentSession = session
Dan kita juga bisa mencari tahu apakah ada sesi terbuka saat ini:
val currentSession: CastSession? = sessionManager.currentCastSession
Kami memiliki dua kondisi utama di mana kami dapat mulai melakukan casting:
- Sesi sudah terbuka.
- Ada konten untuk casting.
Di masing-masing dari dua peristiwa ini, kita dapat memeriksa statusnya, dan jika semuanya sudah beres, maka mulai casting.
Casting
Sekarang kita memiliki apa yang harus dilemparkan dan ke mana harus dilemparkan, kita dapat beralih ke hal yang paling penting. Antara lain, CastSession memiliki objek RemoteMediaClient yang bertanggung jawab untuk keadaan pemutaran konten media. Kami akan bekerja dengannya.
Mari kita buat MediaMetadata , di mana informasi tentang penulis, album, dll akan disimpan. Ini sangat mirip dengan apa yang kita transfer ke MediaSession ketika kita memulai pemutaran lokal.
val mediaMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK ).apply { putString(MediaMetadata.KEY_TITLE, “In C”) putString(MediaMetadata.KEY_ARTIST, “Terry Riley”) mediaContent?.metadata?.posterUrl?.let { poster -> addImage(WebImage(Uri.parse(“https:
MediaMetadata memiliki banyak parameter, dan lebih baik melihatnya di dokumentasi. Saya terkejut bahwa Anda dapat menambahkan gambar bukan melalui bitmap, tetapi hanya dengan tautan di dalam WebImage.
Objek MediaInfo membawa informasi tentang metadata konten dan akan berbicara tentang dari mana konten media berasal, apa jenisnya, bagaimana cara memainkannya:
val mediaInfo = MediaInfo.Builder(“https:
Biarkan saya mengingatkan Anda bahwa contentType adalah jenis konten sesuai dengan spesifikasi MIME .
Anda juga dapat mentransfer sisipan iklan ke MediaInfo:
- setAdBreakClips - menerima daftar iklan AdBreakClipInfo dengan tautan ke konten, judul, waktu, dan waktu dilewati iklan.
- setAdBreaks - informasi tentang tata letak dan waktu pemasangan iklan.
Dalam MediaLoadOptions kami menjelaskan bagaimana kami akan memproses aliran media (kecepatan, posisi awal). Dokumentasi juga mengatakan bahwa melalui setCredentials Anda dapat melewati tajuk permintaan untuk otorisasi, tetapi permintaan saya dari Chromecast tidak menyertakan bidang yang dinyatakan untuk otorisasi.
val mediaLoadOptions = MediaLoadOptions.Builder() .setPlayPosition(position!!) .setAutoplay(true) .setPlaybackRate(playbackSpeed) .setCredentials(context.getString(R.string.bearer_token, authGateway.authState.accessToken!!)) .setCredentialsType(context.getString(R.string.authorization_header_key)) .build()
Setelah semuanya siap, kami dapat memberikan semua data ke RemoteMediaClient, dan Chromecast akan mulai diputar. Penting untuk menjeda pemutaran lokal.
val remoteMediaClient = currentSession!!.remoteMediaClient remoteMediaClient.load(mediaInfo, mediaLoadOptions)
Penanganan acara
Video mulai diputar, lalu apa? Bagaimana jika pengguna menjeda TV? Untuk mempelajari tentang acara dari sisi Chromecast, RemoteMediaClient memiliki panggilan balik:
private val castStatusCallback = object : RemoteMediaClient.Callback() { override fun onStatusUpdated() {
Untuk mengetahui kemajuan saat ini juga sederhana:
val periodMills = 1000L remoteMediaClient.addProgressListener( RemoteMediaClient.ProgressListener { progressMills, durationMills ->
Pengalaman berintegrasi dengan pemain yang ada
Aplikasi yang sedang saya kerjakan sudah memiliki media player yang sudah jadi. Tujuannya adalah untuk mengintegrasikan dukungan Chromecast ke dalamnya. Media player didasarkan pada State Machine, dan pemikiran pertama adalah menambahkan status baru: "CastingState". Tetapi ide ini langsung ditolak, karena setiap negara pemain mencerminkan keadaan pemutaran, dan tidak masalah apa yang berfungsi sebagai implementasi dari ExoPlayer atau ChromeCast.
Kemudian muncul ide untuk membuat sistem delegasi tertentu dengan memprioritaskan dan memproses "siklus hidup" pemain. Semua delegasi dapat menerima acara status pemain: Putar, Jeda, dll. - tetapi hanya delegasi pemimpin yang akan memutar konten media.

Kami memiliki sesuatu seperti antarmuka pemain ini:
interface Player { val isPlaying: Boolean val isReleased: Boolean val duration: Long var positionInMillis: Long var speed: Float var volume: Float var loop: Boolean fun addListener(listener: PlayerCallback) fun removeListener(listener: PlayerCallback): Boolean fun getListeners(): MutableSet<PlayerCallback> fun prepare(mediaContent: MediaContent) fun play() fun pause() fun release() interface PlayerCallback { fun onPlaying(currentPosition: Long) fun onPaused(currentPosition: Long) fun onPreparing() fun onPrepared() fun onLoadingChanged(isLoading: Boolean) fun onDurationChanged(duration: Long) fun onSetSpeed(speed: Float) fun onSeekTo(fromTimeInMillis: Long, toTimeInMillis: Long) fun onWaitingForNetwork() fun onError(error: String?) fun onReleased() fun onPlayerProgress(currentPosition: Long) } }
Di dalamnya akan menjadi Mesin Negara dengan begitu banyak negara:
- Kosong - keadaan awal sebelum inisialisasi.
- Mempersiapkan - pemain memulai pemutaran konten media.
- Disiapkan - Media diunggah dan siap dimainkan.
- Bermain
- Dijeda
- Menunggu jaringan
- Kesalahan

Sebelumnya, setiap negara selama inisialisasi mengeluarkan perintah di ExoPlayer. Sekarang ia akan mengeluarkan perintah ke daftar delegasi Playing, dan delegasi "Lead" akan dapat memprosesnya. Karena delegasi mengimplementasikan semua fungsi pemain, ia juga dapat diwarisi dari antarmuka pemain dan digunakan secara terpisah jika perlu. Maka delegasi abstrak akan terlihat seperti ini:
abstract class PlayingDelegate( protected val playerCallback: Player.PlayerCallback, var isLeading: Boolean = false ) : Player { fun setIsLeading(isLeading: Boolean, positionMills: Long, isPlaying: Boolean) { this.isLeading = isLeading if (isLeading) { onLeading(positionMills, isPlaying) } else { onDormant() } } final override fun addListener(listener: Player.PlayerCallback) {
Sebagai contoh, saya menyederhanakan antarmuka. Pada kenyataannya, ada sedikit lebih banyak peristiwa.
Mungkin ada banyak delegasi karena ada sumber reproduksi. Delegasi Chromecast mungkin terlihat seperti ini:
ChromeCastDelegate.kt class ChromeCastDelegate( private val context: Context, private val castCallback: ChromeCastListener, playerCallback: Player.PlayerCallback ) : PlayingDelegate(playerCallback) { companion object { private const val CONTENT_TYPE_VIDEO = "videos/mp4" private const val CONTENT_TYPE_AUDIO = "audio/mp3" private const val PROGRESS_DELAY_MILLS = 500L } interface ChromeCastListener { fun onCastStarted() fun onCastStopped() } private var sessionManager: SessionManager? = null private var currentSession: CastSession? = null private var mediaContent: MediaContent? = null private var currentPosition: Long = 0 private val mediaSessionListener = object : SessionManagerListener<CastSession> { override fun onSessionStarted(session: CastSession, sessionId: String) { currentSession = session castCallback.onCastStarted() } override fun onSessionEnding(session: CastSession) { currentPosition = session.remoteMediaClient?.approximateStreamPosition ?: currentPosition stopCasting() } override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) { currentSession = session castCallback.onCastStarted() } override fun onSessionStartFailed(session: CastSession, p1: Int) { stopCasting() } override fun onSessionEnded(session: CastSession, p1: Int) {
Sebelum memberikan perintah tentang reproduksi, kita perlu memutuskan delegasi terkemuka. Untuk melakukan ini, mereka ditambahkan dalam urutan prioritas ke pemain, dan masing-masing dari mereka dapat memberikan kesiapan dalam metode readyForLeading (). Kode sampel lengkap dapat dilihat di GitHub .
Apakah ada kehidupan setelah ChromeCast

Setelah saya mengintegrasikan dukungan Chromecast ke dalam aplikasi, saya lebih senang pulang dan menikmati buku audio tidak hanya melalui headphone, tetapi juga melalui Google Home. Sedangkan untuk arsitektur, implementasi pemain dalam aplikasi yang berbeda dapat bervariasi, sehingga pendekatan ini tidak akan sesuai di mana-mana. Tetapi untuk arsitektur kita, itu muncul. Saya harap artikel ini bermanfaat, dan dalam waktu dekat akan ada lebih banyak aplikasi yang dapat diintegrasikan dengan lingkungan digital!