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 trên iOS bằng cách sử dụng SDK người gửi trên iOS.

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

Khung người gửi đề cập đến tệp nhị phân của thư viện lớp Cast và các tài nguyên liên quan xuất hiện trong thời gian chạy trên người gửi. Ứng dụng gửi hoặc ứng dụng Truyền đề cập đến một ứng dụng cũng chạy trên người gửi. Ứng dụng Web receiver tham chiếu đến ứ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 gửi về các sự kiện và để chuyển đổi giữa các trạng thái khác nhau của vòng đời ứng dụng Truyền.

Luồng ứng dụng

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

  • Khung Truyền sẽ khởi động 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ẽ hiển thị hộp thoại Truyền cùng với 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 chạy ứng dụng Trình thu thập dữ liệu web 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 Trình nhận trên web đã được chạy.
  • Khung này tạo một kênh liên lạc giữa 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 Trình nhận web.
  • Khung này đồng bộ hoá trạng thái phát nội dung đa phương tiện giữa người gửi và Web receiver: 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 người gửi, khung này sẽ chuyển các yêu cầu điều khiển nội dung đa phương tiện đó đến Web receiver và khi Web receiver gửi nội dung cập nhật trạng thái nội dung nghe nhìn, khung sẽ cập nhật trạng thái của giao diện người dùng của người gửi.
  • Khi người dùng nhấp vào nút Truyền để ngắt kết nối khỏi Thiết bị truyền, khung sẽ ngắt kết nối ứng dụng của người gửi khỏi Trình thu 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ý.

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

Phương thức gọi từ luồng chính

Khởi động ngữ cảnh Truyền

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

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

Phương thức -[application:didFinishLaunchingWithOptions:] cũng là nơi phù hợp để thiết lập uỷ quyền ghi nhật ký nhằm nhận thông báo ghi nhật ký từ khung. Những thông số 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 Cast UX

SDK Cast iOS cung cấp các tiện ích sau đây tuân thủ Danh sách kiểm tra thiết kế truyền:

  • Lớp phủ giới thiệu: Lớp GCKCastContext có một phương thức presentCastInstructionsViewControllerOnceWithCastButton, có thể được dùng để làm nổi bật nút Truyền vào lần đầu tiên Trình thu web được sử dụng. Ứng dụng của 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ừ SDK của người gửi iOS Cast 4.6.0, nút truyền luôn hiển thị khi thiết bị gửi được kết nối với Wi-Fi. Vào 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 mạng cục bộ vào các thiết bị trên mạng. Sau đó, khi người dùng nhấn vào nút truyền, hộp thoại truyền sẽ hiển thị để liệt kê các thiết bị đã phát hiện. Khi người dùng nhấn vào nút truyền trong khi thiết bị được kết nối, nút này sẽ hiển thị siêu dữ liệu nội dung nghe nhìn hiện tại (chẳng hạn như tiêu đề, tên phòng thu và hình thu nhỏ) hoặc cho phép người dùng ngắt kết nối khỏi thiết bị truyền. Khi người dùng nhấn vào nút truyền khi không có thiết bị nào, một màn hình sẽ hiển thị để 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 đang truyền nội dung và đã di chuyển từ trang nội dung hiện tại hoặc bộ điều khiển mở rộng sang một màn hình khác trong ứng dụng dành cho người gửi, bộ điều khiển nhỏ sẽ hiển thị ở 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 đang truyền nội dung, nếu họ nhấp vào thông báo nội dung nghe nhìn hoặc bộ điều khiển mini, bộ điều khiển mở rộng sẽ chạy, hiển thị siêu dữ liệu nội dung nghe nhìn đang phát và cung cấp một số nút để điều khiển chế độ phát nội dung nghe nhìn.

Thêm nút Truyền

Khung này cung cấp thành phần nút Truyền dưới dạng lớp con UIButton. Bạn có thể thêm thông tin này vào thanh tiêu đề của ứng dụng bằng cách gói lớp này trong một UIBarButtonItem. Một lớp con UIViewController thông thường có thể cài đặt nút Cast 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, thao tác nhấn vào nút này sẽ mở hộp thoại Truyền do khung cung cấp.

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 khám phá thiết bị diễn ra tự động. Bạn không cần phải bắt đầu hoặc dừng quá 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.

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

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, quy trình 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 Trình thu web, 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. Xem Hướng dẫn về vòng đời của ứng dụng của Bộ thu web để biết thêm thông tin về các phiên Truyền và vòng đời Bộ thu web.

Các phiên hoạt động được quản lý bằng lớp GCKSessionManager, 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, chẳng hạn như GCKCastSession đại diện cho các phiên có thiết bị Truyền. Bạn có thể truy cập phiên Truyền đang hoạt động (nếu có) ở dạng thuộc tính currentCastSession của GCKSessionManager.

Bạn có thể sử dụng giao diện GCKSessionManagerListener để theo dõi các sự kiện của phiên, chẳng hạn như tạo phiên, tạm ngưng, tiếp tục và chấm dứt. Khung này sẽ tự động tạm ngưng các phiên khi ứng dụng của người 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 nền trước (hoặc được chạy lại sau khi chấm dứt ứng dụng bất thường/đột ngột trong khi một phiên đang hoạt động).

Nếu bạn đang sử dụng hộp thoại Truyền, thì các phiên sẽ tự động được tạo và loại bỏ theo 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 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 với GCKSessionManager. GCKSessionManagerListener là một giao thức xác định lệnh gọi lại cho các sự kiện như bắt đầu phiên, kết thúc phiên, v.v.

Chuyển luồng

Việc duy trì trạng thái phiên là cơ sở của quá trình chuyển luồng, 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 dừng phát trên một thiết bị (nguồn) và tiếp tục trên một thiết bị khác (đích đến). Mọi thiết bị Truyền có chương trình cơ sở mới nhất đều có thể đóng vai trò là nguồn hoặc đích đến trong quá trình chuyển luồng.

Để có 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 khi thực hiện lệnh gọi lại [sessionManager:didResumeCastSession:].

Vui lòng xem bài viết Chuyển luồng trên Bộ thu web để biết thêm thông tin.

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

Khung Truyền 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 góc nhỏ, chẳng hạn như:

  • Khôi phục sau khi mất Wi-Fi tạm thời
  • Khôi phục từ chế độ ngủ của thiết bị
  • Khôi phục sau khi chuyển ứng dụng sang chế độ 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 có hỗ trợ không gian tên nội dung đa phương tiện, thì một thực thể của GCKRemoteMediaClient sẽ được khung tự động tạo; nó có thể được truy cập dưới dạng thuộc tính remoteMediaClient của thực thể GCKCastSession.

Tất cả các phương thức trên GCKRemoteMediaClient có yêu cầu gửi đến Web receiver sẽ trả về đối tượng GCKRequest có thể dùng để theo dõi yêu cầu đó. Bạn có thể chỉ định 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 thực thể của GCKRemoteMediaClient có thể được nhiều phần của ứng dụng chia sẻ và thực sự một số thành phần nội bộ của khung như hộp thoại Truyền và các nút điều khiển nội dung nghe nhìn thu nhỏ sẽ dùng chung thực thể đó. Để làm được điều đó, GCKRemoteMediaClient hỗ trợ đăng ký nhiều GCKRemoteMediaClientListener.

Thiết lập siêu dữ liệu đa phương tiện

Lớp GCKMediaMetadata biểu thị thông tin về mục nội dung đa phương tiện mà bạn muốn truyền. Ví dụ sau đây tạo một thực thể GCKMediaMetadata mới của phim và đặt tiêu đề, phụ đề, tên phòng thu âm 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 hình ảnh và lưu vào bộ nhớ đệm về cách sử dụng hình ảnh với siêu dữ liệu đa phương tiện.

Tải nội dung nghe nhìn

Để tải một mục nội dung đa phương tiện, hãy tạo một thực thể GCKMediaInformation bằng siêu dữ liệu của nội dung đa phương tiện đó. Sau đó, lấy GCKCastSession hiện tại và dùng GCKRemoteMediaClient để tải nội dung nghe nhìn trên ứng dụng receiver. Sau đó, bạn có thể sử dụng GCKRemoteMediaClient để điều khiển một ứng dụng trình phát nội dung đa phương tiện chạy trên receiver, 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 sử dụng bản nhạc đa phương tiện.

Định dạng video 4K

Để xác định định dạng video của nội dung đa phương tiện, hãy sử dụng thuộc tính videoInfo của GCKMediaStatus để tải thực thể hiện tại của GCKVideoInfo. Thực thể này chứa loại định dạng TV HDR cũng như 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 biểu thị trong thuộc tính hdrType bằng giá trị enum GCKVideoInfoHDRType.

Thêm tay điều khiển mini

Theo Danh sách kiểm tra thiết kế truyền, ứng dụng dành cho người gửi sẽ cung cấp quyền kiểm soát lâu dài, gọi là bộ điều khiển 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ỏ cung cấp quyền truy cập tức thì và lời nhắc hiển thị cho phiên Truyền hiện tại.

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

Khi ứng dụng của người gửi đang phát sự kiện phát trực tiếp 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ỏ.

Xem bài viết Tuỳ chỉnh giao diện người dùng người gửi trên iOS để biết cách ứng dụng người gửi của bạn có thể định cấu hình giao diện của các tiện ích Truyền.

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

  • Cho phép khung Truyền quản lý bố cục của bộ điều khiển thu nhỏ bằng cách gói 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 đó vào bộ đ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.

Gói bằng GCKUICastContainerViewController

Cách đầu tiên là sử dụng GCKUICastContainerViewController để gói một bộ điều khiển khung hiển thị khác và thêm GCKUIMiniMediaControlsViewController ở dưới cùng. Phương pháp này bị hạn chế vì 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 kiểm soát khung hiển thị vùng chứa.

Cách đầu tiên này thường được thực hiện trong phương thức -[application:didFinishLaunchingWithOptions:] của thực thể đại diện cho ứ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 bộ điều khiển chế độ xem hiện có

Cách thứ hai là thêm trực tiếp bộ điều khiển thu nhỏ vào bộ đ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 rồi thêm thực thể đó vào bộ điều khiển khung hiển thị vùng chứa dưới dạng khung hiển thị phụ.

Thiết lập bộ kiểm soát chế độ xem trong tính năng 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 bộ điều khiển khung hiển thị gốc, hãy tạo một thực thể GCKUIMiniMediaControlsViewController rồi thêm thực thể đó vào bộ điều khiển khung hiển thị vùng chứa dưới dạng một khung hiển thị phụ:

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 thông báo cho bộ điều khiển khung hiển thị máy chủ lưu trữ khi nào 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ế Google Cast yêu cầu ứng dụng gửi yêu cầu cung cấp một bộ điều khiển mở rộng cho nội dung nghe nhìn đang được truyền. Tay điều khiển mở rộng là phiên bản toàn màn hình của tay điều khiển mini.

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

Chức năng của khung hiển thị này do lớp GCKUIExpandedMediaControlsViewController triển khai.

Đ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 tính năng uỷ quyền của ứ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 mã sau vào bộ điều khiển chế độ xem để tải bộ đ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 mở khi người dùng nhấn vào bộ điều khiển thu nhỏ.

Khi ứng dụng của người gửi đang phát sự kiện phát trực tiếp 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.

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

Chỉnh âm lượng

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

Điều chỉnh âm lượng bằng nút vật lý

Bạn có thể sử dụng các nút âm lượng vật lý trên thiết bị của người gửi để thay đổi âm lượng của phiên Truyền trên Trình nhận web bằng cách sử dụng cờ physicalVolumeButtonsWillControlDeviceVolume trên GCKCastOptions, đượ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

Điều quan trọng là ứng dụng gửi thư phải 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 trong vòng đời của Cast. Ứng dụng có thể cho người dùng thấy hộp thoại lỗi 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 dùng để ghi nhật ký. Sử dụng GCKLoggerDelegate để tuỳ chỉnh cách bạn xử lý thông điệp nhật ký.

Bằng cách sử dụng GCKLogger, SDK sẽ tạo đầu ra nhật ký ở dạng thông báo gỡ lỗi, lỗi và cảnh báo. Những thông điệp nhật ký này hỗ trợ gỡ lỗi và hữu ích khi khắc phục sự cố cũng như xác định sự cố. Theo mặc định, đầu ra nhật ký sẽ bị chặn, nhưng bằng cách chỉ định GCKLoggerDelegate, ứng dụng gửi có thể nhận các thông báo này từ SDK và ghi chúng 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 cũng như thông báo chi tiết, hãy thêm dòng này vào mã sau khi thiết lập thực thể đại diện (hiển thị 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 điệp 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 cố định hoặc mẫu toàn cầu, ví dụ: GCKUI\*GCK\*Session.