รวมการแคสต์ในแอป iOS ของคุณ

คู่มือนักพัฒนาซอฟต์แวร์นี้จะอธิบายวิธีเพิ่มการสนับสนุน Google Cast ไปยังแอปผู้ส่ง iOS โดยใช้ SDK ผู้ส่งของ iOS

อุปกรณ์เคลื่อนที่หรือแล็ปท็อปเป็นผู้ส่งซึ่งควบคุมการเล่น และอุปกรณ์ Google Cast เป็นเครื่องรับซึ่งจะแสดงเนื้อหาบนทีวี

เฟรมเวิร์กผู้ส่งหมายถึงไบนารีไลบรารีคลาสของ Cast และทรัพยากรที่เกี่ยวข้องซึ่งแสดงขึ้นขณะรันไทม์ของผู้ส่ง แอปผู้ส่งหรือแอปแคสต์หมายถึงแอปที่กำลังทำงานกับผู้ส่งด้วยเช่นกัน แอป Web Receiver หมายถึงแอปพลิเคชัน HTML ที่ทำงานบน Web Receiver

เฟรมเวิร์กผู้ส่งใช้การออกแบบ Callback แบบไม่พร้อมกันเพื่อแจ้งให้แอปผู้ส่งทราบถึงเหตุการณ์ต่างๆ และเพื่อสลับระหว่างสถานะต่างๆ ในวงจรของแอปแคสต์

ขั้นตอนของแอป

ขั้นตอนต่อไปนี้จะอธิบายถึงขั้นตอนการดำเนินการระดับสูงโดยทั่วไปสำหรับแอป iOS ของผู้ส่ง

  • เฟรมเวิร์กการแคสต์จะเริ่มต้น GCKDiscoveryManager ตามพร็อพเพอร์ตี้ที่ระบุไว้ใน GCKCastOptions เพื่อเริ่มสแกนหาอุปกรณ์
  • เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เฟรมเวิร์กจะแสดงกล่องโต้ตอบ "แคสต์" ที่มีรายการอุปกรณ์แคสต์ที่พบ
  • เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิดแอปตัวรับเว็บในอุปกรณ์แคสต์
  • เฟรมเวิร์กนี้จะเรียกใช้ Callback ในแอปผู้ส่งเพื่อยืนยันว่ามีการเปิดตัวแอป Web Receiver
  • เฟรมเวิร์กจะสร้างช่องทางการสื่อสารระหว่างผู้ส่งกับแอปเว็บรีซีฟเวอร์
  • เฟรมเวิร์กนี้จะใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมการเล่นสื่อบน Web Receiver
  • เฟรมเวิร์กจะซิงค์สถานะการเล่นสื่อระหว่างผู้ส่งกับตัวรับเว็บ โดยเฟรมเวิร์กจะอัปเดตสถานะ UI ของผู้ส่ง เฟรมเวิร์กจะส่งคำขอตัวควบคุมสื่อดังกล่าวไปยังเว็บรีซีฟเวอร์
  • เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์ Cast เฟรมเวิร์กจะยกเลิกการเชื่อมต่อแอปผู้ส่งจาก Web Receiver

หากต้องการแก้ปัญหาผู้ส่ง คุณต้องเปิดใช้การบันทึก

ดูรายการชั้นเรียน วิธีการ และเหตุการณ์ทั้งหมดในเฟรมเวิร์ก Google Cast สำหรับ iOS ได้ที่ข้อมูลอ้างอิง Google Cast iOS API ส่วนต่อไปนี้ครอบคลุมขั้นตอน ในการผสานรวม Cast เข้ากับแอป iOS

วิธีการโทรจากเทรดหลัก

เริ่มต้นบริบทการแคสต์

เฟรมเวิร์ก Cast มีออบเจ็กต์ Singleton สำหรับจัดการทั่วโลก ซึ่งก็คือ GCKCastContext ซึ่งประสานงานกิจกรรมทั้งหมดของเฟรมเวิร์ก ออบเจ็กต์นี้ต้องเริ่มต้นล่วงหน้าในวงจรของแอปพลิเคชัน โดยทั่วไปจะอยู่ในเมธอด -[application:didFinishLaunchingWithOptions:] ของการมอบสิทธิ์แอป เพื่อให้การเริ่มเซสชันอัตโนมัติอีกครั้งเมื่อรีสตาร์ทแอปของผู้ส่งสามารถทริกเกอร์ได้อย่างถูกต้อง

ต้องระบุออบเจ็กต์ GCKCastOptions เมื่อเริ่มต้น GCKCastContext คลาสนี้มีตัวเลือกที่ส่งผลต่อลักษณะการทำงานของเฟรมเวิร์ก สิ่งสำคัญที่สุดคือรหัสแอปพลิเคชัน Web Receiver ซึ่งใช้เพื่อกรองผลการค้นพบและเปิดแอป Web Receiver เมื่อเซสชันการแคสต์เริ่มต้น

นอกจากนี้ เมธอด -[application:didFinishLaunchingWithOptions:] ยังเป็นตำแหน่งที่ดีในการตั้งค่าผู้รับมอบสิทธิ์การบันทึกให้รับข้อความการบันทึกข้อมูลจากเฟรมเวิร์ก ซึ่งอาจเป็นประโยชน์ต่อการแก้ไขข้อบกพร่องและการแก้ปัญหา

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

วิดเจ็ต Cast UX

Cast iOS SDK มีวิดเจ็ตที่เป็นไปตามรายการตรวจสอบการออกแบบแคสต์ดังต่อไปนี้

  • การวางซ้อนเบื้องต้น: คลาส GCKCastContext มีเมธอด presentCastInstructionsViewControllerOnceWithCastButton ซึ่งใช้เพื่อแสดงปุ่ม "แคสต์" ให้โดดเด่นในครั้งแรกที่เว็บรีซีฟเวอร์พร้อมให้บริการ แอปผู้ส่งปรับแต่งข้อความ ตำแหน่งของข้อความชื่อ และปุ่มปิดได้

  • ปุ่มแคสต์: ตั้งแต่ SDK 4.6.0 ผู้ส่งของ iOS แคสต์ ปุ่ม "แคสต์" จะปรากฏอยู่เสมอเมื่ออุปกรณ์ผู้ส่งเชื่อมต่อกับ Wi-Fi ครั้งแรกที่ผู้ใช้แตะปุ่ม "แคสต์" หลังจากเปิดแอปครั้งแรก กล่องโต้ตอบสิทธิ์จะปรากฏขึ้นเพื่อให้ผู้ใช้ให้สิทธิ์เข้าถึงเครือข่ายภายในแก่แอปแก่อุปกรณ์ในเครือข่ายได้ จากนั้นเมื่อผู้ใช้แตะปุ่ม "แคสต์" กล่องโต้ตอบการแคสต์จะปรากฏขึ้น ซึ่งจะแสดงรายการอุปกรณ์ที่พบ เมื่อผู้ใช้แตะปุ่ม "แคสต์" ในขณะที่อุปกรณ์เชื่อมต่ออยู่ จะแสดงข้อมูลเมตาของสื่อปัจจุบัน (เช่น ชื่อ ชื่อสตูดิโอบันทึกเสียง และภาพขนาดย่อ) หรืออนุญาตให้ผู้ใช้ยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ เมื่อผู้ใช้แตะปุ่ม "แคสต์" ในขณะที่ไม่มีอุปกรณ์ หน้าจอจะแสดงขึ้นมาเพื่อแจ้งให้ผู้ใช้ทราบถึงสาเหตุที่ไม่พบอุปกรณ์ และวิธีแก้ปัญหา

  • มินิคอนโทรลเลอร์: เมื่อผู้ใช้กำลังแคสต์เนื้อหาและออกจากหน้าเนื้อหาปัจจุบันหรือออกไปจากหน้าเนื้อหาปัจจุบันหรือขยายตัวควบคุมไปยังหน้าจออื่นในแอปผู้ส่ง มินิคอนโทรลเลอร์จะแสดงที่ด้านล่างของหน้าจอเพื่อให้ผู้ใช้ดูข้อมูลเมตาของสื่อที่กำลังแคสต์อยู่และควบคุมการเล่นได้

  • ตัวควบคุมที่ขยาย: เมื่อผู้ใช้กำลังแคสต์เนื้อหา หากผู้ใช้คลิกที่การแจ้งเตือนสื่อหรือมินิคอนโทรลเลอร์ ตัวควบคุมที่ขยายแล้วจะเปิดขึ้น ซึ่งจะแสดงข้อมูลเมตาของสื่อที่เล่นอยู่ในขณะนั้นและมีปุ่มต่างๆ สำหรับควบคุมการเล่นสื่อด้วย

เพิ่มปุ่ม "แคสต์"

เฟรมเวิร์กนี้จะมีคอมโพเนนต์ปุ่ม "แคสต์" เป็นคลาสย่อย UIButton คุณเพิ่มแอปลงในแถบชื่อแอปได้โดยการรวมไว้ใน UIBarButtonItem คลาสย่อย UIViewController ทั่วไปจะติดตั้งปุ่ม "แคสต์" ได้ดังนี้

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

โดยค่าเริ่มต้น การแตะปุ่มดังกล่าวจะเปิดกล่องโต้ตอบ "แคสต์" ที่เฟรมเวิร์กระบุไว้

นอกจากนี้ คุณยังเพิ่ม GCKUICastButton ลงในสตอรีบอร์ดโดยตรงได้ด้วย

กำหนดค่าการค้นพบอุปกรณ์

ในเฟรมเวิร์ก การค้นพบอุปกรณ์จะเกิดขึ้นโดยอัตโนมัติ คุณไม่จำเป็นต้องเริ่มหรือหยุดกระบวนการค้นหาโดยเจาะจง เว้นแต่ว่าคุณจะใช้ UI ที่กำหนดเอง

ชั้นเรียนจะเป็นผู้จัดการการค้นพบในเฟรมเวิร์ก GCKDiscoveryManager ซึ่งเป็นพร็อพเพอร์ตี้ของ GCKCastContext เฟรมเวิร์กนี้จะมีคอมโพเนนต์กล่องโต้ตอบการแคสต์เริ่มต้นสำหรับการเลือกและการควบคุมอุปกรณ์ รายการอุปกรณ์จะเรียงลำดับแบบพจนานุกรมตามชื่อที่เหมาะกับอุปกรณ์

วิธีการทำงานของการจัดการเซสชัน

Cast SDK นำเสนอแนวคิดของเซสชันการแคสต์ ซึ่งเป็นการสร้างขั้นตอนรวมขั้นตอนการเชื่อมต่อกับอุปกรณ์ การเปิด (หรือการเข้าร่วม) แอปตัวรับเว็บ การเชื่อมต่อกับแอปดังกล่าว และการเริ่มต้นช่องทางควบคุมสื่อ ดูข้อมูลเพิ่มเติมเกี่ยวกับเซสชันของการแคสต์และวงจรการใช้งานตัวรับเว็บได้ในคู่มือวงจรของแอปพลิเคชันในฝั่งตัวรับเว็บ

เซสชันจะได้รับการจัดการโดยชั้นเรียน GCKSessionManager ซึ่งเป็นพร็อพเพอร์ตี้ของ GCKCastContext เซสชันแต่ละรายการจะแสดงโดยคลาสย่อยของชั้นเรียน GCKSession: เช่น GCKCastSession หมายถึงเซสชันที่มีอุปกรณ์แคสต์ คุณเข้าถึงเซสชันการแคสต์ที่ใช้งานอยู่ในปัจจุบันได้ (หากมี) เป็นพร็อพเพอร์ตี้ currentCastSession ของ GCKSessionManager

คุณใช้อินเทอร์เฟซ GCKSessionManagerListener เพื่อตรวจสอบเหตุการณ์ของเซสชันได้ เช่น การสร้างเซสชัน การระงับ การกลับมาทำงานอีกครั้ง และการสิ้นสุด เฟรมเวิร์กนี้จะระงับเซสชันโดยอัตโนมัติเมื่อแอปผู้ส่งทำงานอยู่เบื้องหลังและพยายามกลับมาใช้งานอีกครั้งเมื่อแอปกลับมาทำงานในเบื้องหน้า (หรือเปิดขึ้นใหม่หลังจากที่มีการสิ้นสุดแอปที่ผิดปกติ/ฉับพลันขณะที่เซสชันทำงานอยู่)

หากกำลังใช้กล่องโต้ตอบ "แคสต์" ระบบจะสร้างและแยกเซสชันออกโดยอัตโนมัติตามท่าทางสัมผัสของผู้ใช้ ไม่เช่นนั้น แอปจะเริ่มต้นและสิ้นสุดเซสชันอย่างชัดแจ้งผ่านเมธอดใน GCKSessionManager ได้

หากแอปต้องทำการประมวลผลพิเศษเพื่อตอบสนองต่อเหตุการณ์ในวงจรของเซสชัน แอปจะลงทะเบียนอินสแตนซ์ GCKSessionManagerListener อย่างน้อย 1 รายการกับ GCKSessionManager ได้ GCKSessionManagerListener เป็นโปรโตคอลที่กำหนดการเรียกกลับสำหรับเหตุการณ์อย่างเช่นการเริ่มเซสชัน การจบเซสชัน และอื่นๆ

การโอนสตรีม

การรักษาสถานะเซสชันเป็นพื้นฐานของการโอนสตรีม ซึ่งผู้ใช้สามารถย้ายสตรีมเสียงและวิดีโอที่มีอยู่ในอุปกรณ์ต่างๆ โดยใช้คำสั่งเสียง, แอป Google Home หรือจออัจฉริยะ สื่อจะหยุดเล่นบนอุปกรณ์หนึ่ง (ต้นทาง) และจะเล่นต่อในอุปกรณ์อื่น (ปลายทาง) อุปกรณ์แคสต์ทุกเครื่องที่มีเฟิร์มแวร์เวอร์ชันล่าสุดจะใช้เป็นแหล่งที่มาหรือปลายทางในการโอนสตรีมได้

หากต้องการรับอุปกรณ์ปลายทางใหม่ระหว่างการโอนสตรีม ให้ใช้พร็อพเพอร์ตี้ GCKCastSession#device ในระหว่างการเรียกกลับ [sessionManager:didResumeCastSession:]

ดูข้อมูลเพิ่มเติมได้ที่การโอนสตรีมบน Web Receiver

การเชื่อมต่ออีกครั้งอัตโนมัติ

เฟรมเวิร์ก Cast เพิ่มตรรกะการเชื่อมต่อใหม่เพื่อจัดการการเชื่อมต่ออีกครั้งโดยอัตโนมัติในกรณีเล็กๆ หลายจุด เช่น

  • กู้คืนจากสัญญาณ Wi-Fi ที่หายไปชั่วคราว
  • กู้คืนจากโหมดสลีปของอุปกรณ์
  • กู้คืนจากการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
  • กู้คืนหากแอปขัดข้อง

วิธีการทำงานของการควบคุมสื่อ

หากเซสชันการแคสต์สร้างขึ้นด้วยแอปตัวรับเว็บที่รองรับเนมสเปซสื่อ เฟรมเวิร์กจะสร้างอินสแตนซ์ของ GCKRemoteMediaClient โดยอัตโนมัติ และเข้าถึงในฐานะพร็อพเพอร์ตี้ remoteMediaClient ของอินสแตนซ์ GCKCastSession ได้

วิธีการทั้งหมดใน GCKRemoteMediaClient ซึ่งส่งคำขอไปยังเว็บรีซีฟเวอร์ จะส่งคืนออบเจ็กต์ GCKRequest ซึ่งใช้เพื่อติดตามคำขอนั้นได้ คุณมอบหมาย GCKRequestDelegate ให้กับออบเจ็กต์นี้เพื่อรับการแจ้งเตือนเกี่ยวกับผลลัพธ์สุดท้ายของการดำเนินการได้

คาดว่าอาจมีการแชร์อินสแตนซ์ของ GCKRemoteMediaClient ในหลายส่วนของแอป และอันที่จริงแล้วคอมโพเนนต์ภายในของเฟรมเวิร์ก เช่น กล่องโต้ตอบการแคสต์และตัวควบคุมสื่อขนาดเล็กจะแชร์อินสแตนซ์ดังกล่าว ด้วยเหตุนี้ GCKRemoteMediaClient จึงรองรับการลงทะเบียน GCKRemoteMediaClientListener หลายรายการ

ตั้งค่าข้อมูลเมตาของสื่อ

ชั้นเรียน GCKMediaMetadata แสดงข้อมูลเกี่ยวกับรายการสื่อที่คุณต้องการแคสต์ ตัวอย่างต่อไปนี้จะสร้างอินสแตนซ์ GCKMediaMetadata ใหม่ของภาพยนตร์และตั้งชื่อ คำบรรยาย ชื่อสตูดิโอบันทึกเสียง และรูปภาพ 2 รูป

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

ดูส่วนการเลือกรูปภาพและการแคชเกี่ยวกับการใช้รูปภาพที่มีข้อมูลเมตาของสื่อ

โหลดสื่อ

หากต้องการโหลดรายการสื่อ ให้สร้างอินสแตนซ์ GCKMediaInformation โดยใช้ข้อมูลเมตาของสื่อ จากนั้นดาวน์โหลด GCKCastSession เวอร์ชันปัจจุบันและใช้ GCKRemoteMediaClient เพื่อโหลดสื่อในแอปตัวรับสัญญาณ จากนั้นคุณใช้ GCKRemoteMediaClient เพื่อควบคุมแอปมีเดียเพลเยอร์ที่ทำงานอยู่บนตัวรับได้ เช่น เพื่อเล่น หยุดชั่วคราว และหยุด

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

นอกจากนี้ โปรดดูส่วนการใช้แทร็กสื่อ

รูปแบบวิดีโอ 4K

หากต้องการทราบว่าสื่อของคุณเป็นวิดีโอรูปแบบใด ให้ใช้พร็อพเพอร์ตี้ videoInfo ของ GCKMediaStatus เพื่อรับอินสแตนซ์ปัจจุบันของ GCKVideoInfo อินสแตนซ์นี้มีประเภทรูปแบบทีวี HDR รวมถึงความสูงและความกว้างเป็นพิกเซล ระบบจะระบุตัวแปรของรูปแบบ 4K ในพร็อพเพอร์ตี้ hdrType โดยใช้ค่า enum GCKVideoInfoHDRType

เพิ่มตัวควบคุมขนาดเล็ก

ตามรายการตรวจสอบการออกแบบการแคสต์ แอปของผู้ส่งควรมีการควบคุมถาวรที่เรียกว่าตัวควบคุมขนาดเล็ก ซึ่งควรปรากฏขึ้นเมื่อผู้ใช้ออกจากหน้าเนื้อหาปัจจุบัน ตัวควบคุมขนาดเล็กช่วยให้เข้าถึงได้ทันทีและการช่วยเตือนที่มองเห็นได้สำหรับเซสชันการแคสต์ปัจจุบัน

เฟรมเวิร์กแคสต์จะมีแถบควบคุม GCKUIMiniMediaControlsViewController ซึ่งเพิ่มลงในฉากที่ต้องการแสดงตัวควบคุมขนาดเล็กได้

เมื่อแอปผู้ส่งเล่นวิดีโอหรือสตรีมแบบสด SDK จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมขนาดเล็ก

โปรดดูปรับแต่ง UI ของผู้ส่ง iOS เพื่อให้แอปผู้ส่งกำหนดค่ารูปลักษณ์ของวิดเจ็ตแคสต์ได้

การเพิ่มตัวควบคุมขนาดเล็กลงในแอปผู้ส่งทำได้ 2 วิธีดังนี้

  • ให้เฟรมเวิร์ก Cast จัดการเลย์เอาต์ของตัวควบคุมขนาดเล็กโดยการรวมตัวควบคุมมุมมองที่มีอยู่เข้ากับตัวควบคุมมุมมองของตัวเอง
  • จัดการเลย์เอาต์ของวิดเจ็ตตัวควบคุมขนาดเล็กด้วยตัวเองโดยเพิ่มไปยังตัวควบคุมมุมมองที่มีอยู่โดยใส่มุมมองย่อยในสตอรีบอร์ด

ตัดโดยใช้ GCKUICastContainerViewController

วิธีแรกคือการใช้ GCKUICastContainerViewController ซึ่งรวมตัวควบคุมมุมมองอื่นและเพิ่ม GCKUIMiniMediaControlsViewController ไว้ที่ด้านล่าง แนวทางนี้มีข้อจำกัดคือคุณจะไม่สามารถปรับแต่งภาพเคลื่อนไหวและกำหนดค่าลักษณะการทำงานของตัวควบคุมมุมมองคอนเทนเนอร์ได้

วิธีแรกมักจะทำในเมธอด -[application:didFinishLaunchingWithOptions:] ของการมอบสิทธิ์แอป

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

ฝังในตัวควบคุมมุมมองที่มีอยู่

วิธีที่ 2 คือการเพิ่มตัวควบคุมขนาดเล็กลงในตัวควบคุมมุมมองที่มีอยู่โดยตรงโดยใช้ createMiniMediaControlsViewController เพื่อสร้างอินสแตนซ์ GCKUIMiniMediaControlsViewController แล้วเพิ่มลงในตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย

ตั้งค่าตัวควบคุมการดูในการมอบสิทธิ์แอป

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

ในตัวควบคุมมุมมองรูท ให้สร้างอินสแตนซ์ GCKUIMiniMediaControlsViewController แล้วเพิ่มลงในตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย

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 จะบอกตัวควบคุมมุมมองโฮสต์เมื่อตัวควบคุมขนาดเล็กควรมองเห็นได้

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

เพิ่มตัวควบคุมที่ขยาย

รายการตรวจสอบสำหรับการออกแบบของ Google Cast กำหนดให้แอปผู้ส่งมีตัวควบคุมแบบขยายสำหรับสื่อที่กำลังแคสต์ ตัวควบคุมแบบขยายจะเป็นตัวควบคุมขนาดเล็ก เวอร์ชันเต็มหน้าจอ

ตัวควบคุมที่ขยายเป็นมุมมองแบบเต็มหน้าจอที่ควบคุมการเล่นสื่อระยะไกลได้อย่างเต็มที่ มุมมองนี้ควรอนุญาตให้แอปแคสต์จัดการทุกแง่มุมที่จัดการได้ของเซสชันการแคสต์ ยกเว้นการควบคุมระดับเสียงของตัวรับเว็บและวงจรเซสชัน (เชื่อมต่อ/หยุดการแคสต์) และยังให้ข้อมูลสถานะทั้งหมดเกี่ยวกับเซสชันสื่อ (อาร์ตเวิร์ก ชื่อ ชื่อรอง ฯลฯ)

ฟังก์ชันของมุมมองนี้ดำเนินการโดยคลาส GCKUIExpandedMediaControlsViewController

สิ่งแรกที่ต้องทำคือเปิดใช้ตัวควบคุมแบบขยายเริ่มต้นในบริบทการแคสต์ แก้ไขผู้รับมอบสิทธิ์แอปเพื่อเปิดใช้ตัวควบคุมแบบขยายเริ่มต้น

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

เพิ่มโค้ดต่อไปนี้ลงในตัวควบคุมมุมมองเพื่อโหลดตัวควบคุมที่ขยายเมื่อผู้ใช้เริ่มแคสต์วิดีโอ

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

ตัวควบคุมที่ขยายจะเปิดขึ้นโดยอัตโนมัติเมื่อผู้ใช้แตะตัวควบคุมขนาดเล็ก

เมื่อแอปของผู้ส่งกำลังเล่นสตรีมแบบสดผ่านวิดีโอหรือเสียง SDK จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมที่ขยายแล้ว

ดูใช้รูปแบบที่กำหนดเองกับแอป iOS ของคุณ สำหรับวิธีที่แอปผู้ส่งของคุณสามารถกำหนดค่าลักษณะที่ปรากฏของวิดเจ็ตแคสต์ได้

การควบคุมระดับเสียง

เฟรมเวิร์กของ Cast จะจัดการระดับเสียงของแอปผู้ส่งโดยอัตโนมัติ เฟรมเวิร์กจะซิงค์กับระดับเสียงของตัวรับเว็บสำหรับวิดเจ็ต UI ที่ให้มาโดยอัตโนมัติ หากต้องการซิงค์แถบเลื่อนที่แอปมีให้ ให้ใช้ GCKUIDeviceVolumeController

ตัวควบคุมระดับเสียงของปุ่มจริง

ปุ่มปรับระดับเสียงในอุปกรณ์ผู้ส่งสามารถใช้เปลี่ยนระดับเสียงของเซสชันการแคสต์ในเว็บรีซีฟเวอร์ได้โดยใช้แฟล็ก physicalVolumeButtonsWillControlDeviceVolume ใน GCKCastOptions ซึ่งตั้งค่าไว้ใน 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];

จัดการข้อผิดพลาด

แอปของผู้ส่งควรจัดการกับการเรียกกลับที่ผิดพลาดทั้งหมดและตัดสินใจเลือกการตอบสนองที่ดีที่สุดสำหรับแต่ละขั้นในวงจรของการแคสต์ แอปสามารถแสดงกล่องโต้ตอบข้อผิดพลาดต่อผู้ใช้ หรือทำให้สามารถปิดเซสชันการแคสต์ได้

Logging

GCKLogger เป็นรายการเดียวที่ใช้สำหรับการบันทึกตามเฟรมเวิร์ก ใช้ GCKLoggerDelegate เพื่อปรับแต่งวิธีจัดการข้อความบันทึก

เมื่อใช้ GCKLogger SDK จะสร้างเอาต์พุตการบันทึกในรูปแบบของข้อความแก้ไขข้อบกพร่อง ข้อผิดพลาด และคำเตือน ข้อความบันทึกเหล่านี้ช่วยแก้ไขข้อบกพร่องและมีประโยชน์ต่อการแก้ปัญหาและระบุปัญหา โดยค่าเริ่มต้น เอาต์พุตของบันทึกจะถูกระงับ แต่เมื่อกำหนด GCKLoggerDelegate แอปผู้ส่งจะได้รับข้อความเหล่านี้จาก SDK และบันทึกข้อความเหล่านั้นไปยังคอนโซลระบบได้

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

หากต้องการเปิดใช้การแก้ไขข้อบกพร่องและข้อความแบบละเอียดด้วย ให้เพิ่มบรรทัดนี้ลงในโค้ดหลังจากตั้งค่าผู้รับมอบสิทธิ์ (แสดงก่อนหน้านี้)

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;

นอกจากนี้ คุณยังกรองข้อความในบันทึกที่ GCKLogger สร้างได้ด้วย กำหนดระดับการบันทึกขั้นต่ำต่อชั้นเรียน เช่น

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;

ชื่อคลาสอาจเป็นชื่อตามตัวอักษรหรือรูปแบบ glob เช่น GCKUI\* และ GCK\*Session