שילוב של Cast עם אפליקציית Android

במדריך למפתחים מוסבר איך להוסיף תמיכה ב-Google Cast לאפליקציית השולח ב-Android באמצעות Android Sender SDK.

המכשיר הנייד או המחשב הנייד הם השולח ששולט בהפעלה, ומכשיר Google Cast הוא המקלט שמציג את התוכן בטלוויזיה.

מסגרת השולח מתייחסת לקובץ הבינארי של הספרייה של מחלקת ההעברה (cast) ולמשאבים המשויכים שנמצאים בזמן הריצה של השולח. אפליקציית השולח או אפליקציית ההעברה מתייחסות לאפליקציה שפועלת גם על השולח. אפליקציית WebReceiver מתייחסת לאפליקציית ה-HTML שפועלת במכשיר שתומך ב-Cast.

מסגרת השולח משתמשת בעיצוב אסינכרוני של קריאה חוזרת כדי לעדכן את אפליקציית השולח לגבי האירועים ולעבור בין מצבים שונים במחזור החיים של אפליקציית Cast.

זרימת אפליקציה

בשלבים הבאים מתואר תהליך הביצוע ברמה גבוהה הטיפוסי באפליקציה של שולח ל-Android:

  • מסגרת Cast מתחילה באופן אוטומטי את גילוי המכשיר MediaRouter על סמך מחזור החיים של Activity.
  • כשהמשתמש לוחץ על הלחצן להפעלת Cast, במסגרת תיבת הדו-שיח של Cast מוצגת רשימה של מכשירי Cast שנמצאו.
  • כשהמשתמש בוחר מכשיר Cast, ה-framework מנסה להפעיל את אפליקציית Web Acceptr במכשיר Cast.
  • ה-framework מפעיל קריאות חוזרות (callback) באפליקציית השולח כדי לאשר שאפליקציית WebReceiver הופעלה.
  • תוכנת ה-framework יוצרת ערוץ תקשורת בין האפליקציות של השולח לאפליקציות של WebReceiver.
  • תוכנת ה-framework משתמשת בערוץ התקשורת כדי לטעון ולנהל את הפעלת המדיה במקלט האינטרנט.
  • ה-framework מסנכרן את מצב הפעלת המדיה בין השולח למקלט האינטרנט: כשהמשתמש מבצע פעולות בממשק המשתמש של השולח, ה-framework מעביר את הבקשות האלה לבקרת מדיה למקלט האינטרנט, וכשמקלט האינטרנט שולח עדכונים לגבי סטטוס המדיה, המסגרת מעדכנת את המצב של ממשק המשתמש של השולח.
  • כשהמשתמש לוחץ על לחצן הפעלת Cast כדי להתנתק ממכשיר ה-Cast, ה-framework ינתק את אפליקציית השולח ממקלט האינטרנט.

רשימה מקיפה של כל הכיתות, השיטות והאירועים ב-Android SDK של Google Cast ל-Android זמינה בחומר העזר בנושא ממשק API של Google Cast Sender API ל-Android. הקטעים הבאים מתארים את השלבים להוספת Cast לאפליקציה ל-Android.

הגדרת המניפסט של Android

בקובץ AndroidManifest.xml של האפליקציה צריך להגדיר את הרכיבים הבאים של Cast SDK:

uses-sdk

הגדרת רמות ה-API המינימליות ב-Android שנתמכות על ידי Cast SDK, ורמת הטירגוט שלהן. כרגע רמת ה-API המינימלית היא 21 והיעד הוא רמת API 28.

<uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="28" />

android:theme

צריך להגדיר את העיצוב של האפליקציה על סמך גרסת ה-SDK המינימלית של Android. לדוגמה, אם אתם לא מטמיעים עיצוב משלכם, צריך להשתמש בווריאנט של Theme.AppCompat כשמטרגטים גרסה מינימלית של Android SDK לפני Lollipop.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

אתחול ההקשר של הפעלת Cast

ל-framework יש אובייקט גלובלי מסוג סינגלטון, CastContext, שמרכז את כל האינטראקציות של ה-framework.

האפליקציה שלכם צריכה להטמיע את הממשק OptionsProvider כדי לספק את האפשרויות שדרושות לאתחול של הטון CastContext. OptionsProvider מספק מופע של CastOptions שמכיל אפשרויות שמשפיעות על ההתנהגות של ה-framework. החשוב ביותר שבהם הוא מזהה האפליקציה של מקלט האינטרנט, שמשמש לסינון תוצאות הגילוי ולהפעלת האפליקציה של מקלט האינטרנט כשהתחלת סשן של הפעלת Cast.

קוטלין
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

צריך להצהיר על השם המוגדר במלואו של OptionsProvider שהוטמע כשדה מטא-נתונים בקובץ AndroidManifest.xml של אפליקציית השולח:

<application>
    ...
    <meta-data
        android:name=
            "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.foo.CastOptionsProvider" />
</application>

כאשר מתבצעת קריאה ל-CastContext.getSharedInstance(), מתבצעת אתחול מדורג של CastContext.

קוטלין
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

ווידג'טים של חוויית המשתמש ב-Cast

מסגרת Cast מספקת את הווידג'טים שתואמים לרשימת המשימות של עיצוב Cast:

  • הצגה בשכבת-על למבוא: המסגרת מספקת תצוגה מותאמת אישית, IntroductoryOverlay, שמוצגת למשתמש כדי להסב את תשומת הלב ללחצן Cast בפעם הראשונה שיש מקלט זמין. אפליקציית השולח יכולה להתאים אישית את הטקסט ואת המיקום של טקסט הכותרת.

  • הלחצן להפעלת Cast: הלחצן להפעלת Cast מוצג ללא קשר לזמינות של מכשירי Cast. כשהמשתמש לוחץ בפעם הראשונה על הלחצן להפעלת Cast, מוצגת תיבת דו-שיח של Cast שבה מפורטים המכשירים שזוהו. כשהמשתמש לוחץ על הלחצן להפעלת Cast כשהמכשיר מחובר, המטא-נתונים של המדיה הנוכחית (כמו שם אולפן ההקלטות ותמונה ממוזערת) מוצגים, או מאפשרים למשתמש להתנתק ממכשיר ה-Cast. הלחצן 'העברה (cast)' נקרא לפעמים 'הסמל של הפעלת Cast'.

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

  • בקר מורחב: כשהמשתמש מעביר תוכן (cast) באמצעות לחיצה על התראת המדיה או על המיני-בקר, מופעל הבקר המורחב, שמציג את המטא-נתונים של המדיה שמופעלים כרגע ומספק מספר לחצנים לשליטה בהפעלת המדיה.

  • התראה: Android בלבד. כשהמשתמש מעביר תוכן ומנווט אל מחוץ לאפליקציית השולח, מוצגת התראת מדיה שמציגה את המטא-נתונים של המדיה ואת פקדי ההפעלה הנוכחיים שמתבצעת בהם העברה.

  • נעילת מסך: Android בלבד. כשהמשתמש מעביר (cast) תוכן ומנווט (או שתם הזמן הקצוב למכשיר) למסך הנעילה, מוצגת פקד של נעילת מדיה במסך, שמציג את המטא-נתונים של המדיה שממנה מתבצעת ההעברה ואת לחצני ההפעלה.

המדריך הבא כולל תיאורים של אופן הוספת הווידג'טים האלה לאפליקציה.

הוספת לחצן להפעלת Cast

ממשקי ה-API של Android MediaRouter נועדו לאפשר הצגה והפעלה של מדיה במכשירים משניים. באפליקציות ל-Android שמשתמשות ב-API של MediaRouter צריך לכלול לחצן להפעלת Cast כחלק מממשק המשתמש, כדי לאפשר למשתמשים לבחור מסלול מדיה להפעלת מדיה במכשיר משני כמו מכשיר Cast.

עם ה-framework, קל מאוד להוסיף MediaRouteButton בתור Cast button. קודם צריך להוסיף פריט לתפריט או MediaRouteButton בקובץ ה-XML שמגדיר את התפריט, ולהשתמש ב-CastButtonFactory כדי לחבר אותו ל-framework.

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
קוטלין
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
Java
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}

לאחר מכן, אם ה-Activity מקבל בירושה של FragmentActivity, תוכלו להוסיף את MediaRouteButton לפריסה.

// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"
   android:orientation="horizontal" >

   <androidx.mediarouter.app.MediaRouteButton
       android:id="@+id/media_route_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:mediaRouteTypes="user"
       android:visibility="gone" />

</LinearLayout>
קוטלין
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
Java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}

כדי להגדיר את המראה של לחצן הפעלת Cast באמצעות עיצוב, ראו התאמה אישית של לחצן הפעלת Cast.

הגדרת גילוי מכשירים

גילוי המכשיר מנוהל באופן מלא על ידי CastContext. כשמפעילים את CastContext, אפליקציית השולח מציינת את המזהה של אפליקציית WebReceiver, ויכולה לבקש סינון של מרחב שמות על ידי הגדרה של supportedNamespaces ב-CastOptions. CastContext שומר הפניה של MediaRouter באופן פנימי, ויתחיל את תהליך הגילוי בתנאים הבאים:

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

תהליך הגילוי יופסק כשתיבת הדו-שיח של הפעלת Cast תיסגר או כשאפליקציית השולח תיכנס לרקע.

קוטלין
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: MutableList<String> = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
        List<String> supportedNamespaces = new ArrayList<>();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

איך פועל ניהול הסשנים

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

הסשנים מנוהלים על ידי הכיתה SessionManager, שאליה האפליקציה יכולה לגשת דרך CastContext.getSessionManager(). ביקורים בודדים מיוצגים על ידי מחלקות משנה של הכיתה Session. לדוגמה, CastSession מייצג סשנים עם מכשירי Cast. האפליקציה יכולה לגשת לסשן Cast שפעיל כרגע דרך SessionManager.getCurrentCastSession().

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

סשנים נוצרים ומסתיימים באופן אוטומטי בתגובה לתנועות של המשתמשים בתיבות הדו-שיח של MediaRouter.

כדי להבין טוב יותר את השגיאות בהפעלה של Cast, אפליקציות יכולות להשתמש ב-CastContext#getCastReasonCodeForCastStatusCode(int) כדי להמיר את שגיאת ההתחלה של הסשן ל-CastReasonCodes. שימו לב ששגיאות מסוימות בהתחלת סשן (למשל CastReasonCodes#CAST_CANCELLED) מתרחשות כשגיאות מתוכננות, והן לא צריכות להירשם כשגיאה.

אם אתם צריכים להיות מודעים לשינויי המצב של הסשן, אתם יכולים להטמיע SessionManagerListener. בדוגמה הזו אנחנו מאזינים לזמינות של CastSession ב-Activity.

קוטלין
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

        override fun onSessionSuspended(session: CastSession?, reason Int) {}

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onPause() {
        super.onPause()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
        mCastSession = null
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
    @Override
    protected void onPause() {
        super.onPause();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
        mCastSession = null;
    }
}

העברה בסטרימינג

שימור מצב הסשן הוא הבסיס להעברת השידור, שבה המשתמשים יכולים להעביר שידורי אודיו ווידאו קיימים בין מכשירים באמצעות פקודות קוליות, אפליקציית Google Home או מסכים חכמים. המדיה מפסיקה לפעול במכשיר אחד (המקור) וממשיכה במכשיר אחר (היעד). כל מכשיר Cast עם הקושחה האחרונה יכול לשמש כמקורות או יעדים בהעברה בסטרימינג.

כדי לקבל את מכשיר היעד החדש במהלך העברה או הרחבה, צריך לרשום Cast.Listener באמצעות CastSession#addCastListener. לאחר מכן קוראים לפונקציה CastSession#getCastDevice() במהלך הקריאה החוזרת של onDeviceNameChanged.

למידע נוסף, ראו העברת סטרימינג ב-WebReceiver.

חיבור מחדש אוטומטי

תוכנת ה-framework מספקת ReconnectionService, שהאפליקציה של השולח יכולה להפעיל כדי לטפל בחיבור מחדש במקרים רבים של פינות קלות, כמו:

  • התאוששות מאובדן זמני של רשת ה-Wi-Fi
  • התאוששות ממצב שינה במכשיר
  • שחזור מהפעלה ברקע של האפליקציה
  • שחזור אם האפליקציה קרסה

השירות הזה מופעל כברירת מחדל ואפשר להשבית אותו ב-CastOptions.Builder.

אם המיזוג האוטומטי מופעל בקובץ ה-Gradle, אפשר למזג את השירות הזה באופן אוטומטי עם המניפסט של האפליקציה.

תוכנת ה-framework תתחיל את השירות כשיש סשן מדיה ותפסיק אותו בסיום הסשן של המדיה.

איך פועלת בקרת המדיה

ה-framework של Cast מוציא את המחלקה RemoteMediaPlayer מ-Cast 2.x לטובת מחלקה חדשה RemoteMediaClient, שמאפשרת את אותה פונקציונליות בקבוצה של ממשקי API נוחים יותר, ומונעת את הצורך לעבור ב-GoogleApiClient.

כשהאפליקציה שלכם יוצרת CastSession עם אפליקציית Web Gettingr שתומכת במרחב השמות של המדיה, ה-framework יצור באופן אוטומטי מופע של RemoteMediaClient. האפליקציה יכולה לגשת אליה על ידי קריאה ל-method getRemoteMediaClient() במכונה CastSession.

כל ה-methods של RemoteMediaClient ששולחות בקשות למקלט האינטרנט יחזירו אובייקט Pending Results שיכול לשמש למעקב אחרי הבקשה הזו.

בדרך כלל, מספר חלקים באפליקציה ישתפו את המכונה של RemoteMediaClient, ואכן גם כמה רכיבים פנימיים של ה-framework, כמו נאמני מידע מיני הקבועים ושירות ההתראות. לשם כך, המכונה הזו תומכת ברישום של כמה מופעים של RemoteMediaClient.Listener.

הגדרת מטא-נתונים של מדיה

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

קוטלין
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
Java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

במאמר בחירת תמונות מוסבר איך להשתמש בתמונות עם מטא-נתונים של מדיה.

טעינת מדיה

האפליקציה שלך יכולה לטעון פריט מדיה, כמו שמוצג בקוד הבא. קודם כול צריך להשתמש ב-MediaInfo.Builder עם המטא-נתונים של המדיה כדי ליצור מכונה של MediaInfo. מקבלים את ה-RemoteMediaClient מ-CastSession הנוכחי, ואז טוענים את MediaInfo ב-RemoteMediaClient הזה. ניתן להשתמש ב-RemoteMediaClient כדי להפעיל, להשהות אפליקציית נגן מדיה שפועלת במקלט האינטרנט, ולשלוט בה.

קוטלין
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
Java
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

כדאי גם לעיין בקטע שעוסק בשימוש בטראקים של מדיה.

פורמט וידאו 4K

כדי לבדוק מהו פורמט הווידאו של המדיה, משתמשים ב-getVideoInfo() ב-MediaStatus כדי למצוא את המופע הנוכחי של VideoInfo. המכונה הזו מכילה את סוג הפורמט של HDR TV ואת הגובה והרוחב של התצוגה בפיקסלים. וריאציות של פורמט 4K מסומנות באמצעות קבועים HDR_TYPE_*.

התראות שלט רחוק למספר מכשירים

כשמשתמש מעביר תוכן (cast), מכשירי Android אחרים באותה רשת יקבלו התראה כדי לאפשר להם לשלוט בהפעלה. כל מי שהמכשיר שלו מקבל התראות כאלה יכול להשבית אותן במכשיר הזה באפליקציית ההגדרות ב-Google > Google Cast > הצגת התראות של שלט רחוק. (ההתראות כוללות קיצור דרך לאפליקציית ההגדרות.) מידע נוסף מופיע במאמר העברת הודעות לשלט הרחוק.

הוספת מיני-בקר

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

ה-framework מספק תצוגה מותאמת אישית, MiniControllerFragment, שאותו אפשר להוסיף לתחתית של קובץ הפריסה של כל פעילות שבה רוצים להציג את השלט רחוק של המיני.

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

כשאפליקציית השולח מפעילה שידור חי של וידאו או אודיו, ה-SDK מציג באופן אוטומטי לחצן הפעלה/עצירה במקום לחצן ההפעלה/ההשהיה במיני-בקר.

כדי להגדיר את מראה הטקסט של הכותרת והכותרת משנה בתצוגה המותאמת אישית הזו, וכדי לבחור לחצנים, ראו התאמה אישית של Mini Controller.

הוספה של בקר מורחב

רשימת המשימות לעיצוב של Google Cast מחייבת אפליקציית שולח לספק בקר מורחב עבור המדיה שמועברת. הבקר המורחב הוא גרסה של המיני-בקר במסך מלא.

Cast SDK מספק ווידג'ט לבקר המורחב שנקרא ExpandedControllerActivity. זהו כיתת מופשטת שצריך ליצור ממנה מחלקה משנית כדי להוסיף לחצן להפעלת Cast.

קודם כול, יוצרים קובץ משאבים חדש לתפריט עבור הבקר המורחב, כדי לספק את לחצן הפעלת Cast:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

יצירת כיתה חדשה בהיקף של ExpandedControllerActivity.

קוטלין
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
Java
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

עכשיו צריך להצהיר על הפעילות החדשה בקובץ המניפסט של האפליקציה בתוך התג application:

<application>
...
<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>
...
</application>

עורכים את CastOptionsProvider ומשנים את NotificationOptions ואת CastMediaOptions כדי להגדיר את פעילות היעד לפעילות החדשה:

קוטלין
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
Java
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

אפשר לעדכן את ה-method LocalPlayerActivity loadRemoteMedia כדי להציג את הפעילות החדשה כשהמדיה המרוחקת נטענת:

קוטלין
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
Java
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}

כשאפליקציית השולח מפעילה שידור חי של וידאו או אודיו, ה-SDK מציג באופן אוטומטי לחצן הפעלה/עצירה במקום לחצן ההפעלה/ההשהיה בבקר המורחב.

כדי להגדיר את המראה באמצעות עיצובים, בוחרים אילו לחצנים להציג ומוסיפים לחצנים בהתאמה אישית, ראו התאמה אישית של בקר מורחב.

בקרת עוצמת הקול

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

בקרת עוצמת הקול של הלחצן הפיזי

ב-Android, אפשר להשתמש בלחצנים הפיזיים במכשיר השולח כדי לשנות כברירת מחדל את עוצמת הקול של הפעלת Cast במקלט האינטרנט בכל מכשיר עם Jellly Bean ואילך.

שליטה בעוצמת הקול של הלחצנים הפיזיים לפני Jelly Bean

כדי להשתמש במקשי עוצמת הקול הפיזיים כדי לשלוט בעוצמת הקול של מכשיר ה-Web קבלה במכשירי Android ישנים יותר מ-Jenly Bean, אפליקציית השולח צריכה לבטל את dispatchKeyEvent בפעילויות שלו ולהתקשר ל-CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

קוטלין
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

הוספת בקרי מדיה להתראות ולמסך הנעילה

ב-Android בלבד, רשימת המשימות של Google Cast Design מחייבת אפליקציית שולח להטמיע פקדי מדיה בהתראה ובמסך הנעילה, שבהם השולח מעביר תוכן אבל לא המיקוד באפליקציית השולח. ה-framework מספק MediaNotificationService ו-MediaIntentReceiver כדי לעזור לאפליקציית השולח ליצור פקדי מדיה בהתראה ובמסך הנעילה.

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

MediaIntentReceiver הוא BroadcastReceiver שמטפל בפעולות של משתמשים דרך ההתראה.

האפליקציה יכולה להגדיר התראות ואמצעי בקרה על מדיה ממסך הנעילה באמצעות NotificationOptions. האפליקציה יכולה להגדיר אילו לחצני בקרה יוצגו בהתראה, ואילו Activity יפתח בכל פעם שהמשתמש יקיש על ההתראה. אם לא תציינו פעולות באופן מפורש, ייעשה שימוש בערכי ברירת המחדל, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK ו-MediaIntentReceiver.ACTION_STOP_CASTING.

קוטלין
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
Java
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

הצגת בקרי מדיה בהתראות ובמסך הנעילה מופעלים כברירת מחדל, ואפשר להשבית אותה על ידי קריאה ל-setNotificationOptions כשהערך הוא null ב-CastMediaOptions.Builder. כרגע, תכונת מסך הנעילה מופעלת כל עוד ההתראות מופעלות.

קוטלין
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
Java
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();

כשאפליקציית השולח מפעילה שידור חי של וידאו או אודיו, ה-SDK מציג באופן אוטומטי לחצן הפעלה/עצירה במקום לחצן ההפעלה/ההשהיה בפקד ההתראות, אבל לא בפקדים של מסך הנעילה.

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

טיפול בשגיאות

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