Asisten Google memungkinkan Anda menggunakan perintah suara untuk mengontrol banyak perangkat, seperti Google Home, ponsel Anda, dan lain-lain. Alat ini memiliki kemampuan bawaan untuk memahami perintah media ("putar sesuatu dari Beyoncé") dan mendukung kontrol media (seperti jeda, lewati, maju cepat, suka).
Asisten berkomunikasi dengan aplikasi media Android menggunakan media sesi pelatihan. Dapat menggunakan intent atau layanan untuk meluncurkan aplikasi Anda dan memulai pemutaran. Untuk hasil terbaik, aplikasi Anda harus menerapkan semua fitur yang dijelaskan di halaman ini.
Menggunakan sesi media
Setiap aplikasi audio dan video harus menerapkan sesi media agar Asisten dapat beroperasi kontrol transport setelah pemutaran dimulai.
Perhatikan bahwa meskipun Asisten hanya menggunakan tindakan yang tercantum di bagian ini,
adalah menerapkan semua API persiapan dan pemutaran untuk memastikan
kompatibilitas dengan aplikasi lain. Untuk tindakan apa pun yang tidak Anda dukung,
callback sesi media bisa saja
mengembalikan {i>error<i} menggunakan
ERROR_CODE_NOT_SUPPORTED
Aktifkan kontrol media dan transport dengan menyetel tanda ini di atribut
Objek MediaSession
:
Kotlin
session.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS )
Java
session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
Sesi media aplikasi Anda harus mendeklarasikan tindakan yang didukungnya, dan mengimplementasikan
callback sesi media yang sesuai. Deklarasikan tindakan yang didukung di
setActions()
Tujuan Pemutar Musik Android Universal adalah contoh yang baik tentang cara menyiapkan sesi media.
Tindakan pemutaran
Agar dapat memulai pemutaran dari layanan, sesi media harus menerapkan tindakan PLAY
ini dan callback-nya:
Tindakan | Callback |
---|---|
ACTION_PLAY |
onPlay() |
ACTION_PLAY_FROM_SEARCH |
onPlayFromSearch() |
ACTION_PLAY_FROM_URI (*) |
onPlayFromUri() |
Sesi Anda juga harus menerapkan tindakan PREPARE
ini dan callback-nya:
Tindakan | Callback |
---|---|
ACTION_PREPARE |
onPrepare() |
ACTION_PREPARE_FROM_SEARCH |
onPrepareFromSearch() |
ACTION_PREPARE_FROM_URI (*) |
onPrepareFromUri() |
Dengan menerapkan API persiapan, latensi pemutaran setelah perintah suara dapat dikurangi. Aplikasi media yang ingin meningkatkan latensi pemutaran dapat menggunakan tambahan waktu untuk mulai menyimpan konten dalam cache dan menyiapkan pemutaran media.
Mengurai kueri penelusuran
Saat pengguna menelusuri item media tertentu, seperti “Putar musik jazz di
[nama aplikasi Anda]” atau “Dengarkan [judul lagu]”,
onPrepareFromSearch()
atau
onPlayFromSearch()
menerima parameter kueri dan paket tambahan.
Aplikasi Anda harus mengurai kueri penelusuran suara dan memulai pemutaran dengan mengikuti langkah:
- Gunakan paket tambahan dan string kueri penelusuran yang ditampilkan dari penelusuran suara untuk memfilter hasil.
- Buat antrean pemutaran berdasarkan hasil ini.
- Putar item media yang paling relevan dari hasil.
onPlayFromSearch()
mengambil parameter tambahan dengan informasi yang lebih detail dari
cari. Parameter tambahan ini membantu Anda menemukan konten audio di aplikasi Anda untuk diputar.
Jika hasil penelusuran tidak dapat menyediakan data ini, Anda dapat menerapkan logika
mengurai kueri penelusuran mentah dan memutar jalur yang sesuai berdasarkan
kueri.
Parameter tambahan berikut didukung di Android Automotive OS dan Android Auto:
Cuplikan kode berikut menunjukkan cara mengganti onPlayFromSearch()
di MediaSession.Callback
untuk mengurai kueri penelusuran suara dan memulai pemutaran:
Kotlin
override fun onPlayFromSearch(query: String?, extras: Bundle?) { if (query.isNullOrEmpty()) { // The user provided generic string e.g. 'Play music' // Build appropriate playlist queue } else { // Build a queue based on songs that match "query" or "extras" param val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS) if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) { isArtistFocus = true artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST) } else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) { isAlbumFocus = true album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM) } // Implement additional "extras" param filtering } // Implement your logic to retrieve the queue var result: String? = when { isArtistFocus -> artist?.also { searchMusicByArtist(it) } isAlbumFocus -> album?.also { searchMusicByAlbum(it) } else -> null } result = result ?: run { // No focus found, search by query for song title query?.also { searchMusicBySongTitle(it) } } if (result?.isNotEmpty() == true) { // Immediately start playing from the beginning of the search results // Implement your logic to start playing music playMusic(result) } else { // Handle no queue found. Stop playing if the app // is currently playing a song } }
Java
@Override public void onPlayFromSearch(String query, Bundle extras) { if (TextUtils.isEmpty(query)) { // The user provided generic string e.g. 'Play music' // Build appropriate playlist queue } else { // Build a queue based on songs that match "query" or "extras" param String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS); if (TextUtils.equals(mediaFocus, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) { isArtistFocus = true; artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST); } else if (TextUtils.equals(mediaFocus, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) { isAlbumFocus = true; album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM); } // Implement additional "extras" param filtering } // Implement your logic to retrieve the queue if (isArtistFocus) { result = searchMusicByArtist(artist); } else if (isAlbumFocus) { result = searchMusicByAlbum(album); } if (result == null) { // No focus found, search by query for song title result = searchMusicBySongTitle(query); } if (result != null && !result.isEmpty()) { // Immediately start playing from the beginning of the search results // Implement your logic to start playing music playMusic(result); } else { // Handle no queue found. Stop playing if the app // is currently playing a song } }
Untuk contoh yang lebih mendetail tentang cara menerapkan penelusuran suara untuk memutar audio konten dalam aplikasi Anda, lihat Universal Android Music Player contoh.
Menangani kueri kosong
Jika onPrepare()
, onPlay()
, onPrepareFromSearch()
, atau onPlayFromSearch()
dipanggil tanpa kueri penelusuran, aplikasi media Anda harus memutar "saat ini"
lainnya. Jika tidak ada media saat ini, aplikasi harus mencoba memutar sesuatu,
sebagai lagu dari playlist terbaru atau antrean acak. Asisten menggunakan
API ini jika pengguna meminta “Putar musik di [nama aplikasi Anda]” tanpa
informasi tambahan.
Saat pengguna mengucapkan “Putar musik di [nama aplikasi Anda]”, Android Automotive OS atau
Android Auto mencoba meluncurkan aplikasi dan memutar audio dengan memanggil onPlayFromSearch()
aplikasi Anda
. Namun, karena pengguna tidak menyebutkan nama item media, onPlayFromSearch()
menerima parameter kueri kosong. Dalam kasus ini, aplikasi Anda harus
merespons dengan langsung memutar audio, seperti lagu dari
atau antrean acak.
Mendeklarasikan dukungan lama untuk voice action
Pada umumnya, menangani tindakan pemutaran yang dijelaskan di atas memberi aplikasi Anda semua fungsionalitas pemutaran yang dibutuhkan. Namun, beberapa sistem mengharuskan aplikasi Anda untuk berisi filter Intent untuk pencarian. Anda harus mendeklarasikan dukungan untuk Intent ini filter di file manifes aplikasi Anda.
Sertakan kode ini dalam file manifes untuk aplikasi ponsel:
<activity>
<intent-filter>
<action android:name=
"android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name=
"android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Kontrol transport
Setelah sesi media aplikasi Anda aktif, Asisten bisa mengeluarkan perintah suara untuk mengontrol pemutaran dan memperbarui metadata media. Agar hal ini dapat dilakukan, kode tersebut harus mengaktifkan tindakan berikut dan mengimplementasikan metode callback:
Tindakan | Callback | Deskripsi |
---|---|---|
ACTION_SKIP_TO_NEXT |
onSkipToNext() |
Video berikutnya |
ACTION_SKIP_TO_PREVIOUS |
onSkipToPrevious() |
Lagu sebelumnya |
ACTION_PAUSE, ACTION_PLAY_PAUSE |
onPause() |
Jeda |
ACTION_STOP |
onStop() |
Berhenti |
ACTION_PLAY |
onPlay() |
Lanjutkan |
ACTION_SEEK_TO |
onSeekTo() |
Mundur 30 detik |
ACTION_SET_RATING |
onSetRating(android.support.v4.media.RatingCompat) |
Sukai/Tidak sukai. |
ACTION_SET_CAPTIONING_ENABLED |
onSetCaptioningEnabled(boolean) |
Mengaktifkan atau menonaktifkan teks. |
Perhatikan:
- Agar perintah mencari berfungsi,
PlaybackState
harus diperbarui denganstate, position, playback speed, and update time
. Aplikasi harus memanggilsetPlaybackState()
saat status berubah. - Aplikasi media juga harus menjaga agar metadata sesi media selalu terbaru. Ini mendukung pertanyaan seperti "lagu apa yang sedang diputar?" Aplikasi harus memanggil
setMetadata()
jika kolom yang berlaku (seperti judul lagu, artis, dan nama) berubah. MediaSession.setRatingType()
harus ditetapkan untuk menunjukkan jenis rating yang didukung aplikasi, dan aplikasi harus menerapkanonSetRating()
. Jika tidak mendukung rating, aplikasi harus menetapkan jenis rating keRATING_NONE
.
Voice action yang Anda dukung mungkin berbeda-beda menurut jenis konten.
Jenis Konten | Tindakan yang Diperlukan |
---|---|
Musik |
Harus didukung: Putar, Jeda, Berhenti, Lewati ke Berikutnya, dan Lewati ke Sebelumnya Sangat merekomendasikan dukungan untuk: Cari |
Podcast |
Harus didukung: Putar, Jeda, Hentikan, dan Cari Merekomendasikan dukungan untuk: Langsung ke Berikutnya dan Langsung ke Sebelumnya |
Buku audio | Harus didukung: Putar, Jeda, Hentikan, dan Cari |
Radio | Harus didukung: Putar, Jeda, dan Berhenti |
Berita | Harus didukung: Putar, Jeda, Berhenti, Lewati ke Berikutnya, dan Lewati ke Sebelumnya |
Video |
Harus didukung: Putar, Jeda, Berhenti, Seek To, Rewind, dan Fast Forward Sangat merekomendasikan dukungan untuk: Langsung ke Berikutnya dan Lewati ke Sebelumnya |
Anda harus mendukung tindakan yang tercantum di atas sebanyak penawaran produk Anda izinkan, tetapi tetap merespons tindakan lain dengan baik. Misalnya, jika hanya pengguna premium memiliki kemampuan untuk kembali ke item sebelumnya, Anda mungkin menaikkan error jika pengguna paket gratis meminta Asisten untuk kembali ke item sebelumnya. Lihat bagian penanganan error untuk panduan lebih lanjut.
Contoh kueri suara yang dapat dicoba
Tabel berikut menguraikan beberapa contoh kueri yang harus Anda gunakan saat menguji penerapan Anda:
Callback MediaSession | Frasa “Ok Google” untuk digunakan | |
---|---|---|
onPlay() |
"Putar". “Lanjutkan.” |
|
onPlayFromSearch()
onPlayFromUri() |
Musik |
"Putar musik atau lagu di (nama aplikasi).” Kueri ini kosong. “Putar (lagu | artis | album | genre | playlist) di (nama aplikasi).” |
Radio | “Putar (frequency | stasiun) di (nama aplikasi).” | |
Audiobook |
“Baca buku audio saya di (nama aplikasi).” “Baca (buku audio) di (nama aplikasi).” |
|
Podcast | “Putar (podcast) di (nama aplikasi).” | |
onPause() |
“Jeda.” | |
onStop() |
“Berhenti.” | |
onSkipToNext() |
“Berikutnya (lagu | episode | trek).” | |
onSkipToPrevious() |
“Sebelumnya (lagu | episode | lagu).” | |
onSeekTo() |
“Mulai ulang.” "Majukan ## detik." “Kembali ke ## menit.” |
|
T/A (tetap
MediaMetadata
diperbarui) |
“Apa yang sedang diputar?” |
Error
Asisten menangani error dari sesi media saat terjadi, dan melaporkan
mereka kepada pengguna. Pastikan sesi media Anda memperbarui status transport dan
kode error di PlaybackState
dengan benar, seperti yang dijelaskan dalam Bekerja dengan
sesi media Anda. Asisten
mengenali semua kode {i>error<i}
yang dikembalikan oleh
getErrorCode()
Kasus yang sering kali salah ditangani
Berikut adalah beberapa contoh kasus {i>error<i} yang harus ditangani dengan dengan benar:
- Pengguna harus login
- Tetapkan kode error
PlaybackState
keERROR_CODE_AUTHENTICATION_EXPIRED
. - Setel pesan error
PlaybackState
. - Jika diperlukan untuk pemutaran, tetapkan status
PlaybackState
keSTATE_ERROR
, jika tidak, pertahankan sisaPlaybackState
sebagaimana adanya.
- Tetapkan kode error
- Pengguna meminta tindakan yang tidak tersedia
- Tetapkan kode error
PlaybackState
dengan benar. Misalnya, setel atributPlaybackState
hinggaERROR_CODE_NOT_SUPPORTED
jika tindakan tidak didukung atauERROR_CODE_PREMIUM_ACCOUNT_REQUIRED
jika tindakan dilindungi dengan login. - Setel pesan error
PlaybackState
. - Pertahankan sisa
PlaybackState
sebagaimana adanya.
- Tetapkan kode error
- Pengguna meminta konten yang tidak tersedia di aplikasi
- Tetapkan kode error
PlaybackState
dengan benar. Misalnya, gunakanERROR_CODE_NOT_AVAILABLE_IN_REGION
- Setel pesan error
PlaybackState
. - Setel status
PlaybackSate
keSTATE_ERROR
untuk mengganggu pemutaran, jika tidak, pertahankan sisaPlaybackState
sebagaimana adanya.
- Tetapkan kode error
- Pengguna meminta konten yang tidak memiliki kecocokan persis. Sebagai contoh,
pengguna paket gratis yang meminta konten hanya tersedia untuk pengguna tingkat premium.
- Sebaiknya jangan menampilkan error, dan sebaiknya prioritaskan menemukan sesuatu yang mirip untuk dimainkan. Asisten akan menangani ucapan paling banyak respons suara yang relevan sebelum pemutaran dimulai.
Memulai pemutaran dengan intent
Asisten bisa meluncurkan aplikasi audio atau video dan memulai pemutaran dengan mengirimkan dengan deep link.
Intent dan deep link-nya dapat berasal dari sumber berbeda:
- Saat Asisten memulai aplikasi seluler, mereka dapat menggunakan Google {i>search<i} untuk mengambil konten yang telah ditandai menyediakan tindakan menonton dengan link.
- Saat Asisten memulai aplikasi TV, aplikasi Anda harus menyertakan
Penyedia Penelusuran TV
untuk mengekspos URI untuk konten media. Asisten mengirimkan kueri ke
penyedia konten yang harus mengembalikan intent yang berisi URI untuk deep link dan
tindakan opsional.
Jika kueri menampilkan tindakan dalam intent,
Asisten mengirimkan tindakan itu dan URI kembali ke aplikasi Anda.
Jika penyedia tidak menyebutkan
tindakan, Asisten akan menambahkan
ACTION_VIEW
ke Intent.
Asisten menambahkan EXTRA_START_PLAYBACK
ekstra dengan nilai true
ke intent yang dikirimkannya ke aplikasi Anda. Aplikasi Anda akan memulai pemutaran saat
menerima intent dengan EXTRA_START_PLAYBACK
.
Menangani intent selagi aktif
Pengguna dapat meminta Asisten untuk memutar sesuatu saat aplikasi Anda masih diputar konten dari permintaan sebelumnya. Ini berarti aplikasi Anda dapat menerima intent baru untuk memulai pemutaran saat aktivitas pemutarannya sudah diluncurkan dan aktif.
Aktivitas yang mendukung intent dengan deep link harus mengganti
onNewIntent()
untuk menangani permintaan baru.
Saat memulai pemutaran, Asisten mungkin menambahkan tambahan
laporan
ke intent yang dikirimkannya ke aplikasi Anda. Secara khusus, cara ini
dapat menambahkan
FLAG_ACTIVITY_CLEAR_TOP
atau
FLAG_ACTIVITY_NEW_TASK
atau keduanya. Meskipun kode Anda
tidak perlu menangani penanda ini, sistem Android akan meresponsnya.
Hal ini mungkin memengaruhi perilaku aplikasi Anda saat permintaan pemutaran kedua dengan URI baru masuk
sementara URI sebelumnya masih diputar. Dalam kasus semacam ini, ada baiknya untuk menguji respons aplikasi Anda. Anda dapat menggunakan perintah adb
alat baris untuk menyimulasikan situasi (konstanta 0x14000000
adalah bitwise boolean OR dari kedua flag):
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<first_uri>"' -f 0x14000000
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<second_uri>"' -f 0x14000000
Pemutaran dari layanan
Jika aplikasi Anda memiliki
media browser service
yang mengizinkan koneksi dari Asisten,
Asisten bisa memulai aplikasi dengan berkomunikasi dengan perangkat
media session
.
Layanan browser media tidak boleh meluncurkan Aktivitas.
Asisten akan meluncurkan Aktivitas berdasarkan PendingIntent
yang Anda tentukan
dengan setSessionActivity().
Pastikan untuk menetapkan MediaSession.Token saat Anda melakukan inisialisasi layanan browser media. Jangan lupa menyetel tindakan pemutaran yang didukung setiap saat, termasuk selama inisialisasi. Asisten mengharapkan media Anda aplikasi untuk menyetel tindakan pemutaran sebelum Asisten mengirimkan pemutaran pertama perintah.
Untuk memulai dari layanan, Asisten akan menerapkan API klien browser media. Ia melakukan panggilan TransportControls yang memicu callback tindakan PLAY pada sesi media aplikasi Google.
Diagram berikut menunjukkan urutan panggilan yang dihasilkan Asisten dan callback sesi media yang sesuai. (Callback persiapan hanya dikirim jika aplikasi Anda mendukungnya.) Semua panggilan bersifat asinkron. Asisten tidak menunggu respons apa pun dari aplikasi Anda.
Saat pengguna mengeluarkan perintah suara untuk memutar media, Asisten merespons dengan pengumuman singkat. Segera setelah pengumuman itu selesai, Asisten akan mengeluarkan tindakan PLAY. Asisten tidak menunggu status pemutaran tertentu.
Jika aplikasi Anda mendukung tindakan ACTION_PREPARE_*
, Asisten akan memanggil tindakan PREPARE
sebelum memulai pengumuman.
Membuat koneksi ke MediaBrowserService
Agar dapat menggunakan layanan untuk memulai aplikasi Anda, Asisten harus dapat terhubung ke MediaBrowserService dan
mengambil MediaSession.Token-nya. Permintaan koneksi ditangani dalam
onGetRoot()
. Ada dua cara untuk menangani permintaan:
- Menerima semua permintaan koneksi
- Menerima permintaan koneksi dari aplikasi Asisten saja
Menerima semua permintaan koneksi
Anda harus menampilkan BrowserRoot agar dapat mengizinkan Asisten untuk mengirimkan perintah ke sesi media Anda. Cara termudahnya adalah dengan mengizinkan semua aplikasi MediaBrowser untuk tersambung ke MediaBrowserService Anda. Anda harus menampilkan BrowserRoot bukan null. Berikut adalah kode yang berlaku dari Universal Music Player:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? { // To ensure you are not allowing any arbitrary app to browse your app's contents, you // need to check the origin: if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) { // If the request comes from an untrusted package, return an empty browser root. // If you return null, then the media browser will not be able to connect and // no further calls will be made to other media browsing methods. Log.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. Returning empty " + "browser root so all apps can use MediaController. $clientPackageName") return MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null) } // Return browser roots for browsing... }
Java
@Override public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { // To ensure you are not allowing any arbitrary app to browse your app's contents, you // need to check the origin: if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) { // If the request comes from an untrusted package, return an empty browser root. // If you return null, then the media browser will not be able to connect and // no further calls will be made to other media browsing methods. LogHelper.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. " + "Returning empty browser root so all apps can use MediaController." + clientPackageName); return new MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null); } // Return browser roots for browsing... }
Menerima paket dan tanda tangan aplikasi Asisten
Anda dapat secara eksplisit mengizinkan Asisten untuk tersambung ke layanan browser media dengan memeriksa nama dan tanda tangan paketnya. Aplikasi Anda akan menerima nama paket dalam metode onGetRoot MediaBrowserService. Anda harus menampilkan BrowserRoot agar dapat mengizinkan Asisten untuk mengirimkan perintah ke sesi media Anda. Tujuan Pemutar Musik Universal contoh mempertahankan daftar nama paket dan tanda tangan yang diketahui. Di bawah ini adalah nama dan tanda tangan paket yang digunakan oleh Asisten Google.
<signature name="Google" package="com.google.android.googlequicksearchbox">
<key release="false">19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00</key>
<key release="true">f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83</key>
</signature>
<signature name="Google Assistant on Android Automotive OS" package="com.google.android.carassistant">
<key release="false">17:E2:81:11:06:2F:97:A8:60:79:7A:83:70:5B:F8:2C:7C:C0:29:35:56:6D:46:22:BC:4E:CF:EE:1B:EB:F8:15</key>
<key release="true">74:B6:FB:F7:10:E8:D9:0D:44:D3:40:12:58:89:B4:23:06:A6:2C:43:79:D0:E5:A6:62:20:E3:A6:8A:BF:90:E2</key>
</signature>