Thường thì bạn 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ợ chế độ phát trong nền.
Sử dụng MediaSessionService
Để bật tính năng phát trong nền, bạn nên chứa Player
và MediaSession
bên trong 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 đa phương tiệ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 đa phương tiện chạy riêng biệt với hoạt động của ứng dụngKhi lưu trữ người chơi 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 đa phương tiện bên trong lớp đó.
Việc sử dụng MediaSessionService
cho phé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 khám phá dịch vụ của bạn, kết nối với dịch vụ đó và điều khiển chế độ phát mà không cần truy cập vào hoạt động trên 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 của 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 tay điều khiển đầu tiên sắp kết nối và dịch vụ được tạo bản sao và khởi động. Đây là nơi tốt nhất để tạoPlayer
vàMediaSession
.onDestroy()
được gọi khi dịch vụ đang bị dừng. Bạn cần giải phóng tất cả tài nguyên, bao gồm cả trình phát và phiên.
Bạn có thể 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ụ này 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.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your player and media session 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() } }
Java
public 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(); } }
Thay vì tiếp tục phát ở chế độ nền, bạn có thể ngừng dịch vụ trong mọi trường hợp khi người dùng đóng ứng dụng:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Đối với mọi phương thức triển khai thủ công khác của onTaskRemoved
, 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 và dịch vụ trên nền trước có được khởi động hay không.
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 đa phương tiện được tạo khi tạo dịch vụ.
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // [...] 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ụ phát trên nền trước:
<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 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 Mảnh 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 đa phương tiện bằng MediaController
. Giao diện người dùng 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ừ tay điều khiển thông qua MediaSession.Callback
. Việc khởi chạy MediaSession
sẽ tạo một phương thức triển khai mặc định của MediaSession.Callback
. Phương thức này sẽ tự động xử lý tất cả các lệnh mà MediaController
gửi đến trình phát.
Thông báo
MediaSessionService
sẽ tự động tạo MediaNotification
cho bạn và sẽ hoạt động trong hầu hết các trường hợp. Theo mặc định, thông báo đã 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ị các nút điều khiển phát. MediaNotification
nhận biết được phiên của bạn và có thể được dùng để kiểm soát chế độ 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 nhạc 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 đa phương tiện hiện đang phát cùng với các chế độ điều khiển 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 diễn ra tự động dựa trên trạng thái Player
và MediaSession
.
Không thể xoá thông báo trong khi dịch vụ trên 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ó thêm hoạt động tương tác nào 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ụ trên nền trước để hệ thống có thể huỷ bỏ 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 vào lúc khác.
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ể sử 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 tuỳ 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 chế độ đ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ị khởi động lại, bạ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. 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 trình thu nhận nút phát nội dung đa phương tiện và triển khai phương thức onPlaybackResumption
.
Khai báo trình thu 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 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: ControllerInfo ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist and the start position // to use here val resumptionPlaylist = restorePlaylist() settable.set(resumptionPlaylist) } return settable }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist 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.
Cấu hình tay đ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 để kiểm soát việc phát và hiển thị danh sách phát. Đồng thời, phiên này được hiển thị cho các ứng dụng bên ngoài như các chế độ điều khiển nội dung nghe nhìn và Trợ lý trên thiết bị di động hoặc TV, Wear OS cho đồng hồ và Android Auto trong ô tô. Ứng dụng minh hoạ phiên Media3 là ví dụ về một ứng dụng triển khai trường hợp 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.
Sử dụng trình điều khiển thông báo nội dung nghe nhìn
Điều quan trọng cần hiểu là các trình điều khiển nền tảng và cũ này có cùng trạng thái và chế độ hiển thị không thể được tuỳ chỉnh theo trình điều khiển (ví dụ: PlaybackState.getActions()
và PlaybackState.getCustomActions()
hiện có). Bạn có thể sử dụng trình điều khiển thông báo đa phương tiện để định cấu hình trạng thái được đặt trong phiên đa phương tiện của nền tảng nhằm tương thích với các trình điều khiển nền tảng và cũ 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 đa phương tiện dành riêng cho phiên nền tảng như sau:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() 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 AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default button preferences for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); 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 new AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands with default button preferences for all other controllers. return new AcceptedResultBuilder(session).build(); }
Uỷ quyền cho 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, tay điều khiển Android Auto yêu cầu các lệnh có sẵn phù hợp, nếu không, Media3 sẽ từ chối các lệnh tuỳ chỉnh đến từ tay điều khiển đó:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available session commands to accept incoming custom commands from Auto. return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands for all other controllers. return new AcceptedResultBuilder(session).build(); }
Ứng dụng minh hoạ phiên có một mô-đun ô tô, minh hoạ khả năng hỗ trợ cho Automotive OS yêu cầu một APK riêng.