Menambahkan Fitur Inti ke Penerima Android TV

Halaman ini berisi cuplikan kode dan deskripsi fitur yang tersedia untuk menyesuaikan aplikasi Penerima TV Android.

Mengonfigurasi library

Agar Cast Connect API tersedia untuk aplikasi Android TV:

Android
  1. Buka file build.gradle di dalam direktori modul aplikasi Anda.
  2. Pastikan google() disertakan dalam repositories yang tercantum.
      repositories {
        google()
      }
  3. 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.0'
          implementation 'com.google.android.gms:play-services-cast:21.5.0'
        }
    • Untuk aplikasi Android Sender:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.1.0'
          implementation 'com.google.android.gms:play-services-cast-framework:21.5.0'
        }
    Pastikan Anda mengupdate nomor versi ini setiap kali layanan diupdate.
  4. Simpan perubahan, lalu klik Sync Project with Gradle Files di toolbar.
iOS
  1. Pastikan Anda Podfile menargetkan google-cast-sdk 4.8.1 atau yang lebih tinggi
  2. Menargetkan 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.1'
      end
Web
  1. Memerlukan browser Chromium versi M87 atau yang lebih tinggi.
  2. 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

Layanan Google Play versi baru mengharuskan aplikasi diupdate agar dapat digunakan 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 sinyal ketika telah menerima pesan tertentu dari pengirim, seperti jeda.

Untuk informasi selengkapnya tentang sesi media dan cara menginisialisasi sesi media, lihat menggunakan panduan sesi media.

Siklus proses sesi media

Aplikasi Anda harus membuat sesi media saat pemutaran dimulai dan merilisnya saat hal itu 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 memutar aplikasi di latar belakang. Jika adalah aplikasi musik, Anda harus merilisnya saat aplikasi tidak lagi memutar lainnya.

Memperbarui status sesi

Data di sesi media Anda harus terus diperbarui dengan status web. Misalnya, saat pemutaran dijeda, Anda harus memperbarui pemutaran status serta tindakan yang didukung. Tabel berikut mencantumkan status Anda bertanggung jawab untuk tetap {i>up-to-date<i}.

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 Sang seniman.
METADATA_KEY_ALBUM Album.

PlaybackStateCompat

Metode yang Diperlukan Deskripsi
setActions() Menyetel perintah media yang didukung.
setState() Tetapkan status pemutaran dan posisi saat ini.

MediaSessionCompat

Metode yang Diperlukan Deskripsi
setRepeatMode() Menyetel mode berulang.
setShuffleMode() Menyetel mode acak.
setMetadata() Menetapkan metadata media.
setPlaybackState() Menetapkan status pemutaran.
Kotlin
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)
}
Java
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 transport

Aplikasi Anda harus menerapkan callback kontrol pengangkutan sesi media. Tujuan tabel berikut menunjukkan tindakan kontrol transport yang perlu ditangani:

MediaSessionCompat.Callback

Tindakan Deskripsi
onPlay() Lanjutkan
onPause() Jeda
onSeekTo() Mencari ke posisi
onStop() Hentikan media saat ini
Kotlin
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() );
Java
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 akan dibuat dengan namespace aplikasi. Aplikasi Anda bertanggung jawab untuk menanganinya dan membuat instance CastReceiverContext saat aplikasi TV diluncurkan. Objek CastReceiverContext diperlukan untuk berinteraksi dengan Cast saat aplikasi TV berjalan. Objek ini mengaktifkan TV Anda aplikasi Google untuk menerima pesan media Cast yang berasal dari pengirim yang terhubung.

Penyiapan Android TV

Menambahkan filter intent peluncuran

Tambahkan filter intent baru ke aktivitas yang ingin Anda tangani peluncurannya 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 perlu menerapkan ReceiverOptionsProvider untuk menyediakan CastReceiverOptions:

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
          .setStatusText("My App")
          .build()
    }
}
Java
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 transmisi

Lakukan inisialisasi pada CastReceiverContext saat aplikasi Anda dibuat:

Kotlin
override fun onCreate() {
  CastReceiverContext.initInstance(this)

  ...
}
Java
@Override
public void onCreate() {
  CastReceiverContext.initInstance(this);

  ...
}

Mulai CastReceiverContext saat aplikasi berpindah ke latar depan:

Kotlin
CastReceiverContext.getInstance().start()
Java
CastReceiverContext.getInstance().start();

Telepon stop() di CastReceiverContext setelah aplikasi berpindah ke latar belakang untuk aplikasi video atau aplikasi yang tidak mendukung pemutaran di latar belakang:

Kotlin
// Player has stopped.
CastReceiverContext.getInstance().stop()
Java
// Player has stopped.
CastReceiverContext.getInstance().stop();

Selain itu, jika aplikasi Anda mendukung pemutaran di latar belakang, panggil stop() di CastReceiverContext saat berhenti diputar saat berada di latar belakang.

Kami sangat menyarankan Anda menggunakan LifecycleObserver dari androidx.lifecycle library untuk mengelola panggilan CastReceiverContext.start() dan CastReceiverContext.stop(), terutama jika aplikasi native memiliki beberapa aktivitas. Hal ini dapat menghindari ras kondisi saat Anda memanggil start() dan stop() dari aktivitas berbeda.

Kotlin
// 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())
  }
}
Java
// 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 Anda membuat MediaSession, Anda juga harus memberikan token MediaSession saat ini untuk CastReceiverContext agar tahu ke mana harus mengirim perintah dan mengambil status pemutaran media:

Kotlin
val mediaManager: MediaManager = receiverContext.getMediaManager()
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
Java
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

Saat merilis MediaSession karena pemutaran tidak aktif, Anda harus menyetel token null aktif MediaManager:

Kotlin
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
Java
myPlayer.stop();
mediaSession.release();
mediaManager.setSessionCompatToken(null);

Jika aplikasi Anda mendukung pemutaran media saat aplikasi berada di latar belakang, menelepon CastReceiverContext.stop() ketika aplikasi dikirim ke latar belakang, sebaiknya hanya panggil ketika aplikasi berjalan di latar belakang dan tidak lagi memutar media. Contoh:

Kotlin
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()
  }
}
Java
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 mengelola sesi dan semua informasi terkait secara otomatis, termasuk status pemutaran, bukan melacak perubahan secara manual.

MediaSessionConnector.MediaButtonEventHandler dapat digunakan untuk menangani peristiwa MediaButton dengan memanggil setMediaButtonEventHandler(MediaButtonEventHandler) yang biasanya ditangani oleh MediaSessionCompat.Callback secara {i>default<i}.

Untuk mengintegrasikan MediaSessionConnector di aplikasi Anda, tambahkan kode berikut ke class aktivitas pemain atau ke mana pun Anda mengelola sesi media Anda:

Kotlin
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)
    ...
  }
}
Java
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

Aktifkan dukungan Cast Connect

Setelah mengupdate aplikasi pengirim dengan dukungan Cast Connect, Anda dapat mendeklarasikan kesiapannya dengan menetapkan androidReceiverCompatible tanda aktif LaunchOptions ke true.

Android

Memerlukan versi play-services-cast-framework 19.0.0 atau lebih tinggi.

Flag androidReceiverCompatible ditetapkan di LaunchOptions (yang merupakan bagian dari CastOptions):

Kotlin
class CastOptionsProvider : OptionsProvider {
  override fun getCastOptions(context: Context?): CastOptions {
    val launchOptions: LaunchOptions = Builder()
          .setAndroidReceiverCompatible(true)
          .build()
    return CastOptions.Builder()
          .setLaunchOptions(launchOptions)
          ...
          .build()
    }
}
Java
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();
  }
}
iOS

Memerlukan google-cast-sdk versi v4.4.8 atau 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)
Web

Memerlukan versi browser Chromium M87 atau lebih tinggi.

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.

Mendaftarkan perangkat developer

Mendaftarkan nomor seri perangkat Android TV yang akan Anda gunakan 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 Cast pengembangan web, lihat halaman pendaftaran.

Memuat media

Jika Anda telah menerapkan dukungan deep link di aplikasi Android TV, Anda seharusnya memiliki definisi serupa yang dikonfigurasi 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 berdasarkan entitas di pengirim

Di pengirim, Anda dapat meneruskan deep link dengan menyetel entity di media informasi untuk permintaan pemuatan:

Kotlin
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)
Android
Java
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);
iOS
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)
Web

Memerlukan versi browser Chromium M87 atau lebih tinggi.

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 Anda yang Anda tentukan di Konsol Play.

Menyetel kredensial ATV pada pengirim

Mungkin saja aplikasi Web Receiver dan aplikasi Android TV Anda mendukung deep link dan credentials (misalnya jika Anda menangani autentikasi secara berbeda di kedua platform). Untuk mengatasinya, Anda dapat memberikan alternatif entity dan credentials untuk Android TV:

Android
Kotlin
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)
Java
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);
iOS
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)
Web

Memerlukan versi browser Chromium M87 atau lebih tinggi.

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 menggunakan entity dan credentials di permintaan pemuatan. Namun, jika aplikasi Android TV Anda diluncurkan, SDK akan mengganti entity dan credentials dengan atvEntity dan atvCredentials Anda (jika ditentukan).

Memuat berdasarkan Content ID atau MediaQueueData

Jika Anda tidak menggunakan entity atau atvEntity, dan menggunakan Content ID atau URL Konten di Informasi Media Anda atau gunakan Pemuatan Media yang lebih mendetail Meminta Data, Anda perlu menambahkan filter intent yang telah ditentukan berikut ini di aplikasi Android TV Anda:

<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 dimuat berdasarkan entitas, Anda dapat membuat permintaan pemuatan dengan informasi konten Anda dan memanggil load().

Android
Kotlin
val mediaToLoad = MediaInfo.Builder("some-id").build()
val loadRequest = MediaLoadRequestData.Builder()
    .setMediaInfo(mediaToLoad)
    .setCredentials("user-credentials")
    ...
    .build()
remoteMediaClient.load(loadRequest)
Java
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id").build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
iOS
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)
Web

Memerlukan versi browser Chromium M87 atau lebih tinggi.

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 beban

Dalam aktivitas Anda, untuk menangani permintaan pemuatan ini, Anda harus menangani intent dalam callback siklus proses aktivitas Anda:

Kotlin
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.
    ...
  }
}
Java
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 intent adalah intent pemuatan, lalu mengekstrak MediaLoadRequestData dari intent, dan memanggil MediaLoadCommandCallback.onLoad(). Anda perlu mengganti metode ini untuk menangani permintaan pemuatan. Callback harus terdaftar sebelum MediaManager.onNewIntent() dipanggil (disarankan berada di Aktivitas atau Aplikasi onCreate() ).

Kotlin
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)
  }
Java
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 Task onLoad(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 kita menentukan (MediaLoadRequestData) untuk permintaan pemuatan).

Mendukung perintah media

Dukungan kontrol pemutaran dasar

Perintah integrasi dasar mencakup perintah yang kompatibel dengan media sesi. Perintah ini diberi tahu melalui callback sesi media. Anda harus daftarkan callback ke sesi media untuk mendukungnya (Anda mungkin melakukannya sebelumnya).

Kotlin
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())
Java
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.

Kotlin
class MyMediaCommandCallback : MediaCommandCallback() {
    override fun onSkipAd(requestData: RequestData?): Task {
        // Skip your ad
        ...
        return Tasks.forResult(null)
    }
}

val mediaManager = CastReceiverContext.getInstance().getMediaManager()
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
Java
public class MyMediaCommandCallback extends MediaCommandCallback {
  @Override
  public Task onSkipAd(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 harus menentukan perintah yang didukung, sehingga pengirim dapat mengaktifkan atau menonaktifkan kontrol UI tertentu. Sebagai perintah yang merupakan bagian dari MediaSession, menentukan perintah di PlaybackStateCompat. Perintah tambahan harus ditentukan di bagian MediaStatusModifier

Kotlin
// 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)
Java
// 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);

Sembunyikan tombol yang tidak didukung

Jika aplikasi Android TV Anda hanya mendukung kontrol media dasar, tetapi Web Receiver Anda mendukung kontrol yang lebih canggih, Anda harus memastikan bahwa aplikasi pengirim berperilaku dengan benar saat melakukan transmisi ke aplikasi Android TV. Misalnya, jika Android TV Anda aplikasi ini tidak mendukung perubahan kecepatan pemutaran sedangkan aplikasi Web Receiver Anda mendukung, Anda harus mengatur tindakan yang didukung dengan benar pada setiap platform dan memastikan aplikasi pengirim merender UI dengan benar.

Mengubah MediaStatus

Untuk mendukung fitur lanjutan seperti trek, iklan, live streaming, dan antrean, Android Anda Aplikasi TV perlu memberikan informasi tambahan yang tidak dapat dipastikan melalui MediaSession

Kami menyediakan MediaStatusModifier untuk mencapainya. MediaStatusModifier akan selalu beroperasi di MediaSession yang telah Anda tetapkan CastReceiverContext

Untuk membuat dan menyiarkan MediaStatus:

Kotlin
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier()

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData)

mediaManager.broadcastMediaStatus()
Java
MediaManager mediaManager = castReceiverContext.getMediaManager();
MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier();

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData);

mediaManager.broadcastMediaStatus();

Library klien kita akan mendapatkan MediaStatus dasar dari MediaSession, Aplikasi Android TV dapat menentukan status tambahan dan status penggantian melalui Pengubah MediaStatus.

Beberapa status dan metadata dapat menetapkan baik di MediaSession maupun MediaStatusModifier. Kami sangat menyarankan agar Anda hanya menetapkannya dalam MediaSession. Anda masih dapat menggunakan pengubah untuk mengganti status di MediaSession—hal ini tidak disarankan karena status dalam pengubah selalu memiliki prioritas lebih tinggi daripada nilai yang diberikan oleh MediaSession.

Mencegah MediaStatus sebelum mengirimkan

Sama seperti Web Receiver SDK, jika Anda ingin melakukan beberapa sentuhan akhir sebelum Anda kirimkan, Anda dapat menentukan MediaStatusInterceptor untuk memproses MediaStatus ke dikirimkan. Kita meneruskan MediaStatusWriter untuk memanipulasi MediaStatus sebelum dikirim.

Kotlin
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor {
    override fun intercept(mediaStatusWriter: MediaStatusWriter) {
      // Perform customization.
        mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}"))
    }
})
Java
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 untuk meluncurkan atau bergabung ke aplikasi sesi. Misalnya, hanya izinkan pengirim untuk meluncurkan atau bergabung jika:

  • Aplikasi pengirim login ke akun dan profil yang sama dengan aplikasi ATV.
  • Aplikasi pengirim login ke akun yang sama, tetapi profilnya berbeda dengan aplikasi ATV.

Jika aplikasi Anda dapat menangani beberapa pengguna atau pengguna anonim, Anda dapat mengizinkan pengguna untuk bergabung dalam sesi ATV. Jika pengguna memberikan kredensial, aplikasi ATV Anda perlu menangani kredensial mereka sehingga kemajuan mereka dan data pengguna lainnya dapat dilacak dengan benar.

Saat aplikasi pengirim diluncurkan atau bergabung dengan aplikasi Android TV Anda, aplikasi pengirim harus memberikan kredensial yang mewakili siapa yang bergabung dalam sesi.

Sebelum pengirim meluncurkan dan bergabung ke aplikasi Android TV, Anda dapat menentukan pemeriksa peluncuran untuk melihat apakah kredensial pengirim diizinkan. Jika tidak, Cast Connect SDK akan kembali untuk meluncurkan Web Receiver Anda.

Data kredensial peluncuran aplikasi pengirim

Di sisi pengirim, Anda dapat menentukan CredentialsData untuk mewakili bergabung dalam sesi ini.

credentials adalah string yang bisa ditetapkan oleh pengguna, selama ATV Anda aplikasi Anda dapat memahaminya. credentialsType menentukan platform mana CredentialsData berasal dari atau dapat berupa nilai kustom. Secara default, nilai ini disetel ke platform asalnya.

CredentialsData hanya diteruskan ke aplikasi Android TV selama peluncuran atau pada waktu bergabung. Jika Anda menyetelnya lagi saat Anda tersambung, parameter ini tidak akan diteruskan ke aplikasi Android TV. Jika pengirim mengalihkan profil saat terhubung, Anda bisa tetap berada dalam sesi, atau memanggil SessionManager.endCurrentCastSession(boolean stopCasting) jika menurut Anda profil baru tersebut tidak kompatibel dengan sesi tersebut.

Tujuan CredentialsData untuk setiap pengirim dapat diambil menggunakan getSenders di CastReceiverContext untuk mendapatkan SenderInfo, getCastLaunchRequest() untuk mendapatkan CastLaunchRequest, Lalu getCredentialsData().

Android

Memerlukan versi play-services-cast-framework 19.0.0 atau lebih tinggi.

Kotlin
CastContext.getSharedInstance().setLaunchCredentialsData(
    CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build()
)
Java
CastContext.getSharedInstance().setLaunchCredentialsData(
    new CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build());
iOS

Memerlukan google-cast-sdk versi v4.8.1 atau lebih tinggi.

Dapat dipanggil kapan saja setelah opsi ditetapkan: GCKCastContext.setSharedInstanceWith(options).

GCKCastContext.sharedInstance().setLaunch(
    GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Web

Memerlukan versi browser Chromium M87 atau lebih tinggi.

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

Tujuan CredentialsData diteruskan ke aplikasi Android TV Anda saat pengirim mencoba meluncurkan atau bergabung. Anda dapat menerapkan LaunchRequestChecker mengizinkan atau menolak permintaan ini.

Jika permintaan ditolak, Penerima Web akan dimuat, bukan meluncurkan secara native di aplikasi ATV. Anda harus menolak permintaan jika ATV tidak dapat menangani pengguna yang meminta untuk meluncurkan atau bergabung. Contohnya bisa berupa pengguna login ke aplikasi ATV daripada yang meminta 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 menyesuaikannya perilaku 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 dalam LaunchRequestChecker.

Buat class yang menerapkan CastReceiverOptions.LaunchRequestChecker antarmuka:

Kotlin
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.
}
Java
public class MyLaunchRequestChecker
    implements CastReceiverOptions.LaunchRequestChecker {
  @Override
  public Task checkLaunchRequestSupported(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;
}

Lalu setel di ReceiverOptionsProvider:

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(MyLaunchRequestChecker())
        .build()
  }
}
Java
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(new MyLaunchRequestChecker())
        .build();
  }
}

Menyelesaikan true dalam LaunchRequestChecker meluncurkan aplikasi ATV dan false meluncurkan aplikasi Web Receiver.

Mengirim & Menerima Pesan Kustom

Protokol Cast memungkinkan Anda mengirim pesan string khusus antara pengirim dan aplikasi penerima Anda. Anda harus mendaftarkan namespace (channel) untuk mengirim pesan sebelum melakukan inisialisasi CastReceiverContext

Android TV—Menentukan Namespace Kustom

Anda harus menentukan namespace yang didukung di CastReceiverOptions selama penyiapan:

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        .setCustomNamespaces(
            Arrays.asList("urn:x-cast:com.example.cast.mynamespace")
        )
        .build()
  }
}
Java
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

Kotlin
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
    "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
Java
// 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

Kotlin
class MyCustomMessageListener : MessageReceivedListener {
    override fun onMessageReceived(
        namespace: String, senderId: String?, message: String ) {
        ...
    }
}

CastReceiverContext.getInstance().setMessageReceivedListener(
    "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
Java
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());