คู่มือนักพัฒนาซอฟต์แวร์นี้อธิบายวิธีเพิ่มการสนับสนุน Google Cast ลงใน แอปผู้ส่ง Android โดยใช้ Android Sender SDK
อุปกรณ์เคลื่อนที่หรือแล็ปท็อปคือผู้ส่งที่ควบคุมการเล่นและอุปกรณ์ Google Cast เป็นตัวรับซึ่งแสดงเนื้อหาบนทีวี
กรอบการทํางานของผู้ส่งจะอ้างถึงไบนารีไลบรารีคลาส Cast และทรัพยากรที่เกี่ยวข้องซึ่งมีอยู่ที่รันไทม์ของผู้ส่ง แอปผู้ส่งหรือแอปแคสต์ หมายถึงแอปที่ทํางานอยู่บนผู้ส่งด้วย แอปตัวรับสัญญาณเว็บ หมายถึงแอปพลิเคชัน HTML ที่ทํางานอยู่บนอุปกรณ์ที่พร้อมใช้งาน Cast
กรอบการทํางานของผู้ส่งใช้การออกแบบการเรียกกลับแบบไม่พร้อมกันเพื่อแจ้งให้แอปของผู้ส่งทราบถึงเหตุการณ์ และเพื่อสลับไปมาระหว่างสถานะต่างๆ ของวงจรชีวิตของแอป Cast
กระแสของแอป
ขั้นตอนต่อไปนี้จะอธิบายถึงขั้นตอนทั่วไปในระดับสูงสําหรับผู้ส่งแอป Android
- เฟรมเวิร์ก Cast จะเริ่มการค้นหาอุปกรณ์
MediaRouter
โดยอัตโนมัติโดยอิงตามวงจรActivity
- เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เฟรมเวิร์กจะแสดงกล่องโต้ตอบ "แคสต์" พร้อมรายการอุปกรณ์แคสต์ที่ค้นพบ
- เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ กรอบการทํางานดังกล่าวจะพยายามเปิดแอป Web Receiver บนอุปกรณ์ Cast
- เฟรมเวิร์กจะเรียกใช้โค้ดเรียกกลับในแอปผู้ส่งเพื่อยืนยันว่าได้เปิดตัวแอป Web Receiver แล้ว
- เฟรมเวิร์กนี้จะสร้างช่องทางการสื่อสารระหว่างแอปของผู้ส่งและผู้รับเว็บ
- เฟรมเวิร์กนี้ใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมการเล่นสื่อบน Web Receiver
- เฟรมเวิร์กจะซิงค์สถานะการเล่นสื่อระหว่างผู้ส่งและ Web Receiver กล่าวคือเมื่อผู้ใช้ดําเนินการ UI ของผู้ส่ง เฟรมเวิร์กจะส่งคําขอควบคุมสื่อเหล่านั้นไปยัง Web Receiver และเมื่อ Web Receiver ส่งการอัปเดตสถานะสื่อ เฟรมเวิร์กจะอัปเดตสถานะของ UI ของผู้ส่ง
- เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์ Cast เฟรมเวิร์กดังกล่าวจะยกเลิกการเชื่อมต่อแอปผู้ส่งจากตัวรับสัญญาณเว็บ
สําหรับรายการที่ครอบคลุมทุกเมธอด เมธอด และเหตุการณ์ใน Google Cast Android SDK โปรดดูข้อมูลอ้างอิง API ของผู้ส่งของ Google Cast สําหรับ Android ส่วนต่อไปนี้จะอธิบายขั้นตอนในการเพิ่มแคสต์ไปยังแอป Android
กําหนดค่าไฟล์ Manifest ของ Android
ไฟล์ AndroidManifest.xml ของแอปกําหนดให้คุณกําหนดค่าองค์ประกอบต่อไปนี้สําหรับ Cast SDK
uses-sdk
ตั้งค่าระดับ API ขั้นต่ําของ Android และเป้าหมายที่ Cast SDK สนับสนุน ปัจจุบันค่าขั้นต่ําคือ API ระดับ 19 และเป้าหมายคือ API ระดับ 28
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="28" />
ธีม Android
ตั้งค่าธีมของแอปตามเวอร์ชัน SDK ต่ําสุดของ Android เช่น หากไม่ได้ใช้ธีมของตัวเอง คุณควรใช้ Theme.AppCompat
รูปแบบต่างๆ เมื่อกําหนดเป้าหมายเป็น SDK เวอร์ชัน Android ขั้นต่ําเวอร์ชันก่อน Lollipop
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
เริ่มต้นบริบทของ Cast
เฟรมเวิร์กนี้มีออบเจ็กต์ Single CastContext
ที่ใช้กันทั่วโลกซึ่งทําหน้าที่ประสานงานการโต้ตอบทั้งหมดของเฟรมเวิร์ก
แอปต้องใช้อินเทอร์เฟซ OptionsProvider
เพื่อจัดหาตัวเลือกที่จําเป็นในการเริ่มต้น CastContext
เดี่ยว OptionsProvider
มอบอินสแตนซ์ของ CastOptions
ซึ่งมีตัวเลือกต่างๆ ที่ส่งผลต่อลักษณะการทํางานของเฟรมเวิร์ก สิ่งสําคัญที่สุดคือรหัสแอปพลิเคชันตัวรับสัญญาณเว็บซึ่งใช้เพื่อกรองผลการค้นหาการค้นพบและเปิดแอป "ตัวรับสัญญาณเว็บ" เมื่อเริ่มเซสชันการแคสต์
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 มีวิดเจ็ตที่สอดคล้องกับรายการตรวจสอบการออกแบบ Cast ดังต่อไปนี้
การวางซ้อนที่แนะนํา: เฟรมเวิร์กจะแสดงมุมมองที่กําหนดเอง
IntroductoryOverlay
ที่แสดงต่อผู้ใช้เพื่อเรียกปุ่ม "แคสต์" ในครั้งแรกที่ผู้รับพร้อมใช้งาน แอปผู้ส่งสามารถปรับแต่งข้อความและตําแหน่งของข้อความชื่อปุ่ม "แคสต์": ปุ่ม "แคสต์" จะปรากฏเมื่อระบบพบตัวรับสัญญาณที่รองรับแอปของคุณ เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เป็นครั้งแรก กล่องโต้ตอบ "แคสต์" จะแสดงขึ้นซึ่งจะแสดงอุปกรณ์ที่พบ เมื่อผู้ใช้คลิกที่ปุ่ม "แคสต์" ในขณะที่อุปกรณ์เชื่อมต่ออยู่ จะแสดงข้อมูลเมตาของสื่อปัจจุบัน (เช่น ชื่อ ชื่อสตูดิโอบันทึกเสียง และภาพขนาดย่อ) หรืออนุญาตให้ผู้ใช้ยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ได้
ตัวควบคุมขนาดเล็ก: เมื่อผู้ใช้แคสต์เนื้อหาและนําทางออกไปจากหน้าเนื้อหาปัจจุบันหรือตัวควบคุมที่ขยายแล้วไปยังหน้าจออื่นในแอปผู้ส่ง ตัวควบคุมขนาดเล็กจะแสดงที่ด้านล่างของหน้าจอเพื่อให้ผู้ใช้เห็นข้อมูลเมตาของสื่อที่กําลังแคสต์อยู่และเพื่อควบคุมการเล่น
ตัวควบคุมที่ขยายแล้ว: เมื่อผู้ใช้กําลังแคสต์เนื้อหา หากผู้ใช้คลิกที่การแจ้งเตือนสื่อ หรือตัวควบคุมขนาดเล็ก ตัวควบคุมที่ขยายจะทํางาน ซึ่งจะแสดงข้อมูลเมตาของสื่อที่เล่นอยู่ในขณะนั้นและมีปุ่มมากมายเพื่อควบคุมการเล่นสื่อ
การแจ้งเตือน: 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
ไว้ภายในและจะเริ่มกระบวนการค้นหาเมื่อแอปของผู้ส่งเข้าสู่ส่วนหน้า และหยุดเมื่อแอปผู้ส่งเข้าสู่พื้นหลัง
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 } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onPause() { super.onPause() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) mCastSession = null } }
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(); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onPause() { super.onPause(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); mCastSession = null; } }
การโอนสตรีม
การเก็บสถานะเซสชันไว้เป็นพื้นฐานในการโอนสตรีม ซึ่งผู้ใช้สามารถย้ายสตรีมเสียงและวิดีโอที่มีอยู่ในอุปกรณ์ต่างๆ โดยใช้คําสั่งเสียง, แอป Google Home หรือ Smart Display สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อไปในอุปกรณ์อื่น (ปลายทาง) อุปกรณ์แคสต์ที่มีเฟิร์มแวร์ล่าสุดจะใช้เป็นต้นทางหรือปลายทางในการโอนสตรีมได้
หากต้องการรับอุปกรณ์ปลายทางใหม่ในระหว่างการโอนหรือการขยายสตรีม
ให้ลงทะเบียน Cast.Listener
โดยใช้ CastSession#addCastListener
จากนั้นโทร CastSession#getCastDevice()
ระหว่างโทรกลับ onDeviceNameChanged
ดูการโอนสตรีมในเครื่องรับเว็บสําหรับข้อมูลเพิ่มเติม
เชื่อมต่อใหม่อัตโนมัติ
เฟรมเวิร์กมี ReconnectionService
ซึ่งแอปผู้ส่งสามารถเปิดใช้เพื่อจัดการการเชื่อมต่อซ้ําได้ในหลายกรณี เช่น
- กู้คืนจากการสูญเสีย Wi-Fi ชั่วคราว
- กู้คืนจากโหมดสลีปของอุปกรณ์
- กู้คืนจากการเล่นอยู่เบื้องหลังของแอป
- กู้คืนหากแอปขัดข้อง
ซึ่งบริการนี้จะเปิดใช้โดยค่าเริ่มต้น และสามารถปิดได้ใน CastOptions.Builder
บริการนี้จะรวมเข้ากับไฟล์ Manifest ของแอปได้โดยอัตโนมัติหากมีการเปิดใช้การผสานอัตโนมัติในไฟล์ Gradle
เฟรมเวิร์กนี้จะเริ่มให้บริการเมื่อมีเซสชันสื่อ และหยุดการทํางานเมื่อเซสชันสื่อสิ้นสุดลง
วิธีการทํางานของการควบคุมสื่อ
เฟรมเวิร์ก Cast จะเลิกใช้งานคลาส RemoteMediaPlayer
จาก Cast 2.x เพื่อใช้คลาสใหม่แทน RemoteMediaClient
ซึ่งมีฟังก์ชันเดียวกันในชุด API ที่สะดวกยิ่งขึ้น และหลีกเลี่ยงการส่งผ่านใน GoogleApiClient
เมื่อแอปสร้าง
CastSession
ด้วยแอปตัวรับสัญญาณเว็บที่รองรับเนมสเปซของสื่อแล้ว ระบบจะสร้างอินสแตนซ์ของ
RemoteMediaClient
โดยอัตโนมัติโดยเฟรมเวิร์ก ซึ่งแอปจะเข้าถึงได้โดยเรียกใช้เมธอด getRemoteMediaClient()
บนอินสแตนซ์ CastSession
เมธอดทั้งหมดของ RemoteMediaClient
ที่ออกคําขอไปยังตัวรับสัญญาณเว็บจะส่งคืนออบเจ็กต์ PendingResult ที่ใช้ติดตามคําขอนั้นได้
เราคาดว่าอาจมีการแชร์อินสแตนซ์ของ RemoteMediaClient
กับส่วนต่างๆ ของแอป และคอมโพเนนต์ภายในของเฟรมเวิร์กบางอย่าง เช่น ตัวควบคุมขนาดเล็กแบบถาวรและบริการการแจ้งเตือน
ในกรณีนี้ อินสแตนซ์นี้รองรับการลงทะเบียนอินสแตนซ์ RemoteMediaClient.Listener
หลายรายการ
ตั้งค่าข้อมูลเมตาของสื่อ
คลาส MediaMetadata
แสดงข้อมูลเกี่ยวกับรายการสื่อที่คุณต้องการแคสต์ ตัวอย่างต่อไปนี้จะสร้างอินสแตนซ์ MediaMetadata ใหม่ของภาพยนตร์และตั้งชื่อ คําบรรยาย และรูปภาพสองรูป
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 รวมถึงความสูงและความกว้างของการแสดงผลเป็นพิกเซล รูปแบบของรูปแบบ 4K จะระบุด้วยค่าคงที่ HDR_TYPE_*
การแจ้งเตือนบนรีโมตคอนโทรลกับอุปกรณ์หลายเครื่อง
เมื่อผู้ใช้แคสต์ อุปกรณ์ Android เครื่องอื่นๆ ในเครือข่ายเดียวกันจะได้รับการแจ้งเตือนให้ควบคุมการเล่นได้ด้วย ทุกคนที่ได้รับการแจ้งเตือนดังกล่าวจะปิดการแจ้งเตือนสําหรับอุปกรณ์นั้นในแอปการตั้งค่าที่ Google > Google Cast > แสดงการแจ้งเตือนบนรีโมตคอนโทรล (การแจ้งเตือนมีทางลัดไปยังแอปการตั้งค่า) ดูรายละเอียดเพิ่มเติมได้ที่การแจ้งเตือนบนรีโมตคอนโทรลของ Cast
เพิ่มตัวควบคุมขนาดเล็ก
ตามรายการตรวจสอบการออกแบบ 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 จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมขนาดเล็ก
ในการตั้งค่าลักษณะของข้อความและชื่อเรื่องรองของมุมมองที่กําหนดเองนี้ และเลือกปุ่มได้ที่ ปรับแต่งตัวควบคุมขนาดเล็ก
เพิ่มตัวควบคุมที่ขยาย
รายการตรวจสอบการออกแบบ 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 จะแสดงปุ่มเล่น/หยุดแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมที่ขยายโดยอัตโนมัติ
หากต้องการตั้งค่าลักษณะที่ปรากฏโดยใช้ธีม ให้เลือกปุ่มที่จะแสดงและเพิ่มปุ่มที่กําหนดเองที่หัวข้อปรับแต่งตัวควบคุมที่ขยาย
การควบคุมระดับเสียง
เฟรมเวิร์กจะจัดการปริมาณสําหรับแอปของผู้ส่งโดยอัตโนมัติ เฟรมเวิร์กจะซิงค์แอปของผู้ส่งและผู้รับเว็บโดยอัตโนมัติ เพื่อให้ UI ของผู้ส่งรายงานปริมาณที่ระบุโดยผู้รับเว็บเสมอ
ปุ่มควบคุมระดับเสียง
บน Android คุณสามารถใช้ปุ่มบนตัวเครื่องบนอุปกรณ์ส่งเพื่อเปลี่ยนระดับเสียงของเซสชันการแคสต์ในอุปกรณ์รับสัญญาณเว็บโดยค่าเริ่มต้นสําหรับอุปกรณ์ใดๆ ที่ใช้ Jelly Bean หรือใหม่กว่า
การควบคุมระดับเสียงของปุ่มจริงก่อน Jelly Bean
หากต้องการใช้แป้นปรับระดับเสียงเพื่อควบคุมระดับเสียงของอุปกรณ์รับสัญญาณเว็บในอุปกรณ์ 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 Design เท่านั้น รายการตรวจสอบการออกแบบของ 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
ที่มีค่าเป็นนัลใน 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 จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติที่ปุ่มเล่น/หยุดชั่วคราวบนตัวควบคุมการแจ้งเตือน แต่ไม่แสดงตัวควบคุมหน้าจอล็อก
หมายเหตุ: RemoteMediaClient
จะขอโฟกัสเสียงในนามของคุณโดยอัตโนมัติเพื่อแสดงการควบคุมหน้าจอล็อกบนอุปกรณ์ Pre-Lollipop
จัดการข้อผิดพลาด
เป็นสิ่งสําคัญมากที่แอปผู้ส่งจะต้องจัดการกับการตอบกลับการเรียกกลับที่ผิดพลาดทั้งหมดและทําการตัดสินใจได้ดีที่สุดสําหรับแต่ละขั้นตอนของวงจรการส่ง แอปสามารถแสดงกล่องโต้ตอบข้อผิดพลาดต่อผู้ใช้ หรืออาจเลือกสร้างการเชื่อมต่อกับการเชื่อมต่อเว็บรีซีฟเวอร์