دمج ميزة البث في تطبيق Android

يوضّح دليل المطوِّر هذا كيفية إضافة دعم Google Cast إلى جهاز Android. المستخدم المرسل باستخدام حزمة SDK التي تخص Android Sender.

فإن الجهاز الجوّال أو الكمبيوتر المحمول هو المرسِل الذي يتحكم في تشغيل الفيديو جهاز Google Cast هو جهاز الاستقبال الذي يعرض المحتوى على التلفزيون.

يشير إطار عمل المُرسِل إلى البرنامج الثنائي لمكتبة فئات Cast والربط الموارد الموجودة في وقت التشغيل لدى المرسل. تطبيق المرسِل أو تطبيق البث إلى تطبيق يعمل على المُرسِل أيضًا تطبيق WebRecipient يشير إلى تطبيق HTML الذي يعمل على الجهاز الذي يعمل بتكنولوجيا Google Cast

يستخدم إطار عمل المرسِل تصميمًا غير متزامن لمعاودة الاتصال لإعلام المُرسِل للأحداث والانتقال بين الحالات المختلفة لحياة تطبيق Cast الدورة.

مسار التطبيق

تصف الخطوات التالية مسار التنفيذ عالي المستوى النموذجي للمرسل تطبيق Android:

  • يبدأ إطار عمل Google Cast تلقائيًا MediaRouter اكتشاف الجهاز استنادًا إلى دورة حياة Activity.
  • عندما ينقر المستخدم على زر البث، يعرض إطار العمل خيار البث. مربّع حوار يتضمّن قائمة أجهزة البث التي تم اكتشافها
  • عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق WebRecipient على جهاز البث.
  • يستدعي إطار العمل استدعاءات في تطبيق المرسِل للتأكد من أن الويب تم تشغيل تطبيق جهاز الاستقبال.
  • يعمل هذا الإطار على إنشاء قناة اتصال بين المرسل والويب تطبيقات الاستقبال.
  • يستخدم إطار العمل قناة الاتصال لتحميل الوسائط والتحكّم فيها في جهاز استقبال الويب.
  • يعمل إطار العمل على مزامنة حالة تشغيل الوسائط بين المرسِل مستقبل الويب: عندما ينفِّذ المستخدم إجراءات على واجهة المستخدم للمرسل، يتم تمرير إطار العمل طلبات التحكم في الوسائط هذه إلى مستقبل الويب، وعندما يكون جهاز استقبال الويب يرسل تحديثات حالة الوسائط، يعدِّل إطار العمل حالة واجهة مستخدم المُرسِل.
  • عندما ينقر المستخدم على زر البث لقطع الاتصال بجهاز البث، سيعمل إطار العمل على إلغاء ربط تطبيق المرسل من مستقبل الويب.

للحصول على قائمة شاملة بجميع الفئات والأساليب والأحداث في Google Cast حزمة تطوير البرامج (SDK) لنظام التشغيل Android، يُرجى الاطّلاع على مرجع واجهة برمجة التطبيقات Google Cast Sender API Android تتناول الأقسام التالية خطوات إضافة ميزة "البث" إلى تطبيق Android.

إعداد بيان Android

يتطلّب ملف AndroidManifest.xml في تطبيقك ضبط ما يلي: عناصر حزمة تطوير البرامج (SDK) للإرسال:

uses-sdk

يمكنك ضبط الحدّ الأدنى لمستويات واجهة برمجة تطبيقات Android التي تتوافق مع حزمة تطوير البرامج (SDK) الخاصة بالبث. الحد الأدنى حاليًا هو المستوى 23 من واجهة برمجة التطبيقات والهدف هو المستوى 34 من واجهة برمجة التطبيقات.

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

android:theme

يمكنك ضبط مظهر تطبيقك استنادًا إلى الحد الأدنى لإصدار حزمة تطوير البرامج (SDK) لنظام التشغيل Android. على سبيل المثال، إذا لم تكن تنفّذ المظهر الخاص بك، يجب عليك استخدام صيغة من Theme.AppCompat عند استهداف إصدار أقل من حزمة تطوير البرامج (SDK) لنظام التشغيل Android قبل Lollipop.

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

إعداد سياق البث

ويحتوي إطار العمل على كائن سينغلتون عام، CastContext، وينسق جميع تفاعلات إطار العمل.

يجب أن ينفِّذ تطبيقك OptionsProvider لتوفير الخيارات اللازمة لتهيئة CastContext سينغلتون. يقدم OptionsProvider مثيلاً CastOptions والذي يحتوي على خيارات تؤثر في سلوك إطار العمل. الأكثر من هذه الأشياء هو معرّف تطبيق جهاز استقبال الويب الذي يُستخدم لتصفية نتائج الاكتشاف وتشغيل تطبيق "جهاز استقبال الويب" عندما تكون جلسة البث البدء.

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 UX Widgets

يوفّر إطار عمل Google Cast التطبيقات المصغّرة التي تتوافق مع تصميم Cast. قائمة التحقق:

  • محتوى تمهيدي: ويوفر إطار العمل طريقة عرض مخصّصة IntroductoryOverlay، يظهر للمستخدم لجذب الانتباه إلى زر البث يكون الجهاز متاحًا لأوّل مرة. يمكن لتطبيق Sender تخصيص نص العنوان وموضعه النص.

  • زر البث: يكون زر البث مرئيًا بغض النظر عمّا إذا كانت أجهزة البث متوفّرة. عندما ينقر المستخدم لأول مرة على زر البث، يتم عرض مربّع حوار البث. التي تسرد الأجهزة التي تم اكتشافها. عندما ينقر المستخدم على زر البث أثناء اتصال الجهاز، يعرض البيانات الوصفية الحالية للوسائط (مثل عنوان استوديو التسجيل واسمه وصورة مصغّرة) أو يسمح للمستخدم إلغاء الربط بجهاز البث. "زر البث" يُشار إليه أحيانًا كـ "رمز البث".

  • وحدة تحكُّم صغيرة: عندما يبثّ المستخدم المحتوى وينتقل من المحتوى الحالي المحتوى أو وحدة التحكم الموسعة إلى شاشة أخرى في تطبيق المرسل، وحدة تحكم صغيرة أسفل الشاشة للسماح للمستخدم الاطّلاع على البيانات الوصفية للوسائط التي يتم بثها حاليًا والتحكّم في تشغيلها.

  • وحدة تحكم موسّعة: عندما يبث المستخدم المحتوى، إذا نقر على إشعار الوسائط أو وحدة تحكم مصغرة، يتم إطلاق وحدة التحكم الموسعة، والتي تعرض بيانات تعريف الوسائط التي تشغِّل حاليًا البيانات الوصفية وتوفّر عدة أزرار للتحكّم تشغيل الوسائط.

  • الإشعار: على أجهزة Android فقط. عندما يبث المستخدم المحتوى وينتقل بعيدًا عن سيظهر إشعار وسائط يعرض المحتوى الذي يتم بثّه حاليًا البيانات الوصفية للوسائط وعناصر التحكم في التشغيل.

  • شاشة القفل: على أجهزة Android فقط. عندما يبث المستخدم المحتوى ويتنقل فيه (أو على الجهاز) مهلة) إلى شاشة القفل، فسيتم عرض عنصر تحكم في شاشة قفل الوسائط البيانات الوصفية للوسائط التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.

يتضمن الدليل التالي أوصافًا حول كيفية إضافة هذه الأدوات إلى تطبيقك.

إضافة زر بث

نظام التشغيل Android MediaRouter تم تصميم واجهات برمجة التطبيقات لإتاحة عرض الوسائط وتشغيلها على الأجهزة الثانوية. يجب أن تتضمّن تطبيقات Android التي تستخدم واجهة برمجة التطبيقات MediaRouter زر البث كجزء من هذه الميزة. من واجهة المستخدم، للسماح للمستخدمين باختيار مسار وسائط لتشغيل الوسائط عليه جهاز ثانوي مثل جهاز بث

يجعل إطار العمل إضافة MediaRouteButton كـ Cast button أمرًا سهلاً للغاية. يجب أولاً إضافة صنف في القائمة أو MediaRouteButton في ملف xml ملف يحدد القائمة، ويمكنك استخدام CastButtonFactory لربطه بإطار العمل.

// 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);
}

لضبط مظهر زر البث باستخدام مظهر، راجع تخصيص زر البث

ضبط ميزة "اكتشاف الأجهزة"

تتم إدارة ميزة "اكتشاف الجهاز" بالكامل من خلال CastContext عند إعداد CastContext، يُحدِّد تطبيق المُرسِل جهاز استقبال الويب معرّف التطبيق، ويمكنك اختياريًا طلب تصفية مساحة الاسم من خلال الإعداد supportedNamespaces بوصة CastOptions يحتوي CastContext على إشارة إلى MediaRouter داخليًا، وسيبدأ عملية الاكتشاف في ظل الشروط التالية:

  • استنادًا إلى خوارزمية مصمّمة لتحقيق التوازن بين وقت استجابة استكشاف الأجهزة استهلاكًا للبطارية، ستبدأ ميزة "رصد" في بعض الأحيان تلقائيًا عندما دخول تطبيق المرسِل في المقدّمة.
  • مربع حوار البث مفتوح.
  • تحاول حزمة تطوير البرامج (SDK) للإرسال استرداد جلسة الإرسال.

ستتوقف عملية الاكتشاف عند إغلاق مربع حوار البث أو دخول تطبيق المرسل في الخلفية.

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;
    }
}

آلية عمل إدارة الجلسات

تقدِّم حزمة تطوير البرامج (SDK) لتكنولوجيا Cast مفهوم جلسة Google Cast، كل مجموعة تشمل خطوات الاتصال بجهاز أو إطلاق (أو الانضمام) شبكة ويب تطبيق الاستقبال والاتصال بهذا التطبيق وإعداد قناة التحكم في الوسائط الاطّلاع على مستقبل الويب دليل دورة حياة الطلبات لمزيد من المعلومات عن جلسات البث ودورة حياة مستقبل الويب.

يدير الصف الجلسات SessionManager، التي يمكن لتطبيقك الوصول إليها من خلال CastContext.getSessionManager() يتم تمثيل الجلسات الفردية من خلال الفئات الفرعية للفئة. Session على سبيل المثال: CastSession الجلسات التي تتضمن أجهزة بث. يمكن لتطبيقك الوصول إلى واجهة برمجة التطبيقات النشطة حاليًا بث الجلسة عبر SessionManager.getCurrentCastSession()

يمكن لتطبيقك استخدام SessionManagerListener لمراقبة أحداث الجلسة، مثل الإنشاء والتعليق والاستئناف أو الإنهاء. يحاول إطار العمل الاستئناف تلقائيًا من إنهاء غير طبيعي/مفاجئ عندما كانت الجلسة نشطة.

يتم إنشاء الجلسات وإزالتها تلقائيًا استجابةً لإيماءات المستخدم من مربّعات حوار MediaRouter.

للتعرّف بشكل أفضل على أخطاء بدء البثّ، يمكن للتطبيقات استخدام 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.Listener باستخدام CastSession#addCastListener ثم اتصل CastSession#getCastDevice() أثناء معاودة الاتصال على onDeviceNameChanged.

عرض نقل البث على جهاز الاستقبال على الويب لمزيد من المعلومات.

إعادة الاتصال تلقائيًا

يوفر إطار العمل ReconnectionService والذي يمكن أن يفعّله تطبيق المرسل لمعالجة إعادة الاتصال بعدة طرق الحالات الزاوية، مثل:

  • استرداد البيانات بعد فقدان شبكة Wi-Fi مؤقتًا
  • استرداد البيانات بعد إيقاف وضع السكون للجهاز
  • استرداد البيانات بعد تشغيل التطبيق في الخلفية
  • استرداد الحساب في حال تعطُّل التطبيق

تكون هذه الخدمة مفعّلة تلقائيًا، ويمكن إيقافها في CastOptions.Builder

يمكن دمج هذه الخدمة تلقائيًا في بيان التطبيق في حال الدمج التلقائي في ملف Gradle الخاص بك.

سيبدأ إطار العمل الخدمة عند إجراء جلسة وسائط، ثم سيوقفها. عند انتهاء جلسة الوسائط.

آلية عمل "التحكّم في الوسائط"

يوقف إطار عمل Google Cast RemoteMediaPlayer فئة جديدة من Cast 2.x لصالح فئة جديدة RemoteMediaClient، التي توفّر الوظائف نفسها في مجموعة من واجهات برمجة التطبيقات الأكثر ملاءمة الاضطرار إلى اجتياز GoogleApiClient.

عندما يُنشئ تطبيقك CastSession بتطبيق WebRecipients الذي يدعم مساحة اسم الوسائط، وهي مثيل سيتم إنشاء RemoteMediaClient تلقائيًا من خلال إطار العمل. تطبيقك الوصول إليه من خلال استدعاء طريقة getRemoteMediaClient() على CastSession مثال.

ستستخدم كل طرق RemoteMediaClient التي تُصدر طلبات إلى مستقبل الويب إرجاع كائن PendingResult الذي يمكن استخدامه لتتبع هذا الطلب.

من المتوقّع أن تتم مشاركة مثيل RemoteMediaClient بواسطة لأجزاء متعددة من تطبيقك، بل وبعض المكوّنات الداخلية مثل وحدات التحكم الصغيرة الدائمة خدمة الإشعارات وتحقيقًا لهذه الغاية، يدعم هذا المثيل تسجيل مثيلات متعددة من RemoteMediaClient.Listener

ضبط البيانات الوصفية للوسائط

تشير رسالة الأشكال البيانية MediaMetadata تمثل هذه السمة المعلومات حول عنصر الوسائط الذي تريد بثه. تشير رسالة الأشكال البيانية في المثال التالي، يتم إنشاء مثيل 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_*

تلقّي إشعارات عن بُعد لأجهزة متعددة

عندما يُجري المستخدم بثًا، سيتم إرسال محتوى أجهزة Android الأخرى المتصلة بالشبكة نفسها إرسال إشعار للسماح لهم أيضًا بالتحكم في التشغيل. أي شخص لديه جهاز يمكن أن يتم إيقاف هذه الإشعارات على هذا الجهاز من خلال "الإعدادات" في Google > Google Cast > عرض إشعارات جهاز التحكّم عن بُعد (تتضمن الإشعارات اختصارًا لتطبيق "الإعدادات".) لمزيد من التفاصيل، يمكنك مراجعة بثّ إشعارات جهاز التحكّم عن بُعد

إضافة وحدة تحكّم مصغّرة

وفقًا لتصميم فريق Cast قائمة التحقّق يجب على تطبيق المرسل أن يوفر تحكمًا مستمرًا يُعرف باسم mini مسؤول التحكّم بالبيانات يجب أن تظهر عندما ينتقل المستخدم بعيدًا عن صفحة المحتوى الحالية في جزء آخر من تطبيق المرسل. توفِّر وحدة التحكّم المصغّرة تذكيرًا مرئيًا إلى مستخدم جلسة البث الحالية ومن خلال النقر على وحدة التحكم الصغيرة، يمكن للمستخدم الرجوع إلى طريقة عرض وحدة التحكم الموسّعة بملء الشاشة في البثّ.

ويوفر إطار العمل عرضًا مخصصًا، وهو 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 أن يوفر تطبيق المرسل نسخة موسّعة مسؤول التحكّم بالبيانات للوسائط التي تعمل بتكنولوجيا Google Cast وحدة التحكم الموسعة هي إصدار ملء الشاشة من وحدة التحكم الصغيرة.

توفّر حزمة تطوير البرامج (SDK) لتكنولوجيا Google Cast أداةًا لوحدة التحكم الموسّعة التي تسمى ExpandedControllerActivity هذه فئة مجردة يجب أن تتضمن فئة فرعية لإضافة زر البث.

أولاً، قم بإنشاء ملف موارد قائمة جديد لوحدة التحكم الموسعة لتقديمها زر البث:

<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) عرض زر التشغيل/الإيقاف تلقائيًا بدلاً من زر التشغيل/الإيقاف المؤقت في وحدة التحكم الموسعة.

لضبط المظهر باستخدام المظاهر، اختر الأزرار التي سيتم عرضها، وإضافة أزرار مخصّصة، اطّلِع على تخصيص وحدة التحكّم الموسّعة

التحكم في مستوى الصوت

يدير إطار العمل تلقائيًا مستوى صوت تطبيق المرسِل. إطار العمل يعمل على مزامنة تطبيقات المرسل ومستقبل الويب تلقائيًا حتى يتمكن المرسل تعرض واجهة المستخدم دائمًا مستوى الصوت الذي يحدِّده جهاز استقبال الويب.

التحكّم في مستوى الصوت للزرّ الفعلي

في نظام Android، يمكن استخدام الأزرار المادية على جهاز المرسل لتغيير مستوى الصوت التلقائي لجلسة البث على جهاز استقبال الويب لأي جهاز يستخدم 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 من تطبيق المرسِل أن عناصر التحكّم في الوسائط للإشعار وفي القفل الشاشة، التي يقوم فيها المرسِل بالبث بدون التركيز على تطبيق المرسِل. تشير رسالة الأشكال البيانية إطار عمل يوفر 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 مع خالية في 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" تلقائيًا التركيز على الصوت نيابةً عنك.

التعامل مع الأخطاء

من المهم جدًا لتطبيقات المرسل أن تتعامل مع جميع استدعاءات الأخطاء وأن تقرر أفضل استجابة لكل مرحلة من دورة حياة البثّ. يمكن للتطبيق عرض المستخدم أو قد يقرر قطع الاتصال مستقبِل الويب.