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

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

הלוגו של Google Cast

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

מה זה Google Cast?

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

באמצעות Google Cast SDK תוכלו להרחיב את האפליקציה שלכם כך שתשלוט במכשירים שתומכים ב-Google Cast (כמו טלוויזיה או מערכת שמע). באמצעות Cast SDK אפשר להוסיף את רכיבי ממשק המשתמש הנדרשים על סמך רשימת המשימות לעיצוב של 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 שפועלת בו אפליקציית Cast videos כשהמיני-בקר מופיע בתחתית המסך

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

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

4. הכנת פרויקט ההתחלה

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

אנחנו צריכים להוסיף תמיכה ב-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

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

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

שאלות נפוצות

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

איור של השליש העליון של מכשיר iPhone שבו פועלת אפליקציית CastVideos, מוצג בו לחצן ההעברה (cast) בפינה השמאלית העליונה

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

תצורה

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

אתחול

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

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

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

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

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

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 frame

העברת מדיה

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

ניהול סשן של הפעלת Cast

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

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

סשנים של הפעלת Cast מנוהלים על ידי GCKSessionManager, שאליו אפשר לגשת דרך GCKCastContext.sharedInstance().sessionManager. אפשר להשתמש בקריאות החזרה (callbacks) של 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 שתומך בהפעלת מדיה, ה-SDK ייצור באופן אוטומטי מופע של GCKRemoteMediaClient. אפשר לגשת אליו כנכס 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 צריכות לספק שליטה מינימליסטית שתופיע כשהמשתמש מנווט אל מחוץ לדף התוכן הנוכחי. בנגן המיני יש גישה מיידית ותזכורת חזותית לסשן ההעברה הנוכחי.

איור של החלק התחתון של 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 שבו פועלת אפליקציית CastVideos עם שכבת-העל של לחצן ההעברה (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 שבו פועלת אפליקציית CastVideos, מוצג סרטון והשלט הרחוק המורחב מופיע בתחתית המסך

הבקר המורחב מאפשר תצוגת מסך מלא שמאפשרת שליטה מלאה בהפעלת המדיה מרחוק. התצוגה הזו צריכה לאפשר לאפליקציית 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 Receiver, צריך להגדיר את הדגל 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 Receiver, מוסיפים את הקוד הבא לכיתה MediaTableViewController.swift, מתחת לפונקציה loadSelectedItem:

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

בהתאם לאפליקציית המקבל שאליה השולח מבצע העברה (cast), ה-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 שהורדתם בתחילת הקודלהב. לדוגמה:
$ 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 Receiver ב-Android TV. הפעלת סרטון מהמכשיר הנייד עם iOS אמורה להפעיל את הסרטון במכשיר Android Receiver ולאפשר לכם לשלוט בהפעלה באמצעות השלט הרחוק של מכשיר Android TV.

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

אתחול

מתחילים בתיקייה App-Done. מוסיפים את הטקסט הבא לשיטה applicationDidFinishLaunchingWithOptions בקובץ AppDelegate.swift.

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

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

styler.apply()

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

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

styler.castViews.iconTintColor = .lightGray

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

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

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

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

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

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

GCKUICastButton.appearance().tintColor = UIColor.gray

12. מזל טוב

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

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