MediaSession kullanarak oynatmayı kontrol etme ve reklam yayınlama

Medya oturumları, bir ses veya video oynatıcıyla etkileşim kurmak için evrensel bir yol sağlar. Media3'te varsayılan oynatıcı, Player arayüzünü uygulayan ExoPlayer sınıfıdır. Medya oturumunu oynatıcıya bağlamak, uygulamanın harici olarak medya oynatma reklamı yapmasına ve harici kaynaklardan oynatma komutları almasına olanak tanır.

Komutlar, mikrofonlu kulaklıktaki oynat düğmesi veya TV uzaktan kumandası gibi fiziksel düğmelerden gelebilir. Google Asistan'a "duraklatma" talimatı vermek gibi bir medya denetleyicisi olan istemci uygulamalarından da gelebilirler. Medya oturumu, bu komutları medya uygulamasının oynatıcısına aktarır.

Medya oturumu ne zaman seçilmelidir?

MediaSession öğesini uyguladığınızda, kullanıcıların oynatmayı kontrol etmesine izin vermiş olursunuz:

  • Kulaklıklarını kullanarak. Genellikle kullanıcılar medya içeriğini oynatmak veya duraklatmak ya da sonraki veya önceki parçaya gitmek için kulaklıklarında düğmeler veya dokunma etkileşimleri gerçekleştirir.
  • Google Asistan'la konuşarak. Cihazda oynatılan medyaları duraklatmak için "Ok Google, paused" (Ok Google, duraklat) gibi yaygın bir kalıp kullanılır.
  • Wear OS kol saati üzerinden. Bu şekilde çocuğunuzun telefonunda oynarken en yaygın oynatma kontrollerine daha kolay erişebilirsiniz.
  • Medya kontrolleri aracılığıyla. Bu bant, çalışan her medya oturumuna ait kontrolleri gösterir.
  • TV'de. Fiziksel oynatma düğmeleri, platform oynatma kontrolü ve güç yönetimiyle işlemlere izin verir (örneğin TV, ses çubuğu veya A/V alıcısı kapanırsa veya giriş değiştirilirse, uygulamada oynatma durmalıdır).
  • Oynatmayı etkilemesi gereken diğer harici işlemler.

Bu, birçok kullanım alanı için idealdir. Özellikle şu durumlarda MediaSession kullanmayı düşünmeniz gerekir:

  • Filmler veya canlı TV gibi uzun video içeriklerini canlı oynatıyorsanız.
  • Podcast veya müzik oynatma listeleri gibi uzun ses içeriklerini canlı oynatıyorsanız.
  • TV uygulaması oluşturuyorsunuz.

Ancak tüm kullanım alanları MediaSession ile uyumlu değildir. Aşağıdaki durumlarda yalnızca Player özelliğini kullanmak isteyebilirsiniz:

  • Kullanıcı etkileşimi ve etkileşiminin büyük önem taşıdığı kısa içerikler gösteriyorsunuz.
  • Tek bir etkin video yoktur. Örneğin, kullanıcı bir listeyi kaydırıyor ve ekranda aynı anda birden fazla video gösteriliyor.
  • Kullanıcınızın aktif olarak izlemesini beklediğiniz bir tek seferlik tanıtım veya açıklama videosunu oynatıyorsunuz.
  • İçeriğiniz gizlilik açısından hassas ve harici işlemlerin medya meta verilerine erişmesini istemiyorsunuz (ör. bir tarayıcıda gizli mod)

Kullanım alanınız yukarıda listelenenlerin hiçbirine uymuyorsa kullanıcı içerikle aktif olarak etkileşimde bulunmadığında uygulamanızın oynatmaya devam etmesini isteyip istemediğinizi düşünün. Yanıtınız evetse muhtemelen MediaSession ürününü seçmek istersiniz. Yanıtınız hayırsa bunun yerine Player öğesini kullanmak isteyebilirsiniz.

Medya oturumu oluşturma

Yönettiği oynatıcının yanında bir medya oturumu gerçekleşir. Bir Context ve Player nesnesiyle medya oturumu oluşturabilirsiniz. Gerektiğinde bir medya oturumu oluşturmanız ve başlatmanız gerekir. Örneğin, Activity veya Fragment'ın onStart() ya da onResume() yaşam döngüsü yöntemi veya medya oturumuna ve ilişkili oynatıcısına sahip olan Service'nin onCreate() yöntemi.

Medya oturumu oluşturmak için bir Player başlatın ve MediaSession.Builder öğesine şu şekilde sağlayın:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

Otomatik durum işleme

Media3 kitaplığı, oynatıcının durumunu kullanarak medya oturumunu otomatik olarak günceller. Bu nedenle, oynatıcıdan oturuma eşleme işlemini manuel olarak yapmanız gerekmez.

Bu, oynatıcının kendisinden bağımsız bir şekilde bir PlaybackStateCompat oluşturup sürdürmeniz (örneğin, hataları belirtmek için) gereken eski yaklaşımdan bir ara oluşturur.

Benzersiz oturum kimliği

Varsayılan olarak MediaSession.Builder, oturum kimliği olarak boş bir dizeyle oturum oluşturur. Bir uygulama yalnızca tek bir oturum örneği oluşturmayı planlıyorsa bu yeterlidir. Bu, en sık karşılaşılan durumdur.

Bir uygulama aynı anda birden fazla oturum örneğini yönetmek istiyorsa uygulama her oturumun oturum kimliğinin benzersiz olduğundan emin olmalıdır. MediaSession.Builder.setId(String id) ile oturum oluştururken oturum kimliği ayarlanabilir.

IllegalStateException: Session ID must be unique. ID= hata mesajıyla uygulamanızı kilitleyen bir IllegalStateException görürseniz aynı kimliğe sahip daha önce oluşturulmuş bir örnek yayınlanmadan önce beklenmedik bir şekilde bir oturum oluşturulmuş olabilir. Oturumların bir programlama hatası tarafından sızdırılmasını önlemek için bu tür durumlar tespit edilir ve bir istisna uygulanarak bilgilendirilir.

Diğer istemcilere denetim izni ver

Medya oturumu, oynatmayı kontrol etmenin en önemli unsurudur. Harici kaynaklardan gelen komutları, medyanızı oynatma işlemini yapan oynatıcıya yönlendirmenizi sağlar. Bu kaynaklar; mikrofonlu kulaklıktaki veya TV uzaktan kumandasındaki oynat düğmesi gibi fiziksel düğmeler ya da Google Asistan'a "duraklat" talimatı gibi dolaylı komutlar olabilir. Benzer şekilde, bildirim ve kilit ekranı kontrollerini kolaylaştırmak için Android sistemine veya oynatmayı kadrandan kontrol edebilmek için Wear OS kol saatine erişim izni verebilirsiniz. Harici istemciler, medya uygulamanıza oynatma komutları yayınlamak için bir medya denetleyici kullanabilir. Bunlar medya oturumunuz tarafından alınır, ardından komutlar medya oynatıcıya yetki verir.

MediaSession ve MediaController arasındaki etkileşimi gösteren bir diyagram.
Şekil 1: Medya denetleyicisi, harici kaynaklardan komutların medya oturumuna iletilmesini kolaylaştırır.

Bir denetleyici, medya oturumunuza bağlanmak üzereyken onConnect() yöntemi çağrılır. İsteği kabul etmeye veya reddetmeye karar vermek için sağlanan ControllerInfo öğesini kullanabilirsiniz. Bağlantı isteğini kabul etmeyle ilgili bir örneği Kullanılabilir komutları bildirme bölümünde görebilirsiniz.

Bağlantı kurulduktan sonra kumanda, oturuma oynatma komutları gönderebilir. Ardından oturum, bu komutları oyuncuya delege eder. Player arayüzünde tanımlanan oynatma ve oynatma listesi komutları, oturum tarafından otomatik olarak işlenir.

Diğer geri çağırma yöntemleri, örneğin özel oynatma komutları ve oynatma listesini değiştirme gibi istekleri yönetmenize olanak tanır. Bu geri çağırma işlevleri benzer şekilde ControllerInfo nesnesi içerir. Böylece her isteğe nasıl yanıt vereceğinizi denetleyici bazında değiştirebilirsiniz.

Oynatma listesini değiştirme

Bir medya oturumu, oynatma listeleri için ExoPlayer kılavuzunda açıklandığı gibi, oynatıcısının oynatma listesini doğrudan değiştirebilir. Ayrıca kumandada COMMAND_SET_MEDIA_ITEM veya COMMAND_CHANGE_MEDIA_ITEMS kullanılabiliyorsa oynatma listesi üzerinde değişiklik yapılabilir.

Oynatma listesine yeni öğeler eklerken oynatıcı genellikle bunların oynatılabilmesi için tanımlanmış URI ile MediaItem örneklerine ihtiyaç duyar. Varsayılan olarak, yeni eklenen öğeler tanımlı bir URI'ya sahipse player.addMediaItem gibi oynatıcı yöntemlerine otomatik olarak yönlendirilir.

Oynatıcıya eklenen MediaItem örneklerini özelleştirmek isterseniz onAddMediaItems() örneğini geçersiz kılabilirsiniz. Bu adım, tanımlanmış bir URI olmadan medya isteğinde bulunan denetleyicileri desteklemek istediğinizde gereklidir. Bunun yerine, MediaItem genellikle istenen medyayı açıklamak için aşağıdaki alanlardan birini veya daha fazlasını içerir:

  • MediaItem.id: Medyayı tanımlayan genel bir kimliktir.
  • MediaItem.RequestMetadata.mediaUri: Özel bir şema kullanabilecek ve oynatıcı tarafından doğrudan oynatılabilmesi gerekmeyen bir istek URI'si.
  • MediaItem.RequestMetadata.searchQuery: Metin biçiminde bir arama sorgusu (örneğin, Google Asistan'dan).
  • MediaItem.MediaMetadata: "Başlık" veya "sanatçı" gibi yapılandırılmış meta veriler.

Tamamen yeni oynatma listeleriyle ilgili daha fazla özelleştirme seçeneği için başlangıç öğesini ve oynatma listesindeki konumu tanımlamanıza olanak tanıyan onSetMediaItems() değerini de geçersiz kılabilirsiniz. Örneğin, istenen tek bir öğeyi oynatma listesinin tamamını genişletebilir ve oynatıcıya, başlangıçta istenen öğenin dizininden başlamasını söyleyebilirsiniz. Bu özellik ile onSetMediaItems() örnek uygulamasını oturum demo uygulamasında bulabilirsiniz.

Özel düzeni ve özel komutları yönetin

Aşağıdaki bölümlerde, özel komut düğmeleri düzeninin istemci uygulamalarına nasıl tanıtılacağı ve özel komutları göndermek için denetleyicilere nasıl yetki verileceği açıklanmaktadır.

Oturumun özel düzenini tanımlayın

İstemci uygulamalarına hangi oynatma kontrollerinin kullanıcıya gösterilmesini istediğinizi belirtmek için hizmetinizin onCreate() yönteminde MediaSession oluştururken oturumun özel düzenini ayarlayın.

Kotlin

override fun onCreate() {
  super.onCreate()

  val likeButton = CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build()
  val favoriteButton = CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle()))
    .build()

  session =
    MediaSession.Builder(this, player)
      .setCallback(CustomMediaSessionCallback())
      .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
      .build()
}

Java

@Override
public void onCreate() {
  super.onCreate();

  CommandButton likeButton = new CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build();
  CommandButton favoriteButton = new CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
    .build();

  Player player = new ExoPlayer.Builder(this).build();
  mediaSession =
      new MediaSession.Builder(this, player)
          .setCallback(new CustomMediaSessionCallback())
          .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
          .build();
}

Kullanılabilir oynatıcıyı ve özel komutları bildir

Medya uygulamaları, örneğin özel bir düzende kullanılabilecek özel komutlar tanımlayabilir. Örneğin, kullanıcının bir medya öğesini favori öğeler listesine kaydetmesine izin veren düğmeler uygulamak isteyebilirsiniz. MediaController, özel komutlar gönderir, MediaSession.Callback ise bunları alır.

Medya oturumunuza bağlandığında MediaController tarafından hangi özel oturum komutlarının kullanılabileceğini tanımlayabilirsiniz. Bunu, MediaSession.Callback.onConnect() değerini geçersiz kılarak elde edersiniz. onConnect geri çağırma yönteminde, MediaController özelliğinden gelen bir bağlantı isteğini kabul ederken kullanılabilir komut grubunu yapılandırın ve döndürün:

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): MediaSession.ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

MediaController öğesinden özel komut istekleri almak için Callback içindeki onCustomCommand() yöntemini geçersiz kılın.

Kotlin

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(
        SessionResult(SessionResult.RESULT_SUCCESS)
      )
    }
    ...
  }
}

Java

class CustomMediaSessionCallback implements MediaSession.Callback {
  ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session, 
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args
  ) {
    if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(
        new SessionResult(SessionResult.RESULT_SUCCESS)
      );
    }
    ...
  }
}

Callback yöntemlerine iletilen MediaSession.ControllerInfo nesnesinin packageName özelliğini kullanarak hangi medya denetleyicisinin istekte bulunduğunu izleyebilirsiniz. Bu sayede uygulamanızın sistemden, kendi uygulamanızdan veya başka istemci uygulamalarından geliyorsa davranışını belirli bir komuta göre özelleştirebilirsiniz.

Kullanıcı etkileşiminden sonra özel düzeni güncelleme

Özel bir komut verdikten veya oynatıcınızla başka herhangi bir etkileşimde bulunduktan sonra, kumanda arayüzünde gösterilen düzeni güncellemek isteyebilirsiniz. Tipik bir örnek, bu düğmeyle ilişkili işlemi tetikledikten sonra simgesini değiştiren bir açma/kapatma düğmesidir. Düzeni güncellemek için MediaSession.setCustomLayout:

Kotlin

val removeFromFavoritesButton = CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle()))
  .build()
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))

Java

CommandButton removeFromFavoritesButton = new CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle()))
  .build();
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));

Oynatma komutu davranışını özelleştirme

Player arayüzünde play() veya seekToNext() gibi tanımlı bir komutun davranışını özelleştirmek için Player öğenizi ForwardingPlayer içine yerleştirin.

Kotlin

val player = ExoPlayer.Builder(context).build()

val forwardingPlayer = object : ForwardingPlayer(player) {
  override fun play() {
    // Add custom logic
    super.play()
  }

  override fun setPlayWhenReady(playWhenReady: Boolean) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady)
  }
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
  @Override
  public void play() {
    // Add custom logic
    super.play();
  }

  @Override
  public void setPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady);
  }
};

MediaSession mediaSession = 
  new MediaSession.Builder(context, forwardingPlayer).build();

ForwardingPlayer hakkında daha fazla bilgi için Özelleştirme ile ilgili ExoPlayer kılavuzuna bakın.

Oynatıcı komutunun istekte bulunan denetleyicisini tanımlayın

Player yöntemi için yapılan çağrı bir MediaController tarafından gerçekleştirildiğinde, kaynak kaynağını MediaSession.controllerForCurrentRequest ile belirleyebilir ve mevcut istek için ControllerInfo değerini edinebilirsiniz:

Kotlin

class CallerAwareForwardingPlayer(player: Player) :
  ForwardingPlayer(player) {

  override fun seekToNext() {
    Log.d(
      "caller",
      "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}"
    )
    super.seekToNext()
  }
}

Java

public class CallerAwareForwardingPlayer extends ForwardingPlayer {
  public CallerAwareForwardingPlayer(Player player) {
    super(player);
  }

  @Override
  public void seekToNext() {
    Log.d(
        "caller",
        "seekToNext called from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    super.seekToNext();
  }
}

Medya düğmelerine yanıt verme

Medya düğmeleri, Android cihazlarda ve diğer çevre birimi cihazlarında bulunan (Bluetooth mikrofonlu kulaklıktaki oynat/duraklat düğmesi gibi) donanım düğmeleridir. Media3, oturuma geldiğinde sizin için medya düğmesi etkinliklerini işler ve oturum oynatıcısında uygun Player yöntemini çağırır.

Bir uygulama, MediaSession.Callback.onMediaButtonEvent(Intent) değerini geçersiz kılarak varsayılan davranışı geçersiz kılabilir. Böyle bir durumda, uygulamanın tüm API özelliklerini kendi başına işlemesi gerekir/gerekir.