Odtwarzanie w tle z użyciem usługi MediaSessionService

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 szereg interfejsów, które umożliwiają obsługę odtwarzania w tle.

Używanie MediaSessionService

Aby włączyć odtwarzanie w tle, umieść elementy Player i MediaSession w osobnym Service. Dzięki temu urządzenie może nadal odtwarzać multimedia, nawet gdy aplikacja nie jest na pierwszym planie.

Usługa MediaSessionService umożliwia uruchamianie sesji multimedialnej oddzielnie od aktywności aplikacji.
Ilustracja 1. Element MediaSessionService umożliwia uruchomienie sesji multimedialnej niezależnie od działania aplikacji.

Gdy hostujesz odtwarzacz w usłudze, używaj znaku MediaSessionService. Aby to zrobić, utwórz klasę, która rozszerza MediaSessionService, i utwórz w niej sesję multimedialną.

Korzystanie z MediaSessionService umożliwia klientom zewnętrznym, takim jak Asystent Google, systemowe elementy sterujące multimediami, przyciski multimediów na urządzeniach peryferyjnych lub urządzenia towarzyszące, np. Wear OS, wykrywanie usługi, łączenie się z nią i sterowanie odtwarzaniem bez dostępu do aktywności interfejsu aplikacji. W rzeczywistości z tym samym MediaSessionService może być połączonych kilka aplikacji klienckich w tym samym czasie, a każda z nich ma własny MediaController.

Wdrażanie cyklu życia usługi

Musisz zaimplementować 2 metody cyklu życia usługi:

  • onCreate() jest wywoływana, gdy pierwszy kontroler ma się połączyć, a usługa jest tworzona i uruchamiana. To najlepsze miejsce do budowania PlayerMediaSession.
  • onDestroy() jest wywoływana, gdy usługa jest zatrzymywana. Wszystkie zasoby, w tym odtwarzacz i sesja, muszą zostać zwolnione.

Opcjonalnie możesz zastąpić metodę onTaskRemoved(Intent), aby dostosować działanie aplikacji, gdy użytkownik zamknie ją z listy ostatnio używanych aplikacji. Domyślnie usługa pozostaje uruchomiona, 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 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();
  }
}

Zamiast kontynuować odtwarzanie w tle, możesz zatrzymać usługę w każdym przypadku, gdy użytkownik zamknie aplikację:

Kotlin

override fun onTaskRemoved(rootIntent: Intent?) {
  pauseAllPlayersAndStopSelf()
}

Java

@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, a usługa na pierwszym planie została uruchomiona.

Zapewnianie dostępu do sesji multimedialnej

Zastąp metodę onGetSession(), aby przyznać innym klientom dostęp do sesji multimedialnej utworzonej 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ń FOREGROUND_SERVICEFOREGROUND_SERVICE_MEDIA_PLAYBACK, aby uruchomić usługę odtwarzania na pierwszym planie:

<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 MediaSessionServiceforegroundServiceType, 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 użytkownika do odtwarzacza w ramach sesji. Szczegółowe informacje o tworzeniu i używaniu MediaController znajdziesz w przewodniku Tworzenie MediaController.

Obsługa poleceń MediaController

MediaSession otrzymuje polecenia z kontrolera za pomocą MediaSession.Callback. Inicjowanie 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 MediaNotification, który w większości przypadków powinien działać prawidłowo. Domyślnie opublikowane powiadomienie to MediaStylepowiadomienie, 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 strumieniowego odtwarzania muzyki korzystająca z MediaSessionService utworzy MediaNotification, który będzie wyświetlać tytuł, wykonawcę i okładkę albumu aktualnie odtwarzanego elementu multimedialnego wraz z elementami sterującymi odtwarzaniem na podstawie konfiguracji MediaSession.

Wymagane metadane można podać w pliku 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 powiadomienia

Powiadomienie jest tworzone, gdy tylko na playliście pojawi się Player MediaItem.

Wszystkie aktualizacje powiadomień są wprowadzane automatycznie na podstawie stanu PlayerMediaSession.

Nie można usunąć powiadomienia, gdy usługa na pierwszym planie jest uruchomiona. Aby natychmiast usunąć powiadomienie, musisz zadzwonić pod numer Player.release() lub wyczyścić listę odtwarzania za pomocą Player.clearMediaItems().

Jeśli odtwarzacz jest wstrzymany, zatrzymany lub nie działa przez ponad 10 minut bez dalszych interakcji użytkownika, usługa automatycznie przestaje być usługą na pierwszym planie, dzięki czemu system może ją zamknąć. 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 multimediów w przypadku elementów sterujących multimediami na Androidzie. Więcej informacji o dostosowywaniu elementów sterujących multimediami na Androidzie

Aby jeszcze bardziej dostosować samo powiadomienie, utwórz element MediaNotification.Provider z elementem DefaultMediaNotificationProvider.Builder lub utwórz niestandardową implementację interfejsu dostawcy. Dodaj dostawcę do MediaSessionService za pomocą setMediaNotificationProvider.

Wznowienie odtwarzania

Po zakończeniu działania MediaSessionService, a nawet po ponownym uruchomieniu urządzenia, można zaoferować użytkownikom wznowienie odtwarzania, aby mogli ponownie uruchomić usługę i wznowić odtwarzanie w miejscu, w którym je przerwali. Domyślnie wznowienie odtwarzania jest wyłączone. Oznacza to, że użytkownik nie może wznowić odtwarzania, gdy usługa nie działa. Aby włączyć tę funkcję, musisz zadeklarować odbiornik przycisku multimediów i wdrożyć metodę onPlaybackResumption.

Zadeklaruj odbiornik przycisku multimediów Media3

Zacznij od zadeklarowania MediaButtonReceiver w manifeście:

<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 interfejsu systemu Android zażąda wznowienia odtwarzania, wywoływana jest metoda zwrotna 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, metadata (like title
    // and artwork) of the current item 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, 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 losowy, onPlaybackResumption() to dobre miejsce, aby skonfigurować odtwarzacz za pomocą tych parametrów, 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. W przypadku powiadomienia z elementami multimedialnymi zalecamy wypełnienie pól MediaMetadata, takich jak titleartworkData lub artworkUri bieżącego elementu, wartościami dostępnymi lokalnie, ponieważ dostęp do sieci może jeszcze nie być 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 kompatybilność wsteczna

Częstym scenariuszem jest używanie elementu MediaController w interfejsie aplikacji do sterowania odtwarzaniem i wyświetlania playlisty. Jednocześnie sesja jest udostępniana klientom zewnętrznym, takim jak elementy sterujące multimediami na Androidzie i Asystent na telefonie lub telewizorze, Wear OS na zegarkach i Android Auto w samochodach. Przykładem aplikacji, która realizuje taki scenariusz, jest aplikacja demonstracyjna sesji Media3.

Te zewnętrzne klienty mogą korzystać z interfejsów API, takich jak MediaControllerCompat starszej biblioteki AndroidX lub android.media.session.MediaController platformy Android. Media3 jest w pełni zgodna wstecznie ze starszą biblioteką i zapewnia interoperacyjność z interfejsem API platformy Android.

Korzystanie z kontrolera powiadomień o multimediach

Pamiętaj, że te starsze kontrolery i kontrolery platformy mają ten sam stan, a ich widoczności nie można dostosowywać do poszczególnych kontrolerów (np. dostępnych PlaybackState.getActions()PlaybackState.getCustomActions()). Aby skonfigurować stan ustawiony w sesji multimediów na platformie pod kątem zgodności ze starszymi kontrolerami i kontrolerami platformy, możesz użyć kontrolera powiadomień o multimediach.

Na przykład aplikacja może udostępniać implementację interfejsu MediaSession.Callback.onConnect(), aby ustawiać dostępne polecenia i preferencje przycisków multimedialnych specjalnie dla 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 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();
}

Autoryzowanie Androida Auto do wysyłania niestandardowych poleceń

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 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();
}

Aplikacja demonstracyjna sesji ma moduł samochodowy, który pokazuje obsługę systemu operacyjnego Automotive, co wymaga oddzielnego pliku APK.