สร้างแอปสื่อสำหรับรถยนต์

Android Auto และ Android Automotive OS ช่วยให้คุณนำเนื้อหาแอปสื่อไปให้ผู้ใช้ในรถยนต์ได้

การสร้างแอปสื่อสำหรับรถยนต์ทำได้ 2 วิธี

  • คู่มือนี้จะอธิบายวิธีใช้ MediaBrowserService และ MediaSession เพื่อสร้างแอปที่ Android Auto และ Android Automotive OS สามารถเชื่อมต่อได้ เพื่อแสดงผลมุมมองการเรียกดูและการเล่นสื่อที่ได้รับการเพิ่มประสิทธิภาพสำหรับการใช้งานในรถยนต์

  • นอกจากนี้ คุณยังสร้างแอปสื่อโดยใช้เทมเพลตคลังแอปในรถยนต์เพื่อ การจัดรูปแบบที่ปรับแต่งได้ ความสามารถในการเรียกดู และการดำเนินการที่กำหนดเองเพิ่มเติมได้ด้วย ดูรายละเอียดการติดตั้งได้ที่ สร้างแอปสื่อที่ใช้เทมเพลต ปัจจุบันแอปสื่อที่ใช้เทมเพลต รองรับเฉพาะใน Android Auto

คู่มือนี้อธิบายคอมโพเนนต์ที่จำเป็นของ MediaBrowserService และ MediaSession ที่แอปของคุณต้องใช้เพื่อให้ทำงานบน Android Auto หรือ Android Automotive OS ได้ หลังจากสร้างโครงสร้างพื้นฐานของสื่อหลักเสร็จแล้ว คุณจะเพิ่มการรองรับ Android Auto และเพิ่มการรองรับ Android Automotive OS ลงในแอปสื่อได้

คู่มือนี้ถือว่าคุณมีแอปสื่อที่เล่นเสียงในโทรศัพท์อยู่แล้ว และแอปสื่อของคุณเป็นไปตามสถาปัตยกรรมแอปสื่อของ Android

ก่อนเริ่มต้น

  1. อ่านเอกสารประกอบเกี่ยวกับ Android Media API
  2. ดูคำแนะนำด้านการออกแบบได้ที่สร้างแอปสื่อ
  3. โปรดอ่านข้อกำหนดและแนวคิดที่สำคัญซึ่งระบุไว้ในส่วนนี้

คำและแนวคิดสำคัญ

บริการเบราว์เซอร์สื่อ
บริการ Android ที่แอปสื่อของคุณใช้ซึ่งเป็นไปตาม MediaBrowserServiceCompat API แอปของคุณใช้บริการนี้เพื่อแสดงเนื้อหา
เบราว์เซอร์สื่อ
API ที่แอปสื่อใช้เพื่อค้นหาบริการเบราว์เซอร์สื่อและแสดง เนื้อหาของตน Android Auto และ Android Automotive OS ใช้ MediaBrowser เพื่อ ค้นหาบริการ MediaBrowser ของแอป
รายการสื่อ

เบราว์เซอร์สื่อจะจัดระเบียบเนื้อหาในโครงสร้างแบบต้นไม้ของออบเจ็กต์ MediaItem รายการสื่ออาจมีแฟล็กต่อไปนี้อย่างใดอย่างหนึ่งหรือทั้ง 2 อย่าง

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

รายการสื่อที่ทั้งเรียกดูและเล่นได้จะทำงานเหมือนเพลย์ลิสต์ คุณสามารถ เลือกรายการนั้นเพื่อเล่นรายการย่อยทั้งหมด หรือจะเรียกดูรายการย่อยของรายการนั้นก็ได้

เพิ่มประสิทธิภาพสำหรับยานพาหนะ

กิจกรรมสำหรับแอป Android Automotive OS ที่เป็นไปตาม หลักเกณฑ์การออกแบบ Android Automotive OS Android Automotive OS ไม่ได้วาดอินเทอร์เฟซสำหรับกิจกรรมเหล่านี้ ดังนั้นคุณ ต้องตรวจสอบว่าแอปของคุณเป็นไปตามหลักเกณฑ์การออกแบบ โดยปกติแล้ว การปรับปรุงนี้จะ รวมถึงเป้าหมายการแตะและขนาดแบบอักษรที่ใหญ่ขึ้น การรองรับโหมดกลางวันและกลางคืน และ อัตราส่วนคอนทราสต์ที่สูงขึ้น

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

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

กำหนดค่าไฟล์ Manifest ของแอป

คุณต้องกำหนดค่าไฟล์ Manifest ของแอปก่อนจึงจะสร้างบริการเบราว์เซอร์สื่อได้

ประกาศบริการเบราว์เซอร์สื่อ

ทั้ง Android Auto และ Android Automotive OS จะเชื่อมต่อกับแอปของคุณผ่าน บริการเบราว์เซอร์สื่อเพื่อเรียกดูรายการสื่อ ประกาศบริการ Media Browser ในไฟล์ Manifest เพื่อให้ Android Auto และ Android Automotive OS ค้นพบบริการและเชื่อมต่อกับแอปของคุณ

ข้อมูลโค้ดต่อไปนี้แสดงวิธีประกาศบริการเบราว์เซอร์สื่อใน ไฟล์ Manifest ใส่โค้ดนี้ในไฟล์ Manifest สำหรับโมดูล Android Automotive OS และในไฟล์ Manifest สำหรับแอปโทรศัพท์

<application>
    ...
    <service android:name=".MyMediaBrowserService"
             android:exported="true">
        <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService"/>
        </intent-filter>
    </service>
    ...
</application>

ระบุไอคอนแอป

คุณต้องระบุไอคอนแอปที่ Android Auto และ Android Automotive OS ใช้เพื่อแสดงแอปใน UI ของระบบได้ คุณต้องระบุไอคอน 2 ประเภท ได้แก่

  • ไอคอน Launcher
  • ไอคอนการระบุแหล่งที่มา

ไอคอน Launcher

ไอคอน Launcher แสดงแอปของคุณใน UI ของระบบ เช่น ใน Launcher และในถาดไอคอน คุณระบุได้ว่าต้องการใช้ไอคอนจาก แอปบนอุปกรณ์เคลื่อนที่เพื่อแสดงแอปสื่อในรถยนต์โดยใช้การประกาศในไฟล์ Manifest ต่อไปนี้

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

หากต้องการใช้ไอคอนอื่นที่ไม่ใช่ของแอปบนอุปกรณ์เคลื่อนที่ ให้ตั้งค่าพร็อพเพอร์ตี้ android:icon ในองค์ประกอบ <service> ของบริการเบราว์เซอร์สื่อในไฟล์ Manifest ดังนี้

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

ไอคอนการระบุแหล่งที่มา

รูปที่ 1 ไอคอนการระบุแหล่งที่มาในการ์ดสื่อ

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

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

สร้างบริการเบราว์เซอร์สื่อ

คุณสร้างบริการเบราว์เซอร์สื่อได้โดยการขยายคลาส MediaBrowserServiceCompat จากนั้นทั้ง Android Auto และ Android Automotive OS จะใช้บริการของคุณเพื่อทำสิ่งต่อไปนี้ได้

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

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

เวิร์กโฟลว์บริการเบราว์เซอร์สื่อ

ส่วนนี้อธิบายวิธีที่ Android Automotive OS และ Android Auto โต้ตอบกับบริการเบราว์เซอร์สื่อในเวิร์กโฟลว์ของผู้ใช้ทั่วไป

  1. ผู้ใช้เปิดแอปของคุณใน Android Automotive OS หรือ Android Auto
  2. Android Automotive OS หรือ Android Auto จะติดต่อบริการเบราว์เซอร์สื่อของแอปโดยใช้เมธอด onCreate() ในการติดตั้งใช้งานเมธอด onCreate() คุณต้องสร้างและลงทะเบียนออบเจ็กต์ MediaSessionCompat และออบเจ็กต์การเรียกกลับ
  3. Android Automotive OS หรือ Android Auto จะเรียกใช้เมธอด onGetRoot() ของบริการเพื่อรับรายการสื่อรูทในลำดับชั้นของเนื้อหา ระบบจะไม่แสดงรายการสื่อรูท แต่จะใช้เพื่อดึงเนื้อหาเพิ่มเติมจากแอปแทน
  4. Android Automotive OS หรือ Android Auto จะเรียกใช้เมธอด onLoadChildren() ของบริการเพื่อรับรายการสื่อย่อยของรายการสื่อรูท Android Automotive OS และ Android Auto จะแสดงรายการสื่อเหล่านี้เป็นรายการเนื้อหาระดับบนสุด ดูจัดโครงสร้างเมนูรูทในหน้านี้เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่ระบบคาดหวังในระดับนี้
  5. หากผู้ใช้เลือกรายการสื่อที่เรียกดูได้ ระบบจะเรียกใช้ onLoadChildren() เมธอดของบริการอีกครั้งเพื่อดึงข้อมูลรายการเมนูย่อยของรายการที่เลือก
  6. หากผู้ใช้เลือกรายการสื่อที่เล่นได้ Android Automotive OS หรือ Android Auto จะเรียกใช้เมธอดการเรียกกลับของเซสชันสื่อที่เหมาะสมเพื่อดำเนินการดังกล่าว
  7. หากแอปของคุณรองรับ ผู้ใช้จะค้นหาเนื้อหาของคุณได้ด้วย ในกรณีนี้ Android Automotive OS หรือ Android Auto จะเรียกใช้เมธอด onSearch() ของบริการ

สร้างลำดับชั้นของเนื้อหา

Android Auto และ Android Automotive OS จะเรียกใช้บริการ MediaBrowser ของแอปเพื่อ ดูว่ามีเนื้อหาใดบ้าง คุณต้องใช้ 2 วิธีในบริการเบราว์เซอร์สื่อเพื่อรองรับฟีเจอร์นี้ ได้แก่ onGetRoot() และ onLoadChildren()

ใช้งาน onGetRoot

เมธอด onGetRoot() ของบริการจะแสดงข้อมูลเกี่ยวกับโหนดรูทของลำดับชั้นเนื้อหา Android Auto และ Android Automotive OS ใช้โหนดรูทนี้เพื่อขอเนื้อหาที่เหลือ โดยใช้วิธี onLoadChildren()

ข้อมูลโค้ดต่อไปนี้แสดงการใช้งานเมธอด onGetRoot() อย่างง่าย

Kotlin

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? =
    // Verify that the specified package is allowed to access your
    // content. You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        null
    } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {

    // Verify that the specified package is allowed to access your
    // content. You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        return null;
    }

    return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null);
}

ดูตัวอย่างโดยละเอียดเพิ่มเติมของวิธีการนี้ได้ที่เมธอด onGetRoot() ในแอปตัวอย่าง Universal Android Music Player ใน GitHub

เพิ่มการตรวจสอบแพ็กเกจสำหรับ onGetRoot()

เมื่อมีการโทรไปยังเมธอด onGetRoot() ของบริการ แพ็กเกจที่เรียกจะส่งข้อมูลระบุตัวตนไปยังบริการของคุณ บริการของคุณสามารถใช้ข้อมูลนี้เพื่อพิจารณาว่าแพ็กเกจดังกล่าวเข้าถึงเนื้อหาของคุณได้หรือไม่ เช่น คุณสามารถจำกัดสิทธิ์เข้าถึงเนื้อหาของแอปให้เฉพาะรายการแพ็กเกจที่ได้รับอนุมัติได้โดยการเปรียบเทียบ clientPackageName กับรายการที่อนุญาตและยืนยันใบรับรองที่ใช้ลงนามใน APK ของแพ็กเกจ หากยืนยันแพ็กเกจไม่ได้ ให้กลับไปที่ null เพื่อปฏิเสธการเข้าถึงเนื้อหา

หากต้องการให้แอประบบ เช่น Android Auto และ Android Automotive OS เข้าถึงเนื้อหาของคุณได้ บริการของคุณต้องแสดงผล BrowserRoot ที่ไม่ใช่ Null BrowserRoot เสมอเมื่อแอประบบเหล่านี้เรียกใช้เมธอด onGetRoot() ลายเซ็นของแอปของระบบ Android Automotive OS อาจแตกต่างกันไปตามยี่ห้อและรุ่นของรถยนต์ ดังนั้นคุณต้องอนุญาตการเชื่อมต่อจากแอปของระบบทั้งหมดเพื่อรองรับ Android Automotive OS อย่างมีประสิทธิภาพ

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

fun isKnownCaller(
    callingPackage: String,
    callingUid: Int
): Boolean {
    ...
    val isCallerKnown = when {
       // If the system is making the call, allow it.
       callingUid == Process.SYSTEM_UID -> true
       // If the app was signed by the same certificate as the platform
       // itself, also allow it.
       callerSignature == platformSignature -> true
       // ... more cases
    }
    return isCallerKnown
}

ข้อมูลโค้ดนี้เป็นข้อความที่คัดลอกมาจากคลาส PackageValidator ในแอปตัวอย่าง Universal Android Music Player บน GitHub ดูคลาสดังกล่าว เพื่อดูตัวอย่างโดยละเอียดเพิ่มเติมเกี่ยวกับวิธีใช้การตรวจสอบแพ็กเกจสำหรับonGetRoot() ของบริการ

นอกเหนือจากการอนุญาตแอปของระบบแล้ว คุณต้องอนุญาตให้ Google Assistant เชื่อมต่อกับ MediaBrowserService ด้วย โปรดทราบว่า Google Assistant มีชื่อแพ็กเกจแยกกัน สำหรับโทรศัพท์ ซึ่งรวมถึง Android Auto และสำหรับ Android Automotive OS

ใช้ onLoadChildren()

หลังจากได้รับออบเจ็กต์โหนดรูทแล้ว Android Auto และ Android Automotive OS จะสร้างเมนูระดับบนสุดโดยเรียกใช้ onLoadChildren() ในออบเจ็กต์โหนดรูทเพื่อรับออบเจ็กต์ย่อย แอปไคลเอ็นต์สร้างเมนูย่อยโดย เรียกใช้เมธอดเดียวกันนี้โดยใช้ออบเจ็กต์โหนดลูก

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

ข้อมูลโค้ดต่อไปนี้แสดงการใช้งานonLoadChildren() เมธอดอย่างง่าย

Kotlin

override fun onLoadChildren(
    parentMediaId: String,
    result: Result<List<MediaBrowserCompat.MediaItem>>
) {
    // Assume for example that the music catalog is already loaded/cached.

    val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()

    // Check whether this is the root menu:
    if (MY_MEDIA_ROOT_ID == parentMediaId) {

        // Build the MediaItem objects for the top level
        // and put them in the mediaItems list.
    } else {

        // Examine the passed parentMediaId to see which submenu we're at
        // and put the children of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems)
}

Java

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaBrowserCompat.MediaItem>> result) {

    // Assume for example that the music catalog is already loaded/cached.

    List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();

    // Check whether this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {

        // Build the MediaItem objects for the top level
        // and put them in the mediaItems list.
    } else {

        // Examine the passed parentMediaId to see which submenu we're at
        // and put the children of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems);
}

ดูตัวอย่างที่สมบูรณ์ของเมธอดนี้ได้ในเมธอด onLoadChildren() ในแอปตัวอย่าง Universal Android Music Player บน GitHub

สร้างโครงสร้างเมนูหลัก

รูปที่ 2 เนื้อหาระดับบนสุดจะแสดงเป็นแท็บการนำทาง

Android Auto และ Android Automotive OS มีข้อจำกัดเฉพาะเกี่ยวกับ โครงสร้างของเมนูหลัก โดยจะสื่อสารกับ MediaBrowserService ผ่านคำแนะนำรูท ซึ่งอ่านได้ผ่านอาร์กิวเมนต์ Bundle ที่ส่งไปยัง onGetRoot() การทำตามคำแนะนำเหล่านี้จะช่วยให้ระบบแสดงเนื้อหาระดับบนสุดได้อย่างเหมาะสมที่สุด ในรูปแบบแท็บการนำทาง หากคุณไม่ปฏิบัติตามคำแนะนำเหล่านี้ ระบบอาจไม่แสดงเนื้อหาหลักบางส่วนหรือทำให้ค้นพบได้ยากขึ้น ระบบจะส่งคำใบ้ 2 รายการ ดังนี้

ใช้โค้ดต่อไปนี้เพื่ออ่านคำแนะนำรูทที่เกี่ยวข้อง

Kotlin

import androidx.media.utils.MediaConstants

// Later, in your MediaBrowserServiceCompat.
override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle
): BrowserRoot {

  val maximumRootChildLimit = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
      /* defaultValue= */ 4)
  val supportedRootChildFlags = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
      /* defaultValue= */ MediaItem.FLAG_BROWSABLE)

  // Rest of method...
}

Java

import androidx.media.utils.MediaConstants;

// Later, in your MediaBrowserServiceCompat.
@Override
public BrowserRoot onGetRoot(
    String clientPackageName, int clientUid, Bundle rootHints) {

    int maximumRootChildLimit = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
        /* defaultValue= */ 4);
    int supportedRootChildFlags = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
        /* defaultValue= */ MediaItem.FLAG_BROWSABLE);

    // Rest of method...
}

คุณสามารถเลือกแยกตรรกะสำหรับโครงสร้างของลำดับชั้นเนื้อหา ตามค่าของคำแนะนำเหล่านี้ได้ โดยเฉพาะอย่างยิ่งหากลำดับชั้นแตกต่างกันในMediaBrowserการผสานรวมภายนอก Android Auto และ Android Automotive OS เช่น หากปกติคุณแสดงไอเทมที่เล่นได้ระดับรูท คุณอาจต้องการซ้อนไอเทมดังกล่าว ไว้ใต้ไอเทมที่เรียกดูได้ระดับรูทแทน เนื่องจากค่าของแฟล็กที่รองรับ คำแนะนำ

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

  • ระบุไอคอนโมโนโครม (สีเดียว) โดยควรเป็นสีขาวสำหรับรายการแท็บแต่ละรายการ
  • ระบุป้ายกำกับที่สั้นแต่มีความหมายสำหรับรายการแท็บแต่ละรายการ การใช้ป้ายกำกับแบบสั้น จะช่วยลดโอกาสที่สตริงจะถูกตัดทอน

ภาพปกสื่อที่แสดง

ต้องส่งอาร์ตเวิร์กสำหรับรายการสื่อเป็น URI ในเครื่องโดยใช้ ContentResolver.SCHEME_CONTENT หรือ ContentResolver.SCHEME_ANDROID_RESOURCE URI ในเครื่องนี้ต้องแปลงเป็นบิตแมปหรือเวกเตอร์ Drawable ในทรัพยากรของแอปพลิเคชัน สำหรับออบเจ็กต์ MediaDescriptionCompat ที่แสดงถึงรายการใน ลำดับชั้นของเนื้อหา ให้ส่ง URI ผ่าน setIconUri() สำหรับออบเจ็กต์ MediaMetadataCompat ที่แสดงถึงรายการที่กำลังเล่น ให้ส่ง URI ผ่าน putString() โดยใช้คีย์ต่อไปนี้

ขั้นตอนต่อไปนี้อธิบายวิธีดาวน์โหลดอาร์ตเวิร์กจาก URI ของเว็บและแสดงผ่าน URI ในเครื่อง ดูตัวอย่างที่สมบูรณ์ยิ่งขึ้นได้ในการติดตั้งใช้งาน openFile() และเมธอดโดยรอบในแอปตัวอย่าง Universal Android Music Player

  1. สร้าง content:// URI ที่สอดคล้องกับ URI ของเว็บ บริการเบราว์เซอร์สื่อ และเซสชันสื่อจะส่ง URI ของเนื้อหานี้ไปยัง Android Auto และ Android Automotive OS

    Kotlin

    fun Uri.asAlbumArtContentURI(): Uri {
      return Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(this.getPath()) // Make sure you trust the URI
        .build()
    }

    Java

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
  2. ในการติดตั้งใช้งาน ContentProvider.openFile() ให้ตรวจสอบว่ามีไฟล์ สำหรับ URI ที่เกี่ยวข้องหรือไม่ หากไม่มี ให้ดาวน์โหลดและแคชไฟล์รูปภาพ ข้อมูลโค้ดต่อไปนี้ใช้ Glide

    Kotlin

    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
      val context = this.context ?: return null
      val file = File(context.cacheDir, uri.path)
      if (!file.exists()) {
        val remoteUri = Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.path)
            .build()
        val cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
    
        cacheFile.renameTo(file)
        file = cacheFile
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
    }

    Java

    @Nullable
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
      Context context = this.getContext();
      File file = new File(context.getCacheDir(), uri.getPath());
      if (!file.exists()) {
        Uri remoteUri = new Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.getPath())
            .build();
        File cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    
        cacheFile.renameTo(file);
        file = cacheFile;
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }

ดูรายละเอียดเพิ่มเติมเกี่ยวกับผู้ให้บริการเนื้อหาได้ที่การสร้างผู้ให้บริการเนื้อหา

ใช้รูปแบบเนื้อหา

หลังจากสร้างลำดับชั้นของเนื้อหาโดยใช้รายการที่เรียกดูหรือเล่นได้แล้ว คุณ สามารถใช้รูปแบบเนื้อหาที่กำหนดวิธีแสดงรายการเหล่านั้นในรถยนต์ได้

คุณใช้รูปแบบเนื้อหาต่อไปนี้ได้

รายการข้อมูล

รูปแบบเนื้อหานี้จะให้ความสำคัญกับชื่อและข้อมูลเมตามากกว่ารูปภาพ

รายการตารางกริด

รูปแบบเนื้อหานี้ให้ความสำคัญกับรูปภาพมากกว่าชื่อและข้อมูลเมตา

ตั้งค่ารูปแบบเนื้อหาเริ่มต้น

คุณตั้งค่าเริ่มต้นส่วนกลางสำหรับวิธีแสดงรายการสื่อได้โดยใส่ค่าคงที่บางอย่างในBrowserRootกลุ่มพิเศษของเมธอด onGetRoot() ของบริการ Android Auto และ Android Automotive OS จะอ่านชุดข้อมูลนี้และค้นหาค่าคงที่เหล่านั้นเพื่อกำหนดสไตล์ที่เหมาะสม

คุณใช้รายการต่อไปนี้เป็นคีย์ในแพ็กเกจได้

  • DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE: ระบุคำแนะนำในการนำเสนอสำหรับรายการที่เรียกดูได้ทั้งหมดภายในโครงสร้างการเรียกดู
  • DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE: ระบุคำแนะนำในการนำเสนอสำหรับรายการที่เล่นได้ทั้งหมดภายในโครงสร้างการเรียกดู

คีย์สามารถแมปกับค่าคงที่จำนวนเต็มต่อไปนี้เพื่อส่งผลต่อ การนำเสนอรายการเหล่านั้น

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: รายการที่เกี่ยวข้องจะแสดงเป็นรายการ
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM รายการที่เกี่ยวข้องจะแสดงเป็นรายการในตารางกริด
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: รายการที่เกี่ยวข้องจะแสดงเป็นรายการ "หมวดหมู่" ซึ่งเหมือนกับรายการธรรมดา เพียงแต่มีการใช้ระยะขอบรอบๆ ไอคอนของรายการ เนื่องจากไอคอนจะดูดีกว่าเมื่อมีขนาดเล็ก ไอคอน ต้องเป็น Vector Drawable ที่ปรับสีได้ คาดว่าคำใบ้นี้จะแสดงเฉพาะ สำหรับรายการที่เรียกดูได้เท่านั้น
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: รายการที่เกี่ยวข้องจะแสดงเป็นรายการตารางกริด "หมวดหมู่" ซึ่งเหมือนกับรายการกริดทั่วไป เพียงแต่มีการใช้ระยะขอบรอบๆ ไอคอนของรายการ เนื่องจากไอคอนจะดูดีกว่าเมื่อมีขนาดเล็ก ไอคอน ต้องเป็น Vector Drawable ที่ปรับสีได้ คาดว่าคำใบ้นี้จะแสดงเฉพาะ สำหรับรายการที่เรียกดูได้เท่านั้น

ข้อมูลโค้ดต่อไปนี้แสดงวิธีตั้งค่ารูปแบบเนื้อหาเริ่มต้นสำหรับ รายการที่เรียกดูได้เป็นตารางกริด และรายการที่เล่นได้เป็นรายการ

Kotlin

import androidx.media.utils.MediaConstants

@Nullable
override fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    return BrowserRoot(ROOT_ID, extras)
}

Java

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    return new BrowserRoot(ROOT_ID, extras);
}

ตั้งค่ารูปแบบเนื้อหาต่อรายการ

Content Style API ช่วยให้คุณลบล้างรูปแบบเนื้อหาเริ่มต้นสำหรับรายการสื่อที่เรียกดูได้ ของรายการย่อย รวมถึงรายการสื่อเอง

หากต้องการลบล้างค่าเริ่มต้นสำหรับรายการย่อยของรายการสื่อที่เรียกดูได้ ให้สร้าง แพ็กเกจเนื้อหาพิเศษใน MediaDescription ของรายการสื่อ แล้วเพิ่มคำแนะนำเดียวกันกับที่ กล่าวถึงก่อนหน้านี้ DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE จะมีผลกับรายการย่อยที่เล่นได้ของรายการนั้น ส่วน DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLEจะมีผลกับรายการย่อยที่เรียกดูได้ของรายการนั้น

หากต้องการลบล้างค่าเริ่มต้นสำหรับรายการสื่อรายการนั้น ไม่ใช่รายการย่อย ให้สร้างแพ็กเกจเนื้อหาพิเศษใน MediaDescription ของรายการสื่อ และเพิ่มคำแนะนำที่มีคีย์ DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM ใช้ค่าเดียวกันกับที่อธิบายไว้ก่อนหน้านี้เพื่อระบุการนำเสนอของสินค้า

ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้าง MediaItem ที่เรียกดูได้ซึ่ง ลบล้างรูปแบบเนื้อหาเริ่มต้นสำหรับทั้งตัวมันเองและองค์ประกอบย่อย โดยจะจัดรูปแบบ ตัวเองเป็นรายการหมวดหมู่ รายการย่อยที่เรียกดูได้เป็นรายการ และรายการย่อยที่ เล่นได้เป็นรายการตารางกริด

Kotlin

import androidx.media.utils.MediaConstants

private fun createBrowsableMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createBrowsableMediaItem(
    String mediaId,
    String folderName,
    Uri iconUri) {
    MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
    mediaDescriptionBuilder.setMediaId(mediaId);
    mediaDescriptionBuilder.setTitle(folderName);
    mediaDescriptionBuilder.setIconUri(iconUri);
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    mediaDescriptionBuilder.setExtras(extras);
    return new MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}

จัดกลุ่มรายการโดยใช้คำแนะนำชื่อ

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

ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้าง MediaItem ที่มีส่วนหัวของกลุ่มย่อยเป็น "Songs"

Kotlin

import androidx.media.utils.MediaConstants

private fun createMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putString(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
        "Songs")
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), /* playable or browsable flag*/)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) {
   MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
   mediaDescriptionBuilder.setMediaId(mediaId);
   mediaDescriptionBuilder.setTitle(folderName);
   mediaDescriptionBuilder.setIconUri(iconUri);
   Bundle extras = new Bundle();
   extras.putString(
       MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
       "Songs");
   mediaDescriptionBuilder.setExtras(extras);
   return new MediaBrowser.MediaItem(
       mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
}

แอปของคุณต้องส่งรายการสื่อทั้งหมดที่ต้องการจัดกลุ่มไว้ด้วยกันเป็น บล็อกที่ต่อเนื่องกัน ตัวอย่างเช่น สมมติว่าคุณต้องการแสดงกลุ่มรายการสื่อ 2 กลุ่ม ได้แก่ "เพลง" และ "อัลบั้ม" ตามลำดับ และแอปของคุณส่งรายการสื่อ 5 รายการตามลำดับต่อไปนี้

  1. รายการสื่อ A ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. รายการสื่อ B ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  3. รายการสื่อ C ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. รายการสื่อ D ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  5. รายการสื่อ E ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

เนื่องจากรายการสื่อสำหรับกลุ่ม "เพลง" และกลุ่ม "อัลบั้ม" ไม่ได้อยู่ในบล็อกที่ต่อเนื่องกัน Android Auto และ Android Automotive OS จึงตีความว่ามี 4 กลุ่มดังนี้

  • กลุ่ม 1 ชื่อ "เพลง" ซึ่งมีรายการสื่อ ก
  • กลุ่ม 2 ชื่อ "อัลบั้ม" ซึ่งมีรายการสื่อ B
  • กลุ่ม 3 ชื่อ "เพลง" ซึ่งมีรายการสื่อ C และ D
  • กลุ่ม 4 ชื่อ "อัลบั้ม" ที่มีรายการสื่อ E

หากต้องการแสดงรายการเหล่านี้ใน 2 กลุ่ม แอปของคุณต้องส่งรายการสื่อตามลำดับต่อไปนี้แทน

  1. รายการสื่อ A ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. รายการสื่อ C ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  3. รายการสื่อ D ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. รายการสื่อ B ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  5. รายการสื่อ E ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

แสดงตัวบ่งชี้ข้อมูลเมตาเพิ่มเติม

คุณสามารถรวมตัวบ่งชี้ข้อมูลเมตาเพิ่มเติมเพื่อให้ข้อมูลโดยย่อ สำหรับเนื้อหาในโครงสร้างของเบราว์เซอร์สื่อและระหว่างการเล่น ในโครงสร้างการเรียกดู Android Auto และ Android Automotive OS จะอ่านข้อมูลเพิ่มเติมที่เชื่อมโยงกับรายการและมองหาค่าคงที่บางอย่างเพื่อพิจารณาว่าจะแสดงตัวบ่งชี้ใด ในระหว่างการเล่นสื่อ Android Auto และ Android Automotive OS จะอ่านข้อมูลเมตาสำหรับเซสชันสื่อและค้นหาค่าคงที่บางอย่างเพื่อกำหนดตัวบ่งชี้ที่จะแสดง

รูปที่ 3 มุมมองการเล่นพร้อมข้อมูลเมตาที่ระบุเพลง และศิลปิน รวมถึงไอคอนที่ระบุเนื้อหาไม่เหมาะสม

รูปที่ 4 มุมมองการเรียกดูที่มีจุดสำหรับเนื้อหาที่ยังไม่ได้เล่นใน รายการแรกและแถบความคืบหน้าสำหรับเนื้อหาที่เล่นบางส่วนใน รายการที่ 2

คุณใช้ค่าคงที่ต่อไปนี้ได้ทั้ง MediaItemส่วนขยายคำอธิบายและส่วนขยายMediaMetadata

  • EXTRA_DOWNLOAD_STATUS: แสดงสถานะการดาวน์โหลดของรายการ ใช้ค่าคงที่นี้เป็น คีย์ ค่าคงที่แบบยาวต่อไปนี้คือค่าที่เป็นไปได้
  • METADATA_KEY_IS_EXPLICIT: ระบุว่ารายการมีเนื้อหาโจ่งแจ้งหรือไม่ หากต้องการระบุว่ารายการเป็นเนื้อหา โจ่งแจ้ง ให้ใช้ค่าคงที่นี้เป็นคีย์และค่า METADATA_VALUE_ATTRIBUTE_PRESENT เป็นค่า

ค่าคงที่ต่อไปนี้ใช้ได้เท่านั้นในส่วนเสริมของคำอธิบาย MediaItem

  • DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS: ระบุสถานะการสิ้นสุดของเนื้อหารูปแบบยาว เช่น ตอนของพอดแคสต์หรือ หนังสือเสียง ใช้ค่าคงที่นี้เป็นคีย์ โดยค่าที่เป็นไปได้คือค่าคงที่จำนวนเต็มต่อไปนี้
  • DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE: ระบุจำนวนความคืบหน้าในการดูคอนเทนต์แบบยาวเป็นค่าทศนิยม ระหว่าง 0.0 ถึง 1.0 (รวม) ข้อมูลเพิ่มเติมนี้จะให้ข้อมูลเพิ่มเติมเกี่ยวกับPARTIALLY_PLAYINGสถานะเพื่อให้ Android Auto หรือ Android Automotive OS แสดงตัวบ่งชี้ความคืบหน้าที่มีความหมายมากขึ้น เช่น แถบความคืบหน้า หากคุณใช้ส่วนเสริมนี้ โปรดดูส่วนเกี่ยวกับ การอัปเดตแถบความคืบหน้าในมุมมองการเรียกดูขณะเล่นเนื้อหา ในคำแนะนำนี้เพื่อดูวิธีอัปเดตตัวบ่งชี้นี้หลังจากแสดงผลครั้งแรก

หากต้องการแสดงตัวบ่งชี้ที่ปรากฏขณะที่ผู้ใช้กำลังเรียกดูทรีการเรียกดูสื่อ ให้สร้างชุดข้อมูลเพิ่มเติมที่มีค่าคงที่เหล่านี้อย่างน้อย 1 รายการ แล้ว ส่งชุดข้อมูลดังกล่าวไปยังเมธอด MediaDescription.Builder.setExtras()

ข้อมูลโค้ดต่อไปนี้แสดงวิธีแสดงตัวบ่งชี้สำหรับรายการสื่อที่โจ่งแจ้งหรืออาจไม่เหมาะสมซึ่งดูไปแล้ว 70%

Kotlin

import androidx.media.utils.MediaConstants

val extras = Bundle()
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED)
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

Java

import androidx.media.utils.MediaConstants;

Bundle extras = new Bundle();
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED);
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build();
return new MediaBrowserCompat.MediaItem(description, /* flags */);

หากต้องการแสดงตัวบ่งชี้สำหรับรายการสื่อที่กำลังเล่นอยู่ คุณสามารถ ประกาศค่า Longสำหรับ METADATA_KEY_IS_EXPLICIT หรือ EXTRA_DOWNLOAD_STATUS ใน MediaMetadataCompat ของ mediaSession คุณไม่สามารถแสดงเครื่องหมาย DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUSหรือ DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGEในมุมมองการเล่น

ข้อมูลโค้ดต่อไปนี้แสดงวิธีระบุว่าเพลงที่กำลังเล่นใน มุมมองการเล่นเป็นเพลงที่โจ่งแจ้งและดาวน์โหลดแล้ว

Kotlin

import androidx.media.utils.MediaConstants

mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build())

Java

import androidx.media.utils.MediaConstants;

mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build());

อัปเดตแถบความคืบหน้าในมุมมองการเรียกดูขณะเล่นเนื้อหา

ดังที่ได้กล่าวไว้ก่อนหน้านี้ คุณสามารถใช้ DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE เพิ่มเติมเพื่อแสดงแถบความคืบหน้าสำหรับเนื้อหาที่เล่นบางส่วนใน มุมมองการเรียกดู อย่างไรก็ตาม หากผู้ใช้เล่นเนื้อหาที่เล่นค้างไว้ต่อจาก Android Auto หรือ Android Automotive OS ตัวบ่งชี้ดังกล่าวจะ ไม่ถูกต้องเมื่อเวลาผ่านไป

สำหรับ Android Auto และ Android Automotive OS เพื่อให้แถบความคืบหน้าเป็นข้อมูลล่าสุดอยู่เสมอ คุณสามารถระบุข้อมูลเพิ่มเติมใน MediaMetadataCompat และ PlaybackStateCompat เพื่อลิงก์เนื้อหาที่กำลังเล่นกับ รายการสื่อในมุมมองการเรียกดู รายการสื่อต้องมีคุณสมบัติตรงตามข้อกำหนดต่อไปนี้เพื่อให้มีแถบความคืบหน้าที่อัปเดตโดยอัตโนมัติ

  • เมื่อสร้างแล้ว MediaItem ต้องส่ง DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE ในส่วนข้อมูลเพิ่มเติมโดยมีค่าระหว่าง 0.0 ถึง 1.0 (รวม)
  • MediaMetadataCompat ต้องส่ง METADATA_KEY_MEDIA_ID พร้อมค่าสตริงที่เท่ากับ รหัสสื่อ ที่ส่งไปยัง MediaItem
  • PlaybackStateCompat ต้องมีส่วนเสริมที่มีคีย์ PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID ที่แมปกับค่าสตริงที่เท่ากับ รหัสสื่อ ที่ส่งไปยัง MediaItem

ข้อมูลโค้ดต่อไปนี้แสดงวิธีระบุว่ารายการที่กำลังเล่นอยู่ ลิงก์กับรายการในมุมมองการเรียกดู

Kotlin

import androidx.media.utils.MediaConstants

// When the MediaItem is constructed to show in the browse view.
// Suppose the item was 25% complete when the user launched the browse view.
val mediaItemExtras = Bundle()
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters.
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

// Elsewhere, when the user has selected MediaItem for playback.
mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters.
        .build())

val playbackStateExtras = Bundle()
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id")
mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters.
        .build())

Java

import androidx.media.utils.MediaConstants;

// When the MediaItem is constructed to show in the browse view.
// Suppose the item was 25% complete when the user launched the browse view.
Bundle mediaItemExtras = new Bundle();
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters.
        .build();
return MediaBrowserCompat.MediaItem(description, /* flags */);

// Elsewhere, when the user has selected MediaItem for playback.
mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters.
        .build());

Bundle playbackStateExtras = new Bundle();
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id");
mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters.
        .build());

รูปที่ 5 มุมมองการเล่นที่มีตัวเลือก "ผลการค้นหา" สำหรับ ดูรายการสื่อที่เกี่ยวข้องกับการค้นหาด้วยเสียงของผู้ใช้

แอปของคุณสามารถแสดงผลการค้นหาตามบริบทซึ่งจะแสดงต่อผู้ใช้เมื่อเริ่มคำค้นหา Android Auto และระบบปฏิบัติการ Android Automotive จะแสดงผลลัพธ์เหล่านี้ผ่านอินเทอร์เฟซคำค้นหาหรือผ่านความสามารถที่อิงตามคำค้นหาที่ทำก่อนหน้านี้ในเซสชัน ดูข้อมูลเพิ่มเติมได้ที่ส่วนรองรับการสั่งงานด้วยเสียงในคู่มือนี้

หากต้องการแสดงผลการค้นหาที่เรียกดูได้ ให้ใส่คีย์ค่าคงที่ BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED ในแพ็กเกจข้อมูลเพิ่มเติมของเมธอด onGetRoot() ของบริการ โดยแมปกับบูลีน true

ข้อมูลโค้ดต่อไปนี้แสดงวิธีเปิดใช้การรองรับในเมธอด onGetRoot()

Kotlin

import androidx.media.utils.MediaConstants

@Nullable
fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
    return BrowserRoot(ROOT_ID, extras)
}

Java

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true);
    return new BrowserRoot(ROOT_ID, extras);
}

หากต้องการเริ่มแสดงผลการค้นหา ให้ลบล้างเมธอด onSearch() ในบริการเบราว์เซอร์สื่อ Android Auto และระบบปฏิบัติการ Android Automotive จะส่งต่อคำค้นหาของผู้ใช้ไปยังเมธอดนี้ทุกครั้งที่ผู้ใช้เรียกใช้อินเทอร์เฟซคำค้นหาหรือความสามารถในการใช้งาน "ผลการค้นหา"

คุณจัดระเบียบผลการค้นหาจากonSearch()เมธอดของบริการโดยใช้รายการชื่อ เพื่อให้เรียกดูได้ง่ายขึ้น เช่น หากแอปของคุณเล่นเพลง คุณอาจ จัดระเบียบผลการค้นหาตามอัลบั้ม ศิลปิน และเพลง

ข้อมูลโค้ดต่อไปนี้แสดงการใช้งานเมธอด onSearch() อย่างง่าย

Kotlin

fun onSearch(query: String, extras: Bundle) {
  // Detach from results to unblock the caller (if a search is expensive).
  result.detach()
  object:AsyncTask() {
    internal var searchResponse:ArrayList
    internal var succeeded = false
    protected fun doInBackground(vararg params:Void):Void {
      searchResponse = ArrayList()
      if (doSearch(query, extras, searchResponse))
      {
        succeeded = true
      }
      return null
    }
    protected fun onPostExecute(param:Void) {
      if (succeeded)
      {
        // Sending an empty List informs the caller that there were no results.
        result.sendResult(searchResponse)
      }
      else
      {
        // This invokes onError() on the search callback.
        result.sendResult(null)
      }
      return null
    }
  }.execute()
}
// Populates resultsToFill with search results. Returns true on success or false on error.
private fun doSearch(
    query: String,
    extras: Bundle,
    resultsToFill: ArrayList
): Boolean {
  // Implement this method.
}

Java

@Override
public void onSearch(final String query, final Bundle extras,
                        Result<List<MediaItem>> result) {

  // Detach from results to unblock the caller (if a search is expensive).
  result.detach();

  new AsyncTask<Void, Void, Void>() {
    List<MediaItem> searchResponse;
    boolean succeeded = false;
    @Override
    protected Void doInBackground(Void... params) {
      searchResponse = new ArrayList<MediaItem>();
      if (doSearch(query, extras, searchResponse)) {
        succeeded = true;
      }
      return null;
    }

    @Override
    protected void onPostExecute(Void param) {
      if (succeeded) {
       // Sending an empty List informs the caller that there were no results.
       result.sendResult(searchResponse);
      } else {
        // This invokes onError() on the search callback.
        result.sendResult(null);
      }
    }
  }.execute()
}

/** Populates resultsToFill with search results. Returns true on success or false on error. */
private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) {
    // Implement this method.
}

การทำงานของการเรียกดูที่กำหนดเอง

การเรียกดูที่กำหนดเองรายการเดียว

รูปที่ 6 การเรียกดูที่กำหนดเองรายการเดียว

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

เมนูการทำงานเพิ่มเติมของการเรียกดูที่กำหนดเอง

รูปที่ 7 เมนูเพิ่มเติมของการดำเนินการเรียกดูที่กำหนดเอง

หากมีการดำเนินการที่กำหนดเองมากกว่าที่ OEM อนุญาตให้แสดง ระบบจะแสดง เมนูที่ซ่อนอยู่ต่อผู้ใช้

วิธีการทำงาน

การกระทำในการเรียกดูที่กำหนดเองแต่ละรายการจะกำหนดด้วยข้อมูลต่อไปนี้

  • รหัสการดำเนินการ (ตัวระบุสตริงที่ไม่ซ้ำกัน)
  • ป้ายกำกับการดำเนินการ (ข้อความที่แสดงต่อผู้ใช้)
  • URI ของไอคอนการดำเนินการ (Vector Drawable ที่ปรับสมดุลสีเขียว-แดงได้)

คุณกำหนดรายการการเรียกดูที่กำหนดเองทั่วโลกเป็นส่วนหนึ่งของ BrowseRoot จากนั้นคุณจะแนบชุดย่อยของการดำเนินการเหล่านี้กับMediaItem.แต่ละรายการได้

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

การดำเนินการเรียกดูที่กำหนดเองในรูทของโหนดการเรียกดู

รูปที่ 8 แถบเครื่องมือการดำเนินการเรียกดูที่กำหนดเอง

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

วิธีติดตั้งใช้งานการดำเนินการเรียกดูที่กำหนดเอง

ขั้นตอนในการเพิ่มการเรียกดูที่กำหนดเองไปยังโปรเจ็กต์มีดังนี้

  1. ลบล้าง 2 วิธีต่อไปนี้ในการติดตั้งใช้งาน MediaBrowserServiceCompat
  2. แยกวิเคราะห์ขีดจํากัดการดำเนินการที่รันไทม์
    • ใน onGetRoot() ให้รับจำนวนการกระทำสูงสุดที่อนุญาตสำหรับแต่ละ MediaItem โดยใช้คีย์ BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT ใน rootHints Bundle ขีดจำกัด 0 แสดงว่าระบบไม่รองรับฟีเจอร์นี้
  3. สร้างรายการการทำงานของการเรียกดูที่กำหนดเองส่วนกลาง
    • สำหรับการดำเนินการแต่ละรายการ ให้สร้างออบเจ็กต์ Bundle ที่มีคีย์ต่อไปนี้ * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: รหัสการดำเนินการ * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: ป้ายกำกับการดำเนินการ * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: URI ของไอคอนการดำเนินการ * เพิ่มออบเจ็กต์ Bundle ของการดำเนินการทั้งหมดลงในรายการ
  4. เพิ่มรายการส่วนกลางลงใน BrowseRoot ดังนี้
  5. เพิ่มการดำเนินการไปยังออบเจ็กต์ MediaItem โดยทำดังนี้
    • คุณเพิ่มการดำเนินการไปยังออบเจ็กต์ MediaItem แต่ละรายการได้โดยใส่รายการรหัสการดำเนินการในส่วนMediaDescriptionCompat extras โดยใช้คีย์ DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST รายการนี้ต้องเป็นชุดย่อยของรายการการดำเนินการส่วนกลางที่คุณกำหนดไว้ในBrowseRoot
  6. จัดการการดำเนินการและแสดงความคืบหน้าหรือผลลัพธ์
    • ใน onCustomAction ให้จัดการการดำเนินการตามรหัสการดำเนินการและข้อมูลอื่นๆ ที่คุณต้องการ คุณรับรหัสของ MediaItem ที่ทริกเกอร์การดำเนินการจากส่วนเสริมได้โดยใช้คีย์ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
    • คุณอัปเดตรายการการดำเนินการสำหรับ MediaItem ได้โดยใส่คีย์ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM ในแพ็กเกจความคืบหน้าหรือผลลัพธ์

การเปลี่ยนแปลงบางอย่างที่คุณทำได้ใน BrowserServiceCompat เพื่อเริ่มต้นใช้งาน การเรียกดูที่กำหนดเองมีดังนี้

ลบล้าง BrowserServiceCompat

คุณต้องลบล้างเมธอดต่อไปนี้ใน MediaBrowserServiceCompat

public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

ขีดจำกัดการแยกวิเคราะห์การกระทำ

คุณควรตรวจสอบว่าระบบรองรับการดำเนินการเรียกดูที่กำหนดเองกี่รายการ

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
    rootHints.getInt(
            MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
}

สร้างการเรียกดูที่กำหนดเอง

โดยแต่ละการกระทำจะต้องรวมอยู่ใน Bundle แยกกัน

  • รหัสการทำงาน
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
  • ป้ายกำกับการดำเนินการ
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
  • URI ของไอคอนการดำเนินการ
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")

เพิ่มการดำเนินการเรียกดูที่กำหนดเองไปยัง Parceable ArrayList

เพิ่มออบเจ็กต์ Custom Browse Action ทั้งหมดลงใน ArrayListBundle

private ArrayList<Bundle> createCustomActionsList(
                                        CustomBrowseAction browseActions) {
    ArrayList<Bundle> browseActionsBundle = new ArrayList<>();
    for (CustomBrowseAction browseAction : browseActions) {
        Bundle action = new Bundle();
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                browseAction.mId);
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                getString(browseAction.mLabelResId));
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                browseAction.mIcon);
        browseActionsBundle.add(action);
    }
    return browseActionsBundle;
}

เพิ่มรายการการกระทำในการเรียกดูที่กำหนดเองไปยังรูทการเรียกดู

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {
    Bundle browserRootExtras = new Bundle();
    browserRootExtras.putParcelableArrayList(
            BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST,
            createCustomActionsList()));
    mRoot = new BrowserRoot(ROOT_ID, browserRootExtras);
    return mRoot;
}

เพิ่มการดำเนินการไปยัง MediaItem

MediaDescriptionCompat buildDescription (long id, String title, String subtitle,
                String description, Uri iconUri, Uri mediaUri,
                ArrayList<String> browseActionIds) {

    MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
    bob.setMediaId(id);
    bob.setTitle(title);
    bob.setSubtitle(subtitle);
    bob.setDescription(description);
    bob.setIconUri(iconUri);
    bob.setMediaUri(mediaUri);

    Bundle extras = new Bundle();
    extras.putStringArrayList(
          DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST,
          browseActionIds);

    bob.setExtras(extras);
    return bob.build();
}
MediaItem mediaItem = new MediaItem(buildDescription(...), flags);

ผลลัพธ์ของบิลด์ onCustomAction

  • แยกวิเคราะห์ mediaId จาก Bundle extras:
    @Override
    public void onCustomAction(
              @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){
      String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID);
    }
  • สำหรับผลลัพธ์แบบอะซิงโครนัส ให้แยกผลลัพธ์ result.detach()
  • แพ็กเกจผลลัพธ์ของบิลด์
    • ข้อความถึงผู้ใช้
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
    • อัปเดตรายการ(ใช้เพื่ออัปเดตการดำเนินการในรายการ)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
    • เปิดมุมมองการเล่น
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
    • อัปเดตโหนดการเรียกดู
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
  • หากมีข้อผิดพลาด โปรดโทรหา result.sendError(resultBundle).
  • หากต้องการอัปเดตความคืบหน้า โปรดโทรหา result.sendProgressUpdate(resultBundle)
  • โปรดโทรหา result.sendResult(resultBundle) เพื่อดำเนินการให้เสร็จสิ้น

อัปเดตสถานะการดำเนินการ

การใช้เมธอด result.sendProgressUpdate(resultBundle) กับคีย์ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM จะช่วยให้คุณอัปเดต MediaItem เพื่อแสดงสถานะใหม่ของการดำเนินการได้ ซึ่งจะช่วยให้คุณให้ความคิดเห็นแบบเรียลไทม์แก่ผู้ใช้เกี่ยวกับความคืบหน้าและ ผลลัพธ์ของการกระทำของผู้ใช้ได้

ตัวอย่าง: การดำเนินการดาวน์โหลด

ตัวอย่างวิธีใช้ฟีเจอร์นี้เพื่อใช้การดำเนินการดาวน์โหลด ที่มี 3 สถานะ

  1. ดาวน์โหลด: นี่คือสถานะเริ่มต้นของการดำเนินการ เมื่อผู้ใช้เลือก การดำเนินการนี้ คุณสามารถสลับกับการดำเนินการ "ดาวน์โหลด" และเรียก sendProgressUpdate เพื่ออัปเดต UI
  2. กำลังดาวน์โหลด: สถานะนี้บ่งชี้ว่ากำลังดาวน์โหลด คุณสามารถ ใช้สถานะนี้เพื่อแสดงแถบความคืบหน้าหรือตัวบ่งชี้อื่นแก่ผู้ใช้
  3. ดาวน์โหลดแล้ว: สถานะนี้บ่งบอกว่าการดาวน์โหลดเสร็จสมบูรณ์แล้ว เมื่อดาวน์โหลดเสร็จแล้ว คุณสามารถสลับ "กำลังดาวน์โหลด" กับ "ดาวน์โหลดแล้ว" และเรียกใช้ sendResult ด้วยคีย์ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM เพื่อระบุว่าควรรีเฟรชรายการ นอกจากนี้ คุณยังใช้คีย์ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE เพื่อแสดงข้อความว่าดำเนินการสำเร็จแล้วต่อผู้ใช้ได้ด้วย

แนวทางนี้ช่วยให้คุณแสดงความคิดเห็นที่ชัดเจนแก่ผู้ใช้เกี่ยวกับกระบวนการดาวน์โหลด และสถานะปัจจุบัน คุณเพิ่มรายละเอียดได้มากขึ้นด้วยไอคอนเพื่อแสดงสถานะการดาวน์โหลด 25%, 50%, 75%

ตัวอย่าง: การดำเนินการที่ชื่นชอบ

อีกตัวอย่างหนึ่งคือการดำเนินการที่ชื่นชอบซึ่งมี 2 สถานะ ดังนี้

  1. รายการโปรด: การดำเนินการนี้จะแสดงสำหรับรายการที่ไม่ได้อยู่ใน รายการโปรดของผู้ใช้ เมื่อผู้ใช้เลือกการดำเนินการนี้ คุณจะสลับการดำเนินการนี้กับ "รายการโปรด" และเรียกใช้ sendResult ด้วยคีย์ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM เพื่ออัปเดต UI ได้
  2. รายการโปรด: การดำเนินการนี้จะแสดงสำหรับรายการที่อยู่ในรายการโปรดของผู้ใช้ เมื่อผู้ใช้เลือกการดำเนินการนี้ คุณสามารถสลับการดำเนินการนี้กับ "รายการโปรด" และเรียกใช้ sendResult ด้วยคีย์ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM เพื่ออัปเดต UI

แนวทางนี้ช่วยให้ผู้ใช้จัดการรายการโปรดได้อย่างชัดเจนและสอดคล้องกัน

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

ดูตัวอย่างการติดตั้งใช้งานฟีเจอร์นี้ทั้งหมดได้ใน TestMediaApp โปรเจ็กต์

เปิดใช้การควบคุมการเล่น

Android Auto และ Android Automotive OS จะส่งคำสั่งควบคุมการเล่นผ่าน MediaSessionCompat ของบริการ คุณต้องลงทะเบียนเซสชันและใช้วิธีการเรียกกลับที่เชื่อมโยง

ลงทะเบียนเซสชันสื่อ

ในเมธอด onCreate() ของบริการเบราว์เซอร์สื่อ ให้สร้าง MediaSessionCompat จากนั้นลงทะเบียนเซสชันสื่อโดยเรียกใช้ setSessionToken()

ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างและลงทะเบียนเซสชันสื่อ

Kotlin

override fun onCreate() {
    super.onCreate()
    ...
    // Start a new MediaSession.
    val session = MediaSessionCompat(this, "session tag").apply {
        // Set a callback object that implements MediaSession.Callback
        // to handle play control requests.
        setCallback(MyMediaSessionCallback())
    }
    sessionToken = session.sessionToken
    ...
}

Java

public void onCreate() {
    super.onCreate();
    ...
    // Start a new MediaSession.
    MediaSessionCompat session = new MediaSessionCompat(this, "session tag");
    setSessionToken(session.getSessionToken());

    // Set a callback object that implements MediaSession.Callback
    // to handle play control requests.
    session.setCallback(new MyMediaSessionCallback());
    ...
}

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

ใช้คำสั่งเล่น

เมื่อผู้ใช้ขอเล่นรายการสื่อจากแอปของคุณ Android Automotive OS และ Android Auto จะใช้คลาส MediaSessionCompat.Callback จากออบเจ็กต์ MediaSessionCompat ของแอปที่ได้จากบริการเบราว์เซอร์สื่อของแอป เมื่อผู้ใช้ ต้องการควบคุมการเล่นเนื้อหา เช่น หยุดเล่นชั่วคราวหรือข้ามไปยังแทร็กถัดไป Android Auto และ Android Automotive OS จะเรียกใช้เมธอดของออบเจ็กต์การเรียกกลับรายการใดรายการหนึ่ง

หากต้องการจัดการการเล่นเนื้อหา แอปของคุณต้องขยายMediaSessionCompat.Callback คลาสแบบนามธรรมและใช้เมธอดที่แอปของคุณรองรับ

ใช้เมธอดเรียกกลับทั้งหมดต่อไปนี้ที่เหมาะกับ ประเภทเนื้อหาที่แอปของคุณนำเสนอ

onPrepare()
เรียกใช้เมื่อมีการเปลี่ยนแหล่งที่มาของสื่อ Android Automotive OS ยังเรียกใช้ เมธอดนี้ทันทีหลังจากบูตด้วย แอปสื่อของคุณต้องใช้วิธีนี้
onPlay()
เรียกใช้หากผู้ใช้เลือกเล่นโดยไม่เลือกรายการที่เฉพาะเจาะจง แอปของคุณต้องเล่นเนื้อหาเริ่มต้น หรือหากหยุดการเล่นชั่วคราวด้วย onPause() แอปของคุณจะเล่นต่อ

หมายเหตุ: แอปของคุณไม่ควรเริ่มเล่นเพลงโดยอัตโนมัติ เมื่อ Android Automotive OS หรือ Android Auto เชื่อมต่อกับเบราว์เซอร์สื่อ ของคุณ ดูข้อมูลเพิ่มเติมได้ที่ส่วนเกี่ยวกับ การตั้งค่าสถานะการเล่นเริ่มต้น

onPlayFromMediaId()
เรียกใช้เมื่อผู้ใช้เลือกเล่นรายการที่เฉพาะเจาะจง ระบบจะส่งเมธอด ID ที่บริการเบราว์เซอร์สื่อของคุณกำหนด ให้กับรายการสื่อในลำดับชั้นเนื้อหา
onPlayFromSearch()
เรียกใช้เมื่อผู้ใช้เลือกเล่นจากคำค้นหา แอปต้อง เลือกตัวเลือกที่เหมาะสมตามสตริงการค้นหาที่ส่งเข้ามา
onPause()
เรียกใช้เมื่อผู้ใช้เลือกหยุดการเล่นชั่วคราว
onSkipToNext()
เรียกใช้เมื่อผู้ใช้เลือกข้ามไปยังรายการถัดไป
onSkipToPrevious()
เรียกใช้เมื่อผู้ใช้เลือกข้ามไปยังรายการก่อนหน้า
onStop()
เรียกใช้เมื่อผู้ใช้เลือกหยุดเล่น

ลบล้างวิธีการเหล่านี้ในแอปเพื่อระบุฟังก์ชันการทำงานที่ต้องการ คุณไม่จำเป็นต้องใช้วิธีการหากแอปไม่รองรับฟังก์ชันการทำงานของวิธีการนั้น เช่น หากแอปเล่นไลฟ์สด เช่น การถ่ายทอดสดกีฬา คุณไม่จำเป็นต้องใช้วิธีการ onSkipToNext() คุณใช้การติดตั้งใช้งานเริ่มต้นของ onSkipToNext() แทนได้

แอปของคุณไม่จำเป็นต้องมีตรรกะพิเศษใดๆ ในการเล่นเนื้อหาผ่านลำโพงของรถยนต์ เมื่อแอปได้รับคำขอให้เล่นเนื้อหา แอปจะเล่นเสียง ในลักษณะเดียวกับการเล่นเนื้อหาผ่าน ลำโพงหรือหูฟังของโทรศัพท์ผู้ใช้ Android Auto และ Android Automotive OS จะส่งเนื้อหาเสียงไปยังระบบของรถยนต์โดยอัตโนมัติเพื่อเล่นผ่าน ลำโพงของรถยนต์

ดูข้อมูลเพิ่มเติมเกี่ยวกับการเล่นเนื้อหาเสียงได้ที่ ภาพรวมของ MediaPlayer ภาพรวมของแอปเสียง และภาพรวมของ ExoPlayer

ตั้งค่าการดำเนินการเล่นมาตรฐาน

Android Auto และ Android Automotive OS จะแสดงตัวควบคุมการเล่นตาม การดำเนินการที่เปิดใช้ในออบเจ็กต์ PlaybackStateCompat

โดยค่าเริ่มต้น แอปของคุณต้องรองรับการดำเนินการต่อไปนี้

นอกจากนี้ แอปยังรองรับการดำเนินการต่อไปนี้ได้หากเกี่ยวข้องกับเนื้อหาของแอป

นอกจากนี้ คุณยังมีตัวเลือกในการสร้างคิวการเล่นที่แสดงต่อผู้ใช้ได้ แต่ไม่จำเป็นต้องสร้าง โดยเรียกใช้เมธอด setQueue() และ setQueueTitle() เปิดใช้การดำเนินการ ACTION_SKIP_TO_QUEUE_ITEM และกำหนดการเรียกกลับ onSkipToQueueItem()

นอกจากนี้ ให้เพิ่มการรองรับไอคอนกำลังเล่น ซึ่งเป็นตัวบ่งชี้ว่ากำลังเล่นอะไรอยู่ โดยเรียกใช้เมธอด setActiveQueueItemId() แล้วส่งรหัสของรายการที่กำลังเล่นอยู่ในคิว คุณต้อง อัปเดต setActiveQueueItemId() ทุกครั้งที่มีการเปลี่ยนแปลงคิว

Android Auto และ Android Automotive OS จะแสดงปุ่มสำหรับการดำเนินการแต่ละอย่างที่เปิดใช้ รวมถึงคิวการเล่น เมื่อคลิกปุ่ม ระบบจะเรียกใช้การเรียกกลับที่เกี่ยวข้องจาก MediaSessionCompat.Callback

จองพื้นที่ที่ไม่ได้ใช้

Android Auto และ Android Automotive OS จะสำรองพื้นที่ใน UI สำหรับการดำเนินการ ACTION_SKIP_TO_PREVIOUS และ ACTION_SKIP_TO_NEXT หากแอปของคุณไม่รองรับฟังก์ชันใดฟังก์ชันหนึ่งเหล่านี้ Android Auto และ Android Automotive OS จะใช้พื้นที่ดังกล่าวเพื่อแสดงการดำเนินการที่กำหนดเองที่คุณสร้างขึ้น

หากไม่ต้องการกรอกข้อมูลในช่องเหล่านั้นด้วยการดำเนินการที่กำหนดเอง คุณสามารถจองช่องเหล่านั้นเพื่อให้ Android Auto และ Android Automotive OS เว้นช่องว่างไว้เมื่อใดก็ตามที่แอปไม่รองรับฟังก์ชันที่เกี่ยวข้อง โดยให้เรียกใช้เมธอด setExtras() ด้วยชุดข้อมูลเพิ่มเติมที่มีค่าคงที่ซึ่งสอดคล้องกับฟังก์ชันที่สงวนไว้ SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT สอดคล้องกับ ACTION_SKIP_TO_NEXT และ SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV สอดคล้องกับ ACTION_SKIP_TO_PREVIOUS ใช้ค่าคงที่เหล่านี้เป็นคีย์ใน Bundle และใช้บูลีน true เป็นค่า

ตั้งค่า PlaybackState เริ่มต้น

เมื่อ Android Auto และ Android Automotive OS สื่อสารกับบริการเบราว์เซอร์สื่อ เซสชันสื่อจะสื่อสารสถานะการเล่นเนื้อหาโดยใช้ PlaybackStateCompat แอปของคุณไม่ควรเริ่มเล่นเพลงโดยอัตโนมัติเมื่อ Android Automotive OS หรือ Android Auto เชื่อมต่อกับบริการ MediaBrowserService แต่ให้ใช้ Android Auto และระบบปฏิบัติการ Android Automotive เพื่อเล่นต่อหรือเริ่มเล่นโดยอิงตามสถานะของรถยนต์หรือการดำเนินการของผู้ใช้

หากต้องการดำเนินการนี้ ให้ตั้งค่า PlaybackStateCompat เริ่มต้นของเซสชันสื่อเป็น STATE_STOPPED STATE_PAUSED STATE_NONE หรือ STATE_ERROR

เซสชันสื่อภายใน Android Auto และ Android Automotive OS จะมีระยะเวลาเท่ากับ ระยะเวลาการขับรถเท่านั้น ดังนั้นผู้ใช้จึงเริ่มและหยุดเซสชันเหล่านี้บ่อยครั้ง หากต้องการ มอบประสบการณ์การใช้งานไดรฟ์ที่ราบรื่น ให้ติดตามสถานะเซสชันก่อนหน้าของผู้ใช้ เพื่อให้เมื่อแอปสื่อได้รับคำขอเล่นต่อ ผู้ใช้จะเล่นต่อจากจุดที่ค้างไว้ได้โดยอัตโนมัติ เช่น รายการสื่อที่เล่นล่าสุด PlaybackStateCompat และคิว

เพิ่มการดำเนินการเล่นที่กำหนดเอง

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

ใช้การกระทำที่กำหนดเองเพื่อระบุลักษณะการทำงานที่แตกต่างจากการกระทำมาตรฐาน อย่าใช้เพื่อแทนที่หรือทำซ้ำการดำเนินการมาตรฐาน

คุณเพิ่มการดำเนินการที่กำหนดเองได้โดยใช้เมธอด addCustomAction() ในคลาส PlaybackStateCompat.Builder

ข้อมูลโค้ดต่อไปนี้แสดงวิธีเพิ่มการดำเนินการ "เริ่มสถานีวิทยุ" ที่กำหนดเอง

Kotlin

val customActionExtras = Bundle()
customActionExtras.putInt(
  androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
  androidx.media3.session.CommandButton.ICON_RADIO)

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon // or R.drawable.media3_icon_radio
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

Java

Bundle customActionExtras = new Bundle();
customActionExtras.putInt(
  androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
  androidx.media3.session.CommandButton.ICON_RADIO);

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon) // or R.drawable.media3_icon_radio
    .setExtras(customActionExtras)
    .build());

ดูตัวอย่างโดยละเอียดเพิ่มเติมของวิธีการนี้ได้ที่เมธอด setCustomAction() ในแอปตัวอย่าง Universal Android Music Player ใน GitHub

หลังจากสร้างการดำเนินการที่กำหนดเองแล้ว เซสชันสื่อจะตอบสนองต่อการดำเนินการได้ โดยการลบล้างเมธอด onCustomAction()

ข้อมูลโค้ดต่อไปนี้แสดงวิธีที่แอปอาจตอบสนองต่อการดำเนินการ "เริ่มสถานีวิทยุ"

Kotlin

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

Java

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

ดูตัวอย่างโดยละเอียดเพิ่มเติมของวิธีการนี้ได้ที่เมธอด onCustomAction ในแอปตัวอย่าง Universal Android Music Player ใน GitHub

ไอคอนสำหรับการกระทำแบบกำหนดเอง

การดำเนินการที่กำหนดเองแต่ละรายการที่คุณสร้างต้องมีไอคอน

หากคำอธิบายของไอคอนนั้นตรงกับค่าคงที่ CommandButton.ICON_ อย่างใดอย่างหนึ่ง คุณควรกำหนดค่าจำนวนเต็มนั้นสำหรับคีย์ EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT ของ ส่วนเพิ่มเติมของการดำเนินการที่กำหนดเอง ในระบบที่รองรับ การดำเนินการนี้จะลบล้างไอคอน ทรัพยากรที่ส่งไปยัง CustomAction.Builder ซึ่งจะช่วยให้คอมโพเนนต์ของระบบแสดงผลการดำเนินการ และการดำเนินการเล่นอื่นๆ ในรูปแบบที่สอดคล้องกันได้

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

หากการดำเนินการที่กำหนดเองมีสถานะ เช่น สลับการตั้งค่าการเล่นเป็นเปิดหรือปิด ให้ระบุไอคอนที่แตกต่างกันสำหรับสถานะต่างๆ เพื่อให้ผู้ใช้เห็นการเปลี่ยนแปลงเมื่อเลือกการดำเนินการ

ระบุรูปแบบไอคอนอื่นสำหรับดำเนินการที่ปิดใช้

เมื่อการดำเนินการที่กำหนดเองไม่พร้อมใช้งานสำหรับบริบทปัจจุบัน ให้สลับ ไอคอนการดำเนินการที่กำหนดเองกับไอคอนทางเลือกที่แสดงว่าการดำเนินการ ถูกปิดใช้

รูปที่ 6 ตัวอย่างไอคอนการทำงานที่กำหนดเองซึ่งไม่ได้อยู่ในรูปแบบ

ระบุรูปแบบเสียง

หากต้องการระบุว่าสื่อที่กำลังเล่นใช้รูปแบบเสียงพิเศษ คุณสามารถระบุไอคอนที่จะแสดงในรถยนต์ที่รองรับฟีเจอร์นี้ได้ คุณ สามารถตั้งค่า KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI และ KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI ในแพ็กเกจเนื้อหาพิเศษของรายการสื่อที่กำลังเล่น (ส่งไปยัง MediaSession.setMetadata()) อย่าลืมตั้งค่าเนื้อหาพิเศษทั้ง 2 รายการเพื่อรองรับเลย์เอาต์ต่างๆ

นอกจากนี้ คุณยังตั้งค่า KEY_IMMERSIVE_AUDIO extra เพื่อแจ้งให้ OEM รถยนต์ทราบว่านี่คือเสียงสมจริง และควรระมัดระวังเป็นอย่างยิ่ง เมื่อตัดสินใจว่าจะใช้เอฟเฟกต์เสียงที่อาจรบกวน เนื้อหาแบบสมจริงหรือไม่

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

หากต้องการเพิ่มลิงก์ ให้กำหนดค่าข้อมูลเมตา KEY_SUBTITLE_LINK_MEDIA_ID (เพื่อลิงก์จากคำบรรยาย) หรือ KEY_DESCRIPTION_LINK_MEDIA_ID (เพื่อลิงก์จากคำอธิบาย) ดูรายละเอียดได้ที่เอกสารอ้างอิงสำหรับฟิลด์ข้อมูลเมตาเหล่านั้น

รองรับการสั่งงานด้วยเสียง

แอปสื่อของคุณต้องรองรับการสั่งงานด้วยเสียงเพื่อช่วยให้ผู้ขับได้รับประสบการณ์ที่ปลอดภัย และสะดวกสบายซึ่งช่วยลดสิ่งรบกวน เช่น หากแอปของคุณ กำลังเล่นสื่ออยู่ 1 รายการ ผู้ใช้สามารถพูดว่า"เปิด [ชื่อเพลง]" เพื่อสั่งให้แอปเล่นเพลงอื่นโดยไม่ต้องดูหรือแตะที่จอแสดงผลของรถยนต์ ผู้ใช้สามารถเริ่มคำค้นหาได้โดยคลิกปุ่มที่เหมาะสมบนพวงมาลัยหรือพูดคำสั่งเรียกใช้ "Ok Google"

เมื่อ Android Auto หรือ Android Automotive OS ตรวจจับและตีความการสั่งงานด้วยเสียง ระบบจะส่งการสั่งงานด้วยเสียงนั้นไปยังแอปผ่าน onPlayFromSearch() เมื่อได้รับการเรียกกลับนี้ แอปจะค้นหาเนื้อหาที่ตรงกับquery สตริงและเริ่มเล่น

ผู้ใช้สามารถระบุหมวดหมู่ของคำต่างๆ ในคำค้นหาได้ เช่น แนวเพลง ศิลปิน อัลบั้ม ชื่อเพลง สถานีวิทยุ หรือเพลย์ลิสต์ เป็นต้น เมื่อสร้าง การรองรับการค้นหา ให้พิจารณาหมวดหมู่ทั้งหมดที่เหมาะสมกับแอปของคุณ หาก Android Auto หรือ Android Automotive OS ตรวจพบว่าคำค้นหาที่ระบุอยู่ใน หมวดหมู่ใดหมวดหมู่หนึ่ง ระบบจะเพิ่มข้อมูลพิเศษในพารามิเตอร์ extras คุณส่งข้อมูลเพิ่มเติมต่อไปนี้ได้

พิจารณาสตริง query ที่ว่างเปล่า ซึ่ง Android Auto หรือ Android Automotive OS อาจส่งมาหากผู้ใช้ไม่ได้ระบุคำค้นหา เช่น หากผู้ใช้พูดว่า "เปิดเพลงหน่อย" ในกรณีนี้ แอปอาจ เลือกที่จะเริ่มแทร็กที่เล่นล่าสุดหรือแทร็กที่แนะนำใหม่

หากประมวลผลการค้นหาอย่างรวดเร็วไม่ได้ อย่าบล็อกใน onPlayFromSearch() แต่ให้ตั้งค่าสถานะการเล่นเป็น STATE_CONNECTING และทำการค้นหาในเธรดแบบไม่พร้อมกัน

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

นอกจากคำค้นหา "เล่น" แล้ว Android Auto และ Android Automotive OS ยังจดจำคำค้นหาด้วยเสียงเพื่อควบคุมการเล่น เช่น "หยุดเพลงชั่วคราว" และ "เพลงถัดไป" และจับคู่คำสั่งเหล่านี้กับโปรแกรมเรียกกลับของเซสชันสื่อที่เหมาะสม เช่น onPause() และ onSkipToNext()

ดูตัวอย่างโดยละเอียดเกี่ยวกับวิธีใช้การดำเนินการเล่นที่เปิดใช้เสียงใน แอปได้ที่Google Assistant และแอปสื่อ

ใช้มาตรการป้องกันการรบกวน

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

ปิดเสียงปลุกในรถ

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

หากต้องการปฏิบัติตามข้อกำหนดนี้ แอปของคุณ สามารถใช้ CarConnection เป็นสัญญาณก่อนเล่นเสียง แอปของคุณสามารถตรวจสอบว่าโทรศัพท์กำลัง ฉายภาพไปยังหน้าจอรถยนต์หรือไม่โดยสังเกต LiveData สำหรับประเภท การเชื่อมต่อรถยนต์ และตรวจสอบว่าเท่ากับ CONNECTION_TYPE_PROJECTION หรือไม่

หากโทรศัพท์ของผู้ใช้กำลังฉายภาพ แอปสื่อที่รองรับการปลุกต้องทำอย่างใดอย่างหนึ่งต่อไปนี้

  • ปิดใช้การปลุก
  • เล่นการปลุกผ่าน STREAM_ALARM และแสดง UI บนหน้าจอโทรศัพท์เพื่อปิดใช้การปลุก

จัดการโฆษณาสื่อ

โดยค่าเริ่มต้น Android Auto จะแสดงการแจ้งเตือนเมื่อข้อมูลเมตาของสื่อเปลี่ยนแปลง ในระหว่างเซสชันการเล่นเสียง เมื่อแอปสื่อเปลี่ยนจากการเล่นเพลง เป็นการแสดงโฆษณา การแสดง การแจ้งเตือนต่อผู้ใช้จะรบกวนการใช้งาน หากต้องการป้องกันไม่ให้ Android Auto แสดงการแจ้งเตือน ในกรณีนี้ คุณต้องตั้งค่าคีย์ข้อมูลเมตาของสื่อ METADATA_KEY_IS_ADVERTISEMENT เป็น METADATA_VALUE_ATTRIBUTE_PRESENT ตามที่แสดงในข้อมูลโค้ดต่อไปนี้

Kotlin

import androidx.media.utils.MediaConstants

override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
    MediaMetadataCompat.Builder().apply {
        if (isAd(mediaId)) {
            putLong(
                MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        }
        // ...add any other properties you normally would.
        mediaSession.setMetadata(build())
    }
}

Java

import androidx.media.utils.MediaConstants;

@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
    MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
    if (isAd(mediaId)) {
        builder.putLong(
            MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
    }
    // ...add any other properties you normally would.
    mediaSession.setMetadata(builder.build());
}

จัดการข้อผิดพลาดทั่วไป

เมื่อแอปพบข้อผิดพลาด ให้ตั้งค่าสถานะการเล่นเป็น STATE_ERROR และระบุข้อความแสดงข้อผิดพลาดโดยใช้วิธี setErrorMessage() ดูPlaybackStateCompat สำหรับรายการรหัสข้อผิดพลาดที่คุณใช้ได้เมื่อตั้งค่าข้อความแสดงข้อผิดพลาด ข้อความแสดงข้อผิดพลาดต้องแสดงต่อผู้ใช้และแปลเป็นภาษาท้องถิ่นตามภาษาปัจจุบันของผู้ใช้ จากนั้น Android Auto และ Android Automotive OS จะแสดงข้อความแสดงข้อผิดพลาดต่อผู้ใช้ได้

เช่น หากเนื้อหาไม่พร้อมให้บริการในภูมิภาคปัจจุบันของผู้ใช้ คุณสามารถ ใช้รหัสข้อผิดพลาด ERROR_CODE_NOT_AVAILABLE_IN_REGION เมื่อตั้งค่าข้อความแสดงข้อผิดพลาด

Kotlin

mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build())

Java

mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build());

ดูข้อมูลเพิ่มเติมเกี่ยวกับสถานะข้อผิดพลาดได้ที่การใช้เซสชันสื่อ: สถานะ และข้อผิดพลาด

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

ทรัพยากรอื่นๆ