במדריך הזה למפתחים מוסבר איך להוסיף תמיכה ב-Google Cast לאפליקציית השולט ל-iOS באמצעות iOS Sender SDK.
המכשיר הנייד או המחשב הנייד הם השולח ששולט בהפעלה, ומכשיר Google Cast הוא המקלט שמציג את התוכן בטלוויזיה.
מסגרת השולח מתייחסת לקובץ הבינארי של ספריית המחלקות של Cast ולמשאבים המשויכים שקיימים בזמן הריצה בשולח. אפליקציית השולח או אפליקציית Cast מתייחסות לאפליקציה שפועלת גם במכשיר השולח. אפליקציית Web Receiver היא אפליקציית HTML שפועלת ב-Web Receiver.
מסגרת השולח משתמשת בעיצוב של קריאה חוזרת אסינכרונית כדי לעדכן את אפליקציית השולח לגבי אירועים, וכדי לעבור בין מצבים שונים במחזור החיים של אפליקציית Cast.
תהליך השימוש באפליקציה
בשלבים הבאים מתואר תהליך ההפעלה הטיפוסי ברמה גבוהה של אפליקציית שולח ל-iOS:
- Cast Framework מתחיל את
GCKDiscoveryManager
על סמך המאפיינים שצוינו ב-GCKCastOptions
כדי להתחיל לסרוק מכשירים. - כשהמשתמש לוחץ על לחצן Cast, המסגרת מציגה את תיבת הדו-שיח Cast עם רשימת מכשירי Cast שהמערכת זיהתה.
- כשהמשתמש בוחר מכשיר Cast, המסגרת מנסה להפעיל את אפליקציית Web Receiver במכשיר Cast.
- המסגרת מפעילה קריאות חוזרות באפליקציית השולח כדי לאשר שאפליקציית Web Receiver הופעלה.
- המסגרת יוצרת ערוץ תקשורת בין השולח לבין אפליקציות Web Receiver.
- המסגרת משתמשת בערוץ התקשורת כדי לטעון ולשלוט בהפעלת מדיה ב-Web Receiver.
- המסגרת מסנכרנת את מצב ההפעלה של המדיה בין השולח לבין Web Receiver: כשהמשתמש מבצע פעולות בממשק המשתמש של השולח, המסגרת מעבירה את בקשות השליטה במדיה אל Web Receiver, וכש-Web Receiver שולח עדכונים לגבי סטטוס המדיה, המסגרת מעדכנת את המצב של ממשק המשתמש של השולח.
- כשהמשתמש לוחץ על לחצן Cast כדי להתנתק ממכשיר Cast, המסגרת תנתק את אפליקציית השולח מ-Web Receiver.
כדי לפתור בעיות שקשורות לשולח, צריך להפעיל רישום ביומן.
רשימה מקיפה של כל המחלקות, השיטות והאירועים ב-framework של Google Cast ל-iOS זמינה בחומר העזר בנושא Google Cast iOS API. בקטעים הבאים מפורטים השלבים לשילוב Cast באפליקציית iOS.
קריאה לשיטות מה-thread הראשי
הפעלת ההקשר של Cast
ל-Cast Framework יש אובייקט סינגלטון גלובלי, GCKCastContext
, שמתאם את כל הפעילויות של ה-Framework. צריך לאתחל את האובייקט הזה בשלב מוקדם במחזור החיים של האפליקציה, בדרך כלל בשיטה -[application:didFinishLaunchingWithOptions:]
של נציג האפליקציה, כדי שהפעלה אוטומטית מחדש של הסשן בהפעלה מחדש של אפליקציית השולח תופעל בצורה תקינה.
כשמאתחלים את GCKCastContext
, צריך לספק אובייקט GCKCastOptions
.
הכיתה הזו מכילה אפשרויות שמשפיעות על ההתנהגות של המסגרת. הכי חשוב מביניהם הוא מזהה אפליקציית 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
Cast iOS SDK מספק את הווידג'טים האלה שעומדים בדרישות של רשימת הבדיקה של Cast Design:
Introductory Overlay: ל-class
GCKCastContext
יש שיטה,presentCastInstructionsViewControllerOnceWithCastButton
, שאפשר להשתמש בה כדי להבליט את לחצן Cast בפעם הראשונה ש-Web Receiver זמין. אפליקציית השולח יכולה להתאים אישית את הטקסט, את המיקום של הטקסט בכותרת ואת כפתור הסגירה.לחצן Cast: החל מגרסה 4.6.0 של Cast iOS sender SDK, לחצן Cast תמיד גלוי כשהמכשיר השולח מחובר ל-Wi-Fi. בפעם הראשונה שהמשתמש מקיש על לחצן Cast אחרי ההפעלה הראשונית של האפליקציה, מוצג דו-שיח של הרשאות כדי שהמשתמש יוכל להעניק לאפליקציה גישה לרשת המקומית למכשירים ברשת. לאחר מכן, כשהמשתמש מקיש על לחצן ה-Cast, מוצג דו-שיח של Cast עם רשימה של המכשירים שזוהו. כשהמשתמש מקיש על לחצן ה-Cast בזמן שהמכשיר מחובר, מוצגים המטא-נתונים הנוכחיים של המדיה (כמו שם, שם אולפן ההקלטות ותמונה ממוזערת), או שהמשתמש יכול להתנתק ממכשיר ה-Cast. אם המשתמש מקיש על לחצן ה-Cast כשאין מכשירים זמינים, יוצג מסך עם מידע על הסיבות לכך שלא נמצאו מכשירים ועל פתרון בעיות.
השלט המיני: כשהמשתמש מפעיל Cast לתוכן ועובר מדף התוכן הנוכחי או מהשלט המורחב למסך אחר באפליקציית השולח, השלט המיני מוצג בתחתית המסך כדי לאפשר למשתמש לראות את המטא-נתונים של המדיה שמופעלת ב-Cast ולשלוט בהפעלה.
המרכז לבקרה על ההפעלה: כשהמשתמשים מפעילים Cast של תוכן, אם הם לוחצים על ההתראה על המדיה או על המרכז לבקרה על ההפעלה, המרכז לבקרה על ההפעלה מורחב ומוצגים בו המטא-נתונים של המדיה שמופעלת כרגע וכמה לחצנים לשליטה בהפעלת המדיה.
הוספת כפתור Cast
המסגרת מספקת רכיב של לחצן Cast בתור מחלקת משנה של UIButton
. אפשר להוסיף אותו לסרגל הכותרת של האפליקציה על ידי הוספתו לתג UIBarButtonItem
. אפשר להתקין לחצן Cast במחלקת משנה טיפוסית של 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];
כברירת מחדל, הקשה על הכפתור תפתח את תיבת הדו-שיח של Cast שמופיעה ב-framework.
אפשר גם להוסיף את GCKUICastButton
ישירות ללוח התכנון.
הגדרה של גילוי מכשירים
במסגרת, גילוי המכשיר מתבצע באופן אוטומטי. אין צורך להפעיל או להפסיק את תהליך הגילוי באופן מפורש, אלא אם מטמיעים ממשק משתמש בהתאמה אישית.
הגילוי במסגרת מנוהל על ידי המחלקה GCKDiscoveryManager
, שהיא מאפיין של GCKCastContext
. המסגרת מספקת רכיב ברירת מחדל של תיבת דו-שיח של Cast לבחירת מכשיר ולשליטה בו. רשימת המכשירים מסודרת לקסיקוגרפית לפי השם הידידותי של המכשיר.
איך פועל ניהול הסשנים
Cast SDK מציג את הקונספט של סשן Cast, שההגדרה שלו משלבת את השלבים של חיבור למכשיר, הפעלה (או הצטרפות) של אפליקציית Web Receiver, חיבור לאפליקציה הזו והפעלת ערוץ של בקרת מדיה. למידע נוסף על סשנים של Cast ומחזור החיים של Web Receiver, אפשר לעיין במדריך למחזור החיים של אפליקציות של Web Receiver.
הסשנים מנוהלים על ידי המחלקה GCKSessionManager
, שהיא מאפיין של GCKCastContext
.
סשנים בודדים מיוצגים על ידי מחלקות משנה של המחלקה GCKSession
: לדוגמה, GCKCastSession
מייצג סשנים עם מכשירי Cast. אפשר לגשת להפעלה הפעילה של Cast (אם יש כזו) בתור המאפיין currentCastSession
של GCKSessionManager
.
אפשר להשתמש בממשק של GCKSessionManagerListener
כדי לעקוב אחרי אירועים של סשנים, כמו יצירה, השהיה, חידוש וסיום של סשן. המסגרת משעה אוטומטית סשנים כשאפליקציית השולח עוברת לרקע, ומנסה להפעיל אותם מחדש כשהאפליקציה חוזרת לחזית (או מופעלת מחדש אחרי סגירה לא תקינה או פתאומית של האפליקציה בזמן שסשן היה פעיל).
אם משתמשים בתיבת הדו-שיח של Cast, הסשנים נוצרים ומוסרים באופן אוטומטי בתגובה לתנועות של המשתמש. אחרת, האפליקציה יכולה להתחיל ולהפסיק סשנים באופן מפורש באמצעות שיטות ב-GCKSessionManager
.
אם האפליקציה צריכה לבצע עיבוד מיוחד בתגובה לאירועים במחזור החיים של הסשן, היא יכולה לרשום מופע אחד או יותר של GCKSessionManagerListener
באמצעות GCKSessionManager
. GCKSessionManagerListener
הוא פרוטוקול שמגדיר קריאות חוזרות (callback) לאירועים כמו התחלת סשן, סיום סשן וכו'.
החלפת רמקול
שמירת מצב הסשן היא הבסיס להעברת סטרימינג, שבה משתמשים יכולים להעביר סטרימינג קיים של אודיו ווידאו בין מכשירים באמצעות פקודות קוליות, אפליקציית Google Home או מסכים חכמים. הפעלת המדיה נפסקת במכשיר אחד (המקור) וממשיכה במכשיר אחר (היעד). כל מכשיר Cast עם הקושחה העדכנית יכול לשמש כמקור או כיעד בהעברת סטרימינג.
כדי לקבל את מכשיר היעד החדש במהלך העברת השידור, צריך להשתמש במאפיין GCKCastSession#device
במהלך הקריאה החוזרת [sessionManager:didResumeCastSession:]
.
מידע נוסף זמין במאמר בנושא העברת סטרימינג ב-Web Receiver.
חיבור מחדש אוטומטי
ה-Cast framework מוסיף לוגיקה של חיבור מחדש כדי לטפל באופן אוטומטי בחיבור מחדש במקרים רבים ומורכבים, כמו:
- התאוששות מאובדן זמני של חיבור ה-Wi-Fi
- התאוששות ממצב שינה של המכשיר
- שחזור אחרי העברת האפליקציה לרקע
- שחזור אם האפליקציה קרסה
איך פועל ניהול המדיה
אם מקימים סשן Cast עם אפליקציית Web Receiver שתומכת במרחב השמות של המדיה, נוצרת באופן אוטומטי על ידי המסגרת דוגמה של GCKRemoteMediaClient
. אפשר לגשת אליה כאל המאפיין remoteMediaClient
של הדוגמה GCKCastSession
.
כל השיטות ב-GCKRemoteMediaClient
ששולחות בקשות ל-Web Receiver יחזירו אובייקט GCKRequest
שאפשר להשתמש בו כדי לעקוב אחרי הבקשה. אפשר להקצות ל-GCKRequestDelegate
הזה אובייקט כדי לקבל התראות על התוצאה הסופית של הפעולה.
צפוי שמופע של GCKRemoteMediaClient
ישותף על ידי חלקים שונים באפליקציה, ואכן חלק מהרכיבים הפנימיים של המסגרת, כמו תיבת הדו-שיח של 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, ואת הגובה והרוחב בפיקסלים. וריאציות של פורמט 4K מצוינות במאפיין hdrType
באמצעות ערכי enum GCKVideoInfoHDRType
.
הוספת בקרי מיני
לפי רשימת המשימות לבדיקת עיצוב של Cast, אפליקציית השולט צריכה לספק אמצעי בקרה קבוע שנקרא בקר קטן, שמופיע כשהמשתמש יוצא מדף התוכן הנוכחי. השלט הקטן מספק גישה מיידית ותזכורת גלויה להפעלה הנוכחית של Cast.
מסגרת Cast מספקת סרגל בקרה, GCKUIMiniMediaControlsViewController
, שאפשר להוסיף לסצנות שבהן רוצים להציג את בקר המיני.
כשמפעילים שידור חי של סרטון או אודיו באפליקציית השולח, ה-SDK מציג באופן אוטומטי לחצן הפעלה/עצירה במקום לחצן ההפעלה/ההשהיה במיני-בקר.
במאמר התאמה אישית של ממשק המשתמש של אפליקציית השולח ב-iOS מוסבר איך אפליקציית השולח יכולה להגדיר את המראה של הווידג'טים של Cast.
יש שתי דרכים להוסיף את אמצעי הבקרה הקטן לאפליקציית השולח:
- מאפשרים ל-Cast Framework לנהל את הפריסה של בקר המיני על ידי עטיפת בקר התצוגה הקיים בבקר תצוגה משלו.
- כדי לנהל את הפריסה של הווידג'ט של בקר המיני בעצמכם, מוסיפים אותו לבקר התצוגה הקיים על ידי הוספת תצוגת משנה ב-storyboard.
עטיפה באמצעות 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 Design Checklist נדרש מאפליקציית השולח לספק בקר מורחב למדיה שמועברת ב-Cast. השלט המורחב הוא גרסה במסך מלא של השלט המוקטן.
השלט המורחב הוא תצוגה במסך מלא שמאפשרת שליטה מלאה בהפעלת המדיה מרחוק. התצוגה הזו אמורה לאפשר לאפליקציית Cast לנהל כל היבט שניתן לניהול של הפעלת Cast, למעט שליטה בעוצמת הקול של Web Receiver ומחזור החיים של ההפעלה (חיבור/הפסקת 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 מנהל אוטומטית את עוצמת הקול באפליקציית השולט. ה-Framework מסתנכרן אוטומטית עם עוצמת הקול של Web Receiver עבור הווידג'טים של ממשק המשתמש שסופקו. כדי לסנכרן פס הזזה שסופק על ידי האפליקציה, משתמשים ב-GCKUIDeviceVolumeController
.
שליטה בעוצמת הקול באמצעות כפתור פיזי
אפשר להשתמש בלחצני עוצמת הקול הפיזיים במכשיר השולח כדי לשנות את עוצמת הקול של סשן Cast ב-Web Receiver באמצעות הדגל physicalVolumeButtonsWillControlDeviceVolume
ב-GCKCastOptions
, שמוגדר ב-GCKCastContext
.
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID) let options = GCKCastOptions(discoveryCriteria: criteria) options.physicalVolumeButtonsWillControlDeviceVolume = true GCKCastContext.setSharedInstanceWith(options)
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:kReceiverAppID]; GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria :criteria]; options.physicalVolumeButtonsWillControlDeviceVolume = YES; [GCKCastContext setSharedInstanceWithOptions:options];
טיפול בשגיאות
חשוב מאוד שאפליקציות השולח יטפלו בכל הקריאות החוזרות של שגיאות ויחליטו מהי התגובה הטובה ביותר לכל שלב במחזור החיים של Cast. האפליקציה יכולה להציג למשתמש תיבות דו-שיח של שגיאות או להחליט לסיים את סשן ה-Cast.
רישום ביומן
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
.