במדריך למפתחים הזה מוסבר איך להוסיף תמיכה ב-Google Cast לאפליקציית השליחה ל-iOS באמצעות iOS Sender SDK.
המכשיר הנייד או המחשב הנייד הם השולח ששולט בהפעלה, ומכשיר Google Cast הוא המקלט שמוצג בו התוכן בטלוויזיה.
מסגרת השליחה מתייחסת לקובץ הבינארי של ספריית הכיתות של Cast ולמשאבים המשויכים שנמצאים בסביבת זמן הריצה בשולח. אפליקציית השולח או אפליקציית ההעברה (cast) מתייחסות לאפליקציה שפועלת גם במכשיר השולח. אפליקציית Web Receiver היא אפליקציית ה-HTML שפועלת ב-Web Receiver.
מסגרת השליחה משתמשת בתכנון של קריאה חוזרת אסינכררונית כדי להודיע לאפליקציה השולחת על אירועים ולעבור בין מצבים שונים במחזור החיים של אפליקציית Cast.
תהליך השימוש באפליקציה
השלבים הבאים מתארים את תהליך הביצוע האופייני ברמה גבוהה באפליקציית iOS ששולחת הודעות:
- מסגרת ה-Cast מתחילה את
GCKDiscoveryManager
על סמך המאפיינים שצוינו ב-GCKCastOptions
כדי להתחיל לסרוק מכשירים. - כשהמשתמש לוחץ על לחצן ה-Cast, המערכת מציגה את תיבת הדו-שיח של Cast עם רשימת מכשירי ה-Cast שזוהו.
- כשהמשתמש בוחר מכשיר Cast, המסגרת מנסה להפעיל את אפליקציית Web Receiver במכשיר ה-Cast.
- המסגרת מפעילה קריאות חזרה (callbacks) באפליקציית השולח כדי לאשר שהאפליקציה Web Receiver הושקה.
- המסגרת יוצרת ערוץ תקשורת בין אפליקציית השולח לבין אפליקציית Web Receiver.
- המסגרת משתמשת בערוץ התקשורת כדי לטעון את ההפעלה של המדיה ולשלוט בה במכשיר לווידאו באינטרנט.
- המסגרת מסנכרנת את מצב ההפעלה של המדיה בין השולח לבין מקלט האינטרנט: כשהמשתמש מבצע פעולות בממשק המשתמש של השולח, המסגרת מעבירה את בקשות הבקרה על המדיה למקלט האינטרנט, וכשמקלט האינטרנט שולח עדכונים לגבי סטטוס המדיה, המסגרת מעדכנת את המצב של ממשק המשתמש של השולח.
- כשהמשתמש לוחץ על לחצן ההעברה (cast) כדי להתנתק ממכשיר ההעברה, המסגרת מנתקת את אפליקציית השולח ממקלט האינטרנט.
כדי לפתור בעיות בשולח, צריך להפעיל רישום ביומן.
רשימה מקיפה של כל הכיתות, השיטות והאירועים במסגרת Google Cast ל-iOS מופיעה בחומר העזר בנושא Google Cast iOS API. בקטעים הבאים מוסבר איך לשלב את Cast באפליקציה ל-iOS.
קריאה לשיטות מהשרשור הראשי
איפוס ההקשר של Cast
למסגרת Cast יש אובייקט יחיד (singleton) גלובלי, 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
, שאפשר להשתמש בה כדי להדגיש את לחצן ההעברה (cast) בפעם הראשונה שמתקבל מקלט אינטרנט. באפליקציה של השולח אפשר להתאים אישית את הטקסט, את המיקום של טקסט הכותרת ואת הלחצן 'סגירה'.לחצן ההעברה (cast): החל מגרסה 4.6.0 של ערכת ה-SDK לשליחת Cast ל-iOS, הלחצן להעברה תמיד גלוי כשמכשיר השולח מחובר ל-Wi-Fi. בפעם הראשונה שהמשתמש מקייש על לחצן ההעברה (cast) אחרי הפעלת האפליקציה בפעם הראשונה, מוצגת תיבת דו-שיח של הרשאות כדי שהמשתמש יוכל להעניק לאפליקציה גישה לרשת המקומית של המכשירים ברשת. לאחר מכן, כשהמשתמש מקשיב על לחצן ההעברה, תיבת דו-שיח להעברה תוצג עם רשימה של המכשירים שזוהו. כשהמשתמש מקשיב על לחצן ההעברה (cast) בזמן שהמכשיר מחובר, מוצגים המטא-נתונים הנוכחיים של המדיה (כמו שם, שם האולפן שבו בוצעה ההקלטה ותמונה ממוזערת) או שהמשתמש יכול להתנתק ממכשיר ההעברה. כשהמשתמש מקשקש על לחצן ההעברה (cast) ואין מכשירים זמינים, יוצג מסך עם מידע על הסיבה לכך שלא נמצאו מכשירים ועל אופן פתרון הבעיה.
Mini Controller: כשמשתמש מבצע העברה (cast) של תוכן ועוזב את דף התוכן הנוכחי או את הבקר המורחב למסך אחר באפליקציה השולחת, הבקר המיני מוצג בתחתית המסך כדי לאפשר למשתמש לראות את המטא-נתונים של המדיה שמועברת כרגע ולשלוט בהפעלה.
אמצעי בקרה מורחב: כשהמשתמש מעביר תוכן, אם הוא לוחץ על ההתראה על המדיה או על אמצעי הבקרה המיני, נפתח אמצעי הבקרה המורחב. באמצעי הבקרה הזה מוצגים המטא-נתונים של המדיה שמופעלת כרגע, ויש בו כמה לחצנים לשלוט בהפעלת המדיה.
הוספת לחצן Cast
המסגרת מספקת רכיב של לחצן העברה (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
ישירות לסטוריבורד.
הגדרת גילוי המכשירים
במסגרת, זיהוי המכשירים מתבצע באופן אוטומטי. אין צורך להפעיל או להפסיק את תהליך הגילוי באופן מפורש, אלא אם מטמיעים ממשק משתמש מותאם אישית.
הגילוי במסגרת מנוהל על ידי הכיתה GCKDiscoveryManager
, שהיא מאפיין של GCKCastContext
. המסגרת מספקת רכיב ברירת מחדל של תיבת דו-שיח להעברה (cast) לבחירה ולשליטה במכשיר. רשימת המכשירים מסודרת לפי סדר לקסיקלי לפי השם הידידותי של המכשיר.
איך פועל ניהול הסשנים
ב-Cast SDK מוצג הקונספט של סשן Cast, שבו השלבים של חיבור למכשיר, הפעלה (או הצטרפות) לאפליקציית Web Receiver, חיבור לאפליקציה הזו ואיפוס של ערוץ לניהול מדיה משולבים. למידע נוסף על סשנים של העברה (cast) ועל מחזור החיים של מקלט האינטרנט, אפשר לעיין במדריך למחזור החיים של אפליקציות.
הסשנים מנוהלים על ידי הכיתה GCKSessionManager
, שהיא מאפיין של GCKCastContext
.
סשנים ספציפיים מיוצגים על ידי תת-כיתות של הכיתה GCKSession
: לדוגמה, GCKCastSession
מייצג סשנים עם מכשירי Cast. אפשר לגשת לסשן ההעברה (cast) הפעיל הנוכחי (אם יש כזה) כנכס currentCastSession
של GCKSessionManager
.
אפשר להשתמש בממשק GCKSessionManagerListener
כדי לעקוב אחרי אירועים של סשנים, כמו יצירה, השעיה, המשך וסיום של סשנים. המסגרת משהה באופן אוטומטי את הסשנים כשאפליקציית השולח עוברת לרקע, ומנסה להמשיך אותם כשהאפליקציה חוזרת לחזית (או כשהיא מופעלת מחדש אחרי סיום חריג או פתאומי של האפליקציה בזמן שהסשן היה פעיל).
אם משתמשים בתיבת הדו-שיח של העברה, הסשנים נוצרים ומנוהלים באופן אוטומטי בתגובה לתנועות של המשתמש. אחרת, האפליקציה יכולה להתחיל ולסיים סשנים באופן מפורש באמצעות שיטות ב-GCKSessionManager
.
אם האפליקציה צריכה לבצע עיבוד מיוחד בתגובה לאירועים במחזור החיים של הסשן, היא יכולה לרשום מופע אחד או יותר של GCKSessionManagerListener
באמצעות GCKSessionManager
. GCKSessionManagerListener
הוא פרוטוקול שמגדיר קריאות חזרה (callbacks) לאירועים כמו התחלת סשן, סיום סשן וכו'.
העברת סטרימינג
שמירת מצב הסשן היא הבסיס להעברת סטרימינג, שבה משתמשים יכולים להעביר סטרימינג קיים של אודיו ווידאו בין מכשירים באמצעות פקודות קוליות, אפליקציית Google Home או מסכים חכמים. ההפעלה של המדיה נפסקת במכשיר אחד (המקור) וממשיכה במכשיר אחר (היעד). כל מכשיר Cast עם הקושחה העדכנית יכול לשמש כמקור או כיעד בהעברת סטרימינג.
כדי לקבל את מכשיר היעד החדש במהלך העברת הסטרימינג, משתמשים במאפיין GCKCastSession#device
בזמן הקריאה החוזרת (callback) של [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
באמצעות ערכי enum GCKVideoInfoHDRType
.
הוספת פקדים מיני
בהתאם לרשימת המשימות לעיצוב של Cast, אפליקציית השליחה צריכה לספק אמצעי בקרה קבוע שנקרא אמצעי בקרה מיניאטורי, שצריך להופיע כשהמשתמש עוזב את דף התוכן הנוכחי. בנגן המיני יש גישה מיידית ותזכורת חזותית לסשן ההעברה הנוכחי.
מסגרת ה-Cast כוללת סרגל בקרה, GCKUIMiniMediaControlsViewController
, שאפשר להוסיף לסצנות שבהן רוצים להציג את השליטה המינימלית.
כשאפליקציית השולח מפעילה שידור חי של וידאו או אודיו, ה-SDK מציג באופן אוטומטי לחצן הפעלה/עצירה במקום לחצן ההפעלה/השהיה בבקר המיני.
במאמר התאמה אישית של ממשק המשתמש של השולח ב-iOS מוסבר איך אפליקציית השולח יכולה להגדיר את המראה של ווידג'טים של העברה (cast).
יש שתי דרכים להוסיף את השליטה המינימלית לאפליקציית שליחה:
- כדי לאפשר למסגרת Cast לנהל את הפריסה של הבקרה המיניאטורית, צריך לעטוף את ה-view controller הקיים ב-view controller משלו.
- כדי לנהל את הפריסה של הווידג'ט של הבקרה המיניאטורית בעצמכם, תוכלו להוסיף אותו ל-View Controller הקיים באמצעות תצוגת משנה בסטוריבורד.
גיבוב באמצעות GCKUICastContainerViewController
הדרך הראשונה היא להשתמש ב-GCKUICastContainerViewController
, שמקיף בקר שליטה אחר של תצוגה ומוסיף GCKUIMiniMediaControlsViewController
בתחתית המסך. הגישה הזו מוגבלת כי אי אפשר להתאים אישית את האנימציה ולא ניתן להגדיר את ההתנהגות של ה-View Controller של הקונטיינר.
הדרך הראשונה הזו מתבצעת בדרך כלל בשיטה -[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
הטמעה ב-View Controller קיים
הדרך השנייה היא להוסיף את הבקר המיני ישירות לבקר התצוגה הקיים באמצעות createMiniMediaControlsViewController
כדי ליצור מכונה של GCKUIMiniMediaControlsViewController
, ולאחר מכן להוסיף אותה לבקר התצוגה של הקונטיינר בתור תצוגת משנה.
מגדירים את ה-view controller במתווך האפליקציה:
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
ומוסיפים אותו ל-container view controller בתור תצוגת משנה:
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
.
קודם כול, צריך להפעיל את הבקר המורחב שמוגדר כברירת מחדל בהקשר ההעברה (cast). משנים את האפליקציה הנציגה כדי להפעיל את הבקר המורחב שמוגדר כברירת מחדל:
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];
טיפול בשגיאות
חשוב מאוד שאפליקציות השולחות יטפלו בכל הקריאות החוזרות (callbacks) של השגיאות ויבחרו את התגובה הטובה ביותר לכל שלב במחזור החיים של ההעברה (cast). האפליקציה יכולה להציג למשתמש תיבת דו-שיח עם הודעת שגיאה או לסיים את סשן ההעברה (cast).
רישום ביומן
GCKLogger
הוא אובייקט יחיד (singleton) שמשמש את המסגרת לרישום ביומן. אפשר להשתמש ב-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
.