يوضّح دليل المطوّرين هذا كيفية إضافة ميزة Google Cast إلى تطبيقك المُرسِل على أجهزة iOS باستخدام حزمة تطوير البرامج (SDK) المُرسِلة على أجهزة iOS.
الجهاز الجوّال أو الكمبيوتر المحمول هو المُرسِل الذي يتحكّم في التشغيل، بينما جهاز Google Cast هو المستلِم الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى الملف الثنائي لمكتبة فئات Cast والموارد المرتبطة به المتوفّرة في وقت التشغيل على المُرسِل. يشير التطبيق المُرسِل أو تطبيق Cast إلى تطبيق يعمل أيضًا على المُرسِل. يشير تطبيق Web Receiver إلى تطبيق HTML الذي يعمل على Web Receiver.
يستخدم إطار عمل المُرسِل تصميم معاودة الاتصال غير المتزامن لإعلام التطبيق المُرسِل بالأحداث والانتقال بين حالات مختلفة من دورة حياة تطبيق Cast.
سير عمل التطبيق
تصف الخطوات التالية سير عمل التنفيذ النموذجي العالي المستوى لتطبيق مُرسِل على أجهزة iOS:
- يبدأ إطار عمل Cast تشغيل
GCKDiscoveryManagerاستنادًا إلى الخصائص المقدَّمة فيGCKCastOptionsللبدء في البحث عن الأجهزة. - عندما ينقر المستخدم على زر "البث"، يعرض إطار العمل مربّع حوار "البث" الذي يتضمّن قائمة بأجهزة Cast التي تم العثور عليها.
- عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق Web Receiver على جهاز البث.
- يستدعي إطار العمل معاودات الاتصال في التطبيق المُرسِل لتأكيد تشغيل تطبيق Web Receiver.
- ينشئ إطار العمل قناة اتصال بين التطبيقَين المُرسِل وWeb Receiver.
- يستخدم إطار العمل قناة الاتصال لتحميل تشغيل الوسائط والتحكّم فيه على Web Receiver.
- يُزامِن إطار العمل حالة تشغيل الوسائط بين التطبيقَين المُرسِل وWeb Receiver: عندما يتّخذ المستخدم إجراءات في واجهة مستخدم المُرسِل، يمرِّر إطار العمل طلبات التحكّم في الوسائط هذه إلى Web Receiver، وعندما يرسل Web Receiver آخر الأخبار عن حالة الوسائط، يحدِّث إطار العمل حالة واجهة مستخدم المُرسِل.
- عندما ينقر المستخدم على زر "البث" لقطع الاتصال بجهاز Cast، سيقطع إطار العمل اتصال التطبيق المُرسِل بـ Web Receiver.
لتحديد المشاكل في المُرسِل وحلّها، عليكم تفعيل التسجيل.
للاطّلاع على قائمة شاملة بجميع الفئات والطرق والأحداث في إطار عمل Google Cast على أجهزة iOS، يُرجى الرجوع إلى مرجع واجهة برمجة التطبيقات Google Cast على أجهزة iOS. تتناول الأقسام التالية خطوات دمج Cast في تطبيقكم على أجهزة iOS.
استدعاء الطرق من سلسلة التعليمات الرئيسية
تهيئة سياق Cast
يحتوي إطار عمل 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
توفّر حزمة تطوير البرامج (SDK) لـ Cast على أجهزة iOS الأدوات التالية التي تتوافق مع قائمة التحقق من تصميم 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. يوفّر إطار العمل مكوّن مربّع حوار "البث" تلقائيًا لاختيار الجهاز والتحكّم فيه. يتم ترتيب قائمة الأجهزة معجميًا حسب الاسم المألوف للجهاز.
آلية عمل إدارة الجلسات
تقدّم حزمة تطوير البرامج (SDK) لـ Cast مفهوم جلسة Cast، التي يجمع إنشاؤها بين خطوات الاتصال بجهاز وتشغيل (أو الانضمام إلى) تطبيق Web Receiver والاتصال بهذا التطبيق وتهيئة قناة للتحكّم في الوسائط. لمزيد من المعلومات عن جلسات Cast ودورة حياة Web Receiver، يُرجى الرجوع إلى دليل دورة حياة تطبيق 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 التي تُرسِل طلبات إلى Web Receiver
ستعرض
GCKRequest عنصرًا يمكن
استخدامه لتتبُّع هذا الطلب. يمكن تعيين A
GCKRequestDelegate
لهذا العنصر لتلقّي إشعارات حول النتيجة النهائية للعملية.
من المتوقّع أن يتم مشاركة مثيل GCKRemoteMediaClient بين أجزاء متعددة من التطبيق، وفي الواقع تشارك بعض المكوّنات الداخلية لإطار العمل، مثل مربّع حوار "البث" وعناصر التحكّم المصغّرة في الوسائط، المثيل. لهذا الغرض، GCKRemoteMediaClient
تتيح تسجيل عدة عناصر
GCKRemoteMediaClientListener.
ضبط البيانات الوصفية للوسائط
تمثّل فئة
GCKMediaMetadata
معلومات عن عنصر وسائط تريدون بثه. ينشئ المثال التالي مثيلاً جديدًا من GCKMediaMetadata لفيلم ويضبط العنوان والعنوان الفرعي واسم استوديو التسجيل وصورتَين.
let metadata = GCKMediaMetadata() metadata.setString("Big Buck Bunny (2008)", forKey: kGCKMetadataKeyTitle) metadata.setString("Big Buck Bunny tells the story of a giant rabbit with a heart bigger than " + "himself. When one sunny day three rodents rudely harass him, something " + "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon " + "tradition he prepares the nasty rodents a comical revenge.", forKey: kGCKMetadataKeySubtitle) metadata.addImage(GCKImage(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg")!, width: 480, height: 360))
GCKMediaMetadata *metadata = [[GCKMediaMetadata alloc] initWithMetadataType:GCKMediaMetadataTypeMovie]; [metadata setString:@"Big Buck Bunny (2008)" forKey:kGCKMetadataKeyTitle]; [metadata setString:@"Big Buck Bunny tells the story of a giant rabbit with a heart bigger than " "himself. When one sunny day three rodents rudely harass him, something " "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon " "tradition he prepares the nasty rodents a comical revenge." forKey:kGCKMetadataKeySubtitle]; [metadata addImage:[[GCKImage alloc] initWithURL:[[NSURL alloc] initWithString:@"https://commondatastorage.googleapis.com/" "gtv-videos-bucket/sample/images/BigBuckBunny.jpg"] width:480 height:360]];
يُرجى الرجوع إلى قسم اختيار الصور و تخزينها مؤقتًا للاطّلاع على كيفية استخدام الصور مع البيانات الوصفية للوسائط.
تحميل الوسائط
لتحميل عنصر وسائط، أنشئوا
GCKMediaInformation
مثيلاً باستخدام البيانات الوصفية للوسائط. بعد ذلك، احصلوا على الحالي
GCKCastSession و
استخدِموا
GCKRemoteMediaClient
لتحميل الوسائط على تطبيق المستلِم. يمكنكم بعد ذلك استخدام GCKRemoteMediaClient
للتحكّم في تطبيق مشغّل الوسائط الذي يعمل على المستلِم، مثل التشغيل،
والإيقاف المؤقت والإيقاف.
let url = URL.init(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4") guard let mediaURL = url else { print("invalid mediaURL") return } let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL) mediaInfoBuilder.streamType = GCKMediaStreamType.none; mediaInfoBuilder.contentType = "video/mp4" mediaInfoBuilder.metadata = metadata; mediaInformation = mediaInfoBuilder.build() guard let mediaInfo = mediaInformation else { print("invalid mediaInformation") return } if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInfo) { request.delegate = self }
GCKMediaInformationBuilder *mediaInfoBuilder = [[GCKMediaInformationBuilder alloc] initWithContentURL: [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"]]; mediaInfoBuilder.streamType = GCKMediaStreamTypeNone; mediaInfoBuilder.contentType = @"video/mp4"; mediaInfoBuilder.metadata = metadata; self.mediaInformation = [mediaInfoBuilder build]; GCKRequest *request = [self.sessionManager.currentSession.remoteMediaClient loadMedia:self.mediaInformation]; if (request != nil) { request.delegate = self; }
يُرجى أيضًا الرجوع إلى قسم استخدام مسارات الوسائط.
تنسيق الفيديو بدقة 4K
لتحديد تنسيق الفيديو الخاص بالوسائط، استخدِموا السمة videoInfo في
GCKMediaStatus
للحصول على المثيل الحالي من
GCKVideoInfo.
يحتوي هذا المثيل على نوع تنسيق تلفزيون HDR والارتفاع والعرض بالبكسل. يتم الإشارة إلى أشكال تنسيق 4K في السمة hdrType من خلال قيم التعداد GCKVideoInfoHDRType.
إضافة وحدات تحكّم مصغّرة
وفقًا لقائمة التحقق من تصميم Cast، يجب أن يوفّر التطبيق المُرسِل عنصر تحكّم دائمًا يُعرف باسم وحدة التحكّم المصغّرة ، ويجب أن يظهر عندما ينتقل المستخدم من صفحة المحتوى الحالية. توفر وحدة التحكم المصغرة وصولاً فوريًا وتذكيرًا مرئيًا لجلسة Cast الحالية.
يوفّر إطار عمل Cast شريط تحكّم،
GCKUIMiniMediaControlsViewController,
يمكن إضافته إلى المشاهد التي تريدون عرض وحدة التحكّم المصغّرة فيها.
عندما يشغّل التطبيق المُرسِل بثًا مباشرًا للفيديو أو الصوت، تعرض حزمة تطوير البرامج (SDK) تلقائيًا زر "تشغيل/إيقاف" بدلاً من زر "تشغيل/إيقاف مؤقت" في وحدة التحكّم المصغّرة.
يُرجى الرجوع إلى مقالة تخصيص واجهة مستخدم المُرسِل على أجهزة iOS للاطّلاع على كيفية ضبط التطبيق المُرسِل لمظهر أدوات Cast.
هناك طريقتان لإضافة وحدة التحكّم المصغّرة إلى تطبيق مُرسِل:
- السماح لإطار عمل 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.
التحكّم في مستوى الصوت باستخدام الزر الفعلي
يمكن استخدام أزرار مستوى الصوت الفعلية على الجهاز المُرسِل لتغيير الـ صوت في جلسة Cast على 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. يمكن للتطبيق عرض مربّعات حوار الأخطاء للمستخدم أو يمكنه إنهاء جلسة 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.