คู่มือนักพัฒนาแอปนี้อธิบายวิธีเพิ่มการรองรับ Google Cast ลงในแอป iOS Sender โดยใช้ iOS Sender SDK
อุปกรณ์เคลื่อนที่หรือแล็ปท็อปคือผู้ส่งที่ควบคุมการเล่น และอุปกรณ์ Google Cast คือผู้รับที่แสดงเนื้อหาบนทีวี
เฟรมเวิร์กของผู้ส่งหมายถึงไบนารีไลบรารีคลาส Cast และทรัพยากรที่เกี่ยวข้องซึ่งแสดงในรันไทม์ของผู้ส่ง แอปผู้ส่งหรือแอปแคสต์หมายถึงแอปที่ทำงานบนอุปกรณ์ของผู้ส่งด้วย แอป Web Receiver หมายถึงแอปพลิเคชัน HTML ที่ทำงานบน Web Receiver
เฟรมเวิร์กผู้ส่งใช้การออกแบบการเรียกกลับแบบอะซิงโครนัสเพื่อแจ้งเหตุการณ์ให้แอปผู้ส่งทราบและเพื่อเปลี่ยนสถานะต่างๆ ของวงจรชีวิตของแอป Cast
ขั้นตอนของแอป
ขั้นตอนต่อไปนี้อธิบายขั้นตอนการดำเนินการระดับสูงทั่วไปสำหรับผู้ส่งแอป iOS
- เฟรมเวิร์กแคสต์จะเริ่มทำงาน
GCKDiscoveryManager
โดยอิงตามพร็อพเพอร์ตี้ที่ระบุในGCKCastOptions
เพื่อเริ่มสแกนหาอุปกรณ์ - เมื่อผู้ใช้คลิกปุ่มแคสต์ เฟรมเวิร์กจะแสดงกล่องโต้ตอบแคสต์พร้อมรายการอุปกรณ์แคสต์ที่ค้นพบ
- เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิดแอปตัวรับเว็บในอุปกรณ์แคสต์
- เฟรมเวิร์กจะเรียกใช้การเรียกกลับในแอปผู้ส่งเพื่อยืนยันว่าแอปตัวรับเว็บเปิดขึ้นแล้ว
- เฟรมเวิร์กจะสร้างช่องทางการสื่อสารระหว่างผู้ส่งกับแอป Web Receiver
- เฟรมเวิร์กใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมการเล่นสื่อใน Web Receiver
- เฟรมเวิร์กจะซิงค์สถานะการเล่นสื่อระหว่างผู้ส่งและตัวรับเว็บ เมื่อผู้ใช้ดำเนินการกับ UI ของผู้ส่ง เฟรมเวิร์กจะส่งคําขอควบคุมสื่อเหล่านั้นไปยังตัวรับเว็บ และเมื่อตัวรับเว็บส่งการอัปเดตสถานะสื่อ เฟรมเวิร์กจะอัปเดตสถานะของ UI ของผู้ส่ง
- เมื่อผู้ใช้คลิกปุ่มแคสต์เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ เฟรมเวิร์กจะยกเลิกการเชื่อมต่อแอปผู้ส่งจากเว็บรีซีฟเวอร์
หากต้องการแก้ปัญหาเกี่ยวกับผู้ส่ง คุณต้องเปิดใช้การบันทึก
ดูรายการคลาส เมธอด และเหตุการณ์ทั้งหมดในเฟรมเวิร์ก Google Cast บน iOS ได้ที่เอกสารอ้างอิง Google Cast iOS API ส่วนต่อไปนี้จะอธิบายขั้นตอนในการผสานรวม Cast เข้ากับแอป iOS
เรียกเมธอดจากเทรดหลัก
เริ่มต้นบริบทการแคสต์
เฟรมเวิร์ก Cast มีออบเจ็กต์แบบ Singleton ทั่วโลก ซึ่งก็คือ GCKCastContext
ซึ่งจะประสานงานกิจกรรมทั้งหมดของเฟรมเวิร์ก ออบเจ็กต์นี้ต้องได้รับการเริ่มต้นตั้งแต่เนิ่นๆ ในวงจรชีวิตของแอปพลิเคชัน โดยปกติแล้วจะเป็นในเมธอด -[application:didFinishLaunchingWithOptions:]
ของ App Delegate เพื่อให้เซสชันกลับมาทำงานต่อโดยอัตโนมัติเมื่อแอปฝั่งผู้ส่งเริ่มทำงานอีกครั้ง
ต้องระบุออบเจ็กต์ GCKCastOptions
เมื่อเริ่มต้น GCKCastContext
คลาสนี้มีตัวเลือกที่ส่งผลต่อลักษณะการทํางานของเฟรมเวิร์ก ข้อมูลที่สำคัญที่สุดคือรหัสแอปพลิเคชันตัวรับเว็บ ซึ่งใช้กรองผลการค้นหาและเปิดแอปตัวรับเว็บเมื่อเริ่มเซสชันแคสต์
นอกจากนี้ เมธอด -[application:didFinishLaunchingWithOptions:]
ยังเหมาะสําหรับการตั้งค่าผู้รับมอบสิทธิ์การบันทึกเพื่อรับข้อความการบันทึกจากเฟรมเวิร์กด้วย
ซึ่งอาจเป็นประโยชน์ต่อการแก้ไขข้อบกพร่องและแก้ปัญหา
@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
วิดเจ็ต UX ของ Cast
Cast iOS SDK มีวิดเจ็ตต่อไปนี้ที่เป็นไปตามรายการตรวจสอบการออกแบบ Cast
การวางซ้อนข้อมูลเบื้องต้น: คลาส
GCKCastContext
มีเมธอดpresentCastInstructionsViewControllerOnceWithCastButton
ซึ่งสามารถใช้เพื่อไฮไลต์ปุ่มแคสต์ได้เมื่อเว็บรีซีฟเวอร์พร้อมใช้งานเป็นครั้งแรก แอปของผู้ส่งสามารถปรับแต่งข้อความ ตำแหน่งของข้อความชื่อ และปุ่มปิดปุ่มแคสต์: ตั้งแต่ Cast iOS Sender SDK 4.6.0 เป็นต้นไป ปุ่มแคสต์จะปรากฏขึ้นเสมอเมื่ออุปกรณ์ที่ส่งเชื่อมต่อกับ Wi-Fi เมื่อผู้ใช้แตะปุ่มแคสต์เป็นครั้งแรกหลังจากเริ่มแอปครั้งแรก กล่องโต้ตอบสิทธิ์จะปรากฏขึ้นเพื่อให้ผู้ใช้ให้สิทธิ์เข้าถึงเครือข่าย LAN แก่แอปสำหรับอุปกรณ์ในเครือข่าย จากนั้นเมื่อผู้ใช้แตะปุ่มแคสต์ กล่องโต้ตอบแคสต์จะปรากฏขึ้นพร้อมแสดงรายการอุปกรณ์ที่ค้นพบ เมื่อผู้ใช้แตะปุ่มแคสต์ขณะที่อุปกรณ์เชื่อมต่ออยู่ ระบบจะแสดงข้อมูลเมตาของสื่อปัจจุบัน (เช่น ชื่อ ชื่อสตูดิโอบันทึกเสียง และรูปภาพขนาดย่อ) หรืออนุญาตให้ผู้ใช้ยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ เมื่อผู้ใช้แตะปุ่มแคสต์ขณะที่ไม่มีอุปกรณ์พร้อมใช้งาน หน้าจอจะแสดงข้อมูลแก่ผู้ใช้เกี่ยวกับสาเหตุที่ไม่พบอุปกรณ์และวิธีแก้ปัญหา
ตัวควบคุมขนาดเล็ก: เมื่อผู้ใช้แคสต์เนื้อหาและออกจากหน้าเนื้อหาปัจจุบันหรือตัวควบคุมแบบขยายไปยังหน้าจออื่นในแอปผู้ส่ง ตัวควบคุมขนาดเล็กจะแสดงที่ด้านล่างของหน้าจอเพื่อให้ผู้ใช้ดูข้อมูลเมตาของสื่อที่กำลังแคสต์และควบคุมการเล่นได้
ตัวควบคุมแบบขยาย: เมื่อผู้ใช้แคสต์เนื้อหา หากคลิกการแจ้งเตือนสื่อหรือตัวควบคุมขนาดเล็ก ตัวควบคุมแบบขยายจะเปิดขึ้น ซึ่งจะแสดงข้อมูลเมตาของสื่อที่เล่นอยู่ในปัจจุบันและมีปุ่มต่างๆ เพื่อควบคุมการเล่นสื่อ
เพิ่มปุ่มแคสต์
เฟรมเวิร์กมีคอมโพเนนต์ปุ่มแคสต์เป็น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 เริ่มต้นสำหรับการเลือกและควบคุมอุปกรณ์ รายการอุปกรณ์จะเรียงตามลําดับตัวอักษรตามชื่อที่เข้าใจง่ายของอุปกรณ์
วิธีการทํางานของการจัดการเซสชัน
Cast SDK นำเสนอแนวคิดเซสชัน Cast ซึ่งการสร้างเซสชันนี้รวมขั้นตอนการเชื่อมต่อกับอุปกรณ์ การเปิด (หรือเข้าร่วม) แอป Web Receiver การเชื่อมต่อกับแอปดังกล่าว และเริ่มต้นช่องการควบคุมสื่อ ดูข้อมูลเพิ่มเติมเกี่ยวกับเซสชันแคสต์และวงจรชีวิตของ Web Receiver ได้ในคู่มือวงจรชีวิตของแอปพลิเคชันของ Web Receiver
เซสชันได้รับการจัดการโดยคลาส GCKSessionManager
ซึ่งเป็นพร็อพเพอร์ตี้ของ GCKCastContext
เซสชันแต่ละรายการจะแสดงโดยคลาสย่อยของคลาส GCKSession
เช่น GCKCastSession
แสดงเซสชันที่มีอุปกรณ์แคสต์ คุณสามารถเข้าถึงเซสชัน Cast ที่ใช้งานอยู่ในปัจจุบัน (หากมี) ในฐานะพร็อพเพอร์ตี้ currentCastSession
ของ GCKSessionManager
คุณสามารถใช้อินเทอร์เฟซ GCKSessionManagerListener
เพื่อตรวจสอบเหตุการณ์เซสชัน เช่น การสร้างเซสชัน การระงับ การกลับมาทำงานต่อ และการสิ้นสุด เฟรมเวิร์กจะระงับเซสชันโดยอัตโนมัติเมื่อแอปผู้ส่งเปลี่ยนไปทำงานในเบื้องหลัง และจะพยายามกลับมาทำงานต่อเมื่อแอปกลับมาทำงานในเบื้องหน้า (หรือมีการเปิดแอปอีกครั้งหลังจากการสิ้นสุดแอปอย่างผิดปกติ/กะทันหันขณะที่เซสชันทำงานอยู่)
หากมีการใช้กล่องโต้ตอบแคสต์ ระบบจะสร้างและปิดเซสชันโดยอัตโนมัติเพื่อตอบสนองต่อท่าทางสัมผัสของผู้ใช้ หรือแอปจะเริ่มต้นและสิ้นสุดเซสชันได้อย่างชัดเจนผ่านเมธอดใน GCKSessionManager
หากแอปต้องดำเนินการประมวลผลพิเศษเพื่อตอบสนองต่อเหตุการณ์ในวงจรของเซสชัน แอปจะลงทะเบียนอินสแตนซ์ GCKSessionManagerListener
อย่างน้อย 1 รายการกับ GCKSessionManager
ได้ GCKSessionManagerListener
เป็นโปรโตคอลที่กําหนดการเรียกกลับสําหรับเหตุการณ์ต่างๆ เช่น การเริ่มต้นเซสชัน การสิ้นสุดเซสชัน และอื่นๆ
การโอนสตรีม
การรักษาสถานะเซสชันเป็นพื้นฐานของการโอนสตรีม ซึ่งผู้ใช้สามารถย้ายสตรีมเสียงและวิดีโอที่มีอยู่ไปยังอุปกรณ์ต่างๆ ได้โดยใช้คำสั่งเสียง แอป Google Home หรือจออัจฉริยะ สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อในอีกอุปกรณ์หนึ่ง (ปลายทาง) อุปกรณ์แคสต์ทุกรุ่นที่ใช้เฟิร์มแวร์ล่าสุดสามารถทำหน้าที่เป็นแหล่งที่มาหรือปลายทางในการโอนสตรีม
หากต้องการรับอุปกรณ์ปลายทางเครื่องใหม่ระหว่างการโอนสตรีม ให้ใช้พร็อพเพอร์ตี้ GCKCastSession#device
ในระหว่างการเรียกกลับ [sessionManager:didResumeCastSession:]
ดูข้อมูลเพิ่มเติมได้ในหัวข้อการโอนสตรีมใน Web Receiver
การเชื่อมต่อใหม่อัตโนมัติ
เฟรมเวิร์ก Cast จะเพิ่มตรรกะการเชื่อมต่ออีกครั้งเพื่อจัดการการเชื่อมต่ออีกครั้งโดยอัตโนมัติในสถานการณ์เฉพาะที่ละเอียดอ่อนหลายประการ เช่น
- กู้คืนจาก Wi-Fi ที่ขาดหายไปชั่วคราว
- กู้คืนจากโหมดสลีปของอุปกรณ์
- กู้คืนจากแอปที่ทำงานอยู่เบื้องหลัง
- กู้คืนหากแอปขัดข้อง
วิธีการทำงานของการควบคุมสื่อ
หากสร้างเซสชัน Cast กับแอป Web Receiver ที่รองรับเนมสเปซสื่อ เฟรมเวิร์กจะสร้างอินสแตนซ์ของ GCKRemoteMediaClient
โดยอัตโนมัติ ซึ่งเข้าถึงได้ในฐานะพร็อพเพอร์ตี้ remoteMediaClient
ของอินสแตนซ์ GCKCastSession
เมธอดทั้งหมดใน GCKRemoteMediaClient
ที่ส่งคําขอไปยัง Web Receiver จะแสดงผลออบเจ็กต์ GCKRequest
ซึ่งสามารถใช้ติดตามคําขอนั้นได้ คุณสามารถกำหนด GCKRequestDelegate
ให้กับออบเจ็กต์นี้เพื่อรับการแจ้งเตือนเกี่ยวกับผลลัพธ์สุดท้ายของการดำเนินการ
อินสแตนซ์ของ GCKRemoteMediaClient
อาจมีการแชร์โดยส่วนต่างๆ ของแอป และคอมโพเนนต์ภายในบางอย่างของเฟรมเวิร์ก เช่น กล่องโต้ตอบแคสต์และตัวควบคุมสื่อขนาดเล็กก็แชร์อินสแตนซ์เดียวกัน ด้วยเหตุนี้ GCKRemoteMediaClient
จึงรองรับการจดทะเบียน GCKRemoteMediaClientListener
หลายรายการ
ตั้งค่าข้อมูลเมตาของสื่อ
คลาส
GCKMediaMetadata
แสดงข้อมูลเกี่ยวกับรายการสื่อที่คุณต้องการแคสต์ ตัวอย่างต่อไปนี้สร้างอินสแตนซ์ GCKMediaMetadata
ใหม่ของภาพยนตร์และตั้งชื่อ คำบรรยาย ชื่อสตูดิโอบันทึกเสียง และรูปภาพ 2 รูป
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
ของ GCKCastSession
เพื่อโหลดสื่อในแอปรีซีฟเวอร์ จากนั้นคุณจะใช้ 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 ปัจจุบัน
เฟรมเวิร์ก 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() ... }
- (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]; ... }
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
ฝังใน View Controller ที่มีอยู่
วิธีที่สองคือเพิ่มตัวควบคุมขนาดเล็กลงในตัวควบคุมมุมมองที่มีอยู่โดยตรงโดยใช้ createMiniMediaControlsViewController
เพื่อสร้างอินสแตนซ์ GCKUIMiniMediaControlsViewController
แล้วเพิ่มลงในตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย
ตั้งค่า ViewController ใน App Delegate โดยทำดังนี้
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 กําหนดให้แอปผู้ส่งต้องมีตัวควบคุมแบบขยายสําหรับสื่อที่แคสต์ ตัวควบคุมแบบขยายคือตัวควบคุมมินิเวอร์ชันเต็มหน้าจอ
ตัวควบคุมแบบขยายคือมุมมองแบบเต็มหน้าจอที่ให้การควบคุมการเล่นสื่อจากระยะไกลได้อย่างเต็มที่ มุมมองนี้ควรอนุญาตให้แอปแคสต์จัดการแง่มุมที่จัดการได้ทั้งหมดของเซสชันการแคสต์ ยกเว้นการควบคุมระดับเสียงของ Web Receiver และวงจรของเซสชัน (เชื่อมต่อ/หยุดแคสต์) รวมถึงให้ข้อมูลสถานะทั้งหมดเกี่ยวกับเซสชันสื่อ (อาร์ตเวิร์ก ชื่อ คำบรรยาย และอื่นๆ)
ฟังก์ชันการทำงานของมุมมองนี้จะใช้คลาส GCKUIExpandedMediaControlsViewController
สิ่งแรกที่ต้องทำคือการเปิดใช้ตัวควบคุมแบบขยายเริ่มต้นในบริบทแคสต์ แก้ไขผู้รับมอบสิทธิ์แอปเพื่อเปิดใช้ตัวควบคุมแบบขยายเริ่มต้น
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
เพิ่มโค้ดต่อไปนี้ลงใน ViewController เพื่อโหลดตัวควบคุมแบบขยายเมื่อผู้ใช้เริ่มแคสต์วิดีโอ
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
การควบคุมระดับเสียง
เฟรมเวิร์กแคสต์จะจัดการระดับเสียงสำหรับแอปผู้ส่งโดยอัตโนมัติ เฟรมเวิร์กจะซิงค์กับระดับเสียงของเว็บรีซีฟเวอร์โดยอัตโนมัติสำหรับวิดเจ็ต 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];
จัดการข้อผิดพลาด
แอปฝั่งที่ส่งต้องจัดการการเรียกกลับข้อผิดพลาดทั้งหมดและตัดสินใจเลือกการตอบสนองที่ดีที่สุดสำหรับแต่ละระยะของวงจร Cast แอปสามารถแสดงกล่องโต้ตอบแสดงข้อผิดพลาดต่อผู้ใช้หรือเลือกที่จะสิ้นสุดเซสชันแคสต์ก็ได้
การบันทึก
GCKLogger
เป็น Singleton ที่เฟรมเวิร์กใช้สำหรับการบันทึก ใช้ GCKLoggerDelegate
เพื่อปรับแต่งวิธีจัดการข้อความบันทึก
เมื่อใช้ GCKLogger
ทาง SDK จะสร้างเอาต์พุตการบันทึกในรูปแบบข้อความแก้ไขข้อบกพร่อง ข้อผิดพลาด และคำเตือน ข้อความบันทึกเหล่านี้ช่วยแก้ไขข้อบกพร่องและเป็นประโยชน์ในการแก้ปัญหาและระบุปัญหา โดยค่าเริ่มต้น ระบบจะระงับเอาต์พุตบันทึก แต่การกำหนด 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;
ชื่อคลาสอาจเป็นชื่อตามตัวอักษรหรือรูปแบบทั่วไปก็ได้ เช่น GCKUI\*
และ GCK\*Session