يوضّح دليل المطوّرين هذا كيفية إضافة ميزة Google Cast إلى تطبيق المرسِل على iOS باستخدام حزمة تطوير البرامج (SDK) الخاصة بتطبيق المرسِل على iOS.
الجهاز الجوّال أو الكمبيوتر المحمول هو جهاز الإرسال الذي يتحكّم في التشغيل، وجهاز Google Cast هو جهاز الاستقبال الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى ملفات ثنائية لمكتبة فئات 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 تحديثات حالة الوسائط، يعدّل إطار العمل حالة واجهة مستخدم جهاز الإرسال.
- عندما ينقر المستخدم على زر "البث" لقطع الاتصال بجهاز البث، سيقطع إطار العمل اتصال تطبيق المرسِل بـ Web Receiver.
لتحديد المشاكل في المُرسِل وحلّها، عليك تفعيل التسجيل.
للاطّلاع على قائمة شاملة بجميع الفئات والطرق والأحداث في إطار عمل Google Cast لنظام التشغيل iOS، يُرجى الرجوع إلى مرجع Google Cast iOS API. تتناول الأقسام التالية خطوات دمج 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) لنظام التشغيل iOS الخاصة بـ Cast عناصر واجهة المستخدم التالية التي تتوافق مع قائمة التحقّق من تصميم Cast:
الطبقة المتراكبة التمهيدية: يحتوي الصف
GCKCastContext
على طريقة،presentCastInstructionsViewControllerOnceWithCastButton
، يمكن استخدامها لتسليط الضوء على زر Cast في المرة الأولى التي يتوفّر فيها تطبيق Web Receiver. يمكن لتطبيق المُرسِل تخصيص النص وموضع نص العنوان وزر "رفض".زر البث: بدءًا من الإصدار 4.6.0 من حزمة تطوير البرامج (SDK) الخاصة بتطبيق البث على أجهزة iOS، يظهر زر البث دائمًا عندما يكون الجهاز المُرسِل متصلاً بشبكة Wi-Fi. في المرة الأولى التي ينقر فيها المستخدم على زر "البث" بعد تشغيل التطبيق لأول مرة، يظهر مربّع حوار الأذونات ليتمكّن المستخدم من منح التطبيق إذن الوصول إلى الأجهزة على الشبكة المحلية. بعد ذلك، عندما ينقر المستخدم على زر البث، يظهر مربّع حوار البث يعرض الأجهزة التي تم العثور عليها. عندما ينقر المستخدم على زر البث أثناء اتصال الجهاز، يتم عرض البيانات الوصفية الحالية للوسائط (مثل العنوان واسم استوديو التسجيل وصورة مصغّرة) أو السماح للمستخدم بقطع الاتصال بجهاز البث. عندما ينقر المستخدم على زر البث بدون توفّر أي أجهزة، ستظهر شاشة تقدّم للمستخدم معلومات عن سبب عدم العثور على الأجهزة وكيفية تحديد المشاكل وحلّها.
وحدة التحكّم المصغّرة: عندما يبدأ المستخدم في بث المحتوى وينتقل من صفحة المحتوى الحالية أو وحدة التحكّم الموسّعة إلى شاشة أخرى في تطبيق المرسِل، تظهر وحدة التحكّم المصغّرة في أسفل الشاشة للسماح للمستخدم بالاطّلاع على بيانات الوسائط الوصفية التي يتم بثّها حاليًا والتحكّم في التشغيل.
وحدة التحكّم الموسّعة: عندما يبدأ المستخدم في بث المحتوى، إذا نقر على إشعار الوسائط أو وحدة التحكّم المصغّرة، سيتم تشغيل وحدة التحكّم الموسّعة التي تعرض البيانات الوصفية للوسائط التي يتم تشغيلها حاليًا وتوفّر عدة أزرار للتحكّم في تشغيل الوسائط.
إضافة زر "البث"
يوفّر إطار العمل مكوّن زر Cast كفئة فرعية UIButton
. يمكن إضافته إلى شريط عنوان التطبيق من خلال تضمينه في UIBarButtonItem
. يمكن لفئة فرعية نموذجية من
UIViewController
تثبيت زر Cast على النحو التالي:
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 أو الشاشات الذكية. يتوقف تشغيل الوسائط على أحد الأجهزة (الجهاز المصدر) ويستمر على جهاز آخر (الجهاز الوجهة). يمكن لأي جهاز Cast مزوّد بأحدث البرامج الثابتة أن يكون مصدرًا أو وجهة لنقل البث.
للحصول على الجهاز الوجهة الجديد أثناء نقل البث، استخدِم السمة
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 للتلفزيون والارتفاع والعرض بالبكسل. يتم الإشارة إلى صيغ تنسيق 4K في السمة hdrType
باستخدام قيم التعداد GCKVideoInfoHDRType
.
إضافة وحدات تحكّم مصغّرة
وفقًا لقائمة التحقّق من تصميم 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 مستوى الصوت تلقائيًا في تطبيق المرسِل، كما تتم المزامنة تلقائيًا مع مستوى صوت "برنامج استقبال الويب" لعناصر واجهة المستخدم المتوفّرة. لمزامنة شريط تمرير يوفّره التطبيق، استخدِم
GCKUIDeviceVolumeController
.
التحكّم بمستوى الصوت باستخدام الأزرار
يمكن استخدام أزرار التحكّم بمستوى الصوت الخارجية على جهاز المرسِل لتغيير مستوى صوت جلسة Cast على "أداة استقبال الويب" باستخدام العلامة physicalVolumeButtonsWillControlDeviceVolume
على GCKCastOptions
، والتي يتم ضبطها على GCKCastContext
.
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID) let options = GCKCastOptions(discoveryCriteria: criteria) options.physicalVolumeButtonsWillControlDeviceVolume = true GCKCastContext.setSharedInstanceWith(options)
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:kReceiverAppID]; GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria :criteria]; options.physicalVolumeButtonsWillControlDeviceVolume = YES; [GCKCastContext setSharedInstanceWithOptions:options];
معالجة الأخطاء
من المهم جدًا أن تتعامل تطبيقات المرسِل مع جميع عمليات معاودة الاتصال الخاصة بالأخطاء وأن تحدّد أفضل استجابة لكل مرحلة من مراحل دورة حياة Cast. يمكن للتطبيق عرض مربّعات حوار الأخطاء للمستخدم أو يمكنه اختيار إنهاء جلسة البث.
التسجيل
GCKLogger
هو عنصر فردي يستخدمه إطار العمل لتسجيل البيانات. استخدِم
GCKLoggerDelegate
لتخصيص طريقة معالجة رسائل السجلّ.
باستخدام GCKLogger
، تنتج حزمة تطوير البرامج (SDK) ناتج تسجيل على شكل رسائل تصحيح الأخطاء والأخطاء والتحذيرات. تساعد رسائل السجلّ هذه في تصحيح الأخطاء، وهي مفيدة في تحديد المشاكل وحلّها. يتم تلقائيًا إيقاف عرض سجلّ الإخراج، ولكن من خلال تعيين GCKLoggerDelegate
، يمكن لتطبيق المُرسِل تلقّي هذه الرسائل من حزمة تطوير البرامج (SDK) وتسجيلها في وحدة تحكّم النظام.
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate { let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID let kDebugLoggingEnabled = true var window: UIWindow? func applicationDidFinishLaunching(_ application: UIApplication) { ... // Enable logger. GCKLogger.sharedInstance().delegate = self ... } // MARK: - GCKLoggerDelegate func logMessage(_ message: String, at level: GCKLoggerLevel, fromFunction function: String, location: String) { if (kDebugLoggingEnabled) { print(function + " - " + message) } } }
AppDelegate.h
@interface AppDelegate () <GCKLoggerDelegate> @end
AppDelegate.m
@implementation AppDelegate static NSString *const kReceiverAppID = @"AABBCCDD"; static const BOOL kDebugLoggingEnabled = YES; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... // Enable logger. [GCKLogger sharedInstance].delegate = self; ... return YES; } ... #pragma mark - GCKLoggerDelegate - (void)logMessage:(NSString *)message atLevel:(GCKLoggerLevel)level fromFunction:(NSString *)function location:(NSString *)location { if (kDebugLoggingEnabled) { NSLog(@"%@ - %@, %@", function, message, location); } } @end
لتفعيل رسائل تصحيح الأخطاء والرسائل التفصيلية أيضًا، أضِف السطر التالي إلى الرمز بعد ضبط عنصر التحكّم (كما هو موضّح سابقًا):
let filter = GCKLoggerFilter.init() filter.minimumLevel = GCKLoggerLevel.verbose GCKLogger.sharedInstance().filter = filter
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init]; [filter setMinimumLevel:GCKLoggerLevelVerbose]; [GCKLogger sharedInstance].filter = filter;
يمكنك أيضًا فلترة الرسائل المسجّلة التي ينتجها
GCKLogger
.
اضبط الحد الأدنى لمستوى التسجيل لكل فئة، على سبيل المثال:
let filter = GCKLoggerFilter.init() filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton", "GCKUIImageCache", "NSMutableDictionary"]) GCKLogger.sharedInstance().filter = filter
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init]; [filter setLoggingLevel:GCKLoggerLevelVerbose forClasses:@[@"GCKUICastButton", @"GCKUIImageCache", @"NSMutableDictionary" ]]; [GCKLogger sharedInstance].filter = filter;
يمكن أن تكون أسماء الفئات أسماء حرفية أو أنماط glob، مثل GCKUI\*
وGCK\*Session
.