يوضّح دليل المطوّرين هذا كيفية إضافة إمكانية استخدام Google Cast إلى تطبيقك على جهاز Android الذي يرسل المحتوى باستخدام حزمة تطوير البرامج (SDK) الخاصة بتطبيقات Android التي ترسل المحتوى.
الجهاز الجوّال أو الكمبيوتر المحمول هو المرسِل الذي يتحكّم في التشغيل، وجهاز Google Cast هو المستلِم الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى ملفات ثنائية لمكتبة فئات Cast والموارد المرتبطة بها المتوفّرة في وقت التشغيل على جهاز المُرسِل. يشير تطبيق المرسِل أو تطبيق البث إلى تطبيق يعمل أيضًا على جهاز المرسِل. يشير تطبيق Web Receiver إلى تطبيق HTML الذي يعمل على الجهاز الذي يتيح استخدام Google Cast.
يستخدم إطار عمل المرسِل تصميم معاودة الاتصال غير المتزامن لإبلاغ تطبيق المرسِل بالأحداث والانتقال بين حالات مختلفة من دورة حياة تطبيق Cast.
تدفّق التطبيق
توضّح الخطوات التالية مسار التنفيذ النموذجي العالي المستوى لتطبيق Android خاص بالمرسِل:
- يبدأ إطار عمل Cast تلقائيًا
MediaRouterفي رصد الأجهزة استنادًا إلى دورة حياةActivity. - عندما ينقر المستخدم على زر "البث"، يعرض إطار العمل مربّع حوار "البث" مع قائمة بأجهزة البث التي تم العثور عليها.
- عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق 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. ويبلغ الحد الأدنى حاليًا المستوى 24 من واجهة برمجة التطبيقات، بينما يبلغ المستوى المستهدف 35.
<uses-sdk
android:minSdkVersion="24"
android:targetSdkVersion="35" />
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، تظهر للمستخدم لجذب الانتباه إلى زر البث في المرة الأولى التي يتوفّر فيها جهاز استقبال. يمكن لتطبيق "المُرسِل" تخصيص النص وموضع نص العنوان.زر البث: يظهر زر البث بغض النظر عن توفّر أجهزة البث. عندما ينقر المستخدم على زر "البث" لأول مرة، يظهر مربّع حوار "البث" يعرض الأجهزة التي تم العثور عليها. عندما ينقر المستخدم على زر "البث" أثناء توصيل الجهاز، يتم عرض البيانات الوصفية الحالية للوسائط (مثل العنوان واسم استوديو التسجيل وصورة مصغّرة) أو يُسمح للمستخدم بقطع الاتصال بجهاز البث. يُشار أحيانًا إلى "زر البث" باسم "رمز البث".
وحدة التحكّم المصغّرة: عندما يبدأ المستخدم في بث المحتوى وينتقل بعيدًا عن صفحة المحتوى الحالية أو وحدة التحكّم الموسّعة إلى شاشة أخرى في تطبيق المرسِل، تظهر وحدة التحكّم المصغّرة في أسفل الشاشة للسماح للمستخدم بالاطّلاع على بيانات وصفية للوسائط التي يتم بثّها حاليًا والتحكّم في التشغيل.
وحدة التحكّم الموسّعة: عندما يبدأ المستخدم ببث المحتوى، إذا نقر على إشعار الوسائط أو وحدة التحكّم المصغّرة، سيتم تشغيل وحدة التحكّم الموسّعة التي تعرض البيانات الوصفية للوسائط التي يتم تشغيلها حاليًا وتوفّر عدة أزرار للتحكّم في تشغيل الوسائط.
الإشعار: على أجهزة Android فقط عندما يبدأ المستخدم في بث المحتوى وينتقل إلى تطبيق آخر غير تطبيق المرسِل، يظهر إشعار بالوسائط يعرض بيانات الوسائط الوصفية التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.
شاشة القفل: نظام التشغيل Android فقط عندما يبدأ المستخدم في بث المحتوى وينتقل إلى شاشة القفل (أو إذا انتهت مهلة الجهاز)، يظهر عنصر تحكّم في الوسائط على شاشة القفل يعرض بيانات الوسائط الوصفية التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.
يتضمّن الدليل التالي أوصافًا حول كيفية إضافة هذه التطبيقات المصغّرة إلى تطبيقك.
إضافة زر البث
تم تصميم واجهات برمجة التطبيقات
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.
سيتم إيقاف عملية البحث عند إغلاق مربّع حوار البث أو عند انتقال تطبيق المرسِل إلى الخلفية.
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.Listener باستخدام CastSession#addCastListener.
ثم استدعِ
CastSession#getCastDevice()
أثناء عملية الاسترجاع onDeviceNameChanged.
لمزيد من المعلومات، يُرجى الاطّلاع على مقالة إعادة توجيه البث على تطبيق Web Receiver.
إعادة الاتصال تلقائيًا
يوفّر إطار العمل
ReconnectionService
الذي يمكن أن يفعّله تطبيق المرسِل للتعامل مع إعادة الاتصال في العديد من الحالات
الدقيقة، مثل:
- استرداد البيانات بعد فقدان اتصال Wi-Fi مؤقتًا
- الخروج من وضع السكون
- الاسترداد من الخلفية
- الاسترداد في حال تعطُّل التطبيق
تكون هذه الخدمة مفعّلة تلقائيًا، ويمكن إيقافها في
CastOptions.Builder.
يمكن دمج هذه الخدمة تلقائيًا في بيان تطبيقك إذا تم تفعيل ميزة الدمج التلقائي في ملف gradle.
سيبدأ إطار العمل الخدمة عند توفّر جلسة وسائط، وسيتوقف عند انتهاء جلسة الوسائط.
طريقة عمل "أدوات التحكّم في الوسائط"
يتم إيقاف الفئة
RemoteMediaPlayer
نهائيًا من الإصدار 2.x من Cast لصالح فئة جديدة
RemoteMediaClient،
التي توفّر الوظيفة نفسها في مجموعة من واجهات برمجة التطبيقات الأكثر ملاءمة،
وتتجنّب الحاجة إلى تمرير GoogleApiClient.
عندما ينشئ تطبيقك
CastSession
باستخدام تطبيق "أداة استقبال الويب" الذي يتيح استخدام مساحة اسم الوسائط، سيتم إنشاء مثيل من
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 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.
هذا هو الصف التجريدي الذي يجب تصنيفه فرعيًا لإضافة زر البث.
أولاً، أنشئ ملفًا جديدًا لمورد القائمة خاصًا بوحدة التحكّم الموسّعة لتوفير زر البث:
<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) تلقائيًا زر تشغيل/إيقاف بدلاً من زر التشغيل/الإيقاف المؤقت في عنصر التحكّم في الإشعارات، ولكن ليس في عنصر التحكّم في شاشة القفل.
ملاحظة: لعرض عناصر التحكّم في شاشة القفل على الأجهزة التي تعمل بإصدارات Android أقدم من Lollipop، سيطلب RemoteMediaClient تلقائيًا أولويّة الصوت نيابةً عنك.
معالجة الأخطاء
من المهم جدًا أن تتعامل تطبيقات المرسِل مع جميع عمليات معاودة الاتصال الخاصة بالأخطاء وأن تحدّد أفضل استجابة لكل مرحلة من مراحل دورة حياة Cast. يمكن للتطبيق عرض مربّعات حوار الأخطاء للمستخدم أو يمكنه إيقاف الاتصال بجهاز استقبال الويب.