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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

สวิฟต์
@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)
    }
  }
}
วัตถุประสงค์-ค

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

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

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

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

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

สวิฟต์
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
วัตถุประสงค์-ค
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 เริ่มต้นสำหรับการเลือกและควบคุมอุปกรณ์ รายการอุปกรณ์จะเรียงลำดับแบบพจนานุกรมตามชื่อที่เหมาะกับอุปกรณ์

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

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

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

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

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

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

การโอนสตรีม

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

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

ดูข้อมูลเพิ่มเติมได้ที่การโอนสตรีมบนเว็บรีซีฟเวอร์

เชื่อมต่อใหม่โดยอัตโนมัติ

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

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

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

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

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

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

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

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

สวิฟต์
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))
วัตถุประสงค์-ค
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 เพื่อควบคุมแอปโปรแกรมเล่นสื่อที่ทำงานอยู่บนตัวรับ เช่น เพื่อเล่น หยุดชั่วคราว และหยุด

สวิฟต์
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
}
วัตถุประสงค์-ค
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 ที่รวมตัวควบคุมมุมมองอีก 1 รายการและเพิ่ม GCKUIMiniMediaControlsViewController ที่ด้านล่าง แนวทางนี้มีข้อจำกัดตรงที่คุณไม่สามารถปรับแต่งภาพเคลื่อนไหวและกำหนดค่าลักษณะการทำงานของตัวควบคุมมุมมองคอนเทนเนอร์ได้

โดยทั่วไป วิธีแรกนี้จะทําในเมธอด -[application:didFinishLaunchingWithOptions:] ของผู้รับมอบสิทธิ์แอป

สวิฟต์
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()

  ...
}
วัตถุประสงค์-ค
- (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
    }
  }
}
วัตถุประสงค์-ค

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 แล้วเพิ่มลงในตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย

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

สวิฟต์
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
}
วัตถุประสงค์-ค
- (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 แล้วเพิ่มลงในตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย

สวิฟต์
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)
    }
  }

...
วัตถุประสงค์-ค

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

สวิฟต์
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
วัตถุประสงค์-ค
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

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

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

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

คลาส GCKUIExpandedMediaControlsViewController ใช้ฟังก์ชันของมุมมองนี้

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

สวิฟต์
func applicationDidFinishLaunching(_ application: UIApplication) {
  ..

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

  ...
}
วัตถุประสงค์-ค
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

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

สวิฟต์
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

  // Load your media
  sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation)
}
วัตถุประสงค์-ค
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

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

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

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

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

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

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

การควบคุมระดับเสียงของปุ่มบนเครื่อง

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

สวิฟต์
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
วัตถุประสงค์-ค
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

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

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

Logging

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

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

สวิฟต์
@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)
    }
  }
}
วัตถุประสงค์-ค

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

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

สวิฟต์
let filter = GCKLoggerFilter.init()
filter.minimumLevel = GCKLoggerLevel.verbose
GCKLogger.sharedInstance().filter = filter
วัตถุประสงค์-ค
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setMinimumLevel:GCKLoggerLevelVerbose];
[GCKLogger sharedInstance].filter = filter;

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

สวิฟต์
let filter = GCKLoggerFilter.init()
filter.setLoggingLevel(GCKLoggerLevel.verbose, forClasses: ["GCKUICastButton",
                                                            "GCKUIImageCache",
                                                            "NSMutableDictionary"])
GCKLogger.sharedInstance().filter = filter
วัตถุประสงค์-ค
GCKLoggerFilter *filter = [[GCKLoggerFilter alloc] init];
[filter setLoggingLevel:GCKLoggerLevelVerbose
             forClasses:@[@"GCKUICastButton",
                          @"GCKUIImageCache",
                          @"NSMutableDictionary"
                          ]];
[GCKLogger sharedInstance].filter = filter;

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