คู่มือนักพัฒนาซอฟต์แวร์นี้อธิบายวิธีเพิ่มการรองรับ Google Cast ในแอปผู้ส่ง Android โดยใช้ Android Sender SDK
อุปกรณ์เคลื่อนที่หรือแล็ปท็อปคือผู้ส่งที่ควบคุมการเล่น และอุปกรณ์ Google Cast คือตัวรับที่จะแสดงเนื้อหาบนทีวี
เฟรมเวิร์กผู้ส่งหมายถึงไบนารีของไลบรารีคลาสแคสต์และแหล่งข้อมูลที่เกี่ยวข้องขณะรันไทม์กับผู้ส่ง แอปของผู้ส่งหรือแอปแคสต์ หมายถึงแอปที่ทํางานเกี่ยวกับผู้ส่งด้วย แอปตัวรับเว็บหมายถึงแอปพลิเคชัน HTML ที่ทํางานในอุปกรณ์ที่พร้อมใช้งาน Cast
เฟรมเวิร์กผู้ส่งใช้การออกแบบการเรียกกลับแบบไม่พร้อมกันเพื่อแจ้งแอปของผู้ส่งเหตุการณ์และเพื่อสลับระหว่างสถานะต่างๆ ของวงจรแอปแคสต์
โฟลว์แอป
ขั้นตอนต่อไปนี้จะอธิบายขั้นตอนการดําเนินการระดับสูงทั่วไปของผู้ส่งแอป Android
- เฟรมเวิร์กการแคสต์จะเริ่ม
MediaRouter
การค้นพบอุปกรณ์โดยอัตโนมัติตามวงจรของActivity
- เมื่อผู้ใช้คลิกปุ่มแคสต์ เฟรมเวิร์กจะแสดงกล่องโต้ตอบแคสต์ที่มีรายการอุปกรณ์แคสต์ที่ตรวจพบ
- เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิดแอป Web Recipient ในอุปกรณ์ Cast
- เฟรมเวิร์กจะเรียกใช้โค้ดเรียกกลับในแอปของผู้ส่งเพื่อยืนยันว่าได้เปิดตัวแอป Web Receiver
- เฟรมเวิร์กจะสร้างช่องทางการสื่อสารระหว่างแอปของผู้ส่งและผู้รับเว็บ
- เฟรมเวิร์กนี้ใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมการเล่นสื่อบน Web Receiver
- เฟรมเวิร์กจะซิงค์ข้อมูลสถานะการเล่นสื่อระหว่างผู้ส่งกับเว็บผู้รับ กล่าวคือเมื่อผู้ใช้ดําเนินการ UI ของผู้ส่ง เฟรมเวิร์กจะส่งคําขอการควบคุมสื่อเหล่านั้นไปยังเว็บผู้รับ และเมื่อผู้รับเว็บส่งการอัปเดตสถานะสื่อ เฟรมเวิร์กจะอัปเดตสถานะของ UI ของผู้ส่ง
- เมื่อผู้ใช้คลิกปุ่มแคสต์เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์แคสต์ เฟรมเวิร์กจะยกเลิกการเชื่อมต่อแอปผู้ส่งกับตัวรับสัญญาณบนเว็บ
ดูรายการคลาส เมธอด และกิจกรรมทั้งหมดใน Google Cast Android SDK ได้ที่ข้อมูลอ้างอิง Google Cast Sender API สําหรับ Android ส่วนต่อไปนี้จะอธิบายขั้นตอนในการเพิ่ม Cast ในแอป Android
กําหนดค่าไฟล์ Manifest ของ Android
ไฟล์ AndroidManifest.xml ของแอปกําหนดให้คุณต้องกําหนดค่าองค์ประกอบต่อไปนี้สําหรับ Cast SDK
use-sdk
ตั้งค่าระดับ API ขั้นต่ําของ Android และเป้าหมายที่ Cast SDK รองรับ ปัจจุบันขั้นต่ําคือ API ระดับ 19 และเป้าหมายคือ API ระดับ 28
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="28" />
android:ธีม
ตั้งค่าธีมของแอปตาม Android SDK เวอร์ชันขั้นต่ํา เช่น หากไม่ติดตั้งใช้งานธีมของตัวเอง คุณควรใช้ตัวแปร Theme.AppCompat
เมื่อกําหนดเป้าหมายเป็น Android SDK เวอร์ชันขั้นต่ําที่เป็น Pre-Lollipop
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
เริ่มต้นบริบทของแคสต์
เฟรมเวิร์กนี้มีออบเจ็กต์ Singleton ระดับโลก ซึ่งก็คือ CastContext
สําหรับจับคู่การโต้ตอบทั้งหมดของเฟรมเวิร์ก
แอปต้องใช้อินเทอร์เฟซ OptionsProvider
เพื่อจัดหาตัวเลือกที่จําเป็นในการเริ่มต้น CastContext
Singleton OptionsProvider
มีอินสแตนซ์ของ CastOptions
ที่มีตัวเลือกที่ส่งผลต่อลักษณะการทํางานของเฟรมเวิร์ก สิ่งสําคัญที่สุดคือรหัสแอปพลิเคชัน Web Receiver ที่ใช้ในการกรองผลการค้นหา Discovery และเปิดแอป 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
เฟรมเวิร์กแคสต์มีวิดเจ็ตที่สอดคล้องกับรายการตรวจสอบการออกแบบการแคสต์
การวางซ้อนเริ่มต้น: เฟรมเวิร์กจะแสดงมุมมองที่กําหนดเอง
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 แนะนําแนวคิดของเซสชันการแคสต์ ซึ่งเป็นการรวมขั้นตอนการเชื่อมต่อเข้ากับอุปกรณ์ การเปิด (หรือเข้าร่วม) แอป Web Receiver การเชื่อมต่อกับแอปดังกล่าว และเริ่มต้นช่องทางการควบคุมสื่อ ดูข้อมูลเพิ่มเติมเกี่ยวกับเซสชันการแคสต์และอายุการใช้งานของตัวรับสัญญาณบนเว็บได้ในตัวรับข้อมูลเว็บ
เซสชันจะจัดการโดยชั้นเรียน
SessionManager
ซึ่งแอปของคุณจะเข้าถึงได้ผ่าน
CastContext.getSessionManager()
เซสชันแต่ละรายการจะแสดงโดยชั้นเรียนย่อยของชั้นเรียน
Session
ตัวอย่างเช่น
CastSession
เป็นตัวแทนเซสชันที่มีอุปกรณ์แคสต์ แอปของคุณเข้าถึงเซสชันการแคสต์ที่กําลังดําเนินอยู่ผ่าน SessionManager.getCurrentCastSession()
แอปใช้คลาส SessionManagerListener
เพื่อตรวจสอบเหตุการณ์ของเซสชันได้ เช่น การสร้าง การระงับ การรีสตาร์ท และการสิ้นสุด เฟรมเวิร์กจะพยายามดําเนินการต่อโดยอัตโนมัติจาก
การสิ้นสุดที่ผิดปกติ/กะทันหันขณะเซสชัน
ระบบจะสร้างเซสชันและถูกแบ่งย่อยโดยอัตโนมัติเพื่อตอบสนองต่อท่าทางสัมผัสของผู้ใช้จากกล่องโต้ตอบ MediaRouter
หากต้องการทราบการเปลี่ยนแปลงสถานะสําหรับเซสชัน คุณสามารถใช้ SessionManagerListener
ได้ ตัวอย่างนี้ฟังความพร้อมใช้งานของ CastSession
ใน Activity
class MyActivity : Activity() { private var mCastSession: CastSession? = null private lateinit var mSessionManager: SessionManager private val mSessionManagerListener: SessionManagerListener<CastSession> = SessionManagerListenerImpl() private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> { override fun onSessionStarted(session: CastSession?, sessionId: String) { invalidateOptionsMenu() } override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) { invalidateOptionsMenu() } override fun onSessionEnded(session: CastSession?, error: Int) { finish() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mSessionManager = CastContext.getSharedInstance(this).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 CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSessionManager = CastContext.getSharedInstance(this).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 หรือจออัจฉริยะ สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อในอุปกรณ์อื่น (ปลายทาง) อุปกรณ์แคสต์ที่มีเฟิร์มแวร์ล่าสุดจะใช้เป็นต้นทางหรือปลายทางในการโอนสตรีมได้
หากต้องการอุปกรณ์ปลายทางใหม่ในระหว่างการโอนหรือการขยายสตรีม
ให้ลงทะเบียน
Cast.Listener
โดยใช้
CastSession#addCastListener
จากนั้นโทร
CastSession#getCastDevice()
ระหว่างโทรกลับ onDeviceNameChanged
ดูข้อมูลเพิ่มเติมที่หัวข้อการโอนสตรีมบนตัวรับสัญญาณเว็บ
การเชื่อมต่อใหม่โดยอัตโนมัติ
เฟรมเวิร์กนี้จะมี ReconnectionService
ซึ่งแอปผู้ส่งจะเปิดใช้เพื่อจัดการการเชื่อมต่ออีกครั้งได้ในหลายกรณี เช่น มุมต่างๆ ต่อไปนี้
- กู้คืนจากการสูญเสีย Wi-Fi ชั่วคราว
- กู้คืนจากโหมดสลีปของอุปกรณ์
- กู้คืนในเบื้องหลังของแอป
- กู้คืนหากแอปขัดข้อง
ระบบจะเปิดใช้บริการนี้โดยค่าเริ่มต้น และคุณจะปิดได้ใน CastOptions.Builder
บริการนี้สามารถผสานเข้ากับไฟล์ Manifest ของแอปได้โดยอัตโนมัติหากคุณเปิดใช้การผสานอัตโนมัติในไฟล์ Gradle
เฟรมเวิร์กนี้จะเริ่มให้บริการเมื่อมีเซสชันสื่อและหยุดใช้บริการเมื่อเซสชันสื่อสิ้นสุดลง
วิธีการทํางานของการควบคุมสื่อ
เฟรมเวิร์กของ Cast จะเลิกใช้งานคลาส RemoteMediaPlayer
จากแคสต์ 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
เพื่อเล่น หยุดชั่วคราว และควบคุม
แอปโปรแกรมเล่นสื่อที่ทํางานในเครื่องรับเว็บ
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 จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมขนาดเล็ก
หากต้องการกําหนดลักษณะข้อความของชื่อและคําบรรยายของมุมมองที่กําหนดเองนี้ และหากต้องการเลือกปุ่ม โปรดดูปรับแต่งตัวควบคุมขนาดเล็ก
เพิ่มตัวควบคุมแบบขยาย
รายการตรวจสอบการออกแบบของ 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 เท่านั้น รายการตรวจสอบการออกแบบของ 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 จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมการแจ้งเตือน แต่ไม่ใช่การควบคุมหน้าจอล็อก
หมายเหตุ: หากต้องการแสดงตัวควบคุมหน้าจอล็อกในอุปกรณ์ก่อน Lollipop
RemoteMediaClient
จะขอโฟกัสเสียงในนามของคุณโดยอัตโนมัติ
จัดการข้อผิดพลาด
แอปของผู้ส่งจําเป็นต้องใช้การจัดการการเรียกกลับสําหรับข้อผิดพลาดทั้งหมดและตัดสินใจว่าจะให้การตอบสนองที่ดีที่สุดสําหรับแต่ละขั้นตอนของวงจรการแคสต์ แอปสามารถแสดงกล่องโต้ตอบแสดงข้อผิดพลาดแก่ผู้ใช้ หรืออาจเลือกลดการเชื่อมต่อกับการเชื่อมต่อกับ Web Receiver