Điều khiển nội dung nghe nhìn

Các nút điều khiển nội dung nghe nhìn trong Android nằm gần Cài đặt nhanh. Các phiên hoạt động từ nhiều ứng dụng được sắp xếp trong một băng chuyền có thể vuốt. Băng chuyền liệt kê các phiên hoạt động theo thứ tự sau:

  • Các luồng phát trên điện thoại
  • Phát trực tuyến từ xa, chẳng hạn như những nội dung phát hiện thấy trên thiết bị bên ngoài hoặc phiên truyền
  • Các phiên có thể tiếp tục trước đó, theo thứ tự chơi gần đây nhất

Kể từ Android 13 (API cấp 33), để đảm bảo rằng người dùng có thể truy cập vào một tập hợp nhiều chế độ điều khiển nội dung nghe nhìn cho các ứng dụng phát nội dung nghe nhìn, các nút hành động trên chế độ điều khiển nội dung nghe nhìn được bắt nguồn từ trạng thái Player.

Bằng cách này, bạn có thể trình bày một bộ chế độ điều khiển nội dung nghe nhìn nhất quán và trải nghiệm điều khiển nội dung nghe nhìn chỉn chu hơn trên các thiết bị.

Hình 1 cho thấy một ví dụ về giao diện lần lượt trên điện thoại và máy tính bảng.

Các nút điều khiển nội dung đa phương tiện xét về cách chúng xuất hiện trên thiết bị điện thoại và máy tính bảng, sử dụng ví dụ về một bản nhạc mẫu cho thấy cách các nút có thể xuất hiện
Hình 1: Điều khiển nội dung nghe nhìn trên thiết bị điện thoại và máy tính bảng

Hệ thống hiển thị tối đa 5 nút hành động dựa trên trạng thái Player như mô tả trong bảng sau. Ở chế độ thu gọn, chỉ hiển thị 3 ô hành động đầu tiên. Điều này phù hợp với cách hiển thị các chế độ điều khiển nội dung nghe nhìn trong các nền tảng Android khác, chẳng hạn như Auto, Trợ lý và Wear OS.

Khe Tiêu chí Hành động
1 playWhenReady sai hoặc trạng thái phát hiện tại là STATE_ENDED. Phát
playWhenReady là giá trị true và trạng thái phát hiện tại là STATE_BUFFERING. Vòng quay đang tải
playWhenReady là giá trị true và trạng thái phát hiện tại là STATE_READY. Tạm dừng
2 Bạn có thể dùng lệnh của trình phát COMMAND_SEEK_TO_PREVIOUS hoặc COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM. Trước
Không có lệnh nào của người chơi COMMAND_SEEK_TO_PREVIOUSCOMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM và một lệnh tuỳ chỉnh từ bố cục tuỳ chỉnh chưa được đặt có sẵn để lấp đầy vị trí. Điều chỉnh
(chưa được hỗ trợ với Media3) thông tin bổ sung PlaybackState bao gồm giá trị boolean true cho khoá EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV. Trống
3 Bạn có thể dùng lệnh của trình phát COMMAND_SEEK_TO_NEXT hoặc COMMAND_SEEK_TO_NEXT_MEDIA_ITEM. Tiếp theo
Không có lệnh nào của người chơi COMMAND_SEEK_TO_NEXTCOMMAND_SEEK_TO_NEXT_MEDIA_ITEM và một lệnh tuỳ chỉnh từ bố cục tuỳ chỉnh chưa được đặt có sẵn để lấp đầy vị trí. Điều chỉnh
(chưa được hỗ trợ với Media3) thông tin bổ sung PlaybackState bao gồm giá trị boolean true cho khoá EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. Trống
4 Một lệnh tuỳ chỉnh từ bố cục tuỳ chỉnh chưa được đặt sẽ có sẵn để lấp đầy vị trí. Điều chỉnh
5 Một lệnh tuỳ chỉnh từ bố cục tuỳ chỉnh chưa được đặt sẽ có sẵn để lấp đầy vị trí. Điều chỉnh

Các lệnh tuỳ chỉnh được đặt theo thứ tự khi được thêm vào bố cục tuỳ chỉnh.

Tuỳ chỉnh các nút lệnh

Để tuỳ chỉnh các chế độ điều khiển nội dung nghe nhìn của hệ thống bằng Jetpack Media3, bạn có thể đặt bố cục tuỳ chỉnh của phiên hoạt động và các lệnh có sẵn của bộ điều khiển tương ứng khi triển khai một MediaSessionService:

  1. Trong onCreate(), hãy tạo MediaSessionxác định bố cục tuỳ chỉnh của các nút lệnh.

  2. Trong MediaSession.Callback.onConnect(), hãy uỷ quyền cho tay điều khiển bằng cách xác định các lệnh hiện có, bao gồm cả các lệnh tuỳ chỉnh trong ConnectionResult.

  3. Trong MediaSession.Callback.onCustomCommand(), hãy phản hồi lệnh tuỳ chỉnh mà người dùng đã chọn.

Kotlin

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

Java

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

Nếu bạn muốn tìm hiểu thêm về cách định cấu hình MediaSession sao cho các ứng dụng như hệ thống có thể kết nối với ứng dụng đa phương tiện của bạn, hãy xem bài viết Cấp quyền kiểm soát cho các ứng dụng khác.

Với Jetpack Media3, khi bạn triển khai MediaSession, PlaybackState của bạn sẽ tự động được cập nhật với trình phát nội dung đa phương tiện. Tương tự, khi bạn triển khai MediaSessionService, thư viện sẽ tự động phát hành một thông báo MediaStyle cho bạn và luôn cập nhật thông báo đó.

Phản hồi các nút hành động

Khi người dùng nhấn vào một nút hành động trong phần điều khiển nội dung nghe nhìn trên hệ thống, MediaController của hệ thống sẽ gửi lệnh phát đến MediaSession của bạn. Sau đó, MediaSession sẽ uỷ quyền các lệnh đó cho trình phát. Các lệnh đã xác định trong giao diện Player của Media3 sẽ được phiên nội dung đa phương tiện xử lý tự động.

Hãy tham khảo bài viết Thêm lệnh tuỳ chỉnh để xem hướng dẫn về cách phản hồi một lệnh tuỳ chỉnh.

Hành vi trước Android 13

Để có khả năng tương thích ngược, Giao diện người dùng hệ thống tiếp tục cung cấp bố cục thay thế sử dụng các thao tác thông báo cho các ứng dụng không cập nhật để nhắm đến Android 13 hoặc không bao gồm thông tin PlaybackState. Các nút hành động bắt nguồn từ danh sách Notification.Action đi kèm với thông báo MediaStyle. Hệ thống sẽ hiển thị tối đa 5 thao tác theo thứ tự thêm các thao tác đó. Ở chế độ thu gọn, tối đa 3 nút sẽ hiển thị, được xác định bằng các giá trị được truyền vào setShowActionsInCompactView().

Các thao tác tuỳ chỉnh được sắp xếp theo thứ tự thêm vào PlaybackState.

Ví dụ về mã sau đây minh hoạ cách thêm các thao tác vào thông báo MediaStyle :

Kotlin

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

Java

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

Hỗ trợ tiếp tục phát nội dung nghe nhìn

Việc tiếp tục phát nội dung nghe nhìn cho phép người dùng khởi động lại các phiên trước đó trên băng chuyền mà không cần khởi động ứng dụng. Khi bắt đầu phát, người dùng sẽ tương tác với các chế độ điều khiển nội dung nghe nhìn theo cách thông thường.

Bạn có thể bật và tắt tính năng tiếp tục phát bằng ứng dụng Cài đặt, trong tuỳ chọn Âm thanh > Nội dung nghe nhìn. Người dùng cũng có thể truy cập vào phần Cài đặt bằng cách nhấn vào biểu tượng bánh răng xuất hiện sau khi vuốt trên băng chuyền mở rộng.

Media3 cung cấp các API để giúp bạn dễ dàng tiếp tục sử dụng nội dung nghe nhìn. Xem tài liệu về Tiếp tục phát bằng Media3 để nắm được hướng dẫn về cách triển khai tính năng này.

Sử dụng API đa phương tiện cũ

Phần này giải thích cách tích hợp với các chế độ điều khiển nội dung nghe nhìn của hệ thống bằng cách sử dụng API MediaCompat cũ.

Hệ thống sẽ truy xuất thông tin sau từ MediaMetadata của MediaSession và hiển thị thông tin đó khi có sẵn:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (Nếu bạn không đặt thời lượng, thanh tìm kiếm sẽ không hiển thị tiến trình)

Để đảm bảo bạn có thông báo hợp lệ và chính xác về điều khiển nội dung nghe nhìn, hãy đặt giá trị của siêu dữ liệu METADATA_KEY_TITLE hoặc METADATA_KEY_DISPLAY_TITLE thành tiêu đề của nội dung nghe nhìn đang phát.

Trình phát nội dung đa phương tiện hiển thị thời gian đã trôi qua của nội dung nghe nhìn hiện đang phát, cùng với thanh tìm kiếm được liên kết với MediaSession PlaybackState.

Trình phát nội dung đa phương tiện cho thấy tiến trình của nội dung nghe nhìn đang phát, cùng với thanh tìm kiếm được liên kết với MediaSession PlaybackState. Thanh tìm kiếm cho phép người dùng thay đổi vị trí và hiển thị thời gian đã trôi qua cho mục nội dung nghe nhìn. Để bật thanh tìm kiếm, bạn phải triển khai PlaybackState.Builder#setActions và bao gồm ACTION_SEEK_TO.

Khe Hành động Tiêu chí
1 Phát Trạng thái hiện tại của PlaybackState là một trong những trạng thái sau:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Vòng quay đang tải Trạng thái hiện tại của PlaybackState là một trong những trạng thái sau:
  • STATE_CONNECTING
  • STATE_BUFFERING
Tạm dừng Trạng thái hiện tại của PlaybackState không phải là trạng thái nào ở trên.
2 Trước PlaybackState hành động bao gồm ACTION_SKIP_TO_PREVIOUS.
Điều chỉnh PlaybackState hành động không bao gồm ACTION_SKIP_TO_PREVIOUSPlaybackState hành động tuỳ chỉnh bao gồm một hành động tuỳ chỉnh chưa được đặt.
Trống Phần bổ sung PlaybackState bao gồm một giá trị boolean true cho khoá SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Tiếp theo PlaybackState hành động bao gồm ACTION_SKIP_TO_NEXT.
Điều chỉnh PlaybackState hành động không bao gồm ACTION_SKIP_TO_NEXTPlaybackState hành động tuỳ chỉnh bao gồm một hành động tuỳ chỉnh chưa được đặt.
Trống Phần bổ sung PlaybackState bao gồm một giá trị boolean true cho khoá SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Điều chỉnh PlaybackState hành động tuỳ chỉnh bao gồm một thao tác tuỳ chỉnh chưa được đặt.
5 Điều chỉnh PlaybackState hành động tuỳ chỉnh bao gồm một thao tác tuỳ chỉnh chưa được đặt.

Thêm thao tác chuẩn

Các mã ví dụ sau đây minh hoạ cách thêm PlaybackState thao tác chuẩn và thao tác tuỳ chỉnh.

Để phát, tạm dừng, trước và tiếp theo, hãy đặt các thao tác này trong PlaybackState cho phiên phát nội dung đa phương tiện.

Kotlin

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

Java

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

Nếu bạn không muốn nút nào nằm trong các khe trước đó hoặc tiếp theo, đừng thêm ACTION_SKIP_TO_PREVIOUS hoặc ACTION_SKIP_TO_NEXT mà thay vào đó, hãy thêm các thành phần bổ sung vào phiên:

Kotlin

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

Java

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

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

Đối với các thao tác khác mà bạn muốn hiển thị trên các tuỳ chọn điều khiển nội dung nghe nhìn, bạn có thể tạo PlaybackStateCompat.CustomAction rồi thêm vào PlaybackState. Những hành động này sẽ xuất hiện theo thứ tự thêm.

Kotlin

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

Java

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

Phản hồi các thao tác PlaybackState

Khi người dùng nhấn vào một nút, SystemUI sẽ sử dụng MediaController.TransportControls để gửi lệnh lại cho MediaSession. Bạn cần đăng ký một lệnh gọi lại có thể phản hồi đúng cách với những sự kiện này.

Kotlin

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

Java

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

Tiếp tục phát nội dung nghe nhìn

Để ứng dụng trình phát của bạn xuất hiện trong phần cài đặt nhanh, bạn phải tạo thông báo MediaStyle có mã thông báo MediaSession hợp lệ.

Để hiển thị tiêu đề cho thông báo MediaStyle, hãy sử dụng NotificationBuilder.setContentTitle().

Để hiển thị biểu tượng thương hiệu cho trình phát nội dung đa phương tiện, hãy sử dụng NotificationBuilder.setSmallIcon().

Để hỗ trợ việc tiếp tục phát, các ứng dụng phải triển khai MediaBrowserServiceMediaSession. MediaSession của bạn phải triển khai lệnh gọi lại onPlay().

Triển khai MediaBrowserService

Sau khi thiết bị khởi động, hệ thống sẽ tìm 5 ứng dụng đa phương tiện được sử dụng gần đây nhất và cung cấp các chế độ điều khiển có thể dùng để bắt đầu lại quá trình phát trên mỗi ứng dụng.

Hệ thống sẽ cố gắng liên hệ với MediaBrowserService của bạn bằng kết nối từ SystemUI. Ứng dụng của bạn phải cho phép các kết nối như vậy, nếu không thì ứng dụng không thể hỗ trợ việc tiếp tục phát.

Bạn có thể xác định và xác minh các kết nối qua SystemUI bằng tên gói com.android.systemui và chữ ký. SystemUI được ký bằng chữ ký của nền tảng. Bạn có thể xem ví dụ về cách kiểm tra chữ ký nền tảng trong ứng dụng UAMP.

Để hỗ trợ việc tiếp tục phát, MediaBrowserService của bạn phải triển khai các hành vi sau:

  • onGetRoot() phải nhanh chóng trả về một gốc khác rỗng. Logic phức tạp khác sẽ được xử lý trong onLoadChildren()

  • Khi onLoadChildren() được gọi trên mã nhận dạng nội dung đa phương tiện gốc, kết quả phải chứa phần tử con FLAG_PLAYABLE.

  • MediaBrowserService sẽ trả về mục nội dung đa phương tiện đã phát gần đây nhất khi nhận được truy vấn EXTRA_ GẦN ĐÂY. Giá trị được trả về phải là mục nội dung nghe nhìn thực tế thay vì hàm chung.

  • MediaBrowserService phải cung cấp một MediaDescription thích hợp, trong đó có tiêu đềphụ đề không được để trống. Thao tác này cũng nên đặt URI biểu tượng hoặc Bitmap biểu tượng.

Các mã ví dụ sau đây minh hoạ cách triển khai onGetRoot().

Kotlin

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}