דף זה מכיל קטעי קוד ותיאורים של התכונות הזמינות להתאמה אישית של אפליקציה של Android TV Acceptr.
הגדרת הספריות
כדי שממשקי ה-API של Cast Connect יהיו זמינים באפליקציית Android TV:
-
פותחים את הקובץ
build.gradle
שבתוך הספרייה של מודול האפליקציה. -
יש לוודא ש-
google()
כלול ב-repositories
שברשימה.repositories { google() }
-
מוסיפים את הגרסאות העדכניות של הספריות ליחסי התלות, בהתאם לסוג מכשיר היעד של האפליקציה:
-
באפליקציית 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' }
-
באפליקציית AndroidReceiver:
-
שומרים את השינויים ולוחצים על
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
, צריך לעדכן את האפליקציה כדי להשתמש במרחב השמות של 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) שמגיעות משולחים מחוברים.
הגדרת 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() } }
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 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)
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 Aware ואפליקציית 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);
אם האפליקציה 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()
.
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);
טיפול בבקשות טעינה
בפעילות שלכם, כדי לטפל בבקשות הטעינה האלה, צריך לטפל באובייקטים של ה-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. ... } }
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) }
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
לבקשות טעינה).
תמיכה בפקודות מדיה
תמיכה בבקרת הפעלה בסיסית
פקודות השילוב הבסיסיות כוללות את הפקודות שתואמות לסשן מדיה. הפקודות האלה מקבלות התראה באמצעות קריאות חוזרות לסשן מדיה. כדי לתמוך בפעילות הזו, עליכם לרשום קריאה חוזרת לפעילות של מדיה (יכול להיות שאתם כבר עושים את זה).
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()
.
כמו כן, צריך להטמיע כאן חלק מהפקודות של 'הבאים בתור' כי תור ההעברה לא תואם באופן מלא לתור של 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 תומכת באמצעי בקרה מתקדמים יותר, צריך לוודא שאפליקציית השולח תפעל כמו שצריך בזמן ההעברה לאפליקציית 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()
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\"}")) } })
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()
.
יש צורך בגרסה 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
.
כדי לאשר או לדחות את הבקשה.
אם הבקשה נדחית, ה-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. }
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());