במדריך למפתחים מוסבר איך להוסיף תמיכה ב-Google Cast לאפליקציית השולח ב-iOS באמצעות iOS Sender SDK.
המכשיר הנייד או המחשב הנייד הוא השולח ששולט בהפעלה, ומכשיר Google Cast הוא המקלט שמציג את התוכן בטלוויזיה.
מסגרת השולח מתייחסת לקובץ הבינארי של הספרייה של מחלקת ההעברה (cast) ולמשאבים המשויכים שנמצאים בזמן הריצה של השולח. אפליקציית השולח או אפליקציית ההעברה מתייחסות לאפליקציה שפועלת גם על השולח. אפליקציית האינטרנט 'מקלט אינטרנט' מתייחסת לאפליקציית ה-HTML שפועלת ב-WebReceiver.
מסגרת השולח משתמשת בעיצוב אסינכרוני של קריאה חוזרת כדי לעדכן את אפליקציית השולח לגבי האירועים ולעבור בין מצבים שונים במחזור החיים של אפליקציית Cast.
זרימת אפליקציה
בשלבים הבאים מתואר תהליך הביצוע ברמה העליונה הטיפוסי של אפליקציה ל-iOS של שולח:
- כדי להתחיל בסריקה של מכשירים, המסגרת של Cast מתחילה להפעיל את
GCKDiscoveryManager
על סמך המאפיינים שסופקו ב-GCKCastOptions
. - כשהמשתמש לוחץ על הלחצן להפעלת Cast, במסגרת תיבת הדו-שיח של Cast מוצגת רשימה של מכשירי Cast שנמצאו.
- כשהמשתמש בוחר מכשיר Cast, ה-framework מנסה להפעיל את אפליקציית Web Acceptr במכשיר Cast.
- ה-framework מפעיל קריאות חוזרות (callback) באפליקציית השולח כדי לאשר שאפליקציית Web Acceptr הופעלה.
- תוכנת ה-framework יוצרת ערוץ תקשורת בין השולח לאפליקציות של Web Acceptr.
- תוכנת ה-framework משתמשת בערוץ התקשורת כדי לטעון ולנהל את הפעלת המדיה במקלט האינטרנט.
- ה-framework מסנכרן את מצב הפעלת המדיה בין השולח למקלט האינטרנט: כשהמשתמש מבצע פעולות בממשק המשתמש של השולח, ה-framework מעביר את הבקשות האלה לבקרת מדיה למקלט האינטרנט, וכשמקלט האינטרנט שולח עדכונים לגבי סטטוס המדיה, המסגרת מעדכנת את המצב של ממשק המשתמש של השולח.
- כשהמשתמש לוחץ על לחצן הפעלת Cast כדי להתנתק ממכשיר ה-Cast, ה-framework ינתק את אפליקציית השולח ממקלט האינטרנט.
כדי לפתור בעיות בשולח, צריך להפעיל את הרישום ביומן.
רשימה מקיפה של כל הכיתות, השיטות והאירועים ב-framework של Google Cast ל-iOS זמינה בחומר העזר בנושא Google Cast iOS API. בקטעים הבאים מתוארים השלבים לשילוב של Cast באפליקציה שלכם ל-iOS.
שיטות שיחה מה-thread הראשי
אתחול ההקשר של הפעלת Cast
ל-Cast יש אובייקט גלובלי מסוג Singleton, GCKCastContext
, שמרכז את כל הפעילויות של ה-framework. האובייקט הזה צריך להיות מופעל בתחילת מחזור החיים של האפליקציה, בדרך כלל בשיטה -[application:didFinishLaunchingWithOptions:]
של המשתמש שקיבל הרשאה לאפליקציה, כך שהמשך אוטומטי של סשן לאחר הפעלה מחדש של אפליקציית השולח יכול לפעול כמו שצריך.
כשמאתחלים את GCKCastContext
, צריך לספק אובייקט GCKCastOptions
.
הסיווג הזה מכיל אפשרויות שמשפיעות על ההתנהגות של ה-framework. החשוב ביותר שבהם הוא מזהה האפליקציה של מקלט האינטרנט, שמשמש לסינון תוצאות הגילוי ולהפעלת האפליקציה של מקלט האינטרנט כשהתחלת סשן של הפעלת Cast.
כדאי גם להגדיר משתמש עם הרשאה לרישום ביומן לקבל את הודעות הרישום ביומן מה-framework, באמצעות ה-method -[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
, שאפשר להשתמש בה כדי להדגיש את לחצן הפעלת ה-Cast בפעם הראשונה שמקלט אינטרנט זמין. אפליקציית השולח יכולה להתאים אישית את הטקסט, את המיקום של טקסט הכותרת ואת הלחצן Dismiss.הלחצן להפעלת Cast: החל מגרסה 4.6.0 של להפעלת Cast iOS, לחצן ההעברה תמיד גלוי כשהמכשיר של השולח מחובר ל-Wi-Fi. בפעם הראשונה שהמשתמש מקיש על הלחצן להפעלת Cast אחרי שמפעילים את האפליקציה, מופיעה תיבת דו-שיח של הרשאות, כך שהמשתמש יכול לתת לאפליקציה גישה לרשת המקומית למכשירים ברשת. לאחר מכן, כשהמשתמש יקיש על הלחצן להפעלת Cast, מוצגת תיבת דו-שיח של הפעלת Cast עם רשימת המכשירים שזוהו. כשהמשתמש מקיש על הלחצן להפעלת Cast כשהמכשיר מחובר, המטא-נתונים של המדיה הנוכחית (כמו שם אולפן ההקלטות ותמונה ממוזערת) מוצגים, או מאפשרים למשתמש להתנתק ממכשיר ה-Cast. כשהמשתמש מקיש על הלחצן להפעלת Cast כשאין מכשירים זמינים, יוצג מסך עם מידע שמסביר למה המכשירים לא נמצאו ואיך לפתור בעיות.
מיני-בקר: כשהמשתמש מעביר תוכן ועובר מדף התוכן הנוכחי או מהבקר המורחב למסך אחר באפליקציית השולח, הבקר המיני יוצג בחלק התחתון של המסך כדי לאפשר למשתמש לראות את המטא-נתונים של המדיה שמועברת כרגע ולשלוט בהפעלה.
בקר מורחב: כשהמשתמש מבצע העברה (cast) של תוכן, אם הוא לוחץ על התראת המדיה או על השלט הרחוק, הבקר המורחב מציג את המטא-נתונים של המדיה שמופעלים כרגע ומספק מספר לחצנים לשליטה בהפעלת המדיה.
הוספת לחצן להפעלת Cast
ה-framework מספק רכיב של לחצן הפעלת 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];
כברירת מחדל, הקשה על הלחצן תפתח את תיבת הדו-שיח של הפעלת Cast שמסופקת על ידי המסגרת.
אפשר גם להוסיף את GCKUICastButton
ישירות לסטוריבורד.
הגדרת גילוי מכשירים
ב-framework, גילוי המכשיר מתבצע באופן אוטומטי. אין צורך להתחיל או להפסיק את תהליך הגילוי באופן מפורש, אלא אם מטמיעים ממשק משתמש בהתאמה אישית.
הגילוי ב-framework מנוהל על ידי המחלקה GCKDiscoveryManager
, שהיא מאפיין של GCKCastContext
. ה-framework מספק רכיב ברירת מחדל של תיבת דו-שיח של Cast לבחירת מכשירים ולבקרה. רשימת המכשירים מסודרת לקסיקוגרפיה לפי השם הידידותי למכשיר.
איך פועל ניהול הסשנים
ב-Cast SDK מוצג הקונספט של סשן של הפעלת Cast, אמצעי שמשלב את השלבים של התחברות למכשיר, הפעלה (או הצטרפות) של אפליקציית מקלט אינטרנט, התחברות לאפליקציה הזו ואתחול ערוץ בקרת מדיה. לקבלת מידע נוסף על סשנים של הפעלת Cast ועל מחזור החיים של WebReceiver, ראו מדריך מחזור החיים של אפליקציה.
הסשנים מנוהלים על ידי המחלקה GCKSessionManager
, שהיא מאפיין של GCKCastContext
.
סשנים בודדים מיוצגים על ידי מחלקות משנה של המחלקה GCKSession
: לדוגמה, GCKCastSession
מייצג סשנים עם מכשירי Cast. תוכלו לגשת לסשן Cast שפעיל כרגע (אם יש כזה), בתור המאפיין currentCastSession
של GCKSessionManager
.
אפשר להשתמש בממשק GCKSessionManagerListener
כדי לעקוב אחרי אירועי סשן, כמו יצירת סשן, השעיה, המשך וסיום. ה-framework משעה באופן אוטומטי סשנים כשאפליקציית השולח עוברת לרקע, ומנסה להמשיך אותם כשהאפליקציה חוזרת לחזית (או מופעלת מחדש אחרי סיום חריגה או פתאומי של האפליקציה בזמן שהסשן פעיל).
אם משתמשים בתיבת הדו-שיח של הפעלת Cast, הסשנים נוצרים ומסתיימים באופן אוטומטי בתגובה לתנועות של המשתמשים. אחרת, האפליקציה יכולה להתחיל ולסיים סשנים באופן מפורש באמצעות methods ב-GCKSessionManager
.
אם האפליקציה צריכה לבצע עיבוד מיוחד בתגובה לאירועים במחזור החיים של הסשן, היא יכולה לרשום מופע אחד או יותר של GCKSessionManagerListener
עם GCKSessionManager
. GCKSessionManagerListener
הוא פרוטוקול שמגדיר קריאות חוזרות לאירועים כמו התחלת סשן, סיום סשן וכו'.
העברה בסטרימינג
שימור מצב הסשן הוא הבסיס להעברת השידור, שבה המשתמשים יכולים להעביר שידורי אודיו ווידאו קיימים בין מכשירים באמצעות פקודות קוליות, אפליקציית Google Home או מסכים חכמים. המדיה מפסיקה לפעול במכשיר אחד (המקור) וממשיכה במכשיר אחר (היעד). כל מכשיר Cast עם הקושחה האחרונה יכול לשמש כמקורות או יעדים בהעברה בסטרימינג.
כדי לקבל את מכשיר היעד החדש במהלך ההעברה של השידור, צריך להשתמש במאפיין GCKCastSession#device
במהלך הקריאה החוזרת (callback) של [sessionManager:didResumeCastSession:]
.
למידע נוסף, ראו העברת סטרימינג ב-WebReceiver.
חיבור מחדש אוטומטי
ה-Cast framework מוסיפה לוגיקת חיבור מחדש כדי לטפל בחיבור מחדש באופן אוטומטי בהרבה מקרים פינתיים קטנים, כמו:
- התאוששות מאובדן זמני של רשת ה-Wi-Fi
- התאוששות ממצב שינה במכשיר
- שחזור מהפעלה ברקע של האפליקציה
- שחזור אם האפליקציה קרסה
איך פועל ממשק השליטה במדיה
אם סשן של הפעלת Cast נוצר באמצעות אפליקציית WebReceiver שתומכת במרחב השמות של המדיה, ה-framework יצור באופן אוטומטי מכונה של GCKRemoteMediaClient
. אפשר לגשת אליה בתור המאפיין remoteMediaClient
של המכונה GCKCastSession
.
כל ה-methods ב-GCKRemoteMediaClient
ששולחות בקשות למקלט האינטרנט יחזירו אובייקט GCKRequest
שיכול לשמש למעקב אחר הבקשה. אפשר להקצות לאובייקט הזה GCKRequestDelegate
כדי לקבל התראות על התוצאה הסופית של הפעולה.
אפשר לצפות שהמכונה של GCKRemoteMediaClient
תשותף על ידי מספר חלקים של האפליקציה, ואכן חלק מהרכיבים הפנימיים של ה-framework, כמו תיבת הדו-שיח של Cast ופקדי מיני מדיה, כן משתפים את המכונה. לשם כך, GCKRemoteMediaClient
תומך ברישום של מספר נכסי GCKRemoteMediaClientListener
.
הגדרת מטא-נתונים של מדיה
המחלקה GCKMediaMetadata
מייצגת מידע על פריט מדיה שרוצים להפעיל Cast. הדוגמה הבאה יוצרת מופע 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
באמצעות ערכי enum 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; }
ב-Root View Controller, יוצרים מכונה של 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, אפליקציית השולח צריכה לספק בקר מורחב למדיה שמועברת. הבקר המורחב הוא גרסה של המיני-בקר במסך מלא.
הבקר המורחב מאפשר צפייה במסך מלא שמאפשרת שליטה מלאה בהפעלת המדיה מרחוק. התצוגה הזו צריכה לאפשר לאפליקציית Cast לנהל את כל היבט שניתן לנהל בסשן של Cast, פרט לבקרת עוצמת הקול של מקלט האינטרנט ולמחזור החיים של הסשן (חיבור/הפסקה של הפעלת Cast). הוא גם מספק את כל פרטי הסטטוס של סשן המדיה (הגרפיקה, כותרת, כותרת המשנה וכו').
הפונקציונליות של התצוגה הזו מיושמת על ידי המחלקה GCKUIExpandedMediaControlsViewController
.
קודם כול צריך להפעיל את השלט רחוק המורחב שמוגדר כברירת מחדל בהקשר של הפעלת Cast. משנים את הגורם האחראי לאפליקציה כדי להפעיל את השלט הרחוק המורחב שמוגדר כברירת מחדל:
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
מוסיפים את הקוד הבא לבקר התצוגה כדי לטעון את הבקר המורחב כשהמשתמש מתחיל להעביר (cast) סרטון:
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 מנהל באופן אוטומטי את עוצמת הקול של אפליקציית השולח. ה-framework מסתנכרן אוטומטית עם עוצמת הקול של Web Acceptr של הווידג'טים של ממשק המשתמש. כדי לסנכרן פס הזזה שסופק על ידי האפליקציה, משתמשים ב-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. האפליקציה יכולה להציג למשתמש תיבות דו-שיח עם שגיאות, או להחליט אם לסיים את הפעלת ה-Cast.
רישום ביומן
GCKLogger
הוא מקטע יחיד שמשמש לרישום ביומן על ידי ה-framework. באמצעות 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;
שמות המחלקות יכולים להיות שמות מילוליים או תבניות גלובוס, לדוגמה, GCKUI\*
ו-GCK\*Session
.