دمج البث في تطبيق iOS

يصف دليل المطوِّر هذا كيفية إضافة دعم Google Cast إلى تطبيق المُرسِل على iOS باستخدام حزمة SDK للمرسِل لنظام iOS.

الجهاز الجوّال أو الكمبيوتر المحمول هو المرسِل الذي يتحكّم في التشغيل، وجهاز Google Cast هو جهاز الاستقبال الذي يعرض المحتوى على التلفزيون.

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

يستخدم إطار عمل المرسِل تصميمًا غير متزامن لمعاودة الاتصال لإعلام تطبيق المرسِل بالأحداث والانتقال بين الحالات المختلفة لدورة حياة تطبيق Cast.

مسار التطبيق

تصف الخطوات التالية تدفق التنفيذ النموذجي عالي المستوى لتطبيق iOS للمرسل:

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

لتحديد مشاكل المُرسِل وحلّها، عليك تفعيل تسجيل الدخول.

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

طرق الاتصال من سلسلة المحادثات الرئيسية

إعداد سياق البث

يتضمن إطار عمل Google Cast كائنًا مفردًا عامًا، يسمّى GCKCastContext، الذي ينسق جميع أنشطة إطار العمل. يجب إعداد هذا الكائن في مرحلة مبكرة من دورة حياة التطبيق، أي من خلال أسلوب -[application:didFinishLaunchingWithOptions:] لتفويض التطبيق، كي يتم تفعيل الاستئناف التلقائي للجلسة عند إعادة تشغيل تطبيق المُرسِل بشكل صحيح.

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

تُعد طريقة -[application:didFinishLaunchingWithOptions:] أيضًا مكانًا جيدًا لإعداد تفويض التسجيل لتلقي رسائل التسجيل من إطار العمل. ويمكن أن تكون هذه الميزات مفيدة لتصحيح الأخطاء وتحديد المشاكل وحلّها.

سريعة
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
الهدف-ج

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                    initWithApplicationID:kReceiverAppID];
  GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
  [GCKCastContext setSharedInstanceWithOptions:options];

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

تطبيقات Cast UX المصغّرة

توفِّر حزمة تطوير البرامج (SDK) لنظام التشغيل iOS لتكنولوجيا Cast التطبيقات المصغّرة التالية التي تتوافق مع قائمة التحقّق من تصميم Cast:

  • محتوى تمهيدي: تستخدم الفئة GCKCastContext طريقة presentCastInstructionsViewControllerOnceWithCastButton يمكن استخدامها لتسليط الضوء على زر "البثّ" في المرة الأولى التي يتوفّر فيها جهاز استقبال الويب. يمكن لتطبيق المرسل تخصيص النص وموضع نص العنوان والزر "إغلاق".

  • زر البث: بدءًا من الإصدار 4.6.0 من حزمة تطوير البرامج (SDK) للمرسِل بنظام iOS، يكون زر البث مرئيًا دائمًا عندما يكون جهاز المُرسِل متصلاً بشبكة Wi-Fi. في المرة الأولى التي ينقر فيها المستخدم على زر "إرسال" بعد بدء تشغيل التطبيق للمرة الأولى، يظهر مربّع حوار الأذونات حتى يتمكّن المستخدم من منح التطبيق إذن الوصول إلى الشبكة المحلية للأجهزة على الشبكة. وعندما ينقر المستخدم على زر البث، يتم عرض مربّع حوار يتضمّن الأجهزة التي تم اكتشافها. عندما ينقر المستخدم على زر البث أثناء توصيل الجهاز، يعرض البيانات الوصفية الحالية للوسائط (مثل العنوان واسم استوديو التسجيل والصورة المصغّرة) أو يسمح للمستخدم بقطع الاتصال بجهاز البث. عندما ينقر المستخدم على زر البث في حال عدم توفّر أي أجهزة، ستظهر شاشة تعرض للمستخدم معلومات عن سبب عدم العثور على الأجهزة وكيفية استكشاف الأخطاء وإصلاحها.

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

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

إضافة زر البث

يوفّر إطار العمل مكوّن زر البث كفئة فرعية UIButton. ويمكن إضافته إلى شريط العناوين في التطبيق من خلال التفافه في UIBarButtonItem. يمكن للفئة الفرعية UIViewController النموذجية تثبيت زر البث على النحو التالي:

سريعة
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
الهدف-ج
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

سيؤدي النقر على الزر بشكل تلقائي إلى فتح مربّع الحوار "إرسال" المقدَّم من إطار العمل.

يمكن أيضًا إضافة GCKUICastButton إلى لوحة العمل مباشرةً.

ضبط ميزة "اكتشاف الأجهزة"

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

تتم إدارة عملية الاكتشاف في إطار العمل من خلال الفئة GCKDiscoveryManager، وهي سمة من سمات GCKCastContext. يوفر إطار العمل مكوِّنًا تلقائيًا لمربع حوار "البث" لاختيار الأجهزة والتحكم فيها. يتم ترتيب قائمة الأجهزة على نحو معجم حسب الاسم المتوافق مع الجهاز.

آلية عمل إدارة الجلسات

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

تتم إدارة الجلسات من خلال الفئة GCKSessionManager، وهي سمة من سمات GCKCastContext. يتم تمثيل الجلسات الفردية بفئات فرعية للفئة GCKSession: على سبيل المثال، تمثّل GCKCastSession الجلسات التي تتضمّن أجهزة بث. يمكنك الوصول إلى جلسة البثّ النشطة حاليًا (إن وُجدت)، باعتبارها خاصية currentCastSession في GCKSessionManager.

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

إذا كان مربع الحوار "إرسال" قيد الاستخدام، فسيتم إنشاء الجلسات وقطعها تلقائيًا استجابةً لإيماءات المستخدم. وبخلاف ذلك، يمكن للتطبيق بدء الجلسات وإنهائها بشكل صريح من خلال إحدى الطرق على GCKSessionManager.

إذا كان التطبيق يحتاج إلى إجراء معالجة خاصة استجابةً لأحداث مراحل نشاط الجلسة، يمكنه تسجيل حدث GCKSessionManagerListener واحد أو أكثر باستخدام GCKSessionManager. GCKSessionManagerListener هو بروتوكول يعرّف عمليات معاودة الاتصال لمثل هذه الأحداث، مثل بدء الجلسة وانتهاء الجلسة وما إلى ذلك.

نقل البث

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

للحصول على جهاز الوجهة الجديد أثناء نقل البيانات، استخدِم السمة GCKCastSession#device أثناء عملية معاودة الاتصال [sessionManager:didResumeCastSession:].

يُرجى الاطّلاع على نقل البث على جهاز استقبال الويب للحصول على مزيد من المعلومات.

إعادة الاتصال تلقائيًا

يضيف إطار عمل Google Cast منطق إعادة الاتصال للتعامل تلقائيًا مع إعادة الربط في العديد من الحالات الدقيقة، مثل:

  • استرداد البيانات بعد فقدان شبكة Wi-Fi مؤقتًا
  • استرداد البيانات بعد إيقاف وضع السكون للجهاز
  • استرداد البيانات بعد تشغيل التطبيق في الخلفية
  • استرداد الحساب في حال تعطُّل التطبيق

آلية عمل التحكّم في الوسائط

إذا تم إنشاء جلسة بث باستخدام تطبيق استقبال ويب يتوافق مع مساحة اسم الوسائط، سيتم إنشاء مثيل GCKRemoteMediaClient تلقائيًا من خلال إطار العمل، ويمكن الوصول إليه باعتباره السمة remoteMediaClient للمثيل GCKCastSession.

في كل طرق إصدار الطلبات في GCKRemoteMediaClient إلى مستقبل الويب، يتم عرض عنصر GCKRequest يمكن استخدامه لتتبُّع هذا الطلب. يمكن تخصيص GCKRequestDelegate لهذا العنصر لتلقّي إشعارات حول النتيجة النهائية للعملية.

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

ضبط البيانات الوصفية للوسائط

تمثّل الفئة GCKMediaMetadata معلومات عن ملف الوسائط الذي تريد بثه. ينشئ المثال التالي مثيلاً جديدًا من GCKMediaMetadata لفيلم ويحدّد العنوان والعنوان الفرعي واسم استوديو التسجيل وصورتين.

سريعة
let metadata = GCKMediaMetadata()
metadata.setString("Big Buck Bunny (2008)", forKey: kGCKMetadataKeyTitle)
metadata.setString("Big Buck Bunny tells the story of a giant rabbit with a heart bigger than " +
  "himself. When one sunny day three rodents rudely harass him, something " +
  "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon " +
  "tradition he prepares the nasty rodents a comical revenge.",
                   forKey: kGCKMetadataKeySubtitle)
metadata.addImage(GCKImage(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg")!,
                           width: 480,
                           height: 360))
الهدف-ج
GCKMediaMetadata *metadata = [[GCKMediaMetadata alloc]
                                initWithMetadataType:GCKMediaMetadataTypeMovie];
[metadata setString:@"Big Buck Bunny (2008)" forKey:kGCKMetadataKeyTitle];
[metadata setString:@"Big Buck Bunny tells the story of a giant rabbit with a heart bigger than "
 "himself. When one sunny day three rodents rudely harass him, something "
 "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon "
 "tradition he prepares the nasty rodents a comical revenge."
             forKey:kGCKMetadataKeySubtitle];
[metadata addImage:[[GCKImage alloc]
                    initWithURL:[[NSURL alloc] initWithString:@"https://commondatastorage.googleapis.com/"
                                 "gtv-videos-bucket/sample/images/BigBuckBunny.jpg"]
                    width:480
                    height:360]];

راجِع قسم اختيار الصور والتخزين المؤقت حول استخدام الصور مع البيانات الوصفية للوسائط.

تحميل الوسائط

لتحميل ملف وسائط، يمكنك إنشاء مثيل GCKMediaInformation باستخدام البيانات الوصفية للوسائط. بعد ذلك، يمكنك الحصول على GCKCastSession الحالي واستخدام GCKRemoteMediaClient لتحميل الوسائط على تطبيق الاستقبال. يمكنك بعد ذلك استخدام GCKRemoteMediaClient للتحكّم في تطبيق مشغّل وسائط يعمل على جهاز الاستقبال، مثلاً للتشغيل والإيقاف المؤقت والإيقاف.

سريعة
let url = URL.init(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
guard let mediaURL = url else {
  print("invalid mediaURL")
  return
}

let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL)
mediaInfoBuilder.streamType = GCKMediaStreamType.none;
mediaInfoBuilder.contentType = "video/mp4"
mediaInfoBuilder.metadata = metadata;
mediaInformation = mediaInfoBuilder.build()

guard let mediaInfo = mediaInformation else {
  print("invalid mediaInformation")
  return
}

if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInfo) {
  request.delegate = self
}
الهدف-ج
GCKMediaInformationBuilder *mediaInfoBuilder =
  [[GCKMediaInformationBuilder alloc] initWithContentURL:
   [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"]];
mediaInfoBuilder.streamType = GCKMediaStreamTypeNone;
mediaInfoBuilder.contentType = @"video/mp4";
mediaInfoBuilder.metadata = metadata;
self.mediaInformation = [mediaInfoBuilder build];

GCKRequest *request = [self.sessionManager.currentSession.remoteMediaClient loadMedia:self.mediaInformation];
if (request != nil) {
  request.delegate = self;
}

راجِع أيضًا القسم الذي يتناول استخدام مقاطع الوسائط.

تنسيق فيديو بدقة 4K

لتحديد تنسيق الفيديو للوسائط، استخدِم السمة videoInfo في GCKMediaStatus للحصول على المثيل الحالي من GCKVideoInfo. يحتوي هذا المثال على نوع تنسيق تلفزيون عالي الديناميكية والارتفاع والعرض بالبكسل. يُشار إلى صيغ تنسيق 4K في السمة hdrType من خلال قيم التعداد GCKVideoInfoHDRType.

إضافة وحدات تحكُّم مصغّرة

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

يوفّر إطار عمل البث شريط تحكُّم، GCKUIMiniMediaControlsViewController، يمكن إضافته إلى المشاهد التي تريد عرض وحدة التحكّم المصغّرة فيها.

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

راجِع تخصيص واجهة مستخدم iOS Sender لمعرفة الطريقة التي يمكن بها لتطبيق المرسِل ضبط مظهر تطبيقات البثّ.

هناك طريقتان لإضافة وحدة التحكم المصغّرة إلى تطبيق المرسِل:

  • اسمح لإطار عمل Google Cast بإدارة تصميم وحدة التحكم المصغَّرة من خلال إضافة وحدة تحكم في العرض الحالية إلى وحدة التحكم في العرض.
  • يمكنك إدارة تخطيط أداة وحدة التحكم المصغَّرة بنفسك عن طريق إضافتها إلى وحدة التحكم في العرض الحالية من خلال توفير عرض فرعي في مخطط القصة.

التفاف باستخدام GCKUICastContainerViewController

الطريقة الأولى هي استخدام GCKUICastContainerViewController الذي يلتف وحدة تحكم عرض أخرى ويضيف GCKUIMiniMediaControlsViewController في الجزء السفلي. يقتصر هذا الأسلوب على أنه لا يمكنك تخصيص الرسوم المتحركة ولا يمكن تهيئة سلوك وحدة التحكم في عرض الحاوية.

يتم تنفيذ هذه الطريقة الأولى عادةً من خلال طريقة -[application:didFinishLaunchingWithOptions:] لتفويض التطبيق:

سريعة
func applicationDidFinishLaunching(_ application: UIApplication) {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
  let castContainerVC =
          GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window!.rootViewController = castContainerVC
  window!.makeKeyAndVisible()

  ...
}
الهدف-ج
- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  UIStoryboard *appStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  UINavigationController *navigationController =
          [appStoryboard instantiateViewControllerWithIdentifier:@"MainNavigation"];
  GCKUICastContainerViewController *castContainerVC =
          [[GCKCastContext sharedInstance] createCastContainerControllerForViewController:navigationController];
  castContainerVC.miniMediaControlsItemEnabled = YES;
  self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
  self.window.rootViewController = castContainerVC;
  [self.window makeKeyAndVisible];
  ...

}
Swift
var castControlBarsEnabled: Bool {
  set(enabled) {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      castContainerVC.miniMediaControlsItemEnabled = enabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
    }
  }
  get {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      return castContainerVC.miniMediaControlsItemEnabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
      return false
    }
  }
}
الهدف-ج

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, assign) BOOL castControlBarsEnabled;

@end

AppDelegate.m

@implementation AppDelegate

...

- (void)setCastControlBarsEnabled:(BOOL)notificationsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  castContainerVC.miniMediaControlsItemEnabled = notificationsEnabled;
}

- (BOOL)castControlBarsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  return castContainerVC.miniMediaControlsItemEnabled;
}

...

@end

التضمين في وحدة التحكّم في العرض الحالية

والطريقة الثانية هي إضافة وحدة التحكم الصغيرة مباشرةً إلى عنصر التحكم في العرض الحالي باستخدام createMiniMediaControlsViewController لإنشاء مثيل GCKUIMiniMediaControlsViewController ثم إضافته إلى وحدة التحكم في عرض الحاوية كعرض فرعي.

إعداد وحدة التحكم في العرض من خلال تفويض التطبيق:

سريعة
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  ...

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
  window?.clipsToBounds = true

  let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
  rootContainerVC?.miniMediaControlsViewEnabled = true

  ...

  return true
}
الهدف-ج
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  self.window.clipsToBounds = YES;

  RootContainerViewController *rootContainerVC;
  rootContainerVC =
      (RootContainerViewController *)self.window.rootViewController;
  rootContainerVC.miniMediaControlsViewEnabled = YES;

  ...

  return YES;
}

في وحدة التحكم في طريقة العرض الجذر، أنشئ مثيلاً GCKUIMiniMediaControlsViewController وأضفه إلى وحدة التحكم في طريقة عرض الحاوية كعرض فرعي:

سريعة
let kCastControlBarsAnimationDuration: TimeInterval = 0.20

@objc(RootContainerViewController)
class RootContainerViewController: UIViewController, GCKUIMiniMediaControlsViewControllerDelegate {
  @IBOutlet weak private var _miniMediaControlsContainerView: UIView!
  @IBOutlet weak private var _miniMediaControlsHeightConstraint: NSLayoutConstraint!
  private var miniMediaControlsViewController: GCKUIMiniMediaControlsViewController!
  var miniMediaControlsViewEnabled = false {
    didSet {
      if self.isViewLoaded {
        self.updateControlBarsVisibility()
      }
    }
  }

  var overriddenNavigationController: UINavigationController?

  override var navigationController: UINavigationController? {

    get {
      return overriddenNavigationController
    }

    set {
      overriddenNavigationController = newValue
    }
  }
  var miniMediaControlsItemEnabled = false

  override func viewDidLoad() {
    super.viewDidLoad()
    let castContext = GCKCastContext.sharedInstance()
    self.miniMediaControlsViewController = castContext.createMiniMediaControlsViewController()
    self.miniMediaControlsViewController.delegate = self
    self.updateControlBarsVisibility()
    self.installViewController(self.miniMediaControlsViewController,
                               inContainerView: self._miniMediaControlsContainerView)
  }

  func updateControlBarsVisibility() {
    if self.miniMediaControlsViewEnabled && self.miniMediaControlsViewController.active {
      self._miniMediaControlsHeightConstraint.constant = self.miniMediaControlsViewController.minHeight
      self.view.bringSubview(toFront: self._miniMediaControlsContainerView)
    } else {
      self._miniMediaControlsHeightConstraint.constant = 0
    }
    UIView.animate(withDuration: kCastControlBarsAnimationDuration, animations: {() -> Void in
      self.view.layoutIfNeeded()
    })
    self.view.setNeedsLayout()
  }

  func installViewController(_ viewController: UIViewController?, inContainerView containerView: UIView) {
    if let viewController = viewController {
      self.addChildViewController(viewController)
      viewController.view.frame = containerView.bounds
      containerView.addSubview(viewController.view)
      viewController.didMove(toParentViewController: self)
    }
  }

  func uninstallViewController(_ viewController: UIViewController) {
    viewController.willMove(toParentViewController: nil)
    viewController.view.removeFromSuperview()
    viewController.removeFromParentViewController()
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "NavigationVCEmbedSegue" {
      self.navigationController = (segue.destination as? UINavigationController)
    }
  }

...
الهدف-ج

RootContainerViewController.h

static const NSTimeInterval kCastControlBarsAnimationDuration = 0.20;

@interface RootContainerViewController () <GCKUIMiniMediaControlsViewControllerDelegate> {
  __weak IBOutlet UIView *_miniMediaControlsContainerView;
  __weak IBOutlet NSLayoutConstraint *_miniMediaControlsHeightConstraint;
  GCKUIMiniMediaControlsViewController *_miniMediaControlsViewController;
}

@property(nonatomic, weak, readwrite) UINavigationController *navigationController;

@property(nonatomic, assign, readwrite) BOOL miniMediaControlsViewEnabled;
@property(nonatomic, assign, readwrite) BOOL miniMediaControlsItemEnabled;

@end

RootContainerViewController.m

@implementation RootContainerViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  GCKCastContext *castContext = [GCKCastContext sharedInstance];
  _miniMediaControlsViewController =
      [castContext createMiniMediaControlsViewController];
  _miniMediaControlsViewController.delegate = self;

  [self updateControlBarsVisibility];
  [self installViewController:_miniMediaControlsViewController
              inContainerView:_miniMediaControlsContainerView];
}

- (void)setMiniMediaControlsViewEnabled:(BOOL)miniMediaControlsViewEnabled {
  _miniMediaControlsViewEnabled = miniMediaControlsViewEnabled;
  if (self.isViewLoaded) {
    [self updateControlBarsVisibility];
  }
}

- (void)updateControlBarsVisibility {
  if (self.miniMediaControlsViewEnabled &&
      _miniMediaControlsViewController.active) {
    _miniMediaControlsHeightConstraint.constant =
        _miniMediaControlsViewController.minHeight;
    [self.view bringSubviewToFront:_miniMediaControlsContainerView];
  } else {
    _miniMediaControlsHeightConstraint.constant = 0;
  }
  [UIView animateWithDuration:kCastControlBarsAnimationDuration
                   animations:^{
                     [self.view layoutIfNeeded];
                   }];
  [self.view setNeedsLayout];
}

- (void)installViewController:(UIViewController *)viewController
              inContainerView:(UIView *)containerView {
  if (viewController) {
    [self addChildViewController:viewController];
    viewController.view.frame = containerView.bounds;
    [containerView addSubview:viewController.view];
    [viewController didMoveToParentViewController:self];
  }
}

- (void)uninstallViewController:(UIViewController *)viewController {
  [viewController willMoveToParentViewController:nil];
  [viewController.view removeFromSuperview];
  [viewController removeFromParentViewController];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([segue.identifier isEqualToString:@"NavigationVCEmbedSegue"]) {
    self.navigationController =
        (UINavigationController *)segue.destinationViewController;
  }
}

...

@end

ويخبر GCKUIMiniMediaControlsViewControllerDelegate وحدة التحكّم في عرض المضيف بالوقت الذي من المفترض أن تكون فيه وحدة التحكّم المصغَّرة مرئية:

سريعة
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
الهدف-ج
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

إضافة وحدة تحكُّم موسّعة

تتطلب قائمة التحقّق من تصميم Google Cast أن يوفّر تطبيق المُرسِل وحدة تحكُّم موسّعة لتشغيل الوسائط التي يتم بثّها. وحدة التحكم الموسعة هي إصدار ملء الشاشة من وحدة التحكم الصغيرة.

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

يتم تنفيذ وظيفة هذا الملف الشخصي من خلال الفئة GCKUIExpandedMediaControlsViewController.

أول شيء عليك فعله هو تفعيل وحدة التحكم الموسّعة التلقائية في سياق البث. تعديل تفويض التطبيق لتفعيل وحدة التحكّم الموسّعة التلقائية:

سريعة
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
الهدف-ج
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

أضف الرمز التالي إلى وحدة التحكم في العرض لتحميل وحدة التحكم الموسّعة عندما يبدأ المستخدم ببث مقطع فيديو:

سريعة
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

  // Load your media
  sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation)
}
الهدف-ج
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

  // Load your media
  [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation];
}

سيتم تشغيل وحدة التحكم الموسعة تلقائيًا أيضًا عندما ينقر المستخدم على وحدة التحكم الصغيرة.

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

راجِع تطبيق الأنماط المخصّصة على تطبيق iOS لمعرفة كيفية ضبط تطبيق المُرسِل لمظهر أدوات البثّ.

التحكم في مستوى الصوت

يدير إطار عمل Google Cast مستوى صوت تطبيق المرسِل تلقائيًا. تتم مزامنة إطار العمل تلقائيًا مع مستوى صوت جهاز استقبال الويب لأدوات واجهة المستخدم التي يتم توفيرها. لمزامنة شريط تمرير يوفّره التطبيق، استخدِم GCKUIDeviceVolumeController.

التحكّم في مستوى الصوت للزرّ الفعلي

يمكن استخدام أزرار التحكّم بمستوى الصوت الفعلية على جهاز المُرسِل لتغيير مستوى صوت جلسة البث على الجهاز الاستقبال على الويب باستخدام علامة physicalVolumeButtonsWillControlDeviceVolume على GCKCastOptions، المضبوطة على GCKCastContext.

سريعة
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
الهدف-ج
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

التعامل مع الأخطاء

من المهم جدًا لتطبيقات المرسِلين التعامل مع جميع عمليات معاودة الاتصال بالأخطاء وتحديد أفضل استجابة لكل مرحلة من مراحل دورة حياة Cast. يمكن للتطبيق عرض مربعات حوار الأخطاء للمستخدم أو يمكنه اختيار إنهاء جلسة البث.

التسجيل

GCKLogger هو مفردات يُستخدم للتسجيل بواسطة إطار العمل. استخدِم GCKLoggerDelegate لتخصيص كيفية التعامل مع رسائل السجلّ.

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

سريعة
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    ...

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
الهدف-ج

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

لتفعيل رسائل تصحيح الأخطاء والرسائل المطوَّلة أيضًا، أضِف هذا السطر إلى الرمز البرمجي بعد إعداد التفويض (الموضَّح سابقًا):

سريعة
let filter = GCKLoggerFilter.init()
filter.minimumLevel = GCKLoggerLevel.verbose
GCKLogger.sharedInstance().filter = filter
الهدف-ج
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

يمكنك أيضًا فلترة رسائل السجلّ الناتجة عن GCKLogger. اضبط الحد الأدنى لمستوى التسجيل لكل صف، على سبيل المثال:

سريعة
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
الهدف-ج
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

ويمكن أن تكون أسماء الفئات إما أسماء حرفية أو أنماط كروية، مثل GCKUI\* وGCK\*Session.