يصف دليل المطوِّر هذا طريقة إضافة دعم Google Cast إلى تطبيق Android Sender باستخدام حزمة Android Sender SDK.
الجهاز الجوّال أو الكمبيوتر المحمول هو المُرسِل الذي يتحكّم في التشغيل، وجهاز Google Cast هو جهاز الاستقبال الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى البرنامج الثنائي لمكتبة فئات Google Cast والموارد المرتبطة به الموجودة في وقت التشغيل على المُرسِل. يشير تطبيق المرسِل أو تطبيق البث إلى تطبيق يتم تشغيله على المُرسِل أيضًا. يشير تطبيق "جهاز استقبال الويب" إلى تطبيق HTML الذي يعمل على الجهاز الذي يعمل بتكنولوجيا Google Cast
يستخدم إطار عمل المرسِل تصميمًا غير متزامن لمعاودة الاتصال لإعلام تطبيق المرسِل بالأحداث والانتقال بين الحالات المختلفة لدورة حياة تطبيق Cast.
مسار التطبيق
تصف الخطوات التالية تدفق التنفيذ النموذجي عالي المستوى لمرسل تطبيق Android:
- يبدأ إطار عمل Google Cast تلقائيًا استكشاف أجهزة
MediaRouter
استنادًا إلى دورة حياةActivity
. - عندما ينقر المستخدم على زر البث، يعرض إطار العمل مربّع الحوار "إرسال" مع قائمة بأجهزة البث التي تم اكتشافها.
- عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق "جهاز استقبال الويب" على جهاز البث.
- يستدعي إطار العمل استدعاءات في تطبيق المرسِل للتأكّد من إطلاق تطبيق WebRecipient.
- وينشئ إطار العمل قناة اتصال بين تطبيق المرسِل وتطبيقات استقبال الويب.
- يستخدم إطار العمل قناة الاتصال لتحميل تشغيل الوسائط والتحكم فيها على جهاز استقبال الويب.
- يعمل إطار العمل على مزامنة حالة تشغيل الوسائط بين المُرسِل ومستقبل الويب: عندما يُجري المستخدم إجراءات على واجهة المستخدم للمرسِل، يمرر إطار العمل طلبات التحكم في الوسائط هذه إلى مستقبل الويب، وعندما يرسل مستقبل الويب تحديثات حالة الوسائط، يعدِّل إطار العمل حالة واجهة المستخدم للمرسِل.
- عندما ينقر المستخدم على زر "إرسال" لقطع الاتصال بجهاز البث، سيعمل إطار العمل على إلغاء ربط تطبيق المُرسِل من جهاز استقبال الويب.
للحصول على قائمة شاملة بجميع الفئات والأساليب والأحداث في حزمة تطوير البرامج (SDK) لنظام التشغيل Android لخدمة Google Cast، يمكنك الاطّلاع على مرجع واجهة برمجة التطبيقات Google Cast Sender لأجهزة Android. تتناول الأقسام التالية خطوات إضافة ميزة "البث" إلى تطبيق Android.
إعداد بيان Android
يتطلب ملف AndroidManifest.xml في تطبيقك ضبط العناصر التالية في حزمة تطوير البرامج (SDK) لتكنولوجيا Cast:
uses-sdk
يمكنك ضبط الحدّ الأدنى لمستويات واجهة برمجة تطبيقات Android التي تتوافق مع حزمة تطوير البرامج (SDK) الخاصة بالبث. الحد الأدنى حاليًا هو المستوى 23 من واجهة برمجة التطبيقات والهدف هو المستوى 34 لواجهة برمجة التطبيقات.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
يمكنك ضبط مظهر تطبيقك استنادًا إلى الحد الأدنى لإصدار حزمة تطوير البرامج (SDK) لنظام التشغيل Android. على سبيل المثال، إذا لم تكن تطبّق المظهر الخاص بك، عليك استخدام أحد خيارات Theme.AppCompat
عند استهداف إصدار Android SDK كحد أدنى يعمل قبل Lollipop.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
إعداد سياق البث
ويحتوي إطار العمل على كائن سينغلتون عام، CastContext
، ينسق
جميع تفاعلات إطار العمل.
يجب أن ينفذ تطبيقك واجهة
OptionsProvider
لتوفير الخيارات اللازمة لإعداد
CastContext
سينغلتون. يوفّر OptionsProvider
مثيلاً لـ
CastOptions
الذي يحتوي على خيارات تؤثر في سلوك إطار العمل. وأهم هذه الخيارات هو معرّف تطبيق "جهاز استقبال الويب" الذي يُستخدم لفلترة
نتائج الاكتشاف وتشغيل تطبيق "جهاز استقبال الويب" عند بدء جلسة
البث.
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 UX Widgets
يوفّر إطار عمل Google Cast التطبيقات المصغّرة التي تتوافق مع قائمة التحقّق من تصميم Google Cast:
تراكب تمهيدي: يوفّر إطار العمل عرضًا مخصّصًا
IntroductoryOverlay
يظهر للمستخدم لجذب الانتباه إلى زر البث في المرّة الأولى التي يتوفّر فيها جهاز استقبال. يمكن لتطبيق المُرسِل تخصيص النص وموضع نص العنوان.زر البث: يكون زر البث مرئيًا بغض النظر عمّا إذا كانت أجهزة البث متوفّرة. عندما ينقر المستخدم لأول مرة على زر "إرسال"، يظهر مربع حوار "إرسال" يسرد الأجهزة التي تم اكتشافها. عندما ينقر المستخدم على زر "بث" أثناء توصيل الجهاز، يتم عرض البيانات الوصفية الحالية للوسائط (مثل العنوان واسم استوديو التسجيل والصورة المصغّرة) أو يسمح للمستخدم بقطع الاتصال بجهاز البث. يُشار أحيانًا إلى "زر البث" باسم "رمز البث".
وحدة التحكّم الصغيرة: عندما يبثّ المستخدم المحتوى وينتقل من صفحة المحتوى الحالية أو من وحدة التحكّم الموسّعة إلى شاشة أخرى في تطبيق المرسِل، يتمّ عرض وحدة التحكّم المصغّرة أسفل الشاشة للسماح للمستخدم برؤية البيانات الوصفية للوسائط التي يتم بثها حاليًا وللتحكّم في عملية التشغيل.
وحدة تحكم موسّعة: عند بث المستخدم للمحتوى، إذا نقر على إشعار الوسائط أو وحدة التحكّم الصغيرة، يتم تشغيل وحدة التحكّم الموسّعة التي تعرض البيانات الوصفية للوسائط التي تشغِّل حاليًا وتوفّر عدة أزرار للتحكّم في تشغيل الوسائط.
الإشعار: Android فقط. عندما يبث المستخدم المحتوى وينتقل إلى خارج تطبيق المُرسِل، يظهر إشعار وسائط يعرض البيانات الوصفية للوسائط التي يتم بثها حاليًا وعناصر التحكّم في التشغيل.
شاشة القفل: Android فقط. عندما يبث المستخدم المحتوى وينتقل (أو تنقل الجهاز) إلى شاشة القفل، يتم عرض عنصر تحكّم في شاشة قفل الوسائط يعرض البيانات الوصفية الحالية للوسائط التي تبث الوسائط وعناصر التحكّم في التشغيل.
ويتضمّن الدليل التالي أوصافًا حول كيفية إضافة هذه التطبيقات المصغّرة إلى تطبيقك.
إضافة زر بث
تم تصميم واجهات برمجة التطبيقات Android
MediaRouter
لتفعيل عرض الوسائط وتشغيلها على الأجهزة الثانوية.
إنّ تطبيقات Android التي تستخدم واجهة برمجة التطبيقات MediaRouter
يجب أن تتضمّن زر البث كجزء من واجهة المستخدم ليتمكّن المستخدمون من اختيار مسار الوسائط لتشغيل الوسائط على جهاز ثانوي مثل جهاز بث.
يسهّل إطار العمل إضافة
MediaRouteButton
كـ
Cast button
أمرًا في غاية السهولة. وعليك أولاً إضافة عنصر قائمة أو MediaRouteButton
في ملف xml
الذي يحدّد قائمتك واستخدام
CastButtonFactory
لربطها بإطار العمل.
// To add a Cast button, add the following snippet.
// menu.xml
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
// 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، يُحدِّد تطبيق المُرسِل رقم تعريف تطبيق WebRecipient، ويمكنه طلب فلترة مساحات الاسم اختياريًا من خلال ضبط supportedNamespaces
في CastOptions
.
يتضمّن CastContext
إشارة إلى MediaRouter
داخليًا، وسيبدأ عملية الاكتشاف في حال استيفاء الشروط التالية:
- استنادًا إلى خوارزمية مصمَّمة لتحقيق التوازن بين وقت استجابة رصد الأجهزة واستخدام البطارية، يمكن أحيانًا بدء الاكتشاف تلقائيًا عندما يكون تطبيق المرسِل في المقدّمة.
- مربع حوار البث مفتوح.
- تحاول حزمة تطوير البرامج (SDK) للإرسال استرداد جلسة الإرسال.
ستتوقف عملية الاكتشاف عند إغلاق مربع حوار البث أو عند دخول تطبيق المُرسِل في الخلفية.
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 مفهوم جلسة Google Cast، حيث تجمع تأسيسها خطوات الاتصال بالجهاز وتشغيل أو الانضمام إلى أحد تطبيقات جهاز استقبال الويب والاتصال بهذا التطبيق وتهيئة قناة التحكم في الوسائط. يمكنك الاطّلاع على دليل دورة حياة الطلب على جهاز استقبال الويب للحصول على مزيد من المعلومات حول جلسات البث ودورة حياة مستقبل الويب.
تتم إدارة الجلسات من خلال الصف
SessionManager
،
الذي يمكن لتطبيقك الوصول إليه من خلال
CastContext.getSessionManager()
.
يتم تمثيل الجلسات الفردية بفئات فرعية للفئة Session
.
على سبيل المثال، يمثّل الرمز
CastSession
الجلسات التي تتضمّن أجهزة بث. يمكن لتطبيقك الوصول إلى جلسة البث
النشطة حاليًا من خلال
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 } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onPause() { super.onPause() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) mCastSession = null } }
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(); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onPause() { super.onPause(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); mCastSession = null; } }
نقل البث
يتم حاليًا الاحتفاظ بحالة الجلسة أساس نقل البث، حيث يمكن للمستخدمين نقل ملفات الصوت والفيديو الحالية على جميع الأجهزة باستخدام الطلبات الصوتية أو تطبيق Google Home أو الشاشات الذكية. يتوقف تشغيل الوسائط على أحد الأجهزة (المصدر) ويستمر على آخر (الوجهة). يمكن لأي جهاز بث يتضمّن أحدث البرامج الثابتة أن يعمل كمصادر أو وجهات في عملية نقل البث.
للحصول على الجهاز الوجهة الجديد أثناء نقل البث أو توسيعه،
يمكنك تسجيل
Cast.Listener
باستخدام
CastSession#addCastListener
.
بعد ذلك، يمكنك الاتصال بالرقم
CastSession#getCastDevice()
أثناء معاودة الاتصال على onDeviceNameChanged
.
يُرجى الاطّلاع على نقل البث على جهاز استقبال الويب للحصول على مزيد من المعلومات.
إعادة الاتصال تلقائيًا
يوفّر إطار العمل رمز ReconnectionService
يمكن تفعيله من خلال تطبيق المرسِل لمعالجة إعادة الربط في العديد من الحالات البسيطة، مثل:
- استرداد البيانات بعد فقدان شبكة Wi-Fi مؤقتًا
- استرداد البيانات بعد إيقاف وضع السكون للجهاز
- استرداد البيانات بعد تشغيل التطبيق في الخلفية
- استرداد الحساب في حال تعطُّل التطبيق
تكون هذه الخدمة مفعّلة تلقائيًا، ويمكن إيقافها في CastOptions.Builder
.
يمكن دمج هذه الخدمة تلقائيًا في بيان تطبيقك إذا تم تفعيل الدمج التلقائي في ملف Gradle.
سيبدأ إطار العمل الخدمة عند توفُّر جلسة وسائط، ويوقفها عند انتهاء جلسة الوسائط.
آلية عمل "التحكّم في الوسائط"
أوقف إطار عمل Google Cast فئة
RemoteMediaPlayer
من Cast 2.x لصالح فئة جديدة
RemoteMediaClient
،
توفِّر الوظيفة نفسها في مجموعة من واجهات برمجة التطبيقات الأكثر ملاءمةً،
وتتجنّب الاضطرار إلى الانتقال إلى GoogleApiClient.
عندما ينشئ تطبيقك
CastSession
مع تطبيق WebRecipients متوافق مع مساحة اسم الوسائط، سيتم إنشاء مثيل
RemoteMediaClient
تلقائيًا من خلال إطار العمل. ويمكن لتطبيقك
الوصول إليه من خلال استدعاء طريقة getRemoteMediaClient()
على المثيل CastSession
.
وستعرض كل طرق RemoteMediaClient
التي تُصدر الطلبات إلى مستقبل الويب عنصر PendingResult الذي يمكن استخدامه لتتبع هذا الطلب.
من المتوقَّع أن تتم مشاركة مثيل RemoteMediaClient
من خلال أجزاء متعددة من تطبيقك، كما يُتوقع أن تتم مشاركة بعض المكوّنات الداخلية في إطار العمل، مثل عناصر التحكّم المصغّرة الدائمة وخدمة الإشعارات.
وتحقيقًا لهذه الغاية، تتيح هذه النسخة الافتراضية تسجيل نُسخ متعددة من
RemoteMediaClient.Listener
.
ضبط البيانات الوصفية للوسائط
تمثّل الفئة
MediaMetadata
معلومات حول ملف الوسائط الذي تريد بثّه. ينشئ المثال التالي مثيل MediaMetadata جديدًا لفيلم ويحدد
العنوان والعنوان الفرعي وصورتين.
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
لتشغيل تطبيق مشغّل وسائط
يتم تشغيله على "جهاز استقبال الويب" وإيقافه مؤقتًا أو التحكّم به.
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.
يوفر إطار العمل عرضًا مخصصًا، وهو 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) لتكنولوجيا Google Cast تطبيقًا مصغّرًا لوحدة التحكّم الموسّعة التي يُطلق عليها اسم
ExpandedControllerActivity
.
هذه فئة مجردة يجب أن تتضمن فئة فرعية لإضافة زر البث.
أولاً، أنشئ ملف موارد قائمة جديدًا لوحدة التحكم الموسّعة لتقديم زر البث:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
أنشِئ صفًا جديدًا يمتد إلى ExpandedControllerActivity
.
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) تلقائيًا زر التشغيل/الإيقاف بدلاً من زر التشغيل/الإيقاف المؤقت في وحدة التحكم الموسّعة.
لضبط المظهر باستخدام المظاهر، واختيار الأزرار التي سيتم عرضها، وإضافة أزرار مخصَّصة، انظر تخصيص وحدة التحكم الموسعة.
التحكم في مستوى الصوت
يدير إطار العمل تلقائيًا مستوى صوت تطبيق المُرسِل. ويزامن إطار العمل تلقائيًا تطبيقات المُرسِل ومستقبل الويب كي تُبلغ واجهة مستخدم المُرسِل دائمًا عن مستوى الصوت الذي يحدِّده مستقبل الويب.
التحكّم في مستوى الصوت للزرّ الفعلي
على أجهزة Android، يمكن استخدام الأزرار الفعلية على جهاز المُرسِل لتغيير مستوى صوت جلسة البث على "جهاز استقبال الويب" تلقائيًا لأي جهاز يستخدم Jelly Bean أو إصدارات أحدث.
التحكم في مستوى الصوت للزر الفعلي قبل Jelly Bean
لاستخدام مفاتيح مستوى الصوت الخارجية للتحكُّم في مستوى صوت جهاز "استقبال الويب" على أجهزة 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. يمكن للتطبيق عرض مربّعات حوار تتضمّن أخطاء للمستخدم أو يمكنه قطع الاتصال بجهاز استقبال الويب.