เปิดใช้แอป 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ภาพหน้าจอ Android TV ที่แสดง "Chromecast Built-In" หน้าจอ หมายเลขเวอร์ชัน และหมายเลขซีเรียล

หากไม่ได้ลงทะเบียน 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. คลิกไฟล์ > โปรเจ็กต์การซิงค์ของ Android App Studio พร้อมปุ่ม Gradle ซิงค์โปรเจ็กต์กับไฟล์ Gradle
  4. เปิดใช้ตัวเลือกสำหรับนักพัฒนาแอปและการแก้ไขข้อบกพร่อง USB ในอุปกรณ์ Android TV
  5. ADB เชื่อมต่อกับอุปกรณ์ Android TV ของคุณ อุปกรณ์ควรแสดงใน Android Studio รูปภาพแสดงอุปกรณ์ Android TV ที่ปรากฏในแถบเครื่องมือ Android Studio
  6. คลิกปุ่มปุ่ม Android Studio 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. คลิกไฟล์ > โปรเจ็กต์การซิงค์ของ Android Studio พร้อมปุ่ม Gradle ซิงค์โปรเจ็กต์กับไฟล์ Gradle
  4. เลือกอุปกรณ์ ATV และคลิกปุ่ม ปุ่ม Run ของ Android Studio รูปสามเหลี่ยมสีเขียวที่ชี้ไปทางขวาRun เพื่อเรียกใช้แอปและสำรวจ UI แถบเครื่องมือ Android Studio แสดงอุปกรณ์ Android TV ที่เลือก

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

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

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

กิจกรรมหลัก

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

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

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

PlaybackActivity

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

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

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

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

5. การกำหนดค่าการรองรับการแคสต์

การอ้างอิง

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

เมื่อสร้าง 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 เนื่องจากไม่มีการใช้งาน คุณควรตั้งค่าโทเค็นที่เป็นค่าว่างใน MediaManager ดังนี้

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

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

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

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

คำสั่งโหลดจะส่งผ่าน Intent ที่มีชื่อแพ็กเกจที่คุณระบุใน Developer Console คุณต้องเพิ่มตัวกรอง Intent ที่กำหนดไว้ล่วงหน้าต่อไปนี้ในแอป Android TV เพื่อระบุกิจกรรมเป้าหมายที่จะได้รับ Intent นี้ ในไฟล์ AndroidManifest.xml ให้เพิ่มตัวกรองความตั้งใจในการโหลดไปยัง 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 ที่เข้ามาใหม่ ในการจัดการคำขอโหลด เราจะแก้ไขเมธอดนี้และส่งความตั้งใจไปประมวลผลเพิ่มเติมโดยเรียกใช้เมธอด 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())
        }
    }
}

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

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

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

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

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

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

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

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 เมื่อมีการทริกเกอร์ Callback 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