คู่มือนักพัฒนาซอฟต์แวร์นี้จะอธิบายวิธีเพิ่มการสนับสนุน Google Cast ลงใน Android ผู้ส่งที่ใช้ Android Sender SDK
อุปกรณ์เคลื่อนที่หรือแล็ปท็อปคือผู้ส่งซึ่งควบคุมการเล่น และ อุปกรณ์ Google Cast คือตัวรับที่จะแสดงเนื้อหาบน TV
เฟรมเวิร์กผู้ส่งหมายถึงไบนารีไลบรารีคลาสของ Cast และที่เกี่ยวข้อง ที่มีอยู่ขณะรันไทม์ผู้ส่ง แอปผู้ส่งหรือแอปแคสต์ หมายถึงแอปที่ทำงานกับผู้ส่งด้วย แอป Web Receiver หมายถึงแอปพลิเคชัน HTML ที่ทำงานบนอุปกรณ์ที่พร้อมใช้งาน Cast
เฟรมเวิร์กผู้ส่งใช้การออกแบบ Callback แบบไม่พร้อมกันเพื่อแจ้งผู้ส่ง แอปเหตุการณ์และการเปลี่ยนไปมาระหว่างสถานะต่างๆ ในชีวิตของแอป Cast
ขั้นตอนของแอป
ขั้นตอนต่อไปนี้จะอธิบายกระบวนการดำเนินการระดับสูงโดยทั่วไปสำหรับผู้ส่ง แอป Android
- เฟรมเวิร์กการแคสต์จะเริ่มต้นโดยอัตโนมัติ
MediaRouter
การค้นพบอุปกรณ์ตามวงจรชีวิตActivity
- เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เฟรมเวิร์กจะแสดง "แคสต์" กล่องโต้ตอบที่มีรายการอุปกรณ์แคสต์ที่พบ
- เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิดตัว แอป Web Receiver บนอุปกรณ์แคสต์
- เฟรมเวิร์กนี้จะเรียกใช้ Callback ในแอปผู้ส่งเพื่อยืนยันว่า แอปตัวรับสัญญาณเปิดตัวแล้ว
- เฟรมเวิร์กจะสร้างช่องทางการสื่อสารระหว่างผู้ส่งและเว็บ แอปตัวรับสัญญาณ
- เฟรมเวิร์กนี้ใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมสื่อ การเล่นบนเว็บรีซีฟเวอร์
- เฟรมเวิร์กจะซิงค์สถานะการเล่นสื่อระหว่างผู้ส่งกับ ตัวรับเว็บ: เมื่อผู้ใช้ดำเนินการกับ UI ของผู้ส่ง เฟรมเวิร์กจะส่ง คำขอการควบคุมสื่อเหล่านั้นไปยังเว็บรีซีฟเวอร์ และเมื่อเว็บรีซีฟเวอร์ ส่งการอัปเดตสถานะสื่อ เฟรมเวิร์กจะอัปเดตสถานะของ UI ของผู้ส่ง
- เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์ Cast เฟรมเวิร์กจะยกเลิกการเชื่อมต่อแอปผู้ส่งจาก Web Receiver
สำหรับรายการชั้นเรียน วิธีการ และเหตุการณ์ทั้งหมดใน Google Cast Android SDK โปรดดูข้อมูลอ้างอิง API ผู้ส่งของ Google Cast สำหรับ Android ส่วนต่อไปนี้จะอธิบายถึงขั้นตอนในการเพิ่มแคสต์ไปยังแอป Android
กำหนดค่าไฟล์ Manifest ของ Android
ไฟล์ AndroidManifest.xml ของแอปกำหนดให้คุณต้องกำหนดค่าต่อไปนี้ สำหรับ Cast SDK
uses-sdk
ตั้งค่าระดับ API ขั้นต่ำและเป้าหมายที่ Cast SDK รองรับ ค่าต่ำสุดตอนนี้คือ API ระดับ 23 และเป้าหมายคือ API ระดับ 34
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
ตั้งค่าธีมของแอปตามเวอร์ชัน Android SDK ขั้นต่ำ ตัวอย่างเช่น หาก
คุณไม่ได้ใช้ธีมของคุณเอง คุณควรใช้ธีมของ
Theme.AppCompat
เมื่อกำหนดเป้าหมาย Android SDK เวอร์ชันขั้นต่ำที่
ก่อนอมยิ้ม
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
เริ่มต้นบริบทการแคสต์
เฟรมเวิร์กมีออบเจ็กต์ซิงเกิลตันทั่วโลก ซึ่งก็คือ CastContext
ที่พิกัดนั้น
การโต้ตอบทั้งหมดของเฟรมเวิร์ก
แอปของคุณต้องใช้
OptionsProvider
อินเทอร์เฟซสำหรับให้ตัวเลือกที่จำเป็นในการเริ่มต้น
CastContext
Singleton 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
เริ่มต้นแบบ Lazy Loading เมื่อ 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); } }
วิดเจ็ต Cast UX
เฟรมเวิร์ก Cast มีวิดเจ็ตที่สอดคล้องกับการออกแบบแคสต์ รายการตรวจสอบมีดังนี้
การวางซ้อนบทนำ: เฟรมเวิร์กนี้จะมอบการแสดงผลที่กำหนดเอง
IntroductoryOverlay
ที่แสดงให้ผู้ใช้เห็นเพื่อเรียกความสนใจไปยังปุ่ม "แคสต์" ครั้งแรกที่รีซีฟเวอร์พร้อมให้บริการ แอป Sender สามารถ ปรับแต่งข้อความและตำแหน่งของชื่อ ข้อความปุ่ม "แคสต์": ปุ่ม "แคสต์" จะปรากฏขึ้นไม่ว่าอุปกรณ์แคสต์จะพร้อมใช้งานหรือไม่ก็ตาม เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เป็นครั้งแรก กล่องโต้ตอบ "แคสต์" จะปรากฏขึ้น ซึ่งแสดงรายการอุปกรณ์ที่พบ เมื่อผู้ใช้คลิกปุ่ม "แคสต์" ขณะที่อุปกรณ์เชื่อมต่ออยู่ จะแสดงข้อมูลเมตาของสื่อในปัจจุบัน (เช่น ชื่อสตูดิโอบันทึกเสียง และภาพขนาดย่อ) หรืออนุญาตให้ผู้ใช้ เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์ Cast "ปุ่ม "แคสต์" บางครั้งมีการอ้างถึง เป็น "ไอคอนแคสต์"
มินิคอนโทรลเลอร์: เมื่อผู้ใช้กำลังแคสต์เนื้อหาและได้ออกจากสตรีม หน้าเนื้อหาหรือตัวควบคุมที่ขยายไปยังหน้าจออื่นในแอปผู้ส่ง ตัวควบคุมขนาดเล็กแสดงที่ด้านล่างของหน้าจอเพื่อให้ผู้ใช้ ดูข้อมูลเมตาของสื่อที่กำลังแคสต์อยู่และเพื่อควบคุมการเล่น
ตัวควบคุมที่ขยาย: เมื่อผู้ใช้กำลังแคสต์เนื้อหา หากผู้ใช้คลิกการแจ้งเตือนสื่อ หรือมินิตัวควบคุม ตัวควบคุมที่ขยายแล้วจะแสดง กำลังเล่นข้อมูลเมตาของสื่อและมีปุ่มหลายปุ่มเพื่อควบคุม การเล่นสื่อ
การแจ้งเตือน: Android เท่านั้น เมื่อผู้ใช้กำลังแคสต์เนื้อหาและออกไปจาก แอปผู้ส่งจะมีการแจ้งเตือนสื่อแสดงขึ้น ซึ่งจะแสดงข้อความที่กำลังแคสต์อยู่ ข้อมูลเมตาของสื่อและการควบคุมการเล่น
หน้าจอล็อก: Android เท่านั้น เมื่อผู้ใช้แคสต์เนื้อหาและไปยังส่วนต่างๆ (หรืออุปกรณ์ หมดเวลา) ไปยังหน้าจอล็อก ตัวควบคุมหน้าจอล็อกของสื่อจะแสดงขึ้น จะแสดงข้อมูลเมตาของสื่อที่แคสต์และการควบคุมการเล่นอยู่ในปัจจุบัน
คำแนะนำต่อไปนี้จะอธิบายวิธีการเพิ่มวิดเจ็ตเหล่านี้ลงใน แอปของคุณ
เพิ่มปุ่ม "แคสต์"
Android
MediaRouter
API ออกแบบมาเพื่อเปิดใช้การแสดงและเล่นสื่อในอุปกรณ์รอง
แอป Android ที่ใช้ MediaRouter
API ควรมีปุ่ม "แคสต์" เป็นส่วนหนึ่งของแอป
ของอินเทอร์เฟซผู้ใช้ เพื่ออนุญาตให้ผู้ใช้เลือกเส้นทางสื่อเพื่อเล่นสื่อ
อุปกรณ์รอง เช่น อุปกรณ์แคสต์
เฟรมเวิร์กนี้จะช่วยเพิ่ม
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
รับค่าจาก
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 กำลังพยายามกู้คืนเซสชันการแคสต์
ขั้นตอนการค้นหาจะหยุดเมื่อปิดกล่องโต้ตอบการแคสต์หรือ เข้าสู่พื้นหลัง
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 นำเสนอแนวคิดของเซสชันการแคสต์ ซึ่งประกอบด้วยขั้นตอนการเชื่อมต่อกับอุปกรณ์ การเปิด (หรือการเข้าร่วม) เว็บ แอปตัวรับสัญญาณ การเชื่อมต่อกับแอปดังกล่าว และการเริ่มต้นใช้งานช่องทางควบคุมสื่อ ดูเว็บรีซีฟเวอร์ คำแนะนำเกี่ยวกับวงจรของแอปพลิเคชัน สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเซสชันการแคสต์และวงจรชีวิตของตัวรับเว็บ
ชั้นเรียนเป็นผู้จัดการเซสชัน
SessionManager
ที่แอปของคุณเข้าถึงได้ผ่าน
CastContext.getSessionManager()
แต่ละเซสชันจะแสดงโดยคลาสย่อยของชั้นเรียน
Session
ตัวอย่างเช่น
CastSession
แสดงเซสชันที่มีอุปกรณ์แคสต์ แอปของคุณสามารถเข้าถึง
เซสชันการแคสต์ผ่าน
SessionManager.getCurrentCastSession()
แอปของคุณสามารถใช้
SessionManagerListener
เพื่อตรวจสอบเหตุการณ์ของเซสชัน เช่น การสร้าง การระงับ การกลับมาทำงานอีกครั้ง และ
การสิ้นสุดข้อตกลง เฟรมเวิร์กจะพยายามทำงานต่อโดยอัตโนมัติจาก
การสิ้นสุดที่ผิดปกติ/ฉับพลันขณะที่เซสชันทำงานอยู่
ระบบจะสร้างและแยกเซสชันออกโดยอัตโนมัติตามท่าทางสัมผัสของผู้ใช้
จากกล่องโต้ตอบ MediaRouter
เพื่อให้เข้าใจข้อผิดพลาดในการเริ่มต้นแคสต์ได้ดียิ่งขึ้น แอปสามารถใช้
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.Listener
โดยใช้
CastSession#addCastListener
จากนั้นโทร
CastSession#getCastDevice()
ในระหว่าง Callback onDeviceNameChanged
โปรดดู การโอนสตรีมใน Web Receiver เพื่อดูข้อมูลเพิ่มเติม
การเชื่อมต่ออีกครั้งอัตโนมัติ
เฟรมเวิร์กนี้จะนำเสนอ
ReconnectionService
ซึ่งสามารถเปิดใช้โดยแอปผู้ส่งเพื่อจัดการการเชื่อมต่อใหม่ในหลายที่ละเอียด
กรณีมุมต่างๆ เช่น
- กู้คืนจากสัญญาณ Wi-Fi ที่หายไปชั่วคราว
- กู้คืนจากโหมดสลีปของอุปกรณ์
- กู้คืนจากการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
- กู้คืนหากแอปขัดข้อง
ระบบจะเปิดบริการนี้ไว้โดยค่าเริ่มต้น และสามารถปิดได้ใน
CastOptions.Builder
บริการนี้สามารถผสานเข้ากับไฟล์ Manifest ของแอปโดยอัตโนมัติหากรวมโดยอัตโนมัติ ในไฟล์ Gradle
เฟรมเวิร์กจะเริ่มบริการเมื่อมีเซสชันสื่อและหยุดบริการ เมื่อเซสชันสื่อสิ้นสุดลง
วิธีการทำงานของการควบคุมสื่อ
เฟรมเวิร์กของ Cast เลิกใช้งาน
RemoteMediaPlayer
จาก Cast 2.x เพื่อสร้างคลาสใหม่
RemoteMediaClient
ซึ่งมีฟังก์ชันการทำงานเดียวกันในชุด API ที่สะดวกยิ่งขึ้น และ
หลีกเลี่ยงการส่งผ่าน GoogleApiClient
เมื่อแอปของคุณสร้าง
CastSession
ด้วยแอป Web Receiver ที่รองรับเนมสเปซสื่อ ตัวอย่าง
เฟรมเวิร์กจะสร้าง RemoteMediaClient
โดยอัตโนมัติ แอปของคุณสามารถ
เข้าถึงได้โดยเรียกใช้เมธอด getRemoteMediaClient()
ใน CastSession
อินสแตนซ์
วิธีการทั้งหมดของ RemoteMediaClient
ที่ออกคำขอไปยังเว็บรีซีฟเวอร์จะ
แสดงออบเจ็กต์ 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
เพื่อเล่น หยุดชั่วคราว และอื่นๆ
ควบคุมแอปมีเดียเพลเยอร์ที่ทำงานบนตัวรับเว็บ
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 TV และความสูงของการแสดงผล
และความกว้างเป็นพิกเซล ตัวแปรของรูปแบบ 4K จะระบุด้วยค่าคงที่
HDR_TYPE_*
การแจ้งเตือนด้วยรีโมตคอนโทรลไปยังอุปกรณ์หลายเครื่อง
เมื่อผู้ใช้แคสต์ อุปกรณ์ Android อื่นๆ ในเครือข่ายเดียวกันจะได้รับ เพื่อให้ควบคุมการเล่นได้ ทุกคนที่มีอุปกรณ์ คุณสามารถปิดการแจ้งเตือนดังกล่าวสำหรับอุปกรณ์นั้นได้ในการตั้งค่า แอปที่ Google > Google Cast > แสดงการแจ้งเตือนบนรีโมตคอนโทรล (การแจ้งเตือนจะมีทางลัดไปยังแอปการตั้งค่า) ดูรายละเอียดเพิ่มเติมได้ที่ การแจ้งเตือนบนรีโมตคอนโทรลของ Cast
เพิ่มตัวควบคุมขนาดเล็ก
ตามการออกแบบการแคสต์ รายการตรวจสอบ แอปผู้ส่งควรมีการควบคุมแบบถาวรที่เรียกว่า มินิ ผู้ควบคุมข้อมูล ควรปรากฏเมื่อผู้ใช้ออกจากหน้าเนื้อหาปัจจุบันเพื่อ ส่วนอื่นๆ ในแอปผู้ส่ง ตัวควบคุมขนาดเล็กจะแสดงการช่วยเตือนที่มองเห็นได้ ไปยังผู้ใช้เซสชันการแคสต์ปัจจุบัน การแตะบนตัวควบคุมขนาดเล็ก ผู้ใช้สามารถกลับไปที่มุมมองตัวควบคุมที่ขยายแบบเต็มหน้าจอของแคสต์ได้
กรอบการทำงานจะมีมุมมองที่กำหนดเอง หรือ 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 แสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราว ในตัวควบคุมขนาดเล็ก
หากต้องการตั้งค่าลักษณะข้อความของชื่อและคำบรรยายของมุมมองที่กำหนดเองนี้ และหากต้องการเลือกปุ่ม โปรดดู ปรับแต่ง Mini Controller
เพิ่มตัวควบคุมที่ขยาย
รายการตรวจสอบสำหรับการออกแบบของ 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 คุณสามารถใช้ปุ่มบนตัวเครื่องบนอุปกรณ์ผู้ส่งเพื่อเปลี่ยนการตั้งค่า ระดับเสียงของเซสชันการแคสต์บนตัวรับเว็บโดยค่าเริ่มต้นสำหรับอุปกรณ์ใดก็ตามที่ใช้ 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); } }
เพิ่มตัวควบคุมสื่อลงในการแจ้งเตือนและหน้าจอล็อก
รายการตรวจสอบการออกแบบ Google Cast กำหนดให้แอปผู้ส่งต้องใช้สำหรับ Android เท่านั้น
นำการควบคุมสื่อมาใช้ใน
การแจ้งเตือน
และในล็อก
หน้าจอ
ที่ผู้ส่งกำลังแคสต์ แต่แอปผู้ส่งไม่มีโฟกัส
ของเฟรมเวิร์ก
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 แสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราว บนตัวควบคุมการแจ้งเตือน แต่ไม่ใช่ตัวควบคุมหน้าจอล็อก
หมายเหตุ: วิธีแสดงการควบคุมหน้าจอล็อกในอุปกรณ์ Pre-Lollipop
RemoteMediaClient
จะขอการโฟกัสเสียงในนามของคุณโดยอัตโนมัติ
จัดการข้อผิดพลาด
แอปของผู้ส่งต้องจัดการกับการเรียกกลับที่ผิดพลาดทั้งหมดและตัดสินใจ การตอบสนองที่ดีที่สุดสำหรับแต่ละขั้นตอน ในวงจรของการแคสต์ แอปสามารถแสดง ต่อผู้ใช้ หรืออาจตัดสินใจยกเลิกการเชื่อมต่อกับ เว็บรีซีฟเวอร์