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
- استوديو Android الإصدار 3.2 أو الإصدارات الأحدث
- جهاز جوّال يعمل بالإصدار Android 4.1 أو الإصدارات الأحدث Jelly Bean (المستوى 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 Studio. من المفترض أن يظهر تطبيق الفيديو المُسمّى Cast Videos بعد بضع ثوانٍ.
انقر على زر البث في تطبيق الفيديو واختَر جهاز 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- إضافة زر البث
يعرض تطبيق يعمل بتكنولوجيا 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، عليك التسجيل كمطوِّر لتكنولوجيا Google 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 في القسم السابق. من السهل تنفيذ الخطوة الثالثة باستخدام إطار عمل 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.
إدارة جلسة البث
بالنسبة إلى إطار عمل Google Cast، تجمع جلسة البث بين خطوات الاتصال بجهاز وتشغيله (أو الانضمام إليه) والاتصال بتطبيق مستقبِل وتهيئة قناة التحكم في الوسائط إذا كان ذلك مناسبًا. قناة التحكّم في الوسائط هي الطريقة التي يرسل بها إطار عمل Cast الرسائل ويتلقّاها من مشغّل الوسائط المستلِم.
سيتم بدء جلسة البث تلقائيًا عندما يختار المستخدم جهازًا من زر البث، وسيتم إيقافها تلقائيًا عند قطع المستخدم الاتصال. تتم أيضًا معالجة إعادة الاتصال بجلسة استقبال بسبب مشاكل في الشبكة تلقائيًا بواسطة حزمة تطوير البرامج (SDK) للإرسال.
لنضيف 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
الذي يتيح تشغيل الوسائط، سيتم تلقائيًا إنشاء مثيل لـ 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))
...
}
انقر الآن على الزر تشغيل لتشغيل التطبيق على جهازك الجوّال. اربط جهازك بجهاز البث وابدأ تشغيل فيديو. من المفترض أن يظهر الفيديو معروضًا على جهاز الاستقبال.
7- وحدة تحكُّم صغيرة
تتطلب قائمة التحقّق من تصميم البثّ أن توفّر جميع تطبيقات البثّ وحدة تحكّم مصغّرة تظهر عندما ينتقل المستخدم من صفحة المحتوى الحالية. وتوفِّر وحدة التحكّم المصغّرة إمكانية الوصول الفوري وتذكيرًا مرئيًا بجلسة البث الحالية.
توفّر حزمة تطوير البرامج (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()
}
انقر على الزر تشغيل لتشغيل التطبيق على جهازك الجوّال. يمكنك إرسال فيديو والانتقال من التطبيق النموذجي. من المفترض أن يكون هناك إشعار بشأن الفيديو الذي يتم تشغيله حاليًا على جهاز الاستقبال. قفل جهازك الجوّال ومن المفترض أن تعرض شاشة القفل الآن عناصر التحكّم في تشغيل الوسائط على جهاز البث.
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
عندما يكون جهاز 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) لتكنولوجيا 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. تعتمد خدمة Cast Connect على البنية الأساسية للبث، ويعمل تطبيق Android TV كوحدة استقبال.
التبعيات
ملاحظة: لتنفيذ Cast Connect، يجب أن يكون الإصدار play-services-cast-framework
هو 19.0.0
أو إصدار أحدث.
LaunchOptions
لتشغيل تطبيق Android TV، الذي يُشار إليه أيضًا باسم "مُستلِم Android"، يجب ضبط علامة setAndroidReceiverCompatible
على true في عنصر 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 Connect
خطوات تثبيت حزمة 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 Studio وانقر على زر "تشغيل" لتثبيت تطبيق المُرسِل وتشغيله على جهازك الجوّال. في أعلى يسار الشاشة، انقر على رمز البث واختَر جهاز Android TV من الخيارات المتاحة. من المفترض أن ترى الآن أنّه تم إطلاق تطبيق Android TV على جهاز Android TV، ومن المفترض أن يتيح لك تشغيل فيديو التحكّم في تشغيل الفيديو باستخدام جهاز التحكّم عن بُعد في Android TV.
12. تخصيص التطبيقات المصغّرة لميزة البث
يمكنك تخصيص أدوات البث عن طريق ضبط الألوان وتصميم الأزرار والنص ومظهر الصورة المصغّرة واختيار أنواع الأزرار التي سيتم عرضها.
تحديث 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 Sender.