يجب أن يفصح تطبيقك عن MediaBrowserService
باستخدام فلتر intent في البيان. يمكنك اختيار اسم خدمتك الخاصة. في المثال التالي، هي "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()
أدناه هذه الخطوات:
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()); } }
إدارة عمليات ربط العملاء
تقدّم MediaBrowserService
طريقتَين للتعامل مع اتصالات العميل:
عناصر التحكّم في "onGetRoot()
"
والوصول إلى الخدمة
onLoadChildren()
يوفّر العميل إمكانية إنشاء وعرض قائمة بالتدرّج الهرمي لمحتوى MediaBrowserService
.
التحكم في اتصالات العملاء باستخدام "onGetRoot()
"
تعرض الطريقة onGetRoot()
العقدة الأساسية للتسلسل الهرمي للمحتوى. إذا كانت
تُرجع طريقة خالية، يتم رفض الاتصال.
للسماح للعملاء بالاتصال بالخدمة وتصفّح محتوى الوسائط الخاصة بها، يجب أن تعرض onGetRoot() معرِّف BrowserRoot غير خالٍ، وهو رقم تعريف جذر التسلسل الهرمي للمحتوى لديك.
للسماح للعملاء بالاتصال بجلسة MediaSession بدون تصفّح، onGetRoot() لا يزال يتعين أن يعرض BrowserRootًا غير فارغ، لكن يجب أن يمثل المعرف الجذر التسلسل الهرمي للمحتوى الفارغ.
قد تظهر عملية التنفيذ النموذجية للسمة onGetRoot()
على النحو التالي:
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); } }
في بعض الحالات، قد تحتاج إلى تحديد من يمكنه التواصل
إلى MediaBrowserService
. إحدى الطرق هي استخدام قائمة التحكم بالوصول (ACL)
يحدد الاتصالات المسموح بها، أو يحصي بدلاً من ذلك
الاتصالات التي يجب حظرها. للحصول على مثال حول كيفية تنفيذ قائمة التحكم بالوصول (ACL)
تسمح باتصالات محددة، يمكنك الاطلاع على
PackageValidator (أداة التحقّق من صحة الحزمة)
في برنامج Universal Android Music Player
نموذج تطبيق.
ننصحك بتقديم تسلسلات هرمية مختلفة للمحتوى استنادًا إلى
نوع العميل الذي يجري الاستعلام. وعلى وجه الخصوص، يقيّد Android Auto كيفية
يتفاعل المستخدمون مع التطبيقات الصوتية. لمزيد من المعلومات، راجع تشغيل الصوت لـ
الخيار التلقائي: إِنْتَ
يمكنه مراجعة clientPackageName
في وقت الاتصال لتحديد العميل
وعرض علامة BrowserRoot
مختلفة بناءً على العميل (أو rootHints
)
إن وجدت).
إرسال المحتوى باستخدام "onLoadChildren()
"
بعد اتصال العميل، يمكنه اجتياز التسلسل الهرمي للمحتوى من خلال إجراء طلبات متكررة إلى MediaBrowserCompat.subscribe()
لإنشاء تمثيل محلي لواجهة المستخدم. ترسل الطريقة subscribe()
دالة الاستدعاء onLoadChildren()
إلى الخدمة، وتعرض قائمة بكائنات MediaBrowser.MediaItem
.
لكل MediaItem سلسلة معرّف فريدة، وهي عبارة عن رمز مميز مبهم. عندما يريد العميل فتح قائمة فرعية أو تشغيل عنصر، فإنه يمرر المعرف. تكون خدمتك مسؤولة عن ربط المعرف بعقدة القائمة أو عنصر المحتوى المناسب.
قد تظهر عملية تنفيذ بسيطة للسمة onLoadChildren()
على النحو التالي:
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); }
ملاحظة: MediaItem
عنصر تم إرساله بواسطة MediaBrowserService
يجب ألا يحتوي على صور نقطية للرمز. يمكنك استخدام Uri
بدلاً من ذلك من خلال الاتصال
setIconUri()
عند إنشاء MediaDescription
لكلّ عنصر
للحصول على مثال حول كيفية تنفيذ onLoadChildren()
، يُرجى الاطّلاع على نموذج التطبيق Universal Android Music Player.
دورة حياة خدمة متصفح الوسائط
يعتمد سلوك خدمة Android على ما إذا كان قد تم بدءها أو ربطها بعميل واحد أو أكثر. بعد إنشاء الخدمة، يمكن تشغيلها أو ربطها أو كليهما. في جميع هذه الحالات، يعمل بشكل كامل ويمكن أن يؤدي العمل الذي تم تصميمه للقيام به. ويكمن الاختلاف في مدة توفُّر الخدمة. لا يتم إتلاف الخدمة المرتبطة حتى يتم إلغاء ربط جميع العملاء المرتبطين بها. ويمكن إيقاف الخدمة التي بدأت وإتلافها بشكل صريح (بافتراض أنها لم تعد مرتبطة بأي عملاء).
عندما يرتبط MediaBrowser
قيد التشغيل في نشاط آخر بـ MediaBrowserService
، فإنه يربط النشاط بالخدمة، ما يجعل الخدمة مرتبطة (ولكن لم تبدأ). وهذا السلوك التلقائي مضمَّن في فئة MediaBrowserServiceCompat
.
يتم تدمير الخدمة المرتبطة فقط (ولم يتم بدؤها) عند إلغاء ربط جميع عملائها. إذا انقطع اتصال نشاط واجهة المستخدم في هذه المرحلة، تتعطّل الخدمة. مَا مِنْ مُشْكِلَة فِي ذَلِكَ إِذَا لَمْ يَتِمّْ تَشْغِيلْ أَيّْ مُوسِيقَى بَعْدْ. ومع ذلك، عند بدء التشغيل، من المتوقَّع أن يستمر المستخدم في الاستماع حتى بعد تبديل التطبيقات. لا تريد تدمير المشغّل عند إلغاء ربط واجهة المستخدم للعمل مع تطبيق آخر.
لهذا السبب، يجب التأكد من بدء الخدمة عند بدئها
للعب من خلال الاتصال بـ startService()
. حاسمة
إيقاف الخدمة التي بدأت بشكل صريح، سواء كانت مرتبطة أو لا. هذا النمط
تضمن مواصلة تشغيل المشغّل حتى إذا كانت واجهة المستخدم المتحكّمة
إلغاء ربط النشاط.
لإيقاف خدمة تم بدؤها، اتصل بالرقم Context.stopService()
أو stopSelf()
. يتوقف النظام ويدمر الخدمة في أقرب وقت ممكن. ومع ذلك، إذا كان عميل أو أكثر لا يزال مرتبطًا بالخدمة، فسيتأخر الاتصال لإيقاف الخدمة حتى يتم إلغاء ربط جميع عملائها.
يتم التحكم في دورة حياة MediaBrowserService
من خلال طريقة إنشائها، وعدد العملاء المرتبطين بها، والمكالمات التي يتلقاها من استدعاءات جلسة الوسائط. في ما يلي ملخّص:
- يتم إنشاء الخدمة عند بدئها استجابةً لزر وسائط أو عندما يرتبط نشاط بها (بعد الاتصال من خلال
MediaBrowser
). - يجب أن يشتمل معاودة الاتصال
onPlay()
لجلسة الوسائط على رمز يطلبstartService()
. ويضمن ذلك بدء الخدمة واستمرار تشغيلها، حتى عند إلغاء ربط جميع أنشطةMediaBrowser
في واجهة المستخدم المرتبطة بها. - يجب أن يتصل معاودة الاتصال "
onStop()
" بالرقمstopSelf()
. في حال بدء الخدمة، سيوقفها هذا الإجراء. بالإضافة إلى ذلك، يتم إتلاف الخدمة في حال عدم وجود أنشطة مرتبطة بها. وبخلاف ذلك، ستظل الخدمة مرتبطة حتى يتم إلغاء ربط جميع أنشطتها. (في حال تلقي اتصالstartService()
لاحق قبل أن تتلف الخدمة، يتم إلغاء التوقّف المعلَّق).
يوضح المخطط الانسيابي التالي كيفية إدارة دورة حياة الخدمة. يتتبع عدّاد المتغير عدد العملاء المرتبطين:
استخدام إشعارات MediaStyle مع خدمة تعمل في المقدّمة
عندما تكون إحدى الخدمات قيد التشغيل، يجب أن تكون قيد التشغيل في المقدّمة. يتيح ذلك للنظام معرفة أن الخدمة تؤدي وظيفة مفيدة ويجب عدم إنهائها إذا كانت ذاكرة النظام منخفضة. يجب أن تعرض الخدمة التي تعمل في المقدّمة إشعارًا حتى يعرف المستخدم ذلك ويمكنه التحكّم به اختياريًا. يجب أن تضع ميزة معاودة الاتصال onPlay()
الخدمة في المقدّمة. (لاحظ أن هذا معنى خاص لـ "المقدمة". بينما يضع Android الخدمة في المقدّمة لأغراض إدارة العملية، يشغّل المستخدم المشغّل في الخلفية بينما يظهر تطبيق آخر في "المقدمة". على الشاشة).
عند تشغيل خدمة في المقدّمة، يجب عرض إشعار، ومن المفترض أن يكون مع عنصر تحكّم واحد أو أكثر في النقل. يجب أن يتضمّن الإشعار أيضًا معلومات مفيدة من البيانات الوصفية للجلسة.
يمكنك إنشاء إشعار وعرضه عندما يبدأ المشغّل في التشغيل. أفضل مكان لإجراء ذلك هو من خلال طريقة MediaSessionCompat.Callback.onPlay()
.
يستخدم المثال أدناه علامة
NotificationCompat.MediaStyle
,
والمصمَّمة لتطبيقات الموسيقى يعرض القسم كيفية إنشاء إشعار يعرض البيانات الوصفية وعناصر التحكّم في النقل. طريقة الملاءمة
getController()
تتيح لك إنشاء وحدة تحكّم في الوسائط مباشرةً من جلسة الوسائط.
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، يجب الانتباه إلى سلوك إعدادات NotificationCompat:
- عند استخدام "
setContentIntent()
"، ستبدأ الخدمة تلقائيًا عند تلقّي الإشعار. النقر عليها، وهي ميزة مفيدة. - في موقع "غير موثوق به" الوضع
مثل شاشة القفل، يكون مستوى الظهور التلقائي لمحتوى الإشعارات هو
VISIBILITY_PRIVATE
. ربما ترغب في رؤية عناصر التحكم في النقل على شاشة القفل، وبالتاليVISIBILITY_PUBLIC
هو الحل الأمثل. - يُرجى توخّي الحذر عند ضبط لون الخلفية. في إشعار عادي في الإصدار 5.0 من نظام التشغيل Android أو الإصدارات الأحدث، سيتم تطبيق اللون على خلفية أيقونة التطبيق الصغير. ولكن بالنسبة لإشعارات MediaStyle التي تسبق Android 7.0، سيكون لون يُستخدم في خلفية الإشعار بالكامل. اختبِر لون الخلفية. بدء الاستخدام بلطف على العينين وتجنب الألوان الساطعة أو الفلورية للغاية.
لا تتوفّر هذه الإعدادات إلا عند استخدام NotificationCompat.MediaStyle:
- استخدام "
setMediaSession()
" لربط الإشعار بجلستك. يسمح هذا الإجراء بالتطبيقات التابعة لجهات خارجية. والأجهزة المصاحبة للوصول إلى الجلسة والتحكم فيها. - يمكنك استخدام
setShowActionsInCompactView()
لإضافة ما يصل إلى 3 إجراءات ليتم عرضها في. طريقة contentView ذات الحجم القياسي للإشعار. (هنا زر الإيقاف المؤقت محددة). - في Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث، يمكنك تمرير الإشعار سريعًا لإيقاف
إذا لم تعُد الخدمة تعمل في المقدّمة. لا يمكنك إجراء ما يلي:
هذا في الإصدارات السابقة. للسماح للمستخدمين بإزالة الإشعار وإيقاف التشغيل
قبل الإصدار Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات)، يمكنك إضافة زر إلغاء في أعلى يسار
من خلال الاتصال بـ
setShowCancelButton(true)
وsetCancelButtonIntent()
.
عند إضافة زرَّي الإيقاف المؤقت والإلغاء، ستحتاج إلى رمز PendingIntent لإرفاقه
إلى إجراء التشغيل. تؤدي الطريقة MediaButtonReceiver.buildMediaButtonPendingIntent()
وظيفة تحويل
تحويل حالة تشغيل PendingState إلى رمز PendingIntent.