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

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

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

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

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

مسار التطبيق

توضّح الخطوات التالية خطوات التنفيذ العادية العالية المستوى لتطبيق iOS المرسِل:

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

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

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

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

بدء سياق Cast

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

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

تُعدّ طريقة -[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)
    }
  }
}
Objective-C

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

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

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

  • زر البث: بدءًا من الإصدار 4.6.0 من حزمة تطوير البرامج (SDK) الخاصة بجهاز الإرسال في Cast 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)
Objective-C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

By default, tapping the button will open the Cast dialog that is provided by the framework.

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

ضبط إعدادات اكتشاف الأجهزة

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

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

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

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

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

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

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

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

إعادة توجيه البث

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

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

لمزيد من المعلومات، يمكنك الاطّلاع على إعادة توجيه البث على Web Receiver.

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

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

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

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

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

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

من المتوقّع أن تتم مشاركة مثيل GCKRemoteMediaClient من خلال أجزاء متعددة من التطبيق، وبالفعل تشارك بعض المكونات الداخلية للإطار، مثل مربّع حوار Cast وعناصر التحكّم المصغّرة في الوسائط، المثيل. لهذا الغرض، يتيح 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))
Objective-C
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
}
Objective-C
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 والارتفاع والعرض بالبكسل. تتم الإشارة إلى خيارات تنسيق 4K في السمة hdrType من خلال قيم التعداد GCKVideoInfoHDRType.

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

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

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

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

يمكنك الاطّلاع على تخصيص واجهة مستخدم تطبيق الإرسال على iOS لمعرفة كيف يمكن لتطبيق الإرسال ضبط مظهر تطبيقات Cast المصغّرة.

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

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

الالتفاف باستخدام 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()

  ...
}
Objective-C
- (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
    }
  }
}
Objective-C

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
}
Objective-C
- (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)
    }
  }

...
Objective-C

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()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

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

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

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

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

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

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
Objective-C
- (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)
}
Objective-C
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

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

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

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

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

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

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

التحكّم في مستوى الصوت باستخدام الأزرار الخارجية

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

Swift
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Objective-C
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

معالجة الأخطاء

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

يُرجى العِلم أنّ بعض الأخطاء، بما في ذلك GCKErrorCode GCKErrorCodeCancelled، هي سلوك مقصود.

لا تحاول إعادة محاولة الاتصال الذي تعذّر باستخدام GCKErrorCodeCancelled. وقد يؤدي ذلك إلى حدوث سلوك غير متوقّع.

التسجيل

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)
    }
  }
}
Objective-C

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
Objective-C
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
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

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