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

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

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

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

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

กระแสของแอป

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

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

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

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

วิธีการโทรจากชุดข้อความหลัก

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

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

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

นอกจากนี้ เมธอด -[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)
    }
  }
}
วัตถุประสงค์-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

วิดเจ็ต Cast UX

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

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

  • ปุ่ม "แคสต์": 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)
วัตถุประสงค์-C
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
castButton.tintColor = [UIColor grayColor];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];

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

GCKUICastButton สามารถเพิ่มลงในบอร์ดโครงเรื่องได้โดยตรง

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

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

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

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

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

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

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

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

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

การโอนสตรีม

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

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

ดูการโอนสตรีมในเครื่องรับเว็บสําหรับข้อมูลเพิ่มเติม

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

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

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

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

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

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

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

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

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

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

สวิฟต์
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
}
วัตถุประสงค์-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 ด้วยค่าแจกแจง GCKVideoInfoHDRType

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

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

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

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

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

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

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

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

  ...
}
วัตถุประสงค์-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
    }
  }
}
วัตถุประสงค์-C

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate>

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

@end

AppDelegate.m

@implementation AppDelegate

...

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

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

...

@end

ฝังในตัวควบคุมมุมมองที่มีอยู่

วิธีที่ 2 คือเพิ่มตัวควบคุมขนาดเล็กในตัวควบคุมมุมมองที่มีอยู่โดยตรง โดยใช้ 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
}
วัตถุประสงค์-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 และเพิ่มไปยังตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย

สวิฟต์
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)
    }
  }

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

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

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

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

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

ฟังก์ชันของมุมมองนี้จะใช้โดยคลาส GCKUIExpandedMediaControlsViewController

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

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

  GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true

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

  [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES;

  ..
}

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

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

  ...

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

  ...

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

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

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

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

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

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

ปุ่มควบคุมระดับเสียง

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

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

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

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

Logging

GCKLogger เป็นเฟรมเวิร์กเดียวที่ใช้สําหรับการบันทึกโดยเฟรมเวิร์ก ใช้ 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)
    }
  }
}
วัตถุประสงค์-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

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

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

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