เปิดใช้แอป Android TV ได้

1. ภาพรวม

โลโก้ Google Cast

Codelab นี้จะสอนวิธีแก้ไขแอป Android TV ที่มีอยู่เพื่อรองรับการแคสต์และการสื่อสารจากแอปตัวส่ง Cast ที่มีอยู่

Google Cast และ Cast Connect คืออะไร

Google Cast ช่วยให้ผู้ใช้แคสต์เนื้อหาจากอุปกรณ์เคลื่อนที่ไปยังทีวีได้ เซสชัน Google Cast ทั่วไปประกอบด้วย 2 องค์ประกอบ ได้แก่ แอปพลิเคชันผู้ส่งและแอปพลิเคชันผู้รับ แอปพลิเคชันสำหรับผู้ส่ง เช่น แอปหรือเว็บไซต์บนอุปกรณ์เคลื่อนที่ เช่น YouTube.com จะเริ่มต้นและควบคุมการเล่นแอปพลิเคชันตัวรับการแคสต์ แอปพลิเคชันตัวรับการแคสต์เป็นแอป HTML 5 ที่ทำงานในอุปกรณ์ Chromecast และ Android TV

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

Cast Connect สร้างขึ้นจากโครงสร้างพื้นฐานนี้ โดยมีแอป Android TV ทำหน้าที่เป็นตัวรับ คลัง Cast Connect ช่วยให้แอป Android TV ของคุณรับข้อความและออกอากาศสถานะสื่อได้ราวกับเป็นแอปพลิเคชันตัวรับ Cast

เราจะสร้างอะไร

เมื่อทำ Codelab นี้เสร็จแล้ว คุณจะใช้แอปตัวส่ง Cast เพื่อแคสต์วิดีโอไปยังแอป Android TV ได้ นอกจากนี้ แอป Android TV ยังสื่อสารกับแอปตัวส่งผ่านโปรโตคอล Cast ได้ด้วย

สิ่งที่คุณจะได้เรียนรู้

  • วิธีเพิ่มคลัง Cast Connect ลงในแอป ATV ตัวอย่าง
  • วิธีเชื่อมต่ออุปกรณ์ส่งสัญญาณแคสต์และเปิดแอป ATV
  • วิธีเริ่มเล่นสื่อในแอป ATV จากแอปตัวส่ง Cast
  • วิธีส่งสถานะสื่อจากแอป ATV ไปยังแอปผู้ส่ง Cast

สิ่งที่คุณต้องมี

2. รับโค้ดตัวอย่าง

คุณสามารถดาวน์โหลดโค้ดตัวอย่างทั้งหมดลงในคอมพิวเตอร์ได้...

และแตกไฟล์ ZIP ที่ดาวน์โหลด

3. เรียกใช้แอปตัวอย่าง

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

รูปภาพชุดภาพปกวิดีโอ (มีภาพปกที่ไฮไลต์อยู่ 1 ภาพ) วางซ้อนกันเหนือตัวอย่างวิดีโอแบบเต็มหน้าจอ โดยมีคำว่า "Cast Connect" ปรากฏที่ด้านขวาบน

ลงทะเบียนอุปกรณ์ของนักพัฒนาซอฟต์แวร์

หากต้องการเปิดใช้ความสามารถของ Cast Connect สําหรับการพัฒนาแอปพลิเคชัน คุณต้องลงทะเบียนหมายเลขซีเรียลของ Chromecast ในตัวอุปกรณ์ Android TV ที่คุณจะใช้ใน Cast Developer Console คุณดูหมายเลขซีเรียลได้โดยไปที่การตั้งค่า > ค่ากำหนดอุปกรณ์ > Chromecast Built-In > หมายเลขซีเรียลบน Android TV โปรดทราบว่าหมายเลขนี้แตกต่างจากหมายเลขซีเรียลของอุปกรณ์จริง และต้องได้รับจากวิธีการที่อธิบายไว้ข้างต้น

รูปภาพหน้าจอ Android TV ที่แสดงหน้าจอ "Chromecast ในตัว", หมายเลขเวอร์ชัน และหมายเลขซีเรียล

หากไม่ได้ลงทะเบียน Cast Connect จะใช้งานได้กับแอปที่ติดตั้งจาก Google Play Store เท่านั้นเนื่องจากเหตุผลด้านความปลอดภัย หลังจากเริ่มกระบวนการลงทะเบียนไปแล้ว 15 นาที ให้รีสตาร์ทอุปกรณ์

ติดตั้งแอปผู้ส่งสำหรับ Android

ในการทดสอบการส่งคำขอจากอุปกรณ์เคลื่อนที่ เราได้จัดเตรียมแอปพลิเคชันผู้ส่งแบบง่ายที่เรียกว่า "แคสต์วิดีโอ" เป็นไฟล์ mobile-sender-0629.apk ไว้ในการดาวน์โหลดรหัสไปรษณีย์ของซอร์สโค้ด เราจะใช้ ADB เพื่อติดตั้ง APK หากคุณติดตั้งวิดีโอแคสต์เวอร์ชันอื่นไว้แล้ว โปรดถอนการติดตั้งเวอร์ชันนั้นจากโปรไฟล์ทั้งหมดในอุปกรณ์ก่อนดำเนินการต่อ

  1. เปิดใช้ตัวเลือกสำหรับนักพัฒนาแอปและการแก้ไขข้อบกพร่อง USB ในโทรศัพท์ Android
  2. เสียบสายข้อมูล USB เพื่อเชื่อมต่อโทรศัพท์ Android กับคอมพิวเตอร์สำหรับการพัฒนา
  3. ติดตั้ง mobile-sender-0629.apk ในโทรศัพท์ Android

รูปภาพของหน้าต่างเทอร์มินัลที่เรียกใช้คำสั่ง adb install เพื่อติดตั้ง mobile-sender.apk

  1. คุณจะเห็นแอปส่งวิดีโอแคสต์ในโทรศัพท์ Android ไอคอนแอปผู้ส่งวิดีโอแคสต์

รูปภาพแอปส่งวิดีโอแคสต์ที่ทำงานอยู่บนหน้าจอโทรศัพท์ Android

ติดตั้งแอป Android TV

วิธีการต่อไปนี้อธิบายวิธีเปิดและเรียกใช้แอปตัวอย่างที่เสร็จสมบูรณ์ใน Android Studio

  1. เลือกนําเข้าโปรเจ็กต์ในหน้าจอต้อนรับหรือตัวเลือกเมนูไฟล์ > ใหม่ > นําเข้าโปรเจ็กต์...
  2. เลือกไดเรกทอรี ไอคอนโฟลเดอร์app-done จากโฟลเดอร์โค้ดตัวอย่าง แล้วคลิก "ตกลง"
  3. คลิก File > ปุ่มซิงค์โปรเจ็กต์กับ Gradle ของ Android App Studio Sync Project with Gradle Files
  4. เปิดใช้ตัวเลือกสำหรับนักพัฒนาแอปและการแก้ไขข้อบกพร่อง USB ในอุปกรณ์ Android TV
  5. ADB เชื่อมต่อกับอุปกรณ์ Android TV ของคุณ อุปกรณ์ดังกล่าวควรจะแสดงใน Android Studio รูปภาพแสดงอุปกรณ์ Android TV ที่ปรากฏในแถบเครื่องมือของ Android Studio
  6. คลิกปุ่ม ปุ่ม Android Studio Run รูปสามเหลี่ยมสีเขียวที่ชี้ไปทางขวาRun คุณควรเห็นแอป ATV ชื่อ Cast Connect Codelab ปรากฏขึ้นหลังจากผ่านไป 2-3 วินาที

มาเล่น Cast Connect ด้วยแอป ATV

  1. ไปที่หน้าจอหลักของ Android TV
  2. เปิดแอปส่งวิดีโอแคสต์จากโทรศัพท์ Android คลิกปุ่มแคสต์ ไอคอนปุ่มแคสต์ แล้วเลือกอุปกรณ์ ATV
  3. แอป Cast Connect Codelab ATV จะเปิดขึ้นใน ATV และปุ่มแคสต์ในอุปกรณ์ส่งจะระบุว่าเชื่อมต่อแล้ว ไอคอนปุ่มแคสต์ที่มีสีกลับด้าน
  4. เลือกวิดีโอจากแอป ATV แล้ววิดีโอจะเริ่มเล่นบน ATV
  5. ตอนนี้คุณจะเห็นตัวควบคุมขนาดเล็กที่ด้านล่างของแอปผู้ส่งในโทรศัพท์มือถือ คุณสามารถใช้ปุ่มเล่น/หยุดชั่วคราวเพื่อควบคุมการเล่นได้
  6. เลือกวิดีโอจากโทรศัพท์มือถือแล้วเล่น วิดีโอจะเริ่มเล่นบนรถ ATV และตัวควบคุมที่ขยายจะแสดงบนผู้ส่งบนอุปกรณ์เคลื่อนที่
  7. ล็อกโทรศัพท์ และเมื่อปลดล็อกแล้ว คุณควรเห็นการแจ้งเตือนบนหน้าจอล็อกเพื่อควบคุมการเล่นสื่อหรือหยุดการแคสต์

รูปภาพส่วนหนึ่งของหน้าจอโทรศัพท์ Android ที่มีมินิเพลเยอร์เล่นวิดีโอ

4. เตรียมโปรเจ็กต์เริ่มต้น

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

  1. เลือกนําเข้าโปรเจ็กต์ในหน้าจอต้อนรับหรือตัวเลือกเมนูไฟล์ > ใหม่ > นําเข้าโปรเจ็กต์...
  2. เลือกไดเรกทอรี ไอคอนโฟลเดอร์app-start จากโฟลเดอร์โค้ดตัวอย่าง แล้วคลิก "ตกลง"
  3. คลิก File > ปุ่มซิงค์โปรเจ็กต์กับ Gradle ของ Android Studio Sync Project with Gradle Files
  4. เลือกอุปกรณ์ ATV แล้วคลิกปุ่ม ปุ่มเรียกใช้ของ Android Studio ซึ่งเป็นสามเหลี่ยมสีเขียวชี้ไปทางขวาRun เพื่อเรียกใช้แอปและสำรวจ UI แถบเครื่องมือ Android Studio ที่แสดงอุปกรณ์ Android TV ที่เลือก

รูปภาพชุดภาพปกวิดีโอ (มีภาพปกที่ไฮไลต์อยู่ 1 ภาพ) วางซ้อนกันเหนือตัวอย่างวิดีโอแบบเต็มหน้าจอ โดยมีคำว่า "Cast Connect" ปรากฏที่ด้านขวาบน

การออกแบบแอป

แอปแสดงรายการวิดีโอให้ผู้ใช้เลือกดู ผู้ใช้สามารถเลือกวิดีโอที่จะเล่นบน Android TV ได้ แอปประกอบด้วยกิจกรรมหลัก 2 รายการ ได้แก่ MainActivity และ PlaybackActivity

กิจกรรมหลัก

กิจกรรมนี้มีส่วนย่อย (MainFragment) รายการวิดีโอและข้อมูลเมตาที่เกี่ยวข้องจะได้รับการกำหนดค่าในคลาส MovieList และจะเรียกใช้เมธอด setupMovies() เพื่อสร้างรายการออบเจ็กต์ Movie

ออบเจ็กต์ Movie แสดงถึงเอนทิตีวิดีโอที่มีชื่อ คำอธิบาย ภาพขนาดย่อของรูปภาพ และ URL ของวิดีโอ ออบเจ็กต์ Movie แต่ละรายการจะเชื่อมโยงกับ CardPresenter เพื่อแสดงภาพปกวิดีโอพร้อมชื่อและสตูดิโอ และส่งไปยัง ArrayObjectAdapter

เมื่อเลือกรายการ ระบบจะส่งผ่านออบเจ็กต์ Movie ที่เกี่ยวข้องไปยัง PlaybackActivity

PlaybackActivity

กิจกรรมนี้มี Fregment (PlaybackVideoFragment) ซึ่งโฮสต์ VideoView ที่มี ExoPlayer, ตัวควบคุมสื่อบางรายการ และพื้นที่ข้อความเพื่อแสดงคำอธิบายของวิดีโอที่เลือก และอนุญาตให้ผู้ใช้เล่นวิดีโอบน Android TV ผู้ใช้สามารถใช้รีโมตคอนโทรลเพื่อเล่น/หยุดชั่วคราว หรือกรอวิดีโอเพื่อเล่นวิดีโอ

ข้อกำหนดเบื้องต้นของ Cast Connect

Cast Connect ใช้บริการ Google Play เวอร์ชันใหม่ที่กำหนดให้แอป ATV ของคุณต้องอัปเดตเพื่อใช้เนมสเปซ AndroidX

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

5. การกำหนดค่าการรองรับ Cast

การอ้างอิง

อัปเดตแอป build.gradle เพื่อรวมทรัพยากร Dependency ของไลบรารีที่จำเป็น

dependencies {
    ....

    // Cast Connect libraries
    implementation 'com.google.android.gms:play-services-cast-tv:20.0.0'
    implementation 'com.google.android.gms:play-services-cast:21.1.0'
}

ซิงค์โปรเจ็กต์เพื่อยืนยันว่าโปรเจ็กต์สร้างโดยไม่มีข้อผิดพลาด

การเริ่มต้น

CastReceiverContext เป็นออบเจ็กต์เดี่ยวเพื่อประสานงานการโต้ตอบกับแคสต์ทั้งหมด คุณต้องติดตั้งใช้งานอินเทอร์เฟซ ReceiverOptionsProvider เพื่อระบุ CastReceiverOptions เมื่อเริ่มต้น CastReceiverContext

สร้างไฟล์ CastReceiverOptionsProvider.kt และเพิ่มคลาสต่อไปนี้ลงในโปรเจ็กต์

package com.google.sample.cast.castconnect

import android.content.Context
import com.google.android.gms.cast.tv.ReceiverOptionsProvider
import com.google.android.gms.cast.tv.CastReceiverOptions

class CastReceiverOptionsProvider : ReceiverOptionsProvider {
    override fun getOptions(context: Context): CastReceiverOptions {
        return CastReceiverOptions.Builder(context)
                .setStatusText("Cast Connect Codelab")
                .build()
    }
}

จากนั้นระบุผู้ให้บริการตัวเลือกผู้รับภายในแท็ก <application> ของไฟล์ AndroidManifest.xml ของแอป

<application>
  ...
  <meta-data
    android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.google.sample.cast.castconnect.CastReceiverOptionsProvider" />
</application>

หากต้องการเชื่อมต่อกับแอป ATV จากอุปกรณ์ส่ง Cast ให้เลือกกิจกรรมที่ต้องการเปิด ในโค้ดแล็บนี้ เราจะเปิดMainActivityของแอปเมื่อเริ่มเซสชัน Cast ในไฟล์ AndroidManifest.xml ให้เพิ่มตัวกรอง Intent การเปิดตัวใน MainActivity

<activity android:name=".MainActivity">
  ...
  <intent-filter>
    <action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

วงจรชีวิตของบริบทของอุปกรณ์รับการแคสต์

คุณควรเริ่มใช้งาน CastReceiverContext เมื่อเปิดแอป และหยุด CastReceiverContext เมื่อย้ายแอปไปที่เบื้องหลัง เราขอแนะนำให้คุณใช้ LifecycleObserver จากไลบรารี androidx.lifecycle เพื่อจัดการการโทร CastReceiverContext.start() และ CastReceiverContext.stop()

เปิดไฟล์ MyApplication.kt และเริ่มต้นบริบทการแคสต์โดยเรียกใช้ initInstance() ในเมธอด onCreate ของแอปพลิเคชัน ในคลาส AppLifeCycleObserver start() CastReceiverContext เมื่อแอปพลิเคชันกลับมาทำงานต่อ และ stop() เมื่อแอปพลิเคชันหยุดชั่วคราว

package com.google.sample.cast.castconnect

import com.google.android.gms.cast.tv.CastReceiverContext
...

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        CastReceiverContext.initInstance(this)
        ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
    }

    class AppLifecycleObserver : DefaultLifecycleObserver {
        override fun onResume(owner: LifecycleOwner) {
            Log.d(LOG_TAG, "onResume")
            CastReceiverContext.getInstance().start()
        }

        override fun onPause(owner: LifecycleOwner) {
            Log.d(LOG_TAG, "onPause")
            CastReceiverContext.getInstance().stop()
        }
    }
}

การเชื่อมต่อ MediaSession กับ MediaManager

MediaManager เป็นพร็อพเพอร์ตี้ของ CastReceiverContext แบบ Singleton ซึ่งจะจัดการสถานะสื่อ จัดการ Intent การโหลด แปลข้อความเนมสเปซสื่อจากผู้ส่งเป็นคําสั่งสื่อ และส่งสถานะสื่อกลับไปยังผู้ส่ง

เมื่อสร้าง MediaSession คุณจะต้องระบุโทเค็น MediaSession ปัจจุบันให้กับ MediaManager ด้วยเพื่อให้ทราบว่าต้องส่งคำสั่งไปที่ไหนและดึงข้อมูลสถานะการเล่นสื่อ ในไฟล์ PlaybackVideoFragment.kt ให้ตรวจสอบว่า MediaSession ได้รับการเริ่มต้นก่อนที่จะตั้งค่าโทเค็นเป็น MediaManager

import com.google.android.gms.cast.tv.CastReceiverContext
import com.google.android.gms.cast.tv.media.MediaManager
...

class PlaybackVideoFragment : VideoSupportFragment() {
    private var castReceiverContext: CastReceiverContext? = null
    ...

    private fun initializePlayer() {
        if (mPlayer == null) {
            ...
            mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
            ...
            castReceiverContext = CastReceiverContext.getInstance()
            if (castReceiverContext != null) {
                val mediaManager: MediaManager = castReceiverContext!!.getMediaManager()
                mediaManager.setSessionCompatToken(mMediaSession!!.getSessionToken())
            }

        }
    }
}

เมื่อคุณเผยแพร่ MediaSession เนื่องจากไม่มีการเล่น คุณควรตั้งค่าโทเค็น Null ใน MediaManager ดังนี้

private fun releasePlayer() {
    mMediaSession?.release()
    castReceiverContext?.mediaManager?.setSessionCompatToken(null)
    ...
}

มาเรียกใช้แอปตัวอย่างกัน

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

6. กำลังโหลดสื่อ

ระบบจะส่งคําสั่งโหลดผ่าน Intent ที่มีชื่อแพ็กเกจที่คุณกําหนดไว้ในคอนโซลนักพัฒนาซอฟต์แวร์ คุณต้องเพิ่มตัวกรอง Intent ที่กําหนดไว้ล่วงหน้าต่อไปนี้ในแอป Android TV เพื่อระบุกิจกรรมเป้าหมายที่จะรับ Intent นี้ ในไฟล์ AndroidManifest.xml ให้เพิ่มตัวกรอง Intent ของโหลดลงใน PlayerActivity

<activity android:name="com.google.sample.cast.castconnect.PlaybackActivity"
          android:launchMode="singleTask"
          android:exported="true">
  <intent-filter>
     <action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
     <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

การจัดการคําขอโหลดใน Android TV

เมื่อมีการกำหนดค่าให้กิจกรรมรับ Intent ที่มีคำขอโหลด เราจึงจะต้องจัดการต่อไป

แอปเรียกเมธอดส่วนตัวชื่อ processIntent เมื่อกิจกรรมเริ่มต้น เมธอดนี้มีตรรกะในการประมวลผล Intent ที่เข้ามา หากต้องการจัดการคําขอโหลด เราจะแก้ไขเมธอดนี้และส่ง Intent เพื่อประมวลผลเพิ่มเติมโดยการเรียกเมธอด onNewIntent ของอินสแตนซ์ MediaManager หาก MediaManager ตรวจพบว่า Intent เป็นคำขอโหลด ระบบจะดึงออบเจ็กต์ MediaLoadRequestData ออกจาก Intent และเรียกใช้ MediaLoadCommandCallback.onLoad() แก้ไขเมธอด processIntent ในไฟล์ PlaybackVideoFragment.kt เพื่อจัดการ Intent ที่มีคำขอโหลด ดังนี้

fun processIntent(intent: Intent?) {
    val mediaManager: MediaManager = CastReceiverContext.getInstance().getMediaManager()
    // Pass intent to Cast SDK
    if (mediaManager.onNewIntent(intent)) {
        return
    }

    // Clears all overrides in the modifier.
    mediaManager.getMediaStatusModifier().clear()

    // If the SDK doesn't recognize the intent, handle the intent with your own logic.
    ...
}

ต่อไปเราจะขยายคลาสนามธรรม MediaLoadCommandCallback ซึ่งจะลบล้างเมธอด onLoad() ที่ MediaManager เรียกใช้ เมธอดนี้จะรับข้อมูลของคำขอโหลดและแปลงเป็นออบเจ็กต์ Movie เมื่อแปลงแล้ว ภาพยนตร์จะเล่นโดยโปรแกรมเล่นในเครื่อง จากนั้น MediaManager จะอัปเดตด้วย MediaLoadRequest และออกอากาศ MediaStatus ไปยังผู้ส่งที่เชื่อมต่ออยู่ สร้างคลาสส่วนตัวที่ฝังอยู่ชื่อ MyMediaLoadCommandCallback ในไฟล์ PlaybackVideoFragment.kt

import com.google.android.gms.cast.MediaLoadRequestData
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaError
import com.google.android.gms.cast.tv.media.MediaException
import com.google.android.gms.cast.tv.media.MediaCommandCallback
import com.google.android.gms.cast.tv.media.QueueUpdateRequestData
import com.google.android.gms.cast.tv.media.MediaLoadCommandCallback
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import android.widget.Toast
...

private inner class MyMediaLoadCommandCallback :  MediaLoadCommandCallback() {
    override fun onLoad(
        senderId: String?, mediaLoadRequestData: MediaLoadRequestData): Task<MediaLoadRequestData> {
        Toast.makeText(activity, "onLoad()", Toast.LENGTH_SHORT).show()
        return if (mediaLoadRequestData == null) {
            // Throw MediaException to indicate load failure.
            Tasks.forException(MediaException(
                MediaError.Builder()
                    .setDetailedErrorCode(MediaError.DetailedErrorCode.LOAD_FAILED)
                    .setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
                    .build()))
        } else Tasks.call {
            play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
            // Update media metadata and state
            val mediaManager = castReceiverContext!!.mediaManager
            mediaManager.setDataFromLoad(mediaLoadRequestData)
            mediaLoadRequestData
        }
    }
}

private fun convertLoadRequestToMovie(mediaLoadRequestData: MediaLoadRequestData?): Movie? {
    if (mediaLoadRequestData == null) {
        return null
    }
    val mediaInfo: MediaInfo = mediaLoadRequestData.getMediaInfo() ?: return null
    var videoUrl: String = mediaInfo.getContentId()
    if (mediaInfo.getContentUrl() != null) {
        videoUrl = mediaInfo.getContentUrl()
    }
    val metadata: MediaMetadata = mediaInfo.getMetadata()
    val movie = Movie()
    movie.videoUrl = videoUrl
    movie.title = metadata?.getString(MediaMetadata.KEY_TITLE)
    movie.description = metadata?.getString(MediaMetadata.KEY_SUBTITLE)
    if(metadata?.hasImages() == true) {
        movie.cardImageUrl = metadata.images[0].url.toString()
    }
    return movie
}

เมื่อกำหนด Callback แล้ว เราจำเป็นต้องลงทะเบียน Callback กับ MediaManager ต้องลงทะเบียน Callback ก่อนที่จะเรียกใช้ MediaManager.onNewIntent() เพิ่ม setMediaLoadCommandCallback เมื่อเริ่มต้นผู้เล่น

private fun initializePlayer() {
    if (mPlayer == null) {
        ...
        mMediaSession = MediaSessionCompat(getContext(), LOG_TAG)
        ...
        castReceiverContext = CastReceiverContext.getInstance()
        if (castReceiverContext != null) {
            val mediaManager: MediaManager = castReceiverContext.getMediaManager()
            mediaManager.setSessionCompatToken(mMediaSession.getSessionToken())
            mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback())
        }
    }
}

มาเรียกใช้แอปตัวอย่างกัน

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

7. การรองรับคำสั่งควบคุมการแคสต์

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

เพิ่ม MyMediaCommandCallback ลงในอินสแตนซ์ MediaManager โดยใช้ setMediaCommandCallback เมื่อเริ่มต้นโปรแกรมเล่น

private fun initializePlayer() {
    ...
    castReceiverContext = CastReceiverContext.getInstance()
    if (castReceiverContext != null) {
        val mediaManager = castReceiverContext!!.mediaManager
        ...
        mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
    }
}

สร้างคลาส MyMediaCommandCallback เพื่อลบล้างเมธอด เช่น onQueueUpdate() เพื่อรองรับคำสั่งควบคุม Cast ดังต่อไปนี้

private inner class MyMediaCommandCallback : MediaCommandCallback() {
    override fun onQueueUpdate(
        senderId: String?,
        queueUpdateRequestData: QueueUpdateRequestData
    ): Task<Void> {
        Toast.makeText(getActivity(), "onQueueUpdate()", Toast.LENGTH_SHORT).show()
        // Queue Prev / Next
        if (queueUpdateRequestData.getJump() != null) {
            Toast.makeText(
                getActivity(),
                "onQueueUpdate(): Jump = " + queueUpdateRequestData.getJump(),
                Toast.LENGTH_SHORT
            ).show()
        }
        return super.onQueueUpdate(senderId, queueUpdateRequestData)
    }
}

8. การทำงานกับสถานะสื่อ

การแก้ไขสถานะสื่อ

Cast Connect จะได้รับสถานะสื่อพื้นฐานจากเซสชันสื่อ หากต้องการรองรับฟีเจอร์ขั้นสูง แอป Android TV สามารถระบุและลบล้างพร็อพเพอร์ตี้สถานะเพิ่มเติมผ่าน MediaStatusModifier MediaStatusModifier จะทำงานกับ MediaSession ที่คุณตั้งค่าไว้ใน CastReceiverContext เสมอ

ตัวอย่างเช่น หากต้องการระบุ setMediaCommandSupported เมื่อทริกเกอร์การเรียกกลับ onLoad ให้ทำดังนี้

import com.google.android.gms.cast.MediaStatus
...
private class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
    fun onLoad(
        senderId: String?,
        mediaLoadRequestData: MediaLoadRequestData
    ): Task<MediaLoadRequestData> {
        Toast.makeText(getActivity(), "onLoad()", Toast.LENGTH_SHORT).show()
        ...
        return Tasks.call({
            play(convertLoadRequestToMovie(mediaLoadRequestData)!!)
            ...
            // Use MediaStatusModifier to provide additional information for Cast senders.
            mediaManager.getMediaStatusModifier()
                .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT, true)
                .setIsPlayingAd(false)
            mediaManager.broadcastMediaStatus()
            // Return the resolved MediaLoadRequestData to indicate load success.
            mediaLoadRequestData
        })
    }
}

การสกัดกั้น MediaStatus ก่อนส่ง

คุณสามารถระบุ MediaStatusWriter ใน MediaManager เพื่อทำการแก้ไขเพิ่มเติมใน MediaStatus ก่อนที่จะออกอากาศไปยังผู้ส่งที่เชื่อมต่อได้ ซึ่งคล้ายกับ MessageInterceptor ของ Web Receiver SDK

ตัวอย่างเช่น คุณสามารถตั้งค่าข้อมูลที่กําหนดเองใน MediaStatus ก่อนส่งไปยังผู้ส่งที่ใช้อุปกรณ์เคลื่อนที่ได้ ดังนี้

import com.google.android.gms.cast.tv.media.MediaManager.MediaStatusInterceptor
import com.google.android.gms.cast.tv.media.MediaStatusWriter
import org.json.JSONObject
import org.json.JSONException
...

private fun initializePlayer() {
    if (mPlayer == null) {
        ...
        if (castReceiverContext != null) {
            ...
            val mediaManager: MediaManager = castReceiverContext.getMediaManager()
            ...
            // Use MediaStatusInterceptor to process the MediaStatus before sending out.
            mediaManager.setMediaStatusInterceptor(
                MediaStatusInterceptor { mediaStatusWriter: MediaStatusWriter ->
                    try {
                        mediaStatusWriter.setCustomData(JSONObject("{myData: 'CustomData'}"))
                    } catch (e: JSONException) {
                        Log.e(LOG_TAG,e.message,e);
                    }
            })
        }
    }
}        

9. ขอแสดงความยินดี

ตอนนี้คุณทราบวิธีเปิดใช้แอป Android TV โดยใช้ไลบรารี Cast Connect แล้ว

ดูรายละเอียดเพิ่มเติมได้ที่คู่มือนักพัฒนาแอป /cast/docs/android_tv_receiver