ساخت یک سرویس مرورگر رسانه

برنامه شما باید MediaBrowserService با یک فیلتر قصد در مانیفست خود اعلام کند. شما می توانید نام سرویس خود را انتخاب کنید. در مثال زیر، "MediaPlaybackService" است.

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

توجه: اجرای توصیه شده MediaBrowserService MediaBrowserServiceCompat است. که در کتابخانه پشتیبانی media-compat تعریف شده است. در سراسر این صفحه عبارت "MediaBrowserService" به نمونه ای از MediaBrowserServiceCompat اشاره دارد.

جلسه رسانه ای را آغاز کنید

هنگامی که سرویس متد برگشت فراخوانی چرخه حیات onCreate() را دریافت می کند، باید این مراحل را انجام دهد:

کد onCreate() زیر این مراحل را نشان می دهد:

کاتلین

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)
        }
    }
}

جاوا

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());
    }
}

ارتباطات مشتری را مدیریت کنید

MediaBrowserService دو روش دارد که اتصالات سرویس گیرنده را مدیریت می کند: onGetRoot() دسترسی به سرویس را کنترل می کند و onLoadChildren() این امکان را برای مشتری فراهم می کند تا منویی از سلسله مراتب محتوای MediaBrowserService را بسازد و نمایش دهد.

کنترل اتصالات کلاینت با onGetRoot()

متد onGetRoot() گره ریشه سلسله مراتب محتوا را برمی گرداند. اگر متد null برگرداند، اتصال رد می شود.

برای اینکه مشتریان بتوانند به سرویس شما متصل شوند و محتوای رسانه ای آن را مرور کنند، onGetRoot() باید یک BrowserRoot غیر تهی را برگرداند که یک شناسه ریشه است که سلسله مراتب محتوای شما را نشان می دهد.

برای اینکه مشتریان بتوانند بدون مرور به MediaSession شما متصل شوند، onGetRoot() همچنان باید یک BrowserRoot غیر تهی را برگرداند، اما شناسه ریشه باید یک سلسله مراتب محتوای خالی را نشان دهد.

یک پیاده سازی معمولی onGetRoot() ممکن است به شکل زیر باشد:

کاتلین

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)
    }
}

جاوا

@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);
    }
}

در برخی موارد، ممکن است بخواهید کنترل کنید چه کسی می تواند به MediaBrowserService شما متصل شود. یکی از راه‌ها استفاده از فهرست کنترل دسترسی (ACL) است که مشخص می‌کند کدام اتصال‌ها مجاز هستند، یا به‌طور متناوب برشمردن اینکه کدام اتصال‌ها باید ممنوع باشند. برای مثالی از نحوه پیاده‌سازی ACL که به اتصالات خاص اجازه می‌دهد، به کلاس PackageValidator در برنامه نمونه برنامه پخش موسیقی جهانی Android مراجعه کنید.

شما باید بسته به نوع مشتری که پرس و جو می کند، سلسله مراتب محتوای متفاوتی را ارائه دهید. به طور خاص، Android Auto نحوه تعامل کاربران با برنامه‌های صوتی را محدود می‌کند. برای اطلاعات بیشتر، به پخش صدا برای خودکار مراجعه کنید. می توانید در زمان اتصال به clientPackageName نگاه کنید تا نوع کلاینت را تعیین کنید و بسته به کلاینت یک BrowserRoot متفاوت (یا rootHints در صورت وجود) برگردانید.

ارتباط محتوا با onLoadChildren()

پس از اتصال کلاینت، می تواند سلسله مراتب محتوا را با برقراری تماس های مکرر با MediaBrowserCompat.subscribe() طی کند تا یک نمایش محلی از UI ایجاد کند. متد subscribe() onLoadChildren() به سرویس ارسال می کند، که لیستی از اشیاء MediaBrowser.MediaItem را برمی گرداند.

هر MediaItem یک رشته ID منحصر به فرد دارد که یک توکن مات است. هنگامی که یک کلاینت می خواهد یک زیر منو را باز کند یا یک آیتم را پخش کند، شناسه را ارسال می کند. سرویس شما مسئول مرتبط کردن شناسه با گره منو یا آیتم محتوای مناسب است.

یک پیاده سازی ساده از onLoadChildren() ممکن است به شکل زیر باشد:

کاتلین

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)
}

جاوا

@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);
}

توجه: اشیاء MediaItem ارائه شده توسط MediaBrowserService نباید حاوی بیت مپ آیکون باشند. هنگامی که MediaDescription برای هر مورد می‌سازید، با فراخوانی setIconUri() از یک Uri استفاده کنید.

برای مثالی از نحوه پیاده سازی onLoadChildren() ، به برنامه نمونه برنامه پخش موسیقی جهانی Android مراجعه کنید.

چرخه عمر سرویس مرورگر رسانه

رفتار یک سرویس اندروید بستگی به این دارد که آیا شروع شده یا به یک یا چند مشتری متصل شده است . پس از ایجاد یک سرویس، می توان آن را راه اندازی کرد، محدود کرد یا هر دو. در همه این حالت ها، کاملاً کاربردی است و می تواند کاری را که برای انجام آن طراحی شده است، انجام دهد. تفاوت این است که این سرویس چه مدت وجود خواهد داشت. یک سرویس باند تا زمانی که تمام مشتریان محدود آن جدا نشوند از بین نمی رود. یک سرویس شروع شده را می توان به صراحت متوقف و از بین برد (با فرض اینکه دیگر به هیچ مشتری محدود نشود).

وقتی MediaBrowser در حال اجرا در فعالیت دیگری به MediaBrowserService متصل می شود، فعالیت را به سرویس متصل می کند و سرویس را محدود می کند (اما شروع نشده است). این رفتار پیش فرض در کلاس MediaBrowserServiceCompat تعبیه شده است.

سرویسی که فقط محدود شده است (و شروع نشده است) زمانی از بین می رود که همه مشتریان آن باز شوند. اگر فعالیت رابط کاربری شما در این مرحله قطع شود، سرویس از بین می رود. اگر هنوز موسیقی پخش نکرده اید، این مشکلی نیست. با این حال، هنگامی که پخش شروع می شود، کاربر احتمالاً انتظار دارد حتی پس از تغییر برنامه به گوش دادن ادامه دهد. وقتی رابط کاربری را برای کار با برنامه دیگری باز می‌کنید، نمی‌خواهید پخش‌کننده را از بین ببرید.

به همین دلیل، باید مطمئن شوید که سرویس زمانی که شروع به پخش می کند با فراخوانی startService() شروع شده است. سرویس شروع شده باید به صراحت متوقف شود، خواه محدود باشد یا نباشد. این تضمین می‌کند که پخش‌کننده شما به عملکرد خود ادامه می‌دهد، حتی اگر فعالیت کنترل‌کننده رابط کاربری باز شود.

برای توقف سرویس شروع شده، Context.stopService() یا stopSelf() را فراخوانی کنید. سیستم در اسرع وقت سرویس را متوقف و از بین می برد. با این حال، اگر یک یا چند مشتری همچنان به سرویس متصل باشند، تماس برای توقف سرویس به تأخیر می افتد تا زمانی که همه مشتریان آن باز شوند.

چرخه عمر MediaBrowserService با نحوه ایجاد، تعداد مشتریانی که به آن متصل هستند و تماس هایی که از تماس های جلسه رسانه دریافت می کند، کنترل می شود. به طور خلاصه:

  • این سرویس زمانی ایجاد می شود که در پاسخ به یک دکمه رسانه شروع می شود یا زمانی که یک فعالیت به آن متصل می شود (پس از اتصال از طریق MediaBrowser آن).
  • جلسه رسانه onPlay() باید شامل کدی باشد که startService() را فراخوانی می کند. این تضمین می‌کند که سرویس شروع می‌شود و به اجرای آن ادامه می‌دهد، حتی زمانی که تمام فعالیت‌های UI MediaBrowser که به آن محدود شده‌اند، باز شوند.
  • فراخوانی onStop() باید stopSelf() را فراخوانی کند. اگر سرویس شروع شده باشد، آن را متوقف می کند. علاوه بر این، اگر هیچ فعالیتی به آن محدود نشود، سرویس از بین می رود. در غیر این صورت، سرویس تا زمانی که تمام فعالیت های آن از بین نرود، محدود می ماند. (اگر تماس بعدی startService() قبل از نابودی سرویس دریافت شود، توقف معلق لغو می شود.)

فلوچارت زیر نحوه مدیریت چرخه عمر یک سرویس را نشان می دهد. شمارنده متغیر تعداد مشتریان محدود شده را ردیابی می کند:

Service Lifecycle

استفاده از اعلان‌های MediaStyle با سرویس پیش‌زمینه

هنگامی که یک سرویس در حال پخش است، باید در پیش زمینه اجرا شود. این به سیستم اطلاع می‌دهد که سرویس عملکرد مفیدی را انجام می‌دهد و اگر حافظه سیستم کم است، نباید از بین برود. یک سرویس پیش زمینه باید یک اعلان نمایش دهد تا کاربر از آن مطلع شود و بتواند به صورت اختیاری آن را کنترل کند. فراخوانی onPlay() باید سرویس را در پیش زمینه قرار دهد. (توجه داشته باشید که این یک معنی خاص از "پیش زمینه" است. در حالی که Android سرویس را در پیش زمینه به منظور مدیریت فرآیند در نظر می گیرد، برای کاربر پخش کننده در پس زمینه بازی می کند در حالی که برخی از برنامه های دیگر در "پیش زمینه" در "پیش زمینه" قابل مشاهده است. صفحه نمایش.)

هنگامی که یک سرویس در پیش زمینه اجرا می شود، باید یک اعلان را نمایش دهد، در حالت ایده آل با یک یا چند کنترل حمل و نقل. اعلان همچنین باید حاوی اطلاعات مفیدی از فراداده های جلسه باشد.

هنگامی که پخش کننده شروع به بازی می کند، اعلان را بسازید و نمایش دهید. بهترین مکان برای انجام این کار، داخل متد MediaSessionCompat.Callback.onPlay() است.

مثال زیر از NotificationCompat.MediaStyle استفاده می کند که برای برنامه های رسانه طراحی شده است. این نشان می دهد که چگونه می توان یک اعلان ایجاد کرد که متادیتا و کنترل های انتقال را نمایش می دهد. متد راحت getController() به شما امکان می دهد یک کنترلر رسانه را مستقیماً از جلسه رسانه خود ایجاد کنید.

کاتلین

// 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())

جاوا

// 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، از رفتار این تنظیمات NotificationCompat آگاه باشید:

  • هنگامی که از setContentIntent() استفاده می کنید، سرویس شما به طور خودکار با کلیک روی اعلان شروع می شود که یک ویژگی مفید است.
  • در یک موقعیت "غیر قابل اعتماد" مانند صفحه قفل، نمای پیش فرض برای محتویات اعلان VISIBILITY_PRIVATE است. احتمالاً می خواهید کنترل های حمل و نقل را روی صفحه قفل ببینید، بنابراین VISIBILITY_PUBLIC راهی برای رفتن است.
  • هنگام تنظیم رنگ پس زمینه مراقب باشید. در یک اعلان معمولی در اندروید نسخه 5.0 یا بالاتر، رنگ فقط روی پس‌زمینه نماد برنامه کوچک اعمال می‌شود. اما برای اعلان‌های MediaStyle قبل از اندروید 7.0، از رنگ برای کل پس‌زمینه اعلان استفاده می‌شود. رنگ پس زمینه خود را تست کنید. روی چشم ها ملایم باشید و از رنگ های بسیار روشن یا فلورسنت خودداری کنید.

این تنظیمات فقط زمانی در دسترس هستند که از NotificationCompat.MediaStyle استفاده می کنید:

  • از setMediaSession() برای مرتبط کردن اعلان با جلسه خود استفاده کنید. این به برنامه های شخص ثالث و دستگاه های همراه اجازه می دهد تا به جلسه دسترسی داشته باشند و آن را کنترل کنند.
  • از setShowActionsInCompactView() برای اضافه کردن حداکثر 3 اقدام برای نمایش در contentView با اندازه استاندارد اعلان استفاده کنید. (در اینجا دکمه مکث مشخص شده است.)
  • در اندروید 5.0 (سطح API 21) و نسخه‌های جدیدتر، می‌توانید هنگامی که سرویس دیگر در پیش‌زمینه اجرا نمی‌شود، یک اعلان را برای متوقف کردن پخش‌کننده بکشید. در نسخه های قبلی نمی توانید این کار را انجام دهید. برای اینکه به کاربران اجازه دهید اعلان را حذف کنند و پخش را قبل از Android 5.0 (سطح API 21) متوقف کنند، می‌توانید با فراخوانی setShowCancelButton(true) و setCancelButtonIntent() یک دکمه لغو را در گوشه سمت راست بالای اعلان اضافه کنید.

هنگامی که دکمه‌های مکث و لغو را اضافه می‌کنید، به یک PendingIntent برای پیوست کردن به عملکرد پخش نیاز دارید. متد MediaButtonReceiver.buildMediaButtonPendingIntent() کار تبدیل اکشن PlaybackState به PendingIntent را انجام می دهد.