1. نظرة عامة

سيعلّمك هذا الدرس التطبيقي حول الترميز كيفية تعديل تطبيق فيديو حالي على Android لبث المحتوى على جهاز يتيح استخدام Google Cast.
ما هي تكنولوجيا Google Cast؟
تتيح تكنولوجيا Google Cast للمستخدمين بث المحتوى من جهاز جوّال إلى التلفزيون. يمكن للمستخدمين بعد ذلك استخدام أجهزتهم الجوّالة كوحدة تحكّم عن بُعد لتشغيل الوسائط على التلفزيون.
تتيح لك حزمة تطوير البرامج (SDK) الخاصة بتكنولوجيا Google Cast توسيع نطاق تطبيقك للتحكّم في تلفزيون أو نظام صوت. تتيح لك حزمة تطوير البرامج (SDK) الخاصة بـ Cast إضافة عناصر واجهة المستخدم اللازمة استنادًا إلى قائمة تدقيق تصميم Google Cast.
يتم توفير قائمة التحقّق من تصميم Google Cast لتكون تجربة المستخدم في Cast بسيطة ويمكن توقّعها على جميع الأنظمة الأساسية المتوافقة.
ما الذي سنقوم بإنشائه؟
بعد إكمال هذا الدرس العملي، سيكون لديك تطبيق فيديو على Android يمكنه إرسال الفيديوهات إلى جهاز يتيح استخدام Google Cast.
أهداف الدورة التعليمية
- كيفية إضافة حزمة تطوير البرامج (SDK) الخاصة بـ Google Cast إلى تطبيق فيديو نموذجي.
- كيفية إضافة زر "البث" لاختيار جهاز Google Cast
- كيفية الربط بجهاز البث وتشغيل تطبيق استقبال الوسائط
- كيفية بث فيديو
- كيفية إضافة وحدة تحكّم مصغّرة في Cast إلى تطبيقك
- كيفية توفير إشعارات الوسائط وعناصر التحكّم في شاشة القفل
- كيفية إضافة وحدة تحكّم موسَّعة
- كيفية توفير نافذة مرافِقة تمهيدية
- كيفية تخصيص أدوات Cast
- كيفية الدمج مع Cast Connect
المتطلبات
- أحدث إصدار من حزمة تطوير البرامج (SDK) لنظام التشغيل Android
- الإصدار 3.2 أو الإصدارات الأحدث من استوديو Android
- جهاز جوّال واحد يعمل بنظام التشغيل Android 4.1 Jelly Bean (المستوى 16 من واجهة برمجة التطبيقات) أو إصدار أحدث
- كابل بيانات USB لتوصيل جهازك الجوّال بجهاز الكمبيوتر المخصّص للتطوير
- جهاز بث، مثل Chromecast أو Android TV، تم إعداده للوصول إلى الإنترنت.
- تلفزيون أو شاشة مزوَّدان بمنفذ إدخال HDMI
- يجب توفّر جهاز Chromecast with Google TV لاختبار عملية الدمج مع Cast Connect، ولكنّه اختياري لبقية Codelab. إذا لم يكن لديك حساب، يمكنك تخطّي خطوة إضافة دعم Cast Connect في نهاية هذا البرنامج التعليمي.
تجربة الاستخدام
- يجب أن تكون لديك معرفة سابقة بلغة Kotlin وتطوير تطبيقات Android.
- ستحتاج أيضًا إلى معرفة سابقة بمشاهدة التلفزيون :)
كيف ستستخدم هذا البرنامج التعليمي؟
كيف تقيّم تجربتك في إنشاء تطبيقات Android؟
كيف تقيّم تجربتك في مشاهدة التلفزيون؟
2. الحصول على الرمز النموذجي
يمكنك تنزيل كل الرموز النموذجية على جهاز الكمبيوتر...
وفكّ ضغط ملف ZIP الذي تم تنزيله.
3- تشغيل نموذج التطبيق

لنبدأ بالاطّلاع على الشكل الذي سيبدو عليه نموذج التطبيق المكتمل. التطبيق هو مشغّل فيديو أساسي. يمكن للمستخدم اختيار فيديو من قائمة ثم تشغيله على الجهاز أو بثه إلى جهاز بث متوافق مع Google Cast.
بعد تنزيل الرمز، توضّح التعليمات التالية كيفية فتح نموذج التطبيق المكتمل وتشغيله في استوديو Android:
اختَر استيراد مشروع (Import Project) من شاشة الترحيب أو خيارات القائمة ملف (File) > جديد (New) > استيراد مشروع (Import Project)....
اختَر الدليل 
app-done من مجلد نموذج الرمز البرمجي وانقر على "حسنًا".
انقر على ملف >
مزامنة المشروع مع ملفات Gradle.
فعِّل تصحيح أخطاء الجهاز عبر USB على جهاز Android. في الإصدار 4.2 من نظام التشغيل Android والإصدارات الأحدث، تكون شاشة "خيارات المطوّرين" مخفية تلقائيًا. لإظهارها، انتقِل إلى الإعدادات > لمحة عن الهاتف وانقر على رقم الإصدار سبع مرات. ارجع إلى الشاشة السابقة، وانتقِل إلى النظام > الإعدادات المتقدّمة وانقر على خيارات المطوّرين بالقرب من أسفل الشاشة، ثم انقر على تصحيح أخطاء الجهاز عبر USB لتفعيلها.
وصِّل جهاز Android وانقر على الزر
تشغيل في "استوديو Android". بعد بضع ثوانٍ، من المفترض أن يظهر تطبيق الفيديو باسم بث الفيديوهات.
انقر على زر البث في تطبيق الفيديو واختَر جهاز البث.
اختَر فيديو وانقر على زر التشغيل.
سيبدأ تشغيل الفيديو على جهاز البث Google Cast.
سيتم عرض وحدة التحكّم الموسّعة. يمكنك استخدام زر التشغيل/الإيقاف المؤقت للتحكّم في التشغيل.
انتقِل مجددًا إلى قائمة الفيديوهات.
يظهر الآن جهاز تحكّم مصغّر في أسفل الشاشة. 
انقر على زر الإيقاف المؤقت في وحدة التحكّم المصغّرة لإيقاف الفيديو مؤقتًا على جهاز الاستقبال. انقر على زر التشغيل في أداة التحكّم المصغّرة لمواصلة تشغيل الفيديو مجددًا.
انقر على زر الشاشة الرئيسية للجهاز الجوّال. اسحب الإشعارات للأسفل، ومن المفترض أن يظهر لك الآن إشعار بشأن جلسة البث.
اقفل هاتفك، وعند فتح قفله، من المفترض أن يظهر إشعار على شاشة القفل للتحكّم في تشغيل الوسائط أو إيقاف البث.
ارجع إلى تطبيق الفيديو وانقر على زر "البث" لإيقاف البث على جهاز Google Cast.
الأسئلة الشائعة
4. إعداد مشروع البدء

يجب أن نضيف إمكانية استخدام Google Cast إلى تطبيق البث الذي نزّلته. في ما يلي بعض مصطلحات Google Cast التي سنستخدمها في هذا الدرس البرمجي:
- يعمل تطبيق المرسل على جهاز جوّال أو كمبيوتر محمول.
- يتم تشغيل تطبيق جهاز استقبال على جهاز Google Cast.
أنت الآن جاهز للبناء على أساس المشروع المبدئي باستخدام "استوديو Android":
- اختَر الدليل

app-startمن عملية تنزيل الرمز النموذجي (اختَر استيراد مشروع من شاشة الترحيب أو خيار القائمة ملف > جديد > استيراد مشروع...). - انقر على الزر
مزامنة المشروع مع ملفات Gradle. - انقر على الزر
تشغيل لتشغيل التطبيق واستكشاف واجهة المستخدم.
تصميم التطبيقات
يجلب التطبيق قائمة بالفيديوهات من خادم ويب بعيد ويقدّم قائمة للمستخدم لتصفّحها. يمكن للمستخدمين اختيار فيديو للاطّلاع على التفاصيل أو تشغيله على الجهاز الجوّال.
يتألف التطبيق من نشاطَين رئيسيَّين: VideoBrowserActivity وLocalPlayerActivity. لدمج وظائف Google Cast، يجب أن ترث الأنشطة من AppCompatActivity أو من العنصر الرئيسي FragmentActivity. يحدث هذا القيد لأنّنا نحتاج إلى إضافة MediaRouteButton (المقدَّم في مكتبة MediaRouter المتوافقة) كـ MediaRouteActionProvider، ولن ينجح ذلك إلا إذا كان النشاط موروثًا من الفئات المذكورة أعلاه. تعتمد مكتبة MediaRouter المتوافقة على مكتبة AppCompat المتوافقة التي توفّر الفئات المطلوبة.
VideoBrowserActivity
يحتوي هذا النشاط على Fragment (VideoBrowserFragment)، ويتم الاحتفاظ بهذه القائمة في ArrayAdapter (VideoListAdapter). يتم استضافة قائمة الفيديوهات والبيانات الوصفية المرتبطة بها على خادم بعيد كملف JSON. يجلب AsyncTaskLoader (VideoItemLoader) ملف JSON هذا ويعالجه لإنشاء قائمة بكائنات MediaItem.
يمثّل عنصر MediaItem فيديو وبياناته الوصفية المرتبطة به، مثل عنوانه ووصفه وعنوان URL للبث وعنوان URL للصور الداعمة ومقاطع النصوص المرتبطة (للترجمة والشرح) إن وُجدت. يتم تمرير عنصر MediaItem بين الأنشطة، لذا يحتوي MediaItem على طرق مساعدة لتحويله إلى Bundle والعكس.
عندما ينشئ عامل التحميل قائمة MediaItems، فإنّها تمرّر هذه القائمة إلى VideoListAdapter التي تعرض بعد ذلك قائمة MediaItems في VideoBrowserFragment. تظهر للمستخدم قائمة بالصور المصغّرة للفيديوهات مع وصف قصير لكل فيديو. عند اختيار عنصر، يتم تحويل MediaItem المقابل إلى Bundle ويتم تمريره إلى LocalPlayerActivity.
LocalPlayerActivity
يعرض هذا النشاط البيانات الوصفية حول فيديو معيّن ويسمح للمستخدم بتشغيل الفيديو محليًا على الجهاز الجوّال.
يستضيف النشاط VideoView وبعض أدوات التحكّم في الوسائط ومساحة نصية لعرض وصف الفيديو المحدّد. يغطي المشغّل الجزء العلوي من الشاشة، ما يترك مساحة للوصف التفصيلي للفيديو في الأسفل. يمكن للمستخدم تشغيل الفيديوهات أو إيقافها مؤقتًا أو تقديمها وترجيعها أثناء تشغيلها محليًا.
الاعتمادية
بما أنّنا نستخدم AppCompatActivity، نحتاج إلى مكتبة AppCompat المتوافقة. لإدارة قائمة الفيديوهات والحصول على الصور بشكل غير متزامن للقائمة، نستخدم مكتبة Volley.
الأسئلة الشائعة
5. إضافة زر الإرسال

يعرض التطبيق الذي يتيح استخدام Google Cast زر البث في كل نشاط من أنشطته. يؤدي النقر على زر "البث" إلى عرض قائمة بأجهزة البث التي يمكن للمستخدم اختيارها. إذا كان المستخدم يشغّل المحتوى محليًا على جهاز الإرسال، يؤدي اختيار جهاز بث إلى بدء التشغيل أو استئنافه على جهاز البث هذا. في أي وقت أثناء جلسة البث، يمكن للمستخدم النقر على زر البث وإيقاف بث تطبيقك إلى جهاز البث. يجب أن يتمكّن المستخدم من الاتصال بجهاز البث أو قطع الاتصال به أثناء تنفيذ أي نشاط في تطبيقك، كما هو موضّح في قائمة التحقّق من تصميم Google Cast.
الاعتمادية
عدِّل ملف build.gradle الخاص بالتطبيق لتضمين المكتبات التي يعتمد عليها التطبيق:
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.google.android.gms:play-services-cast-framework:21.1.0'
implementation 'com.android.volley:volley:1.2.1'
implementation "androidx.core:core-ktx:1.8.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
زامِن المشروع للتأكّد من إمكانية إنشائه بدون أخطاء.
الإعداد
يحتوي إطار عمل Cast على عنصر فردي عام، وهو CastContext، الذي ينسّق جميع التفاعلات مع Cast.
يجب تنفيذ واجهة OptionsProvider لتوفير CastOptions اللازمة لتهيئة سينغلتون CastContext. الخيار الأكثر أهمية هو معرّف تطبيق جهاز الاستقبال، والذي يُستخدَم لفلترة نتائج البحث عن أجهزة البث ولتشغيل تطبيق جهاز الاستقبال عند بدء جلسة بث.
عند تطوير تطبيق يتيح استخدام Google Cast، عليك التسجيل كمطوّر Cast ثم الحصول على معرّف تطبيق لتطبيقك. في هذا الدرس التطبيقي حول الترميز، سنستخدم نموذج تطبيق.
أضِف ملف CastOptionsProvider.kt الجديد التالي إلى حزمة com.google.sample.cast.refplayer في المشروع:
package com.google.sample.cast.refplayer
import android.content.Context
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.SessionProvider
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}
عليك الآن تعريف OptionsProvider ضمن العلامة "application" في ملف AndroidManifest.xml الخاص بالتطبيق:
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />
يمكنك إعداد CastContext بشكل غير مباشر في طريقة VideoBrowserActivity onCreate:
import com.google.android.gms.cast.framework.CastContext
private var mCastContext: CastContext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastContext = CastContext.getSharedInstance(this)
}
أضِف منطق التهيئة نفسه إلى LocalPlayerActivity.
زر الإرسال
بعد إعداد CastContext، علينا الآن إضافة زر "البث" للسماح للمستخدم باختيار جهاز بث. يتم تنفيذ زر "البث" من خلال MediaRouteButton من مكتبة الدعم MediaRouter. كما هو الحال مع أي رمز إجراء يمكنك إضافته إلى نشاطك (باستخدام ActionBar أو Toolbar)، عليك أولاً إضافة عنصر القائمة المناسب إلى قائمتك.
عدِّل ملف res/menu/browse.xml وأضِف العنصر MediaRouteActionProvider في القائمة قبل عنصر الإعدادات:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
تجاوز طريقة onCreateOptionsMenu() في VideoBrowserActivity باستخدام CastButtonFactory لربط MediaRouteButton بإطار عمل Cast:
import com.google.android.gms.cast.framework.CastButtonFactory
private var mediaRouteMenuItem: MenuItem? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.browse, menu)
mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
R.id.media_route_menu_item)
return true
}
يمكنك تجاهل onCreateOptionsMenu في LocalPlayerActivity بطريقة مشابهة.
انقر على الزر
تشغيل لتشغيل التطبيق على جهازك الجوّال. من المفترض أن يظهر لك زرّ البث في شريط الإجراءات الخاص بالتطبيق، وعند النقر عليه، سيتم عرض قائمة بأجهزة البث على شبكتك المحلية. تتم إدارة عملية رصد الأجهزة تلقائيًا من خلال CastContext. اختَر جهاز البث وسيتم تحميل نموذج تطبيق الاستقبال على جهاز البث. يمكنك التنقّل بين نشاط التصفّح ونشاط المشغّل المحلي، وستبقى حالة زر "البث" متزامنة.
لم نضِف بعد أي ميزة لتشغيل الوسائط، لذا لا يمكنك تشغيل الفيديوهات على جهاز البث حتى الآن. انقر على زر "البث" لقطع الاتصال.
6. بث محتوى الفيديو

سنوسّع نطاق نموذج التطبيق ليشمل تشغيل الفيديوهات عن بُعد على جهاز البث. لإجراء ذلك، علينا الاستماع إلى الأحداث المختلفة التي ينشئها إطار عمل Cast.
بثّ الوسائط
بشكل عام، إذا أردت تشغيل محتوى على جهاز بث، عليك اتّباع الخطوات التالية:
- أنشئ عنصر
MediaInfoيمثّل عنصر وسائط. - وصِّل جهازك بجهاز البث وافتح تطبيق الاستقبال.
- حمِّل العنصر
MediaInfoفي جهاز الاستقبال وشغِّل المحتوى. - تتبُّع حالة الوسائط
- إرسال أوامر التشغيل إلى جهاز الاستقبال استنادًا إلى تفاعلات المستخدم
لقد أكملنا الخطوة 2 في القسم السابق. من السهل تنفيذ الخطوة 3 باستخدام إطار عمل Cast. تتمثّل الخطوة 1 في ربط عنصر بآخر، حيث إنّ MediaInfo هو عنصر يفهمه إطار عمل Cast، وMediaItem هو عملية تغليف تطبيقنا لعنصر وسائط، ويمكننا بسهولة ربط MediaItem بـ MediaInfo.
يميز نموذج التطبيق LocalPlayerActivity بين التشغيل المحلي والتشغيل عن بُعد باستخدام تعداد القيم التالي:
private var mLocation: PlaybackLocation? = null
enum class PlaybackLocation {
LOCAL, REMOTE
}
enum class PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
ليس من المهم في هذا الدرس التطبيقي حول الترميز أن تفهم بالضبط طريقة عمل جميع نماذج منطق اللاعب. من المهم معرفة أنّه يجب تعديل مشغّل الوسائط في تطبيقك ليكون على دراية بموقعَي التشغيل بطريقة مماثلة.
في الوقت الحالي، يكون مشغّل الفيديو المحلي دائمًا في حالة التشغيل المحلية لأنّه لا يعرف أي شيء عن حالات البث بعد. علينا تعديل واجهة المستخدم استنادًا إلى عمليات نقل الحالة التي تحدث في إطار عمل Cast. على سبيل المثال، إذا بدأنا البث، علينا إيقاف التشغيل المحلي وإيقاف بعض عناصر التحكّم. وبالمثل، إذا أوقفنا البث عندما نكون في هذا النشاط، علينا الانتقال إلى التشغيل المحلي. للتعامل مع ذلك، علينا الاستماع إلى الأحداث المختلفة التي ينشئها إطار عمل Cast.
إدارة جلسة البث
بالنسبة إلى إطار عمل Cast، تجمع جلسة Cast بين خطوات الربط بجهاز، وتشغيل تطبيق (أو الانضمام إليه)، والربط بتطبيق استقبال، وتهيئة قناة للتحكّم في الوسائط إذا كان ذلك مناسبًا. قناة التحكّم في الوسائط هي الطريقة التي يرسل بها إطار عمل Cast الرسائل ويستقبلها من مشغّل الوسائط على جهاز الاستقبال.
سيتم بدء جلسة البث تلقائيًا عندما يختار المستخدم جهازًا من زر "البث"، وسيتم إيقافها تلقائيًا عندما يقطع المستخدم الاتصال. تتولّى حزمة تطوير البرامج (SDK) الخاصة بـ Cast أيضًا معالجة إعادة الاتصال بجلسة جهاز استقبال تلقائيًا بسبب مشاكل في الشبكة.
لنضِف SessionManagerListener إلى LocalPlayerActivity:
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
...
private var mSessionManagerListener: SessionManagerListener<CastSession>? = null
private var mCastSession: CastSession? = null
...
private fun setupCastListener() {
mSessionManagerListener = object : SessionManagerListener<CastSession> {
override fun onSessionEnded(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
onApplicationConnected(session)
}
override fun onSessionResumeFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarted(session: CastSession, sessionId: String) {
onApplicationConnected(session)
}
override fun onSessionStartFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarting(session: CastSession) {}
override fun onSessionEnding(session: CastSession) {}
override fun onSessionResuming(session: CastSession, sessionId: String) {}
override fun onSessionSuspended(session: CastSession, reason: Int) {}
private fun onApplicationConnected(castSession: CastSession) {
mCastSession = castSession
if (null != mSelectedMedia) {
if (mPlaybackState == PlaybackState.PLAYING) {
mVideoView!!.pause()
loadRemoteMedia(mSeekbar!!.progress, true)
return
} else {
mPlaybackState = PlaybackState.IDLE
updatePlaybackLocation(PlaybackLocation.REMOTE)
}
}
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
private fun onApplicationDisconnected() {
updatePlaybackLocation(PlaybackLocation.LOCAL)
mPlaybackState = PlaybackState.IDLE
mLocation = PlaybackLocation.LOCAL
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
}
}
في LocalPlayerActivity النشاط، يهمّنا أن يتم إعلامنا عند الاتصال بجهاز البث أو قطع الاتصال به حتى نتمكّن من التبديل إلى المشغّل المحلي أو إيقافه. يُرجى العِلم أنّ الاتصال يمكن أن ينقطع ليس فقط بسبب تشغيل مثيل تطبيقك على جهازك الجوّال، ولكن أيضًا بسبب تشغيل مثيل آخر لتطبيقك (أو تطبيق آخر) على جهاز جوّال مختلف.
يمكن الوصول إلى الجلسة النشطة حاليًا باسم SessionManager.getCurrentSession(). يتم إنشاء الجلسات وإيقافها تلقائيًا استجابةً لتفاعلات المستخدم مع مربّعات حوار Cast.
علينا تسجيل متتبِّع الجلسات وإعداد بعض المتغيرات التي سنستخدمها في النشاط. غيِّر طريقة LocalPlayerActivity onCreate إلى:
import com.google.android.gms.cast.framework.CastContext
...
private var mCastContext: CastContext? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
...
mCastContext = CastContext.getSharedInstance(this)
mCastSession = mCastContext!!.sessionManager.currentCastSession
setupCastListener()
...
loadViews()
...
val bundle = intent.extras
if (bundle != null) {
....
if (shouldStartPlayback) {
....
} else {
if (mCastSession != null && mCastSession!!.isConnected()) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
mPlaybackState = PlaybackState.IDLE
updatePlayButton(mPlaybackState)
}
}
...
}
جارٍ تحميل الوسائط
في Cast SDK، يوفّر RemoteMediaClient مجموعة من واجهات برمجة التطبيقات الملائمة لإدارة تشغيل الوسائط عن بُعد على جهاز الاستقبال. بالنسبة إلى CastSession الذي يتيح تشغيل الوسائط، سيتم إنشاء مثيل RemoteMediaClient تلقائيًا بواسطة حزمة SDK. يمكن الوصول إليه من خلال استدعاء طريقة getRemoteMediaClient() على مثيل CastSession. أضِف الطرق التالية إلى LocalPlayerActivity لتحميل الفيديو المحدّد حاليًا على جهاز الاستقبال:
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaLoadOptions
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.common.images.WebImage
import com.google.android.gms.cast.MediaLoadRequestData
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
remoteMediaClient.load( MediaLoadRequestData.Builder()
.setMediaInfo(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
private fun buildMediaInfo(): MediaInfo? {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
mSelectedMedia?.studio?.let { movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, it) }
mSelectedMedia?.title?.let { movieMetadata.putString(MediaMetadata.KEY_TITLE, it) }
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(1))))
return mSelectedMedia!!.url?.let {
MediaInfo.Builder(it)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.setStreamDuration((mSelectedMedia!!.duration * 1000).toLong())
.build()
}
}
عدِّل الآن الطرق الحالية المختلفة لاستخدام منطق جلسة Cast من أجل إتاحة التشغيل عن بُعد:
private fun play(position: Int) {
startControllersTimer()
when (mLocation) {
PlaybackLocation.LOCAL -> {
mVideoView!!.seekTo(position)
mVideoView!!.start()
}
PlaybackLocation.REMOTE -> {
mPlaybackState = PlaybackState.BUFFERING
updatePlayButton(mPlaybackState)
//seek to a new position within the current media item's new position
//which is in milliseconds from the beginning of the stream
mCastSession!!.remoteMediaClient?.seek(position.toLong())
}
else -> {}
}
restartTrickplayTimer()
}
private fun togglePlayback() {
...
PlaybackState.IDLE -> when (mLocation) {
...
PlaybackLocation.REMOTE -> {
if (mCastSession != null && mCastSession!!.isConnected) {
loadRemoteMedia(mSeekbar!!.progress, true)
}
}
else -> {}
}
...
}
override fun onPause() {
...
mCastContext!!.sessionManager.removeSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
}
override fun onResume() {
Log.d(TAG, "onResume() was called")
mCastContext!!.sessionManager.addSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
if (mCastSession != null && mCastSession!!.isConnected) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
super.onResume()
}
بالنسبة إلى طريقة updatePlayButton، غيِّر قيمة المتغيّر isConnected:
private fun updatePlayButton(state: PlaybackState?) {
...
val isConnected = (mCastSession != null
&& (mCastSession!!.isConnected || mCastSession!!.isConnecting))
...
}
الآن، انقر على الزر
تشغيل لتشغيل التطبيق على جهازك الجوّال. اتصل بجهاز البث وابدأ بتشغيل فيديو. من المفترض أن يظهر الفيديو قيد التشغيل على جهاز الاستقبال.
7. وحدة تحكّم مصغّرة
تتطلّب قائمة التحقّق من تصميم Cast أن يوفّر كل تطبيق Cast وحدة تحكّم مصغّرة تظهر عندما ينتقل المستخدم إلى صفحة أخرى غير صفحة المحتوى الحالية. توفّر وحدة التحكّم المصغّرة إمكانية الوصول الفوري إلى جلسة Cast الحالية وتذكيرًا مرئيًا بها.

توفّر حزمة تطوير البرامج (SDK) الخاصة بـ Cast طريقة عرض مخصّصة، MiniControllerFragment، يمكن إضافتها إلى ملف تصميم التطبيق للأنشطة التي تريد عرض وحدة التحكّم المصغّرة فيها.
أضِف تعريف الجزء التالي إلى أسفل كل من res/layout/player_activity.xml وres/layout/video_browser.xml:
<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"/>
انقر على الزر
تشغيل لتشغيل التطبيق وبث فيديو. عندما يبدأ التشغيل على جهاز الاستقبال، من المفترض أن يظهر جهاز التحكّم المصغّر في أسفل كل نشاط. يمكنك التحكّم في التشغيل عن بُعد باستخدام وحدة التحكّم المصغّرة. في حال التنقّل بين نشاط التصفّح ونشاط المشغّل المحلي، يجب أن تظل حالة وحدة التحكّم المصغّرة متزامنة مع حالة تشغيل الوسائط على جهاز الاستقبال.
8. الإشعارات وشاشة القفل
تتطلّب قائمة التحقّق من تصميم Google Cast أن ينفّذ تطبيق المرسِل أدوات التحكّم في الوسائط من خلال إشعار وشاشة القفل.

توفّر حزمة تطوير البرامج (SDK) الخاصة بـ Cast MediaNotificationService لمساعدة تطبيق المرسِل في إنشاء أدوات التحكّم في الوسائط للإشعار وشاشة القفل. يتم دمج الخدمة تلقائيًا في بيان تطبيقك من خلال Gradle.
سيعمل MediaNotificationService في الخلفية عندما يبدأ المرسِل البث، وسيظهر إشعار يتضمّن صورة مصغّرة وبيانات وصفية حول المحتوى الذي يتم بثه حاليًا، بالإضافة إلى زر تشغيل/إيقاف مؤقت وزر إيقاف.
يمكن تفعيل عناصر التحكّم في الإشعارات وشاشة القفل باستخدام CastOptions عند بدء CastContext. تكون أدوات التحكّم في الوسائط للإشعارات وشاشة القفل مفعَّلة تلقائيًا. تكون ميزة شاشة القفل مفعَّلة طالما أنّ الإشعارات مفعَّلة.
عدِّل CastOptionsProvider وغيِّر عملية تنفيذ getCastOptions لتتطابق مع هذا الرمز:
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.NotificationOptions
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(VideoBrowserActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build()
}
انقر على الزر
تشغيل لتشغيل التطبيق على جهازك الجوّال. Cast فيديو والتنقّل بعيدًا عن نموذج التطبيق، يجب أن يظهر إشعار بالفيديو الذي يتم تشغيله حاليًا على جهاز الاستقبال اقفل جهازك الجوّال، ومن المفترض أن تعرض شاشة القفل الآن عناصر التحكّم في تشغيل الوسائط على جهاز البث.

9- نافذة منبثقة تمهيدية
تتطلّب قائمة التحقّق من تصميم Google Cast أن تُعرِّف تطبيقات الإرسال المستخدمين الحاليين على زر Cast لإعلامهم بأنّ تطبيق الإرسال يتيح الآن إمكانية البث، كما تساعد المستخدمين الجدد في Google Cast.

توفّر حزمة تطوير البرامج (SDK) الخاصة بـ Cast طريقة عرض مخصّصة، IntroductoryOverlay، يمكن استخدامها لإبراز زر البث عند عرضه للمستخدمين لأول مرة. أضِف الرمز التالي إلى VideoBrowserActivity:
import com.google.android.gms.cast.framework.IntroductoryOverlay
import android.os.Looper
private var mIntroductoryOverlay: IntroductoryOverlay? = null
private fun showIntroductoryOverlay() {
mIntroductoryOverlay?.remove()
if (mediaRouteMenuItem?.isVisible == true) {
Looper.myLooper().run {
mIntroductoryOverlay = com.google.android.gms.cast.framework.IntroductoryOverlay.Builder(
this@VideoBrowserActivity, mediaRouteMenuItem!!)
.setTitleText("Introducing Cast")
.setSingleTime()
.setOnOverlayDismissedListener(
object : IntroductoryOverlay.OnOverlayDismissedListener {
override fun onOverlayDismissed() {
mIntroductoryOverlay = null
}
})
.build()
mIntroductoryOverlay!!.show()
}
}
}
الآن، أضِف CastStateListener واستدعِ طريقة showIntroductoryOverlay عندما يتوفّر جهاز بث من خلال تعديل طريقة onCreate وتجاوز طريقتَي onResume وonPause لتتطابقا مع ما يلي:
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
private var mCastStateListener: CastStateListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastStateListener = object : CastStateListener {
override fun onCastStateChanged(newState: Int) {
if (newState != CastState.NO_DEVICES_AVAILABLE) {
showIntroductoryOverlay()
}
}
}
mCastContext = CastContext.getSharedInstance(this)
}
override fun onResume() {
super.onResume()
mCastContext?.addCastStateListener(mCastStateListener!!)
}
override fun onPause() {
super.onPause()
mCastContext?.removeCastStateListener(mCastStateListener!!)
}
محو بيانات التطبيق أو إزالته من جهازك بعد ذلك، انقر على الزر
تشغيل لتشغيل التطبيق على جهازك الجوّال، وسيظهر لك التراكب التمهيدي (إذا لم يظهر التراكب، عليك محو بيانات التطبيق).
10. وحدة تحكّم موسّعة
تتطلّب قائمة التحقّق من تصميم Google Cast أن يوفّر تطبيق المرسِل وحدة تحكّم موسّعة للوسائط التي يتم بثّها. وحدة التحكّم الموسّعة هي نسخة بملء الشاشة من وحدة التحكّم المصغّرة.

توفّر حزمة تطوير البرامج (SDK) الخاصة بـ Cast أداة للوحة التحكّم الموسّعة تُسمّى ExpandedControllerActivity. هذه فئة مجرّدة يجب إنشاء فئة فرعية منها لإضافة زر البث.
أولاً، أنشئ ملف موارد قائمة جديدًا باسم expanded_controller.xml لوحدة التحكّم الموسّعة لتوفير زر البث:
<?xml version="1.0" encoding="utf-8"?>
<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>
أنشئ حزمة جديدة expandedcontrols في حزمة com.google.sample.cast.refplayer. بعد ذلك، أنشئ ملفًا جديدًا باسم ExpandedControlsActivity.kt في حزمة com.google.sample.cast.refplayer.expandedcontrols.
package com.google.sample.cast.refplayer.expandedcontrols
import android.view.Menu
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.google.sample.cast.refplayer.R
import com.google.android.gms.cast.framework.CastButtonFactory
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
}
}
الآن، أضِف تعريف ExpandedControlsActivity في AndroidManifest.xml ضمن علامة application فوق OPTIONS_PROVIDER_CLASS_NAME:
<application>
...
<activity
android:name="com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.google.sample.cast.refplayer.VideoBrowserActivity"/>
</activity>
...
</application>
عدِّل CastOptionsProvider وغيِّر NotificationOptions وCastMediaOptions لضبط النشاط المستهدَف على ExpandedControlsActivity:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
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()
}
عدِّل طريقة LocalPlayerActivity loadRemoteMedia لعرض ExpandedControlsActivity عند تحميل الوسائط البعيدة:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
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(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
انقر على الزر
تشغيل لتشغيل التطبيق على جهازك الجوّال وإرسال فيديو. من المفترض أن تظهر لك وحدة التحكّم الموسَّعة. ارجع إلى قائمة الفيديوهات، وعند النقر على وحدة التحكّم المصغّرة، سيتم تحميل وحدة التحكّم الموسّعة مرة أخرى. انتقِل إلى مكان آخر غير التطبيق للاطّلاع على الإشعار. انقر على صورة الإشعار لتحميل وحدة التحكّم الموسّعة.
11. إضافة ميزة Cast Connect
تتيح مكتبة Cast Connect لتطبيقات الإرسال الحالية التواصل مع تطبيقات Android TV من خلال بروتوكول Cast. تستند ميزة Cast Connect إلى البنية الأساسية لـ Cast، حيث يعمل تطبيق Android TV كجهاز استقبال.
الاعتمادية
ملاحظة: لتنفيذ Cast Connect، يجب أن يكون play-services-cast-framework 19.0.0 أو إصدارًا أحدث.
LaunchOptions
من أجل تشغيل تطبيق Android TV، المعروف أيضًا باسم Android Receiver، يجب ضبط العلامة setAndroidReceiverCompatible على "صحيح" في العنصر LaunchOptions. يحدّد عنصر LaunchOptions هذا طريقة تشغيل جهاز الاستقبال ويتم تمريره إلى CastOptions الذي تعرضه الفئة CastOptionsProvider. سيؤدي ضبط العلامة المذكورة أعلاه على false إلى تشغيل أداة استقبال الويب لمعرّف التطبيق المحدّد في Play Console.
في ملف CastOptionsProvider.kt، أضِف ما يلي إلى طريقة getCastOptions:
import com.google.android.gms.cast.LaunchOptions
...
val launchOptions = LaunchOptions.Builder()
.setAndroidReceiverCompatible(true)
.build()
return new CastOptions.Builder()
.setLaunchOptions(launchOptions)
...
.build()
ضبط بيانات اعتماد الإطلاق
من جهة المُرسِل، يمكنك تحديد CredentialsData لتمثيل المستخدم الذي ينضم إلى الجلسة. credentials هي سلسلة يمكن للمستخدم تحديدها، طالما أنّ تطبيق Android TV يمكنه فهمها. لا يتم تمرير CredentialsData إلا إلى تطبيق Android TV أثناء التشغيل أو الانضمام. إذا ضبطت كلمة المرور مرة أخرى أثناء اتصالك، لن يتم نقلها إلى تطبيق Android TV.
لضبط بيانات اعتماد التشغيل، يجب تحديد CredentialsData وتمريرها إلى العنصر LaunchOptions. أضِف الرمز التالي إلى طريقة getCastOptions في ملف CastOptionsProvider.kt:
import com.google.android.gms.cast.CredentialsData
...
val credentialsData = CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build()
val launchOptions = LaunchOptions.Builder()
...
.setCredentialsData(credentialsData)
.build()
ضبط بيانات الاعتماد على LoadRequest
في حال كان تطبيق Web Receiver وتطبيق Android TV يتعاملان مع credentials بشكل مختلف، قد تحتاج إلى تحديد credentials منفصل لكل منهما. لإجراء ذلك، أضِف الرمز التالي في ملف LocalPlayerActivity.kt ضمن الدالة loadRemoteMedia:
remoteMediaClient.load(MediaLoadRequestData.Builder()
...
.setCredentials("user-credentials")
.setAtvCredentials("atv-user-credentials")
.build())
استنادًا إلى تطبيق الاستقبال الذي يبث إليه جهاز الإرسال، ستتعامل حزمة تطوير البرامج (SDK) الآن تلقائيًا مع بيانات الاعتماد التي سيتم استخدامها في الجلسة الحالية.
اختبار ميزة "البث المتزامن"
خطوات تثبيت حزمة APK لنظام Android TV على جهاز Chromecast مع Google TV
- ابحث عن عنوان IP لجهاز Android TV. عادةً ما يكون متاحًا ضمن الإعدادات > الشبكة والإنترنت > (اسم الشبكة التي يتصل بها جهازك). على اليسار، ستظهر التفاصيل وعنوان IP لجهازك على الشبكة.
- استخدِم عنوان IP الخاص بجهازك للاتصال به عبر ADB باستخدام الوحدة الطرفية:
$ adb connect <device_ip_address>:5555
- من نافذة الوحدة الطرفية، انتقِل إلى مجلد المستوى الأعلى لعينات الدرس التطبيقي حول الترميز التي نزّلتها في بداية هذا الدرس. على سبيل المثال:
$ cd Desktop/android_codelab_src
- ثبِّت ملف .apk في هذا المجلد على Android TV من خلال تنفيذ الأمر التالي:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- من المفترض أن يظهر لك الآن تطبيق باسم بثّ الفيديوهات في قائمة تطبيقاتك على جهاز Android TV.
- ارجع إلى مشروعك في "استوديو Android" وانقر على الزر "تشغيل" لتثبيت تطبيق المرسِل وتشغيله على جهازك الجوّال. في أعلى يسار الشاشة، انقر على رمز البث واختَر جهاز Android TV من الخيارات المتاحة. من المفترض أن يظهر لك الآن تطبيق Android TV الذي تم تشغيله على جهاز Android TV، كما أنّ تشغيل فيديو من المفترض أن يتيح لك التحكّم في تشغيل الفيديو باستخدام جهاز التحكّم عن بُعد في Android TV.
12. تخصيص تطبيقات Cast المصغّرة
يمكنك تخصيص أدوات Cast من خلال ضبط الألوان وتصميم الأزرار والنصوص ومظهر الصور المصغّرة واختيار أنواع الأزرار التي تريد عرضها.
تعديل "res/values/styles_castvideo.xml"
<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="mediaRouteTheme">@style/CustomMediaRouterTheme</item>
<item name="castIntroOverlayStyle">@style/CustomCastIntroOverlay</item>
<item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
<item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
<item name="castExpandedControllerToolbarStyle">
@style/ThemeOverlay.AppCompat.ActionBar
</item>
...
</style>
أضِف تعريفات للتصاميم المخصّصة التالية:
<!-- Customize Cast Button -->
<style name="CustomMediaRouterTheme" parent="Theme.MediaRouter">
<item name="mediaRouteButtonStyle">@style/CustomMediaRouteButtonStyle</item>
</style>
<style name="CustomMediaRouteButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
<item name="mediaRouteButtonTint">#EEFF41</item>
</style>
<!-- Customize Introductory Overlay -->
<style name="CustomCastIntroOverlay" parent="CastIntroOverlay">
<item name="castButtonTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Button</item>
<item name="castTitleTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Title</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Button" parent="android:style/TextAppearance">
<item name="android:textColor">#FFFFFF</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Title" parent="android:style/TextAppearance.Large">
<item name="android:textColor">#FFFFFF</item>
</style>
<!-- Customize Mini Controller -->
<style name="CustomCastMiniController" parent="CastMiniController">
<item name="castShowImageThumbnail">true</item>
<item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
<item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
<item name="castBackground">@color/accent</item>
<item name="castProgressBarColor">@color/orange</item>
</style>
<!-- Customize Expanded Controller -->
<style name="CustomCastExpandedController" parent="CastExpandedController">
<item name="castButtonColor">#FFFFFF</item>
<item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
<item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
<item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
</style>
13. تهانينا
أصبحت الآن تعرف كيفية إتاحة البث في تطبيق فيديو باستخدام أدوات Cast SDK على أجهزة Android.
لمزيد من التفاصيل، يُرجى الاطّلاع على دليل المطوّر Android Sender.