1. نظرة عامة

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

لنبدأ بالاطّلاع على الشكل الذي سيبدو عليه نموذج التطبيق المكتمل. التطبيق هو مشغّل فيديو أساسي. يمكن للمستخدم اختيار فيديو من قائمة ثم تشغيله على الجهاز أو بثه إلى جهاز بث متوافق مع Google Cast.
بعد تنزيل الرمز، توضّح التعليمات التالية كيفية فتح نموذج التطبيق المكتمل وتشغيله في Xcode:
الأسئلة الشائعة
إعداد CocoaPods
لإعداد CocoaPods، انتقِل إلى وحدة التحكّم وثبِّت باستخدام Ruby التلقائي المتاح على macOS:
sudo gem install cocoapods
إذا واجهت أي مشاكل، يُرجى الرجوع إلى المستندات الرسمية لتنزيل أداة إدارة الاعتمادية وتثبيتها.
إعداد المشروع
- انتقِل إلى الوحدة الطرفية وانتقِل إلى دليل codelab.
- ثبِّت التبعيات من ملف Podfile.
cd app-done pod update pod install
- افتح Xcode وانقر على فتح مشروع آخر... (Open another project...)
- اختَر ملف
CastVideos-ios.xcworkspaceمن دليل
app-doneفي مجلد الرمز النموذجي.
تشغيل التطبيق
اختَر الهدف والمحاكي، ثم شغِّل التطبيق:

من المفترض أن يظهر تطبيق الفيديو بعد بضع ثوانٍ.
احرص على النقر على "السماح" عندما يظهر الإشعار بشأن قبول عمليات الربط الواردة بالشبكة. لن يظهر رمز البث إذا لم يتم قبول هذا الخيار.

انقر على زر "البث" واختَر جهاز Google Cast.
اختَر فيديو، ثم انقر على زر التشغيل.
سيبدأ تشغيل الفيديو على جهاز البث Google Cast.
سيتم عرض وحدة التحكّم الموسّعة. يمكنك استخدام زر التشغيل/الإيقاف المؤقت للتحكّم في التشغيل.
انتقِل مجددًا إلى قائمة الفيديوهات.
يظهر الآن جهاز تحكّم مصغّر في أسفل الشاشة.

انقر على زر الإيقاف المؤقت في وحدة التحكّم المصغّرة لإيقاف الفيديو مؤقتًا على جهاز الاستقبال. انقر على زر التشغيل في أداة التحكّم المصغّرة لمواصلة تشغيل الفيديو مجددًا.
انقر على زر البث لإيقاف البث إلى جهاز Google Cast.
4. إعداد مشروع البدء

يجب أن نضيف إمكانية استخدام Google Cast إلى تطبيق البث الذي نزّلته. في ما يلي بعض المصطلحات المستخدَمة في Google Cast والتي سنستخدمها في هذا الدرس التطبيقي حول الترميز:
- يعمل تطبيق المرسل على جهاز جوّال أو كمبيوتر محمول.
- يتم تشغيل تطبيق جهاز استقبال على جهاز Google Cast.
إعداد المشروع
أنت الآن جاهز للبناء على أساس المشروع الأوّلي باستخدام Xcode:
- انتقِل إلى الوحدة الطرفية وانتقِل إلى دليل codelab.
- ثبِّت التبعيات من ملف Podfile.
cd app-start pod update pod install
- افتح Xcode وانقر على فتح مشروع آخر... (Open another project...)
- اختَر ملف
CastVideos-ios.xcworkspaceمن دليل
app-startفي مجلد الرمز النموذجي.
تصميم التطبيقات
يجلب التطبيق قائمة بالفيديوهات من خادم ويب بعيد ويقدّم قائمة للمستخدم لتصفّحها. يمكن للمستخدمين اختيار فيديو للاطّلاع على التفاصيل أو تشغيله على الجهاز الجوّال.
يتألف التطبيق من وحدتَي تحكّم رئيسيتَين في العرض: MediaTableViewController وMediaViewController.
MediaTableViewController
تعرض أداة UITableViewController هذه قائمة بالفيديوهات من مثيل MediaListModel. تتم استضافة قائمة الفيديوهات والبيانات الوصفية المرتبطة بها على خادم بعيد كملف JSON. يجلب MediaListModel ملف JSON هذا ويعالجه لإنشاء قائمة بكائنات MediaItem.
يمثّل العنصر MediaItem فيديو وبياناته الوصفية المرتبطة به، مثل عنوانه ووصفه وعنوان URL الخاص بالصورة وعنوان URL الخاص بالبث.
ينشئ MediaTableViewController مثيلاً من MediaListModel ثم يسجّل نفسه كـ MediaListModelDelegate ليتم إعلامه عند تنزيل البيانات الوصفية للوسائط حتى يتمكّن من تحميل عرض الجدول.
تظهر للمستخدم قائمة بالصور المصغّرة للفيديوهات مع وصف قصير لكل فيديو. عند اختيار عنصر، يتم تمرير MediaItem المطابق إلى MediaViewController.
MediaViewController
تعرض وحدة التحكّم في العرض هذه البيانات الوصفية حول فيديو معيّن وتتيح للمستخدم تشغيل الفيديو على الجهاز الجوّال.
يستضيف عنصر التحكّم في العرض LocalPlayerView وبعض أدوات التحكّم في الوسائط ومساحة نصية لعرض وصف الفيديو المحدّد. يغطي المشغّل الجزء العلوي من الشاشة، ما يترك مساحة للوصف التفصيلي للفيديو أدناه. ويمكن للمستخدم تشغيل الفيديو المحلي أو إيقافه مؤقتًا أو البحث فيه.
الأسئلة الشائعة
5. إضافة زر الإرسال

يعرض التطبيق الذي يتيح استخدام Google Cast زرّ "البث" في كلّ من أدوات التحكّم في العرض. يؤدي النقر على زر "البث" إلى عرض قائمة بأجهزة البث التي يمكن للمستخدم اختيارها. إذا كان المستخدم يشغّل المحتوى محليًا على جهاز الإرسال، يؤدي اختيار جهاز بث إلى بدء التشغيل أو استئنافه على جهاز البث هذا. في أي وقت أثناء جلسة البث، يمكن للمستخدم النقر على زر البث وإيقاف بث تطبيقك إلى جهاز البث. يجب أن يتمكّن المستخدم من الاتصال بجهاز البث أو قطع الاتصال به أثناء تصفّح أي شاشة في تطبيقك، كما هو موضّح في قائمة التحقّق من تصميم Google Cast.
التهيئة
يتطلّب مشروع البدء التبعيات وإعداد Xcode نفسهما اللذين استخدمتهما في تطبيق العيّنة المكتمل. ارجع إلى هذا القسم واتّبِع الخطوات نفسها لإضافة GoogleCast.framework إلى مشروع تطبيق البدء.
الإعداد
يحتوي إطار عمل Cast على عنصر فردي عام، وهو GCKCastContext، الذي ينسّق جميع أنشطة إطار العمل. يجب تهيئة هذا العنصر في وقت مبكر من دورة حياة التطبيق، وعادةً في طريقة application(_:didFinishLaunchingWithOptions:) لمندوب التطبيق، حتى يتم تشغيل استئناف الجلسة التلقائي بشكل صحيح عند إعادة تشغيل تطبيق المرسِل، وحتى يمكن بدء البحث عن الأجهزة.
يجب توفير عنصر GCKCastOptions عند تهيئة GCKCastContext. يحتوي هذا الصف على خيارات تؤثر في سلوك إطار العمل. الأهم من ذلك هو معرّف تطبيق جهاز الاستقبال، والذي يُستخدَم لفلترة نتائج البحث عن أجهزة البث ولتشغيل تطبيق جهاز الاستقبال عند بدء جلسة بث.
تُعدّ طريقة application(_:didFinishLaunchingWithOptions:) أيضًا مكانًا جيدًا لإعداد مفوّض تسجيل لتلقّي رسائل التسجيل من إطار عمل Cast. ويمكن أن تكون هذه المعلومات مفيدة في تصحيح الأخطاء وتحديد المشاكل وحلّها.
عند تطوير تطبيق يتيح استخدام Google Cast، عليك التسجيل كمطوّر Cast ثم الحصول على معرّف تطبيق لتطبيقك. في هذا الدرس التطبيقي حول الترميز، سنستخدم نموذج تطبيق.
أضِف الرمز التالي إلى AppDelegate.swift لتهيئة GCKCastContext باستخدام معرّف التطبيق من الإعدادات التلقائية للمستخدم، وأضِف أداة تسجيل إحصاءات لإطار عمل Google Cast:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
fileprivate var enableSDKLogging = true
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
window?.clipsToBounds = true
setupCastLogging()
...
}
...
func setupCastLogging() {
let logFilter = GCKLoggerFilter()
let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
"GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
GCKLogger.sharedInstance().filter = logFilter
GCKLogger.sharedInstance().delegate = self
}
}
...
// MARK: - GCKLoggerDelegate
extension AppDelegate: GCKLoggerDelegate {
func logMessage(_ message: String,
at _: GCKLoggerLevel,
fromFunction function: String,
location: String) {
if enableSDKLogging {
// Send SDK's log messages directly to the console.
print("\(location): \(function) - \(message)")
}
}
}
زر الإرسال
بعد إعداد GCKCastContext، علينا الآن إضافة زر "البث" للسماح للمستخدم باختيار جهاز بث. توفّر حزمة تطوير البرامج (SDK) الخاصة بـ Cast مكوّن زر البث يُسمى GCKUICastButton كفئة فرعية من UIButton. يمكن إضافته إلى شريط عنوان التطبيق من خلال تضمينه في UIBarButtonItem. علينا إضافة زر البث إلى كل من MediaTableViewController وMediaViewController.
أضِف الرمز التالي إلى MediaTableViewController.swift وMediaViewController.swift:
import GoogleCast
@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
MediaListModelDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
print("MediaTableViewController - viewDidLoad")
super.viewDidLoad()
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
بعد ذلك، أضِف الرمز التالي إلى MediaViewController.swift:
import GoogleCast
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
LocalPlayerViewDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
super.viewDidLoad()
print("in MediaViewController viewDidLoad")
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
الآن شغِّل التطبيق. من المفترض أن يظهر لك زر "البث" في شريط التنقّل الخاص بالتطبيق، وعند النقر عليه، سيتم عرض قائمة بأجهزة البث على شبكتك المحلية. تتم إدارة عملية رصد الأجهزة تلقائيًا من خلال GCKCastContext. اختَر جهاز البث وسيتم تحميل نموذج تطبيق الاستقبال على جهاز البث. يمكنك التنقّل بين نشاط التصفّح ونشاط المشغّل المحلي، وستبقى حالة زر "البث" متزامنة.
لم نضِف بعد أي ميزة لتشغيل الوسائط، لذا لا يمكنك تشغيل الفيديوهات على جهاز البث حتى الآن. انقر على زر "البث" لإيقاف البث.
6. بث محتوى الفيديو

سنوسّع نطاق نموذج التطبيق ليشمل تشغيل الفيديوهات عن بُعد على جهاز البث. لإجراء ذلك، علينا الاستماع إلى الأحداث المختلفة التي ينشئها إطار عمل Cast.
بثّ الوسائط
بشكل عام، إذا أردت تشغيل وسائط على جهاز البث، يجب اتّباع الخطوات التالية:
- أنشئ عنصر
GCKMediaInformationمن حزمة تطوير البرامج (SDK) الخاصة بـ Cast التي تصمّم عنصر وسائط. - يتصل المستخدم بجهاز البث لتشغيل تطبيق الاستقبال.
- حمِّل العنصر
GCKMediaInformationفي جهاز الاستقبال وشغِّل المحتوى. - تتبُّع حالة الوسائط
- إرسال أوامر التشغيل إلى جهاز الاستقبال استنادًا إلى تفاعلات المستخدم
تتمثّل الخطوة الأولى في ربط عنصر بآخر، حيث إنّ GCKMediaInformation هو عنصر تفهمه حزمة تطوير البرامج (SDK) الخاصة بـ Cast، وMediaItem هو عملية تغليف تطبيقنا لعنصر وسائط، ويمكننا بسهولة ربط MediaItem بـ GCKMediaInformation. لقد أكملنا الخطوة 2 في القسم السابق. يمكن تنفيذ الخطوة 3 بسهولة باستخدام Cast SDK.
يميز نموذج التطبيق MediaViewController بين التشغيل المحلي والتشغيل عن بُعد باستخدام تعداد القيم التالي:
enum PlaybackMode: Int {
case none = 0
case local
case remote
}
private var playbackMode = PlaybackMode.none
ليس من المهم في هذا الدرس التطبيقي حول الترميز أن تفهم بالضبط طريقة عمل جميع نماذج منطق اللاعب. من المهم معرفة أنّه يجب تعديل مشغّل الوسائط في تطبيقك ليكون على دراية بموقعَي التشغيل بطريقة مماثلة.
في الوقت الحالي، يكون مشغّل الفيديو المحلي دائمًا في حالة التشغيل المحلية لأنّه لا يعرف أي شيء عن حالات البث بعد. علينا تعديل واجهة المستخدم استنادًا إلى عمليات نقل الحالة التي تحدث في إطار عمل Cast. على سبيل المثال، إذا بدأنا البث، علينا إيقاف التشغيل المحلي وإيقاف بعض عناصر التحكّم. وبالمثل، إذا أوقفنا البث عندما نكون في وحدة التحكّم في العرض هذه، علينا الانتقال إلى التشغيل المحلي. للتعامل مع ذلك، علينا الاستماع إلى الأحداث المختلفة التي ينشئها إطار عمل Cast.
إدارة جلسة البث
بالنسبة إلى إطار عمل Cast، تجمع جلسة Cast بين خطوات الربط بجهاز، وتشغيل تطبيق (أو الانضمام إليه)، والربط بتطبيق استقبال، وتهيئة قناة للتحكّم في الوسائط إذا كان ذلك مناسبًا. قناة التحكّم في الوسائط هي الطريقة التي يرسل بها إطار عمل Cast الرسائل ويستقبلها من مشغّل الوسائط على جهاز الاستقبال.
سيتم بدء جلسة البث تلقائيًا عندما يختار المستخدم جهازًا من زر "البث"، وسيتم إيقافها تلقائيًا عندما يقطع المستخدم الاتصال. يتولّى إطار عمل Cast أيضًا تلقائيًا إعادة الاتصال بجلسة جهاز استقبال بسبب مشاكل في الشبكة.
تتم إدارة جلسات البث من خلال GCKSessionManager، ويمكن الوصول إليها من خلال GCKCastContext.sharedInstance().sessionManager. يمكن استخدام عمليات ردّ الاتصال GCKSessionManagerListener لمراقبة أحداث الجلسة، مثل الإنشاء والتعليق والاستئناف والإنهاء.
أولاً، علينا تسجيل أداة معالجة الجلسات وتهيئة بعض المتغيرات:
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
private var sessionManager: GCKSessionManager!
...
required init?(coder: NSCoder) {
super.init(coder: coder)
sessionManager = GCKCastContext.sharedInstance().sessionManager
...
}
override func viewWillAppear(_ animated: Bool) {
...
let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
if hasConnectedSession, (playbackMode != .remote) {
populateMediaInfo(false, playPosition: 0)
switchToRemotePlayback()
} else if sessionManager.currentSession == nil, (playbackMode != .local) {
switchToLocalPlayback()
}
sessionManager.add(self)
...
}
override func viewWillDisappear(_ animated: Bool) {
...
sessionManager.remove(self)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
super.viewWillDisappear(animated)
}
func switchToLocalPlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
}
func switchToRemotePlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
...
}
// MARK: - GCKSessionManagerListener
func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
print("MediaViewController: sessionManager didStartSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
print("MediaViewController: sessionManager didResumeSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
print("session ended with error: \(String(describing: error))")
let message = "The Casting session has ended.\n\(String(describing: error))"
if let window = appDelegate?.window {
Toast.displayMessage(message, for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
if let error = error {
showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
}
setQueueButtonVisible(false)
}
func sessionManager(_: GCKSessionManager,
didFailToResumeSession _: GCKSession, withError _: Error?) {
if let window = UIApplication.shared.delegate?.window {
Toast.displayMessage("The Casting session could not be resumed.",
for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
...
}
في MediaViewController، يهمّنا أن يتم إعلامنا عند الاتصال بجهاز البث أو قطع الاتصال به حتى نتمكّن من التبديل إلى المشغّل المحلي أو العكس. يُرجى العِلم أنّ الاتصال يمكن أن ينقطع ليس فقط بسبب تشغيل مثيل تطبيقك على جهازك الجوّال، ولكن أيضًا بسبب تشغيل مثيل آخر لتطبيقك (أو تطبيق آخر) على جهاز جوّال مختلف.
يمكن الوصول إلى الجلسة النشطة حاليًا باسم GCKCastContext.sharedInstance().sessionManager.currentCastSession. يتم إنشاء الجلسات وإيقافها تلقائيًا استجابةً لإيماءات المستخدم من مربّعات حوار Cast.
جارٍ تحميل الوسائط
في Cast SDK، يوفّر GCKRemoteMediaClient مجموعة من واجهات برمجة التطبيقات الملائمة لإدارة تشغيل الوسائط عن بُعد على جهاز الاستقبال. بالنسبة إلى GCKCastSession الذي يتيح تشغيل الوسائط، سيتم إنشاء مثيل GCKRemoteMediaClient تلقائيًا بواسطة حزمة SDK. ويمكن الوصول إليه كسمة remoteMediaClient من مثيل GCKCastSession.
أضِف الرمز التالي إلى MediaViewController.swift لتحميل الفيديو المحدّد حاليًا على جهاز الاستقبال:
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
@objc func playSelectedItemRemotely() {
loadSelectedItem(byAppending: false)
}
/**
* Loads the currently selected item in the current cast media session.
* @param appending If YES, the item is appended to the current queue if there
* is one. If NO, or if
* there is no queue, a new queue containing only the selected item is created.
*/
func loadSelectedItem(byAppending appending: Bool) {
print("enqueue item \(String(describing: mediaInfo))")
if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = true
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
let mediaQueueItem = mediaQueueItemBuilder.build()
if appending {
let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
request.delegate = self
} else {
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
request.delegate = self
}
}
}
...
}
عدِّل الآن الطرق الحالية المختلفة لاستخدام منطق "جلسة Cast" من أجل إتاحة التشغيل عن بُعد:
required init?(coder: NSCoder) {
super.init(coder: coder)
...
castMediaController = GCKUIMediaController()
...
}
func switchToLocalPlayback() {
print("switchToLocalPlayback")
if playbackMode == .local {
return
}
setQueueButtonVisible(false)
var playPosition: TimeInterval = 0
var paused: Bool = false
var ended: Bool = false
if playbackMode == .remote {
playPosition = castMediaController.lastKnownStreamPosition
paused = (castMediaController.lastKnownPlayerState == .paused)
ended = (castMediaController.lastKnownPlayerState == .idle)
print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
}
populateMediaInfo((!paused && !ended), playPosition: playPosition)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
playbackMode = .local
}
func switchToRemotePlayback() {
print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
if playbackMode == .remote {
return
}
// If we were playing locally, load the local media on the remote player
if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
print("loading media: \(String(describing: mediaInfo))")
let paused: Bool = (_localPlayerView.playerState == .paused)
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = !paused
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
let mediaQueueItem = mediaQueueItemBuilder.build()
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
request?.delegate = self
}
_localPlayerView.stop()
_localPlayerView.showSplashScreen()
setQueueButtonVisible(true)
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
playbackMode = .remote
}
/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
let hasConnectedCastSession = sessionManager.hasConnectedCastSession
if mediaInfo != nil, hasConnectedCastSession() {
// Display an alert box to allow the user to add to queue or play
// immediately.
if actionSheet == nil {
actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
actionSheet?.addAction(withTitle: "Play Now", target: self,
selector: #selector(playSelectedItemRemotely))
}
actionSheet?.present(in: self, sourceView: _localPlayerView)
return false
}
return true
}
الآن، شغِّل التطبيق على جهازك الجوّال. اتصل بجهاز البث وابدأ بتشغيل فيديو. من المفترض أن يظهر الفيديو قيد التشغيل على جهاز الاستقبال.
7. وحدة تحكّم مصغّرة
تتطلّب قائمة التحقّق من تصميم Cast أن توفّر جميع تطبيقات Cast وحدة تحكّم مصغّرة تظهر عندما ينتقل المستخدم إلى صفحة أخرى غير صفحة المحتوى الحالية. توفّر أداة التحكّم المصغّرة إمكانية الوصول الفوري إلى جلسة Cast الحالية وتذكيرًا مرئيًا بها.

توفّر حزمة تطوير البرامج (SDK) الخاصة بـ Cast شريط تحكّم GCKUIMiniMediaControlsViewController يمكن إضافته إلى المشاهد التي تريد عرض عناصر التحكّم الدائمة فيها.
بالنسبة إلى نموذج التطبيق، سنستخدم GCKUICastContainerViewController الذي يغلّف وحدة تحكّم عرض أخرى ويضيف GCKUIMiniMediaControlsViewController في أسفل الشاشة.
عدِّل الملف AppDelegate.swift وأضِف الرمز التالي لشرط if useCastContainerViewController في الطريقة التالية:
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
as? UINavigationController else { return false }
let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
as GCKUICastContainerViewController
castContainerVC.miniMediaControlsItemEnabled = true
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = castContainerVC
window?.makeKeyAndVisible()
...
}
أضِف هذه السمة ودالة الضبط/الحصول للتحكّم في مستوى ظهور وحدة التحكّم المصغّرة (سنستخدمها في قسم لاحق):
var isCastControlBarsEnabled: Bool {
get {
if useCastContainerViewController {
let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
return castContainerVC!.miniMediaControlsItemEnabled
} else {
let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
return rootContainerVC!.miniMediaControlsViewEnabled
}
}
set(notificationsEnabled) {
if useCastContainerViewController {
var castContainerVC: GCKUICastContainerViewController?
castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
} else {
var rootContainerVC: RootContainerViewController?
rootContainerVC = (window?.rootViewController as? RootContainerViewController)
rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
}
}
}
شغِّل التطبيق وابدأ بث فيديو. عندما يبدأ التشغيل على جهاز الاستقبال، من المفترض أن يظهر جهاز التحكّم المصغّر في أسفل كل مشهد. يمكنك التحكّم في التشغيل عن بُعد باستخدام وحدة التحكّم المصغّرة. في حال التنقّل بين نشاط التصفّح ونشاط المشغّل المحلي، يجب أن تظل حالة وحدة التحكّم المصغّرة متزامنة مع حالة تشغيل الوسائط على جهاز الاستقبال.
8. نافذة منبثقة تمهيدية
تتطلّب قائمة التحقّق من تصميم Google Cast أن تُعرّف تطبيقات الإرسال المستخدمين الحاليين على زر البث لإعلامهم بأنّ تطبيق الإرسال يتيح الآن ميزة "البث"، كما تساعد المستخدمين الجدد في التعرّف على Google Cast.

يحتوي الصف GCKCastContext على طريقة presentCastInstructionsViewControllerOnce يمكن استخدامها لتمييز زر "البث" عند عرضه للمستخدمين لأول مرة. أضِف الرمز التالي إلى MediaViewController.swift وMediaTableViewController.swift:
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
name: NSNotification.Name.gckCastStateDidChange,
object: GCKCastContext.sharedInstance())
}
@objc func castDeviceDidChange(_: Notification) {
if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
// You can present the instructions on how to use Google Cast on
// the first time the user uses you app
GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
}
}
شغِّل التطبيق على جهازك الجوّال، وسيظهر لك التراكب التمهيدي.
9- وحدة تحكّم موسّعة
تتطلّب قائمة التحقّق من تصميم Google Cast أن يوفّر تطبيق المرسِل وحدة تحكّم موسّعة للوسائط التي يتم بثّها. وحدة التحكّم الموسّعة هي نسخة بملء الشاشة من وحدة التحكّم المصغّرة.

وحدة التحكّم الموسّعة هي طريقة عرض بملء الشاشة تتيح التحكّم الكامل في تشغيل الوسائط عن بُعد. يجب أن يتيح هذا العرض لتطبيق البث إدارة كل جانب يمكن إدارته من جلسة البث، باستثناء التحكّم في مستوى صوت جهاز الاستقبال ودورة حياة الجلسة (بدء البث/إيقافه). ويوفّر أيضًا جميع معلومات الحالة حول جلسة الوسائط (العمل الفني والعنوان والعنوان الفرعي وما إلى ذلك).
يتم تنفيذ وظيفة طريقة العرض هذه من خلال الفئة GCKUIExpandedMediaControlsViewController.
أول ما عليك فعله هو تفعيل وحدة التحكّم الموسّعة التلقائية في سياق البث. عدِّل AppDelegate.swift لتفعيل وحدة التحكّم الموسَّعة التلقائية:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
// Add after the setShareInstanceWith(options) is set.
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
...
}
...
}
أضِف الرمز التالي إلى MediaViewController.swift لتحميل وحدة التحكّم الموسّعة عندما يبدأ المستخدم في بث فيديو:
@objc func playSelectedItemRemotely() {
...
appDelegate?.isCastControlBarsEnabled = false
GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}
سيتم أيضًا تشغيل أداة التحكّم الموسّعة تلقائيًا عندما ينقر المستخدم على أداة التحكّم المصغّرة.
شغِّل التطبيق وأرسِل فيديو. من المفترض أن تظهر لك وحدة التحكّم الموسَّعة. ارجع إلى قائمة الفيديوهات، وعند النقر على وحدة التحكّم المصغّرة، سيتم تحميل وحدة التحكّم الموسّعة مرة أخرى.
10. إضافة ميزة Cast Connect
تتيح مكتبة Cast Connect للتطبيقات الحالية التي ترسل المحتوى التواصل مع تطبيقات Android TV من خلال بروتوكول Cast. تستند ميزة Cast Connect إلى البنية الأساسية لـ Cast، حيث يعمل تطبيق Android TV كجهاز استقبال.
الاعتمادية
في Podfile، تأكَّد من توجيه google-cast-sdk إلى 4.4.8 أو أعلى كما هو موضّح أدناه. إذا أجريت تعديلاً على الملف، شغِّل pod update من وحدة التحكّم لمزامنة التغيير مع مشروعك.
pod 'google-cast-sdk', '>=4.4.8'
GCKLaunchOptions
من أجل تشغيل تطبيق Android TV، المعروف أيضًا باسم Android Receiver، يجب ضبط العلامة androidReceiverCompatible على "صحيح" في العنصر GCKLaunchOptions. يحدّد عنصر GCKLaunchOptions هذا طريقة تشغيل جهاز الاستقبال ويتم تمريره إلى GCKCastOptions التي يتم ضبطها في المثيل المشترك باستخدام GCKCastContext.setSharedInstanceWith.
أضِف الأسطر التالية إلى ملف AppDelegate.swift:
let options = GCKCastOptions(discoveryCriteria:
GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
ضبط بيانات اعتماد الإطلاق
من جهة المُرسِل، يمكنك تحديد GCKCredentialsData لتمثيل المستخدم الذي ينضم إلى الجلسة. credentials هي سلسلة يمكن للمستخدم تحديدها، طالما أنّ تطبيق Android TV يمكنه فهمها. لا يتم تمرير GCKCredentialsData إلا إلى تطبيق Android TV أثناء التشغيل أو الانضمام. إذا ضبطت كلمة المرور مرة أخرى أثناء اتصالك، لن يتم نقلها إلى تطبيق Android TV.
لضبط بيانات اعتماد التشغيل، يجب تحديد GCKCredentialsData في أي وقت بعد ضبط GCKLaunchOptions. لتوضيح ذلك، لنضِف منطقًا لزر بيانات الاعتماد من أجل ضبط بيانات الاعتماد التي سيتم تمريرها عند إنشاء الجلسة. أضِف الرمز التالي إلى MediaTableViewController.swift:
class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
...
private var credentials: String? = nil
...
override func viewDidLoad() {
...
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
target: self, action: #selector(toggleLaunchCreds))
...
setLaunchCreds()
}
...
@objc func toggleLaunchCreds(_: Any){
if (credentials == nil) {
credentials = "{\"userId\":\"id123\"}"
} else {
credentials = nil
}
Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
print("Credentials set: "+(credentials ?? "Null"))
setLaunchCreds()
}
...
func setLaunchCreds() {
GCKCastContext.sharedInstance()
.setLaunch(GCKCredentialsData(credentials: credentials))
}
}
ضبط بيانات الاعتماد عند طلب التحميل
للتعامل مع credentials في كلّ من تطبيقات Web وAndroid TV Receiver، أضِف الرمز التالي في فئة MediaTableViewController.swift ضمن الدالة loadSelectedItem:
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...
استنادًا إلى تطبيق الاستقبال الذي يبث إليه جهاز الإرسال، ستطبّق حزمة SDK تلقائيًا بيانات الاعتماد المذكورة أعلاه على الجلسة الجارية.
اختبار ميزة "البث المتزامن"
خطوات تثبيت حزمة APK لنظام Android TV على جهاز Chromecast مع Google TV
- ابحث عن عنوان IP لجهاز Android TV. عادةً ما يكون متاحًا ضمن الإعدادات > الشبكة والإنترنت > (اسم الشبكة التي يتصل بها جهازك). على اليسار، ستظهر التفاصيل وعنوان IP لجهازك على الشبكة.
- استخدِم عنوان IP الخاص بجهازك للاتصال به عبر ADB باستخدام الوحدة الطرفية:
$ adb connect <device_ip_address>:5555
- من نافذة الوحدة الطرفية، انتقِل إلى مجلد المستوى الأعلى لعينات الدرس التطبيقي حول الترميز التي نزّلتها في بداية هذا الدرس. على سبيل المثال:
$ cd Desktop/ios_codelab_src
- ثبِّت ملف .apk في هذا المجلد على Android TV من خلال تنفيذ الأمر التالي:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- من المفترض أن يظهر لك الآن تطبيق باسم بثّ الفيديوهات في قائمة تطبيقاتك على جهاز Android TV.
- بعد الانتهاء، أنشئ التطبيق وشغِّله على محاكي أو جهاز جوّال. بعد إنشاء جلسة بث باستخدام جهاز Android TV، من المفترض أن يتم تشغيل تطبيق Android Receiver على جهاز Android TV. عند تشغيل فيديو من جهاز iOS الجوّال، من المفترض أن يتم تشغيل الفيديو في جهاز Android Receiver وأن يسمح لك بالتحكّم في التشغيل باستخدام جهاز التحكّم عن بُعد لجهاز Android TV.
11. تخصيص تطبيقات Cast المصغّرة
الإعداد
ابدأ بمجلد App-Done. أضِف ما يلي إلى طريقة applicationDidFinishLaunchingWithOptions في ملف AppDelegate.swift.
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let styler = GCKUIStyle.sharedInstance()
...
}
بعد الانتهاء من تطبيق تخصيص واحد أو أكثر كما هو موضّح في بقية هذا الدرس التطبيقي حول الترميز، نفِّذ الأنماط من خلال استدعاء الرمز البرمجي أدناه
styler.apply()
تخصيص طرق العرض في Cast
يمكنك تخصيص جميع طرق العرض التي يديرها إطار عمل Cast Application Framework من خلال اتّباع إرشادات التنسيق التلقائي في جميع طرق العرض. على سبيل المثال، لنغيّر لون صبغة الرمز.
styler.castViews.iconTintColor = .lightGray
يمكنك تجاوز الإعدادات التلقائية على أساس كل شاشة على حدة إذا لزم الأمر. على سبيل المثال، لإلغاء lightGrayColor للون تلوين الرمز فقط لوحدة التحكّم الموسّعة في الوسائط.
styler.castViews.mediaControl.expandedController.iconTintColor = .green
تغيير الألوان
يمكنك تخصيص لون الخلفية لجميع طرق العرض (أو بشكل فردي لكل طريقة عرض). يضبط الرمز التالي لون الخلفية على الأزرق لجميع طرق العرض التي يوفّرها إطار عمل تطبيقات Cast.
styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow
تغيير الخطوط
يمكنك تخصيص الخطوط للتصنيفات المختلفة التي تظهر في طرق عرض الممثلين وطاقم العمل. لنضبط جميع الخطوط على "Courier-Oblique" لأغراض توضيحية.
styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)
تغيير صور الأزرار التلقائية
أضِف صورك المخصّصة إلى المشروع، وعيِّن الصور لأزرارك لتصميمها.
let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
styler.castViews.muteOnImage = muteOnImage
}
تغيير مظهر زر "البث"
يمكنك أيضًا تخصيص مظهر "أدوات Cast" باستخدام بروتوكول UIAppearance. يُطبِّق الرمز التالي مظهر GCKUICastButton على جميع طرق العرض التي يظهر فيها:
GCKUICastButton.appearance().tintColor = UIColor.gray
12. تهانينا
أصبحت الآن تعرف كيفية تفعيل ميزة "الإرسال" في تطبيق فيديو باستخدام أدوات واجهة المستخدم الخاصة بحزمة تطوير البرامج (SDK) الخاصة بميزة "الإرسال" على أجهزة iOS.
لمزيد من التفاصيل، يُرجى الاطّلاع على دليل المطوّر مرسِل iOS.