การควบคุมสื่อ

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

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

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

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

รูปที่ 1 แสดงตัวอย่างว่าข้อมูลจะมีลักษณะอย่างไรบนโทรศัพท์และแท็บเล็ต ตามลำดับ

วันที่ การควบคุมสื่อในแง่ของการแสดงผลบนโทรศัพท์และแท็บเล็ต
            โดยใช้ตัวอย่างแทร็กตัวอย่างที่แสดงให้เห็นลักษณะของปุ่ม
ภาพที่ 1: การควบคุมสื่อบนโทรศัพท์และแท็บเล็ต

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

สล็อต เกณฑ์ การทำงาน
1 playWhenReady เป็นเท็จหรือการเล่นปัจจุบัน สถานะคือ STATE_ENDED เล่น
playWhenReady เป็นจริงและสถานะการเล่นปัจจุบันคือ STATE_BUFFERING ไอคอนหมุนแสดงการโหลด
playWhenReady เป็นจริงและสถานะการเล่นปัจจุบันคือ STATE_READY หยุดชั่วคราว
2 ใช้คําสั่งโปรแกรมเล่น COMMAND_SEEK_TO_PREVIOUS หรือ COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ได้ ก่อนหน้า
ไม่มีทั้งคำสั่งของโปรแกรมเล่น COMMAND_SEEK_TO_PREVIOUS และ COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM และจะใช้คำสั่งที่กำหนดเองจากเลย์เอาต์ที่กำหนดเองที่ยังไม่ได้วางเพื่อเติมช่องโฆษณาได้ กำหนดเอง
(ยังไม่รองรับ Media3) PlaybackState ส่วนเพิ่มเติมจะมีค่าบูลีน true สำหรับคีย์ EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV ว่าง
3 ใช้คําสั่งโปรแกรมเล่น COMMAND_SEEK_TO_NEXT หรือ COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ได้ ถัดไป
ไม่มีทั้งคำสั่งของโปรแกรมเล่น COMMAND_SEEK_TO_NEXT และ COMMAND_SEEK_TO_NEXT_MEDIA_ITEM และจะใช้คำสั่งที่กำหนดเองจากเลย์เอาต์ที่กำหนดเองที่ยังไม่ได้วางเพื่อเติมช่องโฆษณาได้ กำหนดเอง
(ยังไม่รองรับ Media3) PlaybackState ส่วนเพิ่มเติมจะมีค่าบูลีน true สำหรับคีย์ EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT ว่าง
4 คำสั่งที่กำหนดเองจากเลย์เอาต์ที่กำหนดเองที่ยังไม่ได้วางพร้อมให้ใช้งานเพื่อเติมช่องโฆษณา กำหนดเอง
5 คำสั่งที่กำหนดเองจากเลย์เอาต์ที่กำหนดเองที่ยังไม่ได้วางพร้อมให้ใช้งานเพื่อเติมช่องโฆษณา กำหนดเอง

ระบบจะวางคำสั่งที่กำหนดเองตามลำดับที่เพิ่มใน เลย์เอาต์ที่กำหนดเอง

ปรับแต่งปุ่มคำสั่ง

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

  1. ใน onCreate() ให้สร้าง MediaSession และกำหนดเลย์เอาต์ที่กำหนดเอง ปุ่มคำสั่ง

  2. ใน MediaSession.Callback.onConnect() ให้สิทธิ์ตัวควบคุมโดยกำหนดคำสั่งที่ใช้ได้ ซึ่งรวมถึง คำสั่งที่กำหนดเอง ใน ConnectionResult

  3. ใน MediaSession.Callback.onCustomCommand() ตอบสนองต่อคำสั่งที่กำหนดเองที่ผู้ใช้เลือก

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

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

เมื่อใช้ Jetpack Media3 เมื่อคุณใช้ MediaSession PlaybackState ได้รับการอัปเดตโดยอัตโนมัติด้วยมีเดียเพลเยอร์ ในทำนองเดียวกัน เมื่อคุณ ใช้ MediaSessionService ไลบรารีจะเผยแพร่ MediaStyle การแจ้งเตือน ให้กับคุณและคอยอัปเดตให้เป็นปัจจุบันอยู่เสมอ

ตอบสนองต่อปุ่มการทำงาน

เมื่อผู้ใช้แตะปุ่มการทำงานในตัวควบคุมสื่อของระบบ MediaController ส่งคำสั่งการเล่นไปยัง MediaSession จากนั้น MediaSession จะมอบสิทธิ์คำสั่งเหล่านั้นให้โปรแกรมเล่น คำสั่ง ที่กำหนดไว้ใน Player ของ Media3 ระบบจะจัดการอินเทอร์เฟซ โดยสื่อโดยอัตโนมัติ เซสชัน

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

พฤติกรรมก่อน Android 13

สำหรับความเข้ากันได้แบบย้อนหลัง UI ของระบบจะยังคงมีเลย์เอาต์สำรอง ซึ่งใช้การดำเนินการแจ้งเตือนสำหรับแอปที่ไม่ได้อัปเดตให้กำหนดเป้าหมายเป็น Android 13 หรือไม่มีข้อมูล PlaybackState ปุ่มดำเนินการ มาจากรายการ Notification.Action ที่แนบมากับ MediaStyle การแจ้งเตือน ระบบจะแสดงการดำเนินการสูงสุด 5 รายการตามลำดับ เพิ่ม แล้ว ในโหมดกะทัดรัด ปุ่มจะแสดงถึงสามปุ่ม ซึ่งขึ้นอยู่กับ ค่าที่ส่งให้ setShowActionsInCompactView()

การดำเนินการที่กำหนดเองจะวางตามลำดับที่เพิ่มใน PlaybackState

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีเพิ่มการทำงานให้กับ 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();

รองรับการกลับมาเล่นสื่ออีกครั้ง

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

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

Media3 มี API ที่ช่วยให้รองรับการกลับมาเล่นสื่อต่อได้ง่ายขึ้น โปรดดู การกลับมาเล่นอีกครั้งด้วย Media3 เพื่อดูคำแนะนำในการใช้ฟีเจอร์นี้ได้

การใช้ API สื่อแบบเดิม

ส่วนนี้จะอธิบายวิธีผสานรวมกับการควบคุมสื่อของระบบโดยใช้ MediaCompat API เดิม

ระบบจะดึงข้อมูลต่อไปนี้จาก MediaMetadata ของ MediaSession และจะแสดงเมื่อพร้อมให้บริการ:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (หากไม่ได้ตั้งค่าระยะเวลา แถบค้นหาไม่มีการกำหนดค่า แสดงความคืบหน้า)

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

มีเดียเพลเยอร์จะแสดงเวลาที่ผ่านไปของวิดีโอที่กำลังเล่นอยู่ สื่อ พร้อมด้วยแถบค้นหาซึ่งแมปกับ MediaSession PlaybackState

มีเดียเพลเยอร์จะแสดงความคืบหน้าของสื่อที่เล่นอยู่ พร้อมกับ แถบเลื่อนที่แมปกับ PlaybackState ของ MediaSession แถบเลื่อน ช่วยให้ผู้ใช้เปลี่ยนตำแหน่งและแสดงเวลาที่ผ่านไปของสื่อได้ รายการ หากต้องการเปิดใช้งานแถบค้นหา คุณจะต้องติดตั้งใช้งาน PlaybackState.Builder#setActionsและรวม ACTION_SEEK_TO

สล็อต การทำงาน เกณฑ์
1 เล่น สถานะปัจจุบันของ PlaybackState คืออย่างใดอย่างหนึ่งต่อไปนี้
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
ไอคอนหมุนแสดงการโหลด สถานะปัจจุบันของ PlaybackState เป็นอย่างใดอย่างหนึ่งต่อไปนี้
  • STATE_CONNECTING
  • STATE_BUFFERING
หยุดชั่วคราว สถานะปัจจุบันของ PlaybackState ไม่ตรงกับข้อใดข้างต้น
2 ก่อนหน้า PlaybackState การกระทำ ได้แก่ ACTION_SKIP_TO_PREVIOUS
กำหนดเอง PlaybackState การกระทำไม่รวม ACTION_SKIP_TO_PREVIOUS และ PlaybackState การกระทำที่กำหนดเองรวมการกระทำที่กำหนดเองที่ยังไม่ได้วาง
ว่าง PlaybackState ส่วนเพิ่มเติมมีค่าบูลีน true สำหรับคีย์ SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
3 ถัดไป PlaybackState การกระทำ ได้แก่ ACTION_SKIP_TO_NEXT
กำหนดเอง PlaybackState การกระทำไม่รวม ACTION_SKIP_TO_NEXT และ PlaybackState การกระทำที่กำหนดเองรวมการกระทำที่กำหนดเองที่ยังไม่ได้วาง
ว่าง PlaybackState ส่วนเพิ่มเติมมีค่าบูลีน true สำหรับคีย์ SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
4 กำหนดเอง PlaybackState การกระทำที่กำหนดเองจะรวมการกระทำที่กำหนดเองที่ยังไม่ได้วาง
5 กำหนดเอง PlaybackState การกระทำที่กำหนดเองจะรวมการกระทำที่กำหนดเองที่ยังไม่ได้วาง

เพิ่มการดำเนินการมาตรฐาน

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีเพิ่ม PlaybackState มาตรฐานและ การกระทำที่กำหนดเอง

สำหรับการเล่น หยุดชั่วคราว ก่อนหน้า และถัดไป ให้ตั้งค่าการดำเนินการเหล่านี้ใน PlaybackStateสำหรับเซสชันสื่อ

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

หากคุณไม่ต้องการปุ่มใดๆ ในช่องก่อนหน้าหรือถัดไป ไม่ต้องเพิ่ม ACTION_SKIP_TO_PREVIOUS หรือ ACTION_SKIP_TO_NEXT และเพิ่มรายการพิเศษลงใน เซสชัน

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

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

สำหรับการดำเนินการอื่นๆ ที่ต้องการให้แสดงในตัวควบคุมสื่อ คุณสามารถสร้าง PlaybackStateCompat.CustomAction แล้วเพิ่มลงใน PlaybackState แทน การดำเนินการเหล่านี้จะปรากฏใน ลำดับที่เพิ่มเข้ามา

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

การตอบสนองต่อการทำงาน PlaybackState

เมื่อผู้ใช้แตะปุ่ม SystemUI จะใช้ MediaController.TransportControls เพื่อส่งคำสั่งกลับไปยัง MediaSession คุณต้องลงทะเบียนการติดต่อกลับ ที่สามารถตอบสนองต่อเหตุการณ์เหล่านี้ได้อย่างเหมาะสม

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

การกลับมาเล่นสื่ออีกครั้ง

หากต้องการให้แอปโปรแกรมเล่นปรากฏในพื้นที่การตั้งค่าด่วน ให้ทำดังนี้ คุณต้องสร้างการแจ้งเตือน MediaStyle ด้วยโทเค็น MediaSession ที่ถูกต้อง

หากต้องการแสดงชื่อของการแจ้งเตือน MediaStyle ให้ใช้ NotificationBuilder.setContentTitle()

หากต้องการแสดงไอคอนแบรนด์สำหรับมีเดียเพลเยอร์ ให้ใช้ NotificationBuilder.setSmallIcon()

แอปต้องใช้ MediaBrowserService เพื่อรองรับการกลับมาเล่นอีกครั้ง และ MediaSession MediaSession ต้องใช้ Callback onPlay()

การใช้งาน MediaBrowserService

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

ระบบพยายามติดต่อ MediaBrowserService ด้วยการเชื่อมต่อจาก SystemUI แอปของคุณต้องอนุญาตการเชื่อมต่อดังกล่าว มิฉะนั้นจะไม่รองรับ การกลับมาเล่นต่อ

ระบุและยืนยันการเชื่อมต่อจาก SystemUI ได้โดยใช้ชื่อแพ็กเกจ com.android.systemui และลายเซ็น SystemUI ลงนามกับแพลตฟอร์มแล้ว ลายเซ็น ตัวอย่างวิธีตรวจสอบกับลายเซ็นของแพลตฟอร์มมีดังนี้ ที่พบในแอป UAMP

MediaBrowserServiceต้อง ใช้ลักษณะการทำงานเหล่านี้

  • onGetRoot() ต้องแสดงผลรากที่ไม่เป็นค่าว่างอย่างรวดเร็ว ตรรกะที่ซับซ้อนอื่นๆ ควร ได้รับการจัดการใน onLoadChildren()

  • วันและเวลา มีการเรียก onLoadChildren() ในรหัสสื่อราก ผลลัพธ์ต้องมี FLAG_PLAYABLE ของบุตรหลาน

  • MediaBrowserService ควรแสดงรายการสื่อที่เล่นล่าสุดเมื่อ พวกเขาจะได้รับ แสดงเพิ่มเติมล่าสุด คำถาม ค่าที่แสดงผลควรเป็นรายการสื่อจริงแทนที่จะเป็นรายการทั่วไป

  • MediaBrowserService จะต้องระบุ MediaDescription พร้อมค่าที่ไม่ว่างเปล่า ชื่อ และ คำบรรยาย และยังควรตั้งค่า URL ของไอคอน หรือแท็ก บิตแมปของไอคอน

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีใช้งาน 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);
}