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

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

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

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

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

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

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

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

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

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

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

uses-sdk

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

<uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="28" />

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

คอตลิน
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
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()

คอตลิน
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 มอบวิดเจ็ตที่เป็นไปตามรายการตรวจสอบของการออกแบบ การแคสต์

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

คอตลิน
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

คอตลิน
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
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onPause() {
        super.onPause()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
        mCastSession = null
    }
}
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();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
    @Override
    protected void onPause() {
        super.onPause();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
        mCastSession = null;
    }
}

โอนสตรีม

การรักษาสถานะเซสชันเป็นพื้นฐานของการโอนสตรีม ซึ่งผู้ใช้สามารถย้ายสตรีมเสียงและวิดีโอที่มีอยู่ในอุปกรณ์ต่างๆ โดยใช้คำสั่งเสียง, แอป Google Home หรือ Smart Display สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อในอุปกรณ์อื่น (ปลายทาง) อุปกรณ์แคสต์ที่มีเฟิร์มแวร์เวอร์ชันล่าสุดจะใช้เป็นแหล่งที่มาหรือปลายทางในการโอนสตรีมได้

หากต้องการรับอุปกรณ์ปลายทางใหม่ในระหว่างการโอนหรือการขยายสตรีม ให้ลงทะเบียน Cast.Listener โดยใช้ CastSession#addCastListener จากนั้นโทรหา CastSession#getCastDevice() ระหว่างการติดต่อกลับ onDeviceNameChanged

ดูการโอนสตรีมบนเว็บรีซีฟเวอร์สำหรับข้อมูลเพิ่มเติม

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

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

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

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

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

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

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

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

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

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

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

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

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

คอตลิน
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
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 เพื่อเล่น หยุดชั่วคราว และควบคุมแอปโปรแกรมเล่นสื่อที่ทำงานอยู่บนรีซีฟเวอร์

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

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

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

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

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

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

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

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

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

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

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

</menu>

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

คอตลิน
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 เพื่อกำหนดกิจกรรมเป้าหมายเป็นกิจกรรมใหม่ของคุณ

คอตลิน
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 เพื่อแสดงกิจกรรมใหม่เมื่อโหลดสื่อระยะไกลแล้ว

คอตลิน
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 ของผู้ส่งรายงานปริมาณที่ Web Receiver ระบุเสมอ

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

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

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

หากต้องการใช้แป้นปรับระดับเสียงเพื่อควบคุมระดับเสียงของอุปกรณ์ Web Receiver บนอุปกรณ์ Android ที่เก่ากว่า Jelly Bean แอปผู้ส่งควรลบล้าง dispatchKeyEvent ในกิจกรรมของตนและเรียก CastContext.onDispatchVolumeKeyEventBeforeJellyBean()

คอตลิน
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 ซึ่งเป็นค่าเริ่มต้น

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

คอตลิน
// ... 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 จะขอโฟกัสเสียงในนามของคุณโดยอัตโนมัติ

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

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