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

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

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

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

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

โฟลว์ของแอป

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

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

เมธอด -[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

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

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

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

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

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

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

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

Swift
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = UIColor.gray
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
Objective-C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

เมธอดทั้งหมดใน GCKRemoteMediaClient ซึ่งส่งคำขอไปยัง Web Receiver จะแสดง GCKRequest ออบเจ็กต์ซึ่ง ใช้เพื่อติดตามคำขอนั้นได้ A 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 TV รวมถึงความสูงและความกว้างเป็นพิกเซล ระบบจะระบุรูปแบบ 4K ที่แตกต่างกันในพร็อพเพอร์ตี้ hdrType ด้วยค่า Enum GCKVideoInfoHDRType

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

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

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

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

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

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

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

รวมโดยใช้ 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

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

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

ตั้งค่า View Controller ในผู้รับมอบสิทธิ์ของแอปดังนี้

Swift
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  ...

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

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

  ...

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  self.window.clipsToBounds = YES;

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

  ...

  return YES;
}

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

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

Swift
  func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController,
                                       shouldAppear _: Bool) {
    updateControlBarsVisibility()
  }
Objective-C
- (void)miniMediaControlsViewController:
            (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController
                           shouldAppear:(BOOL)shouldAppear {
  [self updateControlBarsVisibility];
}

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

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

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

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

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

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

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

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

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

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

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

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

การบันทึก

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

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