Tích hợp tính năng Truyền vào ứng dụng iOS

Hướng dẫn dành cho nhà phát triển này mô tả cách thêm tính năng hỗ trợ Google Cast vào ứng dụng người gửi iOS bằng iOS Sender SDK.

Thiết bị di động hoặc máy tính xách tay là thiết bị gửi kiểm soát quá trình phát, còn thiết bị Google Cast là thiết bị nhận hiển thị nội dung trên TV.

Khung người gửi đề cập đến tệp nhị phân thư viện lớp Cast và các tài nguyên liên kết có trong thời gian chạy trên người gửi. Ứng dụng người gửi hoặc ứng dụng Cast là một ứng dụng cũng chạy trên người gửi. Ứng dụng Web Receiver là ứng dụng HTML chạy trên Web Receiver.

Khung người gửi sử dụng thiết kế gọi lại không đồng bộ để thông báo cho ứng dụng người gửi về các sự kiện và chuyển đổi giữa nhiều trạng thái của vòng đời ứng dụng Cast.

Luồng ứng dụng

Các bước sau đây mô tả quy trình thực thi cấp cao điển hình cho ứng dụng iOS của người gửi:

  • Khung truyền bắt đầu GCKDiscoveryManager dựa trên các thuộc tính được cung cấp trong GCKCastOptions để bắt đầu quét tìm thiết bị.
  • Khi người dùng nhấp vào nút Truyền, khung sẽ trình bày hộp thoại Truyền có danh sách các thiết bị Truyền được phát hiện.
  • Khi người dùng chọn một thiết bị truyền, khung sẽ cố gắng khởi chạy ứng dụng Web Receiver trên thiết bị truyền.
  • Khung này sẽ gọi các lệnh gọi lại trong ứng dụng người gửi để xác nhận rằng ứng dụng Web Receiver đã được khởi chạy.
  • Khung này tạo ra một kênh giao tiếp giữa ứng dụng người gửi và ứng dụng Web Receiver.
  • Khung này sử dụng kênh giao tiếp để tải và kiểm soát việc phát nội dung nghe nhìn trên Web Receiver.
  • Khung này đồng bộ hoá trạng thái phát nội dung nghe nhìn giữa thiết bị gửi và Trình nhận Web: khi người dùng thực hiện các thao tác trên giao diện người dùng của thiết bị gửi, khung này sẽ truyền các yêu cầu điều khiển nội dung nghe nhìn đó đến Trình nhận Web và khi Trình nhận Web gửi thông tin cập nhật về trạng thái nội dung nghe nhìn, khung này sẽ cập nhật trạng thái của giao diện người dùng của thiết bị gửi.
  • Khi người dùng nhấp vào nút Truyền để ngắt kết nối với thiết bị truyền, khung sẽ ngắt kết nối ứng dụng người gửi khỏi Trình nhận web.

Để khắc phục sự cố cho người gửi, bạn cần bật tính năng ghi nhật ký.

Để xem danh sách đầy đủ tất cả các lớp, phương thức và sự kiện trong khung Google Cast iOS, hãy xem Tài liệu tham khảo về Google Cast iOS API. Các phần sau đây trình bày các bước tích hợp Cast vào ứng dụng iOS.

Gọi các phương thức từ luồng chính

Khởi chạy ngữ cảnh Cast

Khung Cast có một đối tượng singleton chung, GCKCastContext, giúp điều phối tất cả các hoạt động của khung. Đối tượng này phải được khởi tạo sớm trong vòng đời của ứng dụng, thường là trong phương thức -[application:didFinishLaunchingWithOptions:] của uỷ quyền ứng dụng, để quá trình tiếp tục phiên tự động khi khởi động lại ứng dụng người gửi có thể kích hoạt đúng cách.

Bạn phải cung cấp một đối tượng GCKCastOptions khi khởi chạy GCKCastContext. Lớp này chứa các lựa chọn ảnh hưởng đến hành vi của khung. Quan trọng nhất trong số này là mã nhận dạng ứng dụng Web Receiver. Mã này được dùng để lọc kết quả tìm kiếm và khởi chạy ứng dụng Web Receiver khi một phiên truyền bắt đầu.

Phương thức -[application:didFinishLaunchingWithOptions:] cũng là một nơi phù hợp để thiết lập một uỷ quyền ghi nhật ký nhằm nhận các thông báo ghi nhật ký từ khung. Những thông tin này có thể hữu ích khi gỡ lỗi và khắc phục sự cố.

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

Tiện ích UX của Cast

Cast iOS SDK cung cấp những tiện ích tuân thủ Danh sách kiểm tra thiết kế Cast:

  • Lớp phủ giới thiệu: Lớp GCKCastContext có một phương thức là presentCastInstructionsViewControllerOnceWithCastButton. Bạn có thể dùng phương thức này để làm nổi bật nút Truyền lần đầu tiên Bộ nhận web xuất hiện. Ứng dụng người gửi có thể tuỳ chỉnh văn bản, vị trí của văn bản tiêu đề và nút Đóng.

  • Nút truyền: Kể từ Cast iOS Sender SDK 4.6.0, nút truyền luôn xuất hiện khi thiết bị gửi được kết nối với Wi-Fi. Lần đầu tiên người dùng nhấn vào nút Truyền sau khi khởi động ứng dụng, một hộp thoại cấp quyền sẽ xuất hiện để người dùng có thể cấp cho ứng dụng quyền truy cập vào mạng cục bộ đối với các thiết bị trên mạng. Sau đó, khi người dùng nhấn vào nút truyền, một hộp thoại truyền sẽ xuất hiện và liệt kê các thiết bị được phát hiện. Khi người dùng nhấn vào nút truyền trong khi thiết bị đang kết nối, nút này sẽ hiển thị siêu dữ liệu hiện tại của nội dung nghe nhìn (chẳng hạn như tiêu đề, tên của phòng thu và hình thu nhỏ) hoặc cho phép người dùng ngắt kết nối với thiết bị truyền. Khi người dùng nhấn vào nút truyền trong khi không có thiết bị nào, một màn hình sẽ xuất hiện để cung cấp cho người dùng thông tin về lý do không tìm thấy thiết bị và cách khắc phục sự cố.

  • Bộ điều khiển thu nhỏ: Khi người dùng truyền nội dung và đã rời khỏi trang nội dung hiện tại hoặc bộ điều khiển mở rộng để chuyển sang một màn hình khác trong ứng dụng người gửi, bộ điều khiển thu nhỏ sẽ xuất hiện ở cuối màn hình để cho phép người dùng xem siêu dữ liệu nội dung nghe nhìn đang truyền và điều khiển chế độ phát.

  • Bộ điều khiển mở rộng: Khi người dùng truyền nội dung, nếu họ nhấp vào thông báo về nội dung nghe nhìn hoặc bộ điều khiển thu nhỏ, bộ điều khiển mở rộng sẽ khởi chạy. Bộ điều khiển này hiển thị siêu dữ liệu của nội dung nghe nhìn đang phát và cung cấp một số nút để điều khiển việc phát nội dung nghe nhìn.

Thêm nút Truyền

Khung này cung cấp một thành phần nút Truyền dưới dạng một lớp con UIButton. Bạn có thể thêm biểu tượng này vào thanh tiêu đề của ứng dụng bằng cách gói biểu tượng trong một UIBarButtonItem. Một lớp con UIViewController điển hình có thể cài đặt nút Truyền như sau:

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];

Theo mặc định, khi bạn nhấn vào nút này, hộp thoại Truyền do khung cung cấp sẽ mở ra.

Bạn cũng có thể thêm GCKUICastButton trực tiếp vào bảng phân cảnh.

Định cấu hình tính năng khám phá thiết bị

Trong khung này, quá trình phát hiện thiết bị diễn ra tự động. Bạn không cần bắt đầu hoặc dừng quy trình khám phá một cách rõ ràng, trừ phi bạn triển khai một giao diện người dùng tuỳ chỉnh.

Hoạt động khám phá trong khung được quản lý bởi lớp GCKDiscoveryManager, đây là một thuộc tính của GCKCastContext. Khung này cung cấp một thành phần hộp thoại Truyền mặc định để chọn và điều khiển thiết bị. Danh sách thiết bị được sắp xếp theo tên thân thiện của thiết bị theo thứ tự từ điển.

Cách hoạt động của tính năng quản lý phiên

Cast SDK giới thiệu khái niệm về phiên truyền, việc thiết lập phiên này kết hợp các bước kết nối với thiết bị, khởi chạy (hoặc tham gia) ứng dụng Web Receiver, kết nối với ứng dụng đó và khởi chạy kênh điều khiển nội dung nghe nhìn. Hãy xem Hướng dẫn về vòng đời ứng dụng của Web Receiver để biết thêm thông tin về các phiên truyền và vòng đời của Web Receiver.

Các phiên do lớp GCKSessionManager quản lý, đây là một thuộc tính của GCKCastContext. Các phiên riêng lẻ được biểu thị bằng các lớp con của lớp GCKSession: ví dụ: GCKCastSession biểu thị các phiên có thiết bị truyền. Bạn có thể truy cập vào phiên truyền đang hoạt động (nếu có) dưới dạng thuộc tính currentCastSession của GCKSessionManager.

Bạn có thể dùng giao diện GCKSessionManagerListener để theo dõi các sự kiện trong phiên, chẳng hạn như việc tạo, tạm ngưng, tiếp tục và kết thúc phiên. Khung này sẽ tự động tạm ngưng các phiên khi ứng dụng gửi chuyển sang chạy ở chế độ nền và cố gắng tiếp tục các phiên đó khi ứng dụng quay lại chế độ nền trước (hoặc được khởi chạy lại sau khi ứng dụng bị chấm dứt bất thường/đột ngột trong khi một phiên đang hoạt động).

Nếu hộp thoại Truyền đang được dùng, thì các phiên sẽ được tạo và huỷ tự động để phản hồi cử chỉ của người dùng. Nếu không, ứng dụng có thể bắt đầu và kết thúc các phiên một cách rõ ràng thông qua các phương thức trên GCKSessionManager.

Nếu cần xử lý đặc biệt để phản hồi các sự kiện trong vòng đời của phiên, ứng dụng có thể đăng ký một hoặc nhiều thực thể GCKSessionManagerListener bằng GCKSessionManager. GCKSessionManagerListener là một giao thức xác định các lệnh gọi lại cho những sự kiện như bắt đầu phiên, kết thúc phiên, v.v.

Chuyển đổi phiên phát trực tuyến

Việc duy trì trạng thái phiên là cơ sở của tính năng chuyển luồng phát, trong đó người dùng có thể di chuyển các luồng âm thanh và video hiện có giữa các thiết bị bằng lệnh thoại, Ứng dụng Google Home hoặc màn hình thông minh. Nội dung nghe nhìn ngừng phát trên một thiết bị (nguồn) và tiếp tục phát trên một thiết bị khác (đích). Mọi thiết bị truyền có phần mềm mới nhất đều có thể đóng vai trò là nguồn hoặc đích đến trong quá trình truyền trực tuyến.

Để nhận thiết bị đích mới trong quá trình chuyển luồng, hãy sử dụng thuộc tính GCKCastSession#device trong lệnh gọi lại [sessionManager:didResumeCastSession:].

Hãy xem phần Truyền trực tuyến trên Web Receiver để biết thêm thông tin.

Tự động kết nối lại

Khung Cast thêm logic kết nối lại để tự động xử lý việc kết nối lại trong nhiều trường hợp đặc biệt, chẳng hạn như:

  • Khôi phục sau khi mất Wi-Fi tạm thời
  • Khôi phục sau khi thiết bị chuyển sang chế độ ngủ
  • Khôi phục sau khi đưa ứng dụng xuống nền
  • Khôi phục nếu ứng dụng gặp sự cố

Cách hoạt động của chế độ điều khiển nội dung nghe nhìn

Nếu một phiên truyền được thiết lập bằng một ứng dụng Web Receiver hỗ trợ không gian tên nội dung nghe nhìn, thì một phiên bản GCKRemoteMediaClient sẽ được khung tự động tạo; bạn có thể truy cập vào phiên bản này dưới dạng thuộc tính remoteMediaClient của phiên bản GCKCastSession.

Tất cả các phương thức trên GCKRemoteMediaClient đưa ra yêu cầu cho Web Receiver sẽ trả về một đối tượng GCKRequest có thể dùng để theo dõi yêu cầu đó. Bạn có thể chỉ định một GCKRequestDelegate cho đối tượng này để nhận thông báo về kết quả cuối cùng của thao tác.

Dự kiến, nhiều phần của ứng dụng có thể dùng chung thực thể GCKRemoteMediaClient và thực tế là một số thành phần nội bộ của khung như hộp thoại Truyền và các chế độ điều khiển phương tiện thu nhỏ dùng chung thực thể này. Để làm được điều đó, GCKRemoteMediaClient hỗ trợ việc đăng ký nhiều GCKRemoteMediaClientListener.

Đặt siêu dữ liệu về nội dung nghe nhìn

Lớp GCKMediaMetadata biểu thị thông tin về một mục nội dung nghe nhìn mà bạn muốn truyền. Ví dụ sau đây tạo một phiên bản GCKMediaMetadata mới của một bộ phim và đặt tiêu đề, phụ đề, tên của hãng phim và 2 hình ảnh.

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]];

Xem phần Lựa chọn và lưu vào bộ nhớ đệm hình ảnh về việc sử dụng hình ảnh có siêu dữ liệu nội dung nghe nhìn.

Tải nội dung nghe nhìn

Để tải một mục nội dung nghe nhìn, hãy tạo một thực thể GCKMediaInformation bằng siêu dữ liệu của nội dung nghe nhìn. Sau đó, hãy lấy GCKCastSession hiện tại và dùng GCKRemoteMediaClient của nó để tải nội dung nghe nhìn trên ứng dụng nhận. Sau đó, bạn có thể dùng GCKRemoteMediaClient để điều khiển một ứng dụng trình phát nội dung nghe nhìn đang chạy trên thiết bị nhận, chẳng hạn như để phát, tạm dừng và dừng.

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;
}

Ngoài ra, hãy xem phần về cách sử dụng các bản âm thanh và phụ đề.

Định dạng video 4K

Để xác định định dạng video của nội dung nghe nhìn, hãy sử dụng thuộc tính videoInfo của GCKMediaStatus để lấy phiên bản hiện tại của GCKVideoInfo. Phiên bản này chứa loại định dạng TV HDR, chiều cao và chiều rộng tính bằng pixel. Các biến thể của định dạng 4K được chỉ định trong thuộc tính hdrType theo các giá trị enum GCKVideoInfoHDRType.

Thêm bộ điều khiển thu nhỏ

Theo Danh sách kiểm tra thiết kế Cast, ứng dụng người gửi phải cung cấp một chế độ điều khiển liên tục, còn được gọi là bộ điều khiển thu nhỏ. Bộ điều khiển này sẽ xuất hiện khi người dùng rời khỏi trang nội dung hiện tại. Bộ điều khiển thu nhỏ giúp bạn truy cập tức thì và nhắc nhở bạn về phiên truyền hiện tại.

Khung Cast cung cấp một thanh điều khiển, GCKUIMiniMediaControlsViewController, bạn có thể thêm thanh này vào những cảnh mà bạn muốn hiển thị bộ điều khiển thu nhỏ.

Khi ứng dụng người gửi đang phát một sự kiện phát trực tiếp bằng video hoặc âm thanh, SDK sẽ tự động hiển thị nút phát/dừng thay cho nút phát/tạm dừng trong bộ điều khiển thu nhỏ.

Hãy xem phần Tuỳ chỉnh giao diện người dùng của ứng dụng gửi trên iOS để biết cách ứng dụng gửi có thể định cấu hình giao diện của các tiện ích Cast.

Có 2 cách để thêm bộ điều khiển thu nhỏ vào ứng dụng người gửi:

  • Cho phép khung Cast quản lý bố cục của bộ điều khiển thu nhỏ bằng cách bao bọc bộ điều khiển khung hiển thị hiện có bằng bộ điều khiển khung hiển thị riêng.
  • Tự quản lý bố cục của tiện ích bộ điều khiển thu nhỏ bằng cách thêm tiện ích này vào trình điều khiển khung hiển thị hiện có bằng cách cung cấp một khung hiển thị phụ trong bảng phân cảnh.

Bao bọc bằng GCKUICastContainerViewController

Cách đầu tiên là sử dụng GCKUICastContainerViewController. Lớp này bao bọc một trình điều khiển khung hiển thị khác và thêm GCKUIMiniMediaControlsViewController ở dưới cùng. Phương pháp này có hạn chế là bạn không thể tuỳ chỉnh ảnh động và không thể định cấu hình hành vi của trình điều khiển khung hiển thị vùng chứa.

Cách đầu tiên thường được thực hiện trong phương thức -[application:didFinishLaunchingWithOptions:] của uỷ quyền ứng dụng:

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

Nhúng vào trình điều khiển chế độ xem hiện có

Cách thứ hai là thêm bộ điều khiển thu nhỏ trực tiếp vào trình điều khiển khung hiển thị hiện có bằng cách sử dụng createMiniMediaControlsViewController để tạo một thực thể GCKUIMiniMediaControlsViewController, sau đó thêm thực thể đó vào trình điều khiển khung hiển thị vùng chứa dưới dạng một khung hiển thị con.

Thiết lập bộ điều khiển chế độ xem trong uỷ quyền ứng dụng:

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;
}

Trong trình điều khiển khung hiển thị gốc, hãy tạo một thực thể GCKUIMiniMediaControlsViewController và thêm thực thể đó vào trình điều khiển khung hiển thị vùng chứa dưới dạng một khung hiển thị con:

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 cho biết bộ điều khiển chế độ xem máy chủ lưu trữ khi bộ điều khiển thu nhỏ sẽ hiển thị:

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

Thêm bộ điều khiển mở rộng

Danh sách kiểm tra thiết kế của Google Cast yêu cầu ứng dụng truyền phải cung cấp một bộ điều khiển mở rộng cho nội dung nghe nhìn đang được truyền. Bộ điều khiển mở rộng là phiên bản toàn màn hình của bộ điều khiển thu nhỏ.

Bộ điều khiển mở rộng là một chế độ xem toàn màn hình, cho phép bạn kiểm soát hoàn toàn việc phát nội dung nghe nhìn từ xa. Khung hiển thị này sẽ cho phép một ứng dụng truyền quản lý mọi khía cạnh có thể quản lý của một phiên truyền, ngoại trừ chế độ điều khiển âm lượng của Trình nhận web và vòng đời của phiên (kết nối/dừng truyền). Lớp này cũng cung cấp tất cả thông tin trạng thái về phiên phát nội dung nghe nhìn (ảnh bìa, tiêu đề, phụ đề, v.v.).

Chức năng của khung hiển thị này được triển khai bằng lớp GCKUIExpandedMediaControlsViewController.

Điều đầu tiên bạn phải làm là bật bộ điều khiển mở rộng mặc định trong ngữ cảnh truyền. Sửa đổi uỷ quyền ứng dụng để bật bộ điều khiển mở rộng mặc định:

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

Thêm đoạn mã sau vào trình điều khiển khung hiển thị để tải trình điều khiển mở rộng khi người dùng bắt đầu truyền 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];
}

Bộ điều khiển mở rộng cũng sẽ tự động khởi chạy khi người dùng nhấn vào bộ điều khiển thu nhỏ.

Khi ứng dụng người gửi đang phát một sự kiện phát trực tiếp bằng video hoặc âm thanh, SDK sẽ tự động hiển thị nút phát/dừng thay cho nút phát/tạm dừng trong bộ điều khiển mở rộng.

Hãy xem phần Áp dụng kiểu tuỳ chỉnh cho ứng dụng iOS để biết cách ứng dụng người gửi có thể định cấu hình giao diện của các tiện ích Cast.

Điều chỉnh âm lượng

Khung Cast tự động quản lý âm lượng cho ứng dụng người gửi. Khung này tự động đồng bộ hoá với âm lượng của Trình nhận web cho các tiện ích giao diện người dùng được cung cấp. Để đồng bộ hoá một thanh trượt do ứng dụng cung cấp, hãy sử dụng GCKUIDeviceVolumeController.

Điều khiển âm lượng bằng nút vật lý

Bạn có thể dùng các nút âm lượng vật lý trên thiết bị gửi để thay đổi âm lượng của phiên truyền trên Web Receiver bằng cách sử dụng cờ physicalVolumeButtonsWillControlDeviceVolume trên GCKCastOptions. Cờ này được đặt trên 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];

Xử lý lỗi

Các ứng dụng gửi cần xử lý tất cả các lệnh gọi lại lỗi và quyết định phản hồi tốt nhất cho từng giai đoạn của vòng đời Cast. Ứng dụng có thể hiển thị hộp thoại lỗi cho người dùng hoặc có thể quyết định kết thúc phiên truyền.

Ghi nhật ký

GCKLogger là một singleton được khung sử dụng để ghi nhật ký. Sử dụng GCKLoggerDelegate để tuỳ chỉnh cách bạn xử lý thông báo nhật ký.

Khi sử dụng GCKLogger, SDK sẽ tạo ra kết quả ghi nhật ký dưới dạng thông báo gỡ lỗi, lỗi và cảnh báo. Những thông báo nhật ký này hỗ trợ gỡ lỗi và hữu ích cho việc khắc phục sự cố cũng như xác định vấn đề. Theo mặc định, đầu ra nhật ký sẽ bị chặn, nhưng bằng cách chỉ định một GCKLoggerDelegate, ứng dụng gửi có thể nhận các thông báo này từ SDK và ghi nhật ký vào bảng điều khiển hệ thống.

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

Để bật cả thông báo gỡ lỗi và thông báo chi tiết, hãy thêm dòng này vào mã sau khi đặt uỷ quyền (đã trình bày trước đó):

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;

Bạn cũng có thể lọc các thông báo nhật ký do GCKLogger tạo ra. Đặt cấp độ ghi nhật ký tối thiểu cho mỗi lớp, ví dụ:

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;

Tên lớp có thể là tên chữ hoặc mẫu glob, ví dụ: GCKUI\*GCK\*Session.