تفعيل بث تطبيق iOS

1- نظرة عامة

شعار Google Cast

يشرح لك هذا الدرس التطبيقي حول الترميز كيفية تعديل تطبيق فيديو حالي على iOS لبث المحتوى على جهاز يتوافق مع Google Cast.

ما المقصود بـ Google Cast؟

تتيح تكنولوجيا Google Cast للمستخدمين بث المحتوى من جهاز جوّال إلى تلفزيون. يمكن للمستخدمين بعد ذلك استخدام أجهزتهم الجوّالة كوحدة تحكّم عن بُعد لتشغيل الوسائط على التلفزيون.

تتيح لك حزمة تطوير البرامج (SDK) لتقنية Google Cast توسيع نطاق تطبيقك للتحكّم في الأجهزة التي تعمل بتكنولوجيا Google Cast (مثل التلفزيون أو نظام الصوت). تتيح لك حزمة تطوير البرامج (SDK) لتقنية Google Cast إضافة مكونات واجهة المستخدم الضرورية بناءً على قائمة التحقق من تصميم Google Cast.

يتم توفير قائمة التحقق من تصميم Google Cast لتسهيل تجربة المستخدم على تكنولوجيا Google Cast على جميع الأنظمة الأساسية المتوافقة.

ما الذي سنبنيه؟

عند الانتهاء من هذا الدرس التطبيقي حول الترميز، سيتوفّر لك تطبيق فيديو على نظام التشغيل iOS يتيح بث الفيديوهات على جهاز Google Cast.

ما ستتعرَّف عليه

  • كيفية إضافة حزمة تطوير البرامج (SDK) لتقنية Google Cast إلى نموذج تطبيق فيديو
  • كيفية إضافة زر البث لاختيار جهاز Google Cast
  • كيفية توصيل جهاز بث وتشغيل جهاز استقبال الوسائط
  • كيفية بث فيديو
  • كيفية إضافة وحدة تحكُّم صغيرة تعمل بتكنولوجيا Google Cast إلى تطبيقك
  • كيفية إضافة وحدة تحكُّم موسّعة
  • كيفية توفير عنصر تمهيدي على سطح الفيديو.
  • كيفية تخصيص تطبيقات Google Cast
  • كيفية دمج Cast Connect

المتطلبات

  • أحدث إصدار من Xcode.
  • جهاز جوّال واحد يعمل بنظام التشغيل iOS 9 أو إصدار أحدث (أو مُحاكي Xcode).
  • كابل بيانات USB لتوصيل جهازك الجوّال بجهاز كمبيوتر التطوير (في حالة استخدام جهاز).
  • جهاز Google Cast، مثل Chromecast أو Android TV تم ضبطه على الاتصال بالإنترنت
  • تلفزيون أو شاشة بها منفذ إدخال HDMI
  • يجب توفُّر جهاز "Chromecast مع Google TV" لاختبار دمج Cast Connect، ولكنّه اختياري خلال باقي الدرس التطبيقي حول الترميز. إذا لم يكن لديك جهاز، يمكنك تخطي خطوة إضافة دعم Cast Connect في نهاية هذا البرنامج التعليمي.

التجربة

  • يجب أن تكون لديك معرفة سابقة بتطوير iOS.
  • ستحتاج أيضًا إلى معرفة سابقة بمشاهدة التلفزيون :)

كيف ستستخدم هذا البرنامج التعليمي؟

القراءة فقط اقرأه وإكمال التمارين

ما هو تقييمك لتجربتك في إنشاء تطبيقات iOS؟

مبتدئ متوسط مختص

ما هو تقييمك لتجربتك في مشاهدة التلفزيون؟

مبتدئ متوسط مختص

2. الحصول على الرمز النموذجي

يمكنك تنزيل نموذج الرمز بالكامل على الكمبيوتر...

وفك ضغط الملف الذي تم تنزيله.

3. تشغيل نموذج التطبيق

شعار Apple iOS

أولاً، دعنا نرى الشكل الذي يبدو عليه نموذج التطبيق المكتمل. التطبيق مشغّل فيديو أساسي. ويمكن للمستخدم اختيار فيديو من القائمة وتشغيله محليًا على الجهاز أو بثّه على جهاز Google Cast.

بعد تنزيل التعليمة البرمجية، تصف الإرشادات التالية كيفية فتح نموذج التطبيق المكتمل وتشغيله في Xcode:

الأسئلة الشائعة

عملية إعداد CocoaPods

لإعداد CocoaPods، انتقِل إلى وحدة التحكّم وثبِّتها باستخدام رمز Ruby التلقائي المتوفّر على نظام التشغيل macOS:

sudo gem install cocoapods

إذا واجهت أي مشاكل، راجِع المستندات الرسمية لتنزيل أداة إدارة التبعية وتثبيتها.

إعداد المشروع

  1. انتقِل إلى الوحدة الطرفية وانتقِل إلى دليل الدرس التطبيقي حول الترميز.
  2. قم بتثبيت التبعيات من Podfile.
cd app-done
pod update
pod install
  1. افتح Xcode واختَر فتح مشروع آخر...
  2. اختَر الملف CastVideos-ios.xcworkspace من الدليل رمز المجلدapp-done في مجلد نموذج الرموز.

تشغيل التطبيق

اختر الاستهداف والمحاكي، ثم شغِّل التطبيق:

شريط أدوات مُحاكي تطبيق XCode

من المفترض أن يظهر تطبيق الفيديو بعد بضع ثوانٍ.

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

مربّع حوار تأكيد يطلب الإذن لقبول الاتصالات الواردة بالشبكة

انقر على زر الإرسال واختَر جهاز Google Cast.

اختَر فيديو وانقر على زر التشغيل.

سيبدأ تشغيل الفيديو على جهاز Google Cast.

سيتم عرض وحدة التحكّم الموسّعة. يمكنك استخدام زر التشغيل/الإيقاف المؤقت للتحكّم في عملية التشغيل.

انتقل مجددًا إلى قائمة الفيديوهات.

في أسفل الشاشة، يمكن الآن رؤية وحدة تحكّم مصغّرة.

صورة توضيحية لهاتف iPhone يشغّل تطبيق Castالفيديوهات مع وحدة تحكّم مصغّرة تظهر في الأسفل

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

انقر على زر البث لإيقاف البث على جهاز Google Cast.

4. تجهيز المشروع لبدء المشروع

صورة توضيحية لهاتف iPhone يشغِّل تطبيق CastVideo

نحتاج إلى إضافة دعم Google Cast إلى تطبيق البدء الذي نزّلته. في ما يلي بعض مصطلحات Google Cast التي سنستخدمها في هذا الدرس التطبيقي حول الترميز:

  • يعمل تطبيق المرسِل على جهاز جوّال أو كمبيوتر محمول
  • يتم تشغيل تطبيق جهاز الاستقبال على جهاز Google Cast.

إعداد المشروع

أنت الآن جاهز للبناء على مشروع المبتدئين باستخدام Xcode:

  1. انتقِل إلى الوحدة الطرفية وانتقِل إلى دليل الدرس التطبيقي حول الترميز.
  2. قم بتثبيت التبعيات من Podfile.
cd app-start
pod update
pod install
  1. افتح Xcode واختَر فتح مشروع آخر...
  2. اختَر الملف 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. إضافة زر البث

صورة توضيحية للثلث العلوي من هاتف iPhone يستخدم تطبيق Castالفيديوهات وتعرض زر البث في أعلى يسار الشاشة

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

الإعدادات

يتطلّب بدء مشروع بدء المشروع التبعيات نفسها وإعداد Xcode نفسه كما فعلت في نموذج التطبيق المكتمل. ارجع إلى ذلك القسم واتّبِع الخطوات نفسها لإضافة GoogleCast.framework إلى مشروع بدء التطبيق.

الإعداد

يحتوي إطار عمل Cast على عنصر سينغلتون عالمي، وهو GCKCastContext، الذي ينسق جميع أنشطة إطار العمل. يجب إعداد هذا الكائن في مرحلة مبكرة من دورة حياة التطبيق، ويكون ذلك عادةً من خلال طريقة application(_:didFinishLaunchingWithOptions:) في تفويض التطبيق، بحيث يمكن بدء الاستئناف التلقائي للجلسة عند إعادة تشغيل تطبيق المرسِل بشكل صحيح وبدء البحث عن الأجهزة.

يجب تقديم كائن GCKCastOptions عند إعداد GCKCastContext. تتضمّن هذه الفئة خيارات تؤثّر في سلوك إطار العمل. وأهم هذه الخيارات هو معرّف تطبيق جهاز الاستقبال الذي يُستخدم لفلترة نتائج اكتشاف جهاز البث وتشغيل تطبيق جهاز الاستقبال عند بدء جلسة البث.

وتُعدّ طريقة application(_:didFinishLaunchingWithOptions:) أيضًا مكانًا مناسبًا لإعداد تفويض التسجيل لاستلام رسائل التسجيل من إطار عمل تكنولوجيا Google 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) للبث عنصر زر البث يُسمى 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. بث محتوى الفيديو

صورة توضيحية لهاتف iPhone يشغّل تطبيق Castالفيديوهات ويعرض تفاصيل عن فيديو محدّد ("Tears of Steel") يتوفّر في أسفل الشاشة مشغّل مصغّر.

وسيتم توسيع نطاق نموذج التطبيق لتشغيل الفيديوهات عن بُعد أيضًا على جهاز بث. ولتنفيذ ذلك، نحتاج إلى الاستماع إلى الأحداث المختلفة التي أنشأها إطار عمل Cast.

بث الوسائط

بدرجة عالية، إذا أردت تشغيل الوسائط على جهاز بث، يجب إجراء ما يلي:

  1. يمكنك إنشاء عنصر GCKMediaInformation من حزمة تطوير البرامج (SDK) للإرسال لإنشاء نماذج لعنصر وسائط.
  2. يتصل المستخدم بجهاز البث لتشغيل تطبيق جهاز الاستقبال.
  3. حمِّل الكائن GCKMediaInformation في جهاز الاستقبال وشغِّل المحتوى.
  4. تتبُّع حالة الوسائط
  5. إرسال أوامر التشغيل إلى المتلقي بناءً على تفاعلات المستخدم.

تكمن الخطوة الأولى في ربط عنصر بعنصر آخر، لأنّ السمة GCKMediaInformation تفهمها حزمة تطوير البرامج (SDK) للبث، ويمثل MediaItem عملية تضمين لعنصر وسائط في التطبيق، ويمكننا بسهولة ربط عنصر MediaItem بعنصر GCKMediaInformation. سبق أن نفّذنا الخطوة الثانية في القسم السابق. يمكنك تنفيذ الخطوة الثالثة بسهولة باستخدام حزمة تطوير البرامج (SDK) للبث.

يستخدم نموذج التطبيق MediaViewController التمييز بين التشغيل على الجهاز والتشغيل عن بُعد باستخدام هذا التعداد:

enum PlaybackMode: Int {
  case none = 0
  case local
  case remote
}

private var playbackMode = PlaybackMode.none

ليس من الضروري في هذا الدرس التطبيقي حول الترميز أن تفهم بالضبط آلية عمل نموذج منطق التشغيل بالكامل. من المهم أن تدرك أنه يجب تعديل مشغّل الوسائط في تطبيقك ليكون على دراية بموقعَي التشغيل بطريقة مشابهة.

في الوقت الحالي، يكون المشغّل المحلي دائمًا في حالة التشغيل المحلية لأنّه لا يعرف أي شيء عن حالات البثّ بعد. نحتاج إلى تحديث واجهة المستخدم استنادًا إلى عمليات انتقال الحالة التي تحدث في إطار عمل 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. يتم إنشاء الجلسات وإزالتها تلقائيًا استجابةً لإيماءات المستخدم من مربعات حوار البث.

جارٍ تحميل الوسائط

في حزمة تطوير البرامج (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
      }
    }
  }
  ...
}

يمكنك الآن تحديث العديد من الطرق الحالية لاستخدام منطق "جلسة البث" لإتاحة التشغيل عن بُعد:

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. وحدة تحكّم صغيرة

تتطلب قائمة التحقق من تصميم البث أن توفر جميع تطبيقات البث وحدة تحكم صغيرة بحيث تظهر عند انتقال المستخدم بعيدًا عن صفحة المحتوى الحالية. توفر وحدة التحكم الصغيرة إمكانية الوصول الفوري وتذكيرًا مرئيًا لجلسة البث الحالية.

رسم توضيحي للجزء السفلي من هاتف iPhone يتم تشغيل تطبيق Castالفيديوهات مع التركيز على وحدة التحكّم المصغّرة

توفّر حزمة تطوير البرامج (SDK) للبثّ شريط تحكّم 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.

صورة توضيحية لهاتف iPhone يشغّل تطبيق 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 أن يوفر تطبيق المرسل وحدة تحكم موسَّعة للوسائط التي يتم إرسالها. وحدة التحكم الموسعة هي نسخة ملء الشاشة من وحدة التحكم الصغيرة.

صورة توضيحية لهاتف iPhone يشغّل تطبيق Castvideos وهو يشغِّل فيديو ويظهر في الأسفل وحدة التحكّم الموسّعة

تتيح وحدة التحكم الموسّعة عرض ملء الشاشة مما يتيح التحكم الكامل في تشغيل الوسائط عن بُعد. من المفترض أن تسمح طريقة العرض هذه لتطبيق البث بإدارة كل جانب يمكن إدارته في جلسة البث، باستثناء التحكّم في مستوى صوت جهاز الاستقبال ودورة حياة الجلسة (التوصيل/إيقاف البث). وتوفّر أيضًا جميع معلومات الحالة حول جلسة تشغيل الوسائط (العمل الفني والعنوان والعنوان الفرعي وما إلى ذلك).

يتم تنفيذ وظيفة هذا العرض بواسطة الفئة 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 Connect على البنية الأساسية للبث، حيث يعمل تطبيق Android TV كجهاز استقبال.

التبعيات

في Podfile، تأكَّد من توجيه google-cast-sdk إلى 4.4.8 أو مستوى أعلى كما هو موضّح أدناه. إذا أجريت تعديلاً على الملف، شغِّل pod update من وحدة التحكّم لمزامنة التغيير مع مشروعك.

pod 'google-cast-sdk', '>=4.4.8'

GCKLaunchOptions

لتشغيل تطبيق Android TV، الذي يُشار إليه أيضًا باسم "جهاز استقبال Android"، يجب ضبط العلامة 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 هي سلسلة يمكن أن يحدّدها المستخدم طالما أنّ تطبيق ATV يمكنه فهمها. لا يتم إرسال 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 على كل من تطبيق الويب وجهاز استقبال Android TV، أضِف الرمز التالي في فئة MediaTableViewController.swift ضمن الدالة loadSelectedItem:

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...

استنادًا إلى تطبيق المُستلِم الذي يتم الإرسال إليه، ستطبِّق حزمة تطوير البرامج (SDK) تلقائيًا بيانات الاعتماد المذكورة أعلاه على الجلسة الجارية.

اختبار Cast Connect

خطوات تثبيت ملف APK Android TV على جهاز "Chromecast مع Google TV"

  1. ابحث عن عنوان IP لجهاز Android TV. يتوفّر هذا الخيار عادةً ضمن الإعدادات > الشبكة والإنترنت > (اسم الشبكة التي يتصل بها جهازك). سيعرض لك على اليسار التفاصيل وعنوان IP لجهازك على الشبكة.
  2. استخدِم عنوان IP لجهازك من أجل الاتصال به عبر ADB باستخدام الوحدة الطرفية:
$ adb connect <device_ip_address>:5555
  1. من النافذة الطرفية، انتقِل إلى مجلد المستوى الأعلى لنماذج الدرس التطبيقي حول الترميز التي نزّلتها في بداية هذا الدرس التطبيقي حول الترميز. مثال:
$ cd Desktop/ios_codelab_src
  1. ثبِّت ملف AP .في هذا المجلد على Android TV من خلال تنفيذ ما يلي:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. من المفترض أن تتمكن الآن من مشاهدة تطبيق باسم بث الفيديوهات في قائمة تطبيقاتك على جهاز Android TV.
  2. بعد الانتهاء، قم بإنشاء التطبيق وتشغيله على محاكي أو جهاز محمول. عند إعداد جلسة بث باستخدام جهاز Android TV، من المفترض أن يشغِّل الجهاز الآن تطبيق Android Receiver على Android TV. فعند تشغيل فيديو من مرسِل أجهزة iOS على الأجهزة الجوّالة، يجب تشغيل الفيديو على "جهاز استقبال Android" والسماح لك بالتحكم في تشغيله باستخدام جهاز التحكّم عن بُعد في جهاز Android TV.

11. تخصيص تطبيقات Google Cast

الإعداد

ابدأ بمجلد App-Done (تم التطبيق). أضِف الطريقة التالية إلى طريقة applicationDidFinishLaunchingWithOptions في ملف AppDelegate.swift.

func application(_: UIApplication,
                 didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  ...
  let styler = GCKUIStyle.sharedInstance()
  ...
}

بعد الانتهاء من تطبيق تخصيص واحد أو أكثر كما هو مذكور في بقية هذا الدرس التطبيقي حول الترميز، نفِّذ الأنماط من خلال استدعاء التعليمة البرمجية أدناه.

styler.apply()

تخصيص طرق عرض البث

يمكنك تخصيص جميع طرق العرض التي يديرها إطار عمل تطبيق Cast من خلال تطبيق إرشادات التصميم التلقائية على مستوى طرق العرض المختلفة. على سبيل المثال، دعنا نغير لون الرمز الخفيف.

styler.castViews.iconTintColor = .lightGray

يمكنك إلغاء الإعدادات التلقائية لكل شاشة إذا لزم الأمر. على سبيل المثال، لتجاوز تنسيقlightGrayColor للرمز الخفيف فقط لوحدة التحكم في الوسائط الموسّعة.

styler.castViews.mediaControl.expandedController.iconTintColor = .green

تغيير الألوان

يمكنك تخصيص لون الخلفية لجميع طرق العرض (أو بشكل فردي لكل عرض). يضبط الرمز التالي لون الخلفية على اللون الأزرق لجميع طرق العرض المتوفرة في إطار تطبيق تكنولوجيا Google 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
}

تغيير مظهر زر البث

يمكنك أيضًا تصميم تطبيقات مصغَّرة للبث باستخدام بروتوكول UIالمظهر. نسق التعليمات البرمجية التالية لـ GCKUICastButton في جميع طرق العرض التي تظهر:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. تهانينا

أصبحت تعرف الآن كيفية تفعيل تطبيق فيديو للبث باستخدام تطبيقات حزمة تطوير البرامج (SDK) للإرسال على نظام التشغيل iOS.

لمزيد من التفاصيل، يُرجى الاطّلاع على دليل مطوِّر iOS Sender.