คู่มือนักพัฒนาซอฟต์แวร์นี้จะอธิบายวิธีเพิ่มการสนับสนุน Google Cast ไปยังแอปผู้ส่ง iOS โดยใช้ SDK ผู้ส่งของ iOS
อุปกรณ์เคลื่อนที่หรือแล็ปท็อปเป็นผู้ส่งซึ่งควบคุมการเล่น และอุปกรณ์ Google Cast เป็นเครื่องรับซึ่งจะแสดงเนื้อหาบนทีวี
เฟรมเวิร์กผู้ส่งหมายถึงไบนารีไลบรารีคลาสของ Cast และทรัพยากรที่เกี่ยวข้องซึ่งแสดงขึ้นขณะรันไทม์ของผู้ส่ง แอปผู้ส่งหรือแอปแคสต์หมายถึงแอปที่กำลังทำงานกับผู้ส่งด้วยเช่นกัน แอป Web Receiver หมายถึงแอปพลิเคชัน HTML ที่ทำงานบน Web Receiver
เฟรมเวิร์กผู้ส่งใช้การออกแบบ Callback แบบไม่พร้อมกันเพื่อแจ้งให้แอปผู้ส่งทราบถึงเหตุการณ์ต่างๆ และเพื่อสลับระหว่างสถานะต่างๆ ในวงจรของแอปแคสต์
ขั้นตอนของแอป
ขั้นตอนต่อไปนี้จะอธิบายถึงขั้นตอนการดำเนินการระดับสูงโดยทั่วไปสำหรับแอป iOS ของผู้ส่ง
- เฟรมเวิร์กการแคสต์จะเริ่มต้น
GCKDiscoveryManager
ตามพร็อพเพอร์ตี้ที่ระบุไว้ในGCKCastOptions
เพื่อเริ่มสแกนหาอุปกรณ์ - เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เฟรมเวิร์กจะแสดงกล่องโต้ตอบ "แคสต์" ที่มีรายการอุปกรณ์แคสต์ที่พบ
- เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิดแอปตัวรับเว็บในอุปกรณ์แคสต์
- เฟรมเวิร์กนี้จะเรียกใช้ Callback ในแอปผู้ส่งเพื่อยืนยันว่ามีการเปิดตัวแอป Web Receiver
- เฟรมเวิร์กจะสร้างช่องทางการสื่อสารระหว่างผู้ส่งกับแอปเว็บรีซีฟเวอร์
- เฟรมเวิร์กนี้จะใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมการเล่นสื่อบน Web Receiver
- เฟรมเวิร์กจะซิงค์สถานะการเล่นสื่อระหว่างผู้ส่งกับตัวรับเว็บ โดยเฟรมเวิร์กจะอัปเดตสถานะ UI ของผู้ส่ง เฟรมเวิร์กจะส่งคำขอตัวควบคุมสื่อดังกล่าวไปยังเว็บรีซีฟเวอร์
- เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์ Cast เฟรมเวิร์กจะยกเลิกการเชื่อมต่อแอปผู้ส่งจาก Web Receiver
หากต้องการแก้ปัญหาผู้ส่ง คุณต้องเปิดใช้การบันทึก
ดูรายการชั้นเรียน วิธีการ และเหตุการณ์ทั้งหมดในเฟรมเวิร์ก Google Cast สำหรับ iOS ได้ที่ข้อมูลอ้างอิง Google Cast iOS API ส่วนต่อไปนี้ครอบคลุมขั้นตอน ในการผสานรวม Cast เข้ากับแอป iOS
วิธีการโทรจากเทรดหลัก
เริ่มต้นบริบทการแคสต์
เฟรมเวิร์ก 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
วิดเจ็ต Cast UX
Cast iOS SDK มีวิดเจ็ตที่เป็นไปตามรายการตรวจสอบการออกแบบแคสต์ดังต่อไปนี้
การวางซ้อนเบื้องต้น: คลาส
GCKCastContext
มีเมธอดpresentCastInstructionsViewControllerOnceWithCastButton
ซึ่งใช้เพื่อแสดงปุ่ม "แคสต์" ให้โดดเด่นในครั้งแรกที่เว็บรีซีฟเวอร์พร้อมให้บริการ แอปผู้ส่งปรับแต่งข้อความ ตำแหน่งของข้อความชื่อ และปุ่มปิดได้ปุ่มแคสต์: ตั้งแต่ SDK 4.6.0 ผู้ส่งของ iOS แคสต์ ปุ่ม "แคสต์" จะปรากฏอยู่เสมอเมื่ออุปกรณ์ผู้ส่งเชื่อมต่อกับ Wi-Fi ครั้งแรกที่ผู้ใช้แตะปุ่ม "แคสต์" หลังจากเปิดแอปครั้งแรก กล่องโต้ตอบสิทธิ์จะปรากฏขึ้นเพื่อให้ผู้ใช้ให้สิทธิ์เข้าถึงเครือข่ายภายในแก่แอปแก่อุปกรณ์ในเครือข่ายได้ จากนั้นเมื่อผู้ใช้แตะปุ่ม "แคสต์" กล่องโต้ตอบการแคสต์จะปรากฏขึ้น ซึ่งจะแสดงรายการอุปกรณ์ที่พบ เมื่อผู้ใช้แตะปุ่ม "แคสต์" ในขณะที่อุปกรณ์เชื่อมต่ออยู่ จะแสดงข้อมูลเมตาของสื่อปัจจุบัน (เช่น ชื่อ ชื่อสตูดิโอบันทึกเสียง และภาพขนาดย่อ) หรืออนุญาตให้ผู้ใช้ยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ เมื่อผู้ใช้แตะปุ่ม "แคสต์" ในขณะที่ไม่มีอุปกรณ์ หน้าจอจะแสดงขึ้นมาเพื่อแจ้งให้ผู้ใช้ทราบถึงสาเหตุที่ไม่พบอุปกรณ์ และวิธีแก้ปัญหา
มินิคอนโทรลเลอร์: เมื่อผู้ใช้กำลังแคสต์เนื้อหาและออกจากหน้าเนื้อหาปัจจุบันหรือออกไปจากหน้าเนื้อหาปัจจุบันหรือขยายตัวควบคุมไปยังหน้าจออื่นในแอปผู้ส่ง มินิคอนโทรลเลอร์จะแสดงที่ด้านล่างของหน้าจอเพื่อให้ผู้ใช้ดูข้อมูลเมตาของสื่อที่กำลังแคสต์อยู่และควบคุมการเล่นได้
ตัวควบคุมที่ขยาย: เมื่อผู้ใช้กำลังแคสต์เนื้อหา หากผู้ใช้คลิกที่การแจ้งเตือนสื่อหรือมินิคอนโทรลเลอร์ ตัวควบคุมที่ขยายแล้วจะเปิดขึ้น ซึ่งจะแสดงข้อมูลเมตาของสื่อที่เล่นอยู่ในขณะนั้นและมีปุ่มต่างๆ สำหรับควบคุมการเล่นสื่อด้วย
เพิ่มปุ่ม "แคสต์"
เฟรมเวิร์กนี้จะมีคอมโพเนนต์ปุ่ม "แคสต์" เป็นคลาสย่อย UIButton
คุณเพิ่มแอปลงในแถบชื่อแอปได้โดยการรวมไว้ใน UIBarButtonItem
คลาสย่อย UIViewController
ทั่วไปจะติดตั้งปุ่ม "แคสต์" ได้ดังนี้
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24)) castButton.tintColor = UIColor.gray navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
GCKUICastButton *castButton = [[GCKUICastButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)]; castButton.tintColor = [UIColor grayColor]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:castButton];
โดยค่าเริ่มต้น การแตะปุ่มดังกล่าวจะเปิดกล่องโต้ตอบ "แคสต์" ที่เฟรมเวิร์กระบุไว้
นอกจากนี้ คุณยังเพิ่ม GCKUICastButton
ลงในสตอรีบอร์ดโดยตรงได้ด้วย
กำหนดค่าการค้นพบอุปกรณ์
ในเฟรมเวิร์ก การค้นพบอุปกรณ์จะเกิดขึ้นโดยอัตโนมัติ คุณไม่จำเป็นต้องเริ่มหรือหยุดกระบวนการค้นหาโดยเจาะจง เว้นแต่ว่าคุณจะใช้ UI ที่กำหนดเอง
ชั้นเรียนจะเป็นผู้จัดการการค้นพบในเฟรมเวิร์ก GCKDiscoveryManager
ซึ่งเป็นพร็อพเพอร์ตี้ของ GCKCastContext
เฟรมเวิร์กนี้จะมีคอมโพเนนต์กล่องโต้ตอบการแคสต์เริ่มต้นสำหรับการเลือกและการควบคุมอุปกรณ์ รายการอุปกรณ์จะเรียงลำดับแบบพจนานุกรมตามชื่อที่เหมาะกับอุปกรณ์
วิธีการทำงานของการจัดการเซสชัน
Cast SDK นำเสนอแนวคิดของเซสชันการแคสต์ ซึ่งเป็นการสร้างขั้นตอนรวมขั้นตอนการเชื่อมต่อกับอุปกรณ์ การเปิด (หรือการเข้าร่วม) แอปตัวรับเว็บ การเชื่อมต่อกับแอปดังกล่าว และการเริ่มต้นช่องทางควบคุมสื่อ ดูข้อมูลเพิ่มเติมเกี่ยวกับเซสชันของการแคสต์และวงจรการใช้งานตัวรับเว็บได้ในคู่มือวงจรของแอปพลิเคชันในฝั่งตัวรับเว็บ
เซสชันจะได้รับการจัดการโดยชั้นเรียน
GCKSessionManager
ซึ่งเป็นพร็อพเพอร์ตี้ของ
GCKCastContext
เซสชันแต่ละรายการจะแสดงโดยคลาสย่อยของชั้นเรียน
GCKSession
: เช่น
GCKCastSession
หมายถึงเซสชันที่มีอุปกรณ์แคสต์ คุณเข้าถึงเซสชันการแคสต์ที่ใช้งานอยู่ในปัจจุบันได้ (หากมี) เป็นพร็อพเพอร์ตี้ currentCastSession
ของ GCKSessionManager
คุณใช้อินเทอร์เฟซ GCKSessionManagerListener
เพื่อตรวจสอบเหตุการณ์ของเซสชันได้ เช่น การสร้างเซสชัน การระงับ การกลับมาทำงานอีกครั้ง และการสิ้นสุด เฟรมเวิร์กนี้จะระงับเซสชันโดยอัตโนมัติเมื่อแอปผู้ส่งทำงานอยู่เบื้องหลังและพยายามกลับมาใช้งานอีกครั้งเมื่อแอปกลับมาทำงานในเบื้องหน้า (หรือเปิดขึ้นใหม่หลังจากที่มีการสิ้นสุดแอปที่ผิดปกติ/ฉับพลันขณะที่เซสชันทำงานอยู่)
หากกำลังใช้กล่องโต้ตอบ "แคสต์" ระบบจะสร้างและแยกเซสชันออกโดยอัตโนมัติตามท่าทางสัมผัสของผู้ใช้ ไม่เช่นนั้น แอปจะเริ่มต้นและสิ้นสุดเซสชันอย่างชัดแจ้งผ่านเมธอดใน GCKSessionManager
ได้
หากแอปต้องทำการประมวลผลพิเศษเพื่อตอบสนองต่อเหตุการณ์ในวงจรของเซสชัน แอปจะลงทะเบียนอินสแตนซ์ GCKSessionManagerListener
อย่างน้อย 1 รายการกับ GCKSessionManager
ได้ GCKSessionManagerListener
เป็นโปรโตคอลที่กำหนดการเรียกกลับสำหรับเหตุการณ์อย่างเช่นการเริ่มเซสชัน การจบเซสชัน และอื่นๆ
การโอนสตรีม
การรักษาสถานะเซสชันเป็นพื้นฐานของการโอนสตรีม ซึ่งผู้ใช้สามารถย้ายสตรีมเสียงและวิดีโอที่มีอยู่ในอุปกรณ์ต่างๆ โดยใช้คำสั่งเสียง, แอป Google Home หรือจออัจฉริยะ สื่อจะหยุดเล่นบนอุปกรณ์หนึ่ง (ต้นทาง) และจะเล่นต่อในอุปกรณ์อื่น (ปลายทาง) อุปกรณ์แคสต์ทุกเครื่องที่มีเฟิร์มแวร์เวอร์ชันล่าสุดจะใช้เป็นแหล่งที่มาหรือปลายทางในการโอนสตรีมได้
หากต้องการรับอุปกรณ์ปลายทางใหม่ระหว่างการโอนสตรีม ให้ใช้พร็อพเพอร์ตี้ GCKCastSession#device
ในระหว่างการเรียกกลับ [sessionManager:didResumeCastSession:]
ดูข้อมูลเพิ่มเติมได้ที่การโอนสตรีมบน Web Receiver
การเชื่อมต่ออีกครั้งอัตโนมัติ
เฟรมเวิร์ก Cast เพิ่มตรรกะการเชื่อมต่อใหม่เพื่อจัดการการเชื่อมต่ออีกครั้งโดยอัตโนมัติในกรณีเล็กๆ หลายจุด เช่น
- กู้คืนจากสัญญาณ Wi-Fi ที่หายไปชั่วคราว
- กู้คืนจากโหมดสลีปของอุปกรณ์
- กู้คืนจากการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
- กู้คืนหากแอปขัดข้อง
วิธีการทำงานของการควบคุมสื่อ
หากเซสชันการแคสต์สร้างขึ้นด้วยแอปตัวรับเว็บที่รองรับเนมสเปซสื่อ เฟรมเวิร์กจะสร้างอินสแตนซ์ของ GCKRemoteMediaClient
โดยอัตโนมัติ และเข้าถึงในฐานะพร็อพเพอร์ตี้ remoteMediaClient
ของอินสแตนซ์ GCKCastSession
ได้
วิธีการทั้งหมดใน GCKRemoteMediaClient
ซึ่งส่งคำขอไปยังเว็บรีซีฟเวอร์
จะส่งคืนออบเจ็กต์ 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 รวมถึงความสูงและความกว้างเป็นพิกเซล ระบบจะระบุตัวแปรของรูปแบบ 4K ในพร็อพเพอร์ตี้ hdrType
โดยใช้ค่า enum GCKVideoInfoHDRType
เพิ่มตัวควบคุมขนาดเล็ก
ตามรายการตรวจสอบการออกแบบการแคสต์ แอปของผู้ส่งควรมีการควบคุมถาวรที่เรียกว่าตัวควบคุมขนาดเล็ก ซึ่งควรปรากฏขึ้นเมื่อผู้ใช้ออกจากหน้าเนื้อหาปัจจุบัน ตัวควบคุมขนาดเล็กช่วยให้เข้าถึงได้ทันทีและการช่วยเตือนที่มองเห็นได้สำหรับเซสชันการแคสต์ปัจจุบัน
เฟรมเวิร์กแคสต์จะมีแถบควบคุม 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
ฝังในตัวควบคุมมุมมองที่มีอยู่
วิธีที่ 2 คือการเพิ่มตัวควบคุมขนาดเล็กลงในตัวควบคุมมุมมองที่มีอยู่โดยตรงโดยใช้ createMiniMediaControlsViewController
เพื่อสร้างอินสแตนซ์ GCKUIMiniMediaControlsViewController
แล้วเพิ่มลงในตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย
ตั้งค่าตัวควบคุมการดูในการมอบสิทธิ์แอป
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { ... GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true window?.clipsToBounds = true let rootContainerVC = (window?.rootViewController as? RootContainerViewController) rootContainerVC?.miniMediaControlsViewEnabled = true ... return true }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; self.window.clipsToBounds = YES; RootContainerViewController *rootContainerVC; rootContainerVC = (RootContainerViewController *)self.window.rootViewController; rootContainerVC.miniMediaControlsViewEnabled = YES; ... return YES; }
ในตัวควบคุมมุมมองรูท ให้สร้างอินสแตนซ์ GCKUIMiniMediaControlsViewController
แล้วเพิ่มลงในตัวควบคุมมุมมองคอนเทนเนอร์เป็นมุมมองย่อย
let kCastControlBarsAnimationDuration: TimeInterval = 0.20 @objc(RootContainerViewController) class RootContainerViewController: UIViewController, GCKUIMiniMediaControlsViewControllerDelegate { @IBOutlet weak private var _miniMediaControlsContainerView: UIView! @IBOutlet weak private var _miniMediaControlsHeightConstraint: NSLayoutConstraint! private var miniMediaControlsViewController: GCKUIMiniMediaControlsViewController! var miniMediaControlsViewEnabled = false { didSet { if self.isViewLoaded { self.updateControlBarsVisibility() } } } var overriddenNavigationController: UINavigationController? override var navigationController: UINavigationController? { get { return overriddenNavigationController } set { overriddenNavigationController = newValue } } var miniMediaControlsItemEnabled = false override func viewDidLoad() { super.viewDidLoad() let castContext = GCKCastContext.sharedInstance() self.miniMediaControlsViewController = castContext.createMiniMediaControlsViewController() self.miniMediaControlsViewController.delegate = self self.updateControlBarsVisibility() self.installViewController(self.miniMediaControlsViewController, inContainerView: self._miniMediaControlsContainerView) } func updateControlBarsVisibility() { if self.miniMediaControlsViewEnabled && self.miniMediaControlsViewController.active { self._miniMediaControlsHeightConstraint.constant = self.miniMediaControlsViewController.minHeight self.view.bringSubview(toFront: self._miniMediaControlsContainerView) } else { self._miniMediaControlsHeightConstraint.constant = 0 } UIView.animate(withDuration: kCastControlBarsAnimationDuration, animations: {() -> Void in self.view.layoutIfNeeded() }) self.view.setNeedsLayout() } func installViewController(_ viewController: UIViewController?, inContainerView containerView: UIView) { if let viewController = viewController { self.addChildViewController(viewController) viewController.view.frame = containerView.bounds containerView.addSubview(viewController.view) viewController.didMove(toParentViewController: self) } } func uninstallViewController(_ viewController: UIViewController) { viewController.willMove(toParentViewController: nil) viewController.view.removeFromSuperview() viewController.removeFromParentViewController() } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "NavigationVCEmbedSegue" { self.navigationController = (segue.destination as? UINavigationController) } } ...
RootContainerViewController.h
static const NSTimeInterval kCastControlBarsAnimationDuration = 0.20; @interface RootContainerViewController () <GCKUIMiniMediaControlsViewControllerDelegate> { __weak IBOutlet UIView *_miniMediaControlsContainerView; __weak IBOutlet NSLayoutConstraint *_miniMediaControlsHeightConstraint; GCKUIMiniMediaControlsViewController *_miniMediaControlsViewController; } @property(nonatomic, weak, readwrite) UINavigationController *navigationController; @property(nonatomic, assign, readwrite) BOOL miniMediaControlsViewEnabled; @property(nonatomic, assign, readwrite) BOOL miniMediaControlsItemEnabled; @end
RootContainerViewController.m
@implementation RootContainerViewController - (void)viewDidLoad { [super viewDidLoad]; GCKCastContext *castContext = [GCKCastContext sharedInstance]; _miniMediaControlsViewController = [castContext createMiniMediaControlsViewController]; _miniMediaControlsViewController.delegate = self; [self updateControlBarsVisibility]; [self installViewController:_miniMediaControlsViewController inContainerView:_miniMediaControlsContainerView]; } - (void)setMiniMediaControlsViewEnabled:(BOOL)miniMediaControlsViewEnabled { _miniMediaControlsViewEnabled = miniMediaControlsViewEnabled; if (self.isViewLoaded) { [self updateControlBarsVisibility]; } } - (void)updateControlBarsVisibility { if (self.miniMediaControlsViewEnabled && _miniMediaControlsViewController.active) { _miniMediaControlsHeightConstraint.constant = _miniMediaControlsViewController.minHeight; [self.view bringSubviewToFront:_miniMediaControlsContainerView]; } else { _miniMediaControlsHeightConstraint.constant = 0; } [UIView animateWithDuration:kCastControlBarsAnimationDuration animations:^{ [self.view layoutIfNeeded]; }]; [self.view setNeedsLayout]; } - (void)installViewController:(UIViewController *)viewController inContainerView:(UIView *)containerView { if (viewController) { [self addChildViewController:viewController]; viewController.view.frame = containerView.bounds; [containerView addSubview:viewController.view]; [viewController didMoveToParentViewController:self]; } } - (void)uninstallViewController:(UIViewController *)viewController { [viewController willMoveToParentViewController:nil]; [viewController.view removeFromSuperview]; [viewController removeFromParentViewController]; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"NavigationVCEmbedSegue"]) { self.navigationController = (UINavigationController *)segue.destinationViewController; } } ... @end
GCKUIMiniMediaControlsViewControllerDelegate
จะบอกตัวควบคุมมุมมองโฮสต์เมื่อตัวควบคุมขนาดเล็กควรมองเห็นได้
func miniMediaControlsViewController(_: GCKUIMiniMediaControlsViewController, shouldAppear _: Bool) { updateControlBarsVisibility() }
- (void)miniMediaControlsViewController: (GCKUIMiniMediaControlsViewController *)miniMediaControlsViewController shouldAppear:(BOOL)shouldAppear { [self updateControlBarsVisibility]; }
เพิ่มตัวควบคุมที่ขยาย
รายการตรวจสอบสำหรับการออกแบบของ Google Cast กำหนดให้แอปผู้ส่งมีตัวควบคุมแบบขยายสำหรับสื่อที่กำลังแคสต์ ตัวควบคุมแบบขยายจะเป็นตัวควบคุมขนาดเล็ก เวอร์ชันเต็มหน้าจอ
ตัวควบคุมที่ขยายเป็นมุมมองแบบเต็มหน้าจอที่ควบคุมการเล่นสื่อระยะไกลได้อย่างเต็มที่ มุมมองนี้ควรอนุญาตให้แอปแคสต์จัดการทุกแง่มุมที่จัดการได้ของเซสชันการแคสต์ ยกเว้นการควบคุมระดับเสียงของตัวรับเว็บและวงจรเซสชัน (เชื่อมต่อ/หยุดการแคสต์) และยังให้ข้อมูลสถานะทั้งหมดเกี่ยวกับเซสชันสื่อ (อาร์ตเวิร์ก ชื่อ ชื่อรอง ฯลฯ)
ฟังก์ชันของมุมมองนี้ดำเนินการโดยคลาส GCKUIExpandedMediaControlsViewController
สิ่งแรกที่ต้องทำคือเปิดใช้ตัวควบคุมแบบขยายเริ่มต้นในบริบทการแคสต์ แก้ไขผู้รับมอบสิทธิ์แอปเพื่อเปิดใช้ตัวควบคุมแบบขยายเริ่มต้น
func applicationDidFinishLaunching(_ application: UIApplication) { .. GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true ... }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [GCKCastContext sharedInstance].useDefaultExpandedMediaControls = YES; .. }
เพิ่มโค้ดต่อไปนี้ลงในตัวควบคุมมุมมองเพื่อโหลดตัวควบคุมที่ขยายเมื่อผู้ใช้เริ่มแคสต์วิดีโอ
func playSelectedItemRemotely() { GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls() ... // Load your media sessionManager.currentSession?.remoteMediaClient?.loadMedia(mediaInformation) }
- (void)playSelectedItemRemotely { [[GCKCastContext sharedInstance] presentDefaultExpandedMediaControls]; ... // Load your media [self.sessionManager.currentSession.remoteMediaClient loadMedia:mediaInformation]; }
ตัวควบคุมที่ขยายจะเปิดขึ้นโดยอัตโนมัติเมื่อผู้ใช้แตะตัวควบคุมขนาดเล็ก
เมื่อแอปของผู้ส่งกำลังเล่นสตรีมแบบสดผ่านวิดีโอหรือเสียง SDK จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมที่ขยายแล้ว
ดูใช้รูปแบบที่กำหนดเองกับแอป iOS ของคุณ สำหรับวิธีที่แอปผู้ส่งของคุณสามารถกำหนดค่าลักษณะที่ปรากฏของวิดเจ็ตแคสต์ได้
การควบคุมระดับเสียง
เฟรมเวิร์กของ Cast จะจัดการระดับเสียงของแอปผู้ส่งโดยอัตโนมัติ เฟรมเวิร์กจะซิงค์กับระดับเสียงของตัวรับเว็บสำหรับวิดเจ็ต UI ที่ให้มาโดยอัตโนมัติ หากต้องการซิงค์แถบเลื่อนที่แอปมีให้ ให้ใช้ GCKUIDeviceVolumeController
ตัวควบคุมระดับเสียงของปุ่มจริง
ปุ่มปรับระดับเสียงในอุปกรณ์ผู้ส่งสามารถใช้เปลี่ยนระดับเสียงของเซสชันการแคสต์ในเว็บรีซีฟเวอร์ได้โดยใช้แฟล็ก physicalVolumeButtonsWillControlDeviceVolume
ใน GCKCastOptions
ซึ่งตั้งค่าไว้ใน GCKCastContext
let criteria = GCKDiscoveryCriteria(applicationID: kReceiverAppID) let options = GCKCastOptions(discoveryCriteria: criteria) options.physicalVolumeButtonsWillControlDeviceVolume = true GCKCastContext.setSharedInstanceWith(options)
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:kReceiverAppID]; GCKCastOptions *options = [[GCKCastOptions alloc] initWithDiscoveryCriteria :criteria]; options.physicalVolumeButtonsWillControlDeviceVolume = YES; [GCKCastContext setSharedInstanceWithOptions:options];
จัดการข้อผิดพลาด
แอปของผู้ส่งควรจัดการกับการเรียกกลับที่ผิดพลาดทั้งหมดและตัดสินใจเลือกการตอบสนองที่ดีที่สุดสำหรับแต่ละขั้นในวงจรของการแคสต์ แอปสามารถแสดงกล่องโต้ตอบข้อผิดพลาดต่อผู้ใช้ หรือทำให้สามารถปิดเซสชันการแคสต์ได้
Logging
GCKLogger
เป็นรายการเดียวที่ใช้สำหรับการบันทึกตามเฟรมเวิร์ก ใช้ 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