שילוב של Chromecast באפליקציית iOS

במדריך הזה למפתחים מוסבר איך להוסיף תמיכה ב-Google Cast ל-iOS. אפליקציית שולח באמצעות iOS Sender SDK.

המכשיר הנייד או המחשב הנייד הוא השולח ששולט בהפעלה. מכשיר Google Cast הוא המקלט שמציג את התוכן בטלוויזיה.

מסגרת השולח מתייחסת לקובץ הבינארי של הספרייה של מחלקה של Cast, משאבים שקיימים בזמן הריצה אצל השולח. אפליקציית השולח או האפליקציה להפעלת Cast מתייחס לאפליקציה שפועלת גם על השולח. האפליקציה Web Acceptr מתייחס לאפליקציית ה-HTML שפועלת במקלט האינטרנט.

מסגרת השולח משתמשת בעיצוב אסינכרוני של קריאה חוזרת כדי ליידע את השולח אפליקציית אירועים ולעבור בין מצבים שונים של חיי אפליקציית Cast במחזוריות.

זרימת אפליקציה

השלבים הבאים מתארים את תהליך הביצוע ברמה העליונה הטיפוסי של שולח אפליקציה ל-iOS:

  • מערכת Cast מתחילה GCKDiscoveryManager על סמך הנכסים שצוינו GCKCastOptions עד להתחיל לסרוק אחר מכשירים.
  • כשהמשתמש לוחץ על הלחצן להפעלת Cast, ה-framework מציג את ההעברה תיבת דו-שיח עם רשימת מכשירי Cast שהתגלו.
  • כשהמשתמש בוחר מכשיר Cast, ה-framework מנסה להפעיל את אפליקציית מכשיר ה-Cast.
  • ה-framework מפעיל קריאות חוזרות (callbacks) באפליקציית השולח כדי לאשר האפליקציה של מכשיר ה-Web קבלה הופעלה.
  • ה-framework יוצר ערוץ תקשורת בין השולח לבין אפליקציות של המקבל.
  • ה-framework משתמש בערוץ התקשורת כדי לטעון מדיה ולשלוט בה הפעלה במקלט האינטרנט.
  • ה-framework מסתנכרן את מצב הפעלת המדיה בין השולח לבין מקבל אינטרנט: כשהמשתמש מבצע פעולות בממשק המשתמש של השולח, ה-framework עובר הבקשות האלו לבקרת מדיה אל מקלט האינטרנט, ומתי מקלט האינטרנט שולחת עדכונים לגבי סטטוס המדיה, ה-framework מעדכנת את המצב של ממשק המשתמש של השולח.
  • כשהמשתמש לוחץ על לחצן הפעלת Cast כדי להתנתק ממכשיר ה-Cast, ה-framework ינתק את האפליקציה של השולח ממקלט האינטרנט.

כדי לפתור בעיות בשולח, צריך להפעיל את הרישום ביומן.

לרשימה מקיפה של כל הכיתות, השיטות והאירועים ב-Google Cast במסגרת iOS, ראו Google Cast iOS API הפניה. הקטעים הבאים מתארים את השלבים לשילוב של Cast באפליקציה שלכם ל-iOS.

שיטות שיחה מה-thread הראשי

אתחול ההקשר של הפעלת Cast

ל-Cast יש אובייקט גלובלי מסוג singleton, GCKCastContext, שמרכז את כל הפעילויות של המסגרת. צריך לאתחל את האובייקט הזה בתחילת מחזור החיים של האפליקציה, השיטה -[application:didFinishLaunchingWithOptions:] של בעל הגישה לאפליקציה, כך שהמשך אוטומטי של סשן לאחר הפעלה מחדש של אפליקציית השולח יכול לפעול בצורה תקינה.

GCKCastOptions כאשר מאתחלים את GCKCastContext, יש לספק אובייקט. הסיווג הזה מכיל אפשרויות שמשפיעות על ההתנהגות של ה-framework. במידה הרבה ביותר חשוב באחד מהם הוא מזהה האפליקציה של מקלט האינטרנט, שמשמש לסינון תוצאות גילוי ולהפעיל את האפליקציה 'מקלט אינטרנט' כאשר הפעלת Cast בתהליך.

גם שיטת -[application:didFinishLaunchingWithOptions:] היא מקום טוב כדי להגדיר נציג מורשה לרישום ביומן שיקבל את הודעות הרישום ביומן מה-framework. המידע הזה יכול להועיל לניפוי באגים ולפתרון בעיות.

סוויפט
@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 Design רשימת משימות:

  • שכבת-על של סרטון מבוא: למחלקה GCKCastContext יש method, presentCastInstructionsViewControllerOnceWithCastButton, כך שניתן להשתמש בו כדי להדגיש את לחצן הפעלת Cast בפעם הראשונה במקלט אינטרנט זמין. אפליקציית השולח יכולה להתאים אישית את הטקסט והמיקום של הכותרת טקסט ועל הלחצן 'סגירה'.

  • הלחצן להפעלת Cast: החל מגרסה 4.6.0 של Cast ב-Assistant השולח ב-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, גילוי המכשיר מתבצע באופן אוטומטי. אין צורך להתחיל או להפסיק באופן מפורש את תהליך הגילוי, אלא אם מטמיעים ממשק משתמש בהתאמה אישית.

החשיפה במסגרת מנוהלת על ידי הכיתה GCKDiscoveryManager, שהיא תכונה של GCKCastContext framework מספק רכיב ברירת מחדל של תיבת דו-שיח Cast לבחירת מכשירים בקרה. רשימת המכשירים מסודרת לקסיקוגרפיה לפי השם הידידותי למכשיר.

איך פועל ניהול הסשנים

ב-Cast SDK מוצג הקונספט של סשן הפעלת Cast, שמשלבת את השלבים של התחברות למכשיר, השקה (או הצטרפות) של אתר אפליקציית המקבל, התחברות לאפליקציה והפעלה של ערוץ בקרת מדיה. צפייה במקלט האינטרנט מדריך למחזור החיים של אפליקציה לקבלת מידע נוסף על סשנים של הפעלת Cast ועל מחזור החיים של מקלט האינטרנט.

הסשנים מנוהלים על ידי הכיתה GCKSessionManager, שהיא תכונה של GCKCastContext ביקורים בודדים מיוצגים על ידי מחלקות משנה של הכיתה GCKSession: לדוגמה, GCKCastSession מייצג סשנים עם מכשירי Cast. אפשר לגשת להפעלת Cast שפעילה עכשיו סשן (אם בכלל), כמאפיין currentCastSession של GCKSessionManager.

GCKSessionManagerListener יכול לשמש למעקב אחרי אירועי סשן, כמו יצירת סשן, השעיה, חידוש וסיום. ה-framework מושעה באופן אוטומטי ביקורים כשאפליקציית השולח עוברת לרקע ומנסה להמשיך אותם כשהאפליקציה חוזרת לחזית (או מופעלת מחדש לאחר סיום חריגה או פתאומי של האפליקציה בזמן שהסשן פעיל).

אם משתמשים בתיבת הדו-שיח של הפעלת Cast, נוצרים סשנים ומפסיקים באופן אוטומטי בתגובה לתנועות של המשתמש. אחרת, האפליקציה יכולה להתחיל ולהסתיים סשנים באופן מפורש באמצעות methods ב- GCKSessionManager

אם האפליקציה צריכה לבצע עיבוד מיוחד בתגובה למחזור החיים של הסשן אירועים, היא יכולה לרשום מופע אחד או יותר של GCKSessionManagerListener עם GCKSessionManager. GCKSessionManagerListener הוא פרוטוקול שמגדיר קריאות חוזרות (callback) לאירועים כמו התחלת סשן, סיום סשן וכו'.

העברה בסטרימינג

שימור מצב הסשן הוא הבסיס להעברת השידור, המשתמשים יכולים להעביר שידורי אודיו ווידאו קיימים בין מכשירים באמצעות פקודות קוליות, Google Home אפליקציה או מסכים חכמים. המדיה מפסיקה לפעול במכשיר אחד (המקור) וממשיכה במכשיר אחר (המקור היעד). כל מכשיר Cast עם הקושחה האחרונה יכול לשמש כמקורות או יעדים העברה בסטרימינג.

כדי לקבל את מכשיר היעד החדש במהלך ההעברה בסטרימינג, צריך להשתמש ב GCKCastSession#device במהלך [sessionManager:didResumeCastSession:] קריאה חוזרת.

צפייה העברת סטרימינג במקלט אינטרנטי אפשר לקבל מידע נוסף.

חיבור מחדש אוטומטי

מסגרת Cast מוסיפה לוגיקת חיבור מחדש כדי לטפל בחיבור מחדש באופן אוטומטי בהרבה מקרים עדינים, כמו:

  • התאוששות מאובדן זמני של רשת ה-Wi-Fi
  • התאוששות ממצב שינה במכשיר
  • שחזור מהפעלה ברקע של האפליקציה
  • שחזור אם האפליקציה קרסה

איך פועל ממשק השליטה במדיה

אם סשן של הפעלת Cast נוצר באמצעות אפליקציית אינטרנט של מקלט שתומכת במדיה מרחב שמות, מופע של GCKRemoteMediaClient ייווצרו אוטומטית על ידי ה-framework. אפשר לגשת אליו בתור המאפיין remoteMediaClient של GCKCastSession מכונה.

כל השיטות ב-GCKRemoteMediaClient ששולחות בקשות למקלט האינטרנט יחזיר אובייקט GCKRequest יכול לשמש למעקב אחרי הבקשה הזו. א' GCKRequestDelegate אפשר להקצות אותו לאובייקט הזה כדי לקבל התראות על כתוצאה של הפעולה.

צפוי שהמופע של GCKRemoteMediaClient יכול להיות משותף לכמה חלקים של האפליקציה, ואכן כמה רכיבים פנימיים של המסגרת, כמו תיבת הדו-שיח של הפעלת Cast ופקדי מיני מדיה משתפים מכונה. לשם כך, GCKRemoteMediaClient תומך ברישום של כמה GCKRemoteMediaClientListener.

הגדרת מטא-נתונים של מדיה

GCKMediaMetadata class מייצג מידע על פריט מדיה שרוצים להפעיל 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) רשימת המשימות, אפליקציית שולח צריכה לספק שליטה מתמשכת, שנקראת mini בקר משחקים צריכות להופיע כשהמשתמש מנווט אל מחוץ לדף התוכן הנוכחי. המיני-בקר מספק גישה מיידית ותזכורת גלויה סשן הפעלת ה-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, צריך ליצור 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). הוא גם מספק את כל פרטי מצב של סשן המדיה (גרפיקה, כותרת, כותרת משנה וכו') ).

הפונקציונליות של תצוגה זו מיושמת על ידי 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