Często warto odtwarzać multimedia, gdy aplikacja nie działa na pierwszym planie. Na przykład odtwarzacz muzyki zwykle odtwarza muzykę, gdy użytkownik zablokował urządzenie lub używa innej aplikacji. Biblioteka Media3 udostępnia szereg interfejsów, które obsługują odtwarzanie w tle.
Używanie usługi MediaSessionService
Aby włączyć odtwarzanie w tle, umieść Player
i MediaSession
w osobnej usłudze.
Dzięki temu urządzenie może wyświetlać multimedia nawet wtedy, gdy aplikacja nie działa na pierwszym planie.
Gdy hostujesz odtwarzacz w Usłudze, użyj elementu MediaSessionService
.
Aby to zrobić, utwórz klasę, która rozszerza zakres MediaSessionService
`, a następnie utwórz w niej sesję multimediów.
Dzięki usłudze MediaSessionService
klienci zewnętrzni, np. Asystent Google, system sterowania multimediami i urządzenia towarzyszące, takie jak Wear OS, mogą wykrywać Twoją usługę, łączyć się z nią i kontrolować odtwarzanie – bez dostępu do aktywności w interfejsie aplikacji. Z jednym elementem MediaSessionService
może być połączonych jednocześnie wiele aplikacji klienckich, z których każda ma własny
MediaController
.
Wdrażanie cyklu życia usługi
Musisz wdrożyć 3 metody cyklu życia usługi:
- Usługa
onCreate()
jest wywoływana, gdy pierwszy kontroler ma się połączyć, a usługa zostaje utworzona i uruchomiona. To najlepsze miejsce do tworzeniaPlayer
iMediaSession
. onTaskRemoved(Intent)
jest wywoływany, gdy użytkownik zamknie aplikację z ostatnich zadań. Jeśli odtwarzanie trwa nadal, aplikacja może zostawić usługę działającą na pierwszym planie. Jeśli odtwarzacz jest wstrzymany, usługa nie działa na pierwszym planie i musi zostać zatrzymana.onDestroy()
jest wywoływany podczas zatrzymywania usługi. Wszystkie zasoby, w tym odtwarzacz i sesje, muszą być zwolnione.
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(); } }
Zamiast odtwarzania w tle aplikacja może zatrzymać usługę w dowolnym momencie, gdy użytkownik ją zamknie:
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(); }
Udziel dostępu do sesji multimediów
Zastąp metodę onGetSession()
, aby udostępnić innym klientom dostęp do sesji multimediów, która została utworzona podczas tworzenia usługi.
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; } }
Zadeklaruj usługę w pliku manifestu
Aplikacja wymaga uprawnień do uruchomienia usługi na pierwszym planie. Dodaj do pliku manifestu uprawnienie FOREGROUND_SERVICE
. Jeśli kierujesz reklamy na interfejs API na poziomie 34 lub wyższym niż na poziomie FOREGROUND_SERVICE_MEDIA_PLAYBACK
:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
Musisz też zadeklarować w pliku manifestu klasę Service
z filtrem intencji MediaSessionService
.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
Musisz zdefiniować foregroundServiceType
, który zawiera mediaPlayback
, gdy aplikacja działa na urządzeniu z Androidem 10 (poziom interfejsu API 29) lub nowszym.
Sterowanie odtwarzaniem za pomocą: MediaController
W sekcji Aktywność lub Fragment zawierający interfejs odtwarzacza możesz połączyć interfejs z sesją multimediów za pomocą elementu MediaController
. Twój interfejs użytkownika używa kontrolera multimediów do wysyłania z niego poleceń do odtwarzacza w trakcie sesji. Szczegółowe informacje o tworzeniu i używaniu MediaController
znajdziesz w przewodniku Tworzenie MediaController
.
Obsługa poleceń interfejsu
Urządzenie MediaSession
otrzymuje polecenia od kontrolera za pomocą swojego urządzenia MediaSession.Callback
. Zainicjowanie obiektu MediaSession
powoduje utworzenie domyślnej implementacji MediaSession.Callback
, która automatycznie obsługuje wszystkie polecenia wysyłane przez MediaController
do odtwarzacza.
Powiadomienie
MediaSessionService
automatycznie tworzy dla Ciebie MediaNotification
, który w większości przypadków powinien działać. Domyślnie opublikowane powiadomienie jest powiadomieniem MediaStyle
, które zawiera najnowsze informacje z sesji multimediów i zawiera elementy sterujące odtwarzaniem. MediaNotification
wie o Twojej sesji i może służyć do sterowania odtwarzaniem w innych aplikacjach połączonych z tą samą sesją.
Na przykład aplikacja do strumieniowego odtwarzania muzyki za pomocą elementu MediaSessionService
utworzyłaby element MediaNotification
zawierający tytuł, wykonawcę i okładkę albumu odtwarzanego aktualnie elementu multimedialnego wraz z elementami sterującymi odtwarzaniem na podstawie Twojej konfiguracji MediaSession
.
Wymagane metadane możesz podać w pliku multimedialnym lub zadeklarować jako część elementu multimedialnego, jak w tym fragmencie:
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();
Aplikacje mogą dostosowywać przyciski poleceń elementów sterujących Androida Media. Więcej informacji o dostosowywaniu elementów sterujących Android Media
Dostosowywanie powiadomień
Aby dostosować powiadomienie, utwórz MediaNotification.Provider
za pomocą DefaultMediaNotificationProvider.Builder
lub niestandardową implementację interfejsu dostawcy. Dodaj dostawcę do urządzenia MediaSessionService
za pomocą setMediaNotificationProvider
.
Wznawianie odtwarzania
Przyciski multimediów to przyciski sprzętowe dostępne na urządzeniach z Androidem i innych urządzeniach peryferyjnych, np. przycisk odtwarzania i wstrzymywania na zestawie słuchawkowym Bluetooth. Media3 obsługuje wejścia przycisków multimediów, gdy działa usługa.
Deklarowanie odbiornika przycisku multimediów Media3
Media3 zawiera interfejs API, który umożliwia użytkownikom wznawianie odtwarzania po zakończeniu działania aplikacji, a nawet po ponownym uruchomieniu urządzenia. Domyślnie wznawianie odtwarzania jest wyłączone. Oznacza to, że użytkownik nie może wznowić odtwarzania, gdy Twoja usługa nie jest uruchomiona. Aby to zrobić, najpierw zadeklaruj w pliku manifestu parametr MediaButtonReceiver
:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Wdrażanie wywołania zwrotnego wznawiania odtwarzania
Gdy urządzenie Bluetooth lub funkcja wznawiania w interfejsie systemu Android wysyła żądanie wznowienia odtwarzania, wywoływana jest metoda wywołania zwrotnego onPlaybackResumption()
.
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; }
Jeśli masz zapisane inne parametry, takie jak szybkość odtwarzania, tryb powtarzania czy tryb tasowania, parametr onPlaybackResumption()
będzie dobrym miejscem do skonfigurowania odtwarzacza z tymi parametrami, zanim Media3 przygotuje odtwarzacz i rozpocznie odtwarzanie po zakończeniu wywołania zwrotnego.
Zaawansowana konfiguracja kontrolera i zgodność wsteczna
Typowym scenariuszem jest użycie elementu MediaController
w interfejsie aplikacji do kontrolowania odtwarzania i wyświetlania playlisty. Jednocześnie sesja jest widoczna dla klientów zewnętrznych, takich jak sterowanie multimediami na Androidzie oraz Asystent na urządzeniach mobilnych i telewizorach,
Wear OS na zegarki i Android Auto w samochodach. Przykładem aplikacji z takim scenariuszem jest aplikacja demonstracyjna sesji Media3.
Te klienty zewnętrzne mogą używać interfejsów API takich jak MediaControllerCompat
starszej biblioteki AndroidaX lub android.media.session.MediaController
struktury Androida. Usługa Media3 jest w pełni zgodna wstecznie ze starszą biblioteką i zapewnia interoperacyjność z interfejsem Android Framework API.
Używanie kontrolera powiadomień o multimediach
Pamiętaj, że starsze kontrolery lub kontrolery platformy odczytują te same wartości z platformy PlaybackState.getActions()
i PlaybackState.getCustomActions()
. Aby określić działania i działania niestandardowe sesji platformy, aplikacja może używać kontrolera powiadomień o multimediach oraz ustawiać dostępne polecenia i układ niestandardowy. Usługa łączy kontroler powiadomień o multimediach z sesją, a sesja używa ConnectionResult
zwróconego przez onConnect()
wywołania zwrotnego do konfigurowania działań i działań niestandardowych w ramach sesji platformy.
Biorąc pod uwagę działanie na urządzeniach mobilnych, aplikacja może udostępnić implementację MediaSession.Callback.onConnect()
w celu ustawiania dostępnych poleceń i układu niestandardowego na potrzeby sesji platformy w ten sposób:
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(); }
Autoryzuj Androida Auto do wysyłania poleceń niestandardowych
Gdy używasz MediaLibraryService
i obsługujesz Androida Auto w aplikacji mobilnej, kontroler Androida Auto wymaga odpowiednich poleceń. W przeciwnym razie Media3 odrzuca polecenia niestandardowe z tego kontrolera:
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(); }
Aplikacja demonstracyjna sesji zawiera moduł motoryzacyjny, który demonstruje obsługę systemu operacyjnego Automotive, który wymaga osobnego pliku APK.