הדף הזה מכיל קטעי קוד ותיאורים של התכונות הזמינות עבור התאמה אישית של אפליקציה של 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.3 ומעלה -
מטרגטים את iOS מגרסה 14 ואילך. פרטים נוספים זמינים בנתוני הגרסה
אפשר לקבל פרטים נוספים.
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.3' end
- נדרש דפדפן Chromium מגרסה M87 ואילך.
-
הוספת ספריית Web Sender API לפרויקט
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
הדרישה של AndroidX
כדי להשתמש בגרסאות חדשות של Google Play Services, צריך לעדכן את האפליקציה
מרחב השמות androidx
. פועלים לפי ההוראות עבור
מעבר אל AndroidX.
אפליקציה ל-Android TV – דרישות מוקדמות
כדי לתמוך ב-Cast Connect באפליקציה ל-Android TV, צריך ליצור תמיכה באירועי מדיה מסשן. הנתונים שסופקו על ידי סשן המדיה שלך מספק את המידע הבסיסי - לדוגמה, מיקום, מצב הפעלה וכו'. הסטטוס שלכם במדיה. סשן המדיה שלך משמש גם את ספריית Cast Connect כדי לאותת כשהוא קיבל הודעות מסוימות משולח, למשל השהיה.
למידע נוסף על סשן מדיה ועל אופן האתחול של סשן מדיה, לראות את לעבודה עם מדריך פעילויות מדיה.
מחזור החיים של סשן מדיה
האפליקציה צריכה ליצור סשן מדיה כשההפעלה מתחילה ולשחרר אותו כאשר ואי אפשר יותר לשלוט בו. לדוגמה, אם האפליקציה היא אפליקציית וידאו, יש לשחרר את הסשן כשהמשתמש יוצא מפעילות ההפעלה — בחירה באפשרות 'חזרה' כדי לעיין בתוכן אחר או על ידי הפעלת האפליקציה ברקע. אם היא אפליקציית מוזיקה, וצריך לפרסם אותה כשהאפליקציה כבר לא משמיעה מדיה.
מתבצע עדכון של סטטוס הסשן
הנתונים בסשן המדיה צריכים להתעדכן בסטטוס של נגן. לדוגמה, כשההפעלה מושהית, צריך לעדכן את ועל הפעולות הנתמכות. בטבלאות הבאות מפורטים המצבים באחריותך להתעדכן.
MediaMetadataCompat
שדה מטא-נתונים | תיאור |
---|---|
METADATA_KEY_TITLE (חובה) | שם המדיה. |
METADATA_KEY_DISPLAY_SUBTITLE | כותרת המשנה. |
METADATA_KEY_DISPLAY_ICON_URI | כתובת ה-URL של הסמל. |
METADATA_KEY_DURATION (חובה) | משך המדיה. |
METADATA_KEY_MEDIA_URI | מערכת 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 חדש לפעילות שרוצים לטפל בה בהפעלה 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, תהיה לך אפשרות להצהיר (declare)
מוּכנוּת על ידי הגדרה של
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(); } }
נדרשת גרסה v4.4.8
של google-cast-sdk
או
גבוהה יותר.
הדגל 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 ב: Play Developer Console כדי לשייך אותו למזהה של אפליקציית Cast.
רישום מכשירים של מפתחים
רישום המספר הסידורי של מכשיר Android TV שבו מתכוונים להשתמש לפיתוח העברה מ-Play 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 עם קישור העומק ושם החבילה שהגדרתם במסוף המפתחים.
הגדרה של פרטי כניסה ל-ATV אצל השולח
ייתכן שבאפליקציה של מכשיר האינטרנט ובאפליקציה ל-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
מזהה שהכוונה היא כוונת טעינה, הוא מחלץ
MediaLoadRequestData
להפעיל מתוך הכוונה,
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
)
עבור בקשות טעינה).
פקודות מדיה תומכות
תמיכה בסיסית בבקרת ההפעלה
פקודות שילוב בסיסיות כוללות את הפקודות שתואמות למדיה סשן. הפקודות האלה נשלחות באמצעות קריאה חוזרת (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
חלק מפקודות ההעברה לא זמינות ב
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 תומכת רק בשליטה בסיסית במדיה, אבל במקלט האינטרנט האפליקציה תומכת בשליטה מתקדמת יותר, עליך לוודא שאפליקציית השולח תתנהג בצורה נכונה במהלך ההעברה לאפליקציה ל-Android TV. לדוגמה, אם מכשיר 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
.
יירוט מדיה לפני שליחה
זהה ל-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 חוזר להפעלת מקלט האינטרנט.
נתוני פרטי הכניסה להפעלת האפליקציה של השולח
בצד השולח, ניתן לציין את 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());
נדרשת גרסה v4.8.3
של google-cast-sdk
או
גבוהה יותר.
אפשר להתקשר אליהם בכל שלב אחרי שהאפשרויות הוגדרו:
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 שאתה מגדיר.
שליחה & קבלת הודעות מותאמות אישית
פרוטוקול 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());