คู่มือนักพัฒนาซอฟต์แวร์นี้อธิบายวิธีเพิ่มการรองรับ Google Cast ลงในแอปตัวส่ง iOS โดยใช้ iOS Sender SDK
อุปกรณ์เคลื่อนที่หรือแล็ปท็อปคือผู้ส่งที่ควบคุมการเล่น และอุปกรณ์ Google Cast คือผู้รับที่แสดงเนื้อหาบนทีวี
เฟรมเวิร์กของผู้ส่งหมายถึงไบนารีของไลบรารีคลาส Cast และทรัพยากรที่เกี่ยวข้องซึ่งมีอยู่ในรันไทม์บนผู้ส่ง แอปผู้ส่งหรือแอปแคสต์ หมายถึงแอปที่ทำงานบนอุปกรณ์ของผู้ส่งด้วย แอปตัวรับสัญญาณบนเว็บ หมายถึงแอปพลิเคชัน HTML ที่ทำงานบนตัวรับสัญญาณบนเว็บ
เฟรมเวิร์กผู้ส่งใช้การออกแบบการเรียกกลับแบบอะซิงโครนัสเพื่อแจ้งให้แอปผู้ส่งทราบถึงเหตุการณ์ต่างๆ และเพื่อเปลี่ยนสถานะต่างๆ ของวงจรการใช้งานแอป Cast
โฟลว์ของแอป
ขั้นตอนต่อไปนี้อธิบายขั้นตอนการดำเนินการระดับสูงทั่วไปสำหรับแอป iOS ของผู้ส่ง
- เฟรมเวิร์ก Cast จะเริ่ม
GCKDiscoveryManager
โดยอิงตามพร็อพเพอร์ตี้ที่ระบุในGCKCastOptions
เพื่อ เริ่มสแกนหาอุปกรณ์ - เมื่อผู้ใช้คลิกปุ่มแคสต์ เฟรมเวิร์กจะแสดงกล่องโต้ตอบแคสต์ พร้อมรายการอุปกรณ์แคสต์ที่ค้นพบ
- เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิด แอปตัวรับสัญญาณเว็บในอุปกรณ์แคสต์
- เฟรมเวิร์กจะเรียกใช้การเรียกกลับในแอปผู้ส่งเพื่อยืนยันว่ามีการเปิดใช้แอป 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 เมื่อเริ่มเซสชันการแคสต์
-[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
ซึ่งใช้เพื่อไฮไลต์ปุ่มแคสต์ได้เมื่อ Web Receiver พร้อมใช้งานเป็นครั้งแรก แอปผู้ส่งสามารถปรับแต่งข้อความ ตำแหน่งของข้อความชื่อ และปุ่มปิดได้ปุ่มแคสต์: ตั้งแต่ SDK ผู้ส่ง Cast สำหรับ iOS เวอร์ชัน 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 การเชื่อมต่อกับแอปนั้น และการเริ่มต้นช่องควบคุมสื่อ ดูข้อมูลเพิ่มเติมเกี่ยวกับเซสชัน Cast และวงจรของ Web Receiver ได้ที่คู่มือวงจรของแอปพลิเคชัน Web Receiver
เซสชันได้รับการจัดการโดยคลาส
GCKSessionManager
ซึ่งเป็นพร็อพเพอร์ตี้ของ
GCKCastContext
เซสชันแต่ละรายการจะแสดงด้วยคลาสย่อยของคลาส
GCKSession
เช่น
GCKCastSession
แสดงเซสชันที่มีอุปกรณ์แคสต์ คุณสามารถเข้าถึงเซสชัน Cast ที่ใช้งานอยู่ (หากมี) เป็นพร็อพเพอร์ตี้ currentCastSession
ของ GCKSessionManager
คุณสามารถใช้อินเทอร์เฟซ
GCKSessionManagerListener
เพื่อตรวจสอบเหตุการณ์เซสชัน เช่น การสร้างเซสชัน การระงับ การกลับมาทำงานต่อ และการสิ้นสุด เฟรมเวิร์กจะระงับเซสชันโดยอัตโนมัติ
เมื่อแอปผู้ส่งเข้าสู่เบื้องหลัง และพยายามกลับมาทำงานต่อ
เมื่อแอปกลับมาทำงานเบื้องหน้า (หรือเปิดใหม่หลังจากแอป
สิ้นสุดการทำงานอย่างผิดปกติ/กะทันหันขณะที่เซสชันทำงานอยู่)
หากใช้กล่องโต้ตอบการแคสต์ ระบบจะสร้างและปิดเซสชันโดยอัตโนมัติเพื่อตอบสนองต่อท่าทางของผู้ใช้ ไม่เช่นนั้น แอปจะเริ่มและสิ้นสุดเซสชันอย่างชัดเจนผ่านเมธอดใน GCKSessionManager
หากแอปต้องทำการประมวลผลพิเศษเพื่อตอบสนองต่อเหตุการณ์วงจรเซสชัน
แอปสามารถลงทะเบียนอินสแตนซ์ GCKSessionManagerListener
อย่างน้อย 1 รายการด้วย GCKSessionManager
GCKSessionManagerListener
เป็นโปรโตคอลที่กำหนด
การเรียกกลับสำหรับเหตุการณ์ต่างๆ เช่น การเริ่มต้นเซสชัน การสิ้นสุดเซสชัน และอื่นๆ
การถ่ายโอนสตรีม
การรักษาสถานะเซสชันเป็นพื้นฐานของการโอนสตรีม ซึ่งผู้ใช้สามารถย้ายสตรีมเสียงและวิดีโอที่มีอยู่ไปยังอุปกรณ์ต่างๆ ได้โดยใช้คำสั่งเสียง, แอป Google Home หรือจออัจฉริยะ สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อในอีกอุปกรณ์หนึ่ง (ปลายทาง) อุปกรณ์ Cast ที่มีเฟิร์มแวร์ล่าสุดสามารถใช้เป็นแหล่งที่มาหรือปลายทางในการ โอนสตรีมได้
หากต้องการรับอุปกรณ์ปลายทางใหม่ระหว่างการโอนสตรีม ให้ใช้พร็อพเพอร์ตี้
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
เพื่อโหลดสื่อในแอปตัวรับ จากนั้นคุณจะใช้ 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 TV รวมถึงความสูงและความกว้างในหน่วย
พิกเซล ระบบจะระบุรูปแบบ 4K ที่แตกต่างกันในพร็อพเพอร์ตี้ hdrType
โดยใช้ค่า enum GCKVideoInfoHDRType
เพิ่มมินิคอนโทรลเลอร์
ตามรายการตรวจสอบ การออกแบบ Cast แอปตัวส่งควรมีตัวควบคุมแบบถาวรที่เรียกว่ามินิ คอนโทรลเลอร์ ซึ่งควรปรากฏขึ้นเมื่อผู้ใช้ออกจากหน้าเนื้อหาปัจจุบัน มินิคอนโทรลเลอร์ช่วยให้เข้าถึงได้ทันทีและแสดงการช่วยเตือนที่มองเห็นได้สำหรับ เซสชัน Cast ปัจจุบัน
เฟรมเวิร์ก Cast มีแถบควบคุม
GCKUIMiniMediaControlsViewController
ซึ่งเพิ่มลงในฉากที่คุณต้องการแสดงมินิคอนโทรลเลอร์ได้
เมื่อแอปตัวส่งกำลังเล่นไลฟ์สดวิดีโอหรือเสียง SDK จะแสดงปุ่มเล่น/หยุดแทนปุ่มเล่น/หยุดชั่วคราวในมินิคอนโทรลเลอร์โดยอัตโนมัติ
ดูปรับแต่ง UI ของผู้ส่ง iOS เพื่อดูวิธีที่แอปผู้ส่ง กำหนดค่าลักษณะของวิดเจ็ต Cast
การเพิ่มมินิคอนโทรลเลอร์ลงในแอปผู้ส่งทำได้ 2 วิธีดังนี้
- ปล่อยให้เฟรมเวิร์ก Cast จัดการเลย์เอาต์ของมินิคอนโทรลเลอร์โดยการห่อหุ้ม ตัวควบคุมมุมมองที่มีอยู่ด้วยตัวควบคุมมุมมองของเฟรมเวิร์กเอง
- จัดการเลย์เอาต์ของวิดเจ็ตมินิคอนโทรลเลอร์ด้วยตนเองโดยการเพิ่มลงใน ตัวควบคุมมุมมองที่มีอยู่โดยระบุมุมมองย่อยใน Storyboard
Wrap โดยใช้ GCKUICastContainerViewController
วิธีแรกคือการใช้
GCKUICastContainerViewController
ซึ่งจะครอบคลุม View Controller อื่นและเพิ่ม
GCKUIMiniMediaControlsViewController
ที่ด้านล่าง วิธีนี้มีข้อจำกัดตรงที่คุณปรับแต่งภาพเคลื่อนไหวและกำหนดค่าลักษณะการทำงานของ Container View Controller ไม่ได้
โดยปกติแล้ว วิธีแรกนี้จะทำในเมธอด
-[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
ฝังในตัวควบคุมมุมมองที่มีอยู่
วิธีที่ 2 คือการเพิ่มมินิคอนโทรลเลอร์ลงใน ViewController ที่มีอยู่โดยตรงโดยใช้
createMiniMediaControlsViewController
เพื่อสร้างอินสแตนซ์
GCKUIMiniMediaControlsViewController
แล้วเพิ่มลงใน ViewController ของคอนเทนเนอร์เป็นมุมมองย่อย
ตั้งค่าตัวควบคุมมุมมองในตัวแทนแอป
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
สิ่งแรกที่คุณต้องทำคือเปิดใช้ตัวควบคุมที่ขยายเริ่มต้นในบริบทการแคสต์ แก้ไข App Delegate เพื่อเปิดใช้ตัวควบคุมที่ขยายเริ่มต้นโดยทำดังนี้
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
เพิ่มโค้ดต่อไปนี้ลงในตัวควบคุมมุมมองเพื่อโหลดตัวควบคุมแบบขยาย เมื่อผู้ใช้เริ่มแคสต์วิดีโอ
func playSelectedItemRemotely() { GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() ... // Load your media sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation) }
- (void)playSelectedItemRemotely { [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls]; ... // Load your media [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation]; }
นอกจากนี้ ระบบจะเปิดตัวควบคุมแบบขยายโดยอัตโนมัติเมื่อผู้ใช้ แตะตัวควบคุมขนาดเล็กด้วย
เมื่อแอปตัวส่งกำลังเล่นไลฟ์สดวิดีโอหรือเสียง SDK จะแสดงปุ่มเล่น/หยุดแทนปุ่มเล่น/หยุดชั่วคราว ในตัวควบคุมแบบขยายโดยอัตโนมัติ
ดูวิธีที่แอปผู้ส่งจะกำหนดค่าลักษณะของวิดเจ็ต Cast ได้ที่ใช้สไตล์ที่กำหนดเองกับแอป iOS
การควบคุมระดับเสียง
เฟรมเวิร์ก Cast จะจัดการระดับเสียงสำหรับแอปผู้ส่งโดยอัตโนมัติ
เฟรมเวิร์กจะซิงโครไนซ์กับระดับเสียงของ Web Receiver โดยอัตโนมัติสำหรับ
วิดเจ็ต UI ที่ระบุ หากต้องการซิงค์แถบเลื่อนที่แอปมีให้ ให้ใช้
GCKUIDeviceVolumeController
การควบคุมระดับเสียงด้วยปุ่มบนตัวเครื่อง
คุณสามารถใช้ปุ่มปรับระดับเสียงจริงบนอุปกรณ์ที่ส่งเพื่อเปลี่ยนระดับเสียงของเซสชันการแคสต์ใน Web Receiver โดยใช้
physicalVolumeButtonsWillControlDeviceVolume
flag ใน
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;
ชื่อคลาสอาจเป็นชื่อตามตัวอักษรหรือรูปแบบ Glob เช่น
GCKUI\*
และ GCK\*Session