1- نظرة عامة
سيساعدك هذا الدرس التطبيقي حول الترميز في التعرّف على كيفية تعديل تطبيق فيديو Android حالي لبث المحتوى على جهاز مزوّد بتكنولوجيا Google Cast.
ما هي خدمة Google Cast؟
يسمح تطبيق Google Cast للمستخدمين ببث المحتوى من جهاز جوّال إلى تلفزيون. ويمكن للمستخدمين بعد ذلك استخدام أجهزتهم الجوّالة كوحدة تحكّم عن بُعد لتشغيل الوسائط على التلفزيون.
تتيح لك حزمة تطوير البرامج (SDK) في Google Cast توسيع تطبيقك للتحكّم في نظام التلفزيون أو الصوت. تسمح لك حزمة تطوير البرامج (SDK) للبث بإضافة مكوّنات واجهة المستخدم اللازمة استنادًا إلى قائمة التحقّق من تصميم Google Cast.
تم توفير قائمة التحقّق من تصميم Google Cast لتسهيل تجربة مستخدم البث وتوقّعه على جميع الأنظمة الأساسية المتوافقة.
ما الذي سنبنيه؟
عند إتمام هذا الدرس التطبيقي حول الترميز، سيصبح لديك تطبيق فيديو على Android سيتمكّن من بث الفيديوهات إلى جهاز يعمل بتكنولوجيا Google Cast.
ما ستتعرَّف عليه
- كيفية إضافة حزمة Google Cast SDK إلى نموذج تطبيق فيديو
- كيفية إضافة زر بث لاختيار جهاز Google Cast
- كيفية الاتصال بجهاز بث وتشغيل جهاز استقبال الوسائط
- كيفية بث فيديو
- كيفية إضافة وحدة تحكّم مصغّرة للبث إلى تطبيقك
- طريقة دعم إشعارات الوسائط وعناصر التحكّم في شاشة القفل
- كيفية إضافة وحدة تحكُّم موسَّعة.
- كيفية توفير تراكب تمهيدي.
- كيفية تخصيص تطبيقات Cast المصغّرة
- كيفية الدمج مع Cast Connect
المتطلبات
- أحدث إصدار من حزمة تطوير البرامج (SDK) لنظام التشغيل Android
- الإصدار 3.2 من Android Studio أو الإصدارات الأحدث
- جهاز جوّال واحد يعمل بالإصدار Android 4.1 أو الإصدارات الأحدث من نظام Jelly وجه (مستوى واجهة برمجة التطبيقات 16).
- كابل بيانات USB لتوصيل الجهاز الجوّال بجهاز الكمبيوتر المخصص للتطوير.
- جهاز Google Cast مثل Chromecast أو Android TV تم ضبطه مع الاتصال بالإنترنت.
- تلفزيون أو شاشة مزوّدة بمدخل HDMI.
- يجب توفّر جهاز Chromecast مع Google TV لاختبار دمج Cast Connect، ولكنه أمر اختياري لبقية دروس Codelab. إذا لم يكن لديك برنامج، يُرجى تخطّي خطوة إضافة فريق دعم Cast Connect حتى نهاية هذا البرنامج التعليمي.
التجربة
- يجب أن تكون لديك معرفة سابقة بتطوير لغة Kotlin وAndroid.
- ستحتاج أيضًا إلى معرفة سابقة بمشاهدة التلفزيون :)
كيف ستستخدم هذا البرنامج التعليمي؟
كيف تقيّم تجربة إنشاء تطبيقات Android؟
كيف تقيّم تجربة مشاهدة التلفزيون؟
2- الحصول على الرمز النموذجي
يمكنك تنزيل كل الرموز البرمجية على جهاز الكمبيوتر...
وفك ضغط ملف ZIP الذي تم تنزيله.
3. تشغيل نموذج التطبيق
أولاً، لنتعرّف على شكل نموذج التطبيق المكتمل. التطبيق هو مشغّل فيديو أساسي. يمكن للمستخدم اختيار فيديو من قائمة، ثم تشغيل الفيديو محليًا على الجهاز أو بثّه على جهاز Google Cast.
بعد تنزيل الرمز، توضّح التعليمات التالية كيفية فتح نموذج التطبيق المكتمل وتشغيله في Android Studio:
اختَر استيراد المشروع على شاشة الترحيب أو قائمة الخيارات ملف > جديد > استيراد المشروع....
اختَر الدليل app-done
من مجلد الرمز النموذجي وانقر على "حسنًا".
انقر على ملف > مزامنة المشروع مع ملفات Gradle.
تفعيل تصحيح أخطاء USB على جهاز Android - على الإصدار 4.2 من نظام التشغيل Android والإصدارات الأحدث، يتم إخفاء شاشة خيارات المطوّرين تلقائيًا. لإظهاره، انتقِل إلى الإعدادات > لمحة عن الهاتف وانقر على رقم الإصدار سبع مرات. ارجع إلى الشاشة السابقة، وانتقِل إلى النظام > الإعدادات المتقدّمة وانقر على خيارات المطوّرين بالقرب من أسفل الشاشة، ثم انقر على تصحيح أخطاء الجهاز عبر USB لتفعيله.
وصِّل جهاز Android وانقر على الزر تشغيل في "استوديو Android". من المفترض أن يظهر تطبيق الفيديو باسم بث الفيديوهات بعد بضع ثوانٍ.
انقر على زر البث في تطبيق الفيديو واختَر جهاز Google Cast.
اختَر فيديو وانقر على زر التشغيل.
سيبدأ تشغيل الفيديو على جهاز Google Cast.
سيتم عرض وحدة التحكم الموسّعة. ويمكنك استخدام زر التشغيل/الإيقاف المؤقت للتحكّم في التشغيل.
انتقِل مجددًا إلى قائمة الفيديوهات.
تظهر وحدة تحكّم مصغّرة الآن في أسفل الشاشة.
انقر على زر الإيقاف المؤقت في وحدة التحكّم المصغّرة لإيقاف الفيديو مؤقتًا على جهاز الاستقبال. انقر على زر التشغيل في وحدة التحكّم المصغّرة لمواصلة تشغيل الفيديو مرة أخرى.
انقر على زر الصفحة الرئيسية لجهاز جوّال. اسحب الإشعارات للأسفل وسيظهر لك الآن إشعار لجلسة البث.
عليك قفل هاتفك وعند فتح قفله، سيظهر لك إشعار على شاشة القفل للتحكّم في تشغيل الوسائط أو إيقاف البث.
ارجع إلى تطبيق الفيديو وانقر على زر البث لإيقاف البث على جهاز Google Cast.
الأسئلة الشائعة
4- إعداد مشروع البدء
يجب إضافة Google Cast إلى التطبيق الأول الذي نزّلته. في ما يلي بعض مصطلحات Google Cast التي سنستخدمها في هذا الدرس التطبيقي حول الترميز:
- تشغيل تطبيق المُرسِل على جهاز جوّال أو كمبيوتر محمول،
- يتم تشغيل تطبيق المستلِم على جهاز Google Cast.
بعد أن أصبحت جاهزًا للبدء في العمل على مشروع المبتدئين باستخدام "استوديو Android":
- اختَر الدليل
app-start
من تنزيل نموذج الرمز (اختَر استيراد مشروع على شاشة الترحيب أو خيار القائمة ملف > جديد > استيراد مشروع...). - انقر على الزر
مزامنة المشروع مع ملفات Gradle.
- انقر على الزر
Run (تشغيل) لتشغيل التطبيق واستكشاف واجهة المستخدم.
تصميم التطبيقات
يسترجع التطبيق قائمة بالفيديوهات من خادم ويب بعيد ويقدّم قائمة تتيح للمستخدم تصفّحها. ويمكن للمستخدمين اختيار فيديو للاطّلاع على تفاصيله أو تشغيل الفيديو محليًا على الجهاز الجوّال.
يتكون التطبيق من نشاطَين رئيسيَين: VideoBrowserActivity
وLocalPlayerActivity
. لدمج وظيفة Google Cast، يجب أن تحصل الأنشطة على إمّا AppCompatActivity
أو على العنصر الرئيسي FragmentActivity
. سبق أن تم فرض هذه القيود لأنّنا سنحتاج إلى إضافة MediaRouteButton
(المتوفّر في مكتبة دعم Mediarr) MediaRouteActionProvider
، ولن يعمل هذا الإجراء إلا إذا كان النشاط تكتسبه من الصفوف المذكورة أعلاه. تعتمد مكتبة دعم Mediarouter على مكتبة دعم AppCompat التي توفر الفئات المطلوبة.
نشاط متصفح الفيديو
يحتوي هذا النشاط على Fragment
(VideoBrowserFragment
). ويتم دعم هذه القائمة من خلال ArrayAdapter
(VideoListAdapter
). تتم استضافة قائمة الفيديوهات والبيانات الوصفية المرتبطة بها على خادم بعيد كملف JSON. تجلب أداة AsyncTaskLoader
(VideoItemLoader
) ملف JSON هذا وتعالجه لإنشاء قائمة بعناصر MediaItem
.
يحدد العنصر MediaItem
فيديو وبياناته الوصفية المرتبطة به، مثل عنوانه ووصفه وعنوان URL الخاص به، ومصدر عنوان URL للصور الداعمة والمقاطع النصية المرتبطة به (في حال توفّر مقاطع الترجمة والشرح). يتم تمرير الكائن MediaItem
بين الأنشطة، لذلك يستخدم MediaItem
طرق خدمات لتحويلها إلى Bundle
والعكس صحيح.
عندما يُنشئ عامل التحميل قائمة MediaItems
، يمرّر تلك القائمة إلى VideoListAdapter
، ثم يعرض قائمة MediaItems
في VideoBrowserFragment
. يظهر للمستخدم قائمة بالصور المصغّرة مع وصف قصير لكل فيديو. عند اختيار عنصر، يتم تحويل MediaItem
المقابل إلى Bundle
وتمريره إلى 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"
}
يمكنك مزامنة المشروع لتأكيد إنشاء المشروع بدون أخطاء.
الإعداد
يحتوي إطار عمل البث على كائن "سينغلتون" عام، وهو CastContext
، وهو منسَّق لجميع تفاعلات البث.
يجب تنفيذ واجهة 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
CreateCreate:
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
إلى إطار عمل البث:
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
بطريقة مشابهة.
انقر على الزر Run (تشغيل) لتشغيل التطبيق على جهازك الجوّال. من المفترض أن يظهر لك زر البث في شريط إجراءات التطبيق، وعند النقر عليه، سيتم عرض أجهزة البث على شبكتك المحلية. تتم إدارة اكتشاف الأجهزة تلقائيًا من خلال
CastContext
. اختَر جهاز البث وسيتم تحميل نموذج تطبيق جهاز الاستقبال على جهاز البث. يمكنك التنقّل بين نشاط التصفُّح ونشاط المشغّل المحلي وحالة زر البث في حالة المزامنة.
لم يتم توفير أي إمكانية لتشغيل الوسائط، لذا لا يمكنك تشغيل الفيديوهات على جهاز البث بعد. انقر على زر البث لإلغاء الربط.
6- يجري بث محتوى الفيديو
سنوسّع نموذج التطبيق لتشغيل الفيديوهات أيضًا عن بُعد على جهاز بث. ولإجراء ذلك، علينا الاستماع إلى الأحداث المختلفة التي أنشأها إطار عمل البث.
بث الوسائط
على مستوى عالٍ، إذا كنت تريد تشغيل وسائط على جهاز بث، عليك تنفيذ ما يلي:
- أنشئ كائنًا في السمة
MediaInfo
يصنِّف عنصر وسائط. - اتصِل بجهاز البث وفعِّل تطبيق الاستقبال.
- حمِّل عنصر
MediaInfo
إلى جهاز الاستقبال وتشغيل المحتوى. - تتبّع حالة الوسائط.
- يمكنك إرسال أوامر التشغيل إلى جهاز الاستقبال استنادًا إلى تفاعلات المستخدمين.
سبق لنا تنفيذ الخطوة 2 في القسم السابق. من السهل تنفيذ الخطوة 3 من إطار عمل "البث". تتمثل الخطوة الأولى في ربط عنصر بعنصر آخر، وهو MediaInfo
الذي يفهمه إطار عمل "البث" وMediaItem
هو تغليف تطبيقنا لعنصر وسائط. ويمكننا بسهولة ربط MediaItem
بعنصر MediaInfo
.
يحدّد نموذج التطبيق LocalPlayerActivity
مسبقًا بين التشغيل المحلي في مقابل تشغيله عن بُعد باستخدام هذا العدد:
private var mLocation: PlaybackLocation? = null
enum class PlaybackLocation {
LOCAL, REMOTE
}
enum class PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
ليس من المهم في هذا الدرس التطبيقي فهم الترميز لمعرفة آلية عمل كل منطق المشغّل. من المهم معرفة أنّه يجب تعديل مشغّل الوسائط في تطبيقك بحيث يكون على دراية بمكانَي التشغيل بطريقة مشابهة.
في الوقت الحالي، يكون المشغِّل المحلي في حالة التشغيل المحلي دائمًا لأنه لا يعرف أي شيء عن حالات البث. يجب تعديل واجهة المستخدم استنادًا إلى انتقالات الحالة التي تحدث في إطار عمل 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()
. يتم إنشاء الجلسات وإزالتها تلقائيًا استجابةً لتفاعلات المستخدم مع مربّعات حوار البث.
علينا تسجيل مستمع الجلسة وإعداد بعض المتغيّرات التي سنستخدمها في النشاط. تغيير طريقة onCreate
LocalPlayerActivity
إلى:
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)
}
}
...
}
جارٍ تحميل الوسائط
في حزمة تطوير البرامج (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()
}
}
والآن، حدِّث العديد من الطرق الحالية لاستخدام منطق جلسة البث لإتاحة التشغيل عن بُعد:
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))
...
}
انقر على الزر Run (تشغيل) لتشغيل التطبيق على جهازك الجوّال. اتصِل بجهاز البث وابدأ تشغيل فيديو. من المفترض أن يظهر الفيديو على جهاز الاستقبال.
7- وحدة تحكّم صغيرة
وتتطلب قائمة التحقّق من تصميم البث أن توفّر كل تطبيقات البث وحدة تحكّم مصغّرة تظهر عندما ينتقل المستخدم بعيدًا عن صفحة المحتوى الحالية. توفّر وحدة التحكّم المصغّرة إمكانية الوصول الفوري وتذكير مرئي لجلسة البث الحالية.
توفّر حزمة تطوير البرامج (SDK) للبث رمز عرض مخصّص، 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"/>
انقر على الزر Run (تشغيل) لتشغيل التطبيق وبث فيديو. عندما يبدأ التشغيل على جهاز الاستقبال، من المفترض أن تظهر وحدة التحكّم المصغّرة في أسفل كل نشاط. يمكنك التحكّم في التشغيل عن بُعد باستخدام وحدة التحكّم المصغّرة. إذا كنت تنتقل بين نشاط التصفُّح ونشاط المشغّل المحلي، من المفترض أن تظل حالة وحدة التحكّم المصغّرة متزامنة مع حالة تشغيل وسائط المستلِم.
8- الإشعارات وشاشة القفل
وتتطلب قائمة التحقّق من تصميم Google Cast تطبيق مُرسِل لتنفيذ عناصر التحكّم في الوسائط من إشعار وشاشة القفل.
توفّر حزمة تطوير البرامج (SDK) الخاصة بالبث 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()
}
انقر على الزر Run (تشغيل) لتشغيل التطبيق على جهازك الجوّال. يمكنك بث فيديو والابتعاد عن نموذج التطبيق. ومن المفترض أن يظهر إشعار بالفيديو الذي يتم تشغيله حاليًا على جهاز الاستقبال. من خلال قفل جهازك الجوّال، من المفترض أن تعرض شاشة القفل الآن عناصر التحكّم في تشغيل الوسائط على جهاز البث.
9. تراكب تمهيدي
وتتطلب قائمة التحقّق الخاصة بتصميم Google Cast من أحد المُرسِلين تقديم زر البث إلى المستخدمين الحاليين لإعلامهم بأنّ تطبيق المُرسِل متوافق الآن مع البث ويساعد أيضًا المستخدمين الجدد في استخدام Google Cast.
توفِّر حزمة تطوير البرامج (SDK) للبث رمز عرض مخصّص 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) الخاصة بتكنولوجيا Google 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 Connect إلى البنية الأساسية للبث، حيث يعمل تطبيق Android TV كمستلِم.
المهام التابعة
ملاحظة: لتنفيذ Cast Connect، يجب أن يكون play-services-cast-framework
19.0.0
أو أعلى.
خيارات الإطلاق
لتشغيل تطبيق Android TV، المُشار إليه أيضًا باسم "مستلِم Android"، علينا ضبط علامة 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
هي سلسلة يمكن أن يحدّدها المستخدم، شرط أن يتمكن تطبيق ATV من فهمها. يتم تمرير 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) تلقائيًا بيانات الاعتماد المطلوب استخدامها للجلسة الحالية.
اختبار Cast Cast
خطوات تثبيت ملف Android TV APK على جهاز "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 المصغّرة
يمكنك تخصيص أدوات البث من خلال ضبط الألوان وتصميم الأزرار والنص والصورة المصغّرة واختيار أنواع الأزرار لعرضها.
تحديث "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- تهانينا
أصبحت تعرف الآن كيفية تفعيل تطبيق البث من خلال التطبيقات المصغّرة لحزمة تطوير البرامج "بث" على Android.
لمعرفة المزيد من التفاصيل، يُرجى الاطّلاع على دليل مطوِّر برامج مُرسِل Android.