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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

สวิฟต์
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

  func applicationDidFinishLaunching(_ application: UIApplication) {
    let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
วัตถุประสงค์-ค

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc]
                                    initWithApplicationID:kReceiverAppID];
  GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
  [GCKCastContext setSharedInstanceWithOptions:options];

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

วิดเจ็ต Cast UX

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

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

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

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

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

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

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

สวิฟต์
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
วัตถุประสงค์-ค
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

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

หรือจะเพิ่ม GCKUICastButton ลงในสตอรีบอร์ดโดยตรงก็ได้

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

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

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

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

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

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

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

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

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

โอนสตรีม

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

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

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

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

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

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

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

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

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

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

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

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

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

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

โหลดสื่อ

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

สวิฟต์
let url = URL.init(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
guard let mediaURL = url else {
  print("invalid mediaURL")
  return
}

let mediaInfoBuilder = GCKMediaInformationBuilder.init(contentURL: mediaURL)
mediaInfoBuilder.streamType = GCKMediaStreamType.none;
mediaInfoBuilder.contentType = "video/mp4"
mediaInfoBuilder.metadata = metadata;
mediaInformation = mediaInfoBuilder.build()

guard let mediaInfo = mediaInformation else {
  print("invalid mediaInformation")
  return
}

if let request = sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInfo) {
  request.delegate = self
}
วัตถุประสงค์-ค
GCKMediaInformationBuilder *mediaInfoBuilder =
  [[GCKMediaInformationBuilder alloc] initWithContentURL:
   [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"]];
mediaInfoBuilder.streamType = GCKMediaStreamTypeNone;
mediaInfoBuilder.contentType = @"video/mp4";
mediaInfoBuilder.metadata = metadata;
self.mediaInformation = [mediaInfoBuilder build];

GCKRequest *request = [self.sessionManager.currentSession.remoteMediaClient loadMedia:self.mediaInformation];
if (request != nil) {
  request.delegate = self;
}

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

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

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

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

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

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

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

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

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

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

ตัดข้อมูลโดยใช้ GCKUICastContainerViewController

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

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

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

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

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

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

}
Swift
var castControlBarsEnabled: Bool {
  set(enabled) {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      castContainerVC.miniMediaControlsItemEnabled = enabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
    }
  }
  get {
    if let castContainerVC = self.window?.rootViewController as? GCKUICastContainerViewController {
      return castContainerVC.miniMediaControlsItemEnabled
    } else {
      print("GCKUICastContainerViewController is not correctly configured")
      return false
    }
  }
}
วัตถุประสงค์-ค

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

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

@end

AppDelegate.m

@implementation AppDelegate

...

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

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

...

@end

ฝังในตัวควบคุมข้อมูลพร็อพเพอร์ตี้ที่มีอยู่

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

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

สวิฟต์
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  ...

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
  window?.clipsToBounds = true

  let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
  rootContainerVC?.miniMediaControlsViewEnabled = true

  ...

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  self.window.clipsToBounds = YES;

  RootContainerViewController *rootContainerVC;
  rootContainerVC =
      (RootContainerViewController *)self.window.rootViewController;
  rootContainerVC.miniMediaControlsViewEnabled = YES;

  ...

  return YES;
}

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

สวิฟต์
let kCastControlBarsAnimationDuration: TimeInterval = 0.20

@objc(RootContainerViewController)
class RootContainerViewController: UIViewController, GCKUIMiniMediaControlsViewControllerDelegate {
  @IBOutlet weak private var _miniMediaControlsContainerView: UIView!
  @IBOutlet weak private var _miniMediaControlsHeightConstraint: NSLayoutConstraint!
  private var miniMediaControlsViewController: GCKUIMiniMediaControlsViewController!
  var miniMediaControlsViewEnabled = false {
    didSet {
      if self.isViewLoaded {
        self.updateControlBarsVisibility()
      }
    }
  }

  var overriddenNavigationController: UINavigationController?

  override var navigationController: UINavigationController? {

    get {
      return overriddenNavigationController
    }

    set {
      overriddenNavigationController = newValue
    }
  }
  var miniMediaControlsItemEnabled = false

  override func viewDidLoad() {
    super.viewDidLoad()
    let castContext = GCKCastContext.sharedInstance()
    self.miniMediaControlsViewController = castContext.createMiniMediaControlsViewController()
    self.miniMediaControlsViewController.delegate = self
    self.updateControlBarsVisibility()
    self.installViewController(self.miniMediaControlsViewController,
                               inContainerView: self._miniMediaControlsContainerView)
  }

  func updateControlBarsVisibility() {
    if self.miniMediaControlsViewEnabled && self.miniMediaControlsViewController.active {
      self._miniMediaControlsHeightConstraint.constant = self.miniMediaControlsViewController.minHeight
      self.view.bringSubview(toFront: self._miniMediaControlsContainerView)
    } else {
      self._miniMediaControlsHeightConstraint.constant = 0
    }
    UIView.animate(withDuration: kCastControlBarsAnimationDuration, animations: {() -> Void in
      self.view.layoutIfNeeded()
    })
    self.view.setNeedsLayout()
  }

  func installViewController(_ viewController: UIViewController?, inContainerView containerView: UIView) {
    if let viewController = viewController {
      self.addChildViewController(viewController)
      viewController.view.frame = containerView.bounds
      containerView.addSubview(viewController.view)
      viewController.didMove(toParentViewController: self)
    }
  }

  func uninstallViewController(_ viewController: UIViewController) {
    viewController.willMove(toParentViewController: nil)
    viewController.view.removeFromSuperview()
    viewController.removeFromParentViewController()
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "NavigationVCEmbedSegue" {
      self.navigationController = (segue.destination as? UINavigationController)
    }
  }

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

RootContainerViewController.h

static const NSTimeInterval kCastControlBarsAnimationDuration = 0.20;

@interface RootContainerViewController () <GCKUIMiniMediaControlsViewControllerDelegate> {
  __weak IBOutlet UIView *_miniMediaControlsContainerView;
  __weak IBOutlet NSLayoutConstraint *_miniMediaControlsHeightConstraint;
  GCKUIMiniMediaControlsViewController *_miniMediaControlsViewController;
}

@property(nonatomic, weak, readwrite) UINavigationController *navigationController;

@property(nonatomic, assign, readwrite) BOOL miniMediaControlsViewEnabled;
@property(nonatomic, assign, readwrite) BOOL miniMediaControlsItemEnabled;

@end

RootContainerViewController.m

@implementation RootContainerViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  GCKCastContext *castContext = [GCKCastContext sharedInstance];
  _miniMediaControlsViewController =
      [castContext createMiniMediaControlsViewController];
  _miniMediaControlsViewController.delegate = self;

  [self updateControlBarsVisibility];
  [self installViewController:_miniMediaControlsViewController
              inContainerView:_miniMediaControlsContainerView];
}

- (void)setMiniMediaControlsViewEnabled:(BOOL)miniMediaControlsViewEnabled {
  _miniMediaControlsViewEnabled = miniMediaControlsViewEnabled;
  if (self.isViewLoaded) {
    [self updateControlBarsVisibility];
  }
}

- (void)updateControlBarsVisibility {
  if (self.miniMediaControlsViewEnabled &&
      _miniMediaControlsViewController.active) {
    _miniMediaControlsHeightConstraint.constant =
        _miniMediaControlsViewController.minHeight;
    [self.view bringSubviewToFront:_miniMediaControlsContainerView];
  } else {
    _miniMediaControlsHeightConstraint.constant = 0;
  }
  [UIView animateWithDuration:kCastControlBarsAnimationDuration
                   animations:^{
                     [self.view layoutIfNeeded];
                   }];
  [self.view setNeedsLayout];
}

- (void)installViewController:(UIViewController *)viewController
              inContainerView:(UIView *)containerView {
  if (viewController) {
    [self addChildViewController:viewController];
    viewController.view.frame = containerView.bounds;
    [containerView addSubview:viewController.view];
    [viewController didMoveToParentViewController:self];
  }
}

- (void)uninstallViewController:(UIViewController *)viewController {
  [viewController willMoveToParentViewController:nil];
  [viewController.view removeFromSuperview];
  [viewController removeFromParentViewController];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([segue.identifier isEqualToString:@"NavigationVCEmbedSegue"]) {
    self.navigationController =
        (UINavigationController *)segue.destinationViewController;
  }
}

...

@end

GCKUIMiniMediaControlsViewControllerDelegate จะแจ้งตัวควบคุมมุมมองโฮสต์ให้ทราบเมื่อตัวควบคุมขนาดเล็กควรปรากฏให้เห็น

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

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

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

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

คลาส GCKUIExpandedMediaControlsViewController ใช้งานฟังก์ชันของข้อมูลพร็อพเพอร์ตี้นี้

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

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

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

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

  ...

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

  ...

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

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

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

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

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

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

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

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

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

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

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

Logging

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

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

สวิฟต์
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GCKLoggerDelegate {
  let kReceiverAppID = kGCKDefaultMediaReceiverApplicationID
  let kDebugLoggingEnabled = true

  var window: UIWindow?

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

    // Enable logger.
    GCKLogger.sharedInstance().delegate = self

    ...
  }

  // MARK: - GCKLoggerDelegate

  func logMessage(_ message: String,
                  at level: GCKLoggerLevel,
                  fromFunction function: String,
                  location: String) {
    if (kDebugLoggingEnabled) {
      print(function + " - " + message)
    }
  }
}
วัตถุประสงค์-ค

AppDelegate.h

@interface AppDelegate () <GCKLoggerDelegate>
@end

AppDelegate.m

@implementation AppDelegate

static NSString *const kReceiverAppID = @"AABBCCDD";
static const BOOL kDebugLoggingEnabled = YES;

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

  // Enable logger.
  [GCKLogger sharedInstance].delegate = self;

  ...

  return YES;
}

...

#pragma mark - GCKLoggerDelegate

- (void)logMessage:(NSString *)message
           atLevel:(GCKLoggerLevel)level
      fromFunction:(NSString *)function
          location:(NSString *)location {
  if (kDebugLoggingEnabled) {
    NSLog(@"%@ - %@, %@", function, message, location);
  }
}

@end

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

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

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

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

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