ตัวควบคุมสื่อใน Android จะอยู่ที่ใกล้กับการตั้งค่าด่วน เซสชันจากแอปหลายแอปจะจัดเรียงเป็นภาพสไลด์ที่ปัดดูได้ ภาพสไลด์จะแสดงเซสชันตามลําดับนี้
- สตรีมที่เล่นในเครื่องบนโทรศัพท์
- สตรีมระยะไกล เช่น สตรีมที่ตรวจพบในอุปกรณ์ภายนอกหรือเซสชันแคสต์
- เซสชันก่อนหน้าที่เล่นต่อได้ ตามลำดับที่เล่นล่าสุด
ตั้งแต่ Android 13 (API ระดับ 33) เป็นต้นไป ปุ่มดำเนินการในตัวควบคุมสื่อจะมาจากสถานะ Player
เพื่อให้ผู้ใช้เข้าถึงชุดตัวควบคุมสื่อที่สมบูรณ์สำหรับแอปที่เล่นสื่อได้
วิธีนี้จะช่วยให้คุณนำเสนอชุดการควบคุมสื่อที่สอดคล้องกันและประสบการณ์การควบคุมสื่อที่ยอดเยี่ยมยิ่งขึ้นในอุปกรณ์ต่างๆ
รูปที่ 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
ใน
onCreate()
ให้สร้างMediaSession
และกำหนดเลย์เอาต์ที่กำหนดเองของปุ่มคำสั่งใน
MediaSession.Callback.onConnect()
ให้ให้สิทธิ์ผู้ควบคุมโดยกำหนดคำสั่งที่ใช้ได้ ซึ่งรวมถึงคำสั่งที่กำหนดเอง ในConnectionResult
ใน
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 ListenableFutureonCustomCommand( 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 รายการตามลำดับที่เพิ่ม ในโหมดกะทัดรัด ระบบจะแสดงปุ่มได้สูงสุด 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 เป็นหนึ่งในสถานะต่อไปนี้
|
ไอคอนหมุนขณะโหลด |
สถานะปัจจุบันของ PlaybackState เป็นหนึ่งในสถานะต่อไปนี้
|
|
หยุดชั่วคราว | สถานะปัจจุบันของ 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); }