คู่มือนักพัฒนาแอปนี้อธิบายวิธีเพิ่มการรองรับ Google Cast ลงในแอปตัวส่ง Android โดยใช้ Android Sender SDK
อุปกรณ์เคลื่อนที่หรือแล็ปท็อปคือผู้ส่งที่ควบคุมการเล่น และอุปกรณ์ Google Cast คือผู้รับที่แสดงเนื้อหาบนทีวี
เฟรมเวิร์กของผู้ส่งหมายถึงไบนารีของไลบรารีคลาส Cast และทรัพยากรที่เกี่ยวข้องซึ่งมีอยู่ในรันไทม์บนผู้ส่ง แอปผู้ส่งหรือแอปแคสต์ หมายถึงแอปที่ทำงานบนอุปกรณ์ของผู้ส่งด้วย แอป Web Receiver หมายถึงแอปพลิเคชัน HTML ที่ทำงานบนอุปกรณ์ที่พร้อมใช้งาน Cast
เฟรมเวิร์กผู้ส่งใช้การออกแบบการเรียกกลับแบบอะซิงโครนัสเพื่อแจ้งให้แอปผู้ส่งทราบถึงเหตุการณ์ต่างๆ และเพื่อเปลี่ยนสถานะต่างๆ ของวงจรการใช้งานแอป Cast
โฟลว์ของแอป
ขั้นตอนต่อไปนี้อธิบายขั้นตอนการดำเนินการระดับสูงทั่วไปสำหรับแอป Android ของผู้ส่ง
- เฟรมเวิร์ก Cast จะเริ่ม
MediaRouter
การค้นหาอุปกรณ์โดยอัตโนมัติตามวงจรActivity
- เมื่อผู้ใช้คลิกปุ่มแคสต์ เฟรมเวิร์กจะแสดงกล่องโต้ตอบแคสต์ พร้อมรายการอุปกรณ์แคสต์ที่ค้นพบ
- เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิด แอปตัวรับสัญญาณเว็บในอุปกรณ์แคสต์
- เฟรมเวิร์กจะเรียกใช้การเรียกกลับในแอปผู้ส่งเพื่อยืนยันว่าได้เปิดแอป Web Receiver แล้ว
- เฟรมเวิร์กจะสร้างช่องทางการสื่อสารระหว่างแอปผู้ส่งและแอป Web Receiver
- เฟรมเวิร์กใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมการเล่นสื่อใน Web Receiver
- เฟรมเวิร์กจะซิงค์สถานะการเล่นสื่อระหว่างผู้ส่งและ Web Receiver เมื่อผู้ใช้ดำเนินการใน UI ของผู้ส่ง เฟรมเวิร์กจะส่งคำขอควบคุมสื่อเหล่านั้นไปยัง Web Receiver และเมื่อ Web Receiver ส่งข้อมูลอัปเดตสถานะสื่อ เฟรมเวิร์กจะอัปเดตสถานะของ UI ของผู้ส่ง
- เมื่อผู้ใช้คลิกปุ่มแคสต์เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ เฟรมเวิร์กจะยกเลิกการเชื่อมต่อแอปผู้ส่งจาก Web Receiver
ดูรายการคลาส เมธอด และเหตุการณ์ทั้งหมดใน Google Cast Android SDK ได้ที่เอกสารอ้างอิง Google Cast Sender API สำหรับ Android ส่วนต่อไปนี้จะอธิบายขั้นตอนในการเพิ่ม Cast ลงในแอป Android
กำหนดค่าไฟล์ Manifest ของ Android
ไฟล์ AndroidManifest.xml ของแอปกำหนดให้คุณต้องกำหนดค่าองค์ประกอบต่อไปนี้สำหรับ Cast SDK
uses-sdk
ตั้งค่าระดับ API ของ Android ขั้นต่ำและเป้าหมายที่ Cast SDK รองรับ ปัจจุบัน API ระดับต่ำสุดคือ 23 และเป้าหมายคือ API ระดับ 34
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
ตั้งค่าธีมของแอปตามเวอร์ชัน Android SDK ขั้นต่ำ ตัวอย่างเช่น หากคุณไม่ได้ใช้ธีมของคุณเอง คุณควรใช้ตัวแปรของ
Theme.AppCompat
เมื่อกำหนดเป้าหมาย Android SDK เวอร์ชันขั้นต่ำที่
เป็นเวอร์ชันก่อน Lollipop
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
เริ่มต้น CastContext
เฟรมเวิร์กมีออบเจ็กต์ Singleton ส่วนกลางคือ CastContext
ซึ่งประสานงาน
การโต้ตอบทั้งหมดของเฟรมเวิร์ก
แอปของคุณต้องใช้
OptionsProvider
อินเทอร์เฟซเพื่อระบุตัวเลือกที่จำเป็นในการเริ่มต้น
CastContext
ออบเจ็กต์เดี่ยว OptionsProvider
มีอินสแตนซ์ของ
CastOptions
ซึ่งมีตัวเลือกที่ส่งผลต่อลักษณะการทำงานของเฟรมเวิร์ก สิ่งที่สำคัญที่สุดคือรหัสแอปพลิเคชัน Web Receiver ซึ่งใช้เพื่อกรองผลการค้นหาและเปิดแอป Web Receiver เมื่อเริ่มเซสชันการแคสต์
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context): CastOptions { return Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
คุณต้องประกาศชื่อที่มีคุณสมบัติครบถ้วนของ OptionsProvider
ที่ใช้งานเป็นฟิลด์ข้อมูลเมตาในไฟล์ AndroidManifest.xml ของแอปผู้ส่ง
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.foo.CastOptionsProvider" />
</application>
CastContext
จะได้รับการเริ่มต้นอย่างช้าๆ เมื่อมีการเรียกใช้ CastContext.getSharedInstance()
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
public class MyActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { CastContext castContext = CastContext.getSharedInstance(this); } }
วิดเจ็ต UX ของ Cast
เฟรมเวิร์ก Cast มีวิดเจ็ตที่เป็นไปตามรายการตรวจสอบการออกแบบ Cast ดังนี้
ภาพซ้อนทับแนะนำ: เฟรมเวิร์กมี View ที่กำหนดเอง
IntroductoryOverlay
ซึ่งแสดงต่อผู้ใช้เพื่อดึงดูดความสนใจไปยังปุ่มแคสต์ ในครั้งแรกที่ตัวรับพร้อมใช้งาน แอปผู้ส่งสามารถปรับแต่งข้อความและตำแหน่งของข้อความชื่อได้ปุ่มแคสต์: ปุ่มแคสต์จะปรากฏขึ้นไม่ว่าอุปกรณ์แคสต์จะพร้อมใช้งานหรือไม่ก็ตาม เมื่อผู้ใช้คลิกปุ่มแคสต์เป็นครั้งแรก กล่องโต้ตอบแคสต์จะแสดงขึ้น ซึ่งแสดงรายการอุปกรณ์ที่ค้นพบ เมื่อผู้ใช้คลิกปุ่มแคสต์ ขณะที่อุปกรณ์เชื่อมต่ออยู่ ระบบจะแสดงข้อมูลเมตาของสื่อปัจจุบัน (เช่น ชื่อ ชื่อสตูดิโอบันทึกเสียง และภาพปก) หรืออนุญาตให้ผู้ใช้ ยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ บางครั้ง "ปุ่มแคสต์" จะเรียกว่า "ไอคอนแคสต์"
มินิคอนโทรลเลอร์: เมื่อผู้ใช้แคสต์เนื้อหาและออกจากหน้าเนื้อหาปัจจุบัน หรือขยายคอนโทรลเลอร์ไปยังหน้าจออื่นในแอปผู้ส่งแล้ว มินิคอนโทรลเลอร์จะแสดงที่ด้านล่างของหน้าจอเพื่อให้ผู้ใช้ ดูข้อมูลเมตาของสื่อที่กำลังแคสต์และควบคุมการเล่นได้
ตัวควบคุมแบบขยาย เมื่อผู้ใช้แคสต์เนื้อหา หากคลิกการแจ้งเตือนสื่อ หรือตัวควบคุมขนาดเล็ก ตัวควบคุมแบบขยายจะเปิดขึ้น ซึ่งจะแสดง ข้อมูลเมตาของสื่อที่กำลังเล่นอยู่และมีปุ่มหลายปุ่มสำหรับควบคุม การเล่นสื่อ
การแจ้งเตือน: Android เท่านั้น เมื่อผู้ใช้แคสต์เนื้อหาและออกจากแอปผู้ส่ง ระบบจะแสดงการแจ้งเตือนสื่อซึ่งแสดงข้อมูลเมตาของสื่อที่กำลังแคสต์และตัวควบคุมการเล่น
หน้าจอล็อก Android เท่านั้น เมื่อผู้ใช้แคสต์เนื้อหาและไปยังหน้าจอล็อก (หรืออุปกรณ์หมดเวลา) ระบบจะแสดงตัวควบคุมหน้าจอล็อกของสื่อซึ่งแสดงข้อมูลเมตาของสื่อที่กำลังแคสต์และตัวควบคุมการเล่น
คำแนะนำต่อไปนี้มีคำอธิบายเกี่ยวกับวิธีเพิ่มวิดเจ็ตเหล่านี้ลงใน แอป
เพิ่มปุ่มแคสต์
API ของ Android
MediaRouter
ออกแบบมาเพื่อเปิดใช้การแสดงและเล่นสื่อในอุปกรณ์รอง
แอป Android ที่ใช้ MediaRouter
API ควรมีปุ่มแคสต์เป็นส่วนหนึ่ง
ของอินเทอร์เฟซผู้ใช้ เพื่อให้ผู้ใช้เลือกเส้นทางสื่อเพื่อเล่นสื่อใน
อุปกรณ์รอง เช่น อุปกรณ์ Cast ได้
เฟรมเวิร์กช่วยให้การเพิ่ม
MediaRouteButton
เป็น
Cast button
เป็นเรื่องง่าย ก่อนอื่นคุณควรเพิ่มรายการในเมนูหรือMediaRouteButton
ในไฟล์ XML
ที่กำหนดเมนู แล้วใช้
CastButtonFactory
เพื่อเชื่อมต่อกับเฟรมเวิร์ก
// To add a Cast button, add the following snippet.
// menu.xml
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.kt override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.main, menu) CastButtonFactory.setUpMediaRouteButton( applicationContext, menu, R.id.media_route_menu_item ) return true }
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.java @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.main, menu); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item); return true; }
จากนั้นหาก Activity
inherits from
FragmentActivity
คุณจะเพิ่ม
MediaRouteButton
ลงในเลย์เอาต์ได้
// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >
<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/media_route_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:mediaRouteTypes="user"
android:visibility="gone" />
</LinearLayout>
// MyActivity.kt override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_layout) mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton) mCastContext = CastContext.getSharedInstance(this) }
// MyActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layout); mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton); mCastContext = CastContext.getSharedInstance(this); }
หากต้องการตั้งค่าลักษณะของปุ่มแคสต์โดยใช้ธีม โปรดดูปรับแต่งปุ่มแคสต์
กำหนดค่าการค้นหาอุปกรณ์
CastContext
เป็นผู้จัดการการค้นพบอุปกรณ์ทั้งหมด
เมื่อเริ่มต้น CastContext แอปผู้ส่งจะระบุรหัสแอปพลิเคชัน Web Receiver
และขอการกรองเนมสเปซได้โดยไม่บังคับโดยการตั้งค่า
supportedNamespaces
ใน
CastOptions
CastContext
มีการอ้างอิงถึง MediaRouter
ภายใน และจะเริ่มกระบวนการค้นหาภายใต้เงื่อนไขต่อไปนี้
- การค้นหาจะเริ่มโดยอัตโนมัติเป็นครั้งคราวเมื่อแอปผู้ส่งเข้าสู่เบื้องหน้า โดยอิงตามอัลกอริทึมที่ออกแบบมาเพื่อปรับสมดุลเวลาในการค้นหาอุปกรณ์และ การใช้แบตเตอรี่
- กล่องโต้ตอบการแคสต์เปิดอยู่
- Cast SDK กำลังพยายามกู้คืนเซสชัน Cast
กระบวนการค้นหาจะหยุดลงเมื่อปิดกล่องโต้ตอบ Cast หรือ แอปที่ส่งเข้าสู่เบื้องหลัง
class CastOptionsProvider : OptionsProvider { companion object { const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace" } override fun getCastOptions(appContext: Context): CastOptions { val supportedNamespaces: MutableList<String> = ArrayList() supportedNamespaces.add(CUSTOM_NAMESPACE) return CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
class CastOptionsProvider implements OptionsProvider { public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"; @Override public CastOptions getCastOptions(Context appContext) { List<String> supportedNamespaces = new ArrayList<>(); supportedNamespaces.add(CUSTOM_NAMESPACE); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
วิธีการทำงานของการจัดการเซสชัน
Cast SDK นำเสนอแนวคิดของเซสชัน Cast ซึ่งการสร้างเซสชันนี้รวมขั้นตอนการเชื่อมต่อกับอุปกรณ์ การเปิด (หรือเข้าร่วม) แอป Web Receiver การเชื่อมต่อกับแอปนั้น และการเริ่มต้นช่องควบคุมสื่อ ดูข้อมูลเพิ่มเติมเกี่ยวกับเซสชัน Cast และวงจรของ Web Receiver ได้ที่คู่มือวงจรของแอปพลิเคชัน Web Receiver
เซสชันได้รับการจัดการโดยชั้นเรียน
SessionManager
ซึ่งแอปของคุณเข้าถึงได้ผ่าน
CastContext.getSessionManager()
เซสชันแต่ละรายการจะแสดงด้วยคลาสย่อยของคลาส
Session
เช่น
CastSession
แสดงเซสชันที่มีอุปกรณ์แคสต์ แอปของคุณสามารถเข้าถึงเซสชัน Cast ที่ใช้งานอยู่ในปัจจุบันผ่าน SessionManager.getCurrentCastSession()
ได้
แอปของคุณสามารถใช้คลาส
SessionManagerListener
เพื่อตรวจสอบเหตุการณ์เซสชัน เช่น การสร้าง การระงับ การกลับมาทำงานต่อ และการสิ้นสุด เฟรมเวิร์กจะพยายามดำเนินการต่อโดยอัตโนมัติจากการสิ้นสุดที่ผิดปกติ/กะทันหันขณะที่เซสชันทำงานอยู่
ระบบจะสร้างและทำลายเซสชันโดยอัตโนมัติเพื่อตอบสนองต่อท่าทางของผู้ใช้
จากกล่องโต้ตอบ MediaRouter
หากต้องการทำความเข้าใจข้อผิดพลาดในการเริ่มต้น Cast ได้ดียิ่งขึ้น แอปสามารถใช้
CastContext#getCastReasonCodeForCastStatusCode(int)
เพื่อแปลงข้อผิดพลาดในการเริ่มต้นเซสชันเป็น
CastReasonCodes
โปรดทราบว่าข้อผิดพลาดในการเริ่มต้นเซสชันบางอย่าง (เช่น CastReasonCodes#CAST_CANCELLED
)
เป็นลักษณะการทำงานที่ตั้งใจไว้และไม่ควรบันทึกเป็นข้อผิดพลาด
หากต้องการทราบการเปลี่ยนแปลงสถานะของเซสชัน คุณสามารถใช้SessionManagerListener
ได้ ตัวอย่างนี้จะตรวจสอบความพร้อมใช้งานของ
CastSession
ใน Activity
class MyActivity : Activity() { private var mCastSession: CastSession? = null private lateinit var mCastContext: CastContext private lateinit var mSessionManager: SessionManager private val mSessionManagerListener: SessionManagerListener<CastSession> = SessionManagerListenerImpl() private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> { override fun onSessionStarting(session: CastSession?) {} override fun onSessionStarted(session: CastSession?, sessionId: String) { invalidateOptionsMenu() } override fun onSessionStartFailed(session: CastSession?, error: Int) { val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error) // Handle error } override fun onSessionSuspended(session: CastSession?, reason Int) {} override fun onSessionResuming(session: CastSession?, sessionId: String) {} override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) { invalidateOptionsMenu() } override fun onSessionResumeFailed(session: CastSession?, error: Int) {} override fun onSessionEnding(session: CastSession?) {} override fun onSessionEnded(session: CastSession?, error: Int) { finish() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mCastContext = CastContext.getSharedInstance(this) mSessionManager = mCastContext.sessionManager mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession } override fun onDestroy() { super.onDestroy() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) } }
public class MyActivity extends Activity { private CastContext mCastContext; private CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionStartFailed(CastSession session, int error) { int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error); // Handle error } @Override public void onSessionSuspended(CastSession session, int reason) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionResumeFailed(CastSession session, int error) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCastContext = CastContext.getSharedInstance(this); mSessionManager = mCastContext.getSessionManager(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); } @Override protected void onDestroy() { super.onDestroy(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); } }
การถ่ายโอนสตรีม
การรักษาสถานะเซสชันเป็นพื้นฐานของการโอนสตรีม ซึ่งผู้ใช้สามารถย้ายสตรีมเสียงและวิดีโอที่มีอยู่ไปยังอุปกรณ์ต่างๆ ได้โดยใช้คำสั่งเสียง, แอป Google Home หรือจออัจฉริยะ สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อในอีกอุปกรณ์หนึ่ง (ปลายทาง) อุปกรณ์ Cast ที่มีเฟิร์มแวร์ล่าสุดสามารถใช้เป็นแหล่งที่มาหรือปลายทางในการ โอนสตรีมได้
หากต้องการรับอุปกรณ์ปลายทางใหม่ในระหว่างการโอนหรือขยายสตรีม ให้ลงทะเบียน
Cast.Listener
โดยใช้
CastSession#addCastListener
จากนั้นโทร
CastSession#getCastDevice()
ระหว่างonDeviceNameChanged
การโทรกลับ
ดูข้อมูลเพิ่มเติมได้ที่ การสตรีมการโอนใน Web Receiver
การเชื่อมต่อใหม่โดยอัตโนมัติ
เฟรมเวิร์กมี
ReconnectionService
ซึ่งแอปผู้ส่งเปิดใช้เพื่อจัดการการเชื่อมต่อใหม่ในกรณีที่ซับซ้อน
ต่างๆ ได้ เช่น
- กู้คืนจากการสูญเสียสัญญาณ Wi-Fi ชั่วคราว
- กู้คืนจากการพักหน้าจอของอุปกรณ์
- กู้คืนจากการทำงานของแอปในเบื้องหลัง
- กู้คืนหากแอปขัดข้อง
ระบบจะเปิดบริการนี้ไว้โดยค่าเริ่มต้น และคุณปิดบริการนี้ได้ใน
CastOptions.Builder
ระบบจะผสานบริการนี้เข้ากับไฟล์ Manifest ของแอปโดยอัตโนมัติหากเปิดใช้การผสานอัตโนมัติ ในไฟล์ Gradle
เฟรมเวิร์กจะเริ่มบริการเมื่อมีเซสชันสื่อ และหยุดบริการเมื่อเซสชันสื่อสิ้นสุดลง
วิธีการทำงานของส่วนควบคุมสื่อ
เฟรมเวิร์ก Cast เลิกใช้งานคลาส
RemoteMediaPlayer
จาก Cast 2.x เพื่อเปลี่ยนไปใช้คลาสใหม่
RemoteMediaClient
ซึ่งมีฟังก์ชันการทำงานเดียวกันในชุด API ที่สะดวกกว่า และ
ไม่ต้องส่ง GoogleApiClient
เมื่อแอปสร้าง
CastSession
กับแอปตัวรับสัญญาณเว็บที่รองรับเนมสเปซสื่อ เฟรมเวิร์กจะสร้างอินสแตนซ์ของ
RemoteMediaClient
โดยอัตโนมัติ และแอปจะเข้าถึงอินสแตนซ์ดังกล่าวได้โดยเรียกใช้เมธอด getRemoteMediaClient()
ในอินสแตนซ์ CastSession
เมธอดทั้งหมดของ RemoteMediaClient
ที่ออกคำขอไปยัง Web Receiver จะ
แสดงผลออบเจ็กต์ PendingResult ที่ใช้ติดตามคำขอนั้นได้
คาดว่าอินสแตนซ์ของ RemoteMediaClient
อาจแชร์โดย
หลายส่วนของแอป และแน่นอนว่าคอมโพเนนต์ภายในบางอย่างของเฟรมเวิร์ก
เช่น มินิคอนโทรลเลอร์แบบถาวรและบริการแจ้งเตือน
ด้วยเหตุนี้ อินสแตนซ์นี้จึงรองรับการลงทะเบียนอินสแตนซ์หลายรายการของ RemoteMediaClient.Listener
ตั้งค่าข้อมูลเมตาของสื่อ
คลาส
MediaMetadata
แสดงข้อมูลเกี่ยวกับรายการสื่อที่คุณต้องการแคสต์ ตัวอย่างต่อไปนี้สร้างอินสแตนซ์ MediaMetadata ใหม่ของภาพยนตร์และตั้งค่า
ชื่อ คำบรรยาย และรูปภาพ 2 รูป
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()) movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0)))) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()); movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0)))); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));
ดูการเลือกรูปภาพ เกี่ยวกับการใช้รูปภาพที่มีข้อมูลเมตาของสื่อ
โหลดสื่อ
แอปของคุณสามารถโหลดรายการสื่อได้ดังที่แสดงในโค้ดต่อไปนี้ ก่อนอื่นให้ใช้
MediaInfo.Builder
กับข้อมูลเมตาของสื่อเพื่อสร้างอินสแตนซ์
MediaInfo
รับ
RemoteMediaClient
จากCastSession
ปัจจุบัน แล้วโหลดMediaInfo
ลงในRemoteMediaClient
นั้น ใช้ RemoteMediaClient
เพื่อเล่น หยุดชั่วคราว และควบคุม
แอปมีเดียเพลเยอร์ที่ทำงานบน Web Receiver
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build() val remoteMediaClient = mCastSession.getRemoteMediaClient() remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build(); RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());
ดูส่วนการใช้แทร็กสื่อด้วย
รูปแบบวิดีโอ 4K
หากต้องการตรวจสอบรูปแบบวิดีโอของสื่อ ให้ใช้
getVideoInfo()
ใน MediaStatus เพื่อรับอินสแตนซ์ปัจจุบันของ
VideoInfo
อินสแตนซ์นี้มีประเภทรูปแบบทีวี HDR รวมถึงความสูง
และความกว้างของจอแสดงผลเป็นพิกเซล ตัวแปรของรูปแบบ 4K จะระบุด้วยค่าคงที่
HDR_TYPE_*
การแจ้งเตือนของรีโมตคอนโทรลไปยังอุปกรณ์หลายเครื่อง
เมื่อผู้ใช้แคสต์ อุปกรณ์ Android อื่นๆ ในเครือข่ายเดียวกันจะได้รับการแจ้งเตือนเพื่อให้ควบคุมการเล่นได้ด้วย ทุกคนที่อุปกรณ์ได้รับการแจ้งเตือนดังกล่าวจะปิดการแจ้งเตือนสำหรับอุปกรณ์นั้นได้ในแอปการตั้งค่าที่ Google > Google Cast > แสดงการแจ้งเตือนรีโมตคอนโทรล (การแจ้งเตือนจะมีทางลัดไปยังแอปการตั้งค่า) ดูรายละเอียดเพิ่มเติมได้ที่ การแจ้งเตือนบนรีโมตคอนโทรลของ Cast
เพิ่มมินิคอนโทรลเลอร์
ตามรายการตรวจสอบการออกแบบ Cast แอปผู้ส่งควรมีตัวควบคุมแบบถาวรที่เรียกว่ามินิคอนโทรลเลอร์ ซึ่งควรปรากฏขึ้นเมื่อผู้ใช้นำทางออกจากหน้าเนื้อหาปัจจุบันไปยัง ส่วนอื่นของแอปผู้ส่ง มินิคอนโทรลเลอร์จะช่วยเตือนผู้ใช้เกี่ยวกับเซสชัน Cast ปัจจุบัน ให้ผู้ใช้เห็น การแตะตัวควบคุมขนาดเล็กจะช่วยให้ ผู้ใช้กลับไปที่มุมมองตัวควบคุมแบบเต็มหน้าจอที่ขยายแล้วของ Cast ได้
เฟรมเวิร์กมี View ที่กำหนดเอง MiniControllerFragment ซึ่งคุณสามารถเพิ่ม ที่ด้านล่างของไฟล์เลย์เอาต์ของแต่ละกิจกรรมที่คุณต้องการแสดง มินิคอนโทรลเลอร์
<fragment
android:id="@+id/castMiniController"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
เมื่อแอปตัวส่งกำลังเล่นไลฟ์สดวิดีโอหรือเสียง SDK จะแสดงปุ่มเล่น/หยุดแทนปุ่มเล่น/หยุดชั่วคราวในมินิคอนโทรลเลอร์โดยอัตโนมัติ
หากต้องการตั้งค่าลักษณะข้อความของชื่อและคำบรรยายแทนเสียงของมุมมองที่กำหนดเองนี้ และเลือกปุ่ม ให้ดูปรับแต่งมินิคอนโทรลเลอร์
เพิ่มตัวควบคุมที่ขยาย
รายการตรวจสอบการออกแบบ Google Cast กำหนดให้แอปตัวส่งต้องมีตัวควบคุมแบบขยาย สำหรับสื่อที่แคสต์ โดยตัวควบคุมแบบขยายจะเป็นเวอร์ชันเต็มหน้าจอของ ตัวควบคุมขนาดเล็ก
Cast SDK มีวิดเจ็ตสำหรับตัวควบคุมแบบขยายที่เรียกว่า
ExpandedControllerActivity
นี่คือคลาสแบบนามธรรมที่คุณต้องสร้างคลาสย่อยเพื่อเพิ่มปุ่มแคสต์
ก่อนอื่น ให้สร้างไฟล์ทรัพยากรเมนูใหม่สำหรับตัวควบคุมแบบขยายเพื่อแสดง ปุ่มแคสต์
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
สร้างคลาสใหม่ที่ขยาย ExpandedControllerActivity
class ExpandedControlsActivity : ExpandedControllerActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.expanded_controller, menu) CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item) return true } }
public class ExpandedControlsActivity extends ExpandedControllerActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.expanded_controller, menu); CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); return true; } }
ตอนนี้ให้ประกาศกิจกรรมใหม่ในไฟล์ Manifest ของแอปภายในแท็ก application
ดังนี้
<application>
...
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
...
</application>
แก้ไข CastOptionsProvider
และเปลี่ยน NotificationOptions
และ
CastMediaOptions
เพื่อตั้งค่ากิจกรรมเป้าหมายเป็นกิจกรรมใหม่
override fun getCastOptions(context: Context): CastOptions? { val notificationOptions = NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity::class.java.name) .build() val mediaOptions = CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name) .build() return CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build() }
public CastOptions getCastOptions(Context context) { NotificationOptions notificationOptions = new NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) .build(); CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) .build(); return new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build(); }
อัปเดตเมธอด LocalPlayerActivity
loadRemoteMedia
เพื่อแสดงกิจกรรมใหม่เมื่อโหลดสื่อจากระยะไกล
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) { val remoteMediaClient = mCastSession?.remoteMediaClient ?: return remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() { override fun onStatusUpdated() { val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java) startActivity(intent) remoteMediaClient.unregisterCallback(this) } }) remoteMediaClient.load( MediaLoadRequestData.Builder() .setMediaInfo(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position.toLong()).build() ) }
private void loadRemoteMedia(int position, boolean autoPlay) { if (mCastSession == null) { return; } final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); if (remoteMediaClient == null) { return; } remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() { @Override public void onStatusUpdated() { Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class); startActivity(intent); remoteMediaClient.unregisterCallback(this); } }); remoteMediaClient.load(new MediaLoadRequestData.Builder() .setMediaInfo(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position).build()); }
เมื่อแอปตัวส่งกำลังเล่นไลฟ์สดวิดีโอหรือเสียง SDK จะแสดงปุ่มเล่น/หยุดแทนปุ่มเล่น/หยุดชั่วคราว ในตัวควบคุมแบบขยายโดยอัตโนมัติ
หากต้องการตั้งค่าลักษณะที่ปรากฏโดยใช้ธีม ให้เลือกปุ่มที่จะแสดง และเพิ่มปุ่มที่กำหนดเอง โปรดดูปรับแต่งตัวควบคุมแบบขยาย
การควบคุมระดับเสียง
เฟรมเวิร์กจะจัดการระดับเสียงสำหรับแอปผู้ส่งโดยอัตโนมัติ เฟรมเวิร์กจะซิงโครไนซ์แอปผู้ส่งและ Web Receiver โดยอัตโนมัติเพื่อให้ UI ของผู้ส่งรายงานระดับเสียงที่ Web Receiver ระบุเสมอ
การควบคุมระดับเสียงด้วยปุ่มบนตัวเครื่อง
ใน Android คุณสามารถใช้ปุ่มจริงบนอุปกรณ์ของผู้ส่งเพื่อเปลี่ยน ระดับเสียงของเซสชันการแคสต์ใน Web Receiver ได้โดยค่าเริ่มต้นสำหรับอุปกรณ์ที่ใช้ Jelly Bean หรือใหม่กว่า
การควบคุมระดับเสียงด้วยปุ่มจริงก่อน Jelly Bean
หากต้องการใช้ปุ่มปรับระดับเสียงจริงเพื่อควบคุมระดับเสียงของอุปกรณ์ Web Receiver ในอุปกรณ์ Android ที่เก่ากว่า Jelly Bean แอปผู้ส่งควรลบล้าง
dispatchKeyEvent
ในกิจกรรมของตน และเรียกใช้
CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
class MyActivity extends FragmentActivity { @Override public boolean dispatchKeyEvent(KeyEvent event) { return CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event); } }
เพิ่มตัวควบคุมสื่อลงในการแจ้งเตือนและหน้าจอล็อก
เฉพาะใน Android เท่านั้น รายการตรวจสอบการออกแบบ Google Cast กำหนดให้แอปผู้ส่งต้อง
ใช้ตัวควบคุมสื่อใน
การแจ้งเตือน
และในหน้าจอ
ล็อก
เมื่อผู้ส่งกำลังแคสต์ แต่แอปผู้ส่งไม่ได้โฟกัส
เฟรมเวิร์กมี
MediaNotificationService
และ
MediaIntentReceiver
เพื่อช่วยให้แอปผู้ส่งสร้างการควบคุมสื่อในการแจ้งเตือนและในหน้าจอล็อก
MediaNotificationService
จะทำงานเมื่อผู้ส่งกำลังแคสต์ และจะแสดง
การแจ้งเตือนพร้อมภาพขนาดย่อและข้อมูลเกี่ยวกับการแคสต์
รายการปัจจุบัน รวมถึงปุ่มเล่น/หยุดชั่วคราวและปุ่มหยุด
MediaIntentReceiver
คือ BroadcastReceiver
ที่จัดการการดำเนินการของผู้ใช้จาก
การแจ้งเตือน
แอปของคุณสามารถกำหนดค่าการแจ้งเตือนและการควบคุมสื่อจากหน้าจอล็อกผ่าน
NotificationOptions
แอปของคุณสามารถกำหนดค่าปุ่มควบคุมที่จะแสดงในการแจ้งเตือน และกำหนดค่าActivity
ที่จะเปิดเมื่อผู้ใช้แตะการแจ้งเตือน หากไม่ได้ระบุการดำเนินการอย่างชัดเจน ระบบจะใช้ค่าเริ่มต้น
MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK
และ
MediaIntentReceiver.ACTION_STOP_CASTING
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". val buttonActions: MutableList<String> = ArrayList() buttonActions.add(MediaIntentReceiver.ACTION_REWIND) buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK) buttonActions.add(MediaIntentReceiver.ACTION_FORWARD) buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING) // Showing "play/pause" and "stop casting" in the compat view of the notification. val compatButtonActionsIndices = intArrayOf(1, 3) // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. val notificationOptions = NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity::class.java.name) .build()
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". List<String> buttonActions = new ArrayList<>(); buttonActions.add(MediaIntentReceiver.ACTION_REWIND); buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK); buttonActions.add(MediaIntentReceiver.ACTION_FORWARD); buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING); // Showing "play/pause" and "stop casting" in the compat view of the notification. int[] compatButtonActionsIndices = new int[]{1, 3}; // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. NotificationOptions notificationOptions = new NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity.class.getName()) .build();
ระบบจะเปิดการแสดงตัวควบคุมสื่อจากการแจ้งเตือนและหน้าจอล็อกโดยค่าเริ่มต้น และปิดใช้ได้โดยการเรียกใช้
setNotificationOptions
ด้วยค่า Null ใน
CastMediaOptions.Builder
ปัจจุบันฟีเจอร์หน้าจอล็อกจะเปิดอยู่ตราบใดที่การแจ้งเตือน
เปิดอยู่
// ... continue with the NotificationOptions built above val mediaOptions = CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build() val castOptions: CastOptions = Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build()
// ... continue with the NotificationOptions built above CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build(); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build();
เมื่อแอปตัวส่งกำลังเล่นไลฟ์สดวิดีโอหรือเสียง SDK จะแสดงปุ่มเล่น/หยุดแทนปุ่มเล่น/หยุดชั่วคราว ในการควบคุมการแจ้งเตือนโดยอัตโนมัติ แต่จะไม่แสดงในการควบคุมบนหน้าจอล็อก
หมายเหตุ: หากต้องการแสดงตัวควบคุมหน้าจอล็อกในอุปกรณ์ที่ใช้ Android เวอร์ชันก่อน Lollipop
RemoteMediaClient
จะขอโฟกัสเสียงในนามของคุณโดยอัตโนมัติ
จัดการข้อผิดพลาด
แอปผู้ส่งต้องจัดการการเรียกกลับข้อผิดพลาดทั้งหมดและตัดสินใจ การตอบกลับที่ดีที่สุดสำหรับแต่ละขั้นตอนของวงจรการใช้งาน Cast แอปสามารถแสดงกล่องโต้ตอบข้อผิดพลาดต่อผู้ใช้ หรือจะเลือกยกเลิกการเชื่อมต่อกับ Web Receiver ก็ได้