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

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

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

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

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

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

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

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

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

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

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

uses-sdk

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

<uses-sdk
        android:minSdkVersion="23"
        android:targetSdkVersion="34" />

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 בתהליך.

Kotlin
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 מופעל באופן מדורג כשה-CastContext.getSharedInstance() נקראת.

Kotlin
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 רשימת משימות:

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

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

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

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

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

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

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

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

מכשיר Android MediaRouter ממשקי API מיועדים לאפשר הצגה והפעלה של מדיה במכשירים משניים. באפליקציות ל-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" />
Kotlin
// 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>
Kotlin
// 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, אפליקציית השולח מציינת את המכשיר של מקלט האינטרנט מזהה אפליקציה, ואפשר לבקש סינון של מרחב שמות באמצעות ההגדרה supportedNamespaces אינץ' CastOptions. CastContext מכיל הפניה אל MediaRouter באופן פנימי, ויתחיל תהליך הגילוי בתנאים הבאים:

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

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

Kotlin
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 ועל מחזור החיים של מקלט האינטרנט.

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

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

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

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

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

Kotlin
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
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
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();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

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

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

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

צפייה העברת סטרימינג במקלט אינטרנטי אפשר לקבל מידע נוסף.

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

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

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

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

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

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

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

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

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

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

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

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

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

Kotlin
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 כדי להפעיל, להשהות ועוד לשלוט באפליקציית נגן מדיה שפועלת במקלט האינטרנט.

Kotlin
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) רשימת המשימות, אפליקציית שולח צריכה לספק שליטה מתמשכת, שנקראת mini בקר משחקים צריכות להופיע כשהמשתמש מנווט לדף התוכן הנוכחי אל חלק אחר של אפליקציית השולח. המיני-בקר מציג תזכורת גלויה למשתמש בסשן הנוכחי של הפעלת 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 מציג באופן אוטומטי לחצן הפעלה/עצירה במקום לחצן ההפעלה/ההשהיה במיני-בקר.

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

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

כדי לגשת לרשימת המשימות לעיצוב של Google Cast, אפליקציית שולח צריכה לספק כתובת URL מורחבת בקר משחקים עבור המדיה שהופעלה 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.

Kotlin
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 כדי להגדיר את פעילות היעד לפעילות החדשה:

Kotlin
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();
}

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

Kotlin
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 במקלט האינטרנט עבור כל מכשיר שמשתמש Jelly Bean או גרסה חדשה יותר.

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

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

Kotlin
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 נדרשת אפליקציית שולח להטמיע פקדי מדיה התראה ובמנעול screen, שבהם השולח מעביר תוכן אבל אפליקציית השולח לא מתמקדת. framework מספקת MediaNotificationService וגם MediaIntentReceiver כדי לעזור לאפליקציית השולח לפתח פקדי מדיה בהתראה ובנעילה מסך.

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

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

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

Kotlin
// 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 נכון לעכשיו, תכונת מסך הנעילה מופעלת כל עוד ההתראה מופעלת.

Kotlin
// ... 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. האפליקציה יכולה להציג הודעות שגיאה למשתמש או שהוא יכול להחליט לנתק את החיבור מכשיר אינטרנט.