1- نظرة عامة
سيعلّمك هذا الدرس التطبيقي حول الترميز كيفية تعديل تطبيق فيديو حالي على Android لإرسال محتوى إلى جهاز يعمل بتكنولوجيا Google Cast.
ما المقصود بـ Google Cast؟
يتيح Google Cast للمستخدمين إرسال المحتوى من جهاز جوّال إلى جهاز تلفزيون. ويمكن للمستخدمين بعد ذلك استخدام أجهزتهم الجوّالة كوحدة تحكم عن بُعد لتشغيل الوسائط على التلفزيون.
تتيح لك حزمة Google Cast SDK توسيع نطاق تطبيقك للتحكّم في نظام التلفزيون أو الصوت. تتيح لك أدوات Cast Cast إضافة مكونات واجهة المستخدم الضرورية استنادًا إلى قائمة التحقق من Google Cast Design.
يتم توفير قائمة التحقق من تصميم Google Cast لتيسير تجربة المستخدم في Cast، كما يمكن التنبؤ بها عبر جميع الأنظمة الأساسية المتاحة.
ما الذي سنبنيه؟
عند الانتهاء من هذا الدرس التطبيقي حول الترميز، سيصبح لديك تطبيق فيديو متوافق مع Android سيتمكّن من إرسال الفيديوهات إلى جهاز مزوّد بتكنولوجيا Google Cast.
ما ستتعرَّف عليه
- كيفية إضافة حزمة Google Cast SDK إلى نموذج فيديو.
- كيفية إضافة زر الإرسال لاختيار جهاز Google Cast
- كيفية الاتصال بجهاز بث وتشغيل جهاز استقبال الوسائط
- كيفية إرسال فيديو.
- كيفية إضافة وحدة تحكم Cast صغيرة إلى تطبيقك.
- كيفية دعم إشعارات الوسائط وعناصر التحكم في شاشة التأمين.
- كيفية إضافة وحدة تحكم موسّعة.
- كيفية توفير تراكب تمهيدي.
- كيفية تخصيص أدوات الإرسال
- كيفية الدمج مع Cast Connect
المتطلبات
- أحدث إصدار من Android SDK.
- Android Studio الإصدار 3.2 أو الإصدارات الأحدث
- جهاز جوال واحد يعمل بنظام التشغيل Android 4.1+ Jelly Bean (مستوى واجهة برمجة التطبيقات 16).
- كابل بيانات USB لتوصيل جهازك الجوّال بجهاز الكمبيوتر المستخدَم للتطوير.
- جهاز Google Cast مثل Chromecast أو Android TV الذي تم إعداده للاستخدام مع الاتصال بالإنترنت.
- تلفزيون أو شاشة مزودة بمنفذ إدخال HDMI.
- يجب توفّر جهاز Chromecast مع Google TV لاختبار دمج Cast Connect، إلا أنّه اختياري لبقية أجزاء الدرس التطبيقي حول الترميز. إذا لم يكن لديك هذا البرنامج، يُرجى عدم التردد في تخطّي خطوة إضافة دعم "الربط بتكنولوجيا Google Cast" في نهاية هذا البرنامج التعليمي.
التجربة
- يجب أن تكون لديك معرفة سابقة بتطوير لغة Kotlin وAndroid.
- ستحتاج أيضًا إلى معرفة سابقة بمشاهدة التلفزيون :)
كيف ستستخدم هذا البرنامج التعليمي؟
ما تقييمك لتجربتك في إنشاء تطبيقات Android؟
ما هو تقييمك لتجربتك في مشاهدة التلفزيون؟
2- الحصول على نموذج الشفرة
يمكنك تنزيل كل نماذج الشفرة إلى جهاز الكمبيوتر...
وفكّ ضغط الملف المضغوط الذي تم تنزيله.
3. تشغيل نموذج التطبيق
لنرى أولاً كيف يبدو نموذج التطبيق المكتمل. التطبيق عبارة عن مشغّل فيديو أساسي. يمكن للمستخدم اختيار فيديو من قائمة ثم يمكنه تشغيل الفيديو محليًا على الجهاز أو إرساله إلى جهاز Google Cast.
بعد تنزيل الرمز، تصف التعليمات التالية كيفية فتح نموذج التطبيق المكتمل وتشغيله في Android Studio:
حدد استيراد المشروع على شاشة الترحيب أو خيارات القائمة ملف > جديد > استيراد المشروع....
اختَر الدليل app-done
من مجلد الرمز النموذجي وانقر على "حسنًا".
انقر على ملف > مزامنة المشروع مع ملفات Gradle.
تمكين تصحيح أخطاء USB على جهاز Android - على الإصدار Android 4.2 والإصدارات الأحدث، يتم إخفاء شاشة خيارات المطورين افتراضيًا. لإظهاره، انتقل إلى الإعدادات > حول الهاتف وانقر على رقم الإصدار سبع مرات. ارجع إلى الشاشة السابقة، وانتقل إلى النظام > خيارات متقدمة، وانقر على خيارات المطورين بالقرب من الجزء السفلي، ثم انقر على تصحيح أخطاء USB لتشغيله.
وصِّل جهازك الذي يعمل بنظام التشغيل Android وانقر على الزر تشغيل في "استوديو Android". من المفترض أن يظهر تطبيق الفيديو المسمى إرسال مقاطع الفيديو بعد بضع ثوانٍ.
انقر على زر الإرسال في تطبيق الفيديو وحدد جهاز Google Cast.
حدد مقطع فيديو وانقر على زر التشغيل.
سيبدأ تشغيل الفيديو على جهاز Google Cast.
سيتم عرض وحدة التحكم الموسّعة. يمكنك استخدام زر التشغيل/الإيقاف المؤقت للتحكم في التشغيل.
انتقِل مجددًا إلى قائمة الفيديوهات.
تظهر وحدة تحكّم مصغّرة الآن في أسفل الشاشة.
انقر على زر الإيقاف المؤقت في وحدة التحكم المصغّرة لإيقاف الفيديو مؤقتًا على جهاز الاستقبال. انقر على زر التشغيل في وحدة التحكم المصغّرة لمتابعة تشغيل الفيديو مرة أخرى.
انقر على زر الصفحة الرئيسية للجهاز الجوّال. يُرجى سحب الإشعارات للأسفل وسيظهر لك الآن إشعار لجلسة البث.
يمكنك قفل هاتفك وعند فتح قفله، من المفترض أن ترى إشعارًا على شاشة القفل للتحكم في تشغيل الوسائط أو إيقاف الإرسال.
ارجع إلى تطبيق الفيديو وانقر على زر الإرسال لإيقاف الإرسال على جهاز Google Cast.
الأسئلة الشائعة
4. إعداد المشروع الافتتاحي
يجب إضافة Google Cast إلى بدء تشغيل التطبيق الذي نزّلته. في ما يلي بعض مصطلحات Google Cast التي سنستخدمها في هذا الدرس التطبيقي حول الترميز:
- يعمل تطبيق المُرسِل على جهاز جوّال أو كمبيوتر محمول،
- يعمل تطبيق مستلِم على جهاز Google Cast.
أصبحت الآن جاهزًا للبدء في إنجاز المشروع الأول باستخدام Android Studio:
- اختَر الدليل
app-start
من نموذج تنزيل الرمز (اختَر استيراد المشروع في شاشة الترحيب أو خيار القائمة ملف > جديد > استيراد المشروع...). - انقر على الزر
مزامنة المشروع مع ملفات Gradle.
- انقر على الزر
تشغيل لتشغيل التطبيق واستكشاف واجهة المستخدم.
تصميم التطبيقات
يجلب التطبيق قائمة بمقاطع الفيديو من خادم ويب بعيد ويوفر قائمة يتصفحها المستخدم. ويمكن للمستخدمين اختيار فيديو للاطّلاع على التفاصيل أو تشغيل الفيديو محليًا على الجهاز الجوّال.
يتكون التطبيق من نشاطين أساسيين: VideoBrowserActivity
وLocalPlayerActivity
. لدمج وظائف Google Cast، يجب أن تكتسب الأنشطة من AppCompatActivity
أو أصلها من FragmentActivity
. يتوفّر هذا القيد لأننا سنحتاج إلى إضافة السمة MediaRouteButton
(المتوفّرة في مكتبة دعم MediaRouter) باعتبارها 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، يتعين عليك التسجيل كمطوّر برامج 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
ببطء في طريقة VideoBrowserActivity
onCreate:
import com.google.android.gms.cast.framework.CastContext
private var mCastContext: CastContext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastContext = CastContext.getSharedInstance(this)
}
إضافة منطق التهيئة نفسه إلى LocalPlayerActivity
.
زر الإرسال
الآن وبعد إعداد CastContext
، يلزم إضافة زر الإرسال للسماح للمستخدم باختيار جهاز بث. يتم تطبيق زر الإرسال بواسطة MediaRouteButton
من مكتبة دعم MediaRouter. مثل أي رمز إجراء يمكنك إضافته إلى نشاطك (باستخدام ActionBar
أو Toolbar
)، عليك أولاً إضافة عنصر القائمة المقابل إلى قائمتك.
عدّل الملف res/menu/browse.xml
وأضف العنصر MediaRouteActionProvider
في القائمة قبل عنصر الإعدادات:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
يمكنك إلغاء طريقة onCreateOptionsMenu()
لـ VideoBrowserActivity
باستخدام CastButtonFactory
لربط MediaRouteButton
بإطار عمل Cast:
import com.google.android.gms.cast.framework.CastButtonFactory
private var mediaRouteMenuItem: MenuItem? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.browse, menu)
mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
R.id.media_route_menu_item)
return true
}
يمكنك إلغاء onCreateOptionsMenu
في LocalPlayerActivity
بطريقة مشابهة.
انقر على الزر تشغيل لتشغيل التطبيق على جهازك الجوّال. من المفترض أن يظهر لك زر الإرسال في شريط إجراءات التطبيق وعند النقر عليه، سيتم إدراج أجهزة البث على شبكتك المحلية. تتم إدارة عملية اكتشاف الجهاز تلقائيًا من خلال
CastContext
. اختَر جهاز البث وسيتم تحميل نموذج تطبيق المُستلِم على جهاز البث. ويمكنك التنقل بين نشاط التصفُّح ونشاط المشغل المحلي وحالة زر الإرسال في حالة المزامنة.
لم يتم توصيل أي إمكانية لتشغيل الوسائط، لذا لا يمكنك تشغيل مقاطع فيديو على جهاز البث حتى الآن. انقر على زر الإرسال لقطع الاتصال.
6. إرسال محتوى الفيديو
سيتم توسيع نموذج التطبيق لتشغيل مقاطع الفيديو عن بُعد أيضًا على جهاز بث. ولإجراء ذلك، نحتاج إلى الاستماع إلى الأحداث المختلفة التي يتم إنشاؤها بواسطة إطار عمل الإرسال.
بث الوسائط
على مستوى عالٍ، إذا كنت تريد تشغيل الوسائط على جهاز بث، فعليك تنفيذ هذه الإجراءات:
- أنشئ كائن
MediaInfo
يُنشئ نموذجًا لعنصر الوسائط. - يمكنك الاتصال بجهاز Cast وتشغيل تطبيق جهاز الاستقبال.
- حمِّل الكائن
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
}
ليس من المهم في هذا الدرس التطبيقي حول الترميز أن تفهم بالضبط آلية عمل منطق كل نماذج اللاعبين. ومن المهم أن تفهم أنه يجب تعديل مشغّل الوسائط لتطبيقك ليكون على دراية بموقعي التشغيل بطريقة مماثلة.
في الوقت الحالي، يكون المشغل المحلي دائمًا في حالة التشغيل المحلية لأنه لا يعرف أي شيء عن حالات الإرسال حتى الآن. نحتاج إلى تحديث واجهة المستخدم استنادًا إلى انتقالات الحالة التي تحدث في إطار عمل الإرسال. على سبيل المثال، إذا بدأنا الإرسال، نحتاج إلى إيقاف التشغيل المحلي وإيقاف بعض عناصر التحكّم. وبالمثل، إذا توقّفنا إرسال المحتوى عندما نكون في هذا النشاط، نحتاج إلى الانتقال إلى التشغيل المحلي. ولمعالجة ذلك، نحتاج إلى الاستماع إلى الأحداث المختلفة التي يتم إنشاؤها بواسطة إطار عمل الإرسال.
إدارة جلسة الإرسال
بالنسبة إلى إطار عمل الإرسال، تجمع جلسة الإرسال بين خطوات الاتصال بجهاز وإطلاق (أو الانضمام) والاتصال بتطبيق مستلِم وإعداد قناة تحكُّم في الوسائط إذا كان ذلك مناسبًا. قناة التحكم في الوسائط هي كيفية إرسال إطار عمل الإرسال وتلقي الرسائل من مشغّل وسائط المستلم.
سيتم بدء جلسة الإرسال تلقائيًا عندما يختار المستخدم جهازًا من زر الإرسال، وسيتم إيقافها تلقائيًا عند قطع اتصال المستخدم. ويتم أيضًا التعامل مع إعادة الاتصال بجلسة المُستلِم بسبب مشاكل في الشبكة تلقائيًا من خلال حزمة تطوير البرامج (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. وحدة تحكم صغيرة
تتطلب قائمة التحقق من تطبيق "Cast" أن توفّر جميع تطبيقات Cast وحدة تحكم صغيرة تظهر عندما ينتقل المستخدم بعيدًا عن صفحة المحتوى الحالية. وتوفّر وحدة التحكّم المصغّرة إمكانية الوصول الفوري وتذكير مرئي لجلسة البث الحالية.
توفر حزمة تطوير البرامج (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) للإرسال MediaNotificationService
لمساعدة تطبيق المُرسِل في إنشاء عناصر تحكّم في الوسائط للإشعارات وشاشة القفل. يتم دمج الخدمة تلقائيًا في ملف بيان التطبيق حسب المجموعة.
سيتم تشغيل 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
عندما يكون جهاز البث متاحًا من خلال تعديل طريقة 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) للإرسال أداة لأداة التحكم الموسَّعة تُسمى 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 على البنية الأساسية لتطبيق Cast، حيث يعمل تطبيق Android TV كجهاز استقبال.
العناصر التابعة
ملاحظة: لتطبيق Cast Connect، يجب أن يكون play-services-cast-framework
19.0.0
أو أعلى.
خيارات التشغيل
لتشغيل تطبيق Android TV، الذي يُشار إليه أيضًا باسم مستلم Android، نحتاج إلى تعيين علامة setAndroidReceiverCompatible
إلى true في الكائن 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
في حال كان تطبيق "مستقبل الويب" وتطبيق 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 وانقر على زر "تشغيل" لتثبيت تطبيق المرسل وتشغيله على جهازك الجوّال الفعلي. في الزاوية العلوية اليسرى، انقر على رمز الإرسال وحدد جهاز 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- تهانينا
أنت تعرف الآن كيفية تفعيل تطبيق فيديو يعمل بتكنولوجيا Google Cast باستخدام أدوات Cast SDK على نظام التشغيل Android.
لمزيد من التفاصيل، راجع دليل مطوِّري برامج Android Sender.