Tạo ứng dụng đa phương tiện cho ô tô

Android Auto và Android Automotive OS giúp bạn đưa nội dung ứng dụng đa phương tiện của mình đến với người dùng trên ô tô của họ. Ứng dụng đa phương tiện cho ô tô phải cung cấp một dịch vụ trình duyệt nội dung đa phương tiện để Android Auto và Android Automotive OS, hoặc một ứng dụng khác có trình duyệt nội dung đa phương tiện, có thể phát hiện thấy và hiển thị nội dung của bạn.

Hướng dẫn này giả định rằng bạn đã có một ứng dụng đa phương tiện để phát âm thanh trên điện thoại và ứng dụng đa phương tiện đó phù hợp với cấu trúc của ứng dụng đa phương tiện trên Android.

Hướng dẫn này mô tả các thành phần bắt buộc của MediaBrowserServiceMediaSession mà ứng dụng của bạn cần để hoạt động trên Android Auto hoặc Android Automotive OS. Sau khi hoàn thành cơ sở hạ tầng đa phương tiện cốt lõi, bạn có thể thêm tính năng hỗ trợ Android Autothêm tính năng hỗ trợ Android Automotive OS vào ứng dụng đa phương tiện của mình.

Trước khi bạn bắt đầu

  1. Xem tài liệu về API nội dung nghe nhìn của Android.
  2. Xem bài viết Tạo ứng dụng đa phương tiện để biết hướng dẫn thiết kế.
  3. Xem các thuật ngữ và khái niệm chính được liệt kê trong phần này.

Các thuật ngữ và khái niệm chính

Dịch vụ trình duyệt nội dung đa phương tiện
Đây là một dịch vụ Android do ứng dụng đa phương tiện của bạn triển khai và tuân thủ API MediaBrowserServiceCompat. Ứng dụng của bạn dùng dịch vụ này để hiển thị nội dung.
Trình duyệt nội dung đa phương tiện
Đây là một API được các ứng dụng đa phương tiện dùng để khám phá dịch vụ trình duyệt nội dung đa phương tiện và hiển thị nội dung của các ứng dụng đó. Android Auto và Android Automotive OS dùng một trình duyệt nội dung đa phương tiện để tìm dịch vụ trình duyệt nội dung đa phương tiện của ứng dụng.
Mục nội dung đa phương tiện

Trình duyệt nội dung đa phương tiện sắp xếp nội dung theo cây đối tượng MediaItem. Một mục nội dung đa phương tiện có thể chứa một hoặc cả hai cờ sau:

  • FLAG_PLAYABLE: cho biết mục là một lá trên cây nội dung. Mục này biểu thị một luồng âm thanh duy nhất, chẳng hạn như một bài hát trong đĩa nhạc, một chương trong sách nói hoặc một tập podcast.
  • FLAG_BROWSABLE: cho biết mục là một nút trên cây nội dung và có các phần tử con. Ví dụ: mục này biểu thị một đĩa nhạc và phần tử con của đĩa nhạc đó là các bài hát trong đĩa nhạc.

Mục nội dung đa phương tiện vừa có thể xem vừa có thể phát hoạt động như một danh sách phát. Bạn có thể chọn chính mục đó để phát tất cả các phần tử con, hoặc bạn có thể duyệt qua các phần tử con.

Được tối ưu hoá cho xe

Hoạt động trong một ứng dụng trên Android Automotive OS tuân thủ nguyên tắc thiết kế của Android Automotive OS. Giao diện cho các hoạt động này không được vẽ bằng Android Automotive OS, vì vậy, bạn phải đảm bảo rằng ứng dụng của mình tuân thủ các nguyên tắc thiết kế. Thông thường, nguyên tắc này bao gồm đích nhấn và kích thước phông chữ lớn hơn, hỗ trợ chế độ ban ngày và ban đêm, cũng như tỷ lệ tương phản cao hơn.

Chúng tôi chỉ cho phép hiển thị giao diện người dùng được tối ưu hoá cho xe khi Các hạn chế về trải nghiệm người dùng trên ô tô (CUXR) không có hiệu lực vì các giao diện này có thể cần người dùng chú ý hoặc tương tác lâu hơn. CUXR không có hiệu lực khi ô tô dừng hoặc đỗ nhưng vẫn luôn có hiệu lực khi ô tô chuyển động.

Bạn không cần thiết kế hoạt động cho Android Auto vì Android Auto sẽ vẽ giao diện riêng được tối ưu hoá cho xe bằng cách sử dụng thông tin lấy từ dịch vụ trình duyệt nội dung đa phương tiện của bạn.

Định cấu hình tệp kê khai của ứng dụng

Trước khi có thể tạo dịch vụ trình duyệt nội dung đa phương tiện, bạn cần định cấu hình tệp kê khai của ứng dụng.

Khai báo dịch vụ trình duyệt nội dung đa phương tiện

Cả Android Auto và Android Automotive OS đều kết nối với ứng dụng của bạn thông qua dịch vụ trình duyệt nội dung đa phương tiện để duyệt qua các mục nội dung đa phương tiện. Khai báo dịch vụ trình duyệt nội dung đa phương tiện trong tệp kê khai để cho phép Android Auto và Android Automotive OS khám phá dịch vụ này cũng như kết nối với ứng dụng của bạn.

Đoạn mã sau đây minh hoạ cách khai báo dịch vụ trình duyệt nội dung đa phương tiện trong tệp kê khai của bạn. Hãy đưa mã này vào tệp kê khai cho mô-đun Android Automotive OS và tệp kê khai cho ứng dụng điện thoại.

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

Chỉ định biểu tượng ứng dụng

Bạn cần chỉ định biểu tượng ứng dụng mà Android Auto và Android Automotive OS có thể dùng để biểu thị ứng dụng trong giao diện người dùng hệ thống. Bắt buộc phải có 2 loại biểu tượng:

  • Biểu tượng trình chạy
  • Biểu tượng phân bổ

Biểu tượng trình chạy

Biểu tượng trình chạy đại diện cho ứng dụng của bạn trong giao diện người dùng hệ thống, chẳng hạn như trên trình chạy và khay biểu tượng. Bạn có thể chỉ định rằng bạn muốn sử dụng biểu tượng của ứng dụng dành cho thiết bị di động để đại diện cho ứng dụng đa phương tiện trên ô tô bằng cách sử dụng nội dung khai báo tệp kê khai sau:

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

Để sử dụng biểu tượng khác với biểu tượng của ứng dụng dành cho thiết bị di động, hãy đặt thuộc tính android:icon trên phần tử <service> của dịch vụ trình duyệt nội dung đa phương tiện trong tệp kê khai:

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

Biểu tượng phân bổ

Hình 1. Biểu tượng phân bổ trên thẻ nội dung đa phương tiện.

Biểu tượng phân bổ được dùng ở những vị trí mà nội dung đa phương tiện được ưu tiên, chẳng hạn như trên thẻ nội dung đa phương tiện. Hãy cân nhắc việc sử dụng lại biểu tượng nhỏ đối với các thông báo. Biểu tượng này phải là đơn sắc. Bạn có thể chỉ định một biểu tượng dùng để biểu thị ứng dụng của mình bằng cách sử dụng nội dung khai báo tệp kê khai sau:

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

Tạo dịch vụ trình duyệt nội dung đa phương tiện

Bạn tạo một dịch vụ trình duyệt nội dung đa phương tiện bằng cách mở rộng lớp MediaBrowserServiceCompat. Sau đó, cả Android Auto và Android Automotive OS đều có thể dùng dịch vụ của bạn để thực hiện những tác vụ sau:

  • Duyệt qua hệ phân cấp nội dung của ứng dụng để hiển thị trình đơn cho người dùng.
  • Lấy mã thông báo cho đối tượng MediaSessionCompat của ứng dụng để điều khiển chế độ phát âm thanh.

Bạn cũng có thể sử dụng dịch vụ trình duyệt nội dung đa phương tiện để cho phép các ứng dụng khác truy cập vào nội dung đa phương tiện từ ứng dụng của bạn. Các ứng dụng đa phương tiện này có thể là ứng dụng khác trên điện thoại của người dùng hoặc có thể là ứng dụng từ xa khác.

Quy trình dịch vụ trình duyệt nội dung nghe nhìn

Phần này mô tả cách Android Automotive OS và Android Auto tương tác với dịch vụ trình duyệt nội dung đa phương tiện của bạn trong quy trình làm việc thông thường của người dùng.

  1. Người dùng chạy ứng dụng của bạn trên Android Automotive OS hoặc Android Auto.
  2. Android Automotive OS hoặc Android Auto kết nối với dịch vụ trình duyệt nội dung đa phương tiện của ứng dụng bằng phương thức onCreate(). Trong cách triển khai phương thức onCreate(), bạn phải tạo và đăng ký đối tượng MediaSessionCompat cũng như đối tượng gọi lại của nó.
  3. Android Automotive OS hoặc Android Auto gọi phương thức onGetRoot() của dịch vụ để lấy mục nội dung nghe nhìn gốc trong hệ thống phân cấp nội dung của bạn. Mục nội dung nghe nhìn gốc không hiển thị. Thay vào đó, mục này được dùng để truy xuất thêm nội dung từ ứng dụng của bạn.
  4. Android Automotive OS hoặc Android Auto gọi phương thức onLoadChildren() của dịch vụ để lấy phần tử con của mục nội dung nghe nhìn gốc. Android Automotive OS và Android Auto hiển thị các mục nội dung đa phương tiện này ở cấp cao nhất của mục nội dung. Hãy xem phần Cấu trúc trình đơn gốc trên trang này để biết thêm thông tin về những gì hệ thống dự kiến ở cấp độ này.
  5. Nếu người dùng chọn một mục nội dung đa phương tiện có thể xem, thì phương thức onLoadChildren() của dịch vụ sẽ được gọi lại để truy xuất phần tử con của mục trong trình đơn đã chọn.
  6. Nếu người dùng chọn một mục nội dung nghe nhìn có thể phát, thì Android Automotive OS hoặc Android Auto sẽ gọi phương thức gọi lại phiên phát nội dung nghe nhìn thích hợp để thực hiện thao tác đó.
  7. Nếu được ứng dụng của bạn hỗ trợ, người dùng cũng có thể tìm kiếm nội dung của bạn. Trong trường hợp này, Android Automotive OS hoặc Android Auto sẽ gọi phương thức onSearch() của dịch vụ.

Xây dựng hệ thống phân cấp nội dung

Android Auto và Android Automotive OS gọi dịch vụ trình duyệt nội dung đa phương tiện của ứng dụng để xác định nội dung nào có sẵn. Bạn cần triển khai 2 phương thức trong dịch vụ trình duyệt nội dung đa phương tiện để hỗ trợ việc này: onGetRoot()onLoadChildren()

Triển khai onGetRoot

Phương thức onGetRoot() của dịch vụ sẽ trả về thông tin liên quan đến nút gốc trong hệ thống phân cấp nội dung. Android Auto và Android Automotive OS dùng nút gốc này để yêu cầu nội dung còn lại bằng phương thức onLoadChildren().

Đoạn mã sau đây cho thấy cách triển khai đơn giản của phương thức 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);
}

Để biết ví dụ chi tiết hơn về phương thức này, hãy xem phương thức onGetRoot() trong ứng dụng mẫu Universal Android Music Player trên GitHub.

Thêm phương thức xác thực gói cho onGetRoot()

Khi bạn thực hiện lệnh gọi đến phương thức onGetRoot() của dịch vụ, gói gọi sẽ chuyển thông tin nhận dạng đến dịch vụ của bạn. Dịch vụ của bạn có thể dùng thông tin này để quyết định xem gói đó có thể truy cập vào nội dung của bạn hay không. Ví dụ: bạn có thể chỉ cho phép các gói được phê duyệt truy cập vào nội dung của ứng dụng bằng cách so sánh clientPackageName với danh sách cho phép và xác minh chứng chỉ dùng để ký APK của gói đó. Nếu không thể xác minh gói, hãy trả về null để từ chối quyền truy cập vào nội dung của bạn.

Để cấp cho các ứng dụng hệ thống (chẳng hạn như Android Auto và Android Automotive OS) quyền truy cập vào nội dung, dịch vụ của bạn phải luôn trả về một BrowserRoot khác rỗng khi các ứng dụng hệ thống này gọi phương thức onGetRoot(). Chữ ký của ứng dụng hệ thống Android Automotive OS có thể khác nhau tuỳ theo nhà sản xuất và mẫu xe ô tô. Vì vậy, bạn cần cho phép các kết nối từ mọi ứng dụng hệ thống để hỗ trợ Android Automotive OS một cách hiệu quả.

Đoạn mã sau đây minh hoạ cách dịch vụ của bạn có thể xác thực rằng gói gọi là một ứng dụng hệ thống:

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
}

Đoạn mã này là phần trích dẫn từ lớp PackageValidator trong ứng dụng mẫu Universal Android Music Player trên GitHub. Hãy xem lớp đó để biết ví dụ chi tiết hơn về cách triển khai tính năng xác thực gói cho phương thức onGetRoot() của dịch vụ.

Ngoài việc cho phép các ứng dụng hệ thống, bạn phải cho phép Trợ lý Google kết nối với MediaBrowserService của mình. Hãy lưu ý rằng Trợ lý Google có tên gói riêng cho điện thoại (bao gồm Android Auto) và cho Android Automotive OS.

Triển khai onLoadChildren()

Sau khi nhận được đối tượng nút gốc, Android Auto và Android Automotive OS sẽ tạo trình đơn cấp cao nhất bằng cách gọi onLoadChildren() trên đối tượng nút gốc để lấy phần tử con. Các ứng dụng khách xây dựng trình đơn con bằng cách gọi cùng một phương thức này thông qua đối tượng nút con.

Mỗi nút trong hệ thống phân cấp nội dung của bạn được biểu thị bằng một đối tượng MediaBrowserCompat.MediaItem. Mỗi mục nội dung đa phương tiện này được xác định bằng một chuỗi mã nhận dạng duy nhất. Ứng dụng khách coi những chuỗi mã này là mã thông báo mờ. Khi một ứng dụng khách muốn duyệt đến trình đơn con hoặc phát một mục nội dung đa phương tiện, ứng dụng đó sẽ chuyển mã thông báo. Ứng dụng của bạn chịu trách nhiệm liên kết mã thông báo với mục nội dung đa phương tiện thích hợp.

Đoạn mã sau đây cho thấy cách triển khai đơn giản của phương thức onLoadChildren():

Kotlin

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

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

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

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

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

Java

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

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

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

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

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

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

Để biết ví dụ đầy đủ về phương thức này, hãy xem phương thức onLoadChildren() trong ứng dụng mẫu Universal Android Music Player trên GitHub.

Cấu trúc trình đơn gốc

Hình 2. Nội dung gốc xuất hiện ở dạng thẻ điều hướng.

Android Auto và Android Automotive OS có những hạn chế cụ thể về cấu trúc của trình đơn gốc. Các hạn chế này được truyền đến MediaBrowserService thông qua gợi ý gốc. Hệ thống có thể đọc gợi ý gốc thông qua đối số Bundle được truyền vào onGetRoot(). Khi làm theo các gợi ý này, hệ thống có thể hiển thị nội dung gốc một cách tối ưu ở dạng thẻ điều hướng. Nếu bạn không làm theo các gợi ý này, hệ thống có thể bỏ hoặc ẩn bớt một số nội dung gốc. 2 gợi ý được gửi:

Hãy dùng mã sau để đọc các gợi ý gốc có liên quan:

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

Bạn có thể chọn phân nhánh logic cho cấu trúc của hệ phân cấp nội dung dựa trên giá trị của các gợi ý này, cụ thể là nếu hệ phân cấp của bạn thay đổi giữa các hoạt động tích hợp MediaBrowser bên ngoài Android Auto và Android Automotive OS. Ví dụ: nếu thường hiển thị một mục gốc có thể phát, bạn nên lồng mục đó vào một mục gốc có thể xem, do giá trị của gợi ý về cờ được hỗ trợ.

Ngoài gợi ý gốc, bạn cần tuân thủ một số nguyên tắc bổ sung để đảm bảo các thẻ hiển thị một cách tối ưu:

  • Cung cấp biểu tượng đơn sắc, tốt nhất là màu trắng, cho từng mục trên thẻ.
  • Cung cấp các nhãn ngắn gọn nhưng dễ hiểu cho từng mục trên thẻ. Việc tạo nhãn ngắn gọn sẽ giúp hạn chế khả năng chuỗi bị cắt bớt.

Hiển thị hình minh hoạ nội dung đa phương tiện

Hình minh hoạ cho các mục nội dung đa phương tiện phải được chuyển ở dạng URI cục bộ bằng ContentResolver.SCHEME_CONTENT hoặc ContentResolver.SCHEME_ANDROID_RESOURCE. URI cục bộ này phải phân giải đến tệp bitmap hoặc vectơ vẽ được trong tài nguyên của ứng dụng. Đối với đối tượng MediaDescriptionCompat biểu thị các mục trong hệ thống phân cấp nội dung, hãy chuyển URI thông qua setIconUri(). Đối với đối tượng MediaMetadataCompat biểu thị mục đang phát, hãy chuyển URI qua putString() bằng một trong những khoá sau đây:

Các bước sau đây mô tả cách tải hình minh hoạ xuống từ một URI web và hiển thị hình minh hoạ đó thông qua URI cục bộ. Để biết ví dụ hoàn chỉnh hơn, hãy xem cách triển khai openFile() và các phương thức xung quanh trong ứng dụng mẫu Universal Android Music Player.

  1. Tạo một URI content:// tương ứng với URI web. Dịch vụ trình duyệt nội dung đa phương tiện và phiên phát nội dung đa phương tiện sẽ chuyển URI nội dung này sang Android Auto và 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. Trong quá trình triển khai ContentProvider.openFile(), hãy kiểm tra xem tệp có tồn tại cho URI tương ứng hay không. Nếu không có, hãy tải xuống và lưu tệp hình ảnh vào bộ nhớ đệm. Đoạn mã sau đây sử dụng 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);
    }

Để biết thêm thông tin chi tiết về trình cung cấp nội dung, hãy tham khảo bài viết Tạo trình cung cấp nội dung.

Áp dụng kiểu nội dung

Sau khi xây dựng hệ thống phân cấp nội dung bằng cách sử dụng các mục có thể xem hoặc có thể phát, bạn có thể áp dụng kiểu nội dung giúp xác định cách các mục đó xuất hiện trên ô tô.

Bạn có thể dùng các kiểu nội dung sau:

Mục trong danh sách

Kiểu nội dung này ưu tiên tiêu đề và siêu dữ liệu hơn hình ảnh.

Mục trong lưới

Kiểu nội dung này ưu tiên hình ảnh hơn tiêu đề và siêu dữ liệu.

Đặt kiểu nội dung mặc định

Bạn có thể đặt giá trị mặc định chung cho cách hiển thị mục nội dung đa phương tiện bằng việc đưa các hằng số nhất định vào gói dữ liệu bổ sung BrowserRoot cho phương thức onGetRoot() của dịch vụ. Android Auto và Android Automotive OS sẽ đọc gói này và tìm kiếm các hằng số đó để xác định kiểu phù hợp.

Bạn có thể dùng các dữ liệu bổ sung sau làm khoá trong gói:

Các khoá có thể liên kết với những giá trị hằng số nguyên sau đây để thay đổi cách trình bày các mục đó:

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: các mục tương ứng được trình bày ở dạng mục trong danh sách.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: các mục tương ứng được trình bày ở dạng mục trong lưới.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: các mục tương ứng được trình bày ở dạng mục trong danh sách "danh mục". Các mục này giống với mục danh sách thông thường, ngoại trừ một điều là biểu tượng của mục có đường viền bao quanh, vì biểu tượng trông đẹp hơn khi có kích thước nhỏ. Các biểu tượng phải là các vectơ vẽ được có thể phủ màu. Gợi ý này sẽ chỉ được cung cấp cho các mục có thể xem.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: các mục tương ứng được trình bày ở dạng mục trong lưới "danh mục". Các mục này giống với mục lưới thông thường, ngoại trừ một điều là biểu tượng của mục có đường viền bao quanh, vì biểu tượng trông đẹp hơn khi có kích thước nhỏ. Các biểu tượng phải là các vectơ vẽ được có thể phủ màu. Gợi ý này sẽ chỉ được cung cấp cho các mục có thể xem.

Đoạn mã sau đây cho biết cách đặt kiểu nội dung mặc định cho mục có thể xem thành lưới và mục có thể phát thành danh sách:

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

Đặt kiểu nội dung cho từng mục

Content Style API hỗ trợ bạn ghi đè kiểu nội dung mặc định cho mọi phần tử con của mục nội dung đa phương tiện có thể xem, cũng như cho mọi mục nội dung đa phương tiện.

Để ghi đè giá trị mặc định cho phần tử con của một mục nội dung đa phương tiện có thể xem, hãy tạo một gói dữ liệu bổ sung trong MediaDescription của mục nội dung đa phương tiện đó rồi thêm chính gợi ý đã đề cập trước đó. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE áp dụng cho phần tử con có thể phát của mục đó, còn DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE áp dụng cho phần tử con có thể xem của mục đó.

Để ghi đè giá trị mặc định cho chính mục nội dung đa phương tiện cụ thể, không phải phần tử con, hãy tạo gói dữ liệu bổ sung trong MediaDescription của mục nội dung đa phương tiện rồi thêm gợi ý bằng khoá DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM. Hãy dùng chính các giá trị mô tả trước đó để chỉ định cách trình bày mục đó.

Đoạn mã sau đây cho biết cách tạo một MediaItem có thể xem ghi đè kiểu nội dung mặc định cho chính nó và phần tử con. Mục này sẽ định kiểu chính nó là một mục danh sách danh mục, phần tử con có thể xem là mục trong danh sách và phần tử con có thể phát là mục trong lưới:

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

Nhóm các mục bằng gợi ý tiêu đề

Để nhóm các mục nội dung đa phương tiện có liên quan lại với nhau, bạn có thể dùng gợi ý cho từng mục. Mọi mục nội dung đa phương tiện trong một nhóm đều cần khai báo gói dữ liệu bổ sung trong MediaDescription chứa dữ liệu liên kết với khoá DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE và một giá trị chuỗi giống hệt. Hãy bản địa hoá chuỗi này để dùng làm tiêu đề của nhóm.

Đoạn mã sau đây cho biết cách tạo MediaItem có tiêu đề nhóm con là "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*/);
}

Ứng dụng của bạn phải chuyển mọi mục nội dung đa phương tiện mà bạn muốn nhóm lại với nhau ở dạng khối liền kề. Ví dụ: giả sử bạn muốn hiển thị 2 nhóm mục nội dung đa phương tiện: "Bài hát" và "Đĩa nhạc" theo thứ tự đó và ứng dụng của bạn đã chuyển 5 mục nội dung đa phương tiện theo thứ tự sau:

  1. Mục nội dung đa phương tiện A với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Mục nội dung đa phương tiện B với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  3. Mục nội dung đa phương tiện C với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Mục nội dung đa phương tiện D với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  5. Mục nội dung đa phương tiện E với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Vì các mục nội dung đa phương tiện trong nhóm "Bài hát" và "Đĩa nhạc" không được lưu giữ cùng nhau trong khối liền kề, do đó, Android Auto và Android Automotive OS sẽ hiểu thành 4 nhóm sau đây:

  • Nhóm 1 có tên là "Bài hát" chứa mục nội dung đa phương tiện A
  • Nhóm 2 có tên là "Đĩa nhạc" chứa mục nội dung đa phương tiện B
  • Nhóm 3 có tên là "Bài hát" chứa các mục nội dung đa phương tiện C và D
  • Nhóm 4 có tên là "Đĩa nhạc" chứa mục nội dung đa phương tiện E

Để hiển thị các mục này trong 2 nhóm, ứng dụng của bạn phải chuyển các mục nội dung đa phương tiện theo thứ tự sau:

  1. Mục nội dung đa phương tiện A với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Mục nội dung đa phương tiện C với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  3. Mục nội dung đa phương tiện D với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Mục nội dung đa phương tiện B với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  5. Mục nội dung đa phương tiện E với extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Cho thấy chỉ báo siêu dữ liệu bổ sung

Bạn có thể thêm chỉ báo siêu dữ liệu bổ sung để cung cấp thông tin nhanh về nội dung trong cây trình duyệt nội dung đa phương tiện và trong khi phát. Trong cây duyệt qua, Android Auto và Android Automotive OS đọc dữ liệu bổ sung liên kết với mục và tìm những hằng số cụ thể để xác định chỉ báo được xuất hiện. Trong khi phát nội dung đa phương tiện, Android Auto và Android Automotive OS sẽ đọc siêu dữ liệu của phiên phát nội dung đa phương tiện đó rồi tìm những hằng số cụ thể để xác định chỉ báo được xuất hiện.

Hình 3. Khung hiển thị phát có siêu dữ liệu xác định bài hát và nghệ sĩ, cũng như một biểu tượng cho biết nội dung phản cảm.

Hình 4. Khung hiển thị duyệt qua có một dấu chấm cho nội dung chưa phát trên mục đầu tiên và một thanh tiến trình cho nội dung đã phát một phần trên mục thứ hai.

Các hằng số sau đây có thể được dùng trong cả dữ liệu bổ sung mô tả MediaItem lẫn dữ liệu bổ sung MediaMetadata:

Hằng số sau có thể chỉ được dùng trong dữ liệu bổ sung mô tả MediaItem:

Để cho thấy các chỉ báo khi người dùng đang duyệt qua cây trình duyệt nội dung đa phương tiện, hãy tạo một gói dữ liệu bổ sung bao gồm một hoặc nhiều hằng số này và chuyển gói đó cho phương thức MediaDescription.Builder.setExtras().

Đoạn mã sau đây cho biết cách hiển thị chỉ báo cho một mục nội dung đa phương tiện tục tĩu đã phát 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 */);

Để cho thấy chỉ báo đối với mục nội dung đa phương tiện đang phát, bạn có thể khai báo các giá trị Long cho METADATA_KEY_IS_EXPLICIT hoặc EXTRA_DOWNLOAD_STATUS trong MediaMetadataCompat của mediaSession. Bạn không thể hiển thị chỉ báo DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS hoặc DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE trong khung hiển thị phát.

Đoạn mã sau đây minh hoạ cách cho biết rằng bài hát hiện tại trong khung hiển thị phát là tục tĩu và đã được tải xuống:

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());

Cập nhật thanh tiến trình trong khung duyệt qua khi đang phát nội dung

Nư trình bày ở trên, bạn có thể sử dụng dữ liệu bổ sung DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE để hiển thị thanh tiến trình của nội dung được phát một phần trong khung hiển thị duyệt qua. Tuy nhiên, nếu người dùng tiếp tục phát nội dung đã phát một phần qua Android Auto hoặc Android Automotive OS, thì chỉ báo đó trở nên không chính xác khi thời gian trôi qua.

Để Android Auto và Android Automotive OS luôn cập nhật thanh tiến trình, bạn có thể cung cấp thêm thông tin trong MediaMetadataCompatPlaybackStateCompat để liên kết nội dung hiện tại với các mục nội dung đa phương tiện trong khung hiển thị duyệt qua. Các yêu cầu sau đây phải được đáp ứng thì mục nội dung đa phương tiện mới có thanh tiến trình cập nhật tự động:

Đoạn mã sau đây minh hoạ cách cho biết rằng mục đang phát được liên kết với một mục trong khung hiển thị duyệt qua:

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());

Hình 5. Khung hiển thị phát có tuỳ chọn "Kết quả tìm kiếm" để xem các mục nội dung đa phương tiện liên quan đến nội dung tìm kiếm bằng giọng nói của người dùng.

Ứng dụng của bạn có thể cung cấp kết quả tìm kiếm theo ngữ cảnh hiển thị với người dùng khi họ bắt đầu một truy vấn tìm kiếm. Android Auto và Android Automotive OS hiển thị các kết quả này thông qua giao diện truy vấn tìm kiếm hoặc thông qua các thành phần hướng đến truy vấn đã thực hiện trước đó trong phiên phát. Để tìm hiểu thêm, hãy xem phần Hỗ trợ thao tác bằng giọng nói trong hướng dẫn này.

Để hiển thị kết quả tìm kiếm có thể xem, hãy thêm khoá hằng số BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED vào gói dữ liệu bổ sung cho phương thức onGetRoot() của dịch vụ, liên kết đến giá trị boolean là true.

Đoạn mã sau đây cho biết cách bật tính năng hỗ trợ trong phương thức 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);
}

Để bắt đầu cung cấp kết quả tìm kiếm, hãy ghi đè phương thức onSearch() trong dịch vụ trình duyệt nội dung đa phương tiện của bạn. Android Auto và Android Automotive OS sẽ chuyển tiếp các cụm từ tìm kiếm của người dùng đến phương thức này bất cứ khi nào người dùng gọi giao diện truy vấn tìm kiếm hoặc thành phần "Kết quả tìm kiếm".

Bạn có thể sắp xếp kết quả tìm kiếm từ phương thức onSearch() của dịch vụ bằng cách sử dụng mục tiêu đề để giúp kết quả dễ xem hơn. Ví dụ: nếu ứng dụng của bạn phát nhạc, thì bạn có thể sắp xếp kết quả tìm kiếm theo đĩa nhạc, nghệ sĩ và bài hát.

Đoạn mã sau đây cho thấy cách triển khai đơn giản của phương thức 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.
}

Thao tác duyệt qua tuỳ chỉnh

Một thao tác duyệt qua tuỳ chỉnh.

Hình 6. Một thao tác duyệt qua tuỳ chỉnh

Thao tác duyệt qua tuỳ chỉnh cho phép bạn thêm các nhãn và biểu tượng tuỳ chỉnh vào các đối tượng MediaItem trên ứng dụng của bạn dùng cho ứng dụng đa phương tiện trên ô tô, đồng thời xử lý các hoạt động tương tác của người dùng với những thao tác này. Thao tác này giúp bạn mở rộng chức năng của Ứng dụng đa phương tiện theo nhiều cách, chẳng hạn như thêm các thao tác "Tải xuống", "Thêm vào hàng đợi", "Phát đài", "Thêm vào mục yêu thích" hoặc "Xoá".

Một trình đơn mục bổ sung cho các thao tác duyệt qua tuỳ chỉnh.

Hình 7. Mục bổ sung cho Thao tác duyệt qua tuỳ chỉnh

Nếu có nhiều thao tác tuỳ chỉnh hơn số lượng mà OEM (Nhà sản xuất thiết bị gốc) cho phép hiển thị, thì người dùng sẽ thấy một trình đơn mục bổ sung.

Thao tác này hoạt động như thế nào?

Mỗi Thao tác duyệt qua tuỳ chỉnh được xác định bằng những yếu tố sau:

  • Mã thao tác (một giá trị nhận dạng duy nhất ở dạng chuỗi)
  • Nhãn thao tác (văn bản mà người dùng nhìn thấy)
  • URI biểu tượng của thao tác (một vectơ vẽ được có thể được phủ màu)

Bạn xác định chung một danh sách gồm các Thao tác duyệt qua tuỳ chỉnh trong BrowseRoot. Sau đó, bạn có thể đính kèm một nhóm nhỏ các thao tác này vào từng MediaItem. một

Khi người dùng tương tác với một Thao tác duyệt qua tuỳ chỉnh, ứng dụng của bạn sẽ nhận được một lệnh gọi lại trong onCustomAction(). Sau đó, bạn có thể xử lý thao tác và cập nhật danh sách gồm các thao tác cho MediaItem nếu cần. Đây là một danh sách hữu ích để thấy các thao tác có trạng thái như "Yêu thích" và "Tải xuống". Đối với những thao tác không cần cập nhật, chẳng hạn như "Phát đài", bạn không cần cập nhật danh sách gồm các thao tác đó.

Các thao tác duyệt qua tuỳ chỉnh trong thư mục gốc của nút duyệt qua.

Hình 8. Thanh công cụ của Thao tác duyệt qua tuỳ chỉnh

Bạn cũng có thể đính kèm Thao tác duyệt qua tuỳ chỉnh vào thư mục gốc của nút duyệt qua. Những thao tác này sẽ xuất hiện trong thanh công cụ phụ ở dưới thanh công cụ chính.

Cách triển khai Thao tác duyệt qua tuỳ chỉnh

Dưới đây là các bước để thêm các Thao tác duyệt qua tuỳ chỉnh vào dự án của bạn:

  1. Ghi đè 2 phương thức trong quá trình triển khai MediaBrowserServiceCompat:
  2. Phân tích cú pháp các giới hạn thao tác trong thời gian chạy:
    • Trong onGetRoot(), hãy lấy số thao tác tối đa được phép đối với mỗi MediaItem bằng cách sử dụng khoá BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT trong rootHints Bundle. Giới hạn bằng 0 cho biết tính năng này không được hệ thống này hỗ trợ.
  3. Tạo danh sách chung gồm các Thao tác duyệt xem tuỳ chỉnh:
    • Đối với mỗi thao tác, hãy tạo một đối tượng Bundle bằng các khoá sau: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: Mã thao tác * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: Nhãn thao tác * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: URI biểu tượng của thao tác * Thêm tất cả các đối tượng Bundle của thao tác vào một danh sách.
  4. Thêm danh sách chung vào BrowseRoot của bạn:
  5. Thêm các thao tác vào đối tượng MediaItem của bạn:
    • Bạn có thể thêm các thao tác vào từng đối tượng MediaItem bằng cách đưa danh sách mã thao tác vào phần bổ sung MediaDescriptionCompat thông qua khoá DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST. Danh sách này phải là một tập con trong danh sách chung gồm các thao tác mà bạn đã xác định trong BrowseRoot.
  6. Xử lý các thao tác và trả về tiến trình hoặc kết quả:
    • Trong onCustomAction, hãy xử lý thao tác dựa trên mã thao tác và mọi dữ liệu khác mà bạn cần. Bạn có thể lấy mã nhận dạng của MediaItem đã kích hoạt thao tác này từ các phần bổ sung bằng cách sử dụng khoá EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID..
    • Bạn có thể cập nhật danh sách các thao tác cho một MediaItem bằng cách đưa khoá EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM vào gói kết quả hoặc gói tiến trình.

Dưới đây là một số thay đổi bạn có thể thực hiện trong BrowserServiceCompat để bắt đầu sử dụng Thao tác duyệt qua tuỳ chỉnh.

Ghi đè BrowserServiceCompat

Bạn cần phải ghi đè các phương thức sau trong MediaBrowserServiceCompat.

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

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

Phân tích cú pháp giới hạn số thao tác

Bạn nên kiểm tra xem có bao nhiêu Thao tác duyệt qua tuỳ chỉnh được hỗ trợ.

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

Tạo một Thao tác duyệt qua tuỳ chỉnh

Mỗi thao tác cần được đóng gói vào một Bundle riêng biệt.

  • Mã thao tác
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
  • Nhãn thao tác
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
  • URI biểu tượng của thao tác
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")

Thêm các Thao tác duyệt qua tuỳ chỉnh vào Parceable ArrayList

Thêm mọi đối tượng Bundle của Thao tác duyệt qua tuỳ chỉnh vào một 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;
}

Thêm danh sách Thao tác duyệt qua tuỳ chỉnh vào thư mục gốc duyệt qua

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

Thêm các thao tác vào một 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);

Tạo kết quả onCustomAction

  • Cách phân tích cú pháp mediaId từ 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);
    }
  • Đối với các kết quả không đồng bộ, hãy tách kết quả. result.detach()
  • Tạo gói kết quả
    • Thông báo cho người dùng
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
    • Cập nhật mục (dùng để cập nhật các thao tác trong một mục)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
    • Mở chế độ xem Phát
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
    • Cập nhật nút Duyệt qua
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
  • Nếu xảy ra lỗi, hãy gọi result.sendError(resultBundle).
  • Nếu tiến trình cập nhật, hãy gọi result.sendProgressUpdate(resultBundle).
  • Hãy hoàn tất bằng cách gọi result.sendResult(resultBundle).

Cập nhật Trạng thái của thao tác

Bằng cách sử dụng phương thức result.sendProgressUpdate(resultBundle) với khoá EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, bạn có thể cập nhật MediaItem để phản ánh trạng thái mới của thao tác. Việc này giúp bạn cung cấp ý kiến phản hồi theo thời gian thực cho người dùng về tiến trình và kết quả của thao tác mà họ thực hiện.

Ví dụ: Thao tác tải xuống

Dưới đây là ví dụ về cách bạn có thể sử dụng tính năng này để triển khai thao tác tải xuống với 3 trạng thái:

  1. Tải xuống: Đây là trạng thái ban đầu của thao tác này. Khi người dùng chọn thao tác này, bạn có thể hoán đổi thao tác đó với thao tác "Đang tải xuống" và gọi sendProgressUpdate để cập nhật giao diện người dùng.
  2. Đang tải xuống: Trạng thái này cho biết quá trình tải xuống đang diễn ra. Bạn có thể sử dụng trạng thái này để hiển thị thanh tiến trình hoặc một chỉ báo khác cho người dùng.
  3. Đã tải xuống: Trạng thái này cho biết quá trình tải xuống đã hoàn tất. Khi quá trình tải xuống hoàn tất, bạn có thể hoán đổi trạng thái "Đang tải xuống" với "Đã tải xuống" và gọi sendResult bằng khoá EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM để cho biết rằng có mục cần được làm mới. Ngoài ra, bạn có thể sử dụng khoá EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE để hiển thị thông báo thành công cho người dùng.

Phương pháp này giúp bạn cung cấp ý kiến phản hồi rõ ràng cho người dùng về quy trình tải xuống và trạng thái hiện tại của quy trình đó. Bạn có thể thêm nhiều thông tin chi tiết hơn nữa bằng các biểu tượng để cho biết trạng thái tải xuống 25%, 50%, 75%.

Ví dụ: Thao tác thêm vào mục yêu thích

Một ví dụ khác là thao tác yêu thích với 2 trạng thái:

  1. Yêu thích: Thao tác này được hiển thị cho các mục không có trong danh sách yêu thích của người dùng. Khi người dùng chọn thao tác này, bạn có thể hoán đổi thao tác đó với "Đã thêm vào mục yêu thích" và gọi sendResult bằng khoá EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM để cập nhật giao diện người dùng.
  2. Đã thêm vào mục yêu thích: Thao tác này hiển thị cho các mục nằm trong danh sách yêu thích của người dùng. Khi người dùng chọn thao tác này, bạn có thể hoán đổi với thao tác "Thêm vào mục yêu thích" và gọi sendResult bằng khoá EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM để cập nhật giao diện người dùng.

Phương pháp này cung cấp một cách rõ ràng và nhất quán để người dùng quản lý các mục yêu thích của họ.

Những ví dụ này cho thấy tính linh hoạt của các Thao tác duyệt qua tuỳ chỉnh và cách bạn có thể dùng chúng để triển khai nhiều chức năng thông qua phản hồi theo thời gian thực nhằm nâng cao trải nghiệm người dùng trong ứng dụng đa phương tiện trên ô tô.

Để xem một ví dụ hoàn chỉnh về cách triển khai tính năng này, bạn có thể tham khảo dự án TestMediaApp.

Bật bộ điều khiển chế độ phát

Android Auto và Android Automotive OS sẽ gửi các lệnh điều khiển chế độ phát thông qua MediaSessionCompat của dịch vụ. Bạn phải đăng ký một phiên phát và triển khai các phương thức gọi lại liên quan.

Đăng ký một phiên phát nội dung nghe nhìn

Trong phương thức onCreate() của dịch vụ trình duyệt nội dung đa phương tiện, hãy tạo MediaSessionCompat rồi đăng ký phiên phát nội dung đa phương tiện bằng cách gọi setSessionToken().

Đoạn mã sau đây cho biết cách tạo và đăng ký một phiên phát nội dung đa phương tiện:

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());
    ...
}

Khi tạo đối tượng phiên phát nội dung đa phương tiện, bạn sẽ đặt đối tượng gọi lại được dùng để xử lý các yêu cầu điều khiển chế độ phát. Bạn tạo đối tượng gọi lại này bằng cách cung cấp cách triển khai lớp MediaSessionCompat.Callback cho ứng dụng của mình. Phần tiếp theo sẽ thảo luận về cách triển khai đối tượng này.

Triển khai lệnh phát

Khi người dùng yêu cầu phát một mục nội dung đa phương tiện trên ứng dụng của bạn, Android Automotive OS và Android Auto sẽ dùng lớp MediaSessionCompat.Callback từ đối tượng MediaSessionCompat của ứng dụng mà họ đã nhận được từ dịch vụ trình duyệt nội dung đa phương tiện của ứng dụng. Khi người dùng muốn điều khiển chế độ phát nội dung, chẳng hạn như tạm dừng phát hoặc chuyển sang bản nhạc tiếp theo, Android Auto và Android Automotive OS sẽ gọi một trong các phương thức của đối tượng gọi lại.

Để xử lý chế độ phát nội dung, ứng dụng của bạn phải mở rộng lớp trừu tượng MediaSessionCompat.Callback và triển khai các phương thức mà ứng dụng đó hỗ trợ.

Hãy triển khai mọi phương thức gọi lại sau đây nếu phù hợp với loại nội dung mà ứng dụng của bạn cung cấp:

onPrepare()
Được gọi khi nguồn nội dung đa phương tiện thay đổi. Android Automotive OS cũng gọi phương thức này ngay sau khi khởi động. Ứng dụng đa phương tiện của bạn phải triển khai phương thức này.
onPlay()
Được gọi nếu người dùng chọn phát mà không chọn một mục cụ thể. Ứng dụng của bạn phải phát nội dung mặc định hoặc nếu chế độ phát đã bị tạm dừng bằng onPause(), ứng dụng của bạn sẽ tiếp tục phát.

Lưu ý: Ứng dụng của bạn sẽ không tự động bắt đầu phát nhạc khi Android Automotive OS hoặc Android Auto kết nối với dịch vụ trình duyệt nội dung đa phương tiện. Để biết thêm thông tin, hãy xem phần đặt trạng thái phát ban đầu.

onPlayFromMediaId()
Được gọi khi người dùng chọn phát một mục cụ thể. Phương thức này được chuyển mã nhận dạng mà dịch vụ trình duyệt nội dung đa phương tiện đã chỉ định cho mục nội dung đa phương tiện trong hệ thống phân cấp nội dung của bạn.
onPlayFromSearch()
Được gọi khi người dùng chọn phát từ một truy vấn tìm kiếm. Ứng dụng phải đưa ra lựa chọn phù hợp dựa trên chuỗi tìm kiếm đã chuyển vào.
onPause()
Được gọi khi người dùng chọn tạm dừng phát.
onSkipToNext()
Được gọi khi người dùng chọn chuyển sang mục tiếp theo.
onSkipToPrevious()
Được gọi khi người dùng chọn chuyển về mục trước đó.
onStop()
Được gọi khi người dùng chọn ngừng phát.

Hãy ghi đè các phương thức này trong ứng dụng của bạn để cung cấp chức năng bạn muốn. Bạn không cần triển khai một phương thức nếu ứng dụng của bạn không hỗ trợ chức năng đó. Ví dụ: nếu ứng dụng của bạn phát một sự kiện phát trực tiếp, chẳng hạn như phát sóng chương trình thể thao, thì bạn không cần triển khai phương thức onSkipToNext(). Thay vào đó, bạn có thể sử dụng phương thức triển khai mặc định của onSkipToNext().

Ứng dụng của bạn không cần logic đặc biệt nào để phát nội dung qua loa trên ô tô. Khi nhận được một yêu cầu phát nội dung, ứng dụng của bạn có thể phát âm thanh giống như khi phát nội dung qua loa hoặc tai nghe của người dùng. Android Auto và Android Automotive OS sẽ tự động gửi nội dung âm thanh đến hệ thống của ô tô để phát qua loa của ô tô.

Để biết thêm thông tin về cách phát nội dung âm thanh, hãy xem bài viết Tổng quan về MediaPlayer, Tổng quan về ứng dụng âm thanhtổng quan về ExoPlayer.

Đặt thao tác phát tiêu chuẩn

Android Auto và Android Automotive OS cho thấy bộ điều khiển chế độ phát dựa trên các thao tác được bật trong đối tượng PlaybackStateCompat.

Theo mặc định, ứng dụng của bạn phải hỗ trợ những thao tác sau:

Ứng dụng của bạn có thể hỗ trợ thêm các thao tác sau đây nếu phù hợp với nội dung của ứng dụng:

Ngoài ra, bạn sẽ có tuỳ chọn để tạo hàng đợi phát mà người dùng có thể thấy (không bắt buộc). Để thực hiện việc này, hãy gọi phương thức setQueue()setQueueTitle(), bật hành động ACTION_SKIP_TO_QUEUE_ITEM rồi xác định lệnh gọi lại onSkipToQueueItem().

Ngoài ra, hãy hỗ trợ thêm cho biểu tượng Phát hiện nhạc. Đây là chỉ báo cho nội dung hiện đang phát. Để thực hiện việc này, hãy gọi phương thức setActiveQueueItemId() và truyền mã của nội dung đang phát vào hàng đợi. Bạn cần cập nhật setActiveQueueItemId() bất cứ khi nào có thay đổi trong hàng đợi.

Android Auto và Android Automotive OS hiển thị các nút cho mỗi thao tác đã bật cũng như hàng đợi phát. Khi bạn nhấp vào các nút, hệ thống sẽ gọi lệnh gọi lại tương ứng từ MediaSessionCompat.Callback.

Đặt trước không gian không sử dụng

Android Auto và Android Automotive OS sẽ đặt trước không gian trong giao diện người dùng cho các thao tác ACTION_SKIP_TO_PREVIOUSACTION_SKIP_TO_NEXT. Nếu ứng dụng của bạn không hỗ trợ một trong các chức năng này, thì Android Auto và Android Automotive OS sẽ sử dụng không gian để cho thấy mọi thao tác tuỳ chỉnh mà bạn tạo.

Nếu không muốn các thao tác tuỳ chỉnh lấp đầy không gian đó, thì bạn có thể đặt trước không gian. Bằng cách này, Android Auto và Android Automotive OS sẽ để trống không gian bất cứ khi nào ứng dụng của bạn không hỗ trợ chức năng tương ứng. Để làm vậy, hãy gọi phương thức setExtras() bằng một gói dữ liệu bổ sung chứa hằng số tương ứng với các chức năng đã đặt trước. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT tương ứng với ACTION_SKIP_TO_NEXTSESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV tương ứng với ACTION_SKIP_TO_PREVIOUS. Hãy dùng các hằng số này làm khoá trong gói và sử dụng boolean true làm giá trị.

Đặt PlaybackState ban đầu

Khi Android Auto và Android Automotive OS kết nối với dịch vụ trình duyệt nội dung đa phương tiện của bạn, phiên phát nội dung đa phương tiện sẽ thông báo trạng thái phát nội dung bằng PlaybackStateCompat. Ứng dụng của bạn sẽ không tự động bắt đầu phát nhạc khi Android Automotive OS hoặc Android Auto kết nối với dịch vụ trình duyệt nội dung đa phương tiện. Thay vào đó, hãy để Android Auto và Android Automotive OS tiếp tục hoặc bắt đầu phát dựa trên trạng thái của ô tô hoặc các thao tác của người dùng.

Để thực hiện việc này, hãy đặt PlaybackStateCompat ban đầu của phiên phát nội dung đa phương tiện thành STATE_STOPPED, STATE_PAUSED, STATE_NONE, hoặc STATE_ERROR.

Các phiên phát nội dung đa phương tiện trong Android Auto và Android Automotive OS chỉ kéo dài trong thời hạn của ổ đĩa. Vì vậy, người dùng sẽ thường xuyên bắt đầu và dừng các phiên phát này. Để tạo ra trải nghiệm liền mạch giữa các ổ đĩa, hãy theo dõi trạng thái phiên phát trước đó của người dùng để khi ứng dụng đa phương tiện nhận được yêu cầu tiếp tục, người dùng có thể tự động tiếp tục từ nơi họ đã dừng lại, chẳng hạn mục nội dung đa phương tiện phát gần đây nhất, PlaybackStateCompat và hàng đợi.

Thêm các thao tác phát tuỳ chỉnh

Bạn có thể thêm thao tác phát tuỳ chỉnh để cho thấy thao tác bổ sung mà ứng dụng đa phương tiện của bạn hỗ trợ. Nếu không gian cho phép (và không được đặt trước), thì Android sẽ thêm thao tác tuỳ chỉnh vào bộ điều khiển truyền tải. Nếu không, các thao tác tuỳ chỉnh sẽ xuất hiện trong trình đơn mục bổ sung. Các thao tác tuỳ chỉnh sẽ xuất hiện theo thứ tự được thêm vào PlaybackStateCompat.

Sử dụng thao tác tuỳ chỉnh để cung cấp hành vi khác với thao tác chuẩn. Không được dùng các thao tác này để thay thế hoặc sao chép các thao tác chuẩn.

Bạn có thể thêm thao tác tuỳ chỉnh bằng cách sử dụng phương thức addCustomAction() trong lớp PlaybackStateCompat.Builder.

Đoạn mã sau đây cho biết cách thêm một thao tác tuỳ chỉnh có tên "Bắt đầu kênh radio":

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());

Để biết ví dụ chi tiết hơn về phương thức này, hãy xem phương thức setCustomAction() trong ứng dụng mẫu Universal Android Music Player trên GitHub.

Sau khi tạo thao tác tuỳ chỉnh, phiên phát nội dung đa phương tiện của bạn có thể phản hồi thao tác đó bằng cách ghi đè phương thức onCustomAction().

Đoạn mã sau đây cho biết cách ứng dụng của bạn có thể phản hồi một thao tác "Bắt đầu kênh radio":

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

Để biết ví dụ chi tiết hơn về phương thức này, hãy xem phương thức onCustomAction trong ứng dụng mẫu Universal Android Music Player trên GitHub.

Biểu tượng cho các thao tác tuỳ chỉnh

Mỗi thao tác tuỳ chỉnh mà bạn tạo cần có một tài nguyên biểu tượng. Các ứng dụng trên ô tô có thể chạy trên nhiều kích thước và mật độ màn hình. Vì vậy, các biểu tượng bạn cung cấp phải là vectơ vẽ được. Vectơ vẽ được cho phép bạn mở rộng thành phần mà không làm mất chi tiết. Vectơ vẽ được cũng giúp dễ dàng căn chỉnh cạnh và góc theo ranh giới điểm ảnh ở độ phân giải nhỏ hơn.

Nếu một thao tác tuỳ chỉnh có trạng thái, chẳng hạn như thao tác bật/tắt chế độ phát, hãy cung cấp biểu tượng cho từng trạng thái để người dùng có thể nhận thấy rõ sự thay đổi khi chọn thao tác đó.

Cung cấp các kiểu biểu tượng thay thế cho thao tác bị vô hiệu hoá

Khi không dùng được một thao tác tuỳ chỉnh cho ngữ cảnh hiện tại, hãy hoán đổi biểu tượng thao tác tuỳ chỉnh bằng một biểu tượng thay thế cho biết rằng thao tác đó bị vô hiệu hoá.

Hình 6. Mẫu của các biểu tượng thao tác tuỳ chỉnh không theo kiểu.

Cho biết định dạng âm thanh

Để cho biết rằng nội dung nghe nhìn đang phát sẽ sử dụng một định dạng âm thanh đặc biệt, bạn có thể chỉ định các biểu tượng hiển thị trên những ô tô hỗ trợ tính năng này. Bạn có thể đặt KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URIKEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI trong gói bổ sung của mục nội dung nghe nhìn đang phát (đã truyền đến MediaSession.setMetadata()). Hãy nhớ thiết lập cả hai gói bổ sung đó để đảm bảo phù hợp với nhiều bố cục.

Ngoài ra, bạn có thể đặt thêm KEY_IMMERSIVE_AUDIO để cho nhà sản xuất thiết bị gốc (OEM) về ô tô biết rằng đây là âm thanh sống động và họ sẽ phải thật cẩn thận khi quyết định có nên áp dụng hiệu ứng âm thanh làm ảnh hưởng đến nội dung sống động hay không.

Bạn có thể định cấu hình mục nội dung nghe nhìn đang phát để phụ đề, nội dung mô tả hoặc cả hai đều là đường liên kết đến các mục nội dung nghe nhìn khác. Việc này cho phép người dùng chuyển nhanh đến các mục liên quan. Ví dụ: họ có thể chuyển sang các bài hát khác của cùng một nghệ sĩ, các tập khác của cùng một podcast, v.v. Nếu ô tô hỗ trợ tính năng này thì người dùng có thể nhấn vào đường liên kết để duyệt đến nội dung đó.

Để thêm đường liên kết, hãy định cấu hình siêu dữ liệu KEY_SUBTITLE_LINK_MEDIA_ID (để liên kết từ phụ đề) hoặc KEY_DESCRIPTION_LINK_MEDIA_ID (để liên kết từ nội dung mô tả). Để biết thông tin chi tiết, hãy xem tài liệu tham khảo về các trường siêu dữ liệu đó.

Hỗ trợ thao tác bằng giọng nói

Ứng dụng đa phương tiện của bạn phải hỗ trợ thao tác bằng giọng nói để mang đến cho người lái xe trải nghiệm an toàn và thuận tiện giúp giảm thiểu sự phân tâm. Ví dụ: nếu ứng dụng của bạn đang phát một mục nội dung đa phương tiện, thì người dùng có thể nói "Phát [tên bài hát]" để yêu cầu ứng dụng đó phát một bài hát khác mà không cần nhìn hoặc chạm màn hình ô tô. Người dùng có thể bắt đầu truy vấn bằng cách nhấp vào các nút thích hợp trên vô lăng hoặc nói các cụm từ kích hoạt "Ok Google".

Khi Android Auto hoặc Android Automotive OS phát hiện và diễn giải một thao tác bằng giọng nói, thao tác bằng giọng nói đó sẽ được gửi đến ứng dụng thông qua onPlayFromSearch(). Khi nhận được lệnh gọi lại này, ứng dụng sẽ tìm nội dung khớp với chuỗi query và bắt đầu phát.

Người dùng có thể chỉ định nhiều danh mục từ khoá trong truy vấn: thể loại, nghệ sĩ, đĩa nhạc, tên bài hát, đài phát thanh hoặc danh sách phát và nhiều nội dung khác. Khi xây dựng tính năng hỗ trợ tìm kiếm, hãy tính đến tất cả các danh mục phù hợp với ứng dụng của bạn. Nếu Android Auto hoặc Android Automotive OS phát hiện thấy rằng một truy vấn cụ thể phù hợp với các danh mục nhất định, thì các dữ liệu bổ sung sẽ được thêm vào tham số extras. Bạn có thể gửi các dữ liệu bổ sung sau:

Hãy tính đến chuỗi query trống mà Android Auto hoặc Android Automotive OS có thể gửi nếu người dùng không chỉ định cụm từ tìm kiếm. Ví dụ: nếu người dùng nói "Phát nhạc". Trong trường hợp đó, ứng dụng của bạn có thể chọn bắt đầu một bản nhạc được phát gần đây hoặc bản nhạc mới đề xuất.

Nếu hệ thống không thể xử lý nhanh nội dung tìm kiếm, đừng chặn trong onPlayFromSearch(). Thay vào đó, hãy đặt trạng thái phát là STATE_CONNECTING rồi tìm kiếm trên luồng không đồng bộ.

Sau khi bắt đầu phát, hãy cân nhắc việc điền sẵn nội dung có liên quan vào hàng đợi của phiên phát nội dung đa phương tiện. Ví dụ: nếu người dùng yêu cầu phát một đĩa nhạc, thì ứng dụng của bạn có thể điền danh sách các bản nhạc trong đĩa nhạc đó vào hàng đợi này. Ngoài ra, hãy cân nhắc việc triển khai tính năng hỗ trợ kết quả tìm kiếm có thể xem để người dùng có thể chọn một bản nhạc khác khớp với truy vấn của họ.

Ngoài truy vấn "phát", Android Auto và Android Automotive OS cũng sẽ nhận dạng các truy vấn bằng giọng nói để điều khiển chế độ phát như "tạm dừng nhạc" và "bài hát tiếp theo", sau đó sẽ khớp những lệnh này với lệnh gọi lại phiên phát nội dung nghe nhìn thích hợp, chẳng hạn như onPause()onSkipToNext().

Để biết ví dụ chi tiết về cách triển khai các thao tác phát bằng giọng nói trong ứng dụng của bạn, hãy xem bài viết Trợ lý Google và ứng dụng đa phương tiện.

Triển khai các biện pháp bảo vệ để tránh sự phân tâm

Điện thoại của người dùng được kết nối với loa của ô tô trong khi sử dụng Android Auto. Do đó, bạn phải có các biện pháp phòng ngừa bổ sung giúp người lái xe bớt phân tâm.

Chặn chuông báo trên ô tô

Các ứng dụng đa phương tiện trên Android Auto không được bắt đầu phát âm thanh qua loa trên ô tô trừ phi người dùng bắt đầu phát, chẳng hạn như bằng cách nhấn vào nút phát. Ngay cả chuông báo do người dùng lên lịch trên ứng dụng đa phương tiện cũng không được bắt đầu phát nhạc qua loa trên ô tô.

Để đáp ứng yêu cầu này, ứng dụng của bạn có thể dùng CarConnection làm tín hiệu trước khi phát âm thanh bất kỳ. Ứng dụng của bạn có thể kiểm tra xem điện thoại có đang chiếu tới màn hình ô tô hay không bằng cách quan sát loại kết nối ô tô qua LiveData và kiểm tra xem loại kết nối đó có phải là CONNECTION_TYPE_PROJECTION hay không.

Nếu điện thoại của người dùng đang chiếu, thì các ứng dụng đa phương tiện hỗ trợ chuông báo phải thực hiện một trong những việc sau:

  • Tắt chuông báo.
  • Phát chuông báo qua STREAM_ALARM và cung cấp một giao diện người dùng trên màn hình điện thoại để tắt chuông báo.

Xử lý quảng cáo trong nội dung đa phương tiện

Theo mặc định, Android Auto sẽ hiển thị thông báo khi siêu dữ liệu đa phương tiện thay đổi trong phiên phát âm thanh. Khi một ứng dụng đa phương tiện chuyển từ phát nhạc sang chạy quảng cáo, việc hiển thị thông báo cho người dùng sẽ gây phân tâm. Để ngăn Android Auto hiển thị thông báo trong trường hợp này, bạn phải đặt khoá siêu dữ liệu đa phương tiện METADATA_KEY_IS_ADVERTISEMENT thành METADATA_VALUE_ATTRIBUTE_PRESENT, như minh hoạ trong đoạn mã sau:

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

Xử lý lỗi chung

Khi ứng dụng gặp lỗi, hãy đặt trạng thái phát là STATE_ERROR rồi đưa ra một thông báo lỗi bằng phương thức setErrorMessage(). Hãy xem PlaybackStateCompat để biết danh sách mã lỗi mà bạn có thể sử dụng khi đặt thông báo lỗi đó. Người dùng phải thấy được thông báo lỗi đã bản địa hoá theo ngôn ngữ hiện tại của họ. Sau đó, Android Auto và Android Automotive OS có thể hiển thị thông báo lỗi đó với người dùng.

Ví dụ: nếu nội dung không có ở khu vực hiện tại của người dùng, thì bạn có thể sử dụng mã lỗi ERROR_CODE_NOT_AVAILABLE_IN_REGION khi đặt thông báo lỗi đó.

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());

Để biết thêm thông tin về trạng thái lỗi, hãy xem phần Sử dụng phiên phát nội dung nghe nhìn: Trạng thái và lỗi.

Nếu người dùng Android Auto cần mở ứng dụng điện thoại để khắc phục lỗi, hãy cung cấp thông tin đó cho người dùng trong thông báo của bạn. Ví dụ: thông báo lỗi của bạn sẽ có nội dung là "Đăng nhập vào [tên ứng dụng của bạn]" thay cho "Vui lòng đăng nhập".

Tài nguyên khác