Bạn nên phát nội dung đa phương tiện trong 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 dùng một ứng dụng khác. Thư viện Media3 cung cấp một loạt các 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 chứa Player
và MediaSession
trong một Dịch vụ riêng.
Đ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 không chạy ở nền trước.
Khi lưu trữ trình phát 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
sẽ giúp các ứng dụng bên ngoài như Trợ lý Google, các tuỳ chọn đ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 có thể khám phá dịch vụ của bạn, kết nối 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 được kết nối cùng lúc với cùng một MediaSessionService
, 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 3 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, đồng thời dịch vụ được tạo thực thể và khởi động. Đây là nơi tốt nhất để tạoPlayer
vàMediaSession
.onTaskRemoved(Intent)
được gọi khi người dùng đóng ứng dụng khỏi các thao tác gần đây. Nếu đang phát, ứng dụng có thể chọn tiếp tục chạy dịch vụ ở nền trước. Nếu người chơi bị tạm dừng, thì dịch vụ không ở nền trước và cần phải được dừng lại.onDestroy()
được gọi khi dịch vụ đang bị ngừng. Cần phát hành tất cả các tài nguyên, bao gồm cả trình phát và phiên hoạt độ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() } // 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ể dừng dịch vụ này 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 đa phương tiệ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 nội dung đa phương tiện của bạ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
Một ứ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
vào tệp kê khai và nếu bạn nhắm mục tiêu API 34 trở lên là 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 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 foregroundServiceType
bao gồm mediaPlayback
khi ứng dụng của bạn đang chạy trên 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ố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 của bạn sử dụng trình điều khiển nội dung đa phương tiện để gửi các 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 trên giao diện người dùng
MediaSession
nhận các 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 của bạn.
Thông báo
MediaSessionService
sẽ tự động tạo một MediaNotification
cho bạn, mã này hoạt động trong hầu hết trường hợp. Theo mặc định, thông báo đã xuất bản là thông báo MediaStyle
luôn cập nhật thông tin mới nhất của phiên phát nội dung đa phương tiện của bạ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 để điều khiển chế độ phát cho mọi ứng dụng khác được kết nối với cùng 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 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 đa phương tiện hoặc khai báo dưới dạng một phần của 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 trong bộ điều khiển nội dung đa phương tiện trên Android. Đọc thêm về cách tuỳ chỉnh các chế độ điều khiển nội dung đa phương tiện trên Android.
Tuỳ chỉnh thông báo
Để tuỳ chỉnh thông báo, hãy tạo MediaNotification.Provider
bằng DefaultMediaNotificationProvider.Builder
hoặc bằng cách triển khai giao diện nhà cung cấp theo cách tuỳ chỉnh. Thêm nhà cung cấp vào MediaSessionService
của bạn bằng setMediaNotificationProvider
.
Tiếp tục phát
Nút phát nội dung đa phương tiện là các nút phần cứng trên thiết bị Android và thiết bị ngoại vi khác, chẳng hạn như nút phát hoặc tạm dừng trên tai nghe Bluetooth. Media3 xử lý dữ liệu đầ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 bao gồm một API cho phép người dùng tiếp tục phát sau khi ứng dụng đã chấm dứt và thậm chí sau khi thiết bị đã khởi động lại. Tính năng tiếp tục phát sẽ bị tắt theo mặc định, tức 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 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 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 thông số khác như tốc độ phát, chế độ lặp lại hoặc chế độ trộn, thì onPlaybackResumption()
là nơi phù hợp để định cấu hình trình phát bằng các thông 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 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 để kiểm soát việc phát và hiển thị danh sách phát. Đồng thời, phiên này sẽ hiển thị với các ứng dụng khách bên ngoài như các trình đ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 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 khung 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 khung Android.
Sử dụng trình điều khiển thông báo về nội dung nghe nhìn
Quan trọng là bạn phải hiểu rằng các bộ điều khiển cũ hoặc bộ điều khiển khung này đọc cùng một giá trị từ 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 và đặt các lệnh có sẵn cũng như bố cục tuỳ chỉnh của ứng dụng đó. Dịch vụ sẽ kết nối trình điều khiển thông báo nội dung nghe nhìn với phiên của bạn và phiên này sử dụng ConnectionResult
do onConnect()
của lệnh gọi lại trả về để định cấu hình các thao tác và thao tác tuỳ chỉnh của phiên khung.
Trong trường hợp chỉ dành cho thiết bị di động, ứng dụng có thể cung cấp phương thức triển khai MediaSession.Callback.onConnect()
để đặt các lệnh có sẵn và bố cục tuỳ chỉnh dành riêng 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 dành cho thiết bị di động, bộ điều khiển Android Auto sẽ yêu cầu các lệnh có sẵn thích hợp, nếu không Media3 sẽ từ chối các lệnh tuỳ chỉnh được gửi đế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 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ột mô-đun ô tô minh hoạ khả năng hỗ trợ Automotive OS yêu cầu một tệp APK riêng.