Medya tarayıcı hizmeti oluşturma

Uygulamanızın, MediaBrowserService özelliğini manifest dosyasında intent filtresiyle beyan etmesi gerekir. Kendi hizmet adınızı seçebilirsiniz. Aşağıdaki örnekte bu ad "MediaPlaybackService"tir.

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

Not: Önerilen MediaBrowserService uygulaması: MediaBrowserServiceCompat. Bu ad, media-compat destek kitaplığında tanımlanır. Bu sayfada "MediaTarayıcıService" terimi, MediaBrowserServiceCompat öğesinin bir örneğini belirtir.

Medya oturumunu başlatın

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öneten iki yönteme sahiptir: onGetRoot() hizmete erişimi kontrol eder, onLoadChildren() istemcinin MediaBrowserService içerik hiyerarşisinin bir menüsünü oluşturup görüntülemesini sağlar.

onGetRoot() ile istemci bağlantılarını kontrol etme

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

İstemcilerin hizmetinize bağlanmasına ve medya içeriğine göz atmasına izin vermek için onGetRoot(), içerik hiyerarşinizi temsil eden kök kimliği olan, boş olmayan bir TarayıcıRoot döndürmelidir.

İstemcilerin göz atmadan MediaSession'a bağlanmasına izin vermek için onGetRoot() yine de boş olmayan bir ScannerRoot döndürmelidir, ancak kök kimliği boş bir içerik hiyerarşisini temsil etmelidir.

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, MediaBrowserService cihazınıza kimlerin bağlanabileceğini kontrol etmek isteyebilirsiniz. Bunun bir yolu, hangi bağlantılara izin verildiğini belirten bir erişim kontrol listesi (EKL) kullanmaktır ya da alternatif olarak hangi bağlantıların yasaklanması gerektiğini sıralar. Belirli bağlantılara izin veren bir EKL'nin nasıl uygulanacağına ilişkin bir örnek için Universal Android Music Player örnek uygulamasındaki PackageValidator sınıfına bakın.

Sorguyu yapan istemcinin türüne bağlı olarak farklı içerik hiyerarşileri sağlamanız gerekir. Özellikle, Android Auto, kullanıcıların sesli uygulamalarla etkileşimde bulunma şeklini sınırlandırır. Daha fazla bilgi için Otomatik için Ses Oynatma bölümüne bakın. İstemci türünü belirlemek için bağlantı zamanında clientPackageName öğesine bakabilir ve istemciye bağlı olarak farklı bir BrowserRoot (veya varsa rootHints) döndürebilirsiniz.

İçerikleri onLoadChildren() ile iletişim kurma

İstemci bağlandıktan sonra, kullanıcı arayüzünün yerel bir temsilini oluşturmak için MediaBrowserCompat.subscribe() öğesine tekrarlanan çağrılar yaparak içerik hiyerarşisinde gezinebilir. 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. Bir müşteri bir alt menü açmak veya bir öğeyi oynatmak istediğinde kimliği aktarır. Kimliği uygun menü düğümü veya içerik öğesiyle ilişkilendirmekten hizmetiniz 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 nesneleri simge bit eşlemleri içermemelidir. Her bir öğe için MediaDescription oluştururken setIconUri() yöntemini çağırarak bunun yerine bir Uri kullanın.

onLoadChildren() uygulamasının nasıl uygulanacağına ilişkin bir örnek için Universal Android Music Player ö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 veya bağlı olmasına bağlıdır. Hizmet oluşturulduktan sonra başlatılabilir, bağlanabilir veya her ikisi birden olabilir. Bu durumların tümünde, cihaz tam olarak işlevseldir ve tasarlandığı işi gerçekleştirebilir. Aradaki fark, hizmetin ne kadar süreyle var olacağıdır. Bir bağlı 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 kaldırılabilir (artık herhangi bir istemciye bağlı olmadığı varsayılır).

Başka bir etkinlikte çalışan MediaBrowser bir MediaBrowserService hizmetine bağlandığında etkinliği hizmete bağlar. Böylece hizmeti bağlı hale getirir (ancak başlatılmamış olur). Bu varsayılan davranış MediaBrowserServiceCompat sınıfında yerleşik olarak bulunur.

Yalnızca bağlı olan (ve başlatılmayan) bir hizmet, tüm istemcilerinin bağlantısını kaldırdığınızda kaldırılır. Kullanıcı arayüzü etkinliğiniz bu noktada kesilirse hizmet kaldırılır. Henüz hiç müzik çalmadıysanız bu bir sorun değildir. Bununla birlikte, oynatma başladığında kullanıcı büyük olasılıkla uygulama değiştirdikten sonra da 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ı yok etmek istemezsiniz.

Bu nedenle, çalmaya başladığında startService() yöntemini çağırarak hizmetin başlatıldığından emin olmanız gerekir. Başlatılan bir hizmet, bağlı olup olmamasına bakılmaksızın açıkça durdurulmalıdır. Bu, kontrol eden kullanıcı arayüzü etkinliğinin bağlantısı kesilse bile oynatıcınızın çalışmaya devam etmesini sağlar.

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

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

  • Hizmet, bir medya düğmesine yanıt olarak başlatıldığında veya bir etkinlik ona bağlandığında (MediaBrowser üzerinden bağlandıktan sonra) oluşturulur.
  • Medya oturumu onPlay() geri çağırması, startService() kodunu içeren kodu içermelidir. Bu, hizmete bağlı tüm UI MediaBrowser etkinliklerinin bağlantısı kaldırılsa bile hizmetin başlatılmasını ve çalışmaya devam etmesini sağlar.
  • onStop() geri çağırmasıyla stopSelf() çağrılır. Hizmet başlatıldıysa durdurulur. Ayrıca, herhangi bir etkinlik yoksa hizmet imha edilir. Aksi takdirde, tüm etkinliklerinin bağlantısı kaldırılana kadar hizmet bağlı kalır. (Hizmet kaldırılmadan önce bir startService() araması gelirse 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ü

Bir ön plan hizmetiyle MediaStyle bildirimlerini kullanma

Bir hizmet oynatılırken ön planda çalışıyor olmalıdır. Bu, sistemin, hizmetin yararlı bir işlev gerçekleştirdiğini ve sistemde bellek yetersizse sonlandırılmaması gerektiğini bilmesini sağlar. Ön plan hizmetinin, kullanıcı hakkında bilgi sahibi 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 getirir. (Bunun "ön plan"ın özel bir anlamı olduğunu unutmayın. Android, işlem yönetimi amacıyla bu hizmeti ön planda kabul eder. Ancak kullanıcı için oynatıcı arka planda oyun oynarken ekranda "ön planda" başka bir uygulama görünür.)

Bir hizmet ön planda çalıştığında, tercihen bir veya daha fazla aktarım denetimiyle birlikte bir bildirim görüntülemelidir. Bildirimde, oturumun meta verilerinden faydalı bilgiler de yer almalıdır.

Oynatıcı oynamaya başladığında bildirimi oluşturun ve görüntüleyin. Bunu yapmanın en iyi yolu MediaSessionCompat.Callback.onPlay() yöntemini kullanmaktır.

Aşağıdaki örnekte, medya uygulamaları için tasarlanmış NotificationCompat.MediaStyle kullanılmaktadır. Meta verileri ve aktarım kontrollerini gösteren bir bildirimin nasıl oluşturulacağı gösterilmektedir. Kolaylık yöntemi getController(), doğrudan medya oturumunuzdan medya denetleyicisi oluşturmanıza olanak tanır.

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ının davranışına dikkat edin:

  • setContentIntent() hizmetini kullandığınızda bildirim tıklandığında hizmetiniz otomatik olarak başlar. Bu, kullanışlı bir özelliktir.
  • Kilit ekranı gibi "güvenilmeyen" bir durumda, bildirim içeriklerinin varsayılan görünürlüğü VISIBILITY_PRIVATE şeklindedir. Büyük olasılıkla taşıma kontrollerini kilit ekranında görmek istersiniz, o yüzden en iyi yol VISIBILITY_PUBLIC.
  • Arka plan rengini belirlerken dikkatli olun. Android 5.0 veya sonraki sürümlerdeki sıradan bir bildirimde, renk yalnızca küçük uygulama simgesinin arka planına uygulanır. 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. Gözlerinizi nazikçe uygulamayın ve aşırı parlak veya floresan renklerden kaçının.

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

  • Bildirimi oturumunuzla ilişkilendirmek için setMediaSession() öğesini kullanın. Bu işlem, üçüncü taraf uygulamalarının ve tamamlayıcı cihazların oturuma erişip kontrol etmesini sağlar.
  • Bildirimin standart boyutlu ContentView öğesinde gösterilmek üzere en fazla 3 işlem eklemek için setShowActionsInCompactView() öğesini kullanın. (Burada duraklat düğmesi belirtilmiştir.)
  • Android 5.0 (API düzeyi 21) ve sonraki sürümlerde, hizmet artık ön planda çalışmadığında oynatıcıyı durdurmak için bildirimi hızlıca kaydırabilirsiniz. Önceki sürümlerde bunu yapamazsınız. Android 5.0 (API düzeyi 21) sürümünden önce kullanıcıların bildirimi kaldırmasına ve oynatmayı durdurmasına izin vermek için setShowCancelButton(true) ve setCancelButtonIntent() çağrılarını yaparak bildirimin sağ üst köşesine bir iptal düğmesi ekleyebilirsiniz.

Duraklat ve iptal düğmelerini eklediğinizde oynatma işlemine eklemek için bir PendingIntent'e ihtiyacınız olur. MediaButtonReceiver.buildMediaButtonPendingIntent() yöntemi, PlaybackState işlemini PendingIntent'e dönüştürme işini yapar.