Często pożądane jest odtwarzanie multimediów, gdy aplikacja nie jest na pierwszym planie. Na przykład odtwarzacz muzyki zwykle odtwarza muzykę, gdy użytkownik zablokuje urządzenie lub korzysta z innej aplikacji. Biblioteka Media3 udostępnia serię interfejsów, które umożliwiają obsługę odtwarzania w tle.
Używanie MediaSessionService
Aby włączyć odtwarzanie w tle, musisz umieścić Player i MediaSession w osobnej usłudze.
Dzięki temu urządzenie może nadal odtwarzać multimedia, nawet gdy aplikacja nie jest na pierwszym planie.
MediaSessionService umożliwia uruchamianie sesji multimedialnej niezależnie od aktywności aplikacjiJeśli odtwarzacz jest hostowany w Usłudze, należy użyć MediaSessionService.
Aby to zrobić, utwórz klasę, która rozszerza MediaSessionService, i utwórz w niej sesję multimedialną.
Użycie MediaSessionService umożliwia klientom zewnętrznym, takim jak Asystent Google, systemowe opcje sterowania multimediami, przyciski multimedialne na urządzeniach peryferyjnych lub urządzenia towarzyszące, np. Wear OS, wykrywanie Twojej usługi, łączenie się z nią i sterowanie odtwarzaniem bez konieczności uzyskiwania dostępu do aktywności interfejsu aplikacji. W rzeczywistości z jednym MediaSessionService może być połączonych wiele aplikacji klienckich jednocześnie, a każda z nich ma własny MediaController.
Wdrażanie cyklu życia usługi
Musisz zaimplementować 2 metody cyklu życia usługi:
- Metoda
onCreate()jest wywoływana, gdy pierwszy kontroler ma się połączyć, a usługa jest tworzona i uruchamiana. To najlepsze miejsce do budowaniaPlayeriMediaSession. - Metoda
onDestroy()jest wywoływana, gdy usługa jest zatrzymywana. We wszystkich zasobach, w tym w odtwarzaczu i sesji, należy zwolnić pamięć.
Możesz opcjonalnie zastąpić metodę onTaskRemoved(Intent), aby dostosować, co się stanie, gdy użytkownik zamknie aplikację z poziomu ostatnich zadań. Domyślnie usługa działa, jeśli odtwarzanie jest w toku, a w przeciwnym razie jest zatrzymywana.
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; } }
Zamiast kontynuować odtwarzanie w tle, możesz zatrzymać usługę, gdy użytkownik zamknie aplikację:
Kotlin
@OptIn(UnstableApi::class) override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@OptIn(markerClass = UnstableApi.class) @Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
W przypadku każdej innej ręcznej implementacji onTaskRemoved możesz użyć isPlaybackOngoing(), aby sprawdzić, czy odtwarzanie jest uznawane za trwające i czy usługa na pierwszym planie została uruchomiona.
Zapewnianie dostępu do sesji multimedialnej
Zastąp metodę onGetSession(), aby umożliwić innym klientom dostęp do sesji multimedialnej utworzonej podczas tworzenia usługi.
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; } }
Deklarowanie usługi w pliku manifestu
Aby uruchomić usługę odtwarzania na pierwszym planie, aplikacja wymaga uprawnień FOREGROUND_SERVICE i 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ć klasę Service w pliku manifestu z filtrem intencji MediaSessionService i foregroundServiceType, który zawiera 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>
Sterowanie odtwarzaniem za pomocą MediaController
W aktywności lub fragmencie zawierającym interfejs odtwarzacza możesz utworzyć połączenie między interfejsem a sesją multimedialną za pomocą MediaController. Interfejs użytkownika używa kontrolera multimediów do wysyłania poleceń z interfejsu do odtwarzacza w ramach sesji. Szczegółowe informacje o tworzeniu i używaniu MediaController znajdziesz w przewodniku Tworzenie MediaController.
Obsługa poleceń MediaController
MediaSession odbiera polecenia z kontrolera przez MediaSession.Callback. Inicjowanie MediaSession tworzy domyślną implementację MediaSession.Callback, która automatycznie obsługuje wszystkie polecenia wysyłane przez MediaController do odtwarzacza.
Powiadomienie
MediaSessionService automatycznie tworzy MediaNotification, który powinien działać w większości przypadków. Domyślnie opublikowane powiadomienie to powiadomienie MediaStyle, które jest aktualizowane o najnowsze informacje z sesji multimedialnej i wyświetla 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 odtwarzania strumieniowego muzyki, która używa MediaSessionService, utworzy MediaNotification, który wyświetla tytuł, wykonawcę i okładkę albumu dla aktualnie odtwarzanego elementu multimedialnego wraz z elementami sterującymi odtwarzaniem na podstawie konfiguracji MediaSession.
Wymagane metadane można podać w multimediach lub zadeklarować jako część elementu multimedialnego, jak w tym fragmencie kodu:
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();
Cykl życia powiadomień
Powiadomienie jest tworzone, gdy Player ma MediaItem instancje na playliście.
Wszystkie aktualizacje powiadomień są generowane automatycznie na podstawie stanu Player i MediaSession.
Nie można usunąć powiadomienia, gdy usługa na pierwszym planie jest uruchomiona. Aby natychmiast usunąć powiadomienie, musisz wywołać Player.release() lub wyczyścić playlistę za pomocą Player.clearMediaItems().
Jeśli odtwarzacz zostanie wstrzymany, zatrzymany lub ulegnie awarii na dłużej niż 10 minut bez dalszych interakcji użytkownika, usługa zostanie automatycznie wyłączona ze stanu usługi na pierwszym planie, aby system mógł ją usunąć. Możesz wdrożyć wznawianie odtwarzania, aby umożliwić użytkownikowi ponowne uruchomienie cyklu życia usługi i wznowienie odtwarzania w późniejszym czasie.
Dostosowywanie powiadomień
Metadane dotyczące aktualnie odtwarzanego elementu można dostosować, modyfikując MediaItem.MediaMetadata. Jeśli chcesz zaktualizować metadane istniejącego elementu, możesz użyć Player.replaceMediaItem, aby zaktualizować metadane bez przerywania odtwarzania.
Możesz też dostosować niektóre przyciski wyświetlane w powiadomieniu, ustawiając niestandardowe preferencje przycisków multimedialnych w opcjach sterowania multimediami na Androidzie. Więcej informacji o dostosowywaniu opcji sterowania multimediami na Androidzie
Aby dodatkowo dostosować powiadomienie, utwórz MediaNotification.Provider za pomocą DefaultMediaNotificationProvider.Builder lub niestandardowej implementacji interfejsu dostawcy. Dodaj dostawcę do MediaSessionService za pomocą setMediaNotificationProvider.
Wznowienie odtwarzania
Po zakończeniu działania usługi MediaSessionService, a nawet po ponownym uruchomieniu urządzenia, można zaoferować wznowienie odtwarzania, aby użytkownicy mogli ponownie uruchomić usługę i wznowić odtwarzanie od miejsca, w którym zostało przerwane. Domyślnie wznowienie odtwarzania jest wyłączone. Oznacza to, że użytkownik nie może wznowić odtwarzania, gdy usługa nie jest uruchomiona. Aby włączyć tę funkcję, musisz zadeklarować odbiornik przycisku multimediów i wdrożyć metodę onPlaybackResumption.
Deklarowanie odbiornika przycisków multimedialnych Media3
Zacznij od zadeklarowania MediaButtonReceiver w pliku manifestu:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Implementowanie wywołania zwrotnego wznowienia odtwarzania
Gdy urządzenie Bluetooth lub funkcja wznawiania interfejsu systemu Android zażąda wznowienia odtwarzania, wywoływana jest metoda wywołania zwrotnego onPlaybackResumption().
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; }
Jeśli masz zapisane inne parametry, takie jak szybkość odtwarzania, tryb powtarzania lub tryb odtwarzania losowego, onPlaybackResumption() to dobre miejsce na skonfigurowanie odtwarzacza z tymi parametrami, zanim Media3 przygotuje odtwarzacz i rozpocznie odtwarzanie po zakończeniu wywołania zwrotnego.
Ta metoda jest wywoływana podczas uruchamiania, aby utworzyć powiadomienie o wznowieniu interfejsu systemu Android po ponownym uruchomieniu urządzenia z ustawieniem isForPlayback ustawionym na false. W przypadku powiadomienia rozszerzonego zalecamy wypełnienie pól MediaMetadata, takich jak title i artworkData lub artworkUri, bieżącego elementu wartościami dostępnymi lokalnie, ponieważ dostęp do sieci może nie być jeszcze możliwy. Możesz też dodać MediaConstants.EXTRAS_KEY_COMPLETION_STATUS i MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE do MediaMetadata.extras, aby wskazać pozycję wznowienia odtwarzania.
Zaawansowana konfiguracja kontrolera i zgodność wsteczna
Typowym scenariuszem jest użycie elementu MediaController w interfejsie aplikacji do sterowania odtwarzaniem i wyświetlania playlisty. Jednocześnie sesja jest udostępniana klientom zewnętrznym, takim jak opcje sterowania multimediami na Androidzie i Asystent na urządzeniach mobilnych lub telewizorach, Wear OS na zegarkach i Android Auto w samochodach. Przykładem aplikacji, która implementuje taki scenariusz, jest aplikacja demonstracyjna sesji Media3.
Te zewnętrzne klienty mogą używać interfejsów API takich jak MediaControllerCompat starszej biblioteki AndroidX lub android.media.session.MediaController platformy Androida. Media3 jest w pełni zgodna wstecznie ze starszą biblioteką i zapewnia interoperacyjność z interfejsem API platformy Androida.
Identyfikowanie zaufanych kontrolerów
Każda aplikacja może próbować połączyć się z Twoją sesją lub biblioteką multimediów. Jeśli chcesz ograniczyć dostęp do kontrolerów systemowych, kontrolerów z uprawnieniami do sterowania treściami multimedialnymi i własnej aplikacji, możesz użyć ControllerInfo.isTrusted() do podstawowego sprawdzania dostępu. Możesz też określić bardziej szczegółowe kontrolery, takie jak kontroler powiadomień multimedialnych lub kontrolery Androida Auto, jak opisano w sekcjach poniżej.
Korzystanie z kontrolera powiadomień o multimediach
Ważne jest, aby pamiętać, że te starsze kontrolery i kontrolery platformy mają ten sam stan, a widoczności nie można dostosować według kontrolera (np. dostępnych PlaybackState.getActions() i PlaybackState.getCustomActions()).
Możesz użyć kontrolera powiadomień multimedialnych, aby skonfigurować stan ustawiony w sesji multimedialnej platformy pod kątem zgodności z tymi starszymi kontrolerami i kontrolerami platformy.
Na przykład aplikacja może udostępniać implementację MediaSession.Callback.onConnect(), aby ustawić dostępne polecenia i ustawienia przycisków multimedialnych specjalnie dla sesji platformy w ten sposób:
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()); }
Autoryzowanie Androida Auto do wysyłania poleceń niestandardowych
Gdy używasz MediaLibraryService i chcesz obsługiwać Androida Auto za pomocą aplikacji mobilnej, kontroler Androida Auto wymaga odpowiednich dostępnych poleceń. W przeciwnym razie Media3 odrzuci przychodzące polecenia niestandardowe z tego kontrolera:
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()); }
Aplikacja demonstracyjna sesji ma moduł samochodowy, który pokazuje obsługę systemu operacyjnego Automotive OS wymagającego osobnego pakietu APK.