הוספת תכונות ליבה למקלט Android TV

בדף הזה מופיעים קטעי קוד ותיאורים של התכונות הזמינות להתאמה אישית של אפליקציית Android TV Receiver.

הגדרת ספריות

כדי להפוך את ממשקי ה-API של Cast Connect לזמינים לאפליקציה ל-Android TV:

Android
  1. פותחים את הקובץ build.gradle בתוך ספריית המודול של האפליקציה.
  2. מוודאים ש-google() נכלל ב-repositories שמופיע.
      repositories {
        google()
      }
  3. בהתאם לסוג המכשיר היעד של האפליקציה, מוסיפים את הגרסאות העדכניות ביותר של הספריות ליחסי התלות:
    • באפליקציית Android Receiver:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast-tv:21.1.1'
          implementation 'com.google.android.gms:play-services-cast:22.0.0'
        }
    • באפליקציית Sender ל-Android:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.1.1'
          implementation 'com.google.android.gms:play-services-cast-framework:22.0.0'
        }
    חשוב לעדכן את מספר הגרסה הזה בכל פעם שהשירותים מתעדכנים.
  4. שומרים את השינויים ולוחצים על Sync Project with Gradle Files בסרגל הכלים.
iOS
  1. מוודאים שה-Podfile מטרגט את google-cast-sdk בגרסה 4.8.3 ואילך
  2. טירגוט ל-iOS מגרסה 14 ואילך. פרטים נוספים זמינים בהערות הגרסה.
      platform: ios, '14'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.8.3'
      end
אינטרנט
  1. נדרש דפדפן Chromium מגרסה M87 ואילך.
  2. הוספה של ספריית Web Sender API לפרויקט
      <script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>

דרישת AndroidX

בגרסאות חדשות של Google Play Services, האפליקציה צריכה להיות מעודכנת כדי להשתמש במרחב השמות androidx. פועלים לפי ההוראות למיגרציה ל-AndroidX.

אפליקציה ל-Android TV – תנאים מוקדמים

כדי לתמוך ב-Cast Connect באפליקציה ל-Android TV, צריך ליצור אירועים מסשן מדיה ולתמוך בהם. הנתונים שמתקבלים מסשן המדיה מספקים את המידע הבסיסי – למשל המיקום, מצב ההפעלה וכו' – לגבי סטטוס המדיה. סשן המדיה משמש גם את הספרייה של Cast Connect כדי לסמן מתי היא קיבלה הודעות מסוימות מהשולח, כמו 'השהיה'.

מידע נוסף על סשן מדיה ועל איך מאתחלים סשן מדיה זמין במדריך לעבודה עם סשן מדיה.

מחזור החיים של סשן מדיה

האפליקציה צריכה ליצור סשן מדיה כשההפעלה מתחילה ולשחרר אותו כשאי אפשר יותר לשלוט בו. לדוגמה, אם האפליקציה שלכם היא אפליקציית וידאו, עליכם לסיים את הסשן כשהמשתמש יוצא מפעילות ההפעלה – על ידי בחירה באפשרות 'חזרה' כדי לעיין בתוכן אחר או על ידי העברת האפליקציה למצב רקע. אם האפליקציה שלכם היא אפליקציית מוזיקה, עליכם לסיים את הסשן כשהאפליקציה כבר לא מפעילה מדיה.

עדכון סטטוס הסשן

הנתונים בסשן המדיה צריכים להיות מעודכנים בהתאם לסטטוס של הנגן. לדוגמה, כשהפעלה מושהית, צריך לעדכן את מצב ההפעלה ואת הפעולות הנתמכות. בטבלאות הבאות מפורטות המצבים שאתם אחראים לעדכן.

MediaMetadataCompat

שדה מטא-נתונים תיאור
METADATA_KEY_TITLE (חובה) כותרת המדיה.
METADATA_KEY_DISPLAY_SUBTITLE כותרת המשנה.
METADATA_KEY_DISPLAY_ICON_URI כתובת ה-URL של הסמל.
METADATA_KEY_DURATION (חובה) משך המדיה.
METADATA_KEY_MEDIA_URI מזהה התוכן.
METADATA_KEY_ARTIST האומן או האומנית.
METADATA_KEY_ALBUM האלבום.

PlaybackStateCompat

שיטת הבדיקה הנדרשת תיאור
setActions() מגדיר את הפקודות הנתמכות לשימוש במדיה.
setState() מגדירים את מצב ההפעלה ואת המיקום הנוכחי.

MediaSessionCompat

שיטת הבדיקה הנדרשת תיאור
setRepeatMode() מגדיר את מצב החזרה.
setShuffleMode() הגדרת מצב ההפעלה האקראית.
setMetadata() הגדרת מטא-נתונים של מדיה.
setPlaybackState() הגדרת מצב ההפעלה.
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);
}

טיפול בבקרת התעבורה

באפליקציה צריך להטמיע קריאה חוזרת (callback) לבקרת התעבורה של סשן המדיה. בטבלה הבאה מפורטות פעולות בקרת התעבורה שהן צריכות לטפל בהן:

MediaSessionCompat.Callback

פעולות תיאור
onPlay() המשך
onPause() השהיה
onSeekTo()‎ דילוג למיקום מסוים
onStop() הפסקת המדיה הנוכחית
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());

הגדרת תמיכה ב-Cast

כשבקשת הפעלה נשלחת על ידי אפליקציית השולח, נוצרת כוונה עם מרחבים משותפים של אפליקציות. האפליקציה שלכם אחראית לטפל בו וליצור מופע של האובייקט CastReceiverContext כשאפליקציית הטלוויזיה מופעלת. האובייקט CastReceiverContext נדרש כדי ליצור אינטראקציה עם Cast בזמן שאפליקציית הטלוויזיה פועלת. האובייקט הזה מאפשר לאפליקציה בטלוויזיה לקבל הודעות של מדיה להעברה (cast) מכל שולחים מחוברים.

הגדרת Android TV

הוספת מסנן של כוונה להשקת מודעה

מוסיפים מסנן Intent חדש לפעילות שבה רוצים לטפל בכוונה להפעלה מאפליקציית השולח:

<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>

ציון הספק של אפשרויות הנמען

צריך להטמיע ReceiverOptionsProvider כדי לספק את 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();
  }
}

לאחר מכן מציינים את ספק האפשרויות ב-AndroidManifest:

 <meta-data
    android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />

השדה ReceiverOptionsProvider משמש כדי לספק את CastReceiverOptions כשCastReceiverContext מופעל.

ההקשר של מקלט ההעברה (cast)

מפעילים את CastReceiverContext כשהאפליקציה נוצרת:

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

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

  ...
}

מפעילים את CastReceiverContext כשהאפליקציה עוברת לחזית:

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

באפליקציות וידאו או באפליקציות שלא תומכות בהפעלה ברקע, אחרי שהאפליקציה עוברת לרקע, מקישים על stop() בCastReceiverContext:

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

בנוסף, אם האפליקציה תומכת בהפעלה ברקע, תוכלו להפעיל את הפקודה stop() ב-CastReceiverContext כשהיא תפסיק לפעול ברקע.

מומלץ מאוד להשתמש ב-LifecycleObserver מהספרייה androidx.lifecycle כדי לנהל את הקריאה ל-CastReceiverContext.start() ול-CastReceiverContext.stop(), במיוחד אם לאפליקציה המקורית יש כמה פעילויות. כך אפשר למנוע תנאי מרוץ כשקוראים ל-start() ול-stop() מפעילויות שונות.

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">

קישור MediaSession ל-MediaManager

כשיוצרים MediaSession, צריך גם לספק את אסימון ה-MediaSession הנוכחי ל-CastReceiverContext כדי שהוא ידע לאן לשלוח את הפקודות ולאחזר את מצב ההפעלה של המדיה:

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

כשמשחררים את MediaSession בגלל חוסר פעילות בהפעלה, צריך להגדיר אסימון null ב-MediaManager:

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

אם האפליקציה תומכת בהפעלת מדיה בזמן שהיא ברקע, במקום להפעיל את CastReceiverContext.stop() כשהאפליקציה מועברת לרקע, צריך להפעיל אותה רק כשהיא ברקע ולא מפעילה יותר מדיה. לדוגמה:

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();
  }
}

שימוש ב-Exoplayer עם Cast Connect

אם אתם משתמשים ב-Exoplayer, אתם יכולים להשתמש ב-MediaSessionConnector כדי לשמור באופן אוטומטי את הסשן ואת כל המידע הקשור, כולל מצב ההפעלה, במקום לעקוב אחרי השינויים באופן ידני.

אפשר להשתמש ב-MediaSessionConnector.MediaButtonEventHandler כדי לטפל באירועים של MediaButton באמצעות קריאה ל-setMediaButtonEventHandler(MediaButtonEventHandler), שבמקרה אחר מטופלים על ידי MediaSessionCompat.Callback כברירת מחדל.

כדי לשלב את MediaSessionConnector באפליקציה, מוסיפים את הקוד הבא לכיתה של פעילות הנגן או למקום שבו מנהלים את סשן המדיה:

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);
    ...
  }
}

הגדרת אפליקציית השליחה

הפעלת התמיכה ב-Cast Connect

אחרי שתעדכנו את אפליקציית השולח עם תמיכה ב-Cast Connect, תוכלו להצהיר על המוכנות שלה על ידי הגדרת הערך true לסימון androidReceiverCompatible ב-LaunchOptions.

Android

נדרשת גרסת play-services-cast-framework 19.0.0 ואילך.

הדגל androidReceiverCompatible מוגדר ב-LaunchOptions (שנכלל ב-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

נדרשת גרסת google-cast-sdk v4.4.8 ואילך.

הדגל androidReceiverCompatible מוגדר ב-GCKLaunchOptions (שנכלל ב-GCKCastOptions):

let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
אינטרנט

נדרשת גרסת דפדפן Chromium מגרסה M87 ואילך.

const context = cast.framework.CastContext.getInstance();
const castOptions = new cast.framework.CastOptions();
castOptions.receiverApplicationId = kReceiverAppID;
castOptions.androidReceiverCompatible = true;
context.setOptions(castOptions);

הגדרת מסוף הפיתוח של Cast

הגדרת אפליקציית Android TV

מוסיפים את שם החבילה של אפליקציית Android TV ב-Cast Developer Console כדי לשייך אותה למזהה של אפליקציית Cast.

רישום מכשירים למפתחים

במסוף הפיתוח של Cast, רושמים את המספר הסידורי של מכשיר Android TV שבו תשתמשו לפיתוח.

ללא רישום, Cast Connect יפעל רק באפליקציות שהותקנו מחנות Google Play, מטעמי אבטחה.

למידע נוסף על רישום מכשיר Cast או Android TV לפיתוח Cast, אפשר לעיין בדף הרישום.

טעינה של מדיה

אם כבר הטמעתם תמיכה בקישורי עומק באפליקציה ל-Android TV, צריכה להיות הגדרה דומה במניפסט של 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>

טעינה לפי ישות בשולח

בצד השולח, אפשר להעביר את הקישור המעמיק על ידי הגדרת השדה entity בפרטי המדיה של בקשת הטעינה:

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)
אינטרנט

נדרשת גרסת דפדפן Chromium מגרסה M87 ואילך.

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);

פקודת הטעינה נשלחת באמצעות כוונה (intent) עם הקישור המעמיק ושם החבילה שהגדרתם במסוף הפיתוח.

הגדרת פרטי הכניסה ל-ATV בשולח

יכול להיות שאפליקציית Web Receiver ואפליקציית Android TV תומכות בקישור עומק וב-credentials שונים (לדוגמה, אם אתם מטפלים באימות בצורה שונה בשתי הפלטפורמות). כדי לטפל בבעיה, אפשר לספק entity ו-credentials חלופיים ל-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)
אינטרנט

נדרשת גרסת דפדפן Chromium מגרסה M87 ואילך.

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);

אם אפליקציית Web Receiver מופעלת, היא משתמשת ב-entity וב-credentials בבקשת הטעינה. עם זאת, אם האפליקציה ל-Android TV מופעלת, ה-SDK מבטל את ההגדרות של entity ו-credentials ומחליף אותן ב-atvEntity וב-atvCredentials (אם צוינו).

טעינה באמצעות Content ID או MediaQueueData

אם אתם לא משתמשים ב-entity או ב-atvEntity, ואתם משתמשים ב-Content ID או בכתובת ה-URL של התוכן ב'פרטי המדיה', או משתמשים בנתוני הבקשה המפורטים יותר של טעינת המדיה, תצטרכו להוסיף את מסנן הכוונה המוגדר מראש הבא לאפליקציה ל-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>

בצד השולח, בדומה לטעינה לפי ישות, אפשר ליצור בקשת טעינה עם פרטי התוכן ולקרוא ל-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)
אינטרנט

נדרשת גרסת דפדפן Chromium מגרסה M87 ואילך.

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);

טיפול בבקשות טעינה

כדי לטפל בבקשות הטעינה האלה בפעילות, צריך לטפל בכוונות בקריאות החזרה (callbacks) של מחזור החיים של הפעילות:

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.
    ...
  }
}

אם MediaManager מזהה שהכוונה היא כוונה לטעינה, היא מחלצת אובייקט MediaLoadRequestData מהכוונה ומפעילה את MediaLoadCommandCallback.onLoad(). כדי לטפל בבקשת הטעינה, צריך לשנות את השיטה הזו. צריך לרשום את הקריאה החוזרת לפני שמפעילים את MediaManager.onNewIntent() (מומלץ להשתמש בשיטה 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);
}

כדי לעבד את כוונת הטעינה, אפשר לנתח את הכוונה למבני הנתונים שהגדרנו (MediaLoadRequestData לבקשות טעינה).

תמיכה בפקודות מדיה

תמיכה בפקדי הפעלה בסיסיים

פקודות אינטגרציה בסיסיות כוללות את הפקודות שתואמות לסשן המדיה. ההודעות על הפקודות האלה מתקבלות באמצעות קריאות חזרה (callbacks) של סשן המדיה. כדי לתמוך בכך, צריך לרשום קריאה חוזרת (callback) לסשן המדיה (יכול להיות שאתם כבר עושים זאת).

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());

תמיכה בפקודות שליטה ב-Cast

יש פקודות העברה (cast) שלא זמינות ב-MediaSession, כמו skipAd() או setActiveMediaTracks(). בנוסף, צריך להטמיע כאן כמה פקודות של תורים כי התור של Cast לא תואם באופן מלא לתור MediaSession.

Kotlin
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())
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());

ציון הפקודות הנתמכות בנושא מדיה

בדומה למכשיר הקולט להעברה (cast), באפליקציה ל-Android TV צריך לציין אילו פקודות נתמכות, כדי שהשולחים יוכלו להפעיל או להשבית אמצעי בקרה מסוימים בממשק המשתמש. לפקודות שנכללות ב-MediaSession, צריך לציין את הפקודות ב-PlaybackStateCompat. צריך לציין פקודות נוספות ב-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);

הסתרת לחצנים לא נתמכים

אם אפליקציית Android TV תומכת רק בשליטה בסיסית על מדיה, אבל אפליקציית Web Receiver תומכת בשליטה מתקדמת יותר, עליכם לוודא שאפליקציית השולח פועלת בצורה תקינה כשמבצעים העברה (cast) לאפליקציית Android TV. לדוגמה, אם אפליקציית Android TV לא תומכת בשינוי קצב ההפעלה, אבל אפליקציית Web Receiver כן תומכת בכך, עליכם להגדיר את הפעולות הנתמכות בצורה נכונה בכל פלטפורמה ולוודא שאפליקציית השולח מרינדרת את ממשק המשתמש בצורה תקינה.

שינוי של MediaStatus

כדי לתמוך בתכונות מתקדמות כמו טראקים, מודעות, שידורים חיים והוספה לרשימת הצפייה, אפליקציית Android TV צריכה לספק מידע נוסף שלא ניתן לקבל דרך MediaSession.

כדי לעשות זאת, אנחנו מספקים את הכיתה MediaStatusModifier. MediaStatusModifier תמיד יפעל במהירות MediaSession שהגדרתם ב-CastReceiverContext.

כדי ליצור ולשדר 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();

ספריית הלקוח שלנו תקבל את הערך הבסיסי MediaStatus מ-MediaSession, ואפליקציית Android TV יכולה לציין סטטוס נוסף ולשנות את הסטטוס באמצעות המשתנה MediaStatus.

אפשר להגדיר חלק מהמצבים והמטא-נתונים גם ב-MediaSession וגם ב-MediaStatusModifier. מומלץ מאוד להגדיר אותם רק ב-MediaSession. עדיין אפשר להשתמש במשנה כדי לשנות את המצבים ב-MediaSession, אבל לא מומלץ לעשות זאת כי לסטטוס במשנה תמיד יש עדיפות גבוהה יותר מאשר לערכים שסופקו על ידי MediaSession.

תיעוד נתוני MediaStatus לפני השליחה

בדומה ל-SDK של מקלט האינטרנט, אם רוצים לבצע שינויים אחרונים לפני השליחה, אפשר לציין MediaStatusInterceptor כדי לעבד את MediaStatus שרוצים לשלוח. אנחנו מעבירים את הערך של MediaStatusWriter כדי לבצע פעולות על MediaStatus לפני שהוא נשלח.

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\"}"));
    }
});

טיפול בפרטי הכניסה של המשתמשים

יכול להיות שאפליקציית Android TV תאפשר רק למשתמשים מסוימים להפעיל את הסשן באפליקציה או להצטרף אליו. לדוגמה, אפשר לאפשר לשולח להתחיל או להצטרף רק אם:

  • באפליקציה השולחת צריך להיות חשבון ופרופיל זהים לאלה של אפליקציית ה-ATV.
  • האפליקציה השולחת מחוברת לאותו חשבון, אבל לפרופיל שונה מזה של אפליקציית ה-ATV.

אם האפליקציה שלכם יכולה לטפל במספר משתמשים או במשתמשים אנונימיים, תוכלו לאפשר לכל משתמש להצטרף לסשן ה-ATV. אם המשתמש מספק פרטי כניסה, אפליקציית ה-ATV צריכה לטפל בפרטי הכניסה שלו כדי שאפשר יהיה לעקוב בצורה נכונה אחרי ההתקדמות שלו ונתוני משתמשים אחרים.

כשאפליקציית השולח מופעלת או מצטרפת לאפליקציה ב-Android TV, אפליקציית השולח צריכה לספק את פרטי הכניסה שמייצגים את מי שמצטרף לסשן.

לפני ששולח מפעיל את האפליקציה ב-Android TV ומצטרף אליה, אפשר לציין בודק הפעלה כדי לבדוק אם פרטי הכניסה של השולח מותרים. אם לא, ה-SDK של Cast Connect יעבור להפעלת מקלט האינטרנט.

נתוני פרטי הכניסה להפעלת האפליקציה של השולח

בצד השולח, אפשר לציין את השדה CredentialsData כדי לייצג את מי שמצטרף לסשן.

השדה credentials הוא מחרוזת שהמשתמש יכול להגדיר, כל עוד אפליקציית ה-ATV יכולה להבין אותה. השדה credentialsType מגדיר את הפלטפורמה שממנה מגיע הערך CredentialsData, או יכול להיות ערך מותאם אישית. כברירת מחדל, הוא מוגדר לפלטפורמה שממנה הוא נשלח.

הערך של CredentialsData מועבר לאפליקציה ל-Android TV רק במהלך ההפעלה או במהלך ההצטרפות. אם תגדירו אותו שוב בזמן החיבור, הוא לא יועבר לאפליקציה ל-Android TV. אם השולח מחליף את הפרופיל בזמן החיבור, תוכלו להישאר בסשן או להתקשר למספר SessionManager.endCurrentCastSession(boolean stopCasting) אם לדעתכם הפרופיל החדש לא תואם לסשן.

אפשר לאחזר את הערך של CredentialsData לכל שולח באמצעות getSenders ב-CastReceiverContext כדי לקבל את הערך של SenderInfo, getCastLaunchRequest() כדי לקבל את הערך של CastLaunchRequest ואז getCredentialsData().

Android

נדרשת גרסת play-services-cast-framework 19.0.0 ואילך.

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

נדרשת גרסת google-cast-sdk v4.8.3 ואילך.

אפשר להפעיל את הפונקציה הזו בכל שלב אחרי שמגדירים את האפשרויות: GCKCastContext.setSharedInstanceWith(options).

GCKCastContext.sharedInstance().setLaunch(
    GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
אינטרנט

נדרשת גרסת דפדפן Chromium מגרסה M87 ואילך.

אפשר להפעיל את הפונקציה הזו בכל שלב אחרי שמגדירים את האפשרויות: cast.framework.CastContext.getInstance().setOptions(options);.

let credentialsData =
    new chrome.cast.CredentialsData("{\"userId\": \"abc\"}");
cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);

הטמעת כלי לבדיקת בקשות להשקת טלוויזיות חכמות

הערך CredentialsData מועבר לאפליקציה של Android TV כששולח מנסה להפעיל את הפגישה או להצטרף אליה. אפשר להטמיע LaunchRequestChecker. כדי לאשר או לדחות את הבקשה הזו.

אם הבקשה נדחית, מקלט האינטרנט נטען במקום להפעיל את אפליקציית ה-ATV באופן מקורי. צריך לדחות בקשה אם ה-ATV לא יכול לטפל במשתמש שמבקש להפעיל או להצטרף. לדוגמה, משתמש אחר מחובר לאפליקציית ATV מאשר המשתמש שמבצע את הבקשה, והאפליקציה לא יכולה לטפל בהחלפת פרטי הכניסה. לחלופין, אין משתמש שמחובר כרגע לאפליקציית ATV.

אם הבקשה תאושר, אפליקציית ATV תופעל. אתם יכולים להתאים אישית את ההתנהגות הזו בהתאם ליכולת של האפליקציה לשלוח בקשות טעינה כשהמשתמש לא מחובר לאפליקציה לטלוויזיה או אם יש אי-התאמה של המשתמש. אפשר להתאים אישית את ההתנהגות הזו באופן מלא ב-LaunchRequestChecker.

יוצרים מחלקה שמטמיעה את הממשק CastReceiverOptions.LaunchRequestChecker:

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;
}

לאחר מכן מגדירים אותו ב-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();
  }
}

כשתבחרו ב-true ב-LaunchRequestChecker, תוכלו להפעיל את אפליקציית ATV ואת אפליקציית מקלט האינטרנט.false

שליחה וקבלה של הודעות בהתאמה אישית

פרוטוקול Cast מאפשר לשלוח הודעות מחרוזות בהתאמה אישית בין השולחים לאפליקציית המקבל. לפני שמפעילים את CastReceiverContext, צריך לרשום מרחבים משותפים לשמות (ערוצים) כדי לשלוח הודעות.

Android TV – ציון מרחב שמות מותאם אישית

במהלך ההגדרה, צריך לציין את מרחבי השמות הנתמכים ב-CastReceiverOptions:

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 – שליחת הודעות

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 – קבלת הודעות ממרחב שמות מותאם אישית

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());