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

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 ที่แอปสื่อของคุณนำมาใช้ซึ่งเป็นไปตาม API ของ MediaBrowserServiceCompat แอปของคุณใช้บริการนี้เพื่อแสดงเนื้อหา
เบราว์เซอร์สื่อ
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 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 ที่เรียกดูได้ ซึ่งจะลบล้างสไตล์เนื้อหาเริ่มต้นสำหรับทั้งตัว 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. รายการสื่อ ก. ที่มี extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. รายการสื่อ ข. ที่มี 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 ชื่อ "อัลบั้ม" ซึ่งมีรายการสื่อ ข.
  • กลุ่มที่ 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: ระบุจำนวนความคืบหน้าในการดูเนื้อหาแบบยาวเป็นจำนวนทศนิยมระหว่าง 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 โดยใช้คีย์ 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 รายการ

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

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

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

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

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

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

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

รูปที่ 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 (เพื่อลิงก์จากคำอธิบาย) โปรดดูรายละเอียดในเอกสารอ้างอิงสำหรับฟิลด์ข้อมูลเมตาเหล่านั้น

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

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

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