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

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

הלוגו של Google Cast

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

מה זה Google Cast?

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

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

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

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

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

מה תלמדו

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

מה הדרישות כדי להצטרף לתוכנית?

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

ניסיון

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

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

קריאה בלבד קריאה והשלמה של התרגילים

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

מתחילים בינוני ידע

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

מתחילים בינוני ידע

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

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

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

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

הלוגו של Apple iOS

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

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

שאלות נפוצות

הגדרה של CocoaPods

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

sudo gem install cocoapods

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

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

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

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

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

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

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

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

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

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

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

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

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

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

מיני שלט רחוק עכשיו גלוי בחלק התחתון של המסך.

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

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

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

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

איור של אפליקציית CastVideos ב-iPhone

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

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

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

עכשיו הכול מוכן ואפשר להתחיל להשתמש ב-Xcode כדי להוסיף לפרויקטים המקוריים:

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

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

הגדרות אישיות

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

אתחול

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

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

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

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

מוסיפים את הקוד הבא אל AppDelegate.swift כדי לאתחל את GCKCastContext עם מזהה האפליקציה מהגדרות ברירת המחדל של המשתמש, ומוסיפים יומן רישום עבור ה-framework של 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 מספק רכיב של לחצן העברה בשם 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) של תוכן וידאו

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

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

העברה (cast) של מדיה

ככלל, כדי להפעיל מדיה במכשיר CAST, הדברים הבאים צריכים להתרחש:

  1. מה-SDK של Cast יוצרים אובייקט GCKMediaInformation שיוצר מודל של פריט מדיה.
  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 framework.

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

קודם כול, עלינו לרשום את ה-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 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
      }
    }
  }
  ...
}

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

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 דורשת שכל אפליקציות ההעברה יספקו בקר מיני שיופיע כשהמשתמש יוצא מדף התוכן הנוכחי. המיני-בקר מספק גישה מיידית ותזכורת גלויה להפעלת ההעברה הנוכחית.

איור של החלק התחתון של iPhone שבו פועלת אפליקציית CastVideos עם התמקדות במיני-בקר

ה-Cast SDK מספק סרגל בקרה, 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()
  ...
}

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

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

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

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

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

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

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

אתחול

מתחילים בתיקייה 'App-Done' (סיום האפליקציה). צריך להוסיף את הטקסט הבא ל-method applicationDidFinishLaunchingWithOptions בקובץ ה-AppDelegate.swift.

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

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

styler.apply()

התאמה אישית של תצוגות ההעברה

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

styler.castViews.iconTintColor = .lightGray

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

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

שינוי צבעים

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

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

שינוי גופנים

ניתן להתאים אישית גופנים לתוויות שונות המופיעות בתצוגות העברה. לצורך המחשה, נגדיר את כל הגופנים למצב'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 באמצעות UIמראה פרוטוקול. העיצוב הבא של GCKUICastbutton מכסה את כל התצוגות שבהן הוא מופיע:

GCKUICastButton.appearance().tintColor = UIColor.gray

12. מזל טוב

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

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