Người dùng thường muốn phát nội dung nghe nhìn trong khi ứng dụng không chạy ở nền trước. Cho 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ị của họ hoặc đang sử dụng một ứng dụng khác. Thư viện Media3 cung cấp một loạt những giao diện hỗ trợ phát trong nền.
Sử dụng MediaSessionService
Để bật tính năng phát trong nền, bạn cần chứa Player
và
MediaSession
bên trong một Dịch vụ riêng biệt.
Nhờ đó, thiết bị có thể tiếp tục phân phát nội dung nghe nhìn ngay cả khi bạn không đóng ứng dụng
nền trước.
Khi lưu trữ trình phát bên trong một Dịch vụ, bạn nên 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 đa phương tiện bên trong nó.
Việc sử dụng MediaSessionService
sẽ giúp các khách hàng bên ngoài như Google có thể
Bạn có thể dùng Trợ lý, nút điều khiển nội dung nghe nhìn của hệ thống 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 quá trình phát lại, tất cả 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 được kết nối
vào cùng một MediaSessionService
cùng lúc, mỗi ứng dụng đều có
MediaController
Triển khai vòng đời dịch vụ
Bạn cần triển khai 3 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ựngPlayer
vàMediaSession
onTaskRemoved(Intent)
được gọi khi người dùng đóng ứng dụng khỏi công việc gần đây. Nếu quá trình phát đang diễn ra, ứng dụng có thể chọn tiếp tục sử dụng dịch vụ đang chạy ở nền trước. Nếu trình phát bị tạm dừng, dịch vụ sẽ không nằm trong ở chế độ nền trước và cần được dừng lại.onDestroy()
được gọi khi dịch vụ đang dừng. Tất cả tài nguyên bao gồm trình phát và phiên cần được phát hành.
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() } // The user dismissed the app from the recent tasks override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession?.player!! if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf() } } // 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(); } // The user dismissed the app from the recent tasks @Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf(); } } // 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 trong nền, ứng dụng có thể ngừng dịch vụ trong bất cứ trường hợp nào khi người dùng đóng ứng dụng:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession.player if (player.playWhenReady) { // Make sure the service is not in foreground. player.pause() } stopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (player.getPlayWhenReady()) { // Make sure the service is not in foreground. player.pause(); } stopSelf(); }
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 nội dung nghe nhìn của bạn
được tạo khi dịch vụ được tạo.
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 để chạy dịch vụ trên nền trước. Thêm
Quyền FOREGROUND_SERVICE
đối với tệp kê khai và nếu bạn nhắm đến API 34 và
trên cả FOREGROUND_SERVICE_MEDIA_PLAYBACK
:
<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
trong tổng số MediaSessionService
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
Bạn phải xác định một
foregroundServiceType
bao gồm mediaPlayback
khi ứng dụng của bạn đang chạy trên một thiết bị Android
10 (API cấp 29) trở lên.
Đ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ột đường 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 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 hoạt động. Xem
Tạo một MediaController
để biết thông tin chi tiết về cách tạo và sử dụng MediaController
.
Xử lý lệnh giao diện người dùng
MediaSession
nhận các lệnh từ bộ điều khiển thông qua
MediaSession.Callback
Việc khởi chạy MediaSession
sẽ tạo một giá trị mặc định
Việc triển khai MediaSession.Callback
tự động xử lý tất cả
lệnh MediaController
gửi đến người chơi của bạn.
Thông báo
MediaSessionService
sẽ tự động tạo một MediaNotification
cho bạn để
sẽ phát huy hiệu quả trong hầu hết các trường hợp. Theo mặc định, thông báo đã xuất bản sẽ là
Thông báo MediaStyle
liên tục cập nhật thông tin mới nhất
từ phiên phát nội dung đa phương tiện và hiện bộ điều khiển chế độ phát. MediaNotification
nhận biết phiên của bạn và có thể dùng để điều khiển quá trình phát cho bất kỳ ứng dụng nào khác
được kết nối với cùng một phiên.
Ví dụ: ứng dụng phát nhạc 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 đa phương tiện hiện tại đang được phát cùng với các bộ điều khiển phát dựa trên
Cấu hình MediaSession
.
Siêu dữ liệu bắt buộc có thể được cung cấp trong phương tiện truyền thông hoặc được khai báo trong mục nội dung đa phương tiệ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();
Các ứng dụng có thể tuỳ chỉnh các nút lệnh của chế độ điều khiển nội dung đa phương tiện trên Android. Đọc thêm về cách tuỳ chỉnh Android Media kiểm soát.
Tuỳ chỉnh thông báo
Để tuỳ chỉnh thông báo, hãy tạo một
MediaNotification.Provider
với DefaultMediaNotificationProvider.Builder
hoặc bằng cách tạo một hoạt động triển khai tuỳ chỉnh giao diện của nhà cung cấp. Thêm
cho MediaSessionService
của bạn bằng
setMediaNotificationProvider
.
Tiếp tục phát
Nút nội dung đa phương tiện là các nút phần cứng có trên thiết bị Android và thiết bị ngoại vi khác trên thiết bị, chẳng hạn như nút phát hoặc nút tạm dừng trên tai nghe Bluetooth. Nội dung nghe nhìn 3 xử lý đầu vào bằng nút đa phương tiện cho bạn khi dịch vụ đang chạy.
Khai báo trình nhận nút đa phương tiện Media3
Media3 có một API để cho phép người dùng tiếp tục
phát lại sau khi ứng dụng đã chấm dứt và ngay cả sau khi thiết bị đã ngừng
đã khởi động lại. Theo mặc định, tính năng tiếp tục phát sẽ 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, hãy bắt đầu bằng
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 thiết bị Bluetooth yêu cầu tiếp tục phát
Tính năng tiếp tục giao diện người dùng của hệ thống Android,
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 thông số khác như tốc độ phát, chế độ lặp lại hoặc
chế độ trộn bài, onPlaybackResumption()
là nơi phù hợp để định cấu hình trình phát
với 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 ứng dụng để kiểm soát
phát và hiển thị danh sách phát. Đồng thời, phiên hoạt động đó sẽ hiển thị
cho các ứng dụng bên ngoài như chế độ điều khiển nội dung nghe nhìn trên 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ạ trong phiên Media3
là ví dụ về ứng dụng triển khai tình huống như vậy.
Những ứng dụng bên ngoài này có thể sử dụng API như MediaControllerCompat
của API cũ
Thư viện AndroidX hoặc android.media.session.MediaController
của Android
khung. 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 khung Android.
Sử dụng trình điều khiển thông báo nội dung nghe nhìn
Quan trọng là bạn phải hiểu rằng những bộ điều khiển cũ hoặc bộ điều khiển khung này sẽ đọc
cùng các giá trị trong khung PlaybackState.getActions()
và
PlaybackState.getCustomActions()
Để xác định các thao tác và thao tác tuỳ chỉnh của
phiên khung, ứng dụng có thể sử dụng trình điều khiển thông báo nội dung nghe nhìn
đặt các lệnh có sẵn và bố cục tuỳ chỉnh. Dịch vụ này kết nối nội dung nghe nhìn
bộ điều khiển thông báo cho phiên của bạn và phiên hoạt động sẽ sử dụng
ConnectionResult
được onConnect()
của lệnh gọi lại trả về để định cấu hình
và thao tác tuỳ chỉnh của phiên khung đó.
Với trường hợp chỉ dành cho thiết bị di động, ứ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à
bố cục tuỳ chỉnh cụ thể cho phiên khung 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 layout and available commands to configure the legacy/framework session. return AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default custom layout 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 layout and available commands to configure the legacy/framework session. return new AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands without default custom layout for all other controllers. return new AcceptedResultBuilder(session).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, trình điều khiển Android Auto
yêu cầu các lệnh hiện có thích hợp, nếu không Media3 sẽ từ chối
lệnh tùy chỉnh đến từ bộ đ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 with default custom layout 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 without default custom layout for all other controllers. return new AcceptedResultBuilder(session).build(); }
Ứng dụng minh hoạ phiên có mô-đun ô tô, thể hiện tính năng hỗ trợ Automotive OS yêu cầu một tệp APK riêng biệt.