يوضّح دليل المطوّرين هذا كيفية إضافة ميزة Google Cast إلى تطبيقك على جهاز iOS الذي يرسل المحتوى باستخدام حزمة تطوير البرامج (SDK) الخاصة بتطبيقات iOS التي ترسل المحتوى.
الجهاز الجوّال أو الكمبيوتر المحمول هو المرسِل الذي يتحكّم في التشغيل، وجهاز Google Cast هو المستلِم الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى ملفات ثنائية لمكتبة فئات Cast والموارد المرتبطة بها المتوفّرة في وقت التشغيل على جهاز المُرسِل. يشير تطبيق المرسِل أو تطبيق البث إلى تطبيق يعمل أيضًا على جهاز المرسِل. يشير تطبيق Web Receiver إلى تطبيق HTML الذي يتم تشغيله على Web Receiver.
يستخدم إطار عمل المرسِل تصميم معاودة الاتصال غير المتزامن لإبلاغ تطبيق المرسِل بالأحداث والانتقال بين حالات مختلفة من دورة حياة تطبيق Cast.
تدفّق التطبيق
توضّح الخطوات التالية مسار التنفيذ النموذجي العالي المستوى لتطبيق iOS خاص بالمرسِل:
- يبدأ إطار عمل البث
GCKDiscoveryManagerاستنادًا إلى الخصائص المقدَّمة فيGCKCastOptionsلبدء البحث عن الأجهزة. - عندما ينقر المستخدم على زر "البث"، يعرض إطار العمل مربّع حوار "البث" مع قائمة بأجهزة البث التي تم العثور عليها.
- عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق Web Receiver على جهاز البث.
- يستدعي إطار العمل عمليات رد الاتصال في تطبيق المرسِل لتأكيد أنّه تم تشغيل تطبيق Web Receiver.
- ينشئ إطار العمل قناة اتصال بين المرسِل وتطبيقات Web Receiver.
- يستخدم إطار العمل قناة الاتصال لتحميل الوسائط والتحكّم في تشغيلها على Web Receiver.
- يتمكّن إطار العمل من مزامنة حالة تشغيل الوسائط بين جهاز الإرسال وWeb Receiver: عندما ينفّذ المستخدم إجراءات في واجهة مستخدم جهاز الإرسال، يمرّر إطار العمل طلبات التحكّم في الوسائط هذه إلى Web Receiver، وعندما يرسل Web Receiver آخر الأخبار عن حالة الوسائط، يعدّل إطار العمل حالة واجهة مستخدم جهاز الإرسال.
- عندما ينقر المستخدم على زر "البث" لقطع الاتصال بجهاز البث، سيقطع إطار العمل اتصال تطبيق المرسِل بـ "أداة استقبال الويب".
لتحديد المشاكل وحلّها في المُرسِل، عليك تفعيل التسجيل.
للاطّلاع على قائمة شاملة بجميع الفئات والطرق والأحداث في إطار عمل Google Cast لنظام التشغيل iOS، يُرجى الرجوع إلى مرجع Google Cast iOS API. تتناول الأقسام التالية خطوات دمج Cast في تطبيق iOS.
استدعاء الطرق من سلسلة التعليمات الرئيسية
تهيئة سياق البث
يحتوي إطار عمل Cast على عنصر فردي عام، وهو
GCKCastContext، الذي
ينسّق جميع أنشطة إطار العمل. يجب تهيئة هذا العنصر في وقت مبكر من دورة حياة التطبيق، وتحديدًا في طريقة -[application:didFinishLaunchingWithOptions:] الخاصة بمندوب التطبيق، حتى يتم تشغيل استئناف الجلسة التلقائي بشكل صحيح عند إعادة تشغيل تطبيق المرسِل.
يجب توفير عنصر GCKCastOptions عند تهيئة GCKCastContext.
يحتوي هذا الصف على خيارات تؤثّر في سلوك إطار العمل. الأهم من بينها هو معرّف تطبيق Web Receiver، والذي يُستخدَم لفلترة نتائج البحث وتشغيل تطبيق Web Receiver عند بدء جلسة Cast.
تُعدّ طريقة -[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
توفّر حزمة تطوير البرامج لنظام التشغيل iOS الخاصة بـ Cast عناصر واجهة المستخدم التالية التي تتوافق مع قائمة التحقّق من تصميم Cast:
الطبقة المتراكبة التمهيدية: يحتوي الصف
GCKCastContextعلى طريقة،presentCastInstructionsViewControllerOnceWithCastButton، يمكن استخدامها لتسليط الضوء على زر البث في المرة الأولى التي يتوفّر فيها تطبيق Web Receiver. يمكن لتطبيق المُرسِل تخصيص النص وموضع نص العنوان وزر "إغلاق".زر الإرسال: بدءًا من الإصدار 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. يوفّر إطار العمل مكوّنًا تلقائيًا لمربّع حوار Cast من أجل اختيار الجهاز والتحكّم فيه. يتم ترتيب قائمة الأجهزة معجميًا حسب الاسم المبسّط للجهاز.
طريقة عمل إدارة الجلسات
تقدّم حزمة تطوير البرامج (SDK) الخاصة بخدمة Cast مفهوم جلسة Cast، ويجمع إنشاء هذه الجلسة بين خطوات الاتصال بجهاز وتشغيل تطبيق Web Receiver (أو الانضمام إليه) والاتصال بهذا التطبيق وتهيئة قناة للتحكّم في الوسائط. اطّلِع على دليل دورة حياة التطبيق في Web Receiver للحصول على مزيد من المعلومات حول جلسات Cast ودورة حياة Web Receiver.
تتم إدارة الجلسات من خلال الصف
GCKSessionManager،
وهو سمة من سمات
GCKCastContext.
يتم تمثيل الجلسات الفردية بفئات فرعية من الفئة
GCKSession، على سبيل المثال،
GCKCastSession
تمثّل الجلسات التي تستخدم أجهزة Cast. يمكنك الوصول إلى جلسة Cast النشطة حاليًا (إن وجدت)، باعتبارها السمة currentCastSession الخاصة بالكائن GCKSessionManager.
يمكن استخدام واجهة
GCKSessionManagerListener
لمراقبة أحداث الجلسة، مثل إنشاء الجلسة وتعليقها واستئنافها وإنهاءها. يعلّق إطار العمل الجلسات تلقائيًا عندما ينتقل تطبيق المرسِل إلى الخلفية، ويحاول استئنافها عندما يعود التطبيق إلى المقدّمة (أو تتم إعادة تشغيله بعد إنهاء التطبيق بشكل غير طبيعي أو مفاجئ أثناء نشاط الجلسة).
في حال استخدام مربّع الحوار "البث"، يتم إنشاء الجلسات وإيقافها تلقائيًا استجابةً لإيماءات المستخدم. بخلاف ذلك، يمكن للتطبيق بدء الجلسات وإنهائها بشكل صريح من خلال طرق في GCKSessionManager.
إذا كان التطبيق بحاجة إلى إجراء معالجة خاصة استجابةً لأحداث دورة حياة الجلسة، يمكنه تسجيل مثيل واحد أو أكثر من GCKSessionManagerListener باستخدام GCKSessionManager. GCKSessionManagerListener هو بروتوكول يحدّد عمليات ردّ الاتصال لأحداث مثل بدء الجلسة وانتهائها وما إلى ذلك.
إعادة توجيه البث
يُعدّ الحفاظ على حالة الجلسة أساس عملية إعادة توجيه البث، حيث يمكن للمستخدمين نقل بث الصوت والفيديو الحاليين بين الأجهزة باستخدام الطلبات الصوتية أو تطبيق Google Home أو الشاشات الذكية. يتوقف تشغيل الوسائط على أحد الأجهزة (الجهاز المصدر) ويستمر على جهاز آخر (الجهاز الوجهة). يمكن لأي جهاز بث مزوّد بأحدث البرامج الثابتة أن يعمل كمصدر أو وجهة لإعادة توجيه البث.
للحصول على الجهاز الوجهة الجديد أثناء إعادة توجيه البث، استخدِم السمة
GCKCastSession#device
أثناء معاودة الاتصال
[sessionManager:didResumeCastSession:].
لمزيد من المعلومات، يُرجى الاطّلاع على مقالة إعادة توجيه البث على تطبيق Web Receiver.
إعادة الاتصال تلقائيًا
يضيف إطار عمل Cast منطق إعادة الاتصال للتعامل تلقائيًا مع إعادة الاتصال في العديد من الحالات الدقيقة، مثل:
- استرداد البيانات بعد فقدان اتصال Wi-Fi مؤقتًا
- الخروج من وضع السكون
- الاسترداد من الخلفية
- الاسترداد في حال تعطُّل التطبيق
طريقة عمل التحكّم في الوسائط
إذا تم إنشاء جلسة Cast باستخدام تطبيق Web Receiver يتوافق مع مساحة اسم الوسائط، سيتم إنشاء مثيل من GCKRemoteMediaClient تلقائيًا بواسطة إطار العمل، ويمكن الوصول إليه كسمة remoteMediaClient الخاصة بمثيل GCKCastSession.
ستعرض جميع الطرق في GCKRemoteMediaClient التي ترسل طلبات إلى "برنامج استقبال الويب" كائن GCKRequest يمكن استخدامه لتتبُّع هذا الطلب. يمكن تعيين
GCKRequestDelegate
لهذا العنصر لتلقّي إشعارات بشأن النتيجة النهائية للعملية.
من المتوقّع أن تتم مشاركة مثيل GCKRemoteMediaClient
بين أجزاء متعددة من التطبيق، وبالفعل، تشارك بعض المكوّنات الداخلية
لإطار العمل، مثل مربّع حوار Cast وعناصر التحكّم المصغّرة في الوسائط، المثيل. لذلك، يتيح 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.
يحتوي هذا العنصر على نوع تنسيق HDR TV والارتفاع والعرض بالبكسل. يتم الإشارة إلى خيارات تنسيق 4K في السمة hdrType من خلال قيم التعداد GCKVideoInfoHDRType.
إضافة وحدات تحكّم مصغّرة
وفقًا لقائمة التحقّق من تصميم Cast، يجب أن يوفّر تطبيق المرسِل عنصر تحكّم دائمًا يُعرف باسم وحدة التحكّم المصغّرة، ويجب أن يظهر هذا العنصر عندما ينتقل المستخدم إلى صفحة أخرى غير صفحة المحتوى الحالي. يتيح لك جهاز التحكّم المصغّر الوصول الفوري إلى جلسة Cast الحالية ويذكّرك بها بشكل مرئي.
يوفّر إطار عمل Cast شريط تحكّم،
GCKUIMiniMediaControlsViewController،
يمكن إضافته إلى المشاهد التي تريد عرض وحدة التحكّم المصغّرة فيها.
عندما يشغّل تطبيق المرسِل بثًا مباشرًا للفيديو أو الصوت، يعرض حزمة تطوير البرامج (SDK) تلقائيًا زر تشغيل/إيقاف بدلاً من زر التشغيل/الإيقاف المؤقت في وحدة التحكّم المصغّرة.
راجِع تخصيص واجهة مستخدم تطبيق المرسِل على iOS لمعرفة كيف يمكن لتطبيق المرسِل ضبط مظهر التطبيقات المصغّرة الخاصة بميزة "البث".
تتوفّر طريقتان لإضافة وحدة التحكّم المصغّرة إلى تطبيق المرسِل:
- اسمح لإطار عمل 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]; ... }
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 أن يوفّر تطبيق المرسِل وحدة تحكّم موسّعة للوسائط التي يتم بثّها. وحدة التحكّم الموسّعة هي نسخة بملء الشاشة من وحدة التحكّم المصغّرة.
وحدة التحكّم الموسّعة هي طريقة عرض بملء الشاشة تتيح التحكّم الكامل في تشغيل الوسائط عن بُعد. يجب أن يتيح هذا العرض لتطبيق البث إدارة كل الجوانب القابلة للإدارة في جلسة البث، باستثناء التحكّم في مستوى صوت Web Receiver ودورة حياة الجلسة (بدء البث وإيقافه). ويوفّر أيضًا جميع معلومات الحالة حول جلسة الوسائط (العمل الفني والعنوان والعنوان الفرعي وما إلى ذلك).
يتم تنفيذ وظيفة هذا العرض من خلال فئة
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 لمعرفة كيف يمكن لتطبيقك على الجهاز المرسِل ضبط مظهر التطبيقات المصغّرة في Cast.
التحكم في مستوى الصوت
يدير إطار عمل Cast مستوى الصوت لتطبيق المرسِل تلقائيًا، كما تتم المزامنة تلقائيًا مع مستوى صوت Web Receiver لعناصر واجهة المستخدم المتوفّرة. لمزامنة شريط تمرير يوفّره التطبيق، استخدِم
GCKUIDeviceVolumeController.
التحكّم في مستوى الصوت باستخدام الأزرار الخارجية
يمكن استخدام أزرار الصوت المادية على جهاز المرسِل لتغيير مستوى صوت جلسة البث على Web Receiver باستخدام العلامة 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. يمكن للتطبيق عرض مربّعات حوار الخطأ للمستخدم أو يمكنه إنهاء جلسة البث.
يُرجى العِلم أنّ بعض الأخطاء، بما في ذلك
GCKErrorCode
GCKErrorCodeCancelled، هي سلوك مقصود.
لا تحاول إعادة الاتصال إذا تعذّر الاتصال بـ GCKErrorCodeCancelled.
وقد يؤدي ذلك إلى حدوث سلوك غير متوقّع.
التسجيل
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;
يمكن أن تكون أسماء الفئات أسماء حرفية أو أنماط glob، مثل GCKUI\* وGCK\*Session.