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

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

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

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

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

  1. อ่านเอกสารประกอบของ Android Media API
  2. อ่านสร้างแอปสื่อเพื่อดูคำแนะนำเกี่ยวกับการออกแบบ
  3. ตรวจสอบคําศัพท์และแนวคิดสําคัญที่ระบุไว้ในส่วนนี้

คําศัพท์และแนวคิดสําคัญ

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

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

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

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

ส่วนนี้อธิบายวิธีที่ 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 จะเรียกใช้บริการเบราว์เซอร์สื่อของแอปเพื่อค้นหาเนื้อหาที่พร้อมใช้งาน คุณต้องติดตั้งใช้งาน 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 เสมอเมื่ออุปกรณ์ระบบเหล่านี้เรียกใช้เมธอด 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() method แบบง่าย

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 เช่น หากปกติคุณแสดงรายการที่เล่นได้ระดับรูท คุณอาจต้องการฝังรายการนั้นไว้ใต้รายการที่เรียกดูได้ระดับรูทแทน เนื่องจากค่าของคำแนะนำเกี่ยวกับ Flag ที่รองรับ

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

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

แสดงอาร์ตเวิร์กสื่อ

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

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

  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: รายการที่เกี่ยวข้องจะแสดงเป็นรายการ "หมวดหมู่" รายการเหล่านี้เหมือนกับรายการในรายการธรรมดา ยกเว้นจะมีการใช้ระยะขอบรอบไอคอนของรายการ เนื่องจากไอคอนจะดูดีขึ้นเมื่อมีขนาดเล็ก ไอคอนต้องเป็นรูปภาพเวกเตอร์ที่ปรับสีได้ คาดว่าจะมีให้สำหรับรายการที่เรียกดูได้เท่านั้น
  • 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 ที่เรียกดูได้ ซึ่งจะลบล้างสไตล์เนื้อหาเริ่มต้นสำหรับทั้งตัว 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 ที่มีชื่อว่า "เพลง" ที่มีรายการสื่อ A
  • กลุ่ม 2 ที่ชื่อ "อัลบั้ม" ที่มีรายการสื่อ B
  • กลุ่มที่ 3 ชื่อ "เพลง" ซึ่งมีรายการสื่อ C และ D
  • กลุ่มที่ 4 ชื่อ "อัลบั้ม" ซึ่งมีรายการสื่อ E

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

  1. รายการสื่อ ก. ที่มี 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. รายการสื่อ ข. ที่มี 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: ระบุสถานะการดาวน์โหลดของรายการ ใช้ค่าคงที่นี้เป็นตัวกุญแจ โดยค่าคงที่แบบยาวที่เป็นไปได้มีดังนี้
    • STATUS_DOWNLOADED: รายการดังกล่าวดาวน์โหลดเสร็จสมบูรณ์แล้ว
    • STATUS_DOWNLOADING: ระบบกำลังดาวน์โหลดรายการ
    • STATUS_NOT_DOWNLOADED: ไม่ได้ดาวน์โหลดรายการ
  • METADATA_KEY_IS_EXPLICIT: ระบุว่าสินค้ามีเนื้อหาที่อาจไม่เหมาะสมหรือไม่ หากต้องการระบุว่ารายการนั้นชัดเจน ให้ใช้ค่าคงที่นี้เป็นคีย์และค่าแบบยาว METADATA_VALUE_ATTRIBUTE_PRESENT เป็นค่า

ค่าคงที่ต่อไปนี้ใช้ได้เฉพาะในข้อมูลเพิ่มเติมของคำอธิบาย MediaItem

  • DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS: ระบุสถานะความสมบูรณ์ของเนื้อหาแบบยาว เช่น ตอนของพอดแคสต์หรือหนังสือเสียง ใช้ค่าคงที่นี้เป็นตัวคีย์ โดยค่าคงที่จำนวนเต็มต่อไปนี้คือค่าที่เป็นไปได้
  • DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE: ระบุจำนวนความคืบหน้าของเนื้อหาแบบยาวเป็นเลข 2 ระหว่าง 0.0 ถึง 1.0 โดยเป็นเลข 2 หลัก ข้อมูลเพิ่มเติมนี้ให้ข้อมูลเพิ่มเติมเกี่ยวกับสถานะ 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 เพื่อลิงก์เนื้อหาที่กำลังเผยแพร่กับรายการสื่อในมุมมองการเรียกดูได้เพื่อให้แถบความคืบหน้าเป็นข้อมูลล่าสุดอยู่เสมอ รายการสื่อต้องมีข้อกำหนดต่อไปนี้จึงจะมีแถบความคืบหน้าที่อัปเดตโดยอัตโนมัติ

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

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 ใน Bundle rootHints ขีดจำกัดเป็น 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 โดยใช้คีย์ 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

เพิ่มออบเจ็กต์การดำเนินการเรียกดูที่กำหนดเอง Bundle ทั้งหมดลงใน ArrayList

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 รายการ

  • แยกวิเคราะห์รหัสสื่อจาก 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 จะเรียกใช้เมธอดของออบเจ็กต์ Callback อย่างใดอย่างหนึ่ง

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

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

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

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

onPlayFromMediaId()
เรียกใช้เมื่อผู้ใช้เลือกเล่นรายการที่เฉพาะเจาะจง โดยระบบจะส่งรหัสที่บริการเบราว์เซอร์สื่อกำหนดให้กับรายการสื่อในลําดับชั้นเนื้อหาให้กับเมธอด
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 จะแสดงปุ่มสำหรับการดำเนินการที่เปิดใช้แต่ละรายการ และคิวการเล่น เมื่อคลิกปุ่มแล้ว ระบบจะเรียกใช้ Callback ที่เกี่ยวข้องจาก 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 ใช้ค่าคงที่เหล่านี้เป็นคีย์ในแพ็กเกจ และใช้บูลีน true เป็นค่า

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

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

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

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

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

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

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

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

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

Kotlin

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

Java

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .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

ไอคอนสําหรับการดําเนินการที่กำหนดเอง

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

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

ระบุสไตล์ไอคอนอื่นสําหรับการดําเนินการที่ปิดใช้

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

รูปที่ 6 ตัวอย่างไอคอนการดำเนินการที่กำหนดเองซึ่งไม่ตรงกับสไตล์

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

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

นอกจากนี้ คุณยังตั้งค่าข้อมูลเพิ่มเติม KEY_IMMERSIVE_AUDIO เพื่อแจ้งให้ OEM รถยนต์ทราบว่านี่เป็นเสียงสมจริง และ 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 ยังจดจําคําค้นหาด้วยเสียงเพื่อควบคุมการเล่น เช่น "หยุดเพลงชั่วคราว" และ "เพลงถัดไป" และจับคู่คําสั่งเหล่านี้กับการเรียกกลับเซสชันสื่อที่เหมาะสม เช่น 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 จำเป็นต้องเปิดแอปโทรศัพท์เพื่อแก้ไขข้อผิดพลาด ให้แจ้งข้อมูลดังกล่าวให้ผู้ใช้ทราบในข้อความ เช่น ข้อความแสดงข้อผิดพลาดอาจระบุว่า "ลงชื่อเข้าใช้ [ชื่อแอปของคุณ]" แทนที่จะเป็น "โปรดลงชื่อเข้าใช้"

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