Mengintegrasikan Cast ke Aplikasi iOS

Panduan developer ini menjelaskan cara menambahkan dukungan Google Cast ke aplikasi pengirim iOS menggunakan iOS Sender SDK.

Perangkat seluler atau laptop adalah pengirim yang mengontrol pemutaran, dan perangkat Google Cast adalah penerima yang menampilkan konten di TV.

Framework pengirim mengacu pada biner library class Cast dan resource terkait yang ada saat runtime pada pengirim. Aplikasi pengirim atau aplikasi Cast mengacu pada aplikasi yang juga berjalan di pengirim. Aplikasi Penerima Web mengacu pada aplikasi HTML yang berjalan di Penerima Web.

Framework pengirim menggunakan desain callback asinkron untuk memberi tahu aplikasi pengirim peristiwa dan untuk melakukan transisi antara berbagai status siklus proses aplikasi Cast.

Alur aplikasi

Langkah-langkah berikut menjelaskan alur eksekusi tingkat tinggi yang umum untuk aplikasi iOS pengirim:

  • Framework Cast akan memulai GCKDiscoveryManager berdasarkan properti yang disediakan di GCKCastOptions untuk mulai memindai perangkat.
  • Saat pengguna mengklik tombol Cast, framework akan menyajikan dialog Transmisi dengan daftar perangkat Cast yang ditemukan.
  • Saat pengguna memilih perangkat Cast, framework akan mencoba meluncurkan aplikasi Penerima Web di perangkat Transmisi.
  • Framework ini memanggil callback di aplikasi pengirim untuk mengonfirmasi bahwa aplikasi Web Receiver diluncurkan.
  • Framework ini membuat saluran komunikasi antara aplikasi pengirim dan Penerima Web.
  • Framework ini menggunakan saluran komunikasi untuk memuat dan mengontrol pemutaran media di Penerima Web.
  • Framework menyinkronkan status pemutaran media antara pengirim dan Penerima Web: saat pengguna melakukan tindakan UI pengirim, framework akan meneruskan permintaan kontrol media tersebut ke Penerima Web, dan saat Penerima Web mengirim pembaruan status media, framework akan memperbarui status UI pengirim.
  • Saat pengguna mengklik tombol Cast untuk memutuskan koneksi dari perangkat Cast, framework akan memutuskan koneksi aplikasi pengirim dari Penerima Web.

Untuk memecahkan masalah pengirim, Anda harus mengaktifkan logging.

Untuk daftar lengkap semua class, metode, dan peristiwa dalam framework iOS Google Cast, lihat Referensi Google Cast iOS API. Bagian berikut membahas langkah-langkah mengintegrasikan Cast ke aplikasi iOS.

Memanggil metode dari thread utama

Melakukan inisialisasi konteks Cast

Framework Cast memiliki objek singleton global, GCKCastContext, yang mengkoordinasikan semua aktivitas framework. Objek ini harus diinisialisasi di awal siklus proses aplikasi, biasanya dalam metode -[application:didFinishLaunchingWithOptions:] di delegasi aplikasi, agar pelanjutan sesi otomatis saat aplikasi pengirim dimulai ulang dapat dipicu dengan benar.

Objek GCKCastOptions harus diberikan saat melakukan inisialisasi GCKCastContext. Class ini berisi opsi yang memengaruhi perilaku framework. Yang paling penting adalah ID aplikasi Web Receiver, yang digunakan untuk memfilter hasil penemuan dan meluncurkan aplikasi Web Receiver saat sesi Cast dimulai.

Metode -[application:didFinishLaunchingWithOptions:] juga merupakan tempat yang tepat untuk menyiapkan delegasi logging guna menerima pesan logging dari framework. Template ini dapat berguna untuk proses debug dan pemecahan masalah.

Swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                    initWithApplicationID:kReceiverAppID];
  GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
  [GCKCastContext setSharedInstanceWithOptions:options];

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

Widget Cast UX

Cast iOS SDK menyediakan widget berikut yang sesuai dengan Checklist Desain Transmisi:

  • Overlay Pengantar: Class GCKCastContext memiliki metode, presentCastInstructionsViewControllerOnceWithCastButton, yang dapat digunakan untuk menyoroti tombol Cast saat Penerima Web pertama kali tersedia. Aplikasi pengirim dapat menyesuaikan teks, posisi teks judul, dan tombol Tutup.

  • Tombol Cast: Mulai dari SDK pengirim iOS Cast 4.6.0, tombol transmisi selalu terlihat saat perangkat pengirim terhubung ke Wi-Fi. Saat pengguna mengetuk tombol Cast untuk pertama kalinya setelah memulai aplikasi pertama, dialog izin akan muncul sehingga pengguna dapat memberikan akses jaringan lokal aplikasi ke perangkat di jaringan. Selanjutnya, saat pengguna mengetuk tombol transmisi, dialog cast akan ditampilkan yang mencantumkan perangkat yang ditemukan. Ketika pengguna mengetuk tombol transmisi saat perangkat terhubung, perangkat akan menampilkan metadata media saat ini (seperti judul, nama studio rekaman, dan gambar thumbnail) atau memungkinkan pengguna memutuskan koneksi dari perangkat transmisi. Jika pengguna mengetuk tombol transmisi ketika tidak ada perangkat yang tersedia, layar akan ditampilkan untuk memberikan informasi kepada pengguna tentang alasan perangkat tidak ditemukan dan cara memecahkan masalah.

  • Pengontrol Mini: Saat pengguna mentransmisikan konten dan telah keluar dari halaman konten saat ini atau memperluas pengontrol ke layar lain di aplikasi pengirim, pengontrol mini akan ditampilkan di bagian bawah layar untuk memungkinkan pengguna melihat metadata media yang sedang melakukan transmisi dan untuk mengontrol pemutaran.

  • Pengontrol yang Diperluas: Saat pengguna mentransmisikan konten, jika mereka mengklik notifikasi media atau pengontrol mini, pengontrol yang diperluas akan diluncurkan, yang menampilkan metadata media yang sedang diputar dan menyediakan beberapa tombol untuk mengontrol pemutaran media.

Menambahkan tombol Cast

Framework ini menyediakan komponen tombol Cast sebagai subclass UIButton. Ini dapat ditambahkan ke panel judul aplikasi dengan menggabungkannya dalam UIBarButtonItem. Subclass UIViewController standar dapat menginstal tombol Cast sebagai berikut:

Swift
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
Objective-C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

Secara default, mengetuk tombol akan membuka dialog Cast yang disediakan oleh framework.

GCKUICastButton juga dapat ditambahkan langsung ke storyboard.

Mengonfigurasi penemuan perangkat

Dalam framework tersebut, penemuan perangkat terjadi secara otomatis. Tidak perlu memulai atau menghentikan proses penemuan secara eksplisit kecuali jika Anda mengimplementasikan UI kustom.

Penemuan dalam framework dikelola oleh class GCKDiscoveryManager, yang merupakan properti GCKCastContext. Framework ini menyediakan komponen dialog Cast default untuk pemilihan dan kontrol perangkat. Daftar perangkat diurutkan secara leksikografis berdasarkan nama yang sesuai untuk perangkat.

Cara kerja pengelolaan sesi

SDK Cast memperkenalkan konsep sesi Cast, yang menggabungkan langkah-langkah untuk menghubungkan ke perangkat, meluncurkan (atau bergabung ke) aplikasi Penerima Web, terhubung ke aplikasi tersebut, dan menginisialisasi saluran kontrol media. Lihat Panduan siklus proses aplikasi Penerima Web untuk mengetahui informasi selengkapnya tentang sesi Transmisi dan siklus proses Penerima Web.

Sesi dikelola oleh class GCKSessionManager yang merupakan properti GCKCastContext. Masing-masing sesi direpresentasikan oleh subclass class GCKSession: misalnya, GCKCastSession mewakili sesi dengan perangkat Transmisi. Anda dapat mengakses sesi Transmisi yang sedang aktif (jika ada), sebagai properti currentCastSession dari GCKSessionManager.

Antarmuka GCKSessionManagerListener dapat digunakan untuk memantau peristiwa sesi, seperti pembuatan sesi, penangguhan, dimulainya kembali, dan penghentian. Framework ini akan secara otomatis menangguhkan sesi saat aplikasi pengirim beralih ke latar belakang dan mencoba melanjutkan sesi saat aplikasi kembali ke latar depan (atau diluncurkan kembali setelah penghentian aplikasi yang tidak normal/tiba-tiba saat sesi aktif).

Jika dialog Cast sedang digunakan, sesi akan dibuat dan dihapus secara otomatis sebagai respons terhadap gestur pengguna. Jika tidak, aplikasi dapat memulai dan mengakhiri sesi secara eksplisit melalui metode di GCKSessionManager.

Jika aplikasi perlu melakukan pemrosesan khusus sebagai respons terhadap peristiwa siklus proses sesi, aplikasi dapat mendaftarkan satu atau beberapa instance GCKSessionManagerListener dengan GCKSessionManager. GCKSessionManagerListener adalah protokol yang menentukan callback untuk peristiwa seperti awal sesi, akhir sesi, dan sebagainya.

Transfer streaming

Mempertahankan status sesi adalah dasar transfer streaming, tempat pengguna dapat memindahkan streaming audio dan video yang ada di seluruh perangkat menggunakan perintah suara, Aplikasi Google Home, atau layar smart. Media berhenti diputar di satu perangkat (sumber) dan berlanjut di perangkat lain (tujuan). Setiap perangkat Cast dengan firmware terbaru dapat berfungsi sebagai sumber atau tujuan dalam transfer streaming.

Untuk mendapatkan perangkat tujuan baru selama transfer streaming, gunakan properti GCKCastSession#device selama callback [sessionManager:didResumeCastSession:].

Lihat Transfer streaming di Penerima Web untuk mengetahui informasi selengkapnya.

Penyambungan ulang otomatis

Framework Cast menambahkan logika penghubungan kembali untuk otomatis menangani koneksi ulang dalam banyak kasus sudut halus, seperti:

  • Memulihkan Wi-Fi untuk sementara waktu
  • Pulihkan dari mode tidur perangkat
  • Memulihkan dari latar belakang aplikasi
  • Memulihkan jika aplikasi mengalami error

Cara kerja kontrol media

Jika sesi Cast dibuat dengan aplikasi Penerima Web yang mendukung namespace media, instance GCKRemoteMediaClient akan otomatis dibuat oleh framework; instance tersebut dapat diakses sebagai properti remoteMediaClient dari instance GCKCastSession.

Semua metode di GCKRemoteMediaClient yang mengeluarkan permintaan ke Penerima Web akan menampilkan objek GCKRequest yang dapat digunakan untuk melacak permintaan tersebut. GCKRequestDelegate dapat ditetapkan ke objek ini untuk menerima notifikasi tentang hasil akhir operasi.

Instance GCKRemoteMediaClient diharapkan dibagikan oleh beberapa bagian aplikasi, dan memang beberapa komponen internal framework seperti dialog Cast dan kontrol media mini yang membagikan instance tersebut. Untuk itu, GCKRemoteMediaClient mendukung pendaftaran beberapa GCKRemoteMediaClientListener.

Menetapkan metadata media

Class GCKMediaMetadata mewakili informasi tentang item media yang ingin Anda transmisikan. Contoh berikut membuat instance GCKMediaMetadata baru dari sebuah film dan menetapkan judul, subtitel, nama studio rekaman, dan dua gambar.

Swift
let metadata = GCKMediaMetadata()
metadata.setString("Big Buck Bunny (2008)", forKey: kGCKMetadataKeyTitle)
metadata.setString("Big Buck Bunny tells the story of a giant rabbit with a heart bigger than " +
  "himself. When one sunny day three rodents rudely harass him, something " +
  "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon " +
  "tradition he prepares the nasty rodents a comical revenge.",
                   forKey: kGCKMetadataKeySubtitle)
metadata.addImage(GCKImage(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg")!,
                           width: 480,
                           height: 360))
Objective-C
GCKMediaMetadata *metadata = [[GCKMediaMetadata alloc]
                                initWithMetadataType:GCKMediaMetadataTypeMovie];
[metadata setString:@"Big Buck Bunny (2008)" forKey:kGCKMetadataKeyTitle];
[metadata setString:@"Big Buck Bunny tells the story of a giant rabbit with a heart bigger than "
 "himself. When one sunny day three rodents rudely harass him, something "
 "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon "
 "tradition he prepares the nasty rodents a comical revenge."
             forKey:kGCKMetadataKeySubtitle];
[metadata addImage:[[GCKImage alloc]
                    initWithURL:[[NSURL alloc] initWithString:@"https://commondatastorage.googleapis.com/"
                                 "gtv-videos-bucket/sample/images/BigBuckBunny.jpg"]
                    width:480
                    height:360]];

Lihat bagian Pemilihan dan Penyimpanan Gambar dalam Cache tentang penggunaan gambar dengan metadata media.

Memuat media

Untuk memuat item media, buat instance GCKMediaInformation menggunakan metadata media. Lalu, dapatkan GCKCastSession saat ini dan gunakan GCKRemoteMediaClient untuk memuat media di aplikasi penerima. Selanjutnya, Anda dapat menggunakan GCKRemoteMediaClient untuk mengontrol aplikasi pemutar media yang berjalan di penerima, seperti untuk memutar, menjeda, dan berhenti.

Swift
let url = URL.init(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
guard let mediaURL = url else {
  print("invalid mediaURL")
  return
}

let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL)
mediaInfoBuilder.streamType = GCKMediaStreamType.none;
mediaInfoBuilder.contentType = "video/mp4"
mediaInfoBuilder.metadata = metadata;
mediaInformation = mediaInfoBuilder.build()

guard let mediaInfo = mediaInformation else {
  print("invalid mediaInformation")
  return
}

if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInfo) {
  request.delegate = self
}
Objective-C
GCKMediaInformationBuilder *mediaInfoBuilder =
  [[GCKMediaInformationBuilder alloc] initWithContentURL:
   [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"]];
mediaInfoBuilder.streamType = GCKMediaStreamTypeNone;
mediaInfoBuilder.contentType = @"video/mp4";
mediaInfoBuilder.metadata = metadata;
self.mediaInformation = [mediaInfoBuilder build];

GCKRequest *request = [self.sessionManager.currentSession.remoteMediaClient loadMedia:self.mediaInformation];
if (request != nil) {
  request.delegate = self;
}

Lihat juga bagian tentang menggunakan trek media.

Format video 4K

Untuk menentukan format video media Anda, gunakan properti videoInfo GCKMediaStatus untuk mendapatkan instance GCKVideoInfo saat ini. Instance ini berisi jenis format HDR TV serta tinggi dan lebar dalam piksel. Varian format 4K ditunjukkan dalam properti hdrType oleh nilai enum GCKVideoInfoHDRType.

Menambahkan pengontrol mini

Menurut Checklist Desain Cast, aplikasi pengirim harus memberikan kontrol persisten yang dikenal sebagai pengontrol mini yang akan muncul saat pengguna keluar dari halaman konten saat ini. Pengontrol mini menyediakan akses instan dan pengingat yang terlihat untuk sesi Cast saat ini.

Framework Cast menyediakan panel kontrol, GCKUIMiniMediaControlsViewController, yang dapat ditambahkan ke scene tempat Anda ingin menampilkan pengontrol mini.

Saat aplikasi pengirim memutar live stream video atau audio, SDK akan otomatis menampilkan tombol putar/berhenti sebagai pengganti tombol putar/jeda di pengontrol mini.

Lihat Menyesuaikan UI Pengirim iOS untuk mengetahui cara aplikasi pengirim dapat mengonfigurasi tampilan widget Cast.

Ada dua cara untuk menambahkan pengontrol mini ke aplikasi pengirim:

  • Biarkan framework Cast mengelola tata letak pengontrol mini dengan menggabungkan pengontrol tampilan yang ada dengan pengontrol tampilannya sendiri.
  • Kelola sendiri tata letak widget pengontrol mini dengan menambahkannya ke pengontrol tampilan yang ada dengan menyediakan subtampilan di storyboard.

Menggabungkan menggunakan GCKUICastContainerViewController

Cara pertama adalah dengan menggunakan GCKUICastContainerViewController yang menggabungkan pengontrol tampilan lain dan menambahkan GCKUIMiniMediaControlsViewController di bagian bawah. Pendekatan ini terbatas karena Anda tidak dapat menyesuaikan animasi dan tidak dapat mengonfigurasi perilaku pengontrol tampilan penampung.

Cara pertama ini biasanya dilakukan dalam metode -[application:didFinishLaunchingWithOptions:] di delegasi aplikasi:

Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
  let castContainerVC =
          GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window!.rootViewController = castContainerVC
  window!.makeKeyAndVisible()

  ...
}
Objective-C
- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  UIStoryboard *appStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  UINavigationController *navigationController =
          [appStoryboard instantiateViewControllerWithIdentifier:@"MainNavigation"];
  GCKUICastContainerViewController *castContainerVC =
          [[GCKCastContext sharedInstance] createCastContainerControllerForViewController:navigationController];
  castContainerVC.miniMediaControlsItemEnabled = YES;
  self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
  self.window.rootViewController = castContainerVC;
  [self.window makeKeyAndVisible];
  ...

}
Swift
var castControlBarsEnabled: Bool {
  set(enabled) {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      castContainerVC.miniMediaControlsItemEnabled = enabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
    }
  }
  get {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      return castContainerVC.miniMediaControlsItemEnabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
      return false
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, assign) BOOL castControlBarsEnabled;

@end

AppDelegate.m

@implementation AppDelegate

...

- (void)setCastControlBarsEnabled:(BOOL)notificationsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  castContainerVC.miniMediaControlsItemEnabled = notificationsEnabled;
}

- (BOOL)castControlBarsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  return castContainerVC.miniMediaControlsItemEnabled;
}

...

@end

Menyematkan di pengontrol tampilan yang ada

Cara kedua adalah dengan menambahkan pengontrol mini langsung ke pengontrol tampilan yang ada menggunakan createMiniMediaControlsViewController untuk membuat instance GCKUIMiniMediaControlsViewController, lalu menambahkannya ke pengontrol tampilan container sebagai sub-tampilan.

Siapkan pengontrol tampilan Anda di delegasi aplikasi:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
  window?.clipsToBounds = true

  let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
  rootContainerVC?.miniMediaControlsViewEnabled = true

  ...

  return true
}
Objective-C
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  self.window.clipsToBounds = YES;

  RootContainerViewController *rootContainerVC;
  rootContainerVC =
      (RootContainerViewController *)self.window.rootViewController;
  rootContainerVC.miniMediaControlsViewEnabled = YES;

  ...

  return YES;
}

Di pengontrol tampilan root, buat instance GCKUIMiniMediaControlsViewController dan tambahkan ke pengontrol tampilan penampung sebagai sub-tampilan:

Swift
let kCastControlBarsAnimationDuration: TimeInterval = 0.20

@objc(RootContainerViewController)
class RootContainerViewController: UIViewController, GCKUIMiniMediaControlsViewControllerDelegate {
  @IBOutlet weak private var _miniMediaControlsContainerView: UIView!
  @IBOutlet weak private var _miniMediaControlsHeightConstraint: NSLayoutConstraint!
  private var miniMediaControlsViewController: GCKUIMiniMediaControlsViewController!
  var miniMediaControlsViewEnabled = false {
    didSet {
      if self.isViewLoaded {
        self.updateControlBarsVisibility()
      }
    }
  }

  var overriddenNavigationController: UINavigationController?

  override var navigationController: UINavigationController? {

    get {
      return overriddenNavigationController
    }

    set {
      overriddenNavigationController = newValue
    }
  }
  var miniMediaControlsItemEnabled = false

  override func viewDidLoad() {
    super.viewDidLoad()
    let castContext = GCKCastContext.sharedInstance()
    self.miniMediaControlsViewController = castContext.createMiniMediaControlsViewController()
    self.miniMediaControlsViewController.delegate = self
    self.updateControlBarsVisibility()
    self.installViewController(self.miniMediaControlsViewController,
                               inContainerView: self._miniMediaControlsContainerView)
  }

  func updateControlBarsVisibility() {
    if self.miniMediaControlsViewEnabled && self.miniMediaControlsViewController.active {
      self._miniMediaControlsHeightConstraint.constant = self.miniMediaControlsViewController.minHeight
      self.view.bringSubview(toFront: self._miniMediaControlsContainerView)
    } else {
      self._miniMediaControlsHeightConstraint.constant = 0
    }
    UIView.animate(withDuration: kCastControlBarsAnimationDuration, animations: {() -> Void in
      self.view.layoutIfNeeded()
    })
    self.view.setNeedsLayout()
  }

  func installViewController(_ viewController: UIViewController?, inContainerView containerView: UIView) {
    if let viewController = viewController {
      self.addChildViewController(viewController)
      viewController.view.frame = containerView.bounds
      containerView.addSubview(viewController.view)
      viewController.didMove(toParentViewController: self)
    }
  }

  func uninstallViewController(_ viewController: UIViewController) {
    viewController.willMove(toParentViewController: nil)
    viewController.view.removeFromSuperview()
    viewController.removeFromParentViewController()
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "NavigationVCEmbedSegue" {
      self.navigationController = (segue.destination as? UINavigationController)
    }
  }

...
Objective-C

RootContainerViewController.h

static const NSTimeInterval kCastControlBarsAnimationDuration = 0.20;

@interface RootContainerViewController () <GCKUIMiniMediaControlsViewControllerDelegate> {
  __weak IBOutlet UIView *_miniMediaControlsContainerView;
  __weak IBOutlet NSLayoutConstraint *_miniMediaControlsHeightConstraint;
  GCKUIMiniMediaControlsViewController *_miniMediaControlsViewController;
}

@property(nonatomic, weak, readwrite) UINavigationController *navigationController;

@property(nonatomic, assign, readwrite) BOOL miniMediaControlsViewEnabled;
@property(nonatomic, assign, readwrite) BOOL miniMediaControlsItemEnabled;

@end

RootContainerViewController.m

@implementation RootContainerViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  GCKCastContext *castContext = [GCKCastContext sharedInstance];
  _miniMediaControlsViewController =
      [castContext createMiniMediaControlsViewController];
  _miniMediaControlsViewController.delegate = self;

  [self updateControlBarsVisibility];
  [self installViewController:_miniMediaControlsViewController
              inContainerView:_miniMediaControlsContainerView];
}

- (void)setMiniMediaControlsViewEnabled:(BOOL)miniMediaControlsViewEnabled {
  _miniMediaControlsViewEnabled = miniMediaControlsViewEnabled;
  if (self.isViewLoaded) {
    [self updateControlBarsVisibility];
  }
}

- (void)updateControlBarsVisibility {
  if (self.miniMediaControlsViewEnabled &&
      _miniMediaControlsViewController.active) {
    _miniMediaControlsHeightConstraint.constant =
        _miniMediaControlsViewController.minHeight;
    [self.view bringSubviewToFront:_miniMediaControlsContainerView];
  } else {
    _miniMediaControlsHeightConstraint.constant = 0;
  }
  [UIView animateWithDuration:kCastControlBarsAnimationDuration
                   animations:^{
                     [self.view layoutIfNeeded];
                   }];
  [self.view setNeedsLayout];
}

- (void)installViewController:(UIViewController *)viewController
              inContainerView:(UIView *)containerView {
  if (viewController) {
    [self addChildViewController:viewController];
    viewController.view.frame = containerView.bounds;
    [containerView addSubview:viewController.view];
    [viewController didMoveToParentViewController:self];
  }
}

- (void)uninstallViewController:(UIViewController *)viewController {
  [viewController willMoveToParentViewController:nil];
  [viewController.view removeFromSuperview];
  [viewController removeFromParentViewController];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([segue.identifier isEqualToString:@"NavigationVCEmbedSegue"]) {
    self.navigationController =
        (UINavigationController *)segue.destinationViewController;
  }
}

...

@end

GCKUIMiniMediaControlsViewControllerDelegate memberi tahu pengontrol tampilan host kapan pengontrol mini akan terlihat:

Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

Tambahkan pengontrol yang diperluas

Checklist Desain Google Cast memerlukan aplikasi pengirim untuk menyediakan pengontrol yang diperluas untuk media yang sedang ditransmisikan. Pengontrol yang diperluas adalah versi layar penuh dari pengontrol mini.

Pengontrol yang diperluas adalah tampilan layar penuh yang menawarkan kontrol penuh atas pemutaran media jarak jauh. Tampilan ini harus memungkinkan aplikasi transmisi mengelola setiap aspek yang dapat dikelola dari sesi transmisi, dengan pengecualian kontrol volume Penerima Web dan siklus proses sesi (menghubungkan/menghentikan transmisi). Fungsi ini juga memberikan semua informasi status tentang sesi media (karya seni, judul, subtitel, dan seterusnya).

Fungsi tampilan ini diimplementasikan oleh class GCKUIExpandedMediaControlsViewController.

Hal pertama yang harus Anda lakukan adalah mengaktifkan pengontrol default yang diperluas dalam konteks transmisi. Ubah delegasi aplikasi untuk mengaktifkan pengontrol default yang diperluas:

Swift
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
Objective-C
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Tambahkan kode berikut ke pengontrol tampilan untuk memuat pengontrol yang diperluas saat pengguna mulai mentransmisikan video:

Swift
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

  // Load your media
  sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation)
}
Objective-C
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

  // Load your media
  [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation];
}

Pengontrol yang diperluas juga akan diluncurkan secara otomatis saat pengguna mengetuk pengontrol mini.

Saat aplikasi pengirim memutar live stream video atau audio, SDK akan otomatis menampilkan tombol putar/berhenti sebagai pengganti tombol putar/jeda di pengontrol yang diperluas.

Lihat Menerapkan Gaya Kustom ke Aplikasi iOS untuk mengetahui cara aplikasi pengirim dapat mengonfigurasi tampilan widget Cast.

Kontrol volume

Framework Cast secara otomatis mengelola volume aplikasi pengirim. Framework akan otomatis disinkronkan dengan volume Penerima Web untuk widget UI yang disediakan. Untuk menyinkronkan penggeser yang disediakan oleh aplikasi, gunakan GCKUIDeviceVolumeController.

Kontrol volume tombol fisik

Tombol volume fisik di perangkat pengirim dapat digunakan untuk mengubah volume sesi Transmisi di Penerima Web menggunakan tanda physicalVolumeButtonsWillControlDeviceVolume di GCKCastOptions, yang disetel di GCKCastContext.

Swift
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Objective-C
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

Menangani error

Aplikasi pengirim harus menangani semua callback error dan menentukan respons terbaik untuk setiap tahap siklus proses Transmisi. Aplikasi dapat menampilkan dialog error kepada pengguna atau memutuskan untuk mengakhiri sesi Transmisi.

Logging

GCKLogger adalah singleton yang digunakan untuk logging oleh framework. Gunakan GCKLoggerDelegate untuk menyesuaikan cara Anda menangani pesan log.

Dengan menggunakan GCKLogger, SDK akan menghasilkan output logging dalam bentuk pesan debug, error, dan peringatan. Pesan log ini membantu proses debug dan berguna untuk memecahkan masalah serta mengidentifikasi masalah. Secara default, output log disembunyikan, tetapi dengan menetapkan GCKLoggerDelegate, aplikasi pengirim dapat menerima pesan ini dari SDK dan mencatatnya ke dalam log ke konsol sistem.

Swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    ...

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

Untuk mengaktifkan pesan debug dan pesan panjang, tambahkan baris ini ke kode setelah menetapkan delegasi (ditunjukkan sebelumnya):

Swift
let filter = GCKLoggerFilter.init()
filter.minimumLevel = GCKLoggerLevel.verbose
GCKLogger.sharedInstance().filter = filter
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

Anda juga dapat memfilter pesan log yang dihasilkan oleh GCKLogger. Tetapkan level logging minimum per class, misalnya:

Swift
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
Objective-C
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

Nama class dapat berupa nama literal atau pola glob, misalnya, GCKUI\* dan GCK\*Session.