MediaSessionService ile arka planda oynatma

Uygulama ön planda değilken medya oynatılması genellikle istenir. Örneğin, bir müzik çalar genellikle kullanıcı cihazını kilitlediğinde veya başka bir uygulamayı kullanırken müzik ç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çinde bulundurmalısınız. Bu şekilde cihaz, uygulamanız ön planda değilken bile medya yayınlamaya devam edebilir.

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

Hizmet içinde bir oyuncu barındırırken MediaSessionService kullanmalısınız. Bunu yapmak için MediaSessionService` öğesini genişleten bir sınıf oluşturun ve içinde medya oturumunuzu oluşturun.

MediaSessionService kullanıldığında Google Asistan, sistem medya denetimleri veya Wear OS gibi tamamlayıcı cihazlar, uygulamanızın kullanıcı arayüzü etkinliğine hiçbir şekilde erişmeden hizmetinizi keşfetmeyi, hizmete bağlanmayı ve oynatmayı kontrol etmeyi sağlayan harici istemciler tarafından kullanılabilir. Aslında aynı anda aynı MediaSessionService cihazına bağlı birden fazla istemci uygulaması olabilir ve her uygulamanın kendi MediaController sahibi olabilir.

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

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

  • onCreate(), ilk denetleyici bağlanmak üzereyken ve hizmet başlatılıp başlatıldığında çağrılır. Burası Player ve MediaSession derlemek için en iyi yerdir.
  • Kullanıcı, uygulamayı son görevlerden çıkardığında onTaskRemoved(Intent) çağrılır. Oynatma devam ediyorsa uygulama, hizmeti ön planda çalıştırmayı seçebilir. Oynatıcı duraklatılırsa hizmet ön planda değildir ve durdurulması gerekir.
  • Hizmet durdurulduğunda onDestroy() çağrılır. Oynatıcı ve oturum dahil tüm kaynakların 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();
  }
}

Oynatmanın arka planda devam etmesini sağlamak yerine, kullanıcı uygulamayı kapattığında bir uygulama her durumda hizmeti durdurabilir:

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 verin

Hizmet oluşturulurken oluşturulan medya oturumunuza diğer istemcilere erişim izni vermek 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 tanımlama

Bir uygulamanın ön plan hizmetini çalıştırmak için izne ihtiyacı var. FOREGROUND_SERVICE iznini manifest dosyasına ekleyin. API 34 ve üstünü hedefliyorsanız de FOREGROUND_SERVICE_MEDIA_PLAYBACK:

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

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

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

Uygulamanız Android 10 (API düzeyi 29) ve sonraki sürümleri çalıştıran bir cihazda çalışırken mediaPlayback içeren bir foregroundServiceType tanımlamanız gerekir.

MediaController kullanarak oynatmayı kontrol etme

Oynatıcı kullanıcı arayüzünüzü içeren Etkinlik veya Bölüm'de, bir MediaController kullanarak kullanıcı arayüzü ve medya oturumunuz arasında bir bağlantı oluşturabilirsiniz. Kullanıcı arayüzünüz, kullanıcı arayüzünden oturum içindeki oynatıcıya komut göndermek için medya denetleyiciyi kullanır. MediaController oluşturma ve kullanma hakkında ayrıntılı bilgi için MediaController oluşturma kılavuzuna bakın.

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

MediaSession, MediaSession.Callback üzerinden kumandadan komut alır. Bir MediaSession başlatıldığında, MediaController tarafından oynatıcınıza gönderilen tüm komutları otomatik olarak işleyen varsayılan bir MediaSession.Callback uygulaması oluşturulur.

Bildirim

MediaSessionService, sizin için otomatik olarak çoğu durumda işe yarayacak bir MediaNotification oluşturur. Yayınlanan bildirim, varsayılan olarak medya oturumunuzdaki en son bilgilerle güncellenen ve oynatma kontrollerini gösteren MediaStyle bildirimidir. MediaNotification, oturumunuzun farkındadır ve aynı oturuma bağlı diğer uygulamalarda oynatmayı kontrol etmek için kullanılabilir.

Örneğin, MediaSessionService kullanan bir müzik yayın uygulaması, MediaSession yapılandırmanıza bağlı olarak oynatma kontrolleriyle birlikte oynatılan mevcut medya öğesinin başlığını, sanatçısını ve albüm resmini görüntüleyen bir MediaNotification oluşturur.

Gerekli meta veriler medyada sağlanabilir veya aşağıdaki snippet'te olduğu gibi medya öğesinin bir parçası olarak açıklanabilir:

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. Android Medya denetimlerini özelleştirme hakkında daha fazla bilgi edinin.

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. setMediaNotificationProvider ile sağlayıcınızı MediaSessionService cihazınıza ekleyin.

Oynatmayı devam ettirme

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

Media3 medya düğmesi alıcısını bildirme

Media3, kullanıcıların uygulama sonlandırıldıktan ve hatta cihaz yeniden başlatıldıktan sonra oynatmaya devam edebilmelerini sağlayan bir API içerir. Oynatmayı devam ettirme varsayılan olarak kapalıdır. Bu, hizmetiniz çalışmıyorken kullanıcının oynatmayı devam ettiremeyeceği anlamına gelir. Kaydolmak için manifest dosyanızdaki MediaButtonReceiver özelliğini 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>

Oynatma devam ettirme geri çağırmasını uygula

Bir Bluetooth cihaz veya Android Sistem Kullanıcı Arayüzü devam ettirme özelliği tarafından oynatma devam ettirme istendiğinde onPlaybackResumption() geri çağırma yöntemi ç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 karıştırma modu gibi başka parametreler depoladıysanız onPlaybackResumption(), Media3 oynatıcıyı hazırlamadan ve geri çağırma tamamlandığında oynatmaya başlamadan önce oynatıcıyı bu parametrelerle yapılandırmak için iyi bir yerdir.

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

Yaygın bir senaryo, oynatmayı kontrol etmek ve oynatma listesini görüntülemek için uygulama kullanıcı arayüzünde bir MediaController kullanmaktır. Aynı zamanda oturumda; Android medya kontrolleri ve mobil cihazlarda ya da TV'de Asistan, kol saatleri için Wear OS ve arabalarda Android Auto gibi harici istemciler de oturumda gösterilir. Media3 oturum demo uygulaması, böyle bir senaryoyu uygulayan uygulamalara örnek olarak verilebilir.

Bu harici istemciler, eski AndroidX kitaplığının MediaControllerCompat veya Android çerçevesinin android.media.session.MediaController gibi API'lerini kullanabilir. Media3, eski kitaplıkla tamamen geriye uyumludur ve Android Framework API ile birlikte çalışabilir.

Medya bildirim denetleyicisini kullanma

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

Yalnızca mobil cihazlara yönelik bir senaryoda uygulamalar, çerçeve oturumuna özel kullanılabilir komutları ve özel düzeni ayarlamak için aşağıdaki gibi MediaSession.Callback.onConnect() uygulaması sağlayabilir:

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 yetkilendirme

MediaLibraryService kullanılırken ve mobil uygulamayla Android Auto'yu desteklemek için Android Auto denetleyicisinin uygun komutları kullanması gerekir. Aksi takdirde Media3, bu kumandadan 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.