Bạn thường nên phát nội dung nghe nhìn khi ứng dụng không chạy ở nền trước. Ví dụ: trình phát nhạc thường tiếp tục phát nhạc khi người dùng đã khoá thiết bị hoặc đang sử dụng một ứng dụng khác. Thư viện Media3 cung cấp một loạt giao diện cho phép bạn hỗ trợ tính năng phát trong nền.
Sử dụng MediaSessionService
Để bật tính năng phát trong nền, bạn nên đưa Player và MediaSession vào một Dịch vụ riêng biệt.
Điều này cho phép thiết bị tiếp tục phân phát nội dung nghe nhìn ngay cả khi ứng dụng của bạn không chạy ở nền trước.
MediaSessionService cho phép phiên phát nội dung nghe nhìn chạy tách biệt với hoạt động của ứng dụngKhi lưu trữ trình phát bên trong một Dịch vụ, bạn nên sử dụng MediaSessionService.
Để thực hiện việc này, hãy tạo một lớp mở rộng MediaSessionService và tạo phiên phát nội dung nghe nhìn bên trong lớp đó.
Việc sử dụng MediaSessionService giúp các ứng dụng bên ngoài như Trợ lý Google, các nút điều khiển nội dung nghe nhìn của hệ thống, các nút nội dung nghe nhìn trên thiết bị ngoại vi hoặc các thiết bị đồng hành như Wear OS có thể khám phá dịch vụ của bạn, kết nối với dịch vụ đó và điều khiển việc phát mà không cần truy cập vào hoạt động giao diện người dùng của ứng dụng. Trên thực tế, có thể có nhiều ứng dụng khách kết nối với cùng một MediaSessionService cùng một lúc, mỗi ứng dụng có MediaController riêng.
Triển khai vòng đời dịch vụ
Bạn cần triển khai 2 phương thức vòng đời của dịch vụ:
onCreate()được gọi khi bộ điều khiển đầu tiên sắp kết nối và dịch vụ được tạo thực thể và bắt đầu. Đây là nơi tốt nhất để xây dựngPlayervàMediaSession.onDestroy()được gọi khi dịch vụ đang dừng. Tất cả tài nguyên, bao gồm cả trình phát và phiên, đều cần được giải phóng.
Bạn có thể tuỳ ý ghi đè onTaskRemoved(Intent) để tuỳ chỉnh những gì xảy ra khi người dùng đóng ứng dụng khỏi các tác vụ gần đây. Theo mặc định, dịch vụ sẽ tiếp tục chạy nếu quá trình phát đang diễn ra và sẽ dừng nếu không phát.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your Player and MediaSession in the onCreate lifecycle event override fun onCreate() { super.onCreate() val player = ExoPlayer.Builder(this).build() mediaSession = MediaSession.Builder(this, player).build() } // Remember to release the player and media session in onDestroy override fun onDestroy() { mediaSession?.run { player.release() release() mediaSession = null } super.onDestroy() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // Create your Player and MediaSession in the onCreate lifecycle event @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
Thay vì tiếp tục phát trong nền, bạn có thể dừng dịch vụ trong mọi trường hợp khi người dùng đóng ứng dụng:
Kotlin
@OptIn(UnstableApi::class) override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@OptIn(markerClass = UnstableApi.class) @Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Đối với mọi cách triển khai onTaskRemoved theo cách thủ công khác, bạn có thể sử dụng isPlaybackOngoing() để kiểm tra xem quá trình phát có được coi là đang diễn ra hay không và dịch vụ nền trước đã bắt đầu hay chưa.
Cung cấp quyền truy cập vào phiên phát nội dung nghe nhìn
Ghi đè phương thức onGetSession() để cấp cho các ứng dụng khác quyền truy cập vào phiên phát nội dung nghe nhìn được tạo khi dịch vụ được tạo.
Kotlin
class PlaybackService : MediaSessionService() { // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
class PlaybackService extends MediaSessionService { // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
Khai báo dịch vụ trong tệp kê khai
Ứng dụng cần có quyền FOREGROUND_SERVICE và FOREGROUND_SERVICE_MEDIA_PLAYBACK để chạy dịch vụ nền trước phát nội dung:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
Bạn cũng phải khai báo lớp Service trong tệp kê khai bằng bộ lọc ý định
của MediaSessionService và foregroundServiceType bao gồm
mediaPlayback.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
Điều khiển chế độ phát bằng MediaController
Trong Hoạt động hoặc Fragment chứa giao diện người dùng của trình phát, bạn có thể thiết lập mối liên kết giữa giao diện người dùng và phiên phát nội dung nghe nhìn bằng MediaController. Giao diện người dùng của bạn sử dụng trình điều khiển nội dung nghe nhìn để gửi lệnh từ giao diện người dùng đến trình phát trong phiên. Hãy xem hướng dẫn Tạo MediaController để biết thông tin chi tiết về cách tạo và sử dụng MediaController.
Xử lý lệnh MediaController
MediaSession nhận lệnh từ bộ điều khiển thông qua MediaSession.Callback. Việc khởi chạy MediaSession sẽ tạo ra một phương thức triển khai mặc định của MediaSession.Callback, tự động xử lý tất cả lệnh mà MediaController gửi đến trình phát của bạn.
Thông báo
MediaSessionService sẽ tự động tạo MediaNotification cho bạn,
thường sẽ hoạt động trong hầu hết các trường hợp. Theo mặc định, thông báo được xuất bản là một
thông báo MediaStyle luôn được cập nhật thông tin mới nhất
từ phiên phát nội dung nghe nhìn và hiển thị bộ điều khiển chế độ phát. MediaNotification nhận biết được phiên của bạn và có thể dùng để kiểm soát quá trình phát cho mọi ứng dụng khác được kết nối với cùng một phiên.
Ví dụ: một ứng dụng phát trực tuyến sử dụng MediaSessionService sẽ tạo một MediaNotification hiển thị tiêu đề, nghệ sĩ và ảnh bìa đĩa nhạc cho mục nội dung nghe nhìn hiện đang phát cùng với bộ điều khiển chế độ phát dựa trên cấu hình MediaSession của bạn.
Bạn có thể cung cấp siêu dữ liệu bắt buộc trong nội dung nghe nhìn hoặc khai báo siêu dữ liệu đó trong mục nội dung nghe nhìn như trong đoạn mã sau:
Kotlin
val mediaItem = MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build() ) .build() mediaController.setMediaItem(mediaItem) mediaController.prepare() mediaController.play()
Java
MediaItem mediaItem = new MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( new MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build()) .build(); mediaController.setMediaItem(mediaItem); mediaController.prepare(); mediaController.play();
Vòng đời thông báo
Thông báo được tạo ngay khi Player có các thực thể MediaItem trong danh sách phát.
Tất cả thông tin cập nhật về thông báo đều tự động diễn ra dựa trên trạng thái Player và MediaSession.
Bạn không thể xoá thông báo khi dịch vụ nền trước đang chạy. Để
xoá thông báo ngay lập tức, bạn phải gọi Player.release() hoặc xoá
danh sách phát bằng Player.clearMediaItems().
Nếu trình phát bị tạm dừng, dừng hoặc không hoạt động trong hơn 10 phút mà không có hoạt động tương tác nào khác của người dùng, thì dịch vụ sẽ tự động chuyển ra khỏi trạng thái dịch vụ nền trước để hệ thống có thể huỷ dịch vụ đó. Bạn có thể triển khai tính năng tiếp tục phát để cho phép người dùng khởi động lại vòng đời dịch vụ và tiếp tục phát sau đó.
Tuỳ chỉnh thông báo
Bạn có thể tuỳ chỉnh siêu dữ liệu về mục đang phát bằng cách sửa đổi MediaItem.MediaMetadata. Nếu muốn cập nhật siêu dữ liệu của một mục hiện có, bạn có thể dùng Player.replaceMediaItem để cập nhật siêu dữ liệu mà không làm gián đoạn quá trình phát.
Bạn cũng có thể tuỳ chỉnh một số nút xuất hiện trong thông báo bằng cách đặt các lựa chọn ưu tiên về nút nội dung nghe nhìn tuỳ chỉnh cho các nút điều khiển Nội dung nghe nhìn của Android. Đọc thêm về cách tuỳ chỉnh các nút điều khiển nội dung nghe nhìn trên Android.
Để tuỳ chỉnh thêm thông báo, hãy tạo một
MediaNotification.Provider
bằng DefaultMediaNotificationProvider.Builder
hoặc bằng cách tạo một phương thức triển khai tuỳ chỉnh của giao diện nhà cung cấp. Thêm nhà cung cấp vào MediaSessionService bằng setMediaNotificationProvider.
Tiếp tục phát
Sau khi MediaSessionService bị chấm dứt và ngay cả sau khi thiết bị được khởi động lại, bạn vẫn có thể cung cấp tính năng tiếp tục phát để cho phép người dùng khởi động lại dịch vụ và tiếp tục phát từ nơi họ đã dừng lại. Theo mặc định, tính năng tiếp tục phát sẽ bị tắt. Điều này có nghĩa là người dùng không thể tiếp tục phát khi dịch vụ của bạn không chạy. Để chọn sử dụng tính năng này, bạn cần khai báo
một trình nhận nút nội dung nghe nhìn và triển khai phương thức onPlaybackResumption.
Khai báo trình nhận nút nội dung nghe nhìn Media3
Bắt đầu bằng cách khai báo MediaButtonReceiver trong tệp kê khai:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Triển khai lệnh gọi lại tiếp tục phát
Khi thiết bị Bluetooth hoặc tính năng tiếp tục phát của Giao diện người dùng hệ thống Android yêu cầu tiếp tục phát, phương thức gọi lại onPlaybackResumption() sẽ được gọi.
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: MediaSession.ControllerInfo, isForPlayback: Boolean, ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> { val settableFuture = SettableFuture.create<MediaSession.MediaItemsWithStartPosition>() settableFuture.addListener( { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. val resumptionPlaylist = restorePlaylist() settableFuture.set(resumptionPlaylist) }, MoreExecutors.directExecutor(), ) return settableFuture }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller, boolean isForPlayback) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener( () -> { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item and the start position to use here. MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
Nếu bạn đã lưu trữ các tham số khác như tốc độ phát, chế độ lặp lại hoặc chế độ phát ngẫu nhiên, thì onPlaybackResumption() là nơi phù hợp để định cấu hình trình phát bằng các tham số này trước khi Media3 chuẩn bị trình phát và bắt đầu phát khi lệnh gọi lại hoàn tất.
Phương thức này được gọi trong thời gian khởi động để tạo thông báo tiếp tục giao diện người dùng hệ thống Android sau khi khởi động lại thiết bị với isForPlayback được đặt thành false. Đối với thông báo đa dạng, bạn nên điền vào các trường MediaMetadata như title và artworkData hoặc artworkUri của mục hiện tại bằng các giá trị có sẵn trên thiết bị, vì có thể chưa có quyền truy cập mạng. Bạn cũng có thể thêm MediaConstants.EXTRAS_KEY_COMPLETION_STATUS và MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE vào MediaMetadata.extras để cho biết vị trí tiếp tục phát.
Cấu hình bộ điều khiển nâng cao và khả năng tương thích ngược
Một trường hợp phổ biến là sử dụng MediaController trong giao diện người dùng của ứng dụng để điều khiển chế độ phát và hiển thị danh sách phát. Đồng thời, phiên này cũng được hiển thị cho các ứng dụng khách bên ngoài như các nút điều khiển nội dung nghe nhìn của Android và Trợ lý trên thiết bị di động hoặc TV, Wear OS cho đồng hồ và Android Auto trên ô tô. Ứng dụng minh hoạ phiên Media3 là một ví dụ về ứng dụng triển khai tình huống như vậy.
Các ứng dụng bên ngoài này có thể sử dụng các API như MediaControllerCompat của thư viện AndroidX cũ hoặc android.media.session.MediaController của nền tảng Android. Media3 hoàn toàn tương thích ngược với thư viện cũ và
cung cấp khả năng tương tác với API nền tảng Android.
Xác định bộ điều khiển đáng tin cậy
Bất kỳ ứng dụng nào cũng có thể cố gắng kết nối với phiên phát nội dung nghe nhìn hoặc thư viện của bạn. Nếu muốn hạn chế quyền truy cập vào bộ điều khiển hệ thống, bộ điều khiển có quyền kiểm soát nội dung nghe nhìn và ứng dụng của riêng bạn, thì bạn có thể sử dụng ControllerInfo.isTrusted() để kiểm tra quyền truy cập cơ bản. Ngoài ra, bạn có thể xác định các bộ điều khiển cụ thể hơn như bộ điều khiển thông báo nội dung nghe nhìn hoặc bộ điều khiển Android Auto như mô tả trong các phần sau.
Sử dụng trình điều khiển thông báo nội dung nghe nhìn
Điều quan trọng là bạn phải hiểu rằng các bộ điều khiển cũ và bộ điều khiển nền tảng này có cùng trạng thái và chế độ hiển thị không thể tuỳ chỉnh theo bộ điều khiển (ví dụ: PlaybackState.getActions() và PlaybackState.getCustomActions() có sẵn).
Bạn có thể sử dụng bộ điều khiển thông báo nội dung nghe nhìn để định cấu hình trạng thái được đặt trong phiên phát nội dung nghe nhìn của nền tảng nhằm đảm bảo khả năng tương thích với các bộ điều khiển cũ và bộ điều khiển nền tảng này.
Ví dụ: một ứng dụng có thể cung cấp cách triển khai MediaSession.Callback.onConnect() để đặt các lệnh có sẵn và lựa chọn ưu tiên về nút nội dung nghe nhìn dành riêng cho phiên nền tảng như sau:
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { if (session.isMediaNotificationController(controller)) { val playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build() // Custom button preferences and commands to configure the platform session. return immediateFuture( AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(listOf(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build() ) } // Default commands with default button preferences for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { Player.Commands playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS .buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build(); // Custom button preferences and commands to configure the platform session. return immediateFuture( new AcceptedResultBuilder(session, controller) .setMediaButtonPreferences(ImmutableList.of(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build()); } // Default commands with default button preferences for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
Cho phép Android Auto gửi lệnh tuỳ chỉnh
Khi sử dụng MediaLibraryService và để hỗ trợ Android Auto bằng ứng dụng di động, bộ điều khiển Android Auto cần có các lệnh thích hợp, nếu không Media3 sẽ từ chối các lệnh tuỳ chỉnh đến từ bộ điều khiển đó:
Kotlin
override fun onConnectAsync( session: MediaSession, controller: MediaSession.ControllerInfo, ): ListenableFuture<ConnectionResult> { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build() if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build() ) } // Default commands for all other controllers. return immediateFuture(AcceptedResultBuilder(session, controller).build()) }
Java
@Override public ListenableFuture<ConnectionResult> onConnectAsync( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(customCommand).build(); if (session.isMediaNotificationController(controller)) { // ... See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return immediateFuture( new AcceptedResultBuilder(session, controller) .setAvailableSessionCommands(sessionCommands) .build()); } // Default commands for all other controllers. return immediateFuture(new AcceptedResultBuilder(session, controller).build()); }
Ứng dụng minh hoạ của phiên này có một mô-đun ô tô, thể hiện khả năng hỗ trợ cho Automotive OS cần có một tệp APK riêng.