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

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

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

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

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

مسار التطبيق

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

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

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

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

طرق الاتصال من سلسلة التعليمات الرئيسية

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

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

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

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

تطبيق Swift
@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

تطبيقات Google Cast المصغَّرة لتجربة المستخدم

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

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

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

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

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

إضافة زر بث

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

تطبيق Swift
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) الخاصة بالبثّ مفهوم جلسة Google Cast، وهي تجمع بين خطوات الاتصال بجهاز وإطلاق (أو ربط) تطبيق جهاز استقبال الويب والاتصال بهذا التطبيق وإعداد قناة التحكّم في الوسائط. يمكنك الاطّلاع على دليل دورة حياة التطبيق لمزيد من المعلومات حول جلسات البثّ ودورة حياة جهاز استقبال الويب.

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

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

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

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

نقل البث

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

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

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

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

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

  • الاسترداد بعد فقدان مؤقت لشبكة WiFi
  • استرداد البيانات بعد وضع الجهاز في وضع السكون
  • استرداد البيانات بعد استخدام التطبيق في الخلفية
  • الاسترداد إذا تعطّل التطبيق

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

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

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

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

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

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

تطبيق Swift
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 للتحكّم في تطبيق مشغّل وسائط يعمل على جهاز الاستقبال، مثلاً لتشغيل الوسائط وإيقافها مؤقتًا وإيقافها.

تطبيق Swift
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. يحتوي هذا المثيل على نوع تنسيق HDR TV والارتفاع والعرض بالبكسل. تتم الإشارة إلى صيغ تنسيق 4K في السمة hdrType من خلال قيم التعداد GCKVideoInfoHDRType.

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

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

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

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

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

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

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

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

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

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

تطبيق Swift
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، ثم إضافته إلى وحدة التحكّم في عرض الحاوية كعرض فرعي.

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

تطبيق Swift
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 وأضِفه إلى وحدة التحكّم في عرض الحاوية كعرض فرعي:

تطبيق Swift
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 وحدة التحكّم في عرض المضيف بالوقت الذي يجب أن تكون فيه وحدة التحكّم المصغّرة مرئية:

تطبيق Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
الهدف-ج
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

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

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

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

يتم تنفيذ وظيفة طريقة العرض هذه من خلال الفئة GCKUIExpandedMediaControlsViewController.

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

تطبيق Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

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

تطبيق Swift
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 المصغّرة.

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

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

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

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

تطبيق Swift
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];

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

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

التسجيل

GCKLogger عبارة عن نغمة مفردة تُستخدَم للتسجيل من خلال إطار العمل. استخدِم GCKLoggerDelegate لتخصيص طريقة التعامل مع رسائل السجلّ.

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

تطبيق Swift
@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

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

تطبيق Swift
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. اضبط الحد الأدنى لمستوى التسجيل لكل صف، مثل:

تطبيق Swift
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.