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

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

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

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

Android
  1. פותחים את הקובץ build.gradle בספרייה של מודול האפליקציה.
  2. צריך לוודא ש-google() נכלל ב-repositories המפורטים.
      repositories {
        google()
      }
  3. בהתאם לסוג מכשיר היעד של האפליקציה, מוסיפים את הגרסאות האחרונות של הספריות ליחסי התלות:
    • באפליקציה 'מקלט נתונים ל-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'
        }
    • באפליקציית שולח ל-Android:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.1.0'
          implementation 'com.google.android.gms:play-services-cast-framework:21.5.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, צריך לעדכן את האפליקציה של 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 בזמן שאפליקציית הטלוויזיה פועלת. האובייקט הזה מאפשר לאפליקציית הטלוויזיה לקבל הודעות מדיה של 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:

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

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

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

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

צריך לרשום את המספר הסידורי של מכשיר Android TV שבו תשתמשו לצורכי פיתוח ב-Cast 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 נינוח והאפליקציה ל-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);

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

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

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

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

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

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

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

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

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

קוטלין
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 תומכת בשליטה מתקדמת יותר, צריך לוודא שאפליקציית השולח מתנהגת בצורה תקינה כשמפעילים Cast לאפליקציית Android TV. לדוגמה, אם האפליקציה ל-Android TV לא תומכת בשינוי קצב ההפעלה בזמן שאפליקציית Web Acceptr לא תומכת בשינוי של קצב ההפעלה בכל פלטפורמה, צריך להגדיר את הפעולות הנתמכות בכל פלטפורמה ולוודא שממשק המשתמש של השולח פועל כמו שצריך.

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

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

יירוט מדיה לפני שליחה

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

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

בצד השולח, אפשר לציין את 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. כדי לאשר או לדחות את הבקשה הזו.

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