MediaSession kullanarak oynatmayı kontrol etme ve reklam yayınlama

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

Komutlar, mikrofonlu kulaklıktaki oynat düğmesi veya TV uzaktan kumandası gibi fiziksel düğmelerden kaynaklanabilir. Bunlar, medya denetleyicisi olan istemci uygulamalarından da gelebilir (örneğin, Google Asistan'a "duraklatma" talimatı vermek). Medya oturumu, bu komutları medya uygulamasının oynatıcısına yetkilendirir.

Medya oturumu ne zaman seçilir?

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

  • Kulaklık üzerinden. Kullanıcıların medya içeriklerini oynatmak veya duraklatmak ya da sonraki veya önceki parçaya gitmek için kulaklıklarında gerçekleştirebilecekleri düğmeler veya dokunma etkileşimleri genellikle bulunur.
  • Google Asistan'la konuşarak. Cihazda oynatılan bir medyayı duraklatmak için sık kullanılan bir kalıp "Ok Google, duraklat" demektir.
  • Wear OS kol saatiyle. Bu şekilde, telefonlarında oyun oynarken en yaygın oynatma kontrollerine daha kolay erişebilirler.
  • Medya kontrolleri aracılığıyla. Bu bant, yayınlanan her medya oturumuna ait kontrolleri gösterir.
  • TV'de. Fiziksel oynatma düğmeleri, platform oynatma kontrolü ve güç yönetimi ile yapılan işlemlere izin verir (örneğin TV, ses çubuğu veya A/V alıcısı kapanırsa ya da giriş değiştirilirse oynatma, uygulamada durmalıdır).
  • Oynatmayı etkilemesi gereken diğer tüm harici süreçler.

Bu, birçok kullanım alanı için harika bir seçenektir. Özellikle, şu durumlarda MediaSession kullanmanızı kesinlikle öneririz:

  • Film veya canlı TV gibi uzun biçimli video içeriği yayınlıyorsanız.
  • Podcast veya müzik şarkı listeleri gibi uzun biçimli ses içeriklerini canlı yayınlıyorsanız.
  • Bir TV uygulaması oluşturuyorsunuz.

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

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

Kullanım alanınız yukarıda listelenenlerden hiçbirine uymuyorsa kullanıcı içerikle aktif olarak etkileşimde bulunmadığında uygulamanızın oynatmaya devam etmesinin uygun olup olmadığını göz önünde bulundurun. Yanıtınız evetse muhtemelen MediaSession seçeneğini kullanmak istersiniz. Cevabınız hayırsa muhtemelen bunun yerine Player işlevini kullanmak istersiniz.

Medya oturumu oluşturma

Medya oturumu, yönettiği oynatıcıyla birlikte sunulur. Bir Context ve Player nesnesiyle medya oturumu oluşturabilirsiniz. Activity veya Fragment için onStart() veya onResume() yaşam döngüsü yöntemi ya da medya oturumunun ve ilişkili oynatıcısının sahibi olan Service onCreate() yöntemi gibi gerektiğinde bir medya oturumu oluşturup başlatmanız gerekir.

Medya oturumu oluşturmak için bir Player başlatın ve bunu MediaSession.Builder işlevine ş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, oyuncudan oturuma eşleme işlemini manuel olarak gerçekleştirmeniz gerekmez.

Bu, eski yaklaşımdan bir kopmadır. Bu aşamada, oynatıcıdan bağımsız olarak bir PlaybackStateCompat oluşturup sürdürmek (ör. hataları belirtmek için) gereklidir.

Benzersiz oturum kimliği

Varsayılan olarak MediaSession.Builder, oturum kimliği olarak boş dize içeren bir oturum oluşturur. Uygulamalar yalnızca tek bir oturum örneği oluşturmayı amaçlarsa bu yeterli olur. Bu, en yaygın durumdur.

Bir uygulama aynı anda birden çok oturum örneğini yönetmek isterse her oturumun oturum kimliğinin benzersiz olduğundan emin olması gerekir. Oturum kimliği, MediaSession.Builder.setId(String id) ile oturum oluşturulurken ayarlanabilir.

Uygulamanızın IllegalStateException tarafından IllegalStateException: Session ID must be unique. ID= hata mesajıyla kilitlendiğini 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ı nedeniyle sızdırılmasını önlemek için bu tür durumlar tespit edilir ve istisnaya gidilerek bilgilendirilir.

Diğer istemcilere kontrol verme

Medya oturumu, oynatmayı kontrol etmek için çok önemlidir. Harici kaynaklardan gelen komutları, medyanızı oynatma işini yapan oynatıcıya yönlendirmenizi sağlar. Bu kaynaklar, mikrofonlu kulaklıktaki oynat düğmesi veya TV uzaktan kumandası gibi fiziksel düğmeler ya da Google Asistan'a "duraklatma" komutu vermek gibi dolaylı komutlar olabilir. Benzer şekilde, bildirim ve kilit ekranı kontrollerini kolaylaştırmak için Android sistemine veya oynatmayı saat yüzünden kontrol edebilmek için bir Wear OS saate erişim izni vermek isteyebilirsiniz. Harici istemciler, medya uygulamanıza oynatma komutları yayınlamak için bir medya denetleyicisi kullanabilir. Bunlar, medya oturumunuz tarafından alınır ve son olarak komutlar için medya oynatıcıya yetki verir.

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

Kumanda, medya oturumunuza bağlanmak üzereyken onConnect() yöntemi çağrılır. İsteği kabul etmek veya reddetmek için sağlanan ControllerInfodan yararlanabilirsiniz. Bağlantı isteğinin kabul edilmesine ilişkin bir örneği Kullanılabilir komutları bildirme bölümünde görebilirsiniz.

Kumanda bağlandıktan sonra oturuma oynatma komutları gönderebilir. Daha sonra oturum, bu komutları oyuncuya devreder. 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 isteklerini ele almanıza) olanak tanır. Bu geri çağırma işlevleri benzer şekilde bir ControllerInfo nesnesi içerir. Böylece, her isteğe nasıl yanıt vereceğinizi denetleyicilere göre değiştirebilirsiniz.

Oynatma listesini değiştirme

Bir medya oturumu, oynatıcısının oynatma listesini şarkı listeleri için ExoPlayer kılavuzunda açıklandığı gibi doğrudan değiştirebilir. COMMAND_SET_MEDIA_ITEM veya COMMAND_CHANGE_MEDIA_ITEMS mevcutsa kumandalar oynatma listesini de değiştirebilirler.

Oynatma listesine yeni öğeler eklerken oynatıcı genellikle bunları oynatılabilir hale getirmek için tanımlı bir URI'ye sahip MediaItem örneklerine ihtiyaç duyar. Varsayılan olarak, yeni eklenen öğeler URI tanımlanmışsa player.addMediaItem gibi oynatıcı yöntemlerine otomatik olarak yönlendirilir.

Oynatıcıya eklenen MediaItem örneklerini özelleştirmek isterseniz onAddMediaItems() değerini geçersiz kılabilirsiniz. Bu adım, tanımlı 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 biri veya daha fazlasını içerir:

  • MediaItem.id: Medyayı tanımlayan genel bir kimlik.
  • MediaItem.RequestMetadata.mediaUri: Özel bir şema kullanabilen ve oynatıcı tarafından doğrudan oynatılamayan bir istek URI'sı.
  • 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 listeleri için daha fazla özelleştirme seçeneği isterseniz oynatma listesindeki başlangıç öğesini ve konumu tanımlamanıza olanak tanıyan onSetMediaItems() özelliğini de geçersiz kılabilirsiniz. Örneğin, istenen tek bir öğeyi oynatma listesinin tamamını kapsayacak şekilde genişletebilir ve oynatıcıya ilk başta istenen öğenin dizininden başlaması talimatını verebilirsiniz. Bu özellikle birlikte örnek onSetMediaItems() uygulamasını oturum demo uygulamasında bulabilirsiniz.

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

Aşağıdaki bölümlerde, istemci uygulamalarına özel bir özel komut düğmesi düzeninin 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 kontrollerini göstermek 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ı ve özel komutları bildirme

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 kaydedebilmesini sağlayan düğmeler uygulamak isteyebilirsiniz. MediaController özel komutları gönderir ve MediaSession.Callback bunları alır.

MediaController medya oturumunuza bağlandığında bu komutun hangi özel oturum komutlarını kullanabileceğini tanımlayabilirsiniz. Bunu, MediaSession.Callback.onConnect() politikasını geçersiz kılarak yaparsınız. onConnect geri çağırma yönteminde bir MediaController öğesinden gelen 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();
  }
}

Bir MediaController öğesinden özel komut istekleri almak için Callback içinde 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 kaynaklanan belirli bir komuta karşı davranışını uyarlayabilirsiniz.

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

Özel bir komutu veya oynatıcınızla başka bir etkileşimi gerçekleştirdikten sonra, kumandanın kullanıcı arayüzünde gösterilen düzeni güncellemek isteyebilirsiniz. Tipik bir örnek, bu düğmeyle ilişkilendirilen işlemi tetikledikten sonra simgesini değiştiren bir açma/kapatma düğmesidir. Düzeni güncellemek için MediaSession.setCustomLayout öğelerini kullanabilirsiniz:

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ştirin

Player arayüzünde tanımlanan play() veya seekToNext() gibi bir komutu özelleştirmek için Player kodunuzu ForwardingPlayer içine alın.

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 belirleme

Player yöntemine yapılan bir çağrı MediaController kaynaklı olduğunda, kaynak kaynağını MediaSession.controllerForCurrentRequest ile tanımlayabilir ve geçerli istek için ControllerInfo 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 Bluetooth mikrofonlu kulaklıktaki oynat/duraklat düğmesi gibi diğer çevre birimi cihazlarında bulunan donanım düğmeleridir. Media3 oturuma geldiklerinde medya düğmesi etkinliklerini sizin için işler ve oturum oynatıcısında uygun Player yöntemini çağırır.

Bir uygulama, MediaSession.Callback.onMediaButtonEvent(Intent) 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.