يوضِّح دليل المطوّر هذا كيفية إضافة دعم Google Cast إلى تطبيق Android المُرسِل باستخدام حزمة تطوير البرامج (SDK) لنظام التشغيل Android.
الجهاز الجوّال أو الكمبيوتر المحمول هو المُرسِل الذي يتحكّم في التشغيل، وجهاز Google Cast هو المستلِم الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى الملف الثنائي لمكتبة فئات Cast والموارد المرتبطة به المتوفّرة في وقت التشغيل على المُرسِل. يشير تطبيق المُرسِل أو تطبيق Cast إلى تطبيق يعمل أيضًا على المُرسِل. يشير تطبيق Web Receiver إلى تطبيق HTML الذي يتم تشغيله على الجهاز الذي يتيح استخدام Google Cast.
يستخدم إطار عمل المُرسِل تصميم ردّ غير متزامن لإبلاغ تطبيق المُرسِل بالأحداث والانتقال بين حالات مختلفة من دورة حياة تطبيق Cast.
سير عمل التطبيق
توضِّح الخطوات التالية سير عمل التنفيذ النموذجي العالي المستوى لتطبيق Android مُرسِل:
- يبدأ إطار عمل Cast تلقائيًا عملية اكتشاف الأجهزة
MediaRouterاستنادًا إلى دورة حياةActivity. - عندما ينقر المستخدم على زر البث، يعرض إطار العمل مربّع حوار Cast الذي يتضمّن قائمة بأجهزة البث التي تم اكتشافها.
- عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق Web Receiver على جهاز البث.
- يستدعي إطار العمل عمليات معاودة الاتصال في تطبيق المُرسِل للتأكّد من تشغيل تطبيق Web Receiver.
- ينشئ إطار العمل قناة اتصال بين تطبيقات المُرسِل وWeb Receiver.
- يستخدم إطار العمل قناة الاتصال لتحميل تشغيل الوسائط والتحكّم فيه على Web Receiver.
- يُزامِن إطار العمل حالة تشغيل الوسائط بين المُرسِل وWeb Receiver: عندما يتّخذ المستخدم إجراءات في واجهة مستخدم المُرسِل، يمرِّر إطار العمل طلبات التحكّم في الوسائط هذه إلى Web Receiver، وعندما يرسل Web Receiver آخر الأخبار عن حالة الوسائط، يحدِّث إطار العمل حالة واجهة مستخدم المُرسِل.
- عندما ينقر المستخدم على زر البث لإلغاء الاتصال بجهاز البث، سيلغي إطار العمل اتصال تطبيق المُرسِل بتطبيق Web Receiver.
للاطّلاع على قائمة شاملة بجميع الفئات والطرق والأحداث في حزمة تطوير البرامج (SDK) لنظام التشغيل Android من Google Cast ، يُرجى الرجوع إلى مرجع واجهة برمجة التطبيقات المُرسِلة من Google Cast لنظام التشغيل 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
يحتوي إطار العمل على عنصر عام فريد، هو 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:
تراكب تمهيدي: يوفّر إطار العمل عنصر `View` مخصّصًا،
IntroductoryOverlay، يتم عرضه للمستخدم للفت انتباهه إلى زر البث في أول مرة يتوفّر فيها جهاز استقبال. يمكن لتطبيق المُرسِل تخصيص النص وموضع نص العنوان.زر البث: يظهر زر البث بغض النظر عن مدى توفّر أجهزة البث. عندما ينقر المستخدم على زر البث لأول مرة، يظهر مربّع حوار Cast يتضمّن الأجهزة التي تم اكتشافها. عندما ينقر المستخدم على زر Cast أثناء اتصال الجهاز، يعرض هذا الزر البيانات الوصفية الحالية للوسائط (مثل العنوان واسم استوديو التسجيل وصورة مصغّرة) أو يسمح للمستخدم بإلغاء الاتصال بجهاز Cast. يُشار أحيانًا إلى "زر البث" باسم "رمز Cast".
وحدة التحكّم المصغّرة: عندما يبث المستخدم محتوًى وينتقل من صفحة المحتوى الحالية أو وحدة التحكّم الموسّعة إلى شاشة أخرى في تطبيق المُرسِل، تظهر وحدة التحكّم المصغّرة في أسفل الشاشة لتسمح للمستخدم بالاطّلاع على البيانات الوصفية للوسائط التي يتم بثها حاليًا والتحكّم في تشغيلها.
وحدة التحكّم الموسّعة: عندما يبث المستخدم محتوًى، إذا نقر على إشعار الوسائط أو وحدة التحكّم المصغّرة، يتم تشغيل وحدة التحكّم الموسّعة التي تعرض البيانات الوصفية للوسائط قيد التشغيل حاليًا وتوفّر عدة أزرار للتحكّم في تشغيل الوسائط.
الإشعار: على Android فقط. عندما يبث المستخدم محتوًى وينتقل من تطبيق المُرسِل، يظهر إشعار بالوسائط يعرض البيانات الوصفية للوسائط التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.
شاشة القفل: على Android فقط. عندما يبث المستخدم محتوًى وينتقل (أو تنتهي مهلة الجهاز) إلى شاشة القفل، يظهر عنصر تحكّم في الوسائط على شاشة القفل يعرض البيانات الوصفية للوسائط التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.
يتضمّن الدليل التالي أوصافًا لكيفية إضافة هذه الأدوات إلى تطبيقك.
إضافة زر Cast
تم تصميم واجهات برمجة تطبيقات
MediaRouter
لنظام التشغيل Android لتفعيل عرض الوسائط وتشغيلها على أجهزة ثانوية.
يجب أن تتضمّن تطبيقات 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" />
// 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.
ستتوقف عملية الاكتشاف عند إغلاق مربّع حوار 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 (أو الانضمام إليه) والاتصال بهذا التطبيق وتهيئة قناة للتحكّم في الوسائط. لمزيد من المعلومات عن جلسات Cast ودورة حياة Web Receiver، يُرجى الرجوع إلى دليل دورة حياة تطبيق Web Receiver .
تتم إدارة الجلسات من خلال الفئة
SessionManager،
التي يمكن لتطبيقك الوصول إليها من خلال
CastContext.getSessionManager().
يتم تمثيل الجلسات الفردية من خلال فئات فرعية من الفئة
Session.
على سبيل المثال،
CastSession
تمثّل الجلسات مع أجهزة Cast. يمكن لتطبيقك الوصول إلى جلسة Cast النشطة حاليًا
من خلال
SessionManager.getCurrentCastSession().
يمكن لتطبيقك استخدام الفئة
SessionManagerListener
لمراقبة أحداث الجلسة، مثل الإنشاء والتعليق والاستئناف والإنهاء و
الإنهاء. يحاول إطار العمل تلقائيًا استئناف الجلسة بعد إنهاء غير طبيعي أو مفاجئ أثناء نشاطها.
يتم إنشاء الجلسات وإيقافها تلقائيًا استجابةً لإيماءات المستخدم من مربّعات حوار MediaRouter.
لفهم أخطاء بدء Cast بشكل أفضل، يمكن للتطبيقات استخدام
CastContext#getCastReasonCodeForCastStatusCode(int)
لتحويل خطأ بدء الجلسة إلى
CastReasonCodes.
يُرجى العِلم أنّ بعض أخطاء بدء الجلسة (مثل CastReasonCodes#CAST_CANCELLED)
هي سلوك مقصود ويجب عدم تسجيلها كخطأ.
إذا كنت بحاجة إلى معرفة التغييرات في حالة الجلسة، يمكنك تنفيذ SessionManagerListener. يستمع هذا المثال إلى مدى توفّر CastSession في Activity.
class MyActivity : Activity() { private var mCastSession: CastSession? = null private lateinit var mCastContext: CastContext private lateinit var mSessionManager: SessionManager private val mSessionManagerListener: SessionManagerListener<CastSession> = SessionManagerListenerImpl() private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> { override fun onSessionStarting(session: CastSession?) {} override fun onSessionStarted(session: CastSession?, sessionId: String) { invalidateOptionsMenu() } override fun onSessionStartFailed(session: CastSession?, error: Int) { val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error) // Handle error } override fun onSessionSuspended(session: CastSession?, reason Int) {} override fun onSessionResuming(session: CastSession?, sessionId: String) {} override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) { invalidateOptionsMenu() } override fun onSessionResumeFailed(session: CastSession?, error: Int) {} override fun onSessionEnding(session: CastSession?) {} override fun onSessionEnded(session: CastSession?, error: Int) { finish() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mCastContext = CastContext.getSharedInstance(this) mSessionManager = mCastContext.sessionManager 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.
سيبدأ إطار العمل الخدمة عندما تكون هناك جلسة وسائط، وسيتوقف عنها عند انتهاء جلسة الوسائط.
آلية عمل التحكّم في الوسائط
يوقف إطار عمل Cast استخدام الفئة
RemoteMediaPlayer
من Cast 2.x لصالح فئة جديدة هي
RemoteMediaClient،
التي توفّر الوظيفة نفسها في مجموعة من واجهات برمجة التطبيقات الأكثر ملاءمة، و
تتجنّب الحاجة إلى تمرير GoogleApiClient.
عندما ينشئ تطبيقك a
CastSession
مع تطبيق Web Receiver يتيح استخدام مساحة اسم الوسائط، سيُنشئ إطار العمل تلقائيًا مثيلاً من
RemoteMediaClient؛ ويمكن لتطبيقك
الوصول إليه من خلال استدعاء طريقة getRemoteMediaClient() على CastSession.
ستعرض جميع طرق RemoteMediaClient التي تُرسِل طلبات إلى Web Receiver عنصر 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 وارتفاع الشاشة وعرضها بالبكسل. يتم الإشارة إلى أشكال تنسيق 4K من خلال الثوابت
HDR_TYPE_*.
إشعارات وحدة التحكّم عن بُعد لأجهزة متعدّدة
عندما يبث المستخدم محتوًى، ستتلقّى أجهزة Android الأخرى على الشبكة نفسها إشعارًا يسمح لها أيضًا بالتحكّم في التشغيل. يمكن لأي مستخدم يتلقّى جهازه هذه الإشعارات إيقافها لهذا الجهاز في تطبيق "الإعدادات" على Google > Google Cast > عرض إشعارات وحدة التحكّم عن بُعد. (تتضمّن الإشعارات اختصارًا إلى تطبيق "الإعدادات"). لمزيد من التفاصيل، يُرجى الرجوع إلى مقالة إشعارات وحدة التحكّم عن بُعد في Cast.
إضافة وحدة تحكّم مصغّرة
وفقًا لـ قائمة التحقّق من تصميم Cast، يجب أن يوفّر تطبيق المُرسِل عنصر تحكّم ثابتًا يُعرف باسم وحدة التحكّم المصغّرة التي يجب أن تظهر عندما ينتقل المستخدم من صفحة المحتوى الحالية إلى جزء آخر من تطبيق المُرسِل. توفّر وحدة التحكّم المصغّرة تذكيرًا مرئيًا للمستخدم بجلسة Cast الحالية. من خلال النقر على وحدة التحكّم المصغّرة، يمكن للمستخدم العودة إلى عرض وحدة التحكّم الموسّعة بملء الشاشة في Cast.
يوفّر إطار العمل عنصر `MiniControllerFragment` مخصّصًا من `View` يمكنك إضافته إلى أسفل ملف التنسيق لكل نشاط تريد عرض وحدة التحكّم المصغّرة فيه.
<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.
هذه فئة مجرّدة عليك إنشاء فئة فرعية منها لإضافة زر البث.
أولاً، أنشئ ملفًا جديدًا لمورد قائمة لوحدة التحكّم الموسّعة لتوفير زر البث:
<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(); }
عدِّل طريقة loadRemoteMedia في LocalPlayerActivity لعرض نشاطك الجديد عند تحميل الوسائط البعيدة:
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، يمكن استخدام الأزرار الفعلية على جهاز المُرسِل لتغيير مستوى صوت جلسة Cast على Web Receiver تلقائيًا لأي جهاز يستخدم 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.