يصف دليل المطوّر هذا كيفية إضافة دعم Google Cast إلى تطبيق المُرسِل الذي يعمل بنظام التشغيل iOS باستخدام حزمة تطوير البرامج (SDK) لمُرسِل نظام التشغيل iOS.
الجهاز الجوّال أو الكمبيوتر المحمول هو المُرسِل الذي يتحكّم في التشغيل، وجهاز Google Cast هو المستلِم الذي يعرض المحتوى على التلفزيون.
يشير إطار عمل المُرسِل إلى البرنامج الثنائي لمكتبة صف البث والموارد المرتبطة به المتوفّرة في وقت التشغيل على المُرسِل. يشير تطبيق المُرسِل أو تطبيق البث إلى تطبيق يعمل أيضًا على المُرسِل. يشير تطبيق مستقبِل الويب إلى تطبيق HTML الذي يتم تشغيله على مستقبِل الويب.
يستخدِم إطار عمل المُرسِل تصميمًا غير متزامن لمعاودة الاتصال لإعلام تطبيق المُرسِل بالأحداث والانتقال إلى حالة مختلفة من دورة حياة تطبيق البث.
مسار التطبيق
توضّح الخطوات التالية خطوات التنفيذ النموذجية العالية المستوى لأحد تطبيقات iOS للمُرسِل:
- يبدأ إطار عمل البث
GCKDiscoveryManager
بناءً على السمات المقدَّمة فيGCKCastOptions
لبدء البحث عن الأجهزة. - عندما ينقر المستخدم على زر البث، يعرض إطار العمل مربّع حوار البث مع قائمة بأجهزة البث التي تم اكتشافها.
- عندما يختار المستخدم جهاز بث، يحاول إطار العمل تشغيل تطبيق استقبال الويب على جهاز البث.
- يستدعي إطار العمل عمليات معاودة الاتصال في تطبيق المُرسِل لتأكيد تشغيل تطبيق مستقبِل الويب.
- ينشئ إطار العمل قناة اتصال بين المرسِل وتطبيقات استقبال الويب.
- ويستخدم إطار العمل قناة الاتصال لتحميل تشغيل الوسائط والتحكّم به في جهاز استقبال الويب.
- يعمل إطار العمل على مزامنة حالة تشغيل الوسائط بين المُرسِل ومستلِم الويب: عندما يُجري المستخدم إجراءات على واجهة المستخدم للمُرسِل، يمرِّر إطار العمل طلبات التحكّم هذه في الوسائط إلى مستلِم الويب، وعندما يرسل مستلِم الويب تحديثات حالة الوسائط، يعدِّل إطار العمل حالة واجهة مستخدم المُرسِل.
- عندما ينقر المستخدم على زر البث لإلغاء الربط بجهاز البث، سيؤدي هذا إلى إلغاء ربط تطبيق المُرسِل بجهاز استقبال الويب.
لتحديد مشاكل المُرسِل وحلّها، عليك تفعيل التسجيل.
للحصول على قائمة شاملة بجميع الصفوف والأساليب والأحداث في إطار عمل Google Cast لنظام التشغيل iOS، يُرجى الاطّلاع على مرجع واجهة برمجة التطبيقات Google Cast iOS. تتناول الأقسام التالية خطوات دمج ميزة البث في تطبيق iOS.
طُرق الاتصال من سلسلة المحادثات الرئيسية
إعداد سياق البث
يحتوي إطار عمل البث على كائن "سينغلتون" عمومي،
GCKCastContext
،
وهو منسِّق جميع أنشطة إطار العمل. ويجب إعداد هذا الكائن في مرحلة مبكرة من مراحل نشاط التطبيق، وعادةً ما يكون ذلك باستخدام طريقة -[application:didFinishLaunchingWithOptions:]
من تفويض التطبيق، بحيث يمكن استئناف تشغيل الجلسة تلقائيًا عند إعادة تشغيل تطبيق المُرسِل بشكل صحيح.
يجب تقديم عنصر GCKCastOptions
عند إعداد GCKCastContext
.
يحتوي هذا الصف على خيارات تؤثر في سلوك إطار العمل. وأهم
من هذه العوامل هو معرّف تطبيق مستقبِل الويب الذي يُستخدم لفلترة
نتائج الاكتشاف وتشغيل تطبيق "مستلم الويب" عند
بدء جلسة بث.
الطريقة -[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) } } }
Appتفويض.h
@interface AppDelegate () <GCKLoggerDelegate> @end
Appتفويض.m
@implementation AppDelegate static NSString *const kReceiverAppID = @"AABBCCDD"; static const BOOL kDebugLoggingEnabled = YES; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:kReceiverAppID]; GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria]; [GCKCastContext setSharedInstanceWithOptions:options]; // Enable logger. [GCKLogger sharedInstance].delegate = self; ... return YES; } ... #pragma mark - GCKLoggerDelegate - (void)logMessage:(NSString *)message atLevel:(GCKLoggerLevel)level fromFunction:(NSString *)function location:(NSString *)location { if (kDebugLoggingEnabled) { NSLog(@"%@ - %@, %@", function, message, location); } } @end
أدوات "تجربة المستخدم" على Google Cast
توفّر حزمة Cast iOS SDK الأدوات التالية التي تتوافق مع قائمة التحقق من "تصميم البث":
تراكب مركّب: يتضمّن صف
GCKCastContext
طريقة،presentCastInstructionsViewControllerOnceWithCastButton
، يمكن استخدامها لتسليط الضوء على زر البث في المرة الأولى التي يتوفّر فيها جهاز استقبال الويب. يمكن لتطبيق المُرسِل تخصيص النص وموضع نص الزر وزر "إغلاق".زر البث: بدءًا من الإصدار 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) للبث مفهوم جلسة البث الذي يجمع خطوات ربط الجهاز بجهاز أو إطلاق تطبيق على الويب (أو الانضمام إليه) والاتصال بهذا التطبيق وإعداد قناة للتحكّم بالوسائط. اطلع على دليل دورة حياة التطبيق للمستلِم على الويب للحصول على مزيد من المعلومات حول جلسات البث ودورة حياة مستقبِل الويب.
تتم إدارة الجلسات من خلال الصف
GCKSessionManager
،
الذي هو عبارة عن موقع
GCKCastContext
.
يتم تمثيل الجلسات الفردية بفئات فرعية من الصف
GCKSession
: على سبيل المثال،
GCKCastSession
يمثّل جلسات مع أجهزة البث. يمكنك الوصول إلى جلسة البث
النشطة حاليًا (إن وجدت) باعتبارها currentCastSession
للسمة GCKSessionManager
.
يمكن استخدام واجهة GCKSessionManagerListener
لمراقبة أحداث الجلسة، مثل إنشاء الجلسة وتعليقها واستئنافها وإنهاءها. ويعلّق إطار العمل تلقائيًا
الجلسات عند انتقال تطبيق المُرسِل إلى الخلفية ويحاول استئنافها
عند عودة التطبيق إلى المقدّمة (أو إعادة تشغيله بعد
إنهاء تطبيق غير طبيعي/مفاجئ أثناء نشاط الجلسة).
في حال استخدام مربّع حوار البث، يتم إنشاء الجلسات وإزالتها تلقائيًا استجابةً لإيماءات المستخدم. بخلاف ذلك، يمكن للتطبيق بدء جلسات
وإنهاءها بشكلٍ صريح من خلال الطرق على
GCKSessionManager
.
إذا كان التطبيق بحاجة إلى إجراء معالجة خاصة استجابةً لأحداث مراحل نشاط الجلسة، يمكنه تسجيل مثيل واحد أو أكثر من GCKSessionManagerListener
باستخدام GCKSessionManager
. ويُعد GCKSessionManagerListener
بروتوكولاً يحدد استدعاءات هذه الأحداث مثل بداية الجلسة، ونهاية الجلسة، وما إلى ذلك.
نقل مجموعة البث
يتم الحفاظ على حالة الجلسة من خلال عملية نقل البث، حيث يمكن للمستخدمين نقل عمليات بث الصوت والفيديو الحالية على جميع الأجهزة باستخدام الطلبات الصوتية أو تطبيق Google Home أو الشاشات الذكية. يتوقّف تشغيل الوسائط على أحد الأجهزة (المصدر) ويستمر التشغيل على جهاز آخر (الوجهة). يمكن استخدام جهاز بث يتضمّن أحدث البرامج الثابتة كمصادر أو وجهات أثناء نقل البث.
للحصول على الجهاز الوجهة الجديد خلال عملية نقل البث، يمكنك استخدام السمة
GCKCastSession#device
أثناء معاودة الاتصال
[sessionManager:didResumeCastSession:]
.
اطّلِع على نقل ملكية البث على مستقبِل الويب للحصول على مزيد من المعلومات.
إعادة الاتصال التلقائية
يضيف إطار عمل البث إلى منطق إعادة الاتصال لمعالجة إعادة الاتصال تلقائيًا في العديد من الحالات الدقيقة، مثل:
- استرداد البيانات من فقدان مؤقت لشبكة Wi-Fi
- الاسترداد من وضع السكون على الجهاز
- الاسترداد من خلفية التطبيق
- استرداد ما إذا كان التطبيق تعطّل
آلية عمل التحكّم في الوسائط
إذا تم إنشاء جلسة بث باستخدام تطبيق مستقبِل على الويب متوافق مع مساحة أسماء الوسائط، سيتم تلقائيًا إنشاء نسخة افتراضية من
GCKRemoteMediaClient
من خلال إطار العمل، ويمكن الوصول إليها بصفتها السمة
remoteMediaClient
لمثيل
GCKCastSession
.
في جميع الطرق على GCKRemoteMediaClient
التي يتم فيها إرسال طلبات إلى مستلِم الويب، سيتم عرض عنصر GCKRequest
الذي يمكن استخدامه لتتبّع هذا الطلب. يمكن تخصيص 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 TV والارتفاع والعرض بالبكسل. وتتم الإشارة إلى الصيغ بتنسيق 4K في السمة hdrType
من خلال قيم التعداد GCKVideoInfoHDRType
.
إضافة وحدات تحكّم مصغّرة
وفقًا لقائمة التحقق من تصميم البث، يجب أن يوفّر تطبيق المُرسِل عنصر تحكّم دائمًا يُعرف باسم وحدة التحكّم المصغّرة من المفترض أن يظهر عندما ينتقل المستخدم بعيدًا عن صفحة المحتوى الحالي. وحدة التحكم المصغّرة توفر إمكانية الوصول الفوري وتذكير مرئي لجلسة البث الحالية.
يوفّر إطار عمل البث شريط تحكّم
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 } } }
Appتفويض.h
@interface AppDelegate : UIResponder <UIApplicationDelegate> @property (nonatomic, strong) UIWindow *window; @property (nonatomic, assign) BOOL castControlBarsEnabled; @end
Appتفويض.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 من أحد المُرسِلين توفير وحدة تحكّم موسّعة للوسائط التي يتم بثها. وحدة التحكم الموسّعة هي نسخة بملء الشاشة من وحدة التحكم المصغّرة.
وحدة التحكم الموسَّعة هي عرض ملء الشاشة الذي يوفر التحكم الكامل في تشغيل الوسائط عن بُعد. من المفترض أن يسمح هذا العرض لتطبيق البث بإدارة كل جانب من جوانب جلسة البث القابلة للإدارة، باستثناء التحكّم في مستوى الصوت في مستقبِل الويب ورحلة المستخدم (الاتصال/إيقاف البث). وهي توفّر أيضًا كل معلومات الحالة حول جلسة الوسائط (الأعمال الفنية والعنوان والترجمة وما إلى ذلك).
يتم تنفيذ وظائف طريقة العرض هذه بواسطة فئة 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 للتعرّف على كيفية ضبط تطبيق المُرسِل مظهر تطبيقات البث المصغّرة.
التحكم في مستوى الصوت
يدير إطار عمل البث تلقائيًا مستوى صوت تطبيق المُرسِل. وتتم مزامنة إطار العمل تلقائيًا مع مستوى صوت مستقبِل الويب
لأدوات واجهة المستخدم التي يتم تقديمها. لمزامنة شريط تمرير يوفّره التطبيق، استخدِم GCKUIDeviceVolumeController
.
التحكّم في مستوى صوت الزر الفعلي
يمكن استخدام أزرار التحكّم بمستوى الصوت الفعلي على جهاز المُرسِل لتغيير مستوى صوت جلسة البث على جهاز استقبال الويب باستخدام العلامة 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];
التعامل مع الأخطاء
من المهم جدًا أن تعالج تطبيقات المُرسِلين جميع استدعاءات الأخطاء وتحديد أفضل استجابة لكل مرحلة من مراحل إرسال البث. يمكن للتطبيق عرض مربّعات حوار الخطأ للمستخدم أو يمكنه اختيار إنهاء جلسة البث.
التسجيل
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) } } }
Appتفويض.h
@interface AppDelegate () <GCKLoggerDelegate> @end
Appتفويض.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;
يمكن أن تكون أسماء الصفوف إما أسماء حرفية أو أنماطًا شاملة، على سبيل المثال،
GCKUI\*
وGCK\*Session
.