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

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

ตอบสนองต่อปุ่มดำเนินการ

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

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

ลักษณะการทํางานก่อน Android 13

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

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

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีเพิ่มการดำเนินการไปยัง MediaStyle notification

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

การใช้ Media 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

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

สล็อต การทำงาน เกณฑ์
1 เล่น สถานะปัจจุบันของ PlaybackState เป็นหนึ่งในสถานะต่อไปนี้
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
ไอคอนหมุนขณะโหลด สถานะปัจจุบันของ PlaybackState เป็นหนึ่งในสถานะต่อไปนี้
  • STATE_CONNECTING
  • STATE_BUFFERING
หยุดชั่วคราว สถานะปัจจุบันของ PlaybackState ไม่ใช่รายการใดข้างต้น
2 ก่อนหน้า PlaybackState actions มี ACTION_SKIP_TO_PREVIOUS
กำหนดเอง PlaybackState การดําเนินการไม่รวม ACTION_SKIP_TO_PREVIOUS และ PlaybackState การดําเนินการแบบกําหนดเองรวมการดําเนินการแบบกําหนดเองที่ยังไม่ได้วาง
ว่าง PlaybackState extras มีค่าบูลีน true สำหรับคีย์ SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
3 ถัดไป PlaybackState actions มี ACTION_SKIP_TO_NEXT
กำหนดเอง PlaybackState การดําเนินการไม่รวม ACTION_SKIP_TO_NEXT และ PlaybackState การดําเนินการแบบกําหนดเองรวมการดําเนินการแบบกําหนดเองที่ยังไม่ได้วาง
ว่าง PlaybackState extras มีค่าบูลีน 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 ของคุณต้องใช้การเรียกกลับ onPlay()

การใช้งาน MediaBrowserService

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

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

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

MediaBrowserService ของคุณต้องรองรับลักษณะการทำงานต่อไปนี้เพื่อรองรับการเล่นต่อ

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

  • เมื่อเรียกใช้ onLoadChildren() ในรหัสสื่อรูท ผลลัพธ์ต้องมี FLAG_PLAYABLE ย่อย

  • MediaBrowserService ควรแสดงรายการสื่อที่เล่นล่าสุดเมื่อได้รับคําค้นหา EXTRA_RECENT ค่าที่แสดงผลควรเป็นรายการสื่อจริง ไม่ใช่ฟังก์ชันทั่วไป

  • MediaBrowserService ต้องมี MediaDescription ที่เหมาะสมซึ่งมีชื่อและคำบรรยายที่ไม่ว่างเปล่า นอกจากนี้ ยังควรตั้งค่าURI ของไอคอนหรือบิตแมปของไอคอนด้วย

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