يوضّح دليل المطوّرين هذا كيفية إضافة ميزة التوافق مع Google Cast إلى تطبيقك على جهاز Android الذي يرسل المحتوى باستخدام حزمة تطوير البرامج (SDK) الخاصة بتطبيق Android الذي يرسل المحتوى.
الجهاز الجوّال أو الكمبيوتر المحمول هو جهاز الإرسال الذي يتحكّم في التشغيل، وجهاز Google Cast هو جهاز الاستقبال الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى ملفات ثنائية لمكتبة فئات Cast والموارد المرتبطة بها المتوفّرة في وقت التشغيل على جهاز المُرسِل. يشير تطبيق المُرسِل أو تطبيق Cast إلى تطبيق يعمل أيضًا على جهاز المُرسِل. يشير تطبيق Web Receiver إلى تطبيق HTML الذي يعمل على الجهاز المتوافق مع Cast.
يستخدم إطار عمل المرسِل تصميم معاودة الاتصال غير المتزامن لإبلاغ تطبيق المرسِل بالأحداث والانتقال بين حالات مختلفة من دورة حياة تطبيق Cast.
تدفّق التطبيق
توضّح الخطوات التالية مسار التنفيذ العادي العالي المستوى لتطبيق Android الخاص بالمرسِل:
- يبدأ إطار عمل البث تلقائيًا عملية استكشاف الأجهزة استنادًا إلى دورة حياة
Activity
.MediaRouter
- عندما ينقر المستخدم على زر "البث"، يعرض إطار العمل مربّع حوار "البث" مع قائمة بأجهزة البث التي تم العثور عليها.
- عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق Web Receiver على جهاز البث.
- يستدعي إطار العمل عمليات رد الاتصال في تطبيق المرسِل للتأكّد من أنّه تم تشغيل تطبيق Web Receiver.
- ينشئ إطار العمل قناة تواصل بين تطبيق المرسِل وتطبيقات Web Receiver.
- يستخدم إطار العمل قناة الاتصال لتحميل الوسائط والتحكّم في تشغيلها على Web Receiver.
- يتمكّن إطار العمل من مزامنة حالة تشغيل الوسائط بين جهاز الإرسال وWeb Receiver: عندما ينفّذ المستخدم إجراءات في واجهة مستخدم جهاز الإرسال، يمرّر إطار العمل طلبات التحكّم في الوسائط هذه إلى Web Receiver، وعندما يرسل Web Receiver تحديثات حالة الوسائط، يعدّل إطار العمل حالة واجهة مستخدم جهاز الإرسال.
- عندما ينقر المستخدم على زر "البث" لقطع الاتصال بجهاز البث، سيقطع إطار العمل اتصال تطبيق المرسِل بـ Web Receiver.
للحصول على قائمة شاملة بجميع الفئات والطرق والأحداث في حزمة تطوير البرامج (SDK) لنظام التشغيل Android في Google Cast، راجِع مرجع واجهة برمجة التطبيقات Google Cast Sender لنظام التشغيل Android. تتضمّن الأقسام التالية خطوات إضافة ميزة Cast إلى تطبيق Android.
ضبط ملف بيان Android
يتطلّب ملف AndroidManifest.xml في تطبيقك ضبط العناصر التالية لحزمة تطوير البرامج (SDK) الخاصة بـ Cast:
uses-sdk
اضبط الحدّ الأدنى والمستهدف لمستويات واجهة برمجة تطبيقات Android التي تتيحها حزمة تطوير البرامج (SDK) الخاصة بـ Cast. يبلغ الحد الأدنى حاليًا المستوى 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>
تهيئة Cast Context
يحتوي إطار العمل على كائن فردي عام، وهو CastContext
، الذي ينسّق جميع التفاعلات في إطار العمل.
يجب أن ينفّذ تطبيقك واجهة
OptionsProvider
لتوفير الخيارات اللازمة لتهيئة
CastContext
للكائن المفرد. توفّر OptionsProvider
مثيلاً من
CastOptions
الذي يحتوي على خيارات تؤثر في سلوك إطار العمل. الأهم من بينها هو معرّف تطبيق Web Receiver، والذي يُستخدَم لفلترة نتائج البحث ولتشغيل تطبيق Web Receiver عند بدء جلسة 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 } }
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()
.
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
public class MyActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { CastContext castContext = CastContext.getSharedInstance(this); } }
أدوات واجهة مستخدم Cast
يوفّر إطار عمل Cast الأدوات التي تتوافق مع قائمة التحقّق من تصميم Cast:
الطبقة المتراكبة التمهيدية: يوفّر إطار العمل طريقة عرض مخصّصة،
IntroductoryOverlay
، تظهر للمستخدم لجذب الانتباه إلى زر Cast في المرة الأولى التي يتوفّر فيها جهاز استقبال. يمكن لتطبيق Sender تخصيص النص وموضع نص العنوان.زر البث: يظهر زر البث بغض النظر عن توفّر أجهزة البث. عندما ينقر المستخدم على زر "البث" للمرة الأولى، يظهر مربّع حوار "البث" يعرض الأجهزة التي تم العثور عليها. عندما ينقر المستخدم على زر "البث" أثناء اتصال الجهاز، يتم عرض البيانات الوصفية للوسائط الحالية (مثل العنوان واسم استوديو التسجيل وصورة مصغّرة) أو السماح للمستخدم بقطع الاتصال بجهاز البث. يُشار أحيانًا إلى "زر البث" باسم "رمز البث".
وحدة التحكّم المصغّرة: عندما يبدأ المستخدم في بث المحتوى وينتقل من صفحة المحتوى الحالية أو وحدة التحكّم الموسّعة إلى شاشة أخرى في تطبيق المرسِل، تظهر وحدة التحكّم المصغّرة في أسفل الشاشة للسماح للمستخدم بالاطّلاع على بيانات الوسائط الوصفية التي يتم بثّها حاليًا والتحكّم في التشغيل.
وحدة التحكّم الموسّعة: عندما يبدأ المستخدم في بث المحتوى، إذا نقر على إشعار الوسائط أو وحدة التحكّم المصغّرة، سيتم تشغيل وحدة التحكّم الموسّعة التي تعرض البيانات الوصفية للوسائط التي يتم تشغيلها حاليًا وتوفّر عدة أزرار للتحكّم في تشغيل الوسائط.
الإشعار: على أجهزة Android فقط عندما يبث المستخدم محتوًى وينتقل إلى تطبيق آخر غير تطبيق المرسِل، يظهر إشعار بالوسائط يعرض البيانات الوصفية للوسائط التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.
شاشة القفل: نظام التشغيل Android فقط عندما يبدأ المستخدم في بث المحتوى وينتقل إلى شاشة القفل (أو تنتهي مهلة الجهاز)، يظهر عنصر تحكّم في الوسائط على شاشة القفل يعرض بيانات الوسائط الوصفية التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.
يتضمّن الدليل التالي أوصافًا حول كيفية إضافة هذه التطبيقات المصغّرة إلى تطبيقك.
إضافة زر البث
تم تصميم واجهات برمجة التطبيقات
MediaRouter
في Android لتتيح عرض الوسائط وتشغيلها على الأجهزة الثانوية.
يجب أن تتضمّن تطبيقات Android التي تستخدم واجهة برمجة التطبيقات MediaRouter
زر إرسال كجزء من واجهة المستخدم، وذلك للسماح للمستخدمين باختيار مسار وسائط لتشغيل الوسائط على جهاز ثانوي، مثل جهاز Cast.
يسهّل إطار العمل إضافة
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" />
// 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 }
// 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) }
// 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، يحدّد تطبيق المرسِل رقم تعريف تطبيق Web Receiver، ويمكنه اختياريًا طلب فلترة مساحة الاسم من خلال ضبط supportedNamespaces
في CastOptions
.
يحتوي CastContext
على مرجع إلى MediaRouter
داخليًا، وسيبدأ عملية البحث في الحالات التالية:
- استنادًا إلى خوارزمية مصمَّمة لتحقيق التوازن بين وقت استجابة اكتشاف الجهاز واستهلاك البطارية، سيتم بدء عملية الاكتشاف تلقائيًا في بعض الأحيان عندما يدخل تطبيق المرسِل إلى المقدّمة.
- مربّع حوار Cast مفتوح.
- تحاول حزمة تطوير البرامج (SDK) لخدمة 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 } }
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 مفهوم جلسة Cast، ويجمع إنشاء هذه الجلسة بين خطوات الاتصال بجهاز وتشغيل تطبيق Web Receiver (أو الانضمام إليه) والاتصال بهذا التطبيق وتهيئة قناة للتحكّم في الوسائط. اطّلِع على دليل دورة حياة التطبيق في Web Receiver للحصول على مزيد من المعلومات حول جلسات Cast ودورة حياة Web Receiver.
تتم إدارة الجلسات من خلال فئة
SessionManager
،
التي يمكن لتطبيقك الوصول إليها من خلال
CastContext.getSessionManager()
.
يتم تمثيل الجلسات الفردية بفئات فرعية من الفئة
Session
.
على سبيل المثال،
CastSession
تمثّل الجلسات التي تستخدم أجهزة البث. يمكن لتطبيقك الوصول إلى جلسة Cast النشطة حاليًا من خلال SessionManager.getCurrentCastSession()
.
يمكن لتطبيقك استخدام فئة
SessionManagerListener
لمراقبة أحداث الجلسة، مثل الإنشاء والتعليق والاستئناف والإنهاء. يحاول إطار العمل تلقائيًا استئناف عملية التوقف غير الطبيعي/المفاجئ أثناء نشاط إحدى الجلسات.
يتم إنشاء الجلسات وإيقافها تلقائيًا استجابةً لإيماءات المستخدم من مربّعات الحوار MediaRouter
.
لفهم أخطاء بدء البث بشكل أفضل، يمكن للتطبيقات استخدام
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 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) } }
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
.
لمزيد من المعلومات، راجِع مقالة نقل البث على Web Receiver.
إعادة الاتصال تلقائيًا
يوفّر إطار العمل
ReconnectionService
الذي يمكن لتطبيق المرسِل تفعيله للتعامل مع إعادة الاتصال في العديد من الحالات
الدقيقة، مثل:
- استرداد البيانات بعد فقدان اتصال Wi-Fi مؤقتًا
- الاسترداد من وضع السكون على الجهاز
- الاسترداد من وضع التطبيق في الخلفية
- استرداد البيانات في حال تعطُّل التطبيق
تكون هذه الخدمة مفعَّلة تلقائيًا، ويمكن إيقافها في
CastOptions.Builder
.
يمكن دمج هذه الخدمة تلقائيًا في بيان تطبيقك إذا تم تفعيل الدمج التلقائي في ملف Gradle.
سيبدأ إطار العمل الخدمة عند توفّر جلسة وسائط، وسيتوقف عند انتهاء جلسة الوسائط.
طريقة عمل ميزة "التحكّم في الوسائط"
يتم إيقاف الفئة
RemoteMediaPlayer
في الإصدار 2.x من Cast نهائيًا في إطار عمل Cast، وسيتم استبدالها بالفئة الجديدة
RemoteMediaClient
،
التي توفّر الوظيفة نفسها في مجموعة من واجهات برمجة التطبيقات الأكثر ملاءمة،
وتتجنّب الحاجة إلى تمرير GoogleApiClient.
عندما ينشئ تطبيقك
CastSession
مع تطبيق Web Receiver يتيح مساحة اسم الوسائط، سيتم إنشاء مثيل
RemoteMediaClient
تلقائيًا بواسطة إطار العمل، ويمكن لتطبيقك
الوصول إليه من خلال استدعاء طريقة getRemoteMediaClient()
في مثيل CastSession
.
ستعرض جميع طرق RemoteMediaClient
التي ترسل طلبات إلى "برنامج استقبال الويب" كائن PendingResult يمكن استخدامه لتتبُّع هذا الطلب.
من المتوقّع أن تتم مشاركة مثيل RemoteMediaClient
بين أجزاء متعدّدة من تطبيقك، وكذلك بعض المكوّنات الداخلية للإطار، مثل وحدات التحكّم المصغّرة الدائمة وخدمة الإشعارات.
لذلك، يتيح هذا المثال تسجيل مثيلات متعددة من
RemoteMediaClient.Listener
.
ضبط البيانات الوصفية للوسائط
يمثّل الصف
MediaMetadata
المعلومات حول عنصر الوسائط الذي تريد بثه. ينشئ المثال التالي مثيلاً جديدًا من 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))))
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
لتشغيل تطبيق مشغّل الوسائط وإيقافه مؤقتًا والتحكّم فيه بأي طريقة أخرى أثناء تشغيله على Web Receiver.
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())
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، يجب أن يوفّر تطبيق المرسِل عنصر تحكّم دائمًا يُعرف باسم وحدة التحكّم المصغّرة، ويجب أن يظهر عندما ينتقل المستخدم من صفحة المحتوى الحالية إلى جزء آخر من تطبيق المرسِل. تقدّم وحدة التحكّم المصغّرة تذكيرًا مرئيًا للمستخدم بجلسة Cast الحالية. من خلال النقر على وحدة التحكّم المصغّرة، يمكن للمستخدم العودة إلى عرض وحدة التحكّم الموسّعة بملء الشاشة في Cast.
يوفر إطار العمل طريقة عرض مخصّصة، 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 أن يوفّر تطبيق المرسِل وحدة تحكّم موسّعة للوسائط التي يتم بثّها. وحدة التحكّم الموسّعة هي نسخة بملء الشاشة من وحدة التحكّم المصغّرة.
توفّر حزمة تطوير البرامج (SDK) الخاصة بـ Cast أداة للوحة التحكّم الموسّعة تُسمّى
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 } }
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() }
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(); }
عدِّل طريقة 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() ) }
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) تلقائيًا زر تشغيل/إيقاف بدلاً من زر تشغيل/إيقاف مؤقت في وحدة التحكّم الموسّعة.
لضبط المظهر باستخدام المظاهر، اختَر الأزرار التي تريد عرضها، وأضِف أزرارًا مخصّصة، راجِع تخصيص وحدة التحكّم الموسّعة.
التحكم في مستوى الصوت
يدير إطار العمل مستوى الصوت تلقائيًا لتطبيق المرسِل. ويقوم إطار العمل تلقائيًا بمزامنة تطبيقَي المرسِل وWeb Receiver، وبالتالي تعرض واجهة مستخدم المرسِل دائمًا مستوى الصوت الذي يحدّده Web Receiver.
التحكّم بمستوى الصوت باستخدام الأزرار
على أجهزة Android، يمكن استخدام الأزرار المادية على جهاز المرسِل لتغيير مستوى صوت جلسة البث على "برنامج استقبال الويب" تلقائيًا لأي جهاز يعمل بالإصدار Jelly Bean أو الإصدارات الأحدث.
التحكّم في مستوى الصوت باستخدام الأزرار المادية قبل إصدار Jelly Bean
لاستخدام مفاتيح مستوى الصوت المادية للتحكّم في مستوى صوت جهاز Web Receiver على أجهزة Android التي تعمل بإصدار أقدم من Jelly Bean، يجب أن يتجاهل تطبيق المرسِل dispatchKeyEvent
في أنشطته، وأن يستدعي CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
:
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
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
.
// 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()
// 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
.
في الوقت الحالي، تكون ميزة شاشة القفل مفعَّلة طالما أنّ الإشعارات مفعَّلة.
// ... 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()
// ... 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. يمكن للتطبيق عرض مربّعات حوار الخطأ للمستخدم أو يمكنه إيقاف الاتصال بـ Web Receiver.