Medya tarayıcı hizmeti oluşturma

Uygulamanız, manifest dosyasında intent filtresiyle MediaBrowserService öğesini beyan etmelidir. Kendi hizmet adınızı seçebilirsiniz; aşağıdaki örnekte "MediaPlaybackService" uzantısıdır.

<service android:name=".MediaPlaybackService">
  <intent-filter>
    <action android:name="android.media.browse.MediaBrowserService" />
  </intent-filter>
</service>

Not: MediaBrowserService işlevinin önerilen uygulaması MediaBrowserServiceCompat. Bu, medya uyumluluğu destek kitaplığı üzerinden değiştirebilirsiniz. Bu sayfa genelinde "MediaTarayıcıHizmeti" terimi proje yöneticisinin / MediaBrowserServiceCompat.

Medya oturumunu başlat

Hizmet, onCreate() yaşam döngüsü geri çağırma yöntemini aldığında şu adımları uygulamalıdır:

Aşağıdaki onCreate() kodu, şu adımları gösterir:

Kotlin

private const val MY_MEDIA_ROOT_ID = "media_root_id"
private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"

class MediaPlaybackService : MediaBrowserServiceCompat() {

    private var mediaSession: MediaSessionCompat? = null
    private lateinit var stateBuilder: PlaybackStateCompat.Builder

    override fun onCreate() {
        super.onCreate()

        // Create a MediaSessionCompat
        mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply {

            // Enable callbacks from MediaButtons and TransportControls
            setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
                    or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
            )

            // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
            stateBuilder = PlaybackStateCompat.Builder()
                    .setActions(PlaybackStateCompat.ACTION_PLAY
                                    or PlaybackStateCompat.ACTION_PLAY_PAUSE
                    )
            setPlaybackState(stateBuilder.build())

            // MySessionCallback() has methods that handle callbacks from a media controller
            setCallback(MySessionCallback())

            // Set the session's token so that client activities can communicate with it.
            setSessionToken(sessionToken)
        }
    }
}

Java

public class MediaPlaybackService extends MediaBrowserServiceCompat {
    private static final String MY_MEDIA_ROOT_ID = "media_root_id";
    private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";

    private MediaSessionCompat mediaSession;
    private PlaybackStateCompat.Builder stateBuilder;

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

        // Create a MediaSessionCompat
        mediaSession = new MediaSessionCompat(context, LOG_TAG);

        // Enable callbacks from MediaButtons and TransportControls
        mediaSession.setFlags(
              MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
              MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

        // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
        stateBuilder = new PlaybackStateCompat.Builder()
                            .setActions(
                                PlaybackStateCompat.ACTION_PLAY |
                                PlaybackStateCompat.ACTION_PLAY_PAUSE);
        mediaSession.setPlaybackState(stateBuilder.build());

        // MySessionCallback() has methods that handle callbacks from a media controller
        mediaSession.setCallback(new MySessionCallback());

        // Set the session's token so that client activities can communicate with it.
        setSessionToken(mediaSession.getSessionToken());
    }
}

İstemci bağlantılarını yönetme

MediaBrowserService, istemci bağlantılarını yönetmek için kullanılan iki yönteme sahiptir: onGetRoot() kontrolleri ve erişimi olan kullanıcılar onLoadChildren() bir istemciye, MediaBrowserService öğesinin içerik hiyerarşisinin bir menüsünü oluşturma ve görüntüleme olanağı sağlar.

onGetRoot() ile istemci bağlantıları kontrol ediliyor

onGetRoot() yöntemi, içerik hiyerarşisinin kök düğümünü döndürür. Öğe yöntemi null değerini döndürürse bağlantı reddedilir.

Müşterilerin hizmetinize bağlanmasını ve hizmetin medya içeriğine göz atmasını sağlamak için onGetRoot(), boş olmayan bir BrowserRoot döndürmelidir. Bu, içerik hiyerarşinizi temsil eder.

İstemcilerin göz atmadan MediaSession hesabınıza bağlanmasına izin vermek için onGetRoot() onGetRoot() yine de boş olmayan bir BrowserRoot döndürmelidir, ancak kök kimliği bir olabilir.

Tipik bir onGetRoot() uygulaması aşağıdaki gibi görünebilir:

Kotlin

override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot {

    // (Optional) Control the level of access for the specified package name.
    // You'll need to write your own logic to do this.
    return if (allowBrowsing(clientPackageName, clientUid)) {
        // Returns a root ID that clients can use with onLoadChildren() to retrieve
        // the content hierarchy.
        MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)
    } else {
        // Clients can connect, but this BrowserRoot is an empty hierarchy
        // so onLoadChildren returns nothing. This disables the ability to browse for content.
        MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null)
    }
}

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {

    // (Optional) Control the level of access for the specified package name.
    // You'll need to write your own logic to do this.
    if (allowBrowsing(clientPackageName, clientUid)) {
        // Returns a root ID that clients can use with onLoadChildren() to retrieve
        // the content hierarchy.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    } else {
        // Clients can connect, but this BrowserRoot is an empty hierarchy
        // so onLoadChildren returns nothing. This disables the ability to browse for content.
        return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null);
    }
}

Bazı durumlarda, kiminle bağlantı kurabileceğini kontrol etmek MediaBrowserService cihazınıza. Bunun bir yolu, erişim kontrol listesi (EKL) kullanmaktır. Bu ayar hangi bağlantılara izin verildiğini belirtir ya da alternatif olarak hangi bağlantıların yasaklanacağını belirleyin. EKL uygulama örneği için izin veren başka bir bağlantı kullanıyorsanız Paket Doğrulayıcı Universal Android Music Player'daki sınıf örnek uygulama.

İçeriğe bağlı olarak, farklı içerik hiyerarşileri ne tür bir istemcinin sorgu yaptığını gösterir. Özellikle de Android Auto, Kullanıcıların ses uygulamalarıyla etkileşime geçmesi. Daha fazla bilgi için Şunlar için Ses Çalma: Otomatik. Siz İstemciyi belirlemek için bağlantı zamanında clientPackageName öğesine bakabilir türü ve istemciye (veya rootHints) bağlı olarak farklı bir BrowserRoot döndürür .

onLoadChildren() ile içerik paylaşılıyor

İstemci bağlandıktan sonra, kullanıcı arayüzünün yerel bir temsilini oluşturmak için MediaBrowserCompat.subscribe() öğesine yinelenen çağrılar yaparak içerik hiyerarşisini dolaşabilir. subscribe() yöntemi, onLoadChildren() geri çağırmasını hizmete gönderir ve bu işlem, MediaBrowser.MediaItem nesnelerinin bir listesini döndürür.

Her MediaItem, opak bir jeton olan benzersiz bir kimlik dizesine sahiptir. Müşteri bir alt menüyü açmak veya bir öğeyi oynatmak istediğinde kimliği iletir. Hizmetiniz, kimliği uygun menü düğümü veya içerik öğesiyle ilişkilendirmekten sorumludur.

Basit bir onLoadChildren() uygulaması aşağıdaki gibi görünebilir:

Kotlin

override fun onLoadChildren(
        parentMediaId: String,
        result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>
) {
    //  Browsing not allowed
    if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) {
        result.sendResult(null)
        return
    }

    // Assume for example that the music catalog is already loaded/cached.

    val mediaItems = emptyList<MediaBrowserCompat.MediaItem>()

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID == parentMediaId) {
        // Build the MediaItem objects for the top level,
        // and put them in the mediaItems list...
    } else {
        // Examine the passed parentMediaId to see which submenu we're at,
        // and put the children of that menu in the mediaItems list...
    }
    result.sendResult(mediaItems)
}

Java

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaItem>> result) {

    //  Browsing not allowed
    if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) {
        result.sendResult(null);
        return;
    }

    // Assume for example that the music catalog is already loaded/cached.

    List<MediaItem> mediaItems = new ArrayList<>();

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {
        // Build the MediaItem objects for the top level,
        // and put them in the mediaItems list...
    } else {
        // Examine the passed parentMediaId to see which submenu we're at,
        // and put the children of that menu in the mediaItems list...
    }
    result.sendResult(mediaItems);
}

Not: MediaTarayıcı Hizmeti tarafından yayınlanan MediaItem nesne simge bit eşlemleri içermemelidir. Telefon ederek bunun yerine bir Uri kullanın setIconUri() her öğe için MediaDescription oluşturduğunuzu varsayalım.

onLoadChildren() öğesinin nasıl uygulanacağına ilişkin bir örnek için Evrensel Android Müzik Çalar örnek uygulamasına bakın.

Medya tarayıcısı hizmet yaşam döngüsü

Bir Android hizmetinin davranışı, hizmetin bir veya daha fazla istemciye başlatılmasına ya da bağlı olmasına bağlıdır. Bir hizmet oluşturulduktan sonra başlatılabilir, bağlanabilir veya her ikisi birden yapılabilir. Bu durumların hepsinde, tamamen işlevseldir ve yapmak üzere tasarlandığı işleri yapabilir. Aradaki fark, hizmetin ne kadar süre boyunca açık olacağıdır. Bağlı bir hizmet, tüm bağlı istemcilerinin bağlantısı kaldırılana kadar kaldırılmaz. Başlatılan bir hizmet, açıkça durdurulabilir ve yok edilebilir (artık herhangi bir istemciye bağlı olmadığı varsayılır).

Başka bir etkinlikte çalışan MediaBrowser, MediaBrowserService öğesine bağlandığında etkinliği hizmete bağlar ve hizmeti bağlı hale getirir (ancak başlatılmaz). Bu varsayılan davranış MediaBrowserServiceCompat sınıfında yerleşiktir.

Bağlı olan (ve başlatılmamış) bir hizmet, tüm istemcilerinin bağlantısı kaldırıldığında yok edilir. Bu noktada kullanıcı arayüzü etkinliğiniz kesilirse hizmet kaldırılır. Henüz hiç müzik çalmadıysanız bu durum bir sorun teşkil etmez. Ancak oynatma başladığında kullanıcı muhtemelen uygulama değiştirseniz bile dinlemeye devam etmeyi bekler. Başka bir uygulamayla çalışmak için kullanıcı arayüzünün bağlantısını kaldırdığınızda oynatıcıyı kaldırmak istemezsiniz.

Bu nedenle, başlangıç sırasında hizmetin başlatıldığından emin olmanız gerekir startService() numaralı telefonu arayarak oynayın. CEVAP başlatılan hizmetin bağlı olup olmadığından bağımsız olarak açıkça durdurulması gerekir. Bu Kontrol eden kullanıcı arayüzü olsa bile oynatıcınızın çalışmaya devam etmesini sağlar. ve etkinliğin bağlantısı kaldırılır.

Başlatılan bir hizmeti durdurmak için Context.stopService() veya stopSelf() numaralı telefonu arayın. Sistem, hizmeti mümkün olan en kısa sürede durdurur ve kaldırır. Ancak hizmete hâlâ bir veya daha fazla istemci bağlı durumdaysa hizmeti durdurma çağrısı, tüm istemcilerinin bağlantısı kaldırılana kadar ertelenir.

MediaBrowserService öğesinin yaşam döngüsü; oluşturulma şekli, bağlı istemci sayısı ve medya oturumu geri çağırmalarından aldığı çağrılarla kontrol edilir. Özetlemek gerekirse:

  • Hizmet, bir medya düğmesine yanıt olarak başlatıldığında veya bir etkinlik buna bağlandığında (MediaBrowser üzerinden bağlandıktan sonra) oluşturulur.
  • Medya oturumu onPlay() geri çağırması, startService() çağrısı yapan kodu içermelidir. Bu işlem, hizmete bağlı tüm kullanıcı arayüzü MediaBrowser etkinliklerinin bağlantısı kaldırıldığında bile hizmetin başlamasını ve çalışmaya devam etmesini sağlar.
  • onStop() geri çağırması stopSelf() numarasını arayacaktır. Hizmet başlatılmışsa bu işlem durdurulur. Ayrıca, bağlı herhangi bir etkinlik yoksa hizmet kaldırılır. Aksi takdirde, tüm etkinlikleri kaldırılana kadar hizmet bağlı kalır. (Hizmet kaldırılmadan önce sonraki bir startService() çağrısı alınırsa beklemedeki durdurma iptal edilir.)

Aşağıdaki akış şemasında, bir hizmetin yaşam döngüsünün nasıl yönetildiği gösterilmektedir. Değişken sayacı, bağlı istemcilerin sayısını izler:

Hizmet Yaşam Döngüsü

MediaStyle bildirimlerini ön plan hizmetiyle kullanma

Oynatılan bir hizmet ön planda çalışıyor olmalıdır. Bu, sistemin, hizmetin yararlı bir işlev gerçekleştirdiğini ve sistemde bellek azsa kapatılmaması gerektiğini bilmesini sağlar. Ön plan hizmetlerinin, kullanıcının bundan haberdar olması ve isteğe bağlı olarak kontrol edebilmesi için bir bildirim görüntülemesi gerekir. onPlay() geri çağırması, hizmeti ön plana almalıdır. (Bunun "ön plan"ın özel bir anlamı olduğunu unutmayın. Android, süreç yönetimi için hizmeti ön planda sayarken kullanıcıya başka bir uygulama "ön planda" görünürken oynatıcı arka planda oynar. dokunun.)

Bir hizmet ön planda çalışırken ideal olarak bir veya daha fazla aktarım kontrolüne sahip bir bildirim göstermelidir. Bildirimde oturumun meta verilerinden de yararlı bilgiler bulunmalıdır.

Oynatıcı oynamaya başladığında bildirim oluşturun ve görüntüleyin. Bunu yapmak için en iyi yer MediaSessionCompat.Callback.onPlay() yönteminin içindedir.

Aşağıdaki örnekte NotificationCompat.MediaStyle, Google Analytics 4'te tarama yapar. Meta veri ve aktarım denetimlerini gösteren bir bildirimin nasıl oluşturulacağı gösterilmektedir. Kolaylık yöntemi getController(). doğrudan medya oturumunuzdan bir medya denetleyicisi oluşturmanızı sağlar.

Kotlin

// Given a media session and its context (usually the component containing the session)
// Create a NotificationCompat.Builder

// Get the session's metadata
val controller = mediaSession.controller
val mediaMetadata = controller.metadata
val description = mediaMetadata.description

val builder = NotificationCompat.Builder(context, channelId).apply {
    // Add the metadata for the currently playing track
    setContentTitle(description.title)
    setContentText(description.subtitle)
    setSubText(description.description)
    setLargeIcon(description.iconBitmap)

    // Enable launching the player by clicking the notification
    setContentIntent(controller.sessionActivity)

    // Stop the service when the notification is swiped away
    setDeleteIntent(
            MediaButtonReceiver.buildMediaButtonPendingIntent(
                    context,
                    PlaybackStateCompat.ACTION_STOP
            )
    )

    // Make the transport controls visible on the lockscreen
    setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

    // Add an app icon and set its accent color
    // Be careful about the color
    setSmallIcon(R.drawable.notification_icon)
    color = ContextCompat.getColor(context, R.color.primaryDark)

    // Add a pause button
    addAction(
            NotificationCompat.Action(
                    R.drawable.pause,
                    getString(R.string.pause),
                    MediaButtonReceiver.buildMediaButtonPendingIntent(
                            context,
                            PlaybackStateCompat.ACTION_PLAY_PAUSE
                    )
            )
    )

    // Take advantage of MediaStyle features
    setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle()
            .setMediaSession(mediaSession.sessionToken)
            .setShowActionsInCompactView(0)

            // Add a cancel button
            .setShowCancelButton(true)
            .setCancelButtonIntent(
                    MediaButtonReceiver.buildMediaButtonPendingIntent(
                            context,
                            PlaybackStateCompat.ACTION_STOP
                    )
            )
    )
}

// Display the notification and place the service in the foreground
startForeground(id, builder.build())

Java

// Given a media session and its context (usually the component containing the session)
// Create a NotificationCompat.Builder

// Get the session's metadata
MediaControllerCompat controller = mediaSession.getController();
MediaMetadataCompat mediaMetadata = controller.getMetadata();
MediaDescriptionCompat description = mediaMetadata.getDescription();

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);

builder
    // Add the metadata for the currently playing track
    .setContentTitle(description.getTitle())
    .setContentText(description.getSubtitle())
    .setSubText(description.getDescription())
    .setLargeIcon(description.getIconBitmap())

    // Enable launching the player by clicking the notification
    .setContentIntent(controller.getSessionActivity())

    // Stop the service when the notification is swiped away
    .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
       PlaybackStateCompat.ACTION_STOP))

    // Make the transport controls visible on the lockscreen
    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

    // Add an app icon and set its accent color
    // Be careful about the color
    .setSmallIcon(R.drawable.notification_icon)
    .setColor(ContextCompat.getColor(context, R.color.primaryDark))

    // Add a pause button
    .addAction(new NotificationCompat.Action(
        R.drawable.pause, getString(R.string.pause),
        MediaButtonReceiver.buildMediaButtonPendingIntent(context,
            PlaybackStateCompat.ACTION_PLAY_PAUSE)))

    // Take advantage of MediaStyle features
    .setStyle(new MediaStyle()
        .setMediaSession(mediaSession.getSessionToken())
        .setShowActionsInCompactView(0)

        // Add a cancel button
       .setShowCancelButton(true)
       .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
           PlaybackStateCompat.ACTION_STOP)));

// Display the notification and place the service in the foreground
startForeground(id, builder.build());

MediaStyle bildirimlerini kullanırken bu NotificationCompat ayarları:

  • setContentIntent() hizmetini kullanırken, bildirim aldığınızda hizmetiniz otomatik olarak başlatılır. bu pratik bir özelliktir.
  • "Güvenilmeyen" bir durum kilit ekranında olduğu gibi, bildirim içeriklerinin varsayılan görünürlüğü VISIBILITY_PRIVATE şeklindedir. Bir sonraki videoda aktarma denetimlerinin olduğu için VISIBILITY_PUBLIC kullanmanız gerekir.
  • Arka plan rengini ayarlarken dikkatli olun. Normal bir bildirimle Android 5.0 veya sonraki sürümlerde, renk yalnızca küçük uygulama simgesi. Ancak, Android 7.0'dan önceki MediaStyle bildirimlerinde renk bildirim arka planının tamamı için kullanılır. Arka plan renginizi test edin. Git gözleri yumuşatın ve aşırı parlak veya floresan renklerden kaçının.

Bu ayarlar yalnızca NotificationCompat.MediaStyle'ı kullanırken kullanılabilir:

  • setMediaSession() hesabını kullan tıklayın. Bu izin, üçüncü taraf uygulamalarına izin verir ve tamamlayıcı cihazlarla oturuma erişip kontrol edebilirsiniz.
  • Gösterilecek 3 işlem eklemek için setShowActionsInCompactView() kullanın bildirimin standart boyutlu contentView öğesine sahip olmasını sağlayabilirsiniz. (Duraklatma düğmesi şuradadır: belirtilir.)
  • Android 5.0 (API düzeyi 21) ve sonraki sürümlerde, bir bildirimi kaydırarak oynatıcıyı kullanmaya devam edebilirsiniz. Yapamazsınız bunu önceki sürümlerde yapacaksınız. Kullanıcıların bildirimi kaldırmasına ve oynatmayı durdurmasına izin vermek için API düzeyi 21'den önceki sürümlerden önce sayfanın sağ üst köşesine bir iptal düğmesi ekleyebilirsiniz. setShowCancelButton(true) ve setCancelButtonIntent() numaralı telefonu arayarak bildirim.

Duraklat ve iptal düğmelerini eklerken, ekleyeceğiniz bir PendingIntent'e oynatma işlemiyle ilişkilendirir. MediaButtonReceiver.buildMediaButtonPendingIntent() yöntemi dönüştürme işlemini yapar PlaybackState işlemini PendingIntent'e dönüştürme.