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

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

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

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

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

โฟลว์ของแอป

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

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

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

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

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

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

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

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

คู่มือต่อไปนี้มีคำอธิบายเกี่ยวกับวิธีเพิ่มวิดเจ็ตเหล่านี้ลงในแอป

เพิ่มปุ่ม Cast

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

เฟรมเวิร์กช่วยให้การเพิ่มa MediaRouteButton เป็นa 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);
}

หากต้องการตั้งค่าลักษณะที่ปรากฏของปุ่มแคสต์โดยใช้ธีม โปรดดู หัวข้อปรับแต่งปุ่ม Cast

กำหนดค่าการค้นหาอุปกรณ์

การค้นหาอุปกรณ์ได้รับการจัดการทั้งหมดโดย CastContext เมื่อเริ่มต้น CastContext แอปผู้ส่งจะระบุรหัสแอปพลิเคชัน Web Receiver และเลือกขอการกรองเนมสเปซได้โดยการตั้งค่า supportedNamespaces ใน CastOptions CastContext มีการอ้างอิงถึง MediaRouter ภายใน และจะเริ่มกระบวนการค้นหาภายใต้เงื่อนไขต่อไปนี้

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

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

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

เพิ่มคอนโทรลเลอร์แบบขยาย

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

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

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

<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();
}

อัปเดตเมธอด loadRemoteMedia ของ LocalPlayerActivity เพื่อแสดงกิจกรรมใหม่เมื่อโหลดสื่อระยะไกล

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

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

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

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