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

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

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

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

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

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

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

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

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

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

เรียกใช้เมธอดจากเทรดหลัก

เริ่มต้นบริบทของ Cast

เฟรมเวิร์ก 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

วิดเจ็ต UX ของ Cast

Cast iOS SDK มีวิดเจ็ตเหล่านี้ซึ่งเป็นไปตามรายการตรวจสอบการออกแบบ Cast

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

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

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

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

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

เฟรมเวิร์กมีคอมโพเนนต์ปุ่มแคสต์เป็นคลาสย่อย 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];

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

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

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

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

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

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

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

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

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

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

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

การถ่ายโอนสตรีม

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

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

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

การเชื่อมต่อใหม่อัตโนมัติ

เฟรมเวิร์ก Cast จะเพิ่มตรรกะการเชื่อมต่อใหม่เพื่อจัดการการเชื่อมต่อใหม่โดยอัตโนมัติในกรณีที่พบไม่บ่อยนัก เช่น

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

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

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

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

คาดว่าอินสแตนซ์ของ GCKRemoteMediaClient อาจแชร์โดยหลายส่วนของแอป และคอมโพเนนต์ภายในบางอย่างของเฟรมเวิร์ก เช่น กล่องโต้ตอบ Cast และตัวควบคุมสื่อขนาดเล็กก็แชร์อินสแตนซ์ด้วย ด้วยเหตุนี้ 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

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

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

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

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

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

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

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

รวมโดยใช้ GCKUICastContainerViewController

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

วิธีแรกนี้มักจะทำในเมธอด -[application:didFinishLaunchingWithOptions:] ของ App Delegate ดังนี้

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

ฝังใน View Controller ที่มีอยู่

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

ตั้งค่า View Controller ใน App Delegate ดังนี้

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

ใน Root View Controller ให้สร้างอินสแตนซ์ GCKUIMiniMediaControlsViewController และเพิ่มลงใน Container View Controller เป็นมุมมองย่อย ดังนี้

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 กำหนดให้แอปผู้ส่งต้องมีตัวควบคุมแบบขยายสำหรับสื่อที่แคสต์ ตัวควบคุมแบบขยายคือตัวควบคุมขนาดเล็กเวอร์ชันเต็มหน้าจอ

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

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

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

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 จะแสดงปุ่มเล่น/หยุดแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมแบบขยายโดยอัตโนมัติ

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

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

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

การควบคุมระดับเสียงด้วยปุ่มจริง

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

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

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

โปรดทราบว่าข้อผิดพลาดบางอย่าง รวมถึง GCKErrorCode GCKErrorCodeCancelled เป็นลักษณะการทำงานที่ตั้งใจไว้

อย่าพยายามลองเชื่อมต่ออีกครั้งหากการเชื่อมต่อกับ GCKErrorCodeCancelled ล้มเหลว การดำเนินการดังกล่าวอาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิด

การบันทึก

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