Halaman ini berisi cuplikan kode dan deskripsi fitur yang tersedia untuk menyesuaikan aplikasi Penerima Android TV.
Mengonfigurasi library
Untuk menyediakan API Cast Connect bagi aplikasi Android TV Anda:
-
Buka file
build.gradle
di dalam direktori modul aplikasi Anda. -
Pastikan
google()
disertakan dalamrepositories
yang tercantum.repositories { google() }
-
Bergantung pada jenis perangkat target untuk aplikasi Anda, tambahkan versi terbaru
library ke dependensi Anda:
-
Untuk aplikasi Penerima Android:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.1' implementation 'com.google.android.gms:play-services-cast:22.1.0' }
-
Untuk aplikasi Pengirim Android:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:22.1.0' }
-
Untuk aplikasi Penerima Android:
-
Simpan perubahan dan klik
Sync Project with Gradle Files
di toolbar.
-
Pastikan
Podfile
Anda menargetkangoogle-cast-sdk
4.8.3 atau yang lebih tinggi -
Targetkan iOS 14 atau yang lebih tinggi. Lihat Catatan Rilis
untuk mengetahui detail selengkapnya.
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.3' end
- Memerlukan browser Chromium versi M87 atau yang lebih baru.
-
Menambahkan library Web Sender API ke project Anda
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
Persyaratan AndroidX
Versi baru Layanan Google Play mengharuskan aplikasi diperbarui agar dapat menggunakan
namespace androidx
. Ikuti petunjuk untuk
bermigrasi ke AndroidX.
Aplikasi Android TV—prasyarat
Untuk mendukung Cast Connect di aplikasi Android TV, Anda harus membuat dan mendukung peristiwa dari sesi media. Data yang disediakan oleh sesi media Anda memberikan informasi dasar—misalnya, posisi, status pemutaran, dll.—untuk status media Anda. Sesi media Anda juga digunakan oleh library Cast Connect untuk memberi tahu kapan pesan tertentu diterima dari pengirim, seperti jeda.
Untuk mengetahui informasi selengkapnya tentang sesi media dan cara menginisialisasi sesi media, lihat panduan penggunaan sesi media.
Siklus proses sesi media
Aplikasi Anda harus membuat sesi media saat pemutaran dimulai dan melepaskannya saat tidak dapat dikontrol lagi. Misalnya, jika aplikasi Anda adalah aplikasi video, Anda harus melepaskan sesi saat pengguna keluar dari aktivitas pemutaran—baik dengan memilih 'kembali' untuk menjelajahi konten lain atau dengan meminimalkan aplikasi. Jika aplikasi Anda adalah aplikasi musik, Anda harus melepaskannya saat aplikasi Anda tidak lagi memutar media apa pun.
Memperbarui status sesi
Data di sesi media Anda harus selalu terbaru dengan status pemutar Anda. Misalnya, saat pemutaran dijeda, Anda harus memperbarui status pemutaran serta tindakan yang didukung. Tabel berikut mencantumkan status yang harus Anda perbarui.
MediaMetadataCompat
Kolom Metadata | Deskripsi |
---|---|
METADATA_KEY_TITLE (wajib) | Judul media. |
METADATA_KEY_DISPLAY_SUBTITLE | Subtitel. |
METADATA_KEY_DISPLAY_ICON_URI | URL ikon. |
METADATA_KEY_DURATION (wajib) | Durasi media. |
METADATA_KEY_MEDIA_URI | Content ID. |
METADATA_KEY_ARTIST | Artis. |
METADATA_KEY_ALBUM | Albumnya. |
PlaybackStateCompat
Metode yang Diperlukan | Deskripsi |
---|---|
setActions() | Menetapkan perintah media yang didukung. |
setState() | Menetapkan status pemutaran dan posisi saat ini. |
MediaSessionCompat
Metode yang Diperlukan | Deskripsi |
---|---|
setRepeatMode() | Menetapkan mode ulangi. |
setShuffleMode() | Menetapkan mode acak. |
setMetadata() | Menetapkan metadata media. |
setPlaybackState() | Menetapkan status pemutaran. |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() { MediaMetadataCompat metadata = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl()) .build(); PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis()) .build(); mediaSession.setMetadata(metadata); mediaSession.setPlaybackState(playbackState); }
Menangani kontrol transportasi
Aplikasi Anda harus menerapkan callback kontrol transport sesi media. Tabel berikut menunjukkan tindakan kontrol transportasi yang perlu ditangani:
MediaSessionCompat.Callback
Tindakan | Deskripsi |
---|---|
onPlay() | Lanjutkan |
onPause() | Jeda |
onSeekTo() | Mencari posisi |
onStop() | Menghentikan media saat ini |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Mengonfigurasi dukungan Cast
Saat permintaan peluncuran dikirim oleh aplikasi pengirim, intent dibuat
dengan namespace aplikasi. Aplikasi Anda bertanggung jawab untuk menanganinya
dan membuat instance objek
CastReceiverContext
saat aplikasi TV diluncurkan. Objek CastReceiverContext
diperlukan untuk berinteraksi dengan Cast saat aplikasi TV sedang berjalan. Objek ini memungkinkan aplikasi TV Anda menerima pesan media Cast yang berasal dari pengirim terhubung mana pun.
Penyiapan Android TV
Menambahkan filter intent peluncuran
Tambahkan filter intent baru ke aktivitas yang ingin Anda tangani peluncuran intent dari aplikasi pengirim Anda:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Menentukan penyedia opsi penerima
Anda harus menerapkan
ReceiverOptionsProvider
untuk menyediakan
CastReceiverOptions
:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setStatusText("My App") .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setStatusText("My App") .build(); } }
Kemudian, tentukan penyedia opsi di AndroidManifest
Anda:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider
digunakan untuk menyediakan CastReceiverOptions
saat
CastReceiverContext
diinisialisasi.
Konteks penerima Cast
Lakukan inisialisasi
CastReceiverContext
saat aplikasi Anda dibuat:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
Mulai CastReceiverContext
saat aplikasi Anda berpindah ke latar depan:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
Panggil
stop()
di
CastReceiverContext
setelah aplikasi berpindah ke latar belakang untuk aplikasi video atau aplikasi yang tidak mendukung
pemutaran di latar belakang:
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
Selain itu, jika aplikasi Anda mendukung pemutaran di latar belakang, panggil stop()
di CastReceiverContext
saat berhenti diputar di latar belakang.
Sebaiknya gunakan LifecycleObserver dari
library androidx.lifecycle
untuk mengelola panggilan
CastReceiverContext.start()
dan
CastReceiverContext.stop()
,
terutama jika aplikasi native Anda memiliki beberapa aktivitas. Tindakan ini akan menghindari kondisi race
saat Anda memanggil start()
dan stop()
dari aktivitas yang berbeda.
// Create a LifecycleObserver class. class MyLifecycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start() } override fun onStop(owner: LifecycleOwner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop() } } // Add the observer when your application is being created. class MyApplication : Application() { fun onCreate() { super.onCreate() // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */) // Register LifecycleObserver ProcessLifecycleOwner.get().lifecycle.addObserver( MyLifecycleObserver()) } }
// Create a LifecycleObserver class. public class MyLifecycleObserver implements DefaultLifecycleObserver { @Override public void onStart(LifecycleOwner owner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start(); } @Override public void onStop(LifecycleOwner owner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop(); } } // Add the observer when your application is being created. public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */); // Register LifecycleObserver ProcessLifecycleOwner.get().getLifecycle().addObserver( new MyLifecycleObserver()); } }
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
Menghubungkan MediaSession ke MediaManager
Saat membuat
MediaSession
,
Anda juga perlu memberikan token MediaSession
saat ini ke
CastReceiverContext
agar ekstensi tersebut tahu ke mana harus mengirimkan perintah dan mengambil status pemutaran media:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
Saat merilis MediaSession
karena pemutaran tidak aktif, Anda harus menyetel token null di
MediaManager
:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
Jika aplikasi Anda mendukung pemutaran media saat aplikasi berada di latar belakang, alih-alih memanggil
CastReceiverContext.stop()
saat aplikasi Anda dikirim ke latar belakang, Anda harus memanggilnya hanya saat aplikasi Anda berada di latar belakang dan tidak lagi memutar media. Contoh:
class MyLifecycleObserver : DefaultLifecycleObserver { ... // App has moved to the background. override fun onPause(owner: LifecycleOwner) { mIsBackground = true myStopCastReceiverContextIfNeeded() } } // Stop playback on the player. private fun myStopPlayback() { myPlayer.stop() myStopCastReceiverContextIfNeeded() } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private fun myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop() } }
public class MyLifecycleObserver implements DefaultLifecycleObserver { ... // App has moved to the background. @Override public void onPause(LifecycleOwner owner) { mIsBackground = true; myStopCastReceiverContextIfNeeded(); } } // Stop playback on the player. private void myStopPlayback() { myPlayer.stop(); myStopCastReceiverContextIfNeeded(); } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private void myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop(); } }
Menggunakan Exoplayer dengan Cast Connect
Jika Anda menggunakan
Exoplayer
, Anda dapat menggunakan
MediaSessionConnector
untuk mempertahankan sesi dan semua informasi terkait secara otomatis, termasuk
status pemutaran, alih-alih melacak perubahan secara manual.
MediaSessionConnector.MediaButtonEventHandler
dapat digunakan untuk menangani peristiwa MediaButton dengan memanggil
setMediaButtonEventHandler(MediaButtonEventHandler)
yang ditangani oleh
MediaSessionCompat.Callback
secara default.
Untuk mengintegrasikan
MediaSessionConnector
di aplikasi Anda, tambahkan kode berikut ke class aktivitas pemutar atau ke tempat Anda
mengelola sesi media:
class PlayerActivity : Activity() { private var mMediaSession: MediaSessionCompat? = null private var mMediaSessionConnector: MediaSessionConnector? = null private var mMediaManager: MediaManager? = null override fun onCreate(savedInstanceState: Bundle?) { ... mMediaSession = MediaSessionCompat(this, LOG_TAG) mMediaSessionConnector = MediaSessionConnector(mMediaSession!!) ... } override fun onStart() { ... mMediaManager = receiverContext.getMediaManager() mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken()) mMediaSessionConnector!!.setPlayer(mExoPlayer) mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider) mMediaSession!!.isActive = true ... } override fun onStop() { ... mMediaSessionConnector!!.setPlayer(null) mMediaSession!!.release() mMediaManager!!.setSessionCompatToken(null) ... } }
public class PlayerActivity extends Activity { private MediaSessionCompat mMediaSession; private MediaSessionConnector mMediaSessionConnector; private MediaManager mMediaManager; @Override protected void onCreate(Bundle savedInstanceState) { ... mMediaSession = new MediaSessionCompat(this, LOG_TAG); mMediaSessionConnector = new MediaSessionConnector(mMediaSession); ... } @Override protected void onStart() { ... mMediaManager = receiverContext.getMediaManager(); mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken()); mMediaSessionConnector.setPlayer(mExoPlayer); mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider); mMediaSession.setActive(true); ... } @Override protected void onStop() { ... mMediaSessionConnector.setPlayer(null); mMediaSession.release(); mMediaManager.setSessionCompatToken(null); ... } }
Penyiapan aplikasi pengirim
Mengaktifkan dukungan Cast Connect
Setelah mengupdate aplikasi pengirim dengan dukungan Cast Connect, Anda dapat menyatakan
kesiapannya dengan menyetel
flag androidReceiverCompatible
di
LaunchOptions
ke benar (true).
Memerlukan play-services-cast-framework
versi
19.0.0
atau yang lebih baru.
Flag androidReceiverCompatible
ditetapkan di
LaunchOptions
(yang merupakan bagian dari CastOptions
):
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context?): CastOptions { val launchOptions: LaunchOptions = Builder() .setAndroidReceiverCompatible(true) .build() return CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build() } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { LaunchOptions launchOptions = new LaunchOptions.Builder() .setAndroidReceiverCompatible(true) .build(); return new CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build(); } }
Memerlukan google-cast-sdk
versi v4.4.8
atau
yang lebih tinggi.
Flag androidReceiverCompatible
ditetapkan di
GCKLaunchOptions
(yang merupakan bagian dari
GCKCastOptions
):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Memerlukan browser Chromium versi
M87
atau yang lebih baru.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Penyiapan Konsol Developer Cast
Mengonfigurasi aplikasi Android TV
Tambahkan nama paket aplikasi Android TV Anda di Konsol Developer Cast untuk mengaitkannya dengan ID Aplikasi Cast Anda.
Mendaftarkan perangkat developer
Daftarkan nomor seri perangkat Android TV yang akan Anda gunakan untuk pengembangan di Konsol Developer Cast.
Tanpa pendaftaran, Cast Connect hanya akan berfungsi untuk aplikasi yang diinstal dari Google Play Store karena alasan keamanan.
Untuk informasi lebih lanjut tentang mendaftarkan perangkat Cast atau Android TV untuk pengembangan Cast, lihat halaman pendaftaran.
Memuat media
Jika sudah menerapkan dukungan deep link di aplikasi Android TV, Anda harus mengonfigurasi definisi serupa di Manifes Android TV:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
Memuat menurut entitas di pengirim
Di pengirim, Anda dapat meneruskan deep link dengan menyetel entity
di informasi media untuk permintaan pemuatan:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Memerlukan browser Chromium versi
M87
atau yang lebih baru.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Perintah pemuatan dikirim melalui intent dengan deep link dan nama paket yang Anda tentukan di konsol developer.
Menetapkan kredensial ATV di pengirim
Mungkin aplikasi Penerima Web dan aplikasi Android TV Anda mendukung deep link dan credentials
yang berbeda (misalnya, jika Anda menangani autentikasi secara berbeda di kedua platform). Untuk mengatasi hal ini, Anda dapat menyediakan alternatif
entity
dan credentials
untuk Android TV:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Memerlukan browser Chromium versi
M87
atau yang lebih baru.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; request.atvCredentials = 'atv-user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Jika aplikasi Penerima Web diluncurkan, aplikasi tersebut akan menggunakan entity
dan credentials
dalam permintaan pemuatan. Namun, jika aplikasi Android TV Anda diluncurkan, SDK akan mengganti
entity
dan credentials
dengan atvEntity
dan atvCredentials
(jika ditentukan).
Memuat berdasarkan Content ID atau MediaQueueData
Jika Anda tidak menggunakan entity
atau atvEntity
, dan menggunakan ID Konten atau URL Konten di Informasi Media atau menggunakan Data Permintaan Pemuatan Media yang lebih mendetail, Anda harus menambahkan filter intent yang telah ditentukan sebelumnya berikut di aplikasi Android TV:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Di sisi pengirim, mirip dengan load by entity, Anda
dapat membuat permintaan pemuatan dengan informasi konten dan memanggil load()
.
val mediaToLoad = MediaInfo.Builder("some-id").build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Memerlukan browser Chromium versi
M87
atau yang lebih baru.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Menangani permintaan pemuatan
Di aktivitas Anda, untuk menangani permintaan pemuatan ini, Anda perlu menangani intent dalam callback siklus proses aktivitas Anda:
class MyActivity : Activity() { override fun onStart() { super.onStart() val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). override fun onNewIntent(intent: Intent) { val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
public class MyActivity extends Activity { @Override protected void onStart() { super.onStart(); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(getIntent())) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). @Override protected void onNewIntent(Intent intent) { MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
Jika MediaManager
mendeteksi bahwa intent adalah intent pemuatan, objek
MediaLoadRequestData
akan diekstrak dari intent, dan
MediaLoadCommandCallback.onLoad()
akan dipanggil.
Anda harus mengganti metode ini untuk menangani permintaan pemuatan. Callback harus
didaftarkan sebelum
MediaManager.onNewIntent()
dipanggil (sebaiknya menggunakan metode onCreate()
Activity atau Application).
class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback()) } } class MyMediaLoadCommandCallback : MediaLoadCommandCallback() { override fun onLoad( senderId: String?, loadRequestData: MediaLoadRequestData ): Task{ return Tasks.call { // Resolve the entity into your data structure and load media. val mediaInfo = loadRequestData.getMediaInfo() if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw MediaException( MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build() ) } myFillMediaInfo(MediaInfoWriter(mediaInfo)) myPlayerLoad(mediaInfo.getContentUrl()) // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData) ... castReceiverContext.getMediaManager().broadcastMediaStatus() // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData } } private fun myPlayerLoad(contentURL: String) { myPlayer.load(contentURL) // Update the MediaSession state. val playbackState: PlaybackStateCompat = Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis() ) ... .build() mediaSession.setPlaybackState(playbackState) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(String senderId, MediaLoadRequestData loadRequestData) { return Tasks.call(() -> { // Resolve the entity into your data structure and load media. MediaInfo mediaInfo = loadRequestData.getMediaInfo(); if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw new MediaException( new MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build()); } myFillMediaInfo(new MediaInfoWriter(mediaInfo)); myPlayerLoad(mediaInfo.getContentUrl()); // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData); ... castReceiverContext.getMediaManager().broadcastMediaStatus(); // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData; }); } private void myPlayerLoad(String contentURL) { myPlayer.load(contentURL); // Update the MediaSession state. PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis()) ... .build(); mediaSession.setPlaybackState(playbackState); }
Untuk memproses intent pemuatan, Anda dapat mengurai intent ke dalam struktur data
yang kami tentukan
(MediaLoadRequestData
untuk permintaan pemuatan).
Mendukung perintah media
Dukungan kontrol pemutaran dasar
Perintah integrasi dasar mencakup perintah yang kompatibel dengan sesi media. Perintah ini diberi tahu melalui callback sesi media. Anda perlu mendaftarkan callback ke sesi media untuk mendukung hal ini (Anda mungkin sudah melakukannya).
private class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. myPlayer.pause() } override fun onPlay() { // Resume the player and update the play state. myPlayer.play() } override fun onSeekTo(pos: Long) { // Seek and update the play state. myPlayer.seekTo(pos) } ... } mediaSession.setCallback(MyMediaSessionCallback())
private class MyMediaSessionCallback extends MediaSessionCompat.Callback { @Override public void onPause() { // Pause the player and update the play state. myPlayer.pause(); } @Override public void onPlay() { // Resume the player and update the play state. myPlayer.play(); } @Override public void onSeekTo(long pos) { // Seek and update the play state. myPlayer.seekTo(pos); } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Mendukung perintah kontrol Cast
Ada beberapa perintah Cast yang tidak tersedia di
MediaSession
,
seperti
skipAd()
atau
setActiveMediaTracks()
.
Selain itu, beberapa perintah antrean perlu diterapkan di sini karena antrean Cast tidak sepenuhnya kompatibel dengan antrean MediaSession
.
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task<Void?> { // Skip your ad ... return Tasks.forResult(null) } } val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
public class MyMediaCommandCallback extends MediaCommandCallback { @Override public TaskonSkipAd(RequestData requestData) { // Skip your ad ... return Tasks.forResult(null); } } MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
Menentukan perintah media yang didukung
Seperti penerima Cast, aplikasi Android TV Anda harus menentukan perintah mana yang didukung, sehingga pengirim dapat mengaktifkan atau menonaktifkan kontrol UI tertentu. Untuk
perintah yang merupakan bagian dari
MediaSession
,
tentukan perintah dalam
PlaybackStateCompat
.
Perintah tambahan harus ditentukan dalam
MediaStatusModifier
.
// Set media session supported commands val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build() mediaSession.setPlaybackState(playbackState) // Set additional commands in MediaStatusModifier val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
// Set media session supported commands PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build(); mediaSession.setPlaybackState(playbackState); // Set additional commands in MediaStatusModifier MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);
Menyembunyikan tombol yang tidak didukung
Jika aplikasi Android TV Anda hanya mendukung kontrol media dasar, tetapi aplikasi Penerima Web Anda mendukung kontrol yang lebih canggih, Anda harus memastikan aplikasi pengirim Anda berperilaku dengan benar saat melakukan transmisi ke aplikasi Android TV. Misalnya, jika aplikasi Android TV Anda tidak mendukung perubahan kecepatan pemutaran, tetapi aplikasi Penerima Web Anda mendukungnya, Anda harus menyetel tindakan yang didukung dengan benar di setiap platform dan memastikan aplikasi pengirim Anda merender UI dengan benar.
Memodifikasi MediaStatus
Untuk mendukung fitur lanjutan seperti trek, iklan, live, dan antrean, aplikasi Android TV Anda perlu memberikan informasi tambahan yang tidak dapat dipastikan melalui MediaSession
.
Kami menyediakan class
MediaStatusModifier
agar Anda dapat melakukannya. MediaStatusModifier
akan selalu beroperasi pada
MediaSession
yang telah Anda tetapkan di
CastReceiverContext
.
Untuk membuat dan menyiarkan
MediaStatus
:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager(); MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier(); statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData); mediaManager.broadcastMediaStatus();
Library klien kami akan mendapatkan MediaStatus
dasar dari MediaSession
, aplikasi Android TV Anda dapat menentukan status tambahan dan mengganti status melalui pengubah MediaStatus
.
Beberapa status dan metadata dapat ditetapkan di MediaSession
dan
MediaStatusModifier
. Sangat disarankan agar Anda hanya menyetelnya di
MediaSession
. Anda tetap dapat menggunakan pengubah untuk mengganti status di
MediaSession
—hal ini tidak disarankan karena status dalam pengubah selalu
memiliki prioritas yang lebih tinggi daripada nilai yang diberikan oleh MediaSession
.
Mencegat MediaStatus sebelum mengirim
Sama seperti Web Receiver SDK, jika Anda ingin melakukan sentuhan akhir sebelum
mengirim, Anda dapat menentukan
MediaStatusInterceptor
untuk memproses
MediaStatus
yang akan
dikirim. Kita meneruskan
MediaStatusWriter
untuk memanipulasi MediaStatus
sebelum dikirim.
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor { override fun intercept(mediaStatusWriter: MediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}")) } })
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() { @Override public void intercept(MediaStatusWriter mediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}")); } });
Menangani kredensial pengguna
Aplikasi Android TV Anda mungkin hanya mengizinkan pengguna tertentu meluncurkan atau bergabung ke sesi aplikasi. Misalnya, hanya izinkan peluncuran atau bergabungnya pengirim jika:
- Aplikasi pengirim login ke akun dan profil yang sama dengan aplikasi ATV.
- Aplikasi pengirim login ke akun yang sama, tetapi profil yang berbeda dengan aplikasi ATV.
Jika aplikasi Anda dapat menangani beberapa pengguna atau pengguna anonim, Anda dapat mengizinkan pengguna tambahan mana pun untuk bergabung ke sesi ATV. Jika pengguna memberikan kredensial, aplikasi ATV Anda perlu menangani kredensial mereka agar progres dan data pengguna lainnya dapat dilacak dengan benar.
Saat aplikasi pengirim Anda meluncurkan atau bergabung dengan aplikasi Android TV, aplikasi pengirim Anda harus memberikan kredensial yang merepresentasikan siapa yang bergabung dengan sesi.
Sebelum peluncuran dan bergabung dengan aplikasi Android TV Anda, Anda dapat menentukan pemeriksa peluncuran untuk melihat apakah kredensial pengirim diizinkan. Jika tidak, Cast Connect SDK akan melakukan fallback untuk meluncurkan Penerima Web Anda.
Data kredensial peluncuran aplikasi pengirim
Di sisi pengirim, Anda dapat menentukan CredentialsData
untuk menunjukkan siapa yang bergabung ke sesi.
credentials
adalah string yang dapat ditentukan pengguna, selama aplikasi ATV Anda dapat memahaminya. credentialsType
menentukan platform asal CredentialsData
atau dapat berupa nilai kustom. Secara default, setelannya adalah platform tempat pesan dikirim.
CredentialsData
hanya diteruskan ke aplikasi Android TV Anda selama waktu peluncuran atau
bergabung. Jika Anda menyetelnya lagi saat terhubung, profil tidak akan diteruskan ke aplikasi Android TV Anda. Jika pengirim Anda mengganti profil saat terhubung, Anda dapat tetap berada di sesi, atau memanggil
SessionManager.endCurrentCastSession(boolean stopCasting)
jika Anda merasa profil baru tidak kompatibel dengan sesi.
CredentialsData
untuk setiap pengirim dapat diambil menggunakan
getSenders
di
CastReceiverContext
untuk mendapatkan SenderInfo
,
getCastLaunchRequest()
untuk mendapatkan
CastLaunchRequest
,
lalu
getCredentialsData()
.
Memerlukan play-services-cast-framework
versi
19.0.0
atau yang lebih baru.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
Memerlukan google-cast-sdk
versi v4.8.3
atau
yang lebih tinggi.
Dapat dipanggil kapan saja setelah opsi ditetapkan:
GCKCastContext.setSharedInstanceWith(options)
.
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Memerlukan browser Chromium versi
M87
atau yang lebih baru.
Dapat dipanggil kapan saja setelah opsi ditetapkan:
cast.framework.CastContext.getInstance().setOptions(options);
.
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
Menerapkan pemeriksa permintaan peluncuran ATV
CredentialsData
diteruskan ke aplikasi Android TV Anda saat pengirim mencoba meluncurkan atau bergabung. Anda dapat
menerapkan
LaunchRequestChecker
.
untuk mengizinkan atau menolak permintaan ini.
Jika permintaan ditolak, Penerima Web akan dimuat, bukan diluncurkan secara native ke aplikasi ATV. Anda harus menolak permintaan jika ATV Anda tidak dapat menangani pengguna yang meminta untuk meluncurkan atau bergabung. Contohnya, pengguna yang berbeda login ke aplikasi ATV daripada yang membuat permintaan dan aplikasi Anda tidak dapat menangani pengalihan kredensial, atau tidak ada pengguna yang saat ini login ke aplikasi ATV.
Jika permintaan diizinkan, aplikasi ATV akan diluncurkan. Anda dapat menyesuaikan perilaku ini, bergantung pada apakah aplikasi Anda mendukung pengiriman permintaan pemuatan saat pengguna tidak login ke aplikasi ATV atau jika ada ketidakcocokan pengguna. Perilaku ini
dapat sepenuhnya disesuaikan di LaunchRequestChecker
.
Buat class yang mengimplementasikan antarmuka
CastReceiverOptions.LaunchRequestChecker
:
class MyLaunchRequestChecker : LaunchRequestChecker { override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task{ return Tasks.call { myCheckLaunchRequest( launchRequest ) } } } private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean { val credentialsData = launchRequest.getCredentialsData() ?: return false // or true if you allow anonymous users to join. // The request comes from a mobile device, e.g. checking user match. return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) { myCheckMobileCredentialsAllowed(credentialsData.getCredentials()) } else false // Unrecognized credentials type. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public TaskcheckLaunchRequestSupported(CastLaunchRequest launchRequest) { return Tasks.call(() -> myCheckLaunchRequest(launchRequest)); } } private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) { CredentialsData credentialsData = launchRequest.getCredentialsData(); if (credentialsData == null) { return false; // or true if you allow anonymous users to join. } // The request comes from a mobile device, e.g. checking user match. if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) { return myCheckMobileCredentialsAllowed(credentialsData.getCredentials()); } // Unrecognized credentials type. return false; }
Kemudian, tetapkan di
ReceiverOptionsProvider
:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(MyLaunchRequestChecker()) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(new MyLaunchRequestChecker()) .build(); } }
Menyelesaikan true
di
LaunchRequestChecker
meluncurkan aplikasi ATV dan false
meluncurkan aplikasi Penerima Web Anda.
Mengirim & Menerima Pesan Kustom
Protokol Cast memungkinkan Anda mengirim pesan string kustom antara pengirim dan aplikasi penerima. Anda harus mendaftarkan namespace (channel) untuk mengirim pesan di seluruhnya sebelum menginisialisasi CastReceiverContext
.
Android TV—Menentukan Namespace Kustom
Anda perlu menentukan namespace yang didukung di
CastReceiverOptions
selama penyiapan:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace") ) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace")) .build(); } }
Android TV—Mengirim Pesan
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString);
Android TV—Menerima Pesan Namespace Kustom
class MyCustomMessageListener : MessageReceivedListener { override fun onMessageReceived( namespace: String, senderId: String?, message: String ) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
class MyCustomMessageListener implements CastReceiverContext.MessageReceivedListener { @Override public void onMessageReceived( String namespace, String senderId, String message) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());