הפעלת העברה (cast) של אפליקציה ל-iOS

1. סקירה כללית

הלוגו של Google Cast

בשיעור הזה תלמדו איך לשנות אפליקציית וידאו קיימת ב-iOS כדי להפעיל Cast של תוכן במכשיר שתומך ב-Google Cast.

מה זה Google Cast?

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

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

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

מה אנחנו מתכוונים לבנות?

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

מה תלמדו

  • איך להוסיף את Google Cast SDK לאפליקציית סרטונים לדוגמה.
  • איך להוסיף את לחצן הפעלת Cast כדי לבחור מכשיר Google Cast.
  • איך להתחבר למכשיר Cast ולהפעיל מקלט מדיה.
  • איך להפעיל Cast של סרטון.
  • איך להוסיף שלט רחוק של Cast Mini לאפליקציה.
  • איך להוסיף בקר מורחב.
  • איך לספק שכבת-על בהקדמה
  • איך מתאימים אישית ווידג'טים של Cast.
  • איך משלבים Cast Connect

מה נדרש

  • Xcode בגרסה העדכנית ביותר.
  • מכשיר נייד אחד עם iOS מגרסה 9 ואילך (או סימולטור Xcode).
  • כבל נתונים USB שמחברים את המכשיר הנייד למחשב הפיתוח (אם אתם משתמשים במכשיר).
  • מכשיר Google Cast, כמו Chromecast או Android TV, שמוגדר עם גישה לאינטרנט.
  • טלוויזיה או צג עם כניסת HDMI.
  • כדי לבדוק את השילוב של Cast Connect צריך מכשיר Chromecast with Google TV, אבל הוא אופציונלי בשאר הקורס Codelab. אם אין לכם תמיכה, אפשר לדלג על השלב הוספת תמיכה של Cast Connect בסוף המדריך.

ניסיון

  • נדרש ידע קודם בפיתוח ב-iOS.
  • נדרש גם ידע קודם על צפייה בטלוויזיה :)

איך תשתמשו במדריך הזה?

לקריאה בלבד לקרוא אותו ולבצע את התרגילים

איזה דירוג מגיע לחוויה שלך עם בניית אפליקציות ל-iOS?

מתחילים בינונית בקיאים

איזה דירוג מגיע לדעתך לחוויית הצפייה בטלוויזיה?

מתחילים בינונית בקיאים

2. לקבלת הקוד לדוגמה

אפשר להוריד את כל הקוד לדוגמה למחשב...

ופורקים את קובץ ה-ZIP שהורדתם.

3. הרצת האפליקציה לדוגמה

הלוגו של Apple iOS

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

אחרי שהורדתם את הקוד, ההוראות הבאות מתארות איך לפתוח ולהפעיל את האפליקציה לדוגמה שהושלמה ב-Xcode:

שאלות נפוצות

הגדרת CocoaPods

כדי להגדיר CocoaPods, נכנסים למסוף ומתקינים באמצעות ברירת המחדל של Ruby שזמינה ב-macOS:

sudo gem install cocoapods

אם תיתקלו בבעיות, תוכלו לעיין במסמכים הרשמיים כדי להוריד ולהתקין את מנהל התלות.

הגדרת הפרויקט

  1. נכנסים לטרמינל ועוברים לספריית Codelab.
  2. מתקינים את יחסי התלות מה-Podfile.
cd app-done
pod update
pod install
  1. פותחים את Xcode ובוחרים באפשרות Open another project...
  2. בוחרים את הקובץ CastVideos-ios.xcworkspace מהספרייה סמל של תיקייהapp-done שבתיקיית הקוד לדוגמה.

הפעלת האפליקציה

בוחרים את היעד ואת הסימולטור ומריצים את האפליקציה:

סרגל הכלים של סימולטור אפליקציית XCode

אפליקציית הסרטונים אמורה להופיע לאחר מספר שניות.

חשוב ללחוץ על 'אישור' כשמופיעה התראה לגבי אישור התחברות לרשת נכנסת. סמל ההעברה (cast) לא יופיע אם האפשרות הזו לא תאושר.

תיבת דו-שיח לאישור שבה מבקשים הרשאה לקבלת חיבורי רשת נכנסים

לוחצים על הלחצן להפעלת Cast ובוחרים את מכשיר ה-Google Cast.

בוחרים סרטון ולוחצים על לחצן ההפעלה.

הסרטון יתחיל לפעול במכשיר Google Cast.

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

חוזרים לרשימת הסרטונים.

מיני-בקר מוצג עכשיו בתחתית המסך.

איור של iPhone שבו פועלת אפליקציית CastVideos, עם שלט המיני שמופיע בתחתית המסך

אפשר ללחוץ על לחצן ההשהיה במיני-בקר כדי להשהות את הסרטון במקלט. לוחצים על לחצן ההפעלה במיני-בקר כדי להמשיך להפעיל את הסרטון שוב.

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

4. מכינים את הפרויקט לתחילת העבודה

איור של iPhone שבו פועלת אפליקציית Castvideos

אנחנו צריכים להוסיף תמיכה ב-Google Cast לאפליקציה שהורדת. הנה כמה מונחים של Google Cast שנשתמש בהם ב-Codelab הזה:

  • אפליקציית שולח פועלת במכשיר נייד או במחשב נייד,
  • אפליקציית מקלט פועלת במכשיר Google Cast.

הגדרת פרויקט

עכשיו אתם מוכנים להתחיל לעבוד על הפרויקט לתחילת השימוש באמצעות Xcode:

  1. נכנסים לטרמינל ועוברים לספריית Codelab.
  2. מתקינים את יחסי התלות מה-Podfile.
cd app-start
pod update
pod install
  1. פותחים את Xcode ובוחרים באפשרות Open another project...
  2. בוחרים את הקובץ CastVideos-ios.xcworkspace מהספרייה סמל של תיקייהapp-start שבתיקיית הקוד לדוגמה.

עיצוב אפליקציות

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

האפליקציה מורכבת משני בקרי תצוגה עיקריים: MediaTableViewController ו-MediaViewController.

MediaTableViewController

רכיב UITableViewController הזה מציג רשימה של סרטונים ממכונה של MediaListModel. רשימת הסרטונים והמטא-נתונים שמשויכים אליהם מתארחים בשרת מרוחק כקובץ JSON. MediaListModel מאחזר את ה-JSON הזה ומעבד אותו כדי ליצור רשימה של MediaItem אובייקטים.

אובייקט MediaItem יוצר מודל של סרטון ואת המטא-נתונים שמשויכים אליו, כמו השם, התיאור, כתובת ה-URL של התמונה וכתובת ה-URL של השידור.

הפקודה MediaTableViewController יוצרת מכונה של MediaListModel ואז רושמת את עצמה כ-MediaListModelDelegate. כך נשלחת התראה כשמתבצעת הורדה של המטא-נתונים של המדיה, כדי שאפשר יהיה לטעון את תצוגת הטבלה.

למשתמש מוצגת רשימה של תמונות ממוזערות של סרטונים, עם תיאור קצר של כל סרטון. כשבוחרים פריט, הערך של MediaItem התואם מועבר אל MediaViewController.

MediaViewController

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

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

שאלות נפוצות

5. הוספת הלחצן להפעלת Cast

איור של השליש העליון של iPhone שבו פועלת אפליקציית Cast videos, לחצן הפעלת Cast בפינה הימנית העליונה.

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

תצורה

בפרויקט ההתחלה נדרשים אותם יחסי תלות והגדרת Xcode כמו שנדרשים עבור האפליקציה לדוגמה שהושלמה. חוזרים לקטע הזה ומבצעים את אותם שלבים כדי להוסיף את GoogleCast.framework לפרויקט ההתחלתי של האפליקציה.

אתחול

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

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

השיטה application(_:didFinishLaunchingWithOptions:) מתאימה גם להגדרת משתמש עם הרשאה לרישום ביומן לקבלת הודעות הרישום ביומן מ-Cast framework. המידע הזה יכול להועיל לניפוי באגים ולפתרון בעיות.

כשמפתחים אפליקציה משלכם שתומכת ב-Cast, צריך להירשם כמפתח Cast ולקבל מזהה אפליקציה לאפליקציה. ב-Codelab הזה, נשתמש במזהה אפליקציה לדוגמה.

צריך להוסיף את הקוד הבא אל AppDelegate.swift כדי לאתחל את GCKCastContext עם מזהה האפליקציה מברירות המחדל של המשתמש, ולהוסיף יומן עבור מסגרת Google Cast:

import GoogleCast

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  fileprivate var enableSDKLogging = true

  ...

  func application(_: UIApplication,
                   didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    ...
    let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
    options.physicalVolumeButtonsWillControlDeviceVolume = true
    GCKCastContext.setSharedInstanceWith(options)

    window?.clipsToBounds = true
    setupCastLogging()
    ...
  }
  ...
  func setupCastLogging() {
    let logFilter = GCKLoggerFilter()
    let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
                        "GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
    logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
    GCKLogger.sharedInstance().filter = logFilter
    GCKLogger.sharedInstance().delegate = self
  }
}

...

// MARK: - GCKLoggerDelegate

extension AppDelegate: GCKLoggerDelegate {
  func logMessage(_ message: String,
                  at _: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if enableSDKLogging {
      // Send SDK's log messages directly to the console.
      print("\(location): \(function) - \(message)")
    }
  }
}

לחצן הפעלת Cast

עכשיו, לאחר אתחול של GCKCastContext, צריך להוסיף את לחצן הפעלת Cast כדי לאפשר למשתמש לבחור מכשיר Cast. ה-Cast SDK מספק רכיב של לחצן הפעלת Cast שנקרא GCKUICastButton כמחלקה משנית של UIButton. אפשר להוסיף אותו לשורת הכותרת של האפליקציה על ידי גלישתה ב-UIBarButtonItem. צריך להוסיף את לחצן הפעלת Cast גם ל-MediaTableViewController וגם ל-MediaViewController.

מוסיפים את הקוד הבא אל MediaTableViewController.swift ואל MediaViewController.swift:

import GoogleCast

@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
  MediaListModelDelegate, GCKRequestDelegate {
  private var castButton: GCKUICastButton!
  ...
  override func viewDidLoad() {
    print("MediaTableViewController - viewDidLoad")
    super.viewDidLoad()

    ...
    castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
                                               width: CGFloat(24), height: CGFloat(24)))
    // Overwrite the UIAppearance theme in the AppDelegate.
    castButton.tintColor = UIColor.white
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)

    ...
  }
  ...
}

בשלב הבא צריך להוסיף את הקוד הבא ל-MediaViewController.swift:

import GoogleCast

@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
  LocalPlayerViewDelegate, GCKRequestDelegate {
  private var castButton: GCKUICastButton!
  ...
  override func viewDidLoad() {
    super.viewDidLoad()
    print("in MediaViewController viewDidLoad")
    ...
    castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
                                               width: CGFloat(24), height: CGFloat(24)))
    // Overwrite the UIAppearance theme in the AppDelegate.
    castButton.tintColor = UIColor.white
    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)

    ...
  }
  ...
}

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

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

6. העברה (cast) של תוכן וידאו

איור של אפליקציית iPhone שפועלת בו אפליקציית Cast videos, שבה מוצגים פרטים על סרטון מסוים ('קרעות פלדה'). בחלק התחתון נמצא המיני-נגן

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

הפעלת Cast של מדיה

ככלל, אם רוצים להפעיל מדיה במכשיר Cast, צריכים להתקיים התנאים הבאים:

  1. יוצרים אובייקט GCKMediaInformation מה-Cast SDK כדי ליצור מודל של פריט מדיה.
  2. המשתמש מתחבר למכשיר Cast כדי להפעיל את אפליקציית המקבל.
  3. טוענים את האובייקט GCKMediaInformation במקלט ומפעילים את התוכן.
  4. עוקבים אחרי סטטוס המדיה.
  5. שליחת פקודות הפעלה למקלט על סמך אינטראקציות של המשתמש.

שלב 1 מסתכם במיפוי של אובייקט אחד לאובייקט אחר; GCKMediaInformation הוא משהו ש-Cast SDK מבין, ו-MediaItem הוא הסתרת פריט מדיה באפליקציה שלנו; אנחנו יכולים למפות בקלות MediaItem לGCKMediaInformation. כבר ביצענו את שלב 2 בקטע הקודם. קל לעשות את שלב 3 באמצעות Cast SDK.

האפליקציה לדוגמה MediaViewController כבר יוצרת הבחנה בין הפעלה מקומית לבין הפעלה מרחוק באמצעות enum הבא:

enum PlaybackMode: Int {
  case none = 0
  case local
  case remote
}

private var playbackMode = PlaybackMode.none

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

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

ניהול הפעלת Cast

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

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

סשנים של הפעלות Cast מנוהלים על ידי GCKSessionManager, שניתן לגשת אליו דרך GCKCastContext.sharedInstance().sessionManager. הקריאות החוזרות (callback) של GCKSessionManagerListener יכולות לשמש למעקב אחר אירועי סשנים, כמו יצירה, השעיה, המשך וסיום.

קודם צריך לרשום את ה-session שלנו (session Listener) ולאתחל כמה משתנים:

class MediaViewController: UIViewController, GCKSessionManagerListener,
  GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {

  ...
  private var sessionManager: GCKSessionManager!
  ...

  required init?(coder: NSCoder) {
    super.init(coder: coder)

    sessionManager = GCKCastContext.sharedInstance().sessionManager

    ...
  }

  override func viewWillAppear(_ animated: Bool) {
    ...

    let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
    if hasConnectedSession, (playbackMode != .remote) {
      populateMediaInfo(false, playPosition: 0)
      switchToRemotePlayback()
    } else if sessionManager.currentSession == nil, (playbackMode != .local) {
      switchToLocalPlayback()
    }

    sessionManager.add(self)

    ...
  }

  override func viewWillDisappear(_ animated: Bool) {
    ...

    sessionManager.remove(self)
    sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
    ...
    super.viewWillDisappear(animated)
  }

  func switchToLocalPlayback() {
    ...

    sessionManager.currentCastSession?.remoteMediaClient?.remove(self)

    ...
  }

  func switchToRemotePlayback() {
    ...

    sessionManager.currentCastSession?.remoteMediaClient?.add(self)

    ...
  }


  // MARK: - GCKSessionManagerListener

  func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
    print("MediaViewController: sessionManager didStartSession \(session)")
    setQueueButtonVisible(true)
    switchToRemotePlayback()
  }

  func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
    print("MediaViewController: sessionManager didResumeSession \(session)")
    setQueueButtonVisible(true)
    switchToRemotePlayback()
  }

  func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
    print("session ended with error: \(String(describing: error))")
    let message = "The Casting session has ended.\n\(String(describing: error))"
    if let window = appDelegate?.window {
      Toast.displayMessage(message, for: 3, in: window)
    }
    setQueueButtonVisible(false)
    switchToLocalPlayback()
  }

  func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
    if let error = error {
      showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
    }
    setQueueButtonVisible(false)
  }

  func sessionManager(_: GCKSessionManager,
                      didFailToResumeSession _: GCKSession, withError _: Error?) {
    if let window = UIApplication.shared.delegate?.window {
      Toast.displayMessage("The Casting session could not be resumed.",
                           for: 3, in: window)
    }
    setQueueButtonVisible(false)
    switchToLocalPlayback()
  }

  ...
}

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

הסשן הפעיל כרגע זמין בתור GCKCastContext.sharedInstance().sessionManager.currentCastSession. סשנים נוצרים ומסתיימים באופן אוטומטי בתגובה לתנועות משתמשים בתיבות הדו-שיח של Cast.

המדיה בטעינה

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

כדי לטעון במקלט את הסרטון הנוכחי שנבחר, צריך להוסיף אל MediaViewController.swift את הקוד הבא:

@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
  GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
  ...

  @objc func playSelectedItemRemotely() {
    loadSelectedItem(byAppending: false)
  }

  /**
   * Loads the currently selected item in the current cast media session.
   * @param appending If YES, the item is appended to the current queue if there
   * is one. If NO, or if
   * there is no queue, a new queue containing only the selected item is created.
   */
  func loadSelectedItem(byAppending appending: Bool) {
    print("enqueue item \(String(describing: mediaInfo))")
    if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
      let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
      mediaQueueItemBuilder.mediaInformation = mediaInfo
      mediaQueueItemBuilder.autoplay = true
      mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
      let mediaQueueItem = mediaQueueItemBuilder.build()
      if appending {
        let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
        request.delegate = self
      } else {
        let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
        queueDataBuilder.items = [mediaQueueItem]
        queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off

        let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
        mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
        mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()

        let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
        request.delegate = self
      }
    }
  }
  ...
}

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

required init?(coder: NSCoder) {
  super.init(coder: coder)
  ...
  castMediaController = GCKUIMediaController()
  ...
}

func switchToLocalPlayback() {
  print("switchToLocalPlayback")
  if playbackMode == .local {
    return
  }
  setQueueButtonVisible(false)
  var playPosition: TimeInterval = 0
  var paused: Bool = false
  var ended: Bool = false
  if playbackMode == .remote {
    playPosition = castMediaController.lastKnownStreamPosition
    paused = (castMediaController.lastKnownPlayerState == .paused)
    ended = (castMediaController.lastKnownPlayerState == .idle)
    print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
  }
  populateMediaInfo((!paused && !ended), playPosition: playPosition)
  sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
  playbackMode = .local
}

func switchToRemotePlayback() {
  print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
  if playbackMode == .remote {
    return
  }
  // If we were playing locally, load the local media on the remote player
  if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
    print("loading media: \(String(describing: mediaInfo))")
    let paused: Bool = (_localPlayerView.playerState == .paused)
    let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
    mediaQueueItemBuilder.mediaInformation = mediaInfo
    mediaQueueItemBuilder.autoplay = !paused
    mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
    mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
    let mediaQueueItem = mediaQueueItemBuilder.build()

    let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
    queueDataBuilder.items = [mediaQueueItem]
    queueDataBuilder.repeatMode = .off

    let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
    mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()

    let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
    request?.delegate = self
  }
  _localPlayerView.stop()
  _localPlayerView.showSplashScreen()
  setQueueButtonVisible(true)
  sessionManager.currentCastSession?.remoteMediaClient?.add(self)
  playbackMode = .remote
}

/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
  let hasConnectedCastSession = sessionManager.hasConnectedCastSession
  if mediaInfo != nil, hasConnectedCastSession() {
    // Display an alert box to allow the user to add to queue or play
    // immediately.
    if actionSheet == nil {
      actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
      actionSheet?.addAction(withTitle: "Play Now", target: self,
                             selector: #selector(playSelectedItemRemotely))
    }
    actionSheet?.present(in: self, sourceView: _localPlayerView)
    return false
  }
  return true
}

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

7. מיני-בקר

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

איור של החלק התחתון של iPhone שפועלת בו אפליקציית Cast videos, תוך התמקדות במיני-בקר

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

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

משנים את הקובץ AppDelegate.swift ומוסיפים את הקוד הבא לתנאי if useCastContainerViewController בשיטה הבאה:

func application(_: UIApplication,
                 didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  ...
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
    as? UINavigationController else { return false }
  let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
    as GCKUICastContainerViewController
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window?.rootViewController = castContainerVC
  window?.makeKeyAndVisible()
  ...
}

מוסיפים את המאפיין הזה ואת ה-setter/getter כדי לשלוט בחשיפה של המיני-בקר (נשתמש בהם בקטע מאוחר יותר):

var isCastControlBarsEnabled: Bool {
    get {
      if useCastContainerViewController {
        let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
        return castContainerVC!.miniMediaControlsItemEnabled
      } else {
        let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
        return rootContainerVC!.miniMediaControlsViewEnabled
      }
    }
    set(notificationsEnabled) {
      if useCastContainerViewController {
        var castContainerVC: GCKUICastContainerViewController?
        castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
        castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
      } else {
        var rootContainerVC: RootContainerViewController?
        rootContainerVC = (window?.rootViewController as? RootContainerViewController)
        rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
      }
    }
  }

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

8. שכבת-על של סרטון היכרות

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

איור של iPhone שפועלת בו אפליקציית Cast videos עם שכבת-על של לחצן הפעלת Cast, שבה מודגש הלחצן 'הפעלת Cast' ומוצגת ההודעה 'יש לגעת כדי להפעיל Cast של מדיה אל הטלוויזיה ואל הרמקולים'

לכיתה GCKCastContext יש שיטה presentCastInstructionsViewControllerOnce, שבעזרתה ניתן להדגיש את לחצן הפעלת Cast כשהוא מוצג למשתמשים לראשונה. מוסיפים את הקוד הבא אל MediaViewController.swift ואל MediaTableViewController.swift:

override func viewDidLoad() {
  ...

  NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
                                         name: NSNotification.Name.gckCastStateDidChange,
                                         object: GCKCastContext.sharedInstance())
}

@objc func castDeviceDidChange(_: Notification) {
  if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
    // You can present the instructions on how to use Google Cast on
    // the first time the user uses you app
    GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
  }
}

מפעילים את האפליקציה במכשיר הנייד ו אמורה להופיע שכבת-על ראשונית.

9. שלט רחוק מורחב

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

איור של iPhone שפועלת בו אפליקציית Cast videos ומופעל בו סרטון כשהבקר המורחב מופיע בתחתית המסך

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

הפונקציונליות של התצוגה הזו מיושמת על ידי המחלקה GCKUIExpandedMediaControlsViewController.

הדבר הראשון שצריך לעשות הוא להפעיל את השלט הרחוק המורחב שמוגדר כברירת מחדל בהקשר של הפעלת Cast. משנים את AppDelegate.swift כדי להפעיל את השלט הרחוק המורחב שמוגדר כברירת מחדל:

import GoogleCast

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  ...

  func application(_: UIApplication,
                   didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...
    // Add after the setShareInstanceWith(options) is set.
    GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
    ...
  }
  ...
}

מוסיפים את הקוד הבא אל MediaViewController.swift כדי לטעון את הבקר המורחב כשהמשתמש מתחיל להעביר (cast) סרטון:

@objc func playSelectedItemRemotely() {
  ...
  appDelegate?.isCastControlBarsEnabled = false
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}

הבקר המורחב יופעל גם באופן אוטומטי כשהמשתמש יקיש על המיני-בקר.

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

10. הוספת תמיכה ב-Cast Connect

ספריית Cast Connect מאפשרת לאפליקציות שולח קיימות לתקשר עם אפליקציות ל-Android TV באמצעות פרוטוקול Cast. Cast Connect מבוסס על התשתית של Cast, כשאפליקציית Android TV פועלת כמקלט.

יחסי תלות

ב-Podfile, יש לוודא שה-google-cast-sdk מופנה אל 4.4.8 ומעלה, כפי שמפורט בהמשך. אם ביצעתם שינוי בקובץ, מריצים את הפקודה pod update מהמסוף כדי לסנכרן את השינוי עם הפרויקט.

pod 'google-cast-sdk', '>=4.4.8'

GCKLaunchOptions

כדי להפעיל את האפליקציה Android TV, שנקראת גם Android היעדים, צריך להגדיר את הדגל androidReceiverCompatible כ-true באובייקט GCKLaunchOptions. אובייקט GCKLaunchOptions הזה קובע את אופן ההפעלה של המקבל ומועבר אל GCKCastOptions שמוגדר במכונה המשותפת באמצעות GCKCastContext.setSharedInstanceWith.

עליך להוסיף את השורות הבאות ל-AppDelegate.swift:

let options = GCKCastOptions(discoveryCriteria:
                          GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions

GCKCastContext.setSharedInstanceWith(options)

הגדרת פרטי הכניסה להפעלה

בצד השולח, אפשר לציין את GCKCredentialsData כדי לייצג את מי שיצטרף לסשן. הערך credentials הוא מחרוזת שאפשר להגדיר על ידי המשתמש, כל עוד אפליקציית ATV יכולה להבין אותה. GCKCredentialsData מועבר לאפליקציה ל-Android TV רק בזמן ההפעלה או ההצטרפות. אם תגדירו אותה שוב בזמן החיבור, היא לא תועבר לאפליקציה ל-Android TV.

כדי להגדיר את פרטי הכניסה להפעלה, צריך להגדיר את GCKCredentialsData בכל שלב אחרי הגדרת GCKLaunchOptions. כדי להמחיש זאת, נוסיף לוגיקה ללחצן Creds כדי להגדיר את פרטי הכניסה שיועברו כשהסשן ייווצר. צריך להוסיף את הקוד הבא לMediaTableViewController.swift:

class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
  ...
  private var credentials: String? = nil
  ...
  override func viewDidLoad() {
    ...
    navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
                                                       target: self, action: #selector(toggleLaunchCreds))
    ...
    setLaunchCreds()
  }
  ...
  @objc func toggleLaunchCreds(_: Any){
    if (credentials == nil) {
        credentials = "{\"userId\":\"id123\"}"
    } else {
        credentials = nil
    }
    Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
    print("Credentials set: "+(credentials ?? "Null"))
    setLaunchCreds()
  }
  ...
  func setLaunchCreds() {
    GCKCastContext.sharedInstance()
        .setLaunch(GCKCredentialsData(credentials: credentials))
  }
}

הגדרת פרטי כניסה בבקשה לטעינה

כדי לטפל ב-credentials באפליקציות של המקלט באינטרנט וגם באפליקציות של Android TV, צריך להוסיף את הקוד הבא לכיתה MediaTableViewController.swift בפונקציה loadSelectedItem:

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...

בהתאם לאפליקציית המקבל שאליה השולח מעביר את התוכן, ה-SDK יחיל באופן אוטומטי את פרטי הכניסה שלמעלה על הסשן המתמשך.

בדיקת Cast Connect

שלבים להתקנת ה-APK של Android TV ב-Chromecast with Google TV

  1. מאתרים את כתובת ה-IP של מכשיר Android TV. בדרך כלל, המדיניות זמינה בקטע הגדרות > רשת ו אינטרנט > (שם הרשת שאליה המכשיר מחובר). בצד שמאל יוצגו הפרטים ואת כתובת ה-IP של המכשיר שלך ברשת.
  2. משתמשים בכתובת ה-IP של המכשיר כדי להתחבר אליו דרך ADB באמצעות הטרמינל:
$ adb connect <device_ip_address>:5555
  1. מחלון הטרמינל, עוברים לתיקייה ברמה העליונה של דוגמאות ה-Codelab שהורדתם בתחילת השיעור הזה ב-Codelab. לדוגמה:
$ cd Desktop/ios_codelab_src
  1. כדי להתקין את קובץ ה-APK שבתיקייה הזו ב-Android TV, מריצים את:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
  1. עכשיו אמורה להופיע אפליקציה בשם העברת סרטונים בתפריט האפליקציות שלך במכשיר Android TV.
  2. בסיום, יש לבנות את האפליקציה ולהפעיל אותה באמולטור או במכשיר נייד. כשיוצרים סשן של הפעלת Cast במכשיר Android TV, האפליקציה אמורה להפעיל את האפליקציה Android Acceptr ב-Android TV. הפעלת סרטון מהשולח של המכשיר הנייד ב-iOS אמורה להפעיל את הסרטון במקלט Android ולאפשר לכם לשלוט בהפעלה באמצעות השלט הרחוק של מכשיר Android TV.

11. התאמה אישית של ווידג'טים של הפעלת Cast

אתחול

מתחילים בתיקייה App-Done. צריך להוסיף את הערכים הבאים ל-method applicationDidFinishLaunchingWithOptions בקובץ AppDelegate.swift.

func application(_: UIApplication,
                 didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  ...
  let styler = GCKUIStyle.sharedInstance()
  ...
}

אחרי שתסיימו להחיל התאמה אישית אחת או יותר כפי שמתואר בהמשך הקוד, תוכלו לבצע את השמירה של הסגנונות באמצעות הקוד הבא:

styler.apply()

התאמה אישית של תצוגות Cast

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

styler.castViews.iconTintColor = .lightGray

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

styler.castViews.mediaControl.expandedController.iconTintColor = .green

הצבעים משתנים

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

styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow

שינוי גופנים

ניתן להתאים אישית גופנים לתוויות שונות המוצגות בתצוגות Cast. נגדיר את כל הגופנים ל-'Courier-Oblique' למטרות המחשה.

styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)

שינוי של תמונות הלחצן שמוגדרות כברירת מחדל

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

let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
  styler.castViews.muteOnImage = muteOnImage
}

שינוי העיצוב של הלחצן להפעלת Cast

אפשר גם לעצב ווידג'טים להעברה באמצעות פרוטוקול UIAppearance. נושאי הקוד הבאים של לחצן GCKUICast בכל התצוגות שבהן הוא מופיע:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. מזל טוב

עכשיו אתם יודעים איך להפעיל Cast של אפליקציית וידאו באמצעות הווידג'טים של Cast SDK ב-iOS.

פרטים נוספים זמינים במדריך למפתחים בנושא שליחת הודעות ב-iOS.