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

1. ภาพรวม

โลโก้ Google Cast

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

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

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

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

สิ่งที่ต้องมี

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

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

และคลายการบีบอัดไฟล์ ZIP ที่ดาวน์โหลด

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

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

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

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

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

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

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

ติดตั้งแอปผู้ส่งของ Android

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

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

รูปภาพหน้าต่างเทอร์มินัลที่เรียกใช้คำสั่งการติดตั้ง adb เพื่อติดตั้ง 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 ที่เลือก

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

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

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

MainActivity

กิจกรรมนี้มี Fragment (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. การกำหนดค่าการสนับสนุน 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 ให้เลือกกิจกรรมที่คุณต้องการเปิด ใน Codelab นี้ เราจะเปิดตัว MainActivity ของแอปเมื่อเริ่มเซสชันการแคสต์ ในไฟล์ AndroidManifest.xml ให้เพิ่มตัวกรองความตั้งใจในการเปิดใช้ใน 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 เนื่องจากไม่มีการใช้งานเล่น คุณควรตั้งค่าโทเค็นค่า Null ใน MediaManager

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

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

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

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

ระบบจะส่งคำสั่งโหลดผ่าน Intent ที่มีชื่อแพ็กเกจที่คุณกำหนดใน Developer Console คุณต้องเพิ่มตัวกรอง 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
}

เมื่อกำหนดโค้ดเรียกกลับแล้ว เราต้องลงทะเบียนลงใน MediaManager ต้องลงทะเบียนโค้ดเรียกกลับก่อนเรียกใช้ 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 จากผู้ส่ง ให้คลิกปุ่ม "แคสต์" ไอคอนปุ่ม &quot;แคสต์&quot; แล้วเลือกอุปกรณ์ 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 Callback ให้ทำดังนี้

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
        })
    }
}

สกัดกั้นสถานะสื่อก่อนส่งออกไป

คุณระบุ MediaStatusWriter ใน MediaManager เพื่อดำเนินการแก้ไขเพิ่มเติมกับ MediaStatus ก่อนที่จะประกาศไปยังผู้ส่งที่เชื่อมต่อได้เช่นเดียวกับ MessageInterceptor ของ 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