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

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

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

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

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

โฟลว์ของแอป

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

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

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

ดูรายการคลาส เมธอด และเหตุการณ์ทั้งหมดในเฟรมเวิร์ก Google Cast สำหรับ iOS ได้ที่เอกสารอ้างอิง Google Cast iOS API ส่วนต่อไปนี้จะอธิบายขั้นตอน การผสานรวม 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];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ทุกวิธีใน GCKRemoteMediaClient ที่ออกคำขอไปยัง Web Receiver จะแสดงออบเจ็กต์ 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 จัดการเลย์เอาต์ของมินิคอนโทรลเลอร์โดยการห่อหุ้ม ตัวควบคุมมุมมองที่มีอยู่ด้วยตัวควบคุมมุมมองของเฟรมเวิร์กเอง
  • จัดการเลย์เอาต์ของวิดเจ็ตมินิคอนโทรลเลอร์ด้วยตนเองโดยการเพิ่มลงใน ตัวควบคุมมุมมองที่มีอยู่ด้วยการระบุมุมมองย่อยในสตอรี่บอร์ด

Wrap โดยใช้ GCKUICastContainerViewController

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

โดยปกติแล้ว วิธีแรกนี้จะทำในเมธอด -[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 คือการเพิ่มมินิคอนโทรลเลอร์ลงใน View Controller ที่มีอยู่โดยตรงโดยใช้ createMiniMediaControlsViewController เพื่อสร้างอินสแตนซ์ GCKUIMiniMediaControlsViewController แล้วเพิ่มลงใน Container View Controller เป็น Subview

ตั้งค่าตัวควบคุมมุมมองในตัวแทนแอป

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

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

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

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

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

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

การบันทึก

GCKLogger เป็น Singleton ที่เฟรมเวิร์กใช้สำหรับการบันทึก ใช้ 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