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

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

הגדרת הספריות

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

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

דרישה של AndroidX

כדי להשתמש במרחב השמות של androidx, צריך לעדכן את האפליקציה כדי להשתמש במרחב השמות של androidx לגרסאות חדשות של Google Play Services. פועלים לפי ההוראות למעבר ל-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 מערכת Content ID.
METADATA_KEY_ARTIST האומן/ית.
METADATA_KEY_ALBUM האלבום.

PlaybackStateCompat

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

MediaSessionCompat

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

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

הגדרת Android TV

הוספה של מסנן Intent להשקה

מוסיפים מסנן 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:

קוטלין
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:

קוטלין
override fun onCreate() {
  CastReceiverContext.initInstance(this)

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

  ...
}

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

קוטלין
CastReceiverContext.getInstance().start()
Java
CastReceiverContext.getInstance().start();

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

קוטלין
// Player has stopped.
CastReceiverContext.getInstance().stop()
Java
// Player has stopped.
CastReceiverContext.getInstance().stop();

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

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

קוטלין
// 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 כדי שהוא ידע לאן לשלוח את הפקודות ולאחזר את מצב ההפעלה של המדיה:

קוטלין
val mediaManager: MediaManager = receiverContext.getMediaManager()
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
Java
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

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

קוטלין
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
Java
myPlayer.stop();
mediaSession.release();
mediaManager.setSessionCompatToken(null);

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

קוטלין
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();
  }
}

שימוש בנגן Exo Player עם Cast Connect

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

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

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

קוטלין
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, תוכלו להצהיר על מוּכנוּת האפליקציה על ידי הגדרת הדגל androidReceiverCompatible ל-LaunchOptions כ-true.

Android

יש צורך בגרסה play-services-cast-framework 19.0.0 ומעלה.

הדגל androidReceiverCompatible מוגדר ב-LaunchOptions (שהוא חלק מ-CastOptions):

קוטלין
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 Developer Console

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

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

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

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

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

קוטלין
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 עם קישור העומק שלכם ושם החבילה שהגדרתם ב-Developer Console.

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

יכול להיות שאפליקציית Web Aware ואפליקציית Android TV תומכות בקישורי עומק שונים וב-credentials (לדוגמה, אם הטיפול באימות מתבצע באופן שונה בשתי הפלטפורמות). כדי לפתור את הבעיה, אפשר לספק entity ו-credentials חלופיים ל-Android TV:

Android
קוטלין
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 Aware מופעלת, היא משתמשת ב-entity וב-credentials בבקשת הטעינה. עם זאת, אם אפליקציית Android TV מופעלת, ערכת ה-SDK מבטלת את entity ואת credentials עם atvEntity ו-atvCredentials (אם צוין).

טעינה לפי Content ID או MediaQueueData

אם אתם לא משתמשים ב-entity או ב-atvEntity ואתם משתמשים במערכת Content ID או בכתובת URL של תוכן בפרטי המדיה שלכם, או אם אתם משתמשים בנתוני הבקשה המפורטים יותר לטעינת מדיה, עליכם להוסיף לאפליקציה ל-Android TV את מסנן ה-Intent המוגדר מראש הבא:

<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
קוטלין
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);

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

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

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

קוטלין
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 לבקשות טעינה).

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

תמיכה בבקרת הפעלה בסיסית

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

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

קוטלין
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());

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

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

קוטלין
// Set media session supported commands
val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder()
    .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE)
    .setState(PlaybackStateCompat.STATE_PLAYING)
    .build()

mediaSession.setPlaybackState(playbackState)

// Set additional commands in MediaStatusModifier
val mediaManager = CastReceiverContext.getInstance().getMediaManager()
mediaManager.getMediaStatusModifier()
    .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
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 Acceptr תומכת באמצעי בקרה מתקדמים יותר, צריך לוודא שאפליקציית השולח תפעל כמו שצריך בזמן ההעברה לאפליקציית Android TV. לדוגמה, אם האפליקציה ל-Android TV לא תומכת בשינוי של קצב ההפעלה בזמן שהאפליקציה WebReceiver תומכת בה, צריך להגדיר את הפעולות הנתמכות בכל פלטפורמה ולוודא שאפליקציית השולח מעבדת את ממשק המשתמש כראוי.

שינוי סטטוס המדיה

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

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

כדי ליצור ולשדר את MediaStatus:

קוטלין
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 לפני שליחה

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

קוטלין
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 ומצטרף אליה, תוכלו להגדיר בודק הפעלה כדי לראות אם פרטי הכניסה של השולח מותרים. אם לא, Cast Connect SDK יחזור להפעלה של Web Aware.

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

בצד השולח, תוכלו לציין את השדה 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 ומעלה.

קוטלין
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.1 ואילך.

אפשר לבצע קריאה בכל שלב לאחר הגדרת האפשרויות: 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);

הטמעת בודק בקשות הפעלה של ATV

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

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

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

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

קוטלין
class MyLaunchRequestChecker : LaunchRequestChecker {
  override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task {
    return Tasks.call {
      myCheckLaunchRequest(
           launchRequest
      )
    }
  }
}

private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean {
  val credentialsData = launchRequest.getCredentialsData()
     ?: return false // or true if you allow anonymous users to join.

  // The request comes from a mobile device, e.g. checking user match.
  return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) {
     myCheckMobileCredentialsAllowed(credentialsData.getCredentials())
  } else false // Unrecognized credentials type.
}
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:

קוטלין
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 יפעיל את אפליקציית Web Acceptr.

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

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

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

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

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

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

קוטלין
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());