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 Connect
المتطلبات
- أحدث إصدار من حزمة تطوير البرامج (SDK) لنظام التشغيل Android
- الإصدار 3.2 من استوديو Android أو الإصدارات الأحدث
- جهاز جوّال واحد يعمل بنظام التشغيل Android 4.1 أو إصدار أحدث من نظام التشغيل Jelly Bean (المستوى 16 من واجهة برمجة التطبيقات)
- كابل بيانات USB لتوصيل جهازك الجوّال بالكمبيوتر المخصّص للتطوير
- جهاز Google Cast، مثل Chromecast أو Android TV تم ضبطه للوصول إلى الإنترنت
- تلفزيون أو شاشة مزوّدة بمنفذ إدخال HDMI.
- يجب توفُّر جهاز Chromecast مع Google TV لاختبار عملية دمج Cast Connect، ولكنّه اختياري لبقية خطوات Codelab. إذا لم يكن لديك هذا البرنامج، يُرجى تخطّي خطوة إضافة دعم Cast Connect في نهاية هذا الدليل التوجيهي.
تجربة الاستخدام
- يجب أن تكون لديك معرفة سابقة بتطوير تطبيقات Android وKotlin.
- ستحتاج أيضًا إلى معرفة سابقة بمشاهدة التلفزيون :)
كيف ستستخدم هذا البرنامج التعليمي؟
ما مدى رضاك عن تجربة إنشاء تطبيقات Android؟
ما مدى رضاك عن تجربة مشاهدة التلفزيون؟
2. الحصول على نموذج الرمز
يمكنك تنزيل جميع الرموز النموذجية على جهاز الكمبيوتر...
وفك ضغط ملف ZIP الذي تم تنزيله.
3- تشغيل نموذج التطبيق
أولاً، لنلقِ نظرة على شكل نموذج التطبيق المكتمل. التطبيق هو مشغل فيديو أساسي. يمكن للمستخدم اختيار فيديو من قائمة، ثم تشغيله على الجهاز أو بثّه على جهاز Google Cast.
بعد تنزيل الرمز، توضّح التعليمات التالية كيفية فتح نموذج التطبيق المكتمل وتشغيله في استوديو Android:
اختَر استيراد مشروع في شاشة الترحيب أو من خيارات القائمة ملف > جديد > استيراد مشروع....
اختَر الدليل app-done
من مجلد "نموذج الرموز" وانقر على "حسنًا".
انقر على ملف > مزامنة المشروع مع ملفات Gradle.
فعِّل وضع تصحيح أخطاء USB على جهاز Android. في الإصدار 4.2 من نظام التشغيل Android والإصدارات الأحدث، تكون شاشة "خيارات المطوّر" مخفية تلقائيًا. لإظهاره، انتقِل إلى الإعدادات > لمحة عن الهاتف وانقر على رقم الإصدار سبع مرات. ارجع إلى الشاشة السابقة، ثم انتقِل إلى النظام > الإعدادات المتقدّمة وانقر على خيارات المطوّرين بالقرب من أسفل الشاشة، ثم انقر على تصحيح أخطاء الجهاز عبر USB لتفعيله.
وصِّل جهاز Android وانقر على الزر تشغيل في Android Studio. من المفترض أن يظهر تطبيق الفيديو الذي يحمل اسم بث الفيديوهات بعد بضع ثوانٍ.
انقر على زرّ البثّ في تطبيق الفيديو واختَر جهاز Google Cast.
اختَر فيديو وانقر على زر التشغيل.
سيبدأ تشغيل الفيديو على جهاز 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- إضافة زرّ "البث"
يعرض التطبيق المتوافق مع 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
. الخيار الأكثر أهمية هو رقم تعريف تطبيق المُستلِم، والذي يُستخدَم لفلترة نتائج اكتشاف أجهزة البث وتشغيل تطبيق المُستلِم عند بدء جلسة بث.
عند تطوير تطبيق متوافق مع 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
بشكلٍ كسول في طريقة onCreate في VideoBrowserActivity
:
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. لإجراء ذلك، علينا الاستماع إلى الأحداث المختلفة التي ينشئها إطار عمل 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.
إدارة جلسة البث
بالنسبة إلى إطار عمل البث، تجمع جلسة البث بين خطوات الاتصال بجهاز وتشغيله (أو الانضمام إليه) والاتصال بتطبيق مستقبِل وتهيئة قناة التحكم في الوسائط إذا كان ذلك مناسبًا. قناة التحكم في الوسائط هي الوسيلة التي يستخدمها إطار عمل البث في إرسال الرسائل واستلامها من مشغّل وسائط جهاز الاستقبال.
ستبدأ جلسة البث تلقائيًا عندما يختار المستخدم جهازًا من خلال زر البث، وستتوقف تلقائيًا عند قطع اتصال المستخدم. تتولى حزمة تطوير البرامج (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()
. يتم إنشاء الجلسات وإغلاقها تلقائيًا استجابةً لتفاعلات المستخدمين مع مربّعات حوار البث.
نحتاج إلى تسجيل مستمع الجلسة الخاص بنا وتهيئة بعض المتغيرات التي سنستخدمها في النشاط. غيِّر طريقة 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)
}
}
...
}
جارٍ تحميل الوسائط
في حزمة تطوير البرامج (SDK) لتطبيقات البث، يوفّر RemoteMediaClient
مجموعة من واجهات برمجة التطبيقات الملائمة لإدارة تشغيل الوسائط عن بُعد على جهاز الاستقبال. بالنسبة إلى CastSession
الذي يتيح تشغيل الوسائط، ستنشئ حزمة SDK مثيلًا من RemoteMediaClient
تلقائيًا. ويمكن الوصول إليه من خلال استدعاء طريقة 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))
...
}
الآن، انقر على الزر تشغيل لتشغيل التطبيق على جهازك الجوّال. اربط جهازك بجهاز البث وابدأ تشغيل فيديو. من المفترض أن ترى الفيديو قيد التشغيل على جهاز الاستقبال.
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"/>
انقر على الزر تشغيل لتشغيل التطبيق وبث فيديو. عند بدء التشغيل على جهاز الاستقبال، من المفترض أن يظهر جهاز التحكّم المصغّر في أسفل كل نشاط. يمكنك التحكّم في التشغيل عن بُعد باستخدام وحدة التحكّم الصغيرة. في حال الانتقال بين نشاط التصفّح ونشاط المشغّل على الجهاز، من المفترض أن تظل حالة وحدة التحكّم المصغّرة متزامنة مع حالة تشغيل الوسائط في جهاز الاستقبال.
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()
}
انقر على الزر تشغيل لتشغيل التطبيق على جهازك الجوّال. بث فيديو وانتقِل بعيدًا عن التطبيق النموذجي. من المفترض أن يظهر إشعار بالفيديو الذي يتم تشغيله حاليًا على جهاز الاستقبال. قفل جهازك الجوّال ومن المفترض أن تعرض شاشة القفل الآن عناصر التحكّم في تشغيل الوسائط على جهاز البث.
9. العنصر التمهيدي على سطح الفيديو
تتطلّب قائمة التحقّق من تصميم Google 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
عندما يكون جهاز Cast متاحًا من خلال تعديل طريقة 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"، نحتاج إلى ضبط علامة setAndroidReceiverCompatible
على "صحيح" في كائن LaunchOptions
. يحدِّد عنصر LaunchOptions
هذا كيفية تشغيل المُستلِم ويتم تمريره إلى CastOptions
الذي تعرضه فئة CastOptionsProvider
. سيؤدي ضبط العلامة المذكورة أعلاه على false
إلى تشغيل مستقبل الويب لمعرّف التطبيق المحدّد في Cast Developer 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 Connect
خطوات تثبيت حزمة APK لنظام التشغيل Android TV على جهاز "Chromecast مع Google TV"
- ابحث عن عنوان IP لجهاز Android TV. وهي متاحة عادةً ضمن الإعدادات > الشبكة والإنترنت > (اسم الشبكة التي يتصل بها جهازك). على يسار الصفحة، ستظهر التفاصيل وعنوان IP لجهازك على الشبكة.
- استخدِم عنوان IP لجهازك للاتصال به عبر ADB باستخدام وحدة التحكّم الطرفية:
$ adb connect <device_ip_address>:5555
- من نافذة المحطة الطرفية، انتقِل إلى مجلد المستوى الأعلى لمحاكيات رمز Lab التي نزّلتها في بداية هذا الإصدار التجريبي من رمز Lab. على سبيل المثال:
$ cd Desktop/android_codelab_src
- ثبِّت ملف .apk في هذا المجلد على Android TV من خلال تشغيل:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- ستتمكّن الآن من رؤية التطبيق باسم بث الفيديوهات في قائمة تطبيقاتك على جهاز Android TV.
- ارجع إلى مشروع Android Studio، وانقر على الزر "تشغيل" لتثبيت تطبيق المرسل وتشغيله على جهازك الجوّال الفعلي. في أعلى يسار الشاشة، انقر على رمز البث واختَر جهاز 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. تهانينا
لقد تعرّفت الآن على كيفية تفعيل ميزة البث في تطبيق فيديو باستخدام التطبيقات المصغّرة لحزمة تطوير البرامج (SDK) لبث الوسائط على أجهزة Android.
لمزيد من التفاصيل، يُرجى الاطّلاع على دليل المطوِّر لمرسلي Android.