Ứng dụng 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 MediaBrowserService
được đề xuất
là MediaBrowserServiceCompat
.
được xác định trong phần tử
thư viện hỗ trợ media-compat.
Xuyên suốt trang này, thuật ngữ "MediaBrowserService" đề cập đến một thực thể của
trong tổng số MediaBrowserServiceCompat
.
Khởi chạy phiên nội dung nghe nhìn
Khi nhận được phương thức gọi lại trong vòng đời onCreate()
, dịch vụ phải thực hiện các bước sau:
- Tạo và khởi chạy phiên nội dung nghe nhìn
- Đặt lệnh gọi lại phiên phát nội dung nghe nhìn
- Đặt mã thông báo phiên đa phương tiện
Mã onCreate()
dưới đây minh hoạ các bước này:
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 khách hàng
MediaBrowserService
có 2 phương thức xử lý kết nối ứng dụng:
Chế độ cài đặt onGetRoot()
quyền truy cập vào dịch vụ và
onLoadChildren()
cung cấp cho ứng dụng khả năng tạo và hiển thị trình đơn hệ phân cấp nội dung của MediaBrowserService
.
Kiểm soát kết nối ứng dụng 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
trả về giá trị rỗng, kết nối bị từ chối.
Để cho phép khách hà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 ID 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 qua, onGetRoot() vẫn phải trả về một BrowserRoot không rỗng, nhưng ID gốc phải đại diện cho một hệ thống phân cấp nội dung trống.
Quy trình triển khai onGetRoot()
điển hình 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ào MediaBrowserService
của bạn. Một cách là sử dụng danh sách kiểm soát quyền truy cập (ACL)
chỉ định những kết nối được phép hoặc liệt kê một cách khác
những kết nối bị cấm. Để xem ví dụ về cách triển khai ACL
cho phép các kết nối cụ thể, hãy xem
PackageValidator trong một gói
trong Universal Android Music Player
ứng dụng mẫu.
Bạn nên cân nhắc việc cung cấp các hệ thống phân cấp nội dung khác nhau tuỳ thuộc vào
kiểu khách hàng nào đang thực hiện truy vấn. Cụ thể, Android Auto 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ự động. Bạn
có thể xem clientPackageName
tại thời điểm kết nối để xác định ứng dụng
loại 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 cục bộ của giao diện người dùng. Phương thức subscribe()
gửi lệnh gọi lại onLoadChildren()
đến dịch vụ, lệnh 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 này. 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 đơn giản của onLoadChildren()
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 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 mỗi 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 tạo, 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, ứng dụng có đầy đủ chức năng và có thể thực hiện công việc được thiết kế để thực hiện. Điểm khác biệt là khoảng thời gian dịch vụ sẽ tồn tại. Dịch vụ ràng buộc sẽ không bị huỷ bỏ cho đến khi tất cả ứng dụng ràng buộc của dịch vụ đó huỷ liên kết. Dịch vụ đã bắt đầu có thể bị dừng và huỷ bỏ một cách rõ ràng (giả sử dịch vụ đó không còn bị ràng buộc với bất kỳ ứng dụng nào).
Khi một MediaBrowser
chạy trong một hoạt động khác kết nối với một MediaBrowserService
, nó sẽ liên kết hoạt động đó với dịch vụ, khiến dịch vụ được liên kết (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ị huỷ khi tất cả ứng dụng khách huỷ liên kết. Nếu hoạt động giao diện người dùng của bạn ngắt kết nối vào thời điểm này, thì dịch vụ sẽ bị huỷ bỏ. Nếu bạn chưa phát bản nhạc nào thì việc này không có vấn đề gì. Tuy nhiên, khi quá trình phát bắt đầu, có thể người dùng muốn tiếp tục nghe ngay cả khi đã chuyển ứng dụng. Bạn không muốn huỷ bỏ trình phát 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ụ được khởi động ngay khi bắt đầu
để phát bằng cách gọi startService()
. Đáp
dịch vụ đã bắt đầu phải được dừng một cách rõ ràng, dù dịch vụ đó có bị ràng buộc hay không. Chiến dịch này
đảm bảo trình phát của bạn tiếp tục hoạt động ngay cả khi giao diện người dùng đang điều khiển
huỷ liên kết hoạt động.
Để dừng một dịch vụ đã bắt đầu, hãy gọi Context.stopService()
hoặc stopSelf()
. Hệ thống sẽ ngừng và huỷ bỏ dịch vụ ngay khi có thể. Tuy nhiên, nếu một hoặc nhiều ứng dụng vẫn còn bị ràng buộc với dịch vụ, lệnh gọi dừng dịch vụ sẽ bị trì hoãn cho đến khi tất cả ứng dụng khách huỷ liên kết.
Vòng đời của MediaBrowserService
được kiểm soát theo cách nó được tạo, số lượng ứng dụng khách được liên kết với nó và số lệnh gọi nhận được từ các lệnh gọi lại phiên đa phương tiện. Tóm tắt:
- Dịch vụ được tạo khi khởi động để phản hồi một nút đa phương tiện hoặc khi một hoạt động liên kết với hoạt động đó (sau khi kết nối qua
MediaBrowser
). - Lệnh gọi lại
onPlay()
của 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ụ bắt đầu và tiếp tục chạy, ngay cả khi tất cả hoạt độngMediaBrowser
của giao diện người dùng được liên kết với dịch vụ đã huỷ liên kết. - Lệnh gọi lại
onStop()
sẽ gọistopSelf()
. Nếu dịch vụ đã được bắt đầu, thì dịch vụ sẽ dừng lại. Ngoài ra, dịch vụ sẽ bị huỷ bỏ 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 dịch vụ đó được huỷ liên kết. (Nếu lệnh gọistartService()
tiếp theo được nhận 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 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 một dịch vụ đang hoạt động, 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 cho thấy thông báo để người dùng biết về dịch vụ đó và có thể kiểm soát nếu muốn. Lệnh gọi lại onPlay()
sẽ đưa dịch vụ lên nền trước. (Xin lưu ý rằng đây là một ý nghĩa đặc biệt của "nền trước". Mặc dù Android xem xét dịch vụ ở nền trước nhằm mục đích quản lý quy trình, nhưng đối với người dùng, trình phát đang phát trong nền còn một số ứng dụng khác chỉ hiển thị ở "nền trước" trên màn hình).
Khi một dịch vụ chạy ở nền trước, dịch vụ đó phải hiển thị thông báo, lý tưởng nhất là đi kèm một hoặc nhiều phương thức đ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. Bạn nên thực hiện việc này bên trong phương thức MediaSessionCompat.Callback.onPlay()
.
Ví dụ bên dưới sử dụng phương pháp
NotificationCompat.MediaStyle
,
được thiết kế cho các ứng dụng đa phương tiện. Hướng dẫn này cho biết cách tạo một thông báo hiển thị 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 của mình.
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 thông báo này Chế độ cài đặt NotificationCompat:
- Khi bạn sử dụng
setContentIntent()
, dịch vụ của bạn sẽ tự động bắt đầu khi có thông báo được nhấp vào, một tính năng tiện dụng. - Trong trường hợp "không đáng tin cậy" tình hình hiện tại
chẳng hạn 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 có thể muốn thấy các nút điều khiển chuyển giao trên màn hình khoá, vì vậy bạn có thể sử 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 ở Android phiên bản 5.0 trở lên, màu chỉ được áp dụng cho nền của biểu tượng ứng dụng nhỏ. Nhưng đối với thông báo MediaStyle trước Android 7.0, màu được dùng cho toàn bộ nền thông báo. Kiểm tra màu nền của bạn. Tìm nhẹ nhàng cho mắt và tránh các màu quá sáng hoặc màu huỳnh quang.
Các chế độ cài đặt này chỉ dùng được khi bạn đang 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. Chế độ này cho phép các ứng dụng bên thứ ba và các thiết bị đồng hành để truy cập và kiểm soát phiên. - Sử dụng
setShowActionsInCompactView()
để thêm tối đa 3 hành động sẽ xuất hiện contentView có kích thước chuẩn của thông báo. (Ở đây, nút tạm dừng đã chỉ định.) - Trong Android 5.0 (API cấp 21) trở lên, bạn có thể vuốt thông báo sang bên để dừng
trình phát sau khi dịch vụ không còn chạy ở nền trước. Bạn không thể thực hiện
trong các phiên bản trước. Để cho phép người dùng xoá thông báo và dừng phát
trước phiên bản Android 5.0 (API cấp 21), bạn có thể thêm một nút huỷ ở góc trên bên phải của
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 cần có một PendingIntent để đính kèm
đối với hành động phát. Phương thức MediaButtonReceiver.buildMediaButtonPendingIntent()
thực hiện công việc chuyển đổi
thao tác PlaybackState vào một PendingIntent.