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

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

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

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

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

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

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

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

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

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

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

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

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

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

นอกจากนี้ เมธอด -[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 ซึ่งสามารถใช้เพื่อไฮไลต์ปุ่มแคสต์ได้เมื่อเว็บรีซีฟเวอร์พร้อมใช้งานเป็นครั้งแรก แอปของผู้ส่งสามารถปรับแต่งข้อความ ตำแหน่งของข้อความชื่อ และปุ่มปิด

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

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

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

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

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

การโอนสตรีม

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

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

ดูข้อมูลเพิ่มเติมได้ในหัวข้อการโอนสตรีมใน Web Receiver

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

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

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

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

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

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

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

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

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

Swift
let metadata = GCKMediaMetadata()
metadata.setString("Big Buck Bunny (2008)", forKey: kGCKMetadataKeyTitle)
metadata.setString("Big Buck Bunny tells the story of a giant rabbit with a heart bigger than " +
  "himself. When one sunny day three rodents rudely harass him, something " +
  "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon " +
  "tradition he prepares the nasty rodents a comical revenge.",
                   forKey: kGCKMetadataKeySubtitle)
metadata.addImage(GCKImage(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg")!,
                           width: 480,
                           height: 360))
Objective-C
GCKMediaMetadata *metadata = [[GCKMediaMetadata alloc]
                                initWithMetadataType:GCKMediaMetadataTypeMovie];
[metadata setString:@"Big Buck Bunny (2008)" forKey:kGCKMetadataKeyTitle];
[metadata setString:@"Big Buck Bunny tells the story of a giant rabbit with a heart bigger than "
 "himself. When one sunny day three rodents rudely harass him, something "
 "snaps... and the rabbit ain't no bunny anymore! In the typical cartoon "
 "tradition he prepares the nasty rodents a comical revenge."
             forKey:kGCKMetadataKeySubtitle];
[metadata addImage:[[GCKImage alloc]
                    initWithURL:[[NSURL alloc] initWithString:@"https://commondatastorage.googleapis.com/"
                                 "gtv-videos-bucket/sample/images/BigBuckBunny.jpg"]
                    width:480
                    height:360]];

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

โหลดสื่อ

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

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

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

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

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

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

ตัดโดยใช้ GCKUICastContainerViewController

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

โดยปกติแล้ววิธีแรกนี้จะดำเนินการในวิธี -[application:didFinishLaunchingWithOptions:] ของตัวแทนแอป

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

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
  let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
  let castContainerVC =
          GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
  castContainerVC.miniMediaControlsItemEnabled = true
  window = UIWindow(frame: UIScreen.main.bounds)
  window!.rootViewController = castContainerVC
  window!.makeKeyAndVisible()

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

  // Wrap main view in the GCKUICastContainerViewController and display the mini controller.
  UIStoryboard *appStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  UINavigationController *navigationController =
          [appStoryboard instantiateViewControllerWithIdentifier:@"MainNavigation"];
  GCKUICastContainerViewController *castContainerVC =
          [[GCKCastContext sharedInstance] createCastContainerControllerForViewController:navigationController];
  castContainerVC.miniMediaControlsItemEnabled = YES;
  self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
  self.window.rootViewController = castContainerVC;
  [self.window makeKeyAndVisible];
  ...

}
Swift
var castControlBarsEnabled: Bool {
  set(enabled) {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      castContainerVC.miniMediaControlsItemEnabled = enabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
    }
  }
  get {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      return castContainerVC.miniMediaControlsItemEnabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
      return false
    }
  }
}
Objective-C

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, assign) BOOL castControlBarsEnabled;

@end

AppDelegate.m

@implementation AppDelegate

...

- (void)setCastControlBarsEnabled:(BOOL)notificationsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  castContainerVC.miniMediaControlsItemEnabled = notificationsEnabled;
}

- (BOOL)castControlBarsEnabled {
  GCKUICastContainerViewController *castContainerVC;
  castContainerVC =
      (GCKUICastContainerViewController *)self.window.rootViewController;
  return castContainerVC.miniMediaControlsItemEnabled;
}

...

@end

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

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

ตั้งค่า ViewController ใน 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;
}

ในวิดเจ็ตราก ให้สร้างอินสแตนซ์ 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

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

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

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

Swift
func playSelectedItemRemotely() {
  GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()

  ...

  // Load your media
  sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation)
}
Objective-C
- (void)playSelectedItemRemotely {
  [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls];

  ...

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

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

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

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

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

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

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

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

Swift
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
Objective-C
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                          initWithApplicationID:kReceiverAppID];
GCKCastOptions *options = [[GCKCastOptions alloc]
                                          initWithDiscoveryCriteria :criteria];
options.physicalVolumeButtonsWillControlDeviceVolume = YES;
[GCKCastContext setSharedInstanceWithOptions:options];

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

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

การบันทึก

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;

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