MediaSessionService ile arka planda oynatma

Uygulama ön planda değilken medya oynatmak genellikle istenir. Örneğin, müzik çalar genellikle kullanıcı cihazını kilitlediğinde veya başka bir uygulamayı kullanırken müziği çalmaya devam eder. Media3 kitaplığı, arka planda oynatmayı desteklemenize olanak tanıyan bir dizi arayüz sağlar.

MediaSessionService kullanma

Arka planda oynatmayı etkinleştirmek için Player ve MediaSession öğelerini ayrı bir Hizmet içine eklemeniz gerekir. Bu şekilde, uygulamanız kapalıyken bile cihaz medya yayınlamaya devam edebilir görebilirsiniz.

MediaSessionService, medya oturumunun ayrı olarak çalıştırılmasına izin verir
  uygulama etkinliğinden
Şekil 1: MediaSessionService, medya oturumunun uygulamanın etkinliğinden ayrı olarak çalışmasına olanak tanır

Bir Hizmet içinde oyuncu barındırırken MediaSessionService kullanmanız gerekir. Bunun için MediaSessionService sınıfını genişleten bir sınıf oluşturun ve medya oturumunuzu bu sınıfın içinde oluşturun.

MediaSessionService kullanımı, Google gibi harici müşterilerin işini kolaylaştırır Keşfetmek için Asistan, sistem medya kontrolleri veya Wear OS gibi tamamlayıcı cihazlar hizmete bağlanabilir, hizmete bağlanabilir ve oynatmayı kontrol edebilir. Üstelik tüm bunları kullanıcı arayüzü etkinliği. Aslında, aynı anda aynı MediaSessionService'e bağlı birden fazla istemci uygulaması olabilir. Bu uygulamalardan her biri kendi MediaController'ına sahiptir.

Hizmet yaşam döngüsünü uygulayın

Hizmetinizle ilgili üç yaşam döngüsü yöntemini uygulamanız gerekir:

  • onCreate(), ilk kumanda bağlanmak üzereyken çağrılır ve ve başlatıldığına dair bir ipucudur. Player ve MediaSession oluşturmak için en iyi yerdir.
  • onTaskRemoved(Intent), kullanıcı uygulamayı son görevlerden bahsetmeye devam edelim. Oynatma devam ediyorsa uygulama, hizmeti ön planda çalıştırmayı seçebilir. Oynatıcı duraklatılmışsa hizmet ön planda değildir ve durdurulması gerekir.
  • Hizmet durdurulurken onDestroy() çağrılır. Tüm kaynaklar ve oturumun serbest bırakılması gerekir.

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

Uygulama, arka planda oynatmayı devam ettirmenin alternatifi olarak Kullanıcı uygulamayı kapattığında her durumda hizmeti durdurma:

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

Medya oturumuna erişim izni ver

Diğer istemcilerin, hizmet oluşturulduğunda oluşturulan medya oturumunuza erişebilmesi için onGetSession() yöntemini geçersiz kılın.

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

Manifest dosyasında hizmeti beyan edin

Uygulamanın ön plan hizmeti çalıştırması için izin gerekir. URL'yi manifest dosyası için FOREGROUND_SERVICE izninizi kontrol edin ve API 34 ile yukarıdaki de FOREGROUND_SERVICE_MEDIA_PLAYBACK üzerinde:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

Ayrıca, Service sınıfınızı manifest dosyasında MediaSessionService intent filtresiyle belirtmeniz gerekir.

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

Hedef kitlenizin kim olduğunu foregroundServiceType Uygulamanız Android yüklü bir cihazda çalışırken mediaPlayback dahil 10 (API düzeyi 29) ve sonraki sürümler.

MediaController kullanarak oynatmayı kontrol etme

Oynatıcı kullanıcı arayüzünüzü içeren etkinlikte veya fragmanda, MediaController kullanarak kullanıcı arayüzü ile medya oturumunuz arasında bir bağlantı oluşturabilirsiniz. Kullanıcı arayüzünüz komutlarını kullanıcı arayüzünden oynatıcıya kabul edilir. MediaController oluşturma ve kullanmayla ilgili ayrıntılar için MediaController oluşturma kılavuzuna bakın.

Kullanıcı arayüzü komutlarını işleme

MediaSession, MediaSession.Callback üzerinden kontrolörden komut alır. Bir MediaSession'ü başlattığınızda, MediaController'nin oynatıcınıza gönderdiği tüm komutları otomatik olarak işleyen varsayılan bir MediaSession.Callback uygulaması oluşturulur.

Bildirim

MediaSessionService, çoğu durumda işe yarayacak bir MediaNotification'ı sizin için otomatik olarak oluşturur. Varsayılan olarak, yayınlanan bildirim MediaStyle bildirim en son bilgilerle güncellenen bir oynatma kontrollerini görüntüler. MediaNotification oturumunuzun farkındadır ve diğer uygulamaların oynatmayı kontrol etmek için kullanılabilir her şeyi kapsıyor.

Örneğin, MediaSessionService kullanan bir müzik yayın uygulaması Öğeye ait başlığı, sanatçıyı ve albüm resmini gösteren MediaNotification bağlı olarak, oynatma kontrolleriyle birlikte oynatılan mevcut medya öğesi MediaSession yapılandırması.

Gerekli meta veriler medyada sağlanabilir veya medya öğesini seçin:

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

Uygulamalar, Android medya kontrollerinin komut düğmelerini özelleştirebilir. Devamını okuyun özelleştirme konusunda kontrol eder.

Bildirim özelleştirme

Bildirimi özelleştirmek için DefaultMediaNotificationProvider.Builder ile veya sağlayıcı arayüzünün özel bir uygulamasını oluşturarak bir MediaNotification.Provider oluşturun. MediaSessionService ile kullanmak üzere setMediaNotificationProvider

Oynatmayı devam ettirme

Medya düğmeleri, Android cihazlarda ve diğer çevre birimlerinde bulunan donanım düğmeleridir. cihazları (örneğin, Bluetooth mikrofonlu kulaklıktaki oynat veya duraklat düğmesi) kullanın. Medya3 hizmet çalışırken medya düğmesi girişlerini sizin için işler.

Media3 medya düğmesi alıcısını tanımlama

Media3, kullanıcıların bir uygulama sona erdikten sonra ve hatta cihaz yeniden başlatıldıktan sonra oynatmayı devam ettirmelerini sağlayan bir API içerir. Varsayılan olarak, oynatmayı devam ettirme kapalıdır. Bu, kullanıcının hizmetiniz çalışmadığında oynatmaya devam edilemez. Özelliği etkinleştirmek için manifest dosyanızda MediaButtonReceiver öğesini tanımlayarak başlayın:

<receiver android:name="androidx.media3.session.MediaButtonReceiver"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.MEDIA_BUTTON" />
  </intent-filter>
</receiver>

Oynatmayı devam ettirme geri çağırma işlevini uygulama

Bluetooth cihazı veya Android sistem kullanıcı arayüzünün devam ettirme özelliği, onPlaybackResumption() çağrılır.

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

Oynatma hızı, tekrar modu veya karma mod gibi başka parametreler depoladıysanız Media3 oynatıcıyı hazırlamadan ve geri çağırma tamamlandığında oynatmayı başlatmadan önce oynatıcıyı bu parametrelerle yapılandırmak için onPlaybackResumption() iyi bir yerdir.

Gelişmiş denetleyici yapılandırması ve geriye dönük uyumluluk

Çalmayı kontrol etmek ve oynatma listesini görüntülemek için uygulama kullanıcı arayüzünde MediaController kullanılması yaygın bir durumdur. Aynı zamanda oturum, Android medya kontrolleri ve mobil cihazlarda veya TV'de Asistan, kol saatleri için Wear OS ve arabalarda Android Auto gibi harici istemcilere gösterilir. Media3 oturum demo uygulaması, bu tür bir senaryoyu uygulayan bir uygulama örneğidir.

Bu harici istemciler, eski AndroidX kitaplığının MediaControllerCompat veya Android çerçevesinin android.media.session.MediaController gibi API'leri kullanabilir. Media3 eski kitaplıkla tamamen geriye dönük uyumludur ve Android Framework API ile birlikte çalışabilirlik özelliği sunar.

Medya bildirim denetleyicisini kullanma

Bu eski veya çerçeve denetleyicilerinin, PlaybackState.getActions() ve PlaybackState.getCustomActions() çerçevesinden aynı değerleri okuduğunu anlamanız önemlidir. Bir uygulama, çerçeve oturumunun işlemlerini ve özel işlemlerini belirlemek için medya bildirimi denetleyicisini kullanabilir ve mevcut komutlarını ve özel düzenini ayarlayabilir. Hizmet, medya bildirimi denetleyicisini oturumunuza bağlar ve oturum, çerçeve oturumunun işlemlerini ve özel işlemlerini yapılandırmak için geri çağırma işlevinizin onConnect() tarafından döndürülen ConnectionResult değerini kullanır.

Yalnızca mobil kullanıma yönelik bir senaryo göz önünde bulundurulduğunda bir uygulama, Mevcut komutları ayarlamak için MediaSession.Callback.onConnect() çerçeve oturumu için özel olarak aşağıdaki gibi özel düzen:

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

Android Auto'yu özel komutlar göndermesi için yetkilendirin

MediaLibraryService kullanırken ve Android Auto'yu mobil uygulamayla desteklemek için Android Auto denetleyicisinin uygun komutlara sahip olması gerekir. Aksi takdirde Media3, bu denetleyiciden gelen özel komutları reddeder:

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

Oturum demo uygulamasında, ayrı bir APK gerektiren Automotive OS desteğini gösteren bir otomotiv modülü bulunur.