ผสานรวมการแคสต์ลงในแอป Android ของคุณ

คู่มือนักพัฒนาแอปนี้อธิบายวิธีเพิ่มการรองรับ 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 ขั้นต่ำคือ 24 และเป้าหมายคือ API ระดับ 35

<uses-sdk
        android:minSdkVersion="24"
        android:targetSdkVersion="35" />

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>

เริ่มต้นบริบท Cast

เฟรมเวิร์กมีออบเจ็กต์ Singleton ส่วนกลางคือ CastContext ซึ่งประสานงาน การโต้ตอบทั้งหมดของเฟรมเวิร์ก

แอปของคุณต้องใช้ OptionsProvider อินเทอร์เฟซเพื่อระบุตัวเลือกที่จำเป็นในการเริ่มต้นใช้งาน CastContext แบบซิงเกิลตัน OptionsProvider มีอินสแตนซ์ของ CastOptions ซึ่งมีตัวเลือกที่มีผลต่อลักษณะการทำงานของเฟรมเวิร์ก สิ่งที่สำคัญที่สุดในบรรดาสิ่งเหล่านี้คือรหัสแอปพลิเคชัน Web Receiver ซึ่งใช้เพื่อกรองผลการค้นหาและเปิดแอป Web Receiver เมื่อเริ่มเซสชันการแคสต์

Kotlin
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
    }
}
Java
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()

Kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
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 ควรมีปุ่มแคสต์เป็นส่วนหนึ่งของอินเทอร์เฟซผู้ใช้ เพื่อให้ผู้ใช้เลือกเส้นทางสื่อเพื่อเล่นสื่อในอุปกรณ์รอง เช่น อุปกรณ์แคสต์ได้

เฟรมเวิร์กช่วยให้การเพิ่ม 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" />
Kotlin
// 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
}
Java
// 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>
Kotlin
// 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)
}
Java
// 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 หรือเมื่อแอปที่ส่งเข้าสู่เบื้องหลัง

Kotlin
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
    }
}
Java
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 แอปของคุณสามารถเข้าถึงเซสชัน Cast ที่ใช้งานอยู่ปัจจุบันผ่าน SessionManager.getCurrentCastSession()

แอปของคุณสามารถใช้คลาส SessionManagerListener เพื่อตรวจสอบเหตุการณ์เซสชัน เช่น การสร้าง การระงับ การกลับมาทำงานต่อ และการสิ้นสุด เฟรมเวิร์กจะพยายามดำเนินการต่อจากการสิ้นสุดที่ผิดปกติ/กะทันหันโดยอัตโนมัติขณะที่เซสชันทำงานอยู่

ระบบจะสร้างและปิดเซสชันโดยอัตโนมัติเพื่อตอบสนองต่อท่าทางของผู้ใช้จากกล่องโต้ตอบ MediaRouter

หากต้องการทำความเข้าใจข้อผิดพลาดในการเริ่มต้น Cast ได้ดียิ่งขึ้น แอปสามารถใช้ CastContext#getCastReasonCodeForCastStatusCode(int) เพื่อแปลงข้อผิดพลาดในการเริ่มต้นเซสชันเป็น CastReasonCodes โปรดทราบว่าข้อผิดพลาดในการเริ่มต้นเซสชันบางอย่าง (เช่น CastReasonCodes#CAST_CANCELLED) เป็นลักษณะการทำงานที่ตั้งใจไว้ และไม่ควรบันทึกเป็นข้อผิดพลาด

หากต้องการทราบการเปลี่ยนแปลงสถานะของเซสชัน คุณสามารถใช้ SessionManagerListenerได้ ตัวอย่างนี้จะตรวจสอบความพร้อมของ CastSession ใน Activity

Kotlin
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)
    }
}
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() ระหว่างการเรียกกลับ onDeviceNameChanged

ดูข้อมูลเพิ่มเติมได้ที่ การถ่ายโอนสตรีมใน Web Receiver

การเชื่อมต่อใหม่โดยอัตโนมัติ

เฟรมเวิร์กมี ReconnectionService ซึ่งแอปผู้ส่งสามารถเปิดใช้เพื่อจัดการการเชื่อมต่อใหม่ในกรณีที่ซับซ้อน ต่างๆ เช่น

  • กู้คืนจากการสูญเสียสัญญาณ Wi-Fi ชั่วคราว
  • กู้คืนจากการพักหน้าจอของอุปกรณ์
  • กู้คืนจากการทำงานของแอปในเบื้องหลัง
  • กู้คืนหากแอปขัดข้อง

บริการนี้จะเปิดอยู่โดยค่าเริ่มต้น และปิดได้ใน CastOptions.Builder

ระบบจะผสานบริการนี้เข้ากับไฟล์ Manifest ของแอปโดยอัตโนมัติหากเปิดใช้การผสานอัตโนมัติ ในไฟล์ Gradle

เฟรมเวิร์กจะเริ่มบริการเมื่อมีเซสชันสื่อ และหยุดบริการเมื่อเซสชันสื่อสิ้นสุดลง

วิธีการทำงานของตัวควบคุมสื่อ

เฟรมเวิร์ก Cast จะเลิกใช้งานคลาส RemoteMediaPlayer จาก Cast 2.x เพื่อให้ใช้คลาสใหม่ RemoteMediaClient แทน ซึ่งมีฟังก์ชันการทำงานเดียวกันในชุด API ที่สะดวกกว่า และ ไม่ต้องส่ง GoogleApiClient

เมื่อแอปสร้าง CastSession กับแอป Web Receiver ที่รองรับเนมสเปซสื่อ Framework จะสร้างอินสแตนซ์ของ RemoteMediaClient โดยอัตโนมัติ และแอปของคุณจะเข้าถึงอินสแตนซ์ดังกล่าวได้โดยเรียกใช้เมธอด getRemoteMediaClient() ในอินสแตนซ์ CastSession

ทุกวิธีของ RemoteMediaClient ที่ส่งคำขอไปยัง Web Receiver จะ แสดงออบเจ็กต์ PendingResult ที่ใช้ติดตามคำขอนั้นได้

คาดว่าอินสแตนซ์ของ RemoteMediaClient อาจแชร์โดย หลายส่วนของแอป และแน่นอนว่าคอมโพเนนต์ภายในบางอย่างของ เฟรมเวิร์ก เช่น มินิคอนโทรลเลอร์แบบถาวรและ บริการแจ้งเตือน ด้วยเหตุนี้ อินสแตนซ์นี้จึงรองรับการลงทะเบียนอินสแตนซ์หลายรายการของ RemoteMediaClient.Listener

ตั้งค่าข้อมูลเมตาของสื่อ

คลาส MediaMetadata แสดงข้อมูลเกี่ยวกับรายการสื่อที่คุณต้องการแคสต์ ตัวอย่างต่อไปนี้สร้างอินสแตนซ์ MediaMetadata ใหม่ของภาพยนตร์และตั้งค่า ชื่อ คำบรรยาย และรูปภาพ 2 รูป

Kotlin
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))))
Java
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

Kotlin
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())
Java
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

เพิ่มมินิคอนโทรลเลอร์

ตามรายการตรวจสอบการออกแบบ 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

Kotlin
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
    }
}
Java
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 เพื่อตั้งค่ากิจกรรมเป้าหมายเป็นกิจกรรมใหม่

Kotlin
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()
}
Java
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 เพื่อแสดงกิจกรรมใหม่เมื่อโหลดสื่อจากระยะไกล

Kotlin
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()
    )
}
Java
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()

Kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
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

Kotlin
// 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()
Java
// 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 ปัจจุบันฟีเจอร์หน้าจอล็อกจะเปิดอยู่ตราบใดที่การแจ้งเตือน เปิดอยู่

Kotlin
// ... 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()
Java
// ... 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 ก็ได้