1. Ringkasan
Codelab ini akan mengajari Anda cara memodifikasi aplikasi Android TV yang ada untuk mendukung transmisi dan komunikasi dari aplikasi pengirim Cast yang sudah ada.
Apa itu Google Cast dan Cast Connect?
Google Cast memungkinkan pengguna mentransmisikan konten dari perangkat seluler ke TV. Sesi Google Cast umumnya terdiri dari dua komponen — aplikasi pengirim dan penerima. Aplikasi pengirim, seperti aplikasi seluler atau situs web, seperti YouTube.com, memulai dan mengontrol pemutaran aplikasi penerima Cast. Aplikasi penerima Cast adalah aplikasi HTML 5 yang berjalan di perangkat Chromecast dan Android TV.
Hampir semua status dalam sesi Cast disimpan di aplikasi penerima. Saat status diperbarui, misalnya jika item media baru dimuat, status media akan disiarkan ke semua pengirim. Siaran ini berisi status sesi Cast saat ini. Aplikasi Pengirim menggunakan status media ini untuk menampilkan informasi pemutaran di UI-nya.
Cast Connect dibuat di atas infrastruktur ini, dengan aplikasi Android TV yang bertindak sebagai penerima. Library Cast Connect memungkinkan aplikasi Android TV Anda menerima pesan dan menyiarkan status media seolah-olah aplikasi penerima transmisi.
Apa yang akan kita buat?
Setelah menyelesaikan codelab ini, Anda dapat menggunakan aplikasi pengirim Cast untuk mentransmisikan video ke aplikasi Android TV. Aplikasi Android TV juga dapat berkomunikasi dengan aplikasi pengirim melalui protokol Cast.
Yang akan Anda pelajari
- Cara menambahkan library Cast Connect ke contoh aplikasi ATV.
- Cara menghubungkan pengirim Cast dan meluncurkan aplikasi ATV.
- Cara memulai pemutaran media di aplikasi ATV dari aplikasi pengirim Cast.
- Cara mengirim status media dari aplikasi ATV ke aplikasi pengirim Cast.
Yang Anda butuhkan
- Android SDK terbaru.
- Android Studio terbaru. Khususnya,
Chipmunk | 2021.2.1
atau versi yang lebih baru. - Perangkat Android TV yang telah mengaktifkan opsi developer dan proses debug USB.
- Ponsel Android yang telah mengaktifkan opsi developer dan proses debug USB.
- Kabel data USB untuk menghubungkan ponsel Android dan perangkat Android TV ke komputer pengembangan.
- Pengetahuan dasar tentang pengembangan aplikasi Android menggunakan Kotlin.
2. Mendapatkan kode contoh
Anda dapat mendownload semua kode contoh ke komputer Anda...
dan mengekstrak file zip yang didownload.
3. Menjalankan aplikasi contoh
Pertama, mari kita lihat bagaimana tampilan contoh aplikasi yang sudah selesai. Aplikasi Android TV menggunakan UI Leanback dan pemutar video dasar. Pengguna dapat memilih video dari daftar yang kemudian diputar di TV saat dipilih. Dengan aplikasi pengirim seluler yang menyertainya, pengguna juga dapat mentransmisikan video ke aplikasi Android TV.
Mendaftarkan perangkat developer
Guna mengaktifkan kemampuan Cast Connect untuk pengembangan aplikasi, Anda harus mendaftarkan nomor seri Chromecast bawaan perangkat Android TV yang akan digunakan di Konsol Developer Cast. Anda dapat menemukan nomor seri dengan membuka Settings > Device Preferences > Chromecast built-in > Serial number di Android TV. Perhatikan bahwa nomor ini berbeda dengan nomor seri perangkat fisik Anda dan harus diperoleh dari metode yang dijelaskan di atas.
Tanpa pendaftaran, Cast Connect hanya akan berfungsi untuk aplikasi yang diinstal dari Google Play Store karena alasan keamanan. Setelah 15 menit memulai proses pendaftaran, mulai ulang perangkat Anda.
Menginstal aplikasi pengirim Android
Untuk menguji permintaan pengiriman dari perangkat seluler, kami telah menyediakan aplikasi pengirim sederhana yang disebut Transmisikan Video sebagai file mobile-sender-0629.apk
dalam download zip kode sumber. Kami akan memanfaatkan ADB untuk menginstal APK. Jika Anda telah menginstal versi Cast Video yang berbeda, uninstal versi tersebut dari semua profil yang ada di perangkat sebelum melanjutkan.
- Aktifkan opsi developer dan proses debug USB di ponsel Android Anda.
- Colokkan kabel data USB untuk menghubungkan ponsel Android dengan komputer pengembangan.
- Instal
mobile-sender-0629.apk
ke ponsel Android Anda.
- Anda dapat menemukan aplikasi pengirim Transmisikan Video di ponsel Android Anda.
Menginstal aplikasi Android TV
Petunjuk berikut menjelaskan cara membuka dan menjalankan contoh aplikasi yang telah selesai di Android Studio:
- Pilih Import Project di layar sambutan atau File > Baru > Opsi menu Import Project....
- Pilih direktori
app-done
dari folder kode contoh, lalu klik OK. - Klik File > Sinkronkan Project dengan File Gradle.
- Aktifkan opsi developer dan proses debug USB di perangkat Android TV.
- ADB terhubung dengan perangkat Android TV, perangkat akan ditampilkan di Android Studio.
- Klik tombol Run, Anda akan melihat aplikasi ATV bernama Cast Connect Codelab muncul setelah beberapa detik.
Mari putar Cast Connect dengan aplikasi ATV
- Buka Layar Utama Android TV.
- Buka aplikasi pengirim Cast Video dari ponsel Android. Klik tombol Cast dan pilih perangkat ATV.
- Aplikasi Cast Connect Codelab ATV akan diluncurkan di ATV dan tombol Cast di pengirim akan menunjukkan bahwa aplikasi terhubung .
- Pilih video dari aplikasi ATV, dan video akan mulai diputar di ATV Anda.
- Di ponsel Anda, pengontrol mini kini terlihat di bagian bawah aplikasi pengirim. Anda dapat menggunakan tombol putar/jeda untuk mengontrol pemutaran.
- Pilih video dari ponsel dan putar. Video akan mulai diputar di ATV dan pengontrol yang diperluas akan ditampilkan pada pengirim seluler Anda.
- Kunci ponsel dan saat membuka kunci, Anda akan melihat notifikasi di layar kunci untuk mengontrol pemutaran media atau menghentikan transmisi.
4. Menyiapkan project awal
Setelah memverifikasi integrasi Cast Connect aplikasi yang telah selesai, kita perlu menambahkan dukungan untuk Cast Connect ke aplikasi awal yang Anda download. Sekarang Anda siap untuk mengerjakan project awal menggunakan Android Studio:
- Pilih Import Project di layar sambutan atau File > Baru > Opsi menu Import Project....
- Pilih direktori
app-start
dari folder kode contoh, lalu klik OK. - Klik File > Sinkronkan Project dengan File Gradle.
- Pilih perangkat ATV, lalu klik tombol Run untuk menjalankan aplikasi dan menjelajahi UI.
Desain aplikasi
Aplikasi ini menyediakan daftar video yang dapat dijelajahi pengguna. Pengguna dapat memilih video untuk diputar di Android TV. Aplikasi ini terdiri dari dua aktivitas utama: MainActivity
dan PlaybackActivity
.
MainActivity
Aktivitas ini berisi Fragmen (MainFragment
). Daftar video dan metadata terkaitnya dikonfigurasi dalam class MovieList
dan metode setupMovies()
dipanggil untuk membuat daftar objek Movie
.
Objek Movie
mewakili entity video dengan judul, deskripsi, thumbnail gambar, dan url video. Setiap objek Movie
terikat dengan CardPresenter
untuk menampilkan thumbnail video dengan judul dan studio serta diteruskan ke ArrayObjectAdapter
.
Saat item dipilih, objek Movie
yang sesuai diteruskan ke PlaybackActivity
.
PlaybackActivity
Aktivitas ini berisi Fragmen (PlaybackVideoFragment
) yang menghosting VideoView
dengan ExoPlayer
, beberapa kontrol media, dan area teks untuk menampilkan deskripsi video yang dipilih dan memungkinkan pengguna memutar video di Android TV. Pengguna dapat menggunakan remote control untuk memutar/menjeda atau mencari pemutaran video.
Prasyarat Cast Connect
Cast Connect menggunakan versi baru Layanan Google Play yang mengharuskan aplikasi ATV Anda diupdate agar dapat menggunakan namespace AndroidX.
Untuk mendukung Cast Connect di aplikasi Android TV, Anda harus membuat dan mendukung peristiwa dari sesi media. Library Cast Connect menghasilkan status media berdasarkan status sesi media. Sesi media Anda juga digunakan oleh library Cast Connect untuk memberi tahu kapan pesan tertentu diterima dari pengirim, seperti jeda.
5. Mengonfigurasi Dukungan Cast
Dependensi
Perbarui file build.gradle
aplikasi untuk menyertakan dependensi library yang diperlukan:
dependencies {
....
// Cast Connect libraries
implementation 'com.google.android.gms:play-services-cast-tv:20.0.0'
implementation 'com.google.android.gms:play-services-cast:21.1.0'
}
Sinkronkan project untuk mengonfirmasikan project dibuat tanpa error.
Inisialisasi
CastReceiverContext
adalah objek singleton untuk mengoordinasikan semua interaksi Cast. Anda harus mengimplementasikan antarmuka ReceiverOptionsProvider
untuk memberikan CastReceiverOptions
saat CastReceiverContext
diinisialisasi.
Buat file CastReceiverOptionsProvider.kt
dan tambahkan class berikut ke project:
package com.google.sample.cast.castconnect
import android.content.Context
import com.google.android.gms.cast.tv.ReceiverOptionsProvider
import com.google.android.gms.cast.tv.CastReceiverOptions
class CastReceiverOptionsProvider : ReceiverOptionsProvider {
override fun getOptions(context: Context): CastReceiverOptions {
return CastReceiverOptions.Builder(context)
.setStatusText("Cast Connect Codelab")
.build()
}
}
Kemudian tentukan penyedia opsi penerima dalam tag <application>
dari file aplikasi AndroidManifest.xml
:
<application>
...
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.castconnect.CastReceiverOptionsProvider" />
</application>
Untuk terhubung dengan aplikasi ATV dari pengirim Cast, pilih aktivitas yang ingin Anda luncurkan. Dalam codelab ini, kita akan meluncurkan MainActivity
aplikasi saat sesi Cast dimulai. Di file AndroidManifest.xml
, tambahkan filter intent peluncuran di MainActivity
.
<activity android:name=".MainActivity">
...
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Siklus Proses Konteks Penerima Cast
Anda harus memulai CastReceiverContext
saat aplikasi diluncurkan dan menghentikan CastReceiverContext
saat aplikasi Anda dipindahkan ke latar belakang. Sebaiknya gunakan LifecycleObserver
dari library androidx.Lifecycle untuk mengelola panggilan CastReceiverContext.start()
dan CastReceiverContext.stop()
Buka file MyApplication.kt
, lakukan inisialisasi konteks transmisi dengan memanggil initInstance()
di metode onCreate
aplikasi. Di class AppLifeCycleObserver
, start()
pada CastReceiverContext
saat aplikasi dilanjutkan dan stop()
saat aplikasi dijeda:
package com.google.sample.cast.castconnect
import com.google.android.gms.cast.tv.CastReceiverContext
...
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CastReceiverContext.initInstance(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
}
class AppLifecycleObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onResume")
CastReceiverContext.getInstance().start()
}
override fun onPause(owner: LifecycleOwner) {
Log.d(LOG_TAG, "onPause")
CastReceiverContext.getInstance().stop()
}
}
}
Menghubungkan MediaSession ke MediaManager
MediaManager
adalah properti singleton CastReceiverContext
, yang mengelola status media, menangani intent pemuatan, menerjemahkan pesan namespace media dari pengirim ke perintah media, dan mengirim status media kembali ke pengirim.
Saat membuat MediaSession
, Anda juga perlu memberikan token MediaSession
saat ini ke MediaManager
agar ekstensi tersebut tahu ke mana harus mengirimkan perintah dan mengambil status pemutaran media. Di file PlaybackVideoFragment.kt
, pastikan MediaSession
diinisialisasi sebelum menyetel token ke MediaManager
.
import com.google.android.gms.cast.tv.CastReceiverContext
import com.google.android.gms.cast.tv.media.MediaManager
...
class PlaybackVideoFragment : VideoSupportFragment() {
private var castReceiverContext: CastReceiverContext? = null
...
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext!!.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession!!.getSessionToken())
}
}
}
}
Saat merilis MediaSession
karena pemutaran tidak aktif, Anda harus menyetel token null di MediaManager
:
private fun releasePlayer() {
mMediaSession?.release()
castReceiverContext?.mediaManager?.setSessionCompatToken(null)
...
}
Mari kita jalankan aplikasi contoh
Klik tombol Run untuk men-deploy aplikasi di perangkat ATV, tutup aplikasi, dan kembali ke Layar Utama ATV. Dari pengirim, klik tombol Cast dan pilih perangkat ATV. Anda akan melihat aplikasi ATV diluncurkan di perangkat ATV dan status tombol Cast terhubung.
6. Memuat Media
Perintah pemuatan dikirim melalui intent dengan nama paket yang Anda tentukan di konsol developer. Anda perlu menambahkan filter intent berikut yang telah ditentukan sebelumnya di aplikasi Android TV untuk menentukan aktivitas target yang akan menerima intent ini. Dalam file AndroidManifest.xml
, tambahkan filter intent pemuatan ke PlayerActivity
:
<activity android:name="com.google.sample.cast.castconnect.PlaybackActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Menangani Permintaan Pemuatan di Android TV
Setelah aktivitas dikonfigurasikan untuk menerima intent ini yang berisi permintaan pemuatan, kita harus menanganinya.
Aplikasi ini memanggil metode pribadi yang disebut processIntent
saat aktivitas dimulai. Metode ini berisi logika untuk memproses intent masuk. Untuk menangani permintaan pemuatan, kami akan mengubah metode ini dan mengirim intent agar diproses lebih lanjut dengan memanggil metode onNewIntent
instance MediaManager
. Jika MediaManager
yang mendeteksi intent adalah permintaan pemuatan, objek MediaLoadRequestData
diekstrak dari intent dan memanggil MediaLoadCommandCallback.onLoad()
. Ubah metode processIntent
dalam file PlaybackVideoFragment.kt
untuk menangani intent yang berisi permintaan pemuatan:
fun processIntent(intent: Intent?) {
val mediaManager: MediaManager = CastReceiverContext.getInstance().getMediaManager()
// Pass intent to Cast SDK
if (mediaManager.onNewIntent(intent)) {
return
}
// Clears all overrides in the modifier.
mediaManager.getMediaStatusModifier().clear()
// If the SDK doesn't recognize the intent, handle the intent with your own logic.
...
}
Selanjutnya, kita akan memperluas class abstrak MediaLoadCommandCallback
yang akan mengganti metode onLoad()
yang dipanggil oleh MediaManager
. Metode ini menerima data permintaan pemuatan dan mengonversinya menjadi objek Movie
. Setelah dikonversi, film akan diputar oleh pemutar lokal. MediaManager
kemudian diperbarui dengan MediaLoadRequest
dan menyiarkan MediaStatus
ke pengirim yang terhubung. Buat class pribadi bertingkat yang disebut MyMediaLoadCommandCallback
di file PlaybackVideoFragment.kt
:
import com.google.android.gms.cast.MediaLoadRequestData
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaError
import com.google.android.gms.cast.tv.media.MediaException
import com.google.android.gms.cast.tv.media.MediaCommandCallback
import com.google.android.gms.cast.tv.media.QueueUpdateRequestData
import com.google.android.gms.cast.tv.media.MediaLoadCommandCallback
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import android.widget.Toast
...
private inner class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
override fun onLoad(
senderId: String?, mediaLoadRequestData: MediaLoadRequestData): Task<MediaLoadRequestData> {
Toast.makeText(activity, "onLoad()", Toast.LENGTH_SHORT).show()
return if (mediaLoadRequestData == null) {
// Throw MediaException to indicate load failure.
Tasks.forException(MediaException(
MediaError.Builder()
.setDetailedErrorCode(MediaError.DetailedErrorCode.LOAD_FAILED)
.setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
.build()))
} else Tasks.call {
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
// Update media metadata and state
val mediaManager = castReceiverContext!!.mediaManager
mediaManager.setDataFromLoad(mediaLoadRequestData)
mediaLoadRequestData
}
}
}
private fun convertLoadRequestToMovie(mediaLoadRequestData: MediaLoadRequestData?): Movie? {
if (mediaLoadRequestData == null) {
return null
}
val mediaInfo: MediaInfo = mediaLoadRequestData.getMediaInfo() ?: return null
var videoUrl: String = mediaInfo.getContentId()
if (mediaInfo.getContentUrl() != null) {
videoUrl = mediaInfo.getContentUrl()
}
val metadata: MediaMetadata = mediaInfo.getMetadata()
val movie = Movie()
movie.videoUrl = videoUrl
movie.title = metadata?.getString(MediaMetadata.KEY_TITLE)
movie.description = metadata?.getString(MediaMetadata.KEY_SUBTITLE)
if(metadata?.hasImages() == true) {
movie.cardImageUrl = metadata.images[0].url.toString()
}
return movie
}
Setelah Callback ditetapkan, kita perlu mendaftarkannya ke MediaManager
. Callback harus terdaftar sebelum MediaManager.onNewIntent()
dipanggil. Tambahkan setMediaLoadCommandCallback
saat pemutar diinisialisasi:
private fun initializePlayer() {
if (mPlayer == null) {
...
mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
mediaManager.setSessionCompatToken(mMediaSession.getSessionToken())
mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback())
}
}
}
Mari kita jalankan aplikasi contoh
Klik tombol Run untuk men-deploy aplikasi di perangkat ATV. Dari pengirim, klik tombol Cast dan pilih perangkat ATV. Aplikasi ATV akan diluncurkan di perangkat ATV. Pilih video di seluler, video akan mulai diputar di ATV. Periksa apakah Anda menerima notifikasi di ponsel yang berisi kontrol pemutaran. Coba gunakan kontrol seperti jeda, maka video di perangkat ATV akan dijeda.
7. Mendukung Perintah Kontrol Cast
Aplikasi saat ini kini mendukung perintah dasar yang kompatibel dengan sesi media, seperti putar, jeda, dan cari. Namun, ada beberapa perintah kontrol Cast yang tidak tersedia di sesi media. Anda perlu mendaftarkan MediaCommandCallback
untuk mendukung perintah kontrol Cast tersebut.
Tambahkan MyMediaCommandCallback
ke instance MediaManager
menggunakan setMediaCommandCallback
saat pemutar diinisialisasi:
private fun initializePlayer() {
...
castReceiverContext = CastReceiverContext.getInstance()
if (castReceiverContext != null) {
val mediaManager = castReceiverContext!!.mediaManager
...
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
}
}
Buat class MyMediaCommandCallback
untuk mengganti metode, seperti onQueueUpdate()
untuk mendukung perintah kontrol Cast tersebut:
private inner class MyMediaCommandCallback : MediaCommandCallback() {
override fun onQueueUpdate(
senderId: String?,
queueUpdateRequestData: QueueUpdateRequestData
): Task<Void> {
Toast.makeText(getActivity(), "onQueueUpdate()", Toast.LENGTH_SHORT).show()
// Queue Prev / Next
if (queueUpdateRequestData.getJump() != null) {
Toast.makeText(
getActivity(),
"onQueueUpdate(): Jump = " + queueUpdateRequestData.getJump(),
Toast.LENGTH_SHORT
).show()
}
return super.onQueueUpdate(senderId, queueUpdateRequestData)
}
}
8. Menggunakan Status Media
Memodifikasi Status Media
Cast Connect mendapatkan status media dasar dari sesi media. Untuk mendukung fitur lanjutan, aplikasi Android TV dapat menentukan dan mengganti properti status tambahan melalui MediaStatusModifier
. MediaStatusModifier
akan selalu beroperasi pada MediaSession
yang telah Anda tetapkan di CastReceiverContext
.
Misalnya, untuk menentukan setMediaCommandSupported
saat onLoad
callback dipicu:
import com.google.android.gms.cast.MediaStatus
...
private class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
fun onLoad(
senderId: String?,
mediaLoadRequestData: MediaLoadRequestData
): Task<MediaLoadRequestData> {
Toast.makeText(getActivity(), "onLoad()", Toast.LENGTH_SHORT).show()
...
return Tasks.call({
play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
...
// Use MediaStatusModifier to provide additional information for Cast senders.
mediaManager.getMediaStatusModifier()
.setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT, true)
.setIsPlayingAd(false)
mediaManager.broadcastMediaStatus()
// Return the resolved MediaLoadRequestData to indicate load success.
mediaLoadRequestData
})
}
}
Mencegah MediaStatus Sebelum Mengirim
Serupa dengan MessageInterceptor
SDK penerima Web, Anda dapat menentukan MediaStatusWriter
di MediaManager
untuk melakukan modifikasi tambahan pada MediaStatus
sebelum disiarkan ke pengirim yang terhubung.
Misalnya, Anda dapat menyetel data khusus di MediaStatus
sebelum mengirim ke pengirim seluler:
import com.google.android.gms.cast.tv.media.MediaManager.MediaStatusInterceptor
import com.google.android.gms.cast.tv.media.MediaStatusWriter
import org.json.JSONObject
import org.json.JSONException
...
private fun initializePlayer() {
if (mPlayer == null) {
...
if (castReceiverContext != null) {
...
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
...
// Use MediaStatusInterceptor to process the MediaStatus before sending out.
mediaManager.setMediaStatusInterceptor(
MediaStatusInterceptor { mediaStatusWriter: MediaStatusWriter ->
try {
mediaStatusWriter.setCustomData(JSONObject("{myData: 'CustomData'}"))
} catch (e: JSONException) {
Log.e(LOG_TAG,e.message,e);
}
})
}
}
}
9. Selamat
Kini Anda telah mengetahui cara mengaktifkan Cast aplikasi Android TV menggunakan Cast Connect Library.
Lihat panduan developer untuk mengetahui detail selengkapnya: /cast/docs/android_tv_receiver.