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

คู่มือนักพัฒนาซอฟต์แวร์นี้จะอธิบายวิธีเพิ่มการสนับสนุน Google Cast ลงใน Android ผู้ส่งที่ใช้ Android Sender SDK

อุปกรณ์เคลื่อนที่หรือแล็ปท็อปคือผู้ส่งซึ่งควบคุมการเล่น และ อุปกรณ์ Google Cast คือตัวรับที่จะแสดงเนื้อหาบน TV

เฟรมเวิร์กผู้ส่งหมายถึงไบนารีไลบรารีคลาสของ Cast และที่เกี่ยวข้อง ที่มีอยู่ขณะรันไทม์ผู้ส่ง แอปผู้ส่งหรือแอปแคสต์ หมายถึงแอปที่ทำงานกับผู้ส่งด้วย แอป Web Receiver หมายถึงแอปพลิเคชัน HTML ที่ทำงานบนอุปกรณ์ที่พร้อมใช้งาน Cast

เฟรมเวิร์กผู้ส่งใช้การออกแบบ Callback แบบไม่พร้อมกันเพื่อแจ้งผู้ส่ง แอปเหตุการณ์และการเปลี่ยนไปมาระหว่างสถานะต่างๆ ในชีวิตของแอป Cast

ขั้นตอนของแอป

ขั้นตอนต่อไปนี้จะอธิบายกระบวนการดำเนินการระดับสูงโดยทั่วไปสำหรับผู้ส่ง แอป Android

  • เฟรมเวิร์กการแคสต์จะเริ่มต้นโดยอัตโนมัติ MediaRouter การค้นพบอุปกรณ์ตามวงจรชีวิต Activity
  • เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เฟรมเวิร์กจะแสดง "แคสต์" กล่องโต้ตอบที่มีรายการอุปกรณ์แคสต์ที่พบ
  • เมื่อผู้ใช้เลือกอุปกรณ์แคสต์ เฟรมเวิร์กจะพยายามเปิดตัว แอป Web Receiver บนอุปกรณ์แคสต์
  • เฟรมเวิร์กนี้จะเรียกใช้ Callback ในแอปผู้ส่งเพื่อยืนยันว่า แอปตัวรับสัญญาณเปิดตัวแล้ว
  • เฟรมเวิร์กจะสร้างช่องทางการสื่อสารระหว่างผู้ส่งและเว็บ แอปตัวรับสัญญาณ
  • เฟรมเวิร์กนี้ใช้ช่องทางการสื่อสารเพื่อโหลดและควบคุมสื่อ การเล่นบนเว็บรีซีฟเวอร์
  • เฟรมเวิร์กจะซิงค์สถานะการเล่นสื่อระหว่างผู้ส่งกับ ตัวรับเว็บ: เมื่อผู้ใช้ดำเนินการกับ UI ของผู้ส่ง เฟรมเวิร์กจะส่ง คำขอการควบคุมสื่อเหล่านั้นไปยังเว็บรีซีฟเวอร์ และเมื่อเว็บรีซีฟเวอร์ ส่งการอัปเดตสถานะสื่อ เฟรมเวิร์กจะอัปเดตสถานะของ UI ของผู้ส่ง
  • เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์ Cast เฟรมเวิร์กจะยกเลิกการเชื่อมต่อแอปผู้ส่งจาก Web Receiver

สำหรับรายการชั้นเรียน วิธีการ และเหตุการณ์ทั้งหมดใน Google Cast Android SDK โปรดดูข้อมูลอ้างอิง API ผู้ส่งของ Google Cast สำหรับ Android ส่วนต่อไปนี้จะอธิบายถึงขั้นตอนในการเพิ่มแคสต์ไปยังแอป Android

กำหนดค่าไฟล์ Manifest ของ Android

ไฟล์ AndroidManifest.xml ของแอปกำหนดให้คุณต้องกำหนดค่าต่อไปนี้ สำหรับ Cast SDK

uses-sdk

ตั้งค่าระดับ API ขั้นต่ำและเป้าหมายที่ Cast SDK รองรับ ค่าต่ำสุดตอนนี้คือ API ระดับ 23 และเป้าหมายคือ API ระดับ 34

<uses-sdk
        android:minSdkVersion="23"
        android:targetSdkVersion="34" />

android:theme

ตั้งค่าธีมของแอปตามเวอร์ชัน Android SDK ขั้นต่ำ ตัวอย่างเช่น หาก คุณไม่ได้ใช้ธีมของคุณเอง คุณควรใช้ธีมของ Theme.AppCompat เมื่อกำหนดเป้าหมาย Android SDK เวอร์ชันขั้นต่ำที่ ก่อนอมยิ้ม

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

เริ่มต้นบริบทการแคสต์

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

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

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 เริ่มต้นแบบ Lazy Loading เมื่อ 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);
    }
}

วิดเจ็ต Cast UX

เฟรมเวิร์ก Cast มีวิดเจ็ตที่สอดคล้องกับการออกแบบแคสต์ รายการตรวจสอบมีดังนี้

  • การวางซ้อนบทนำ: เฟรมเวิร์กนี้จะมอบการแสดงผลที่กำหนดเอง IntroductoryOverlay ที่แสดงให้ผู้ใช้เห็นเพื่อเรียกความสนใจไปยังปุ่ม "แคสต์" ครั้งแรกที่รีซีฟเวอร์พร้อมให้บริการ แอป Sender สามารถ ปรับแต่งข้อความและตำแหน่งของชื่อ ข้อความ

  • ปุ่ม "แคสต์": ปุ่ม "แคสต์" จะปรากฏขึ้นไม่ว่าอุปกรณ์แคสต์จะพร้อมใช้งานหรือไม่ก็ตาม เมื่อผู้ใช้คลิกปุ่ม "แคสต์" เป็นครั้งแรก กล่องโต้ตอบ "แคสต์" จะปรากฏขึ้น ซึ่งแสดงรายการอุปกรณ์ที่พบ เมื่อผู้ใช้คลิกปุ่ม "แคสต์" ขณะที่อุปกรณ์เชื่อมต่ออยู่ จะแสดงข้อมูลเมตาของสื่อในปัจจุบัน (เช่น ชื่อสตูดิโอบันทึกเสียง และภาพขนาดย่อ) หรืออนุญาตให้ผู้ใช้ เพื่อยกเลิกการเชื่อมต่อจากอุปกรณ์ Cast "ปุ่ม "แคสต์" บางครั้งมีการอ้างถึง เป็น "ไอคอนแคสต์"

  • มินิคอนโทรลเลอร์: เมื่อผู้ใช้กำลังแคสต์เนื้อหาและได้ออกจากสตรีม หน้าเนื้อหาหรือตัวควบคุมที่ขยายไปยังหน้าจออื่นในแอปผู้ส่ง ตัวควบคุมขนาดเล็กแสดงที่ด้านล่างของหน้าจอเพื่อให้ผู้ใช้ ดูข้อมูลเมตาของสื่อที่กำลังแคสต์อยู่และเพื่อควบคุมการเล่น

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

  • การแจ้งเตือน: Android เท่านั้น เมื่อผู้ใช้กำลังแคสต์เนื้อหาและออกไปจาก แอปผู้ส่งจะมีการแจ้งเตือนสื่อแสดงขึ้น ซึ่งจะแสดงข้อความที่กำลังแคสต์อยู่ ข้อมูลเมตาของสื่อและการควบคุมการเล่น

  • หน้าจอล็อก: Android เท่านั้น เมื่อผู้ใช้แคสต์เนื้อหาและไปยังส่วนต่างๆ (หรืออุปกรณ์ หมดเวลา) ไปยังหน้าจอล็อก ตัวควบคุมหน้าจอล็อกของสื่อจะแสดงขึ้น จะแสดงข้อมูลเมตาของสื่อที่แคสต์และการควบคุมการเล่นอยู่ในปัจจุบัน

คำแนะนำต่อไปนี้จะอธิบายวิธีการเพิ่มวิดเจ็ตเหล่านี้ลงใน แอปของคุณ

เพิ่มปุ่ม "แคสต์"

Android MediaRouter API ออกแบบมาเพื่อเปิดใช้การแสดงและเล่นสื่อในอุปกรณ์รอง แอป Android ที่ใช้ MediaRouter API ควรมีปุ่ม "แคสต์" เป็นส่วนหนึ่งของแอป ของอินเทอร์เฟซผู้ใช้ เพื่ออนุญาตให้ผู้ใช้เลือกเส้นทางสื่อเพื่อเล่นสื่อ อุปกรณ์รอง เช่น อุปกรณ์แคสต์

เฟรมเวิร์กนี้จะช่วยเพิ่ม MediaRouteButton ในฐานะ Cast button ง่ายมาก คุณควรเพิ่มรายการในเมนูหรือ MediaRouteButton ใน XML ก่อน ที่กำหนดเมนูของคุณ และใช้ CastButtonFactory เพื่อนำไปต่อยอดกับเฟรมเวิร์ก

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
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 กำลังพยายามกู้คืนเซสชันการแคสต์

ขั้นตอนการค้นหาจะหยุดเมื่อปิดกล่องโต้ตอบการแคสต์หรือ เข้าสู่พื้นหลัง

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 นำเสนอแนวคิดของเซสชันการแคสต์ ซึ่งประกอบด้วยขั้นตอนการเชื่อมต่อกับอุปกรณ์ การเปิด (หรือการเข้าร่วม) เว็บ แอปตัวรับสัญญาณ การเชื่อมต่อกับแอปดังกล่าว และการเริ่มต้นใช้งานช่องทางควบคุมสื่อ ดูเว็บรีซีฟเวอร์ คำแนะนำเกี่ยวกับวงจรของแอปพลิเคชัน สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเซสชันการแคสต์และวงจรชีวิตของตัวรับเว็บ

ชั้นเรียนเป็นผู้จัดการเซสชัน SessionManager ที่แอปของคุณเข้าถึงได้ผ่าน CastContext.getSessionManager() แต่ละเซสชันจะแสดงโดยคลาสย่อยของชั้นเรียน Session ตัวอย่างเช่น CastSession แสดงเซสชันที่มีอุปกรณ์แคสต์ แอปของคุณสามารถเข้าถึง เซสชันการแคสต์ผ่าน SessionManager.getCurrentCastSession()

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

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

เพื่อให้เข้าใจข้อผิดพลาดในการเริ่มต้นแคสต์ได้ดียิ่งขึ้น แอปสามารถใช้ 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() ในระหว่าง Callback onDeviceNameChanged

โปรดดู การโอนสตรีมใน Web Receiver เพื่อดูข้อมูลเพิ่มเติม

การเชื่อมต่ออีกครั้งอัตโนมัติ

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

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

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

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

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

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

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

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

วิธีการทั้งหมดของ RemoteMediaClient ที่ออกคำขอไปยังเว็บรีซีฟเวอร์จะ แสดงออบเจ็กต์ PendingResult ที่สามารถใช้เพื่อติดตามคำขอนั้น

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

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

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

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 เพื่อเล่น หยุดชั่วคราว และอื่นๆ ควบคุมแอปมีเดียเพลเยอร์ที่ทำงานบนตัวรับเว็บ

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

เพิ่มตัวควบคุมขนาดเล็ก

ตามการออกแบบการแคสต์ รายการตรวจสอบ แอปผู้ส่งควรมีการควบคุมแบบถาวรที่เรียกว่า มินิ ผู้ควบคุมข้อมูล ควรปรากฏเมื่อผู้ใช้ออกจากหน้าเนื้อหาปัจจุบันเพื่อ ส่วนอื่นๆ ในแอปผู้ส่ง ตัวควบคุมขนาดเล็กจะแสดงการช่วยเตือนที่มองเห็นได้ ไปยังผู้ใช้เซสชันการแคสต์ปัจจุบัน การแตะบนตัวควบคุมขนาดเล็ก ผู้ใช้สามารถกลับไปที่มุมมองตัวควบคุมที่ขยายแบบเต็มหน้าจอของแคสต์ได้

กรอบการทำงานจะมีมุมมองที่กำหนดเอง หรือ MiniControllerFragment ซึ่งคุณสามารถเพิ่ม ไว้ที่ด้านล่างของไฟล์เลย์เอาต์ของแต่ละกิจกรรมที่ต้องการแสดง มินิคอนโทรลเลอร์

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

เมื่อแอปผู้ส่งกำลังเล่นสตรีมแบบสดที่เป็นวิดีโอหรือเสียง SDK แสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราว ในตัวควบคุมขนาดเล็ก

หากต้องการตั้งค่าลักษณะข้อความของชื่อและคำบรรยายของมุมมองที่กำหนดเองนี้ และหากต้องการเลือกปุ่ม โปรดดู ปรับแต่ง Mini Controller

เพิ่มตัวควบคุมที่ขยาย

รายการตรวจสอบสำหรับการออกแบบของ Google Cast กำหนดให้แอปผู้ส่งต้องมีขยาย ผู้ควบคุมข้อมูล สำหรับสื่อที่กำลังแคสต์อยู่ ตัวควบคุมที่ขยายเป็นเวอร์ชันแบบเต็มหน้าจอของ มินิคอนโทรลเลอร์

Cast SDK มีวิดเจ็ตสำหรับตัวควบคุมที่ขยายชื่อ ExpandedControllerActivity นี่คือคลาสนามธรรมที่คุณต้องให้คลาสย่อยเพิ่มปุ่ม "แคสต์"

ก่อนอื่น ให้สร้างไฟล์แหล่งข้อมูลเมนูใหม่สำหรับตัวควบคุมที่ขยายเพื่อระบุ ปุ่ม "แคสต์"

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

สร้างชั้นเรียนใหม่ที่ขยายเวลา ExpandedControllerActivity

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 คุณสามารถใช้ปุ่มบนตัวเครื่องบนอุปกรณ์ผู้ส่งเพื่อเปลี่ยนการตั้งค่า ระดับเสียงของเซสชันการแคสต์บนตัวรับเว็บโดยค่าเริ่มต้นสำหรับอุปกรณ์ใดก็ตามที่ใช้ 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);
    }
}

เพิ่มตัวควบคุมสื่อลงในการแจ้งเตือนและหน้าจอล็อก

รายการตรวจสอบการออกแบบ Google Cast กำหนดให้แอปผู้ส่งต้องใช้สำหรับ Android เท่านั้น นำการควบคุมสื่อมาใช้ใน การแจ้งเตือน และในล็อก หน้าจอ ที่ผู้ส่งกำลังแคสต์ แต่แอปผู้ส่งไม่มีโฟกัส ของเฟรมเวิร์ก 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 แสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราว บนตัวควบคุมการแจ้งเตือน แต่ไม่ใช่ตัวควบคุมหน้าจอล็อก

หมายเหตุ: วิธีแสดงการควบคุมหน้าจอล็อกในอุปกรณ์ Pre-Lollipop RemoteMediaClient จะขอการโฟกัสเสียงในนามของคุณโดยอัตโนมัติ

จัดการข้อผิดพลาด

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