แอปของคุณต้องประกาศ 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()
ด้านล่างแสดงขั้นตอนเหล่านี้
Kotlin
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) } } }
Java
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()
โดยทั่วไปอาจมีลักษณะดังนี้
Kotlin
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) } }
Java
@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()
อย่างง่ายอาจมีลักษณะเช่นนี้
Kotlin
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) }
Java
@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()
ช่วยให้คุณสร้างตัวควบคุมสื่อจากเซสชันสื่อได้โดยตรง
Kotlin
// 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())
Java
// 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