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

คู่มือนักพัฒนาแอปนี้อธิบายวิธีเพิ่มการรองรับ Google Cast ลงในแอป Android Sender โดยใช้ Android Sender SDK

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

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

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

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

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

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

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

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

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

uses-sdk

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

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

android:theme

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

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

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

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

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

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 ดังนี้

  • การวางซ้อนข้อมูลเบื้องต้น: เฟรมเวิร์กมีมุมมองที่กำหนดเอง 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 แอปผู้ส่งจะระบุรหัสแอปพลิเคชันตัวรับเว็บ และสามารถขอการกรองเนมสเปซได้โดยการตั้งค่า supportedNamespaces ใน CastOptions CastContext เก็บข้อมูลอ้างอิงถึง MediaRouter ไว้ภายใน และจะเริ่มต้นกระบวนการค้นพบภายใต้เงื่อนไขต่อไปนี้

  • ระบบจะเริ่มการค้นพบโดยอัตโนมัติเป็นครั้งคราวเมื่อแอปผู้ส่งอยู่เบื้องหน้า โดยอิงตามอัลกอริทึมที่ออกแบบมาเพื่อรักษาสมดุลระหว่างเวลาในการตอบสนองของการค้นพบอุปกรณ์และการใช้แบตเตอรี่
  • กล่องโต้ตอบแคสต์เปิดอยู่
  • Cast SDK กำลังพยายามกู้คืนเซสชัน 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 การเชื่อมต่อกับแอปดังกล่าว และเริ่มต้นช่องการควบคุมสื่อ ดูข้อมูลเพิ่มเติมเกี่ยวกับเซสชันแคสต์และวงจรชีวิตของ Web Receiver ได้ในคู่มือวงจรชีวิตของแอปพลิเคชันของ Web Receiver

เซสชันจะได้รับการจัดการโดยชั้นเรียน SessionManager ซึ่งแอปของคุณเข้าถึงได้ผ่าน CastContext.getSessionManager() เซสชันแต่ละรายการแสดงโดยคลาสย่อยของคลาส Session เช่น CastSession represent sessions with Cast devices แอปของคุณเข้าถึงเซสชัน Cast ที่ใช้งานอยู่ในปัจจุบันได้ผ่าน SessionManager.getCurrentCastSession()

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

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

แอปสามารถใช้ CastContext#getCastReasonCodeForCastStatusCode(int) เพื่อแปลงข้อผิดพลาดในการเริ่มเซสชันเป็น CastReasonCodes เพื่อให้เข้าใจข้อผิดพลาดในการเริ่ม Cast ได้ดียิ่งขึ้น โปรดทราบว่าข้อผิดพลาดในการเริ่มต้นเซสชันบางรายการ (เช่น 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 ที่รองรับเนมสเปซของสื่อ เฟรมเวิร์กจะสร้างอินสแตนซ์ของ RemoteMediaClient โดยอัตโนมัติ แอปของคุณจะเข้าถึงได้โดยเรียกใช้เมธอด getRemoteMediaClient() ในอินสแตนซ์ CastSession

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

คาดว่าอินสแตนซ์ของ RemoteMediaClient อาจแชร์โดยส่วนต่างๆ ของแอป รวมถึงคอมโพเนนต์ภายในบางอย่างของเฟรมเวิร์ก เช่น Mini Controller แบบถาวรและบริการแจ้งเตือน ด้วยเหตุนี้ อินสแตนซ์นี้จึงรองรับการลงทะเบียน 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 และการแสดงผลความสูงและความกว้างเป็นพิกเซล ตัวแปรของรูปแบบ 4K จะระบุด้วยค่าคงที่ HDR_TYPE_*

การแจ้งเตือนของรีโมตคอนโทรลไปยังอุปกรณ์หลายเครื่อง

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

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

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

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

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

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

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

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

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

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

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

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

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

</menu>

สร้างคลาสใหม่ที่ขยาย ExpandedControllerActivity

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 จะแสดงปุ่มเล่น/หยุดโดยอัตโนมัติแทนปุ่มเล่น/หยุดชั่วคราวในตัวควบคุมแบบขยาย

หากต้องการตั้งค่าลักษณะที่ปรากฏโดยใช้ธีม เลือกปุ่มที่จะแสดง และเพิ่มปุ่มที่กำหนดเอง โปรดดูปรับแต่งตัวควบคุมแบบขยาย

การควบคุมระดับเสียง

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

การควบคุมระดับเสียงด้วยปุ่มบนตัวเครื่อง

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

การควบคุมระดับเสียงด้วยปุ่มจริงก่อน Jelly Bean

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

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

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

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