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

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

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

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

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

กระแสของแอป

ขั้นตอนต่อไปนี้จะอธิบายถึงขั้นตอนทั่วไปในระดับสูงสําหรับผู้ส่งแอป Android

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

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

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

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

uses-sdk

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

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

ธีม Android

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

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

เริ่มต้นบริบทของ Cast

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

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

โคตลิน
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);
    }
}

วิดเจ็ต Cast UX

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

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

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

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

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

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

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

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

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

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

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

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
โคทลิน
// 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 ด้วยแอปตัวรับสัญญาณเว็บที่รองรับเนมสเปซของสื่อแล้ว ระบบจะสร้างอินสแตนซ์ของ RemoteMediaClient โดยอัตโนมัติโดยเฟรมเวิร์ก ซึ่งแอปจะเข้าถึงได้โดยเรียกใช้เมธอด getRemoteMediaClient() บนอินสแตนซ์ CastSession

เมธอดทั้งหมดของ RemoteMediaClient ที่ออกคําขอไปยังตัวรับสัญญาณเว็บจะส่งคืนออบเจ็กต์ PendingResult ที่ใช้ติดตามคําขอนั้นได้

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

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

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

โคตลิน
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 ได้

เฟรมเวิร์กนี้มีมุมมอง 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 ของผู้ส่งรายงานปริมาณที่ระบุโดยผู้รับเว็บเสมอ

ปุ่มควบคุมระดับเสียง

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

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

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

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

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

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