แอปของคุณต้องประกาศ MediaBrowserService
ด้วยตัวกรอง Intent ในไฟล์ Manifest คุณเลือกชื่อบริการของตนเองได้ ในตัวอย่างต่อไปนี้คือ "MediaPlaybackService"
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
หมายเหตุ: การใช้งานที่แนะนำของ MediaBrowserService
มีค่า MediaBrowserServiceCompat
ซึ่งระบุไว้ใน
media-compat support library
ในหน้านี้ คำว่า "MediaBrowserService" หมายถึงอินสแตนซ์
จาก MediaBrowserServiceCompat
เริ่มต้นเซสชันสื่อ
เมื่อบริการได้รับเมธอด Callback สำหรับวงจรของ onCreate()
บริการควรทําตามขั้นตอนต่อไปนี้
- สร้างและเริ่มต้นเซสชันสื่อ
- ตั้งค่า Callback ของเซสชันสื่อ
- ตั้งค่าโทเค็นเซสชันสื่อ
โค้ด onCreate()
ด้านล่างแสดงขั้นตอนเหล่านี้
private const val MY_MEDIA_ROOT_ID = "media_root_id"
private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"
class MediaPlaybackService : MediaBrowserServiceCompat() {
private var mediaSession: MediaSessionCompat? = null
private lateinit var stateBuilder: PlaybackStateCompat.Builder
override fun onCreate() {
super.onCreate()
// Create a MediaSessionCompat
mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply {
// Enable callbacks from MediaButtons and TransportControls
setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
// Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
stateBuilder = PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PLAY_PAUSE
)
setPlaybackState(stateBuilder.build())
// MySessionCallback() has methods that handle callbacks from a media controller
setCallback(MySessionCallback())
// Set the session's token so that client activities can communicate with it.
setSessionToken(sessionToken)
}
}
}
public class MediaPlaybackService extends MediaBrowserServiceCompat {
private static final String MY_MEDIA_ROOT_ID = "media_root_id";
private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";
private MediaSessionCompat mediaSession;
private PlaybackStateCompat.Builder stateBuilder;
@Override
public void onCreate() {
super.onCreate();
// Create a MediaSessionCompat
mediaSession = new MediaSessionCompat(context, LOG_TAG);
// Enable callbacks from MediaButtons and TransportControls
mediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
// Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
stateBuilder = new PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PLAY_PAUSE);
mediaSession.setPlaybackState(stateBuilder.build());
// MySessionCallback() has methods that handle callbacks from a media controller
mediaSession.setCallback(new MySessionCallback());
// Set the session's token so that client activities can communicate with it.
setSessionToken(mediaSession.getSessionToken());
}
}
จัดการการเชื่อมต่อไคลเอ็นต์
MediaBrowserService
มี 2 วิธีที่ใช้จัดการกับการเชื่อมต่อไคลเอ็นต์ ได้แก่
ตัวควบคุม onGetRoot()
รายการ
การเข้าถึงบริการ และ
onLoadChildren()
ทำให้ลูกค้าสามารถสร้างและแสดงเมนูในลำดับชั้นเนื้อหาของ MediaBrowserService
การควบคุมการเชื่อมต่อไคลเอ็นต์ด้วย onGetRoot()
เมธอด onGetRoot()
จะแสดงโหนดรากของลำดับชั้นเนื้อหา หาก
เมธอดแสดงผลเป็น Null เนื่องจากการเชื่อมต่อถูกปฏิเสธ
หากต้องการให้ลูกค้าเชื่อมต่อกับบริการของคุณและเรียกดูเนื้อหาสื่อของบริการได้ onGetRoot() ต้องแสดงผล BrowserRoot ที่ไม่เป็นค่าว่างซึ่งเป็นรหัสรากที่ แสดงถึงลำดับชั้นเนื้อหา
หากต้องการให้ไคลเอ็นต์เชื่อมต่อกับ MediaSession ของคุณโดยไม่ต้องเรียกดู onGetRoot() ยังคงต้องแสดงผล BrowserRoot ที่ไม่เป็นค่าว่าง แต่รหัสรูทควรแสดง ลำดับชั้นเนื้อหาที่ว่างเปล่า
การใช้งาน onGetRoot()
โดยทั่วไปอาจมีลักษณะดังนี้
override fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot {
// (Optional) Control the level of access for the specified package name.
// You'll need to write your own logic to do this.
return if (allowBrowsing(clientPackageName, clientUid)) {
// Returns a root ID that clients can use with onLoadChildren() to retrieve
// the content hierarchy.
MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)
} else {
// Clients can connect, but this BrowserRoot is an empty hierarchy
// so onLoadChildren returns nothing. This disables the ability to browse for content.
MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null)
}
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
Bundle rootHints) {
// (Optional) Control the level of access for the specified package name.
// You'll need to write your own logic to do this.
if (allowBrowsing(clientPackageName, clientUid)) {
// Returns a root ID that clients can use with onLoadChildren() to retrieve
// the content hierarchy.
return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
} else {
// Clients can connect, but this BrowserRoot is an empty hierarchy
// so onLoadChildren returns nothing. This disables the ability to browse for content.
return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null);
}
}
ในบางกรณี คุณอาจต้องการควบคุมว่าใครจะเชื่อมต่อได้บ้าง
ไปยัง MediaBrowserService
วิธีหนึ่งคือการใช้รายการควบคุมการเข้าถึง (ACL)
ซึ่งระบุการเชื่อมต่อที่ได้รับอนุญาต หรือแจกแจงรายการ
การเชื่อมต่อที่ควรห้าม ตัวอย่างของวิธีติดตั้งใช้งาน ACL
ซึ่งอนุญาตให้มีการเชื่อมต่อที่เฉพาะเจาะจง โปรดดู
PackageValidator
ใน Universal Android Music Player
แอปตัวอย่าง
คุณควรพิจารณาระบุลำดับชั้นเนื้อหาที่แตกต่างกันโดยขึ้นอยู่กับ
ประเภทของลูกค้าที่ทำการค้นหา โดยเฉพาะอย่างยิ่ง Android Auto จำกัดวิธีการ
ผู้ใช้โต้ตอบกับแอปเสียง สำหรับข้อมูลเพิ่มเติม โปรดดู การเล่นเสียงสำหรับ
อัตโนมัติ คุณ
ให้ดู clientPackageName
ขณะเชื่อมต่อเพื่อระบุไคลเอ็นต์
และแสดง BrowserRoot
ที่แตกต่างกันโดยขึ้นอยู่กับไคลเอ็นต์ (หรือ rootHints
)
หากมี)
กำลังสื่อสารเนื้อหากับ onLoadChildren()
หลังจากที่ไคลเอ็นต์เชื่อมต่อแล้ว ไคลเอ็นต์จะข้ามลำดับชั้นเนื้อหาได้โดยการเรียกใช้ MediaBrowserCompat.subscribe()
ซ้ำๆ เพื่อสร้างการแสดง UI ในเครื่อง เมธอด subscribe()
จะส่ง Callback onLoadChildren()
ไปยังบริการ ซึ่งแสดงรายการออบเจ็กต์ MediaBrowser.MediaItem
MediaItem แต่ละรายการมีสตริงรหัสที่ไม่ซ้ำกัน ซึ่งเป็นโทเค็นแบบทึบ เมื่อลูกค้าต้องการเปิดเมนูย่อยหรือเล่นรายการ ระบบจะส่งรหัส บริการของคุณมีหน้าที่ในการเชื่อมโยงรหัสกับโหนดเมนูหรือรายการเนื้อหาที่เหมาะสม
การใช้งาน onLoadChildren()
อย่างง่ายอาจมีลักษณะเช่นนี้
override fun onLoadChildren(
parentMediaId: String,
result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>
) {
// Browsing not allowed
if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) {
result.sendResult(null)
return
}
// Assume for example that the music catalog is already loaded/cached.
val mediaItems = emptyList<MediaBrowserCompat.MediaItem>()
// Check if 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)
}
@Override
public void onLoadChildren(final String parentMediaId,
final Result<List<MediaItem>> result) {
// Browsing not allowed
if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) {
result.sendResult(null);
return;
}
// Assume for example that the music catalog is already loaded/cached.
List<MediaItem> mediaItems = new ArrayList<>();
// Check if 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);
}
หมายเหตุ: MediaItem
ออบเจ็กต์ที่ส่งโดย MediaBrowserService
ไม่ควรมีไอคอนบิตแมป โปรดใช้ Uri
แทนโดยการโทรออก
วันที่ setIconUri()
เมื่อคุณสร้าง MediaDescription
สำหรับแต่ละรายการ
สำหรับตัวอย่างวิธีใช้ onLoadChildren()
โปรดดูแอปตัวอย่าง Universal Android Music Player
วงจรชีวิตบริการเบราว์เซอร์สื่อ
ลักษณะการทำงานของบริการ Android จะขึ้นอยู่กับว่าบริการนั้นเริ่มต้นแล้วหรือมีการเชื่อมโยงกับไคลเอ็นต์อย่างน้อย 1 ราย หลังจากสร้างบริการแล้ว บริการจะเริ่มต้น ผูกกับ หรือทั้ง 2 อย่างก็ได้ ในทุกสถานะเหล่านี้ โทรศัพท์ทำงานได้อย่างสมบูรณ์และสามารถทำงานตามที่ออกแบบมาได้ สิ่งที่แตกต่างคือระยะเวลาของบริการ บริการที่มีผลผูกพันจะไม่ถูกทำลายจนกว่าไคลเอ็นต์ที่ผูกไว้ทั้งหมดจะยกเลิกการเชื่อมโยง บริการเริ่มต้นสามารถหยุดและทำลายได้อย่างชัดแจ้ง (หากไม่มีการเชื่อมโยงกับไคลเอ็นต์ใดๆ อีกต่อไป)
เมื่อ MediaBrowser
ที่ทำงานในกิจกรรมอื่นเชื่อมต่อกับ MediaBrowserService
ระบบจะเชื่อมโยงกิจกรรมกับบริการ ทำให้บริการมีผลผูกพัน (แต่ยังไม่เริ่มต้น) ลักษณะการทำงานเริ่มต้นนี้มีให้ในตัว MediaBrowserServiceCompat
บริการที่มีการเชื่อมโยงเท่านั้น (และไม่ได้เริ่มต้น) จะถูกทำลายเมื่อไคลเอ็นต์ทั้งหมดยกเลิกการเชื่อมโยง หากกิจกรรม UI ถูกตัดการเชื่อมต่อในจุดนี้ บริการจะถูกทำลาย แต่ไม่ต้องกังวลหากคุณยังไม่ได้เปิดเพลงใดๆ อย่างไรก็ตาม เมื่อเริ่มเล่น ผู้ใช้น่าจะต้องการฟังเพลงต่อแม้ว่าจะเปลี่ยนแอปแล้วก็ตาม คุณไม่ต้องการทำลายโปรแกรมเล่นเมื่อคุณยกเลิกการเชื่อมโยง UI เพื่อทำงานกับแอปอื่น
ด้วยเหตุนี้ คุณจึงต้องมั่นใจว่าบริการได้เริ่มทำงานเมื่อเริ่มต้น
ให้เล่นโดยโทรไปที่ startService()
ต
บริการที่เริ่มต้นจะต้องหยุดทำงานอย่างชัดเจน ไม่ว่าจะมีการเชื่อมโยงไว้หรือไม่ก็ตาม ช่วงเวลานี้
ทำให้มั่นใจได้ว่าโปรแกรมเล่นของคุณจะทำงานได้อย่างต่อเนื่องแม้ว่า UI ที่ใช้ควบคุม
ยกเลิกการเชื่อมโยง
หากต้องการหยุดบริการที่เริ่มต้น โปรดโทรไปที่ Context.stopService()
หรือ stopSelf()
ระบบจะหยุดและทำลายบริการโดยเร็วที่สุด อย่างไรก็ตาม หากมีลูกค้าอย่างน้อย 1 รายที่ยังคงผูกกับบริการ การเรียกใช้ให้หยุดบริการจะล่าช้าจนกว่าไคลเอ็นต์ทั้งหมดจะยกเลิกการเชื่อมโยง
วงจรของ MediaBrowserService
จะควบคุมโดยวิธีสร้าง จำนวนไคลเอ็นต์ที่เชื่อมโยงกับเซสชัน และการโทรที่ได้รับจาก Callback ของเซสชันสื่อ โดยสรุปได้ดังนี้
- บริการสร้างขึ้นเมื่อเริ่มต้นตอบสนองต่อปุ่มสื่อ หรือเมื่อกิจกรรมเชื่อมโยงกับบริการ (หลังจากเชื่อมต่อผ่าน
MediaBrowser
) - Callback ของเซสชันสื่อ
onPlay()
ควรมีโค้ดที่เรียกใช้startService()
การดำเนินการนี้จะทำให้บริการเริ่มต้นและทำงานต่อไปแม้ว่ากิจกรรมMediaBrowser
ของ UI ทั้งหมดที่เชื่อมโยงกับบริการดังกล่าวจะยกเลิกการเชื่อมโยงแล้วก็ตาม - Callback
onStop()
ควรโทรหาstopSelf()
หากเริ่มต้นบริการแล้ว การดำเนินการนี้จะหยุดลง นอกจากนี้ บริการจะถูกทำลายหากไม่มีกิจกรรมที่เชื่อมโยงกับบริการ มิฉะนั้น บริการจะยังคงมีผลจนกว่ากิจกรรมทั้งหมดจะยกเลิกการเชื่อมโยง (หากได้รับการเรียกstartService()
ครั้งต่อๆ ไปก่อนที่บริการจะถูกทำลาย การแวะพักที่รอดำเนินการจะถูกยกเลิก)
โฟลว์ชาร์ตต่อไปนี้แสดงการจัดการวงจรของบริการ ตัวนับตัวแปรจะติดตามจำนวนไคลเอ็นต์ที่เข้าเงื่อนไข
การใช้การแจ้งเตือน MediaStyle กับบริการที่ทำงานอยู่เบื้องหน้า
เมื่อเปิดบริการ บริการควรทำงานอยู่ในเบื้องหน้า ซึ่งจะช่วยให้ระบบทราบว่าบริการกำลังทำงานที่เป็นประโยชน์ และไม่ควรหยุดทำงานหากระบบมีหน่วยความจำต่ำ บริการที่ทำงานอยู่เบื้องหน้าต้องแสดงการแจ้งเตือนเพื่อให้ผู้ใช้รับรู้และสามารถเลือกควบคุมได้ Callback onPlay()
ควรให้ความสำคัญกับบริการที่ทำงานอยู่เบื้องหน้า (โปรดทราบว่านี่เป็นความหมายพิเศษของ "พื้นหน้า" ในขณะที่ Android จะพิจารณาบริการในเบื้องหน้าเพื่อวัตถุประสงค์ในการจัดการกระบวนการ สำหรับผู้ใช้ที่เล่นวิดีโออยู่ในเบื้องหลังและในแอปอื่นบางส่วนจะแสดงอยู่ใน "เบื้องหน้า" บนหน้าจอ)
เมื่อบริการทำงานอยู่ในเบื้องหน้า บริการดังกล่าวต้องแสดงการแจ้งเตือน พร้อมกับตัวควบคุมการรับส่งข้อมูลอย่างน้อย 1 รายการ โดยการแจ้งเตือนควรมีข้อมูลที่เป็นประโยชน์จากข้อมูลเมตาของเซสชันด้วย
สร้างและแสดงการแจ้งเตือนเมื่อโปรแกรมเล่นเริ่มเล่น ตำแหน่งที่ดีที่สุดที่ทำได้คือภายในเมธอด MediaSessionCompat.Callback.onPlay()
ตัวอย่างด้านล่างใช้เมธอด
NotificationCompat.MediaStyle
,
ซึ่งออกแบบมาสำหรับแอปสื่อ ซึ่งจะแสดงวิธีสร้างการแจ้งเตือนที่แสดงข้อมูลเมตาและการควบคุมการรับส่งข้อมูล วิธีการที่สะดวก
getController()
ช่วยให้คุณสร้างตัวควบคุมสื่อจากเซสชันสื่อได้โดยตรง
// Given a media session and its context (usually the component containing the session)
// Create a NotificationCompat.Builder
// Get the session's metadata
val controller = mediaSession.controller
val mediaMetadata = controller.metadata
val description = mediaMetadata.description
val builder = NotificationCompat.Builder(context, channelId).apply {
// Add the metadata for the currently playing track
setContentTitle(description.title)
setContentText(description.subtitle)
setSubText(description.description)
setLargeIcon(description.iconBitmap)
// Enable launching the player by clicking the notification
setContentIntent(controller.sessionActivity)
// Stop the service when the notification is swiped away
setDeleteIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
context,
PlaybackStateCompat.ACTION_STOP
)
)
// Make the transport controls visible on the lockscreen
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// Add an app icon and set its accent color
// Be careful about the color
setSmallIcon(R.drawable.notification_icon)
color = ContextCompat.getColor(context, R.color.primaryDark)
// Add a pause button
addAction(
NotificationCompat.Action(
R.drawable.pause,
getString(R.string.pause),
MediaButtonReceiver.buildMediaButtonPendingIntent(
context,
PlaybackStateCompat.ACTION_PLAY_PAUSE
)
)
)
// Take advantage of MediaStyle features
setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
.setShowActionsInCompactView(0)
// Add a cancel button
.setShowCancelButton(true)
.setCancelButtonIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
context,
PlaybackStateCompat.ACTION_STOP
)
)
)
}
// Display the notification and place the service in the foreground
startForeground(id, builder.build())
// Given a media session and its context (usually the component containing the session)
// Create a NotificationCompat.Builder
// Get the session's metadata
MediaControllerCompat controller = mediaSession.getController();
MediaMetadataCompat mediaMetadata = controller.getMetadata();
MediaDescriptionCompat description = mediaMetadata.getDescription();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
builder
// Add the metadata for the currently playing track
.setContentTitle(description.getTitle())
.setContentText(description.getSubtitle())
.setSubText(description.getDescription())
.setLargeIcon(description.getIconBitmap())
// Enable launching the player by clicking the notification
.setContentIntent(controller.getSessionActivity())
// Stop the service when the notification is swiped away
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
PlaybackStateCompat.ACTION_STOP))
// Make the transport controls visible on the lockscreen
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
// Add an app icon and set its accent color
// Be careful about the color
.setSmallIcon(R.drawable.notification_icon)
.setColor(ContextCompat.getColor(context, R.color.primaryDark))
// Add a pause button
.addAction(new NotificationCompat.Action(
R.drawable.pause, getString(R.string.pause),
MediaButtonReceiver.buildMediaButtonPendingIntent(context,
PlaybackStateCompat.ACTION_PLAY_PAUSE)))
// Take advantage of MediaStyle features
.setStyle(new MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(0)
// Add a cancel button
.setShowCancelButton(true)
.setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
PlaybackStateCompat.ACTION_STOP)));
// Display the notification and place the service in the foreground
startForeground(id, builder.build());
เมื่อใช้การแจ้งเตือน MediaStyle โปรดระมัดระวังการทำงานของการแจ้งเตือนเหล่านี้ การตั้งค่า NotificationCompat:
- เมื่อคุณใช้
setContentIntent()
บริการของคุณจะเริ่มต้นโดยอัตโนมัติเมื่อมีการแจ้งเตือน ซึ่งเป็นฟีเจอร์ที่มีประโยชน์ - อยู่ในเกณฑ์ "ไม่น่าเชื่อถือ" สถานการณ์
เช่น หน้าจอล็อก ระดับการเข้าถึงเริ่มต้นสำหรับเนื้อหาการแจ้งเตือนคือ
VISIBILITY_PRIVATE
คุณอาจต้องการดู ตัวควบคุมการนำส่งบนหน้าจอล็อก ดังนั้นVISIBILITY_PUBLIC
จึงเป็นตัวช่วยที่ต้องใช้ - โปรดระมัดระวังเมื่อตั้งค่าสีพื้นหลัง ในการแจ้งเตือนปกติใน Android เวอร์ชัน 5.0 ขึ้นไป สีจะมีผลเฉพาะกับพื้นหลังของ ไอคอนแอปขนาดเล็ก แต่สำหรับการแจ้งเตือน MediaStyle ก่อน Android 7.0 สี จะใช้สำหรับพื้นหลังของการแจ้งเตือนทั้งหมด ทดสอบสีพื้นหลัง เริ่ม อ่อนโยนต่อดวงตาและหลีกเลี่ยงการใช้สีที่สว่างมากหรือสีฟลูออเรสเซนต์
การตั้งค่าเหล่านี้จะใช้ได้เฉพาะเมื่อคุณใช้ NotificationCompat.MediaStyle อยู่เท่านั้น
- ใช้
setMediaSession()
เพื่อเชื่อมโยงการแจ้งเตือนกับเซสชันของคุณ การดำเนินการนี้ช่วยให้แอปของบุคคลที่สาม และอุปกรณ์ที่ใช้ร่วมกันเพื่อเข้าถึงและควบคุมเซสชัน - ใช้
setShowActionsInCompactView()
เพื่อเพิ่มการดำเนินการสูงสุด 3 อย่างที่จะแสดง contentView ขนาดมาตรฐานของการแจ้งเตือน (นี่คือปุ่มหยุดชั่วคราว ที่ระบุ) - ใน Android 5.0 (API ระดับ 21) ขึ้นไป คุณสามารถปัดการแจ้งเตือนออกไปเพื่อหยุด
เมื่อบริการไม่ได้ทำงานอยู่ในเบื้องหน้าอีกต่อไป คุณไม่สามารถทำ
ในเวอร์ชันก่อนหน้า เพื่ออนุญาตให้ผู้ใช้นำการแจ้งเตือนออกและหยุดการเล่น
ก่อน Android 5.0 (API ระดับ 21) คุณสามารถเพิ่มปุ่มยกเลิก
การแจ้งเตือนโดยโทรไปที่
setShowCancelButton(true)
และsetCancelButtonIntent()
เมื่อเพิ่มปุ่มหยุดชั่วคราวและยกเลิก คุณจะต้องมี PendingIntent เพื่อแนบ
การดำเนินการเล่น เมธอด MediaButtonReceiver.buildMediaButtonPendingIntent()
มีหน้าที่แปลง
จากการทำงาน PlayState ลงใน PendingIntent