يوضِّح دليل المطوّر هذا كيفية إضافة دعم 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) من Google Cast على Android، يُرجى الرجوع إلى مرجع واجهة برمجة التطبيقات للمُرسِل من 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.
عندما ينشئ تطبيقك
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.