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

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

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

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

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

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

โฟลว์แอป

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

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

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

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

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

use-sdk

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

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

android:ธีม

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

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

เริ่มต้นบริบทของแคสต์

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

แอปต้องใช้อินเทอร์เฟซ OptionsProvider เพื่อจัดหาตัวเลือกที่จําเป็นในการเริ่มต้น CastContext Singleton OptionsProvider มีอินสแตนซ์ของ CastOptions ที่มีตัวเลือกที่ส่งผลต่อลักษณะการทํางานของเฟรมเวิร์ก สิ่งสําคัญที่สุดคือรหัสแอปพลิเคชัน Web Receiver ที่ใช้ในการกรองผลการค้นหา Discovery และเปิดแอป 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);
    }
}

วิดเจ็ต Cast UX

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

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

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

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

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

หากต้องการทราบการเปลี่ยนแปลงสถานะสําหรับเซสชัน คุณสามารถใช้ SessionManagerListener ได้ ตัวอย่างนี้ฟังความพร้อมใช้งานของ CastSession ใน Activity

โคตลิน
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mSessionManager = CastContext.getSharedInstance(this).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 CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSessionManager = CastContext.getSharedInstance(this).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 หรือจออัจฉริยะ สื่อจะหยุดเล่นในอุปกรณ์หนึ่ง (แหล่งที่มา) และเล่นต่อในอุปกรณ์อื่น (ปลายทาง) อุปกรณ์แคสต์ที่มีเฟิร์มแวร์ล่าสุดจะใช้เป็นต้นทางหรือปลายทางในการโอนสตรีมได้

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

ดูข้อมูลเพิ่มเติมที่หัวข้อการโอนสตรีมบนตัวรับสัญญาณเว็บ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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