Odtwarzanie w tle z użyciem usługi MediaSessionService

Często dobrze jest odtwarzać multimedia, gdy aplikacja nie działa na pierwszym planie. Dla: Odtwarzacz muzyki zwykle odtwarza treści tylko wtedy, gdy użytkownik jest zablokowany z jego urządzenia lub korzysta z innej aplikacji. Biblioteka Media3 udostępnia szereg funkcji które umożliwiają odtwarzanie w tle.

Korzystanie z usługi MediaSessionService

Aby włączyć odtwarzanie w tle, pliki Player i MediaSession w osobnej Usłudze. Dzięki temu urządzenie może wyświetlać multimedia nawet wtedy, gdy aplikacja nie jest zainstalowana na pierwszym planie.

Usługa MediaSessionService umożliwia oddzielne uruchamianie sesji multimediów
  związane z aktywnością w aplikacji
Rys. 1. Na urządzeniu MediaSessionService można włączyć odtwarzanie multimediów sesja uruchamiana niezależnie od aktywności w aplikacji

Jeśli hostujesz odtwarzacz w usłudze, używaj MediaSessionService. Aby to zrobić, utwórz klasę, która rozszerza zakres MediaSessionService”, a następnie utwórz i całej sesji multimedialnej.

Użycie MediaSessionService umożliwia klientom zewnętrznym, takim jak Google. z Asystentem, systemem sterowania multimediami w systemie lub na urządzeniach towarzyszących, takich jak Wear OS, połączyć się z nią i sterować odtwarzaniem, a wszystko to bez dostępu do ani działań związanych z interfejsem aplikacji. Wiele aplikacji klienckich może być połączonych z tą samą usługą MediaSessionService jednocześnie, każda aplikacja ma własną MediaController

Wdrażanie cyklu życia usługi

Musisz wdrożyć 3 metody cyklu życia usługi:

  • Funkcja onCreate() jest wywoływana, gdy ma się połączyć pierwszy kontroler. została utworzona i uruchomiona usługa. To najlepsze miejsce do budowania Player i MediaSession
  • Pole onTaskRemoved(Intent) jest wywoływane, gdy użytkownik zamknie aplikację w Ostatnie zadania. Jeśli odtwarzanie jest w toku, aplikacja może zachować usługę które działają na pierwszym planie. Jeśli odtwarzacz jest wstrzymany, usługi nie ma w na pierwszym planie i należy go zatrzymać.
  • Funkcja onDestroy() jest wywoływana podczas zatrzymywania usługi. Wszystkie zasoby w tym odtwarzacz i sesję.
.

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 zatrzymanie usługi w każdym przypadku, gdy użytkownik zamknie aplikację:

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

Zapewnianie dostępu do sesji multimediów

Zastąp metodę onGetSession(), aby udzielić innym klientom dostępu do Twoich multimediów 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ń do uruchomienia usługi na pierwszym planie. Dodaj parametr FOREGROUND_SERVICE do pliku manifestu, a jeśli kierujesz reklamy na interfejs API 34 powyżej także 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 za pomocą filtra intencji z 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 w tym mediaPlayback, gdy Twoja aplikacja działa na urządzeniu z Androidem 10 (poziom interfejsu API 29) lub wyższym.

Steruj odtwarzaniem za pomocą: MediaController

W aktywności lub fragmencie zawierającym interfejs odtwarzacza możesz utworzyć link między interfejsem a sesją multimediów za pomocą interfejsu MediaController. Interfejs użytkownika używa za pomocą kontrolera multimediów, aby wysyłać polecenia z interfejsu do odtwarzacza . Zobacz Tworzenie MediaController przewodnik po tworzeniu i używaniu MediaController.

Obsługa poleceń interfejsu

MediaSession odbiera polecenia od kontrolera przez swój MediaSession.Callback Inicjowanie elementu MediaSession powoduje utworzenie wartości domyślnej implementacja MediaSession.Callback, która automatycznie obsługuje wszystkie polecenia, które MediaController wysyła do odtwarzacza.

Powiadomienie

MediaSessionService automatycznie tworzy dla Ciebie MediaNotification, który powinno zadziałać w większości przypadków. Opublikowane powiadomienie jest domyślnie MediaStyle powiadomienie który jest zawsze na bieżąco z sesji multimediów i wyświetlają elementy sterujące odtwarzaniem. MediaNotification wie o Twojej sesji i może kontrolować odtwarzanie w innych aplikacjach połączonych z tą samą sesją.

Na przykład aplikacja do strumieniowego odtwarzania muzyki, która korzysta z MediaSessionService, mogłaby utworzyć MediaNotification, w którym wyświetla się tytuł, wykonawca i okładka albumu bieżącego elementu multimedialnego odtwarzanego razem z elementami sterującymi odtwarzaniem na podstawie Konfiguracja MediaSession.

Wymagane metadane można podać w mediach lub zadeklarować je jako część element multimedialny jak ten fragment 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();

Aplikacje mogą dostosować przyciski poleceń w elementach sterujących Android Media. Więcej informacji o dostosowywaniu usługi Android Media .

Dostosowywanie powiadomień

Aby dostosować powiadomienie, utwórz MediaNotification.Provider z DefaultMediaNotificationProvider.Builder lub tworząc niestandardową implementację interfejsu dostawcy. Dodaj: operatora sieci MediaSessionService za pomocą setMediaNotificationProvider

Wznowienie odtwarzania

Przyciski multimediów to przyciski sprzętowe dostępne na urządzeniach z Androidem i innych urządzeniach peryferyjnych urządzeniach takich jak przycisk odtwarzania lub wstrzymywania w zestawie słuchawkowym Bluetooth. Multimedia3 obsługuje wejściowe przyciski multimediów, gdy usługa jest uruchomiona.

Zadeklarowanie odbiornika przycisku multimediów Media3

Media3 udostępnia interfejs API, który umożliwia użytkownikom wznowienie pracy. odtwarzania po zakończeniu działania aplikacji, nawet po wyłączeniu urządzenia. Zrestartowano. Wznawianie odtwarzania jest domyślnie wyłączone. Oznacza to, że użytkownik nie może wznowić odtwarzania, gdy usługa nie jest uruchomiona. Aby wyrazić zgodę, zacznij od zadeklaruj pole 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>

Wdrażanie wywołania zwrotnego wznowienia odtwarzania

Gdy urządzenie Bluetooth lub funkcja wznowienia interfejsu systemu Android, onPlaybackResumption() metoda wywołania zwrotnego.

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 lub trybu losowego, onPlaybackResumption() to dobre miejsce do konfigurowania odtwarzacza z tymi parametrami, zanim Media3 przygotuje odtwarzacz i rozpocznie odtwarzanie, oddzwanianie zostanie zakończone.

Zaawansowana konfiguracja kontrolera i zgodność wsteczna

Typowym scenariuszem jest używanie w interfejsie aplikacji MediaController do sterowania odtwarzania i wyświetlania playlisty. Jednocześnie sesja jest udostępniana do klientów zewnętrznych, takich jak elementy sterujące multimediami na Androidzie i Asystenta na urządzeniach mobilnych lub telewizorach, Wear OS na zegarkach i Androida Auto w samochodach. Aplikacja demonstracyjna sesji Media3 znajdziesz przykład aplikacji, w której istnieje taki scenariusz.

Ci zewnętrzni klienci mogą używać interfejsów API takich jak MediaControllerCompat starszej wersji biblioteka AndroidaX lub android.media.session.MediaController Androida. platformy. Media3 jest w pełni zgodne wstecznie ze starszą biblioteką i zapewnia interoperacyjność z interfejsem Android Framework API.

Użyj kontrolera powiadomień o multimediach

Warto pamiętać, że starsze wersje kontrolerów lub kontrolerów platformy odczytują tych samych wartości z platformy PlaybackState.getActions() i PlaybackState.getCustomActions() Aby określić działania i działania niestandardowe w przypadku sesja platformy, aplikacja może używać kontrolera powiadomień o multimediach i ustaw dostępne polecenia oraz układ niestandardowy. Usługa łączy multimedia do administratora powiadomień, a sesja korzysta z Wartość ConnectionResult została zwrócona przez adres onConnect() wywołania zwrotnego do skonfigurowania niestandardowych działań i niestandardowych działań w ramach sesji platformy.

Biorąc pod uwagę sytuację tylko na urządzeniach mobilnych, aplikacja może zapewnić implementację MediaSession.Callback.onConnect(), aby ustawić dostępne polecenia, niestandardowy układ dla sesji platformy w następujący 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();
}

Autoryzowanie Androida Auto do wysyłania poleceń niestandardowych

W przypadku korzystania z MediaLibraryService oraz obsługiwać Androida Auto za pomocą aplikacji mobilnej – wymaga odpowiednich dostępnych poleceń. W przeciwnym razie element Media3 uniemożliwi 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 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 ma moduł motoryzacyjny, które potwierdza obsługę systemu operacyjnego Automotive, który wymaga osobnego pliku APK.