Ứng dụng của bạn phải khai báo MediaBrowserService
bằng bộ lọc ý định trong tệp kê khai. Bạn có thể chọn tên dịch vụ của riêng mình; trong ví dụ sau, đó là "MediaPlaybackService".
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
Lưu ý: Cách triển khai được đề xuất của MediaBrowserService
là MediaBrowserServiceCompat
.
được xác định trong thư viện hỗ trợ Media-compat.
Trong toàn bộ trang này, thuật ngữ "MediaBrowserService" đề cập đến một phiên bản của MediaBrowserServiceCompat
.
Khởi động phiên phát nội dung đa phương tiện
Khi nhận được phương thức gọi lại trong vòng đời onCreate()
, dịch vụ sẽ thực hiện các bước sau:
- Tạo và khởi động phiên phát nội dung đa phương tiện
- Đặt lệnh gọi lại phiên phát nội dung đa phương tiện
- Đặt mã phiên phát nội dung đa phương tiện
Mã onCreate()
bên dưới minh hoạ các bước sau:
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()); } }
Quản lý kết nối máy khách
MediaBrowserService
có hai phương thức xử lý các kết nối ứng dụng: onGetRoot()
kiểm soát quyền truy cập vào dịch vụ và onLoadChildren()
cho phép ứng dụng tạo và hiển thị trình đơn của hệ phân cấp nội dung của MediaBrowserService
.
Kiểm soát việc kết nối máy khách bằng onGetRoot()
Phương thức onGetRoot()
trả về nút gốc của hệ phân cấp nội dung. Nếu phương thức này trả về giá trị rỗng, thì kết nối sẽ bị từ chối.
Để cho phép ứng dụng kết nối với dịch vụ của bạn và duyệt qua nội dung đa phương tiện của dịch vụ đó, onGetRoot() phải trả về một browserRoot không rỗng, là một mã nhận dạng gốc đại diện cho hệ thống phân cấp nội dung của bạn.
Để cho phép ứng dụng kết nối với MediaSession mà không cần duyệt web, onGetRoot() vẫn phải trả về một giá trị trình duyệt không rỗng, nhưng mã nhận dạng gốc phải đại diện cho một hệ phân cấp nội dung trống.
Quy trình triển khai onGetRoot()
thông thường có thể có dạng như sau:
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); } }
Trong một số trường hợp, bạn có thể muốn kiểm soát những người có thể kết nối với MediaBrowserService
của mình. Bạn có thể sử dụng danh sách kiểm soát quyền truy cập (ACL) để chỉ định các kết nối nào được phép hoặc liệt kê các kết nối nào nên bị cấm. Để biết ví dụ về cách triển khai ACL cho phép các kết nối cụ thể, hãy xem lớp PackageValidator trong ứng dụng mẫu Universal Android Music Player.
Bạn nên cân nhắc cung cấp nhiều hệ phân cấp nội dung tuỳ thuộc vào loại ứng dụng khách đang thực hiện truy vấn. Cụ thể, Android Auto sẽ giới hạn cách người dùng tương tác với các ứng dụng âm thanh. Để biết thêm thông tin, hãy xem phần Phát âm thanh cho ô tô. Bạn có thể xem clientPackageName
tại thời điểm kết nối để xác định loại ứng dụng và trả về một BrowserRoot
khác tuỳ thuộc vào ứng dụng (hoặc rootHints
nếu có).
Đang trao đổi nội dung với onLoadChildren()
Sau khi kết nối, ứng dụng có thể truyền tải hệ phân cấp nội dung bằng cách thực hiện các lệnh gọi lặp lại đến MediaBrowserCompat.subscribe()
để tạo một bản trình bày giao diện người dùng cục bộ. Phương thức subscribe()
gửi lệnh gọi lại onLoadChildren()
đến dịch vụ. Lệnh gọi lại này sẽ trả về danh sách các đối tượng MediaBrowser.MediaItem
.
Mỗi MediaItem có một chuỗi mã nhận dạng duy nhất. Đây là một mã thông báo mờ. Khi muốn mở trình đơn con hoặc phát một mục, ứng dụng sẽ chuyển mã nhận dạng. Dịch vụ của bạn chịu trách nhiệm liên kết mã nhận dạng với nút trình đơn hoặc mục nội dung thích hợp.
Cách triển khai onLoadChildren()
đơn giản có thể có dạng như sau:
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); }
Lưu ý: Các đối tượng MediaItem
do MediaBrowserService phân phối không được chứa các bitmap biểu tượng. Thay vào đó, hãy sử dụng Uri
bằng cách gọi setIconUri()
khi bạn tạo MediaDescription
cho từng mục.
Để biết ví dụ về cách triển khai onLoadChildren()
, hãy xem ứng dụng mẫu Universal Android Music Player.
Vòng đời dịch vụ trình duyệt nội dung đa phương tiện
Hành vi của một dịch vụ Android phụ thuộc vào việc dịch vụ đó được bắt đầu hay liên kết với một hay nhiều ứng dụng. Sau khi được tạo, một dịch vụ có thể được bắt đầu, liên kết hoặc cả hai. Ở tất cả các trạng thái này, dịch vụ này có đầy đủ chức năng và có thể thực hiện công việc mà nó được thiết kế để thực hiện. Sự khác biệt là dịch vụ sẽ tồn tại trong bao lâu. Dịch vụ ràng buộc sẽ không bị huỷ cho đến khi tất cả ứng dụng khách liên kết của dịch vụ đó huỷ liên kết. Một dịch vụ đã bắt đầu có thể bị dừng và huỷ một cách rõ ràng (giả sử dịch vụ đó không còn liên kết với bất kỳ ứng dụng nào).
Khi MediaBrowser
chạy trong một hoạt động khác kết nối với MediaBrowserService
, nó sẽ liên kết hoạt động đó với dịch vụ, khiến dịch vụ bị ràng buộc (nhưng chưa bắt đầu). Hành vi mặc định này được tích hợp vào lớp MediaBrowserServiceCompat
.
Dịch vụ chỉ được liên kết (và chưa bắt đầu) sẽ bị hủy khi tất cả các ứng dụng khách của dịch vụ đó huỷ liên kết. Nếu hoạt động trên giao diện người dùng bị ngắt kết nối tại thời điểm này, thì dịch vụ sẽ bị huỷ. Đây không phải là vấn đề nếu bạn chưa phát bản nhạc nào. Tuy nhiên, khi bắt đầu phát, người dùng có thể sẽ tiếp tục nghe nội dung ngay cả sau khi chuyển ứng dụng. Bạn không muốn huỷ bỏ người chơi khi huỷ liên kết để giao diện người dùng hoạt động với một ứng dụng khác.
Vì lý do này, bạn cần đảm bảo dịch vụ đã bắt đầu khi bắt đầu phát bằng cách gọi startService()
. Dịch vụ đã bắt đầu phải được dừng một cách rõ ràng, dù có bị ràng buộc hay không. Điều này đảm bảo rằng người chơi sẽ tiếp tục hoạt động ngay cả khi hoạt động trên giao diện người dùng đang kiểm soát bị huỷ liên kết.
Để dừng một dịch vụ đã bắt đầu, hãy gọi Context.stopService()
hoặc stopSelf()
. Hệ thống sẽ dừng lại và huỷ dịch vụ ngay khi có thể. Tuy nhiên, nếu một hoặc nhiều ứng dụng vẫn bị ràng buộc với dịch vụ, cuộc gọi ngừng dịch vụ sẽ bị trì hoãn cho đến khi tất cả các ứng dụng khách đó huỷ liên kết.
Vòng đời của MediaBrowserService
được kiểm soát bởi cách tạo, số lượng ứng dụng khách liên kết với vòng đời và số lệnh gọi nhận được từ phương thức gọi lại phiên phát nội dung đa phương tiện. Tóm tắt:
- Dịch vụ được tạo khi khởi động theo nút nội dung đa phương tiện hoặc khi một hoạt động liên kết với nút đó (sau khi kết nối qua
MediaBrowser
). - Lệnh gọi lại
onPlay()
phiên phát nội dung đa phương tiện phải bao gồm mã gọistartService()
. Điều này đảm bảo dịch vụ sẽ bắt đầu và tiếp tục chạy, ngay cả khi mọi hoạt độngMediaBrowser
trên giao diện người dùng được liên kết với dịch vụ đều bị huỷ liên kết. - Lệnh gọi lại
onStop()
sẽ gọistopSelf()
. Nếu dịch vụ đã được bắt đầu, dịch vụ sẽ dừng lại. Ngoài ra, dịch vụ sẽ bị huỷ nếu không có hoạt động nào liên kết với dịch vụ đó. Nếu không, dịch vụ sẽ vẫn bị ràng buộc cho đến khi tất cả các hoạt động của nó huỷ liên kết. (Nếu một lệnh gọistartService()
tiếp theo nhận được trước khi dịch vụ bị huỷ, thì lệnh dừng đang chờ xử lý sẽ bị huỷ.)
Sơ đồ quy trình sau đây minh hoạ cách quản lý vòng đời của một dịch vụ. Bộ đếm biến theo dõi số lượng ứng dụng bị ràng buộc:
Sử dụng thông báo MediaStyle với dịch vụ trên nền trước
Khi đang phát, dịch vụ phải chạy ở nền trước. Điều này cho hệ thống biết rằng dịch vụ đang thực hiện một chức năng hữu ích và không nên bị tắt nếu hệ thống sắp hết bộ nhớ. Dịch vụ trên nền trước phải hiển thị thông báo để người dùng biết và có thể tuỳ ý kiểm soát thông báo đó. Lệnh gọi lại onPlay()
sẽ đặt dịch vụ ở nền trước. (Lưu ý rằng đây là ý nghĩa đặc biệt của "nền trước". Mặc dù Android xem dịch vụ ở nền trước là nhằm mục đích quản lý quy trình, nhưng đối với người dùng, người chơi đang phát trong nền trong khi một số ứng dụng khác hiển thị ở nền trước trên màn hình.)
Khi chạy ở nền trước, dịch vụ phải hiển thị thông báo, tốt nhất là hiển thị một hoặc nhiều chức năng điều khiển truyền tải. Thông báo cũng phải bao gồm thông tin hữu ích từ siêu dữ liệu của phiên.
Tạo và hiển thị thông báo khi người chơi bắt đầu chơi. Nơi tốt nhất để thực hiện việc này là bên trong phương thức MediaSessionCompat.Callback.onPlay()
.
Ví dụ bên dưới sử dụng NotificationCompat.MediaStyle
, được thiết kế cho các ứng dụng đa phương tiện. Hướng dẫn này trình bày cách tạo một thông báo trình bày siêu dữ liệu và các phương thức điều khiển truyền tải. Phương thức tiện lợi
getController()
cho phép bạn tạo trình điều khiển nội dung nghe nhìn ngay trong phiên phát nội dung nghe nhìn.
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());
Khi sử dụng thông báo MediaStyle, hãy lưu ý hành vi của các chế độ cài đặt NotificationCompat sau:
- Khi bạn sử dụng
setContentIntent()
, dịch vụ của bạn sẽ tự động bắt đầu khi người dùng nhấp vào thông báo, một tính năng hữu ích. - Trong tình huống "không đáng tin cậy" như màn hình khoá, chế độ hiển thị mặc định cho nội dung thông báo là
VISIBILITY_PRIVATE
. Bạn nên xem các nút điều khiển phương tiện di chuyển trên màn hình khoá, vì vậy, bạn có thể dùngVISIBILITY_PUBLIC
. - Hãy cẩn thận khi đặt màu nền. Trong một thông báo thông thường trên Android phiên bản 5.0 trở lên, màu sắc chỉ được áp dụng cho nền của biểu tượng ứng dụng nhỏ. Tuy nhiên, đối với các thông báo MediaStyle trước Android 7.0, màu sắc được sử dụng cho toàn bộ nền thông báo. Kiểm tra màu nền. Để mắt nhẹ nhàng và tránh các màu quá sáng hoặc huỳnh quang.
Các chế độ cài đặt này chỉ dùng được khi bạn sử dụng NotificationCompat.MediaStyle:
- Sử dụng
setMediaSession()
để liên kết thông báo với phiên của bạn. Điều này cho phép các ứng dụng bên thứ ba và thiết bị đồng hành truy cập và kiểm soát phiên này. - Sử dụng
setShowActionsInCompactView()
để thêm tối đa 3 hành động sẽ xuất hiện trong contentView có kích thước chuẩn của thông báo. (Ở đây, nút tạm dừng sẽ được chỉ định.) - Trong Android 5.0 (API cấp 21) trở lên, bạn có thể vuốt một thông báo sang bên để dừng người chơi sau khi dịch vụ không còn chạy ở nền trước nữa. Bạn không thể làm điều này trong các phiên bản cũ hơn. Để cho phép người dùng xoá thông báo và dừng phát
trước Android 5.0 (API cấp 21), bạn có thể thêm nút huỷ ở góc trên bên phải
thông báo bằng cách gọi
setShowCancelButton(true)
vàsetCancelButtonIntent()
.
Khi thêm các nút tạm dừng và huỷ, bạn sẽ cần một PendingIntent để đính kèm vào hành động phát. Phương thức MediaButtonReceiver.buildMediaButtonPendingIntent()
thực hiện công việc chuyển đổi một hành động PlaybackState thành một PendingIntent.