הדף הזה מכיל קטעי קוד ותיאורים של התכונות הזמינות להתאמה אישית של אפליקציה ל-Android TV Chromecast.
הגדרת הספריות
כדי שממשקי ה-API של Cast Connect יהיו זמינים באפליקציה ל-Android TV:
-
פותחים את הקובץ
build.gradle
בספרייה של מודול האפליקציה. -
צריך לוודא ש-
google()
נכלל ב-repositories
המפורטים.repositories { google() }
-
בהתאם לסוג מכשיר היעד של האפליקציה, מוסיפים את הגרסאות האחרונות של הספריות ליחסי התלות:
-
באפליקציה 'מקלט נתונים ל-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' }
-
באפליקציה 'מקלט נתונים ל-Android':
-
שומרים את השינויים ולוחצים על
Sync Project with Gradle Files
בסרגל הכלים.
-
חשוב לוודא שהשדה
Podfile
מטרגט אתgoogle-cast-sdk
בגרסה 4.8.1 ואילך -
מטרגטים את iOS מגרסה 14 ואילך. אפשר לקרוא פרטים נוספים
בקטע נתוני הגרסה.
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.1' end
- נדרש דפדפן Chromium מגרסה M87 ואילך.
-
הוספת ספריית 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) }
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() );
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() } }
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) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
מפעילים את CastReceiverContext
כשהאפליקציה עוברת לחזית:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
מתקשרים
stop()
ב-CastReceiverContext
אחרי שהאפליקציה עוברת לרקע של אפליקציות וידאו או אפליקציות שלא תומכות בהפעלה ברקע:
// Player has stopped. CastReceiverContext.getInstance().stop()
// 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()) } }
// 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())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
כשמשחררים את MediaSession
בגלל הפעלה לא פעילה, צריך להגדיר אסימון null ב-MediaManager
:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
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() } }
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) ... } }
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.
נדרשת 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() } }
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(); } }
נדרשת 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)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
נדרש דפדפן 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:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
נדרש דפדפן 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()
.
val mediaToLoad = MediaInfo.Builder("some-id").build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
נדרש דפדפן 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. ... } }
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) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(String senderId, MediaLoadRequestData loadRequestData) { return Tasks.call(() -> { // Resolve the entity into your data structure and load media. MediaInfo mediaInfo = loadRequestData.getMediaInfo(); if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw new MediaException( new MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build()); } myFillMediaInfo(new MediaInfoWriter(mediaInfo)); myPlayerLoad(mediaInfo.getContentUrl()); // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData); ... castReceiverContext.getMediaManager().broadcastMediaStatus(); // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData; }); } private void myPlayerLoad(String contentURL) { myPlayer.load(contentURL); // Update the MediaSession state. PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis()) ... .build(); mediaSession.setPlaybackState(playbackState); }
כדי לעבד את הכוונה של הטעינה, אפשר לנתח את הכוונה במבני הנתונים שהגדרנו (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())
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())
public class MyMediaCommandCallback extends MediaCommandCallback { @Override public TaskonSkipAd(RequestData requestData) { // Skip your ad ... return Tasks.forResult(null); } } MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
ציון פקודות המדיה הנתמכות
בדומה למקלטי 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)
// 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()
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\"}")) } })
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()
.
נדרשת play-services-cast-framework
מגרסה 19.0.0
ואילך.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
נדרשת 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. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public TaskcheckLaunchRequestSupported(CastLaunchRequest launchRequest) { return Tasks.call(() -> myCheckLaunchRequest(launchRequest)); } } private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) { CredentialsData credentialsData = launchRequest.getCredentialsData(); if (credentialsData == null) { return false; // or true if you allow anonymous users to join. } // The request comes from a mobile device, e.g. checking user match. if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) { return myCheckMobileCredentialsAllowed(credentialsData.getCredentials()); } // Unrecognized credentials type. return false; }
לאחר מכן מגדירים אותו ב-ReceiverOptionsProvider
:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(MyLaunchRequestChecker()) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(new MyLaunchRequestChecker()) .build(); } }
פתרון הבעיה 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() } }
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)
// 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());
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());