يساعدك Android Auto ونظام التشغيل Android Automotive في توفير محتوى تطبيقك الإعلامي للمستخدمين في سياراتهم. يجب أن يقدّم تطبيق الوسائط للسيارات خدمة تصفّح وسائط لكي يتمكّن Android Auto ونظام التشغيل Android Automotive أو تطبيق آخر يتضمّن تصفّح الوسائط من اكتشاف المحتوى الخاص بك وعرضه.
يفترض هذا الدليل أنّ لديك تطبيق وسائط لتشغيل المحتوى الصوتي على هاتف ويتوافق مع بنية تطبيق الوسائط في Android.
يصف هذا الدليل المكوّنات المطلوبة لتطبيق MediaBrowserService
وMediaSession
التي يحتاجها تطبيقك لكي يعمل على Android Auto أو Android
Automotive. بعد الانتهاء من البنية الأساسية الأساسية للوسائط، يمكنك
إتاحة استخدام تطبيقك مع Android Auto وإتاحة استخدام
نظام التشغيل Android Automotive مع تطبيقك.
قبل البدء
- راجِع مستندات Android media API.
- راجِع مقالة إنشاء تطبيقات وسائط للحصول على إرشادات حول التصميم.
- راجِع المصطلحات والمفاهيم الرئيسية المدرَجة في هذا القسم.
المصطلحات والمفاهيم الرئيسية
- خدمة متصفّح الوسائط
- خدمة Android ينفذها تطبيق الوسائط الخاص بك وتتوافق مع واجهة برمجة التطبيقات
MediaBrowserServiceCompat
يستخدم تطبيقك هذه الخدمة لعرض محتواه. - متصفّح الوسائط
- واجهة برمجة تطبيقات تستخدمها تطبيقات الوسائط لاستكشاف خدمات متصفّحات الوسائط وعرض المحتوى يستخدم نظاما التشغيل Android Auto وAndroid Automotive متصفّح وسائط للعثور على خدمة متصفّح الوسائط في تطبيقك.
- ملف وسائط
يُنظّم متصفّح الوسائط المحتوى في شجرة من كائنات
MediaItem
. يمكن أن يتضمّن عنصر الوسائط إحدى العلامتَين التاليتَين أو كلتيهما:FLAG_PLAYABLE
: يشير إلى أنّ العنصر هو صفحة خصائص تفصيلية في شجرة المحتوى. يمثّل العنصر بثًا صوتيًا واحدًا، مثل أغنية في ألبوم أو فصل في كتاب مسموع أو حلقة من برنامج بودكاست.FLAG_BROWSABLE
: يشير إلى أنّ العنصر هو عقدة في شجرة المحتوى ويضم عناصر فرعية. على سبيل المثال، يمثّل العنصر ألبومًا، وعناصره الثانوية هي الأغاني المضمّنة في الألبوم.
يعمل عنصر الوسائط القابل للتصفّح والتشغيل كقائمة تشغيل. يمكنك اختيار العنصر نفسه لتشغيل كل العناصر الفرعية له، أو يمكنك تصفُّح العناصر الفرعية.
- محسّنة للمركبات
نشاط لتطبيق يعمل بنظام التشغيل Android Automotive يلتزم بإرشادات تصميم نظام التشغيل Android Automotive لا يرسم نظام التشغيل Android Automotive واجهة هذه الأنشطة، لذا يجب التأكّد من أنّ تطبيقك يلتزم بإرشادات التصميم. وعادةً ما يشمل ذلك أهداف أكبر حجمًا للنقر عليها وحجم خطوط أكبر، وإمكانية استخدام الوضعَين النهاري والليلي، و ratiosشديدة التباين.
لا يُسمح بعرض واجهات المستخدم المحسّنة للمركبات إلا عندما لا تكون "قيود تجربة المستخدم في المركبات" (CUXR) سارية، لأنّ هذه الواجهات قد تتطلّب تركيزًا أو تفاعلًا مطوّلاً من المستخدم. لا تكون ميزات CUXR سارية عندما تكون السيارة متوقفة أو متوقفة، ولكنها تكون سارية دائمًا عندما تكون السيارة في حركة.
لا تحتاج إلى تصميم أنشطة لنظام Android Auto، لأنّه يرسم واجهته المحسّنة للمركبات باستخدام المعلومات الواردة من خدمة تصفّح الوسائط.
ضبط ملفات بيان تطبيقك
قبل أن تتمكّن من إنشاء خدمة متصفّح الوسائط، عليك ضبط ملفات البيان في تطبيقك.
الإفصاح عن خدمة متصفّح الوسائط
يتصل كل من Android Auto ونظام التشغيل Android Automotive بتطبيقك من خلال خدمة متصفّح الوسائط لتصفّح عناصر الوسائط. حدِّد خدمة تصفُّح الوسائط في البيان للسماح لنظامَي التشغيل Android Auto وAndroid Automotive باكتشاف الخدمة والربط بتطبيقك.
يوضّح المقتطف التالي من الرمز البرمجي كيفية الإفصاح عن خدمة متصفّح الوسائط فيملف البيان. أدرِج هذا الرمز في ملف بيان وحدة Android Automotive OS وفي ملف بيان تطبيق الهاتف.
<application>
...
<service android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
...
</application>
تحديد رموز التطبيقات
عليك تحديد رموز التطبيقات التي يمكن لنظامَي Android Auto وAndroid Automotive استخدامها لتمثيل تطبيقك في واجهة مستخدم النظام. يجب توفير نوعَين من الرموز:
- رمز مشغّل التطبيقات
- رمز تحديد المصدر
رمز مشغّل التطبيقات
يمثّل رمز مشغّل التطبيقات تطبيقك في واجهة مستخدم النظام، مثل مشغّل التطبيقات ولوحة الرموز. يمكنك تحديد أنّك تريد استخدام الرمز من تطبيقك المتوافق مع الأجهزة الجوّالة لتمثيل تطبيق الوسائط في السيارة باستخدام بيان البيان التالي:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
لاستخدام رمز مختلف عن رمز تطبيقك المتوافق مع الأجهزة الجوّالة، اضبط سمة android:icon
على عنصر <service>
لخدمة متصفّح الوسائط في البيان:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
رمز تحديد المصدر
يتم استخدام رمز الإسناد في الأماكن التي يكون فيها محتوى الوسائط هو الأولوية، مثل بطاقات الوسائط. ننصحك بإعادة استخدام الرمز الصغير المستخدَم للإشعارات. يجب أن يكون هذا الرمز أحادي اللون. يمكنك تحديد رمز يُستخدَم لتمثيل تطبيقك باستخدام بيان البيان التالي:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
إنشاء خدمة متصفّح الوسائط
يمكنك إنشاء خدمة تصفّح وسائط من خلال توسيع فئة MediaBrowserServiceCompat
. يمكن بعد ذلك لكل من Android Auto ونظام التشغيل Android Automotive استخدام خدمتك للقيام بما يلي:
- تصفَّح التسلسل الهرمي لمحتوى تطبيقك لعرض قائمة للمستخدم.
- احصل على الرمز المميّز لعنصر
MediaSessionCompat
في تطبيقك للتحكّم في تشغيل الصوت.
يمكنك أيضًا استخدام خدمة متصفّح الوسائط للسماح للعملاء الآخرين بالوصول إلى محتوى الوسائط من تطبيقك. ويمكن أن تكون عملاء الوسائط هذه تطبيقات أخرى على هاتف المستخدم، أو يمكن أن تكون عملاء عن بُعد آخرين.
سير عمل خدمة متصفّح الوسائط
يصف هذا القسم كيفية تفاعل نظام التشغيل Android Automotive وAndroid Auto مع خدمة متصفّح الوسائط أثناء سير عمل المستخدم المعتاد.
- يشغِّل المستخدم تطبيقك على نظام التشغيل Android Automotive أو Android Auto.
- يتواصل نظام التشغيل Android Automotive أو Android Auto مع خدمة browser
لوسائط تطبيقك باستخدام الطريقة
onCreate()
. عند تنفيذonCreate()
الطريقة، يجب إنشاء عنصرMediaSessionCompat
وتسجيله وتسجيل عنصر الاستدعاء المرتبط به. - يُطلِق نظام التشغيل Android Automotive أو Android Auto طريقة
onGetRoot()
في خدمتك للحصول على عنصر الوسائط الجذر في التسلسل الهرمي للمحتوى. لا يتم عرض ملف الوسائط الجذر، ويُستخدَم بدلاً من ذلك لاسترداد المزيد من المحتوى من تطبيقك. - يُطلِق نظام التشغيل Android Automotive أو Android Auto أسلوب
onLoadChildren()
في خدمتك للحصول على العناصر الفرعية لعنصر الوسائط الجذر. يعرض نظام التشغيل Android Automotive و Android Auto عناصر الوسائط هذه على أنّها المستوى الأعلى من عناصر المحتوى. اطّلِع على تنظيم القائمة الرئيسية في هذه الصفحة للحصول على مزيد من المعلومات حول ما يتوقّعه النظام على هذا المستوى. - إذا اختار المستخدم عنصر وسائط قابلاً للتصفّح، يتم استدعاء
onLoadChildren()
طريقة خدمتك مرة أخرى لاسترداد العناصر الفرعية لعنصر القائمة المحدّد. - إذا اختار المستخدم عنصر وسائط قابلاً للتشغيل، يُطلِق نظام التشغيل Android Automotive أو Android Auto طريقة طلب معاودة الاتصال المناسبة لجلسة الوسائط لتنفيذ هذا الإجراء.
- ويمكن للمستخدم أيضًا البحث في المحتوى الخاص بك إذا كان تطبيقك يتيح ذلك. في هذه الحالة، يُطلِق نظام التشغيل Android Automotive أو Android Auto
onSearch()
طريقة خدمتك.
إنشاء التدرّج الهرمي للمحتوى
يتصل Android Auto ونظام التشغيل Android Automotive بخدمة متصفّح الوسائط في تطبيقك لتحديد المحتوى المتاح. يجب تنفيذ طريقتَين في
خدمة متصفّح الوسائط لتفعيل هذه الميزة: onGetRoot()
و
onLoadChildren()
تنفيذ onGetRoot
تُعرِض طريقة onGetRoot()
في خدمتك معلومات عن العقدة الجذر للهيكل الهرمي للمحتوى.
يستخدم Android Auto ونظام التشغيل Android Automotive هذه العقدة الجذر لطلب باقي
المحتوى باستخدام onLoadChildren()
الطريقة.
يعرض مقتطف الرمز البرمجي التالي تنفيذًا بسيطًا لطريقة
onGetRoot()
:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): BrowserRoot? = // Verify that the specified package is allowed to access your // content. You'll need to write your own logic to do this. if (!isValid(clientPackageName, clientUid)) { // If the request comes from an untrusted package, return null. // No further calls will be made to other media browsing methods. null } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // Verify that the specified package is allowed to access your // content. You'll need to write your own logic to do this. if (!isValid(clientPackageName, clientUid)) { // If the request comes from an untrusted package, return null. // No further calls will be made to other media browsing methods. return null; } return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null); }
للحصول على مثال أكثر تفصيلاً لهذه الطريقة، اطّلِع على طريقة onGetRoot()
في نموذج تطبيق Universal Android Music Player على GitHub.
إضافة عملية التحقّق من الحزمة لـ onGetRoot()
عند إجراء مكالمة إلى onGetRoot()
طريقة خدمتك، تُرسِل حزمة الاتصال معلومات التعريف إلى خدمتك. يمكن أن تستخدم
خدمتك هذه المعلومات لتحديد ما إذا كان بإمكان هذه الحزمة الوصول إلى
المحتوى الخاص بك. على سبيل المثال، يمكنك حصر إمكانية الوصول إلى محتوى تطبيقك بقائمة
الحِزم الموافَق عليها من خلال مقارنة clientPackageName
بقائمة المسموح بها
والتحقق من الشهادة المستخدَمة لتوقيع حزمة APK. إذا تعذّر
إثبات ملكية الحزمة، اضغط على null
لرفض الوصول إلى المحتوى.
لمنح تطبيقات النظام، مثل Android Auto وAndroid Automotive،
إذن الوصول إلى المحتوى الخاص بك، يجب أن تعرض خدمتك دائمًا قيمة غير صفرية
BrowserRoot
عندما تستدعي تطبيقات النظام هذه الأسلوبonGetRoot()
. يمكن أن يختلف توقيع تطبيق نظام التشغيل Android Automotive استنادًا
إلى طراز السيارة وعلامتها التجارية، لذا عليك السماح بعمليات الربط من جميع
تطبيقات النظام لتتوافق مع نظام التشغيل Android Automotive بشكلٍ سليم.
يوضّح مقتطف الرمز البرمجي التالي كيفية التحقّق من أنّ حزمة الاتصال هي تطبيق نظام:
fun isKnownCaller(
callingPackage: String,
callingUid: Int
): Boolean {
...
val isCallerKnown = when {
// If the system is making the call, allow it.
callingUid == Process.SYSTEM_UID -> true
// If the app was signed by the same certificate as the platform
// itself, also allow it.
callerSignature == platformSignature -> true
// ... more cases
}
return isCallerKnown
}
مقتطف الرمز البرمجي هذا هو مقتطف من فئة PackageValidator
في نموذج تطبيق Universal Android Music Player على GitHub. اطّلِع على هذه الفئة
للاطّلاع على مثال أكثر تفصيلاً حول كيفية تنفيذ عملية التحقّق من الحزمة لطريقة onGetRoot()
لخدمة.
بالإضافة إلى السماح بتطبيقات النظام، يجب السماح لخدمة "مساعد Google"
بالاتصال بجهاز MediaBrowserService
. يُرجى العِلم أنّ "مساعد Google" لديه
أسماء حِزم منفصلة
للهاتف، بما في ذلك Android Auto، ونظام التشغيل Android Automotive.
تنفيذ onLoadChildren()
بعد تلقّي عنصر العقدة الجذر، ينشئ نظاما التشغيل Android Auto وAndroid Automotive
قائمة على مستوى أعلى من خلال استدعاء onLoadChildren()
على عنصر العقدة الجذر للحصول على عناصره الفرعية. تُنشئ تطبيقات العميل قوائم فرعية من خلال
استدعاء هذه الطريقة نفسها باستخدام كائنات العقد الفرعية.
يتم تمثيل كل عقدة في التدرّج الهرمي للمحتوى بعنصر MediaBrowserCompat.MediaItem
. يتم تحديد كل عنصر من عناصر الوسائط هذه من خلال سلسلة معرّف فريدة. تتعامل التطبيقات العميلة
مع سلاسل التعريف هذه كرموز مميّزة غير شفافة. عندما يريد تطبيق العميل الانتقال
إلى قائمة فرعية أو تشغيل عنصر وسائط، يُرسِل الرمز المميّز. يتحمّل تطبيقك مسؤولية
ربط الرمز المميّز بعنصر الوسائط المناسب.
يعرض مقتطف الرمز البرمجي التالي تنفيذًا بسيطًا للإجراء onLoadChildren()
:
Kotlin
override fun onLoadChildren( parentMediaId: String, result: Result<List<MediaBrowserCompat.MediaItem>> ) { // Assume for example that the music catalog is already loaded/cached. val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf() // Check whether 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<MediaBrowserCompat.MediaItem>> result) { // Assume for example that the music catalog is already loaded/cached. List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>(); // Check whether 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); }
للحصول على مثال كامل على هذه الطريقة، اطّلِع على onLoadChildren()
في نموذج تطبيق Universal Android Music Player على GitHub.
تنظيم القائمة الرئيسية
يفرض كل من Android Auto ونظام التشغيل Android Automotive قيودًا محدّدة على
بنية القائمة الرئيسية. ويتم إرسال هذه المعلومات إلى MediaBrowserService
من خلال تلميحات الجذر التي يمكن قراءتها من خلال الوسيطة Bundle
التي يتم تمريرها إلى
onGetRoot()
.
من خلال اتّباع هذه النصائح، يعرض النظام المحتوى الجذر بشكلٍ مثالي
كعلامات تبويب للتنقّل. في حال عدم اتّباع هذه النصائح، قد يتم
إسقاط بعض المحتوى الأساسي أو يصبح من الصعب على النظام العثور عليه. يتم إرسال تلميحتَين:
- الحدّ الأقصى لعدد العناصر الفرعية للجذر: في معظم الحالات، يكون هذا العدد أربعة. وهذا يعني أنّه لا يمكن عرض أكثر من أربع علامات تبويب.
- العلامات المسموح بها على العناصر الفرعية الجذر:
يمكنك توقّع أن تكون هذه القيمة
MediaItem#FLAG_BROWSABLE
. وهذا يعني أنّه لا يمكن عرض علامات تبويب إلا للعناصر القابلة للتصفّح، وليس للعناصر القابلة للتشغيل.
استخدِم الرمز البرمجي التالي لقراءة تلميحات الجذر ذات الصلة:
Kotlin
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle ): BrowserRoot { val maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4) val supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE) // Rest of method... }
Java
import androidx.media.utils.MediaConstants; // Later, in your MediaBrowserServiceCompat. @Override public BrowserRoot onGetRoot( String clientPackageName, int clientUid, Bundle rootHints) { int maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4); int supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE); // Rest of method... }
يمكنك اختيار تقسيم منطق بنية التسلسل الهرمي للمحتوى
استنادًا إلى قيم هذه التلميحات، خاصةً إذا كان التسلسل الهرمي يختلف بين عمليات دمج
MediaBrowser
خارج Android Auto ونظام التشغيل Android Automotive.
على سبيل المثال، إذا كنت تعرض عادةً عنصرًا قابلاً للتشغيل في الجذر، قد تحتاج إلى تداخله
ضمن عنصر قابل للتصفّح في الجذر بدلاً من ذلك بسبب قيمة التلميح
للرموز المتوافقة.
بالإضافة إلى إشارات الجذر، هناك إرشادات إضافية يجب اتّباعها للمساعدة في ضمان عرض علامات التبويب على النحو الأمثل:
- قدِّم رموزًا أحادية اللون، ويُفضّل أن تكون بيضاء، لكل عنصر من عناصر علامة التبويب.
- قدِّم تصنيفات قصيرة ولكن ذات مغزى لكل عنصر من عناصر علامة التبويب. إنّ إبقاء التصنيفات قصيرة يقلل من احتمالية اقتطاع السلاسل.
عرض العمل الفني للوسائط
يجب إرسال العمل الفني لعناصر الوسائط كمعرّف موارد منتظم محلي باستخدام إما
ContentResolver.SCHEME_CONTENT
أو ContentResolver.SCHEME_ANDROID_RESOURCE
.
يجب أن يحلّ معرّف الموارد المنتظم (URI) المحلي هذا إلى ملف رسومات نقطية أو ملف رسومات متجهة قابلَين للرسم في موارد
التطبيق. بالنسبة إلى عناصر MediaDescriptionCompat
التي تمثّل عناصر في
التسلسل الهرمي للمحتوى، يجب تمرير معرّف الموارد المنتظم (URI) من خلال setIconUri()
.
بالنسبة إلى عناصر MediaMetadataCompat
التي تمثّل المحتوى الذي يتم تشغيله حاليًا، يجب تمرير ملف URI من خلال putString()
،
باستخدام أي من المفاتيح التالية:
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
MediaMetadataCompat.METADATA_KEY_ART_URI
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
توضّح الخطوات التالية كيفية تنزيل العمل الفني من عنوان URL للويب وعرضه
من خلال عنوان URL محلي. للحصول على مثال أكثر اكتمالاً، اطّلِع على تنفيذ openFile()
والطُرق المحيطة به في نموذج تطبيق Universal Android Music Player.
أنشئ معرّف موارد منتظم (URI)
content://
يتوافق مع معرّف الموارد المنتظم للويب. تُرسِل خدمة متصفّح الوسائط وجلسة الوسائط عنوان URL هذا للمحتوى إلى Android Auto و نظام التشغيل Android Automotive.Kotlin
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
Java
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
في عملية تنفيذ
ContentProvider.openFile()
، تحقّق مما إذا كان هناك ملف متوفرًا لمعرّف الموارد المنتظم المقابل. إذا لم يكن الأمر كذلك، نزِّل ملف الصورة واحفظه في ذاكرة التخزين المؤقت. يستخدم مقتطف الرمز البرمجي التالي Glide.Kotlin
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
Java
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
لمزيد من التفاصيل عن مقدّمي المحتوى، يُرجى الاطّلاع على مقالة إنشاء مقدّم محتوى.
تطبيق أنماط المحتوى
بعد إنشاء التسلسل الهرمي للمحتوى باستخدام عناصر قابلة للتصفّح أو التشغيل، يمكنك تطبيق أنماط المحتوى التي تحدّد كيفية عرض هذه العناصر في السيارة.
يمكنك استخدام أنماط المحتوى التالية:
- عناصر القائمة
-
يمنح أسلوب المحتوى هذا الأولوية للعناوين والبيانات الوصفية على الصور.
- عناصر الشبكة
-
يمنح أسلوب المحتوى هذا الأولوية للصور على العناوين والبيانات الوصفية.
ضبط أنماط المحتوى التلقائية
يمكنك ضبط الإعدادات التلقائية العامة لكيفية عرض عناصر الوسائط من خلال تضمين
ثوابت معيّنة في حِزمة BrowserRoot
الإضافية لطريقة
onGetRoot()
في خدمتك. يقرأ Android Auto ونظام التشغيل Android Automotive هذه الحِزمة ويبحثان عن
هذه الثوابت لتحديد النمط المناسب.
يمكن استخدام الإضافات التالية كمفاتيح في الحزمة:
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: يشير إلى تلميح عرض لجميع العناصر القابلة للتصفّح ضمن شجرة التصفّح.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: يشير إلى تلميح عرض لجميع العناصر القابلة للتشغيل ضمن شجرة التصفّح.
يمكن ربط المفاتيح بالقيم الثابتة الصحيحة التالية للتأثير في عرض هذه العناصر:
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: يتم عرض العناصر المقابلة كعناصر قائمة.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: يتم عرض العناصر المقابلة كعناصر شبكة.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: يتم عرض العناصر المقابلة كعناصر في قائمة "الفئة". هذه العناصر هي مثل عناصر القائمة العادية باستثناء أنّه يتم تطبيق الهوامش حول رموز العناصر، لأنّ الرموز تبدو أفضل عندما تكون صغيرة. يجب أن تكون الأيقونات متّجهًا قابلاً للرسم وقابلًا للتلوين. من المتوقّع أن يتم توفير هذا التلميح فقط للعناصر القابلة للتصفّح.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: يتم عرض العناصر المقابلة كعناصر في شبكة "الفئة". هذه العناصر هي مثل عناصر الشبكة العادية، باستثناء أنّه يتم تطبيق الهوامش حول أيقونات العناصر، لأنّها تبدو أفضل عندما تكون صغيرة. يجب أن تكون الأيقونات متّجهًا قابلاً للرسم وقابلًا للتلوين. من المتوقّع أن يتم توفير هذا التلميح فقط للعناصر القابلة للتصفّح.
يوضّح مقتطف الرمز التالي كيفية ضبط نمط المحتوى التلقائي لأجل عرض العناصر القابلة للتصفّح في شبكات والعناصر القابلة للتشغيل في قوائم:
Kotlin
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
Java
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
ضبط أنماط المحتوى لكل عنصر
تتيح لك Content Style API إلغاء نمط المحتوى التلقائي لأي عناصر فرعية لعنصر وسائط قابل للتصفّح، بالإضافة إلى أي عنصر وسائط نفسه.
لإلغاء الإعداد التلقائي للعناصر الفرعية لعنصر وسائط قابل للتصفّح، أنشئ ملفًا برمجيًا لشدَّة ملف MediaDescription
الخاص بعنصر الوسائط وأضِف الاقتراحات
نفسها المذكورة سابقًا. تنطبق سمة DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
على العناصر الفرعية القابلة للتشغيل من هذا العنصر، بينما تنطبق سمة
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
على العناصر الفرعية القابلة للتصفّح من هذا العنصر.
لتجاوز الإعداد التلقائي لعنصر وسائط معيّن نفسه، وليس
لعناصره الفرعية، أنشئ حِزمة إضافات في MediaDescription
لعنصر الوسائط
وأضِف تلميحًا باستخدام المفتاح
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
.
استخدِم القيم نفسها الموضّحة سابقًا لتحديد طريقة عرض هذا العنصر.
يوضّح مقتطف الرمز البرمجي التالي كيفية إنشاء MediaItem
قابل للتصفّح ويتجاوز أسلوب المحتوى التلقائي لكل من العنصر نفسه والعناصر الفرعية. ويتم تنسيقه
كعنصر قائمة فئة، وعناصره الفرعية القابلة للتصفّح كعناصر قائمة، وعناصره
القابلة للتشغيل كعناصر شبكة:
Kotlin
import androidx.media.utils.MediaConstants private fun createBrowsableMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Java
import androidx.media.utils.MediaConstants; private MediaBrowser.MediaItem createBrowsableMediaItem( String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
تجميع العناصر باستخدام نصائح العناوين
لتجميع عناصر الوسائط ذات الصلة معًا، استخدِم تلميحًا لكل عنصر. يجب أن يُعلن كل عنصر وسائط
في مجموعة عن حِزمة إضافات في MediaDescription
تتضمّن تعيينًا باستخدام المفتاح
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
وقيمة سلسلة متطابقة. أدخِل ترجمة لهذه السلسلة التي تُستخدَم كعنوان للمجموعة.
يوضّح مقتطف الرمز التالي كيفية إنشاء MediaItem
يتضمّن عنوانًا ل subgroup
"Songs"
:
Kotlin
import androidx.media.utils.MediaConstants private fun createMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putString( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Java
import androidx.media.utils.MediaConstants; private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putString( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
يجب أن يرسل تطبيقك جميع عناصر الوسائط التي تريد تجميعها معًا في قالب متجاور. على سبيل المثال، لنفترض أنّك تريد عرض مجموعتَين من عناصر الوسائط، "الأغاني" و "الألبومات"، بهذا الترتيب، ويُرسل تطبيقك خمسة عناصر وسائط بالترتيب التالي:
- ملف الوسائط "أ" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- ملف الوسائط "ب" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
- ملف الوسائط "ج" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- ملف الوسائط "د" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- ملف الوسائط "هـ" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
بما أنّ عناصر الوسائط الخاصة بمجموعة "الأغاني" ومجموعة "الألبومات" لا يتم تجميعها معًا في مجموعات متسلسلة، يفسّر Android Auto ونظام التشغيل Android Automotive هذا على أنّه المجموعات الأربع التالية:
- المجموعة 1 التي تحمل اسم "أغانٍ" وتتضمّن عنصر الوسائط "أ"
- المجموعة 2 التي تُسمى "الألبومات" وتتضمن عنصر الوسائط "ب"
- المجموعة 3 التي تُسمى "أغانٍ" وتتضمن عنصرَي الوسائط "ج" و"د"
- المجموعة 4 التي تُسمى "الألبومات" وتتضمن عنصر الوسائط "هـ"
لعرض هذه العناصر في مجموعتَين، يجب أن يُرسل تطبيقك عناصر الوسائط بالترتيب التالي بدلاً من ذلك:
- ملف الوسائط "أ" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- ملف الوسائط "ج" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- ملف الوسائط "د" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- ملف الوسائط "ب" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
- ملف الوسائط "هـ" الذي يتضمّن
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
عرض مؤشرات البيانات الوصفية الإضافية
يمكنك تضمين مؤشرات بيانات وصفية إضافية لتوفير معلومات عن المحتوى في شجرة متصفّح الوسائط وأثناء التشغيل. ضمن شجيرة التنقّل، يقرأ Android Auto ونظام التشغيل Android Automotive الإضافات المرتبطة بعنصر معيّن ويبحثان عن ثوابت معيّنة لتحديد المؤشرات التي يجب عرضها. أثناء تشغيل الوسائط، يقرأ Android Auto ونظام التشغيل Android Automotive ملف المحتوى الوصفي لجلسة الوسائط ويبحثان عن ثوابت معيّنة لتحديد المؤشرات التي سيتم عرضها.
يمكن استخدام الثوابت التالية في كلّ من MediaItem
وصف الإضافات
MediaMetadata
الإضافات:
EXTRA_DOWNLOAD_STATUS
: يشير إلى حالة تنزيل المحتوى. استخدِم هذا الثابت كأحد المفاتيح، وتكون الثوابت الطويلة التالية هي القيم المحتمَلة:STATUS_DOWNLOADED
: تم تنزيل المحتوى بالكامل.STATUS_DOWNLOADING
: يتم تنزيل العنصر.STATUS_NOT_DOWNLOADED
: لم يتم تنزيل العنصر.
METADATA_KEY_IS_EXPLICIT
: تشير إلى ما إذا كان العنصر يتضمّن محتوًى فاضحًا. للإشارة إلى أنّ العنصر واضح، استخدِم هذا الثابت كمفتاح والقيمة الطويلةMETADATA_VALUE_ATTRIBUTE_PRESENT
.
لا يمكن استخدام الثوابت التالية إلا في MediaItem
العناصر الإضافية للوصف:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: يشير إلى حالة اكتمال المحتوى الطويل، مثل حلقات البودكاست أو الكتب المسموعة. استخدِم هذا الثابت كمفتاح، وتكون الأعداد الكاملة التالية الثابتة هي القيم المحتمَلة:DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: لم يتم تشغيل المحتوى مطلقًا.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: تم تشغيل المحتوى جزئيًا، والموقع الحالي في مكان ما في المنتصف.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: تم إكمال العنصر.
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: يشير إلى نسبة التقدّم في إكمال المحتوى الطويل كقيمة مضاعفة تتراوح بين 0.0 و1.0، شاملة. يوفّر هذا الملحق مزيدًا من المعلومات حول حالةPARTIALLY_PLAYING
لكي يعرض Android Auto أو Android Automotive مؤشرًا للتقدّم أكثر وضوحًا، مثل ملف شريط التقدم. إذا كنت تستخدم هذه الميزة الإضافية، اطّلِع على القسم المعنيّ بموضوع تعديل شريط التقدم في طريقة العرض "تصفّح" أثناء تشغيل المحتوى في هذا الدليل لمعرفة كيفية إبقاء هذا المؤشر محدّثًا بعد مرّة الظهور الأولى.
لعرض المؤشرات التي تظهر أثناء تصفّح المستخدِم لشجيرة تصفّح الوسائط، أنشئ حِزمة إضافات تتضمّن واحدًا أو أكثر من هذه الثوابت
وأرسِل هذه الحِزمة إلى طريقة MediaDescription.Builder.setExtras()
.
يوضّح المقتطف التالي من الرمز البرمجي كيفية عرض مؤشرات لمحتوى وسائط فاضح قد اكتمل بنسبة% 70:
Kotlin
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Java
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
لعرض مؤشرات لعنصر وسائط يتم تشغيله حاليًا، يمكنك
تحديد قيم Long
لـ METADATA_KEY_IS_EXPLICIT
أو EXTRA_DOWNLOAD_STATUS
في MediaMetadataCompat
من mediaSession
. لا يمكنك عرض المؤشّرين
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
أو
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
في طريقة عرض التشغيل.
يوضّح مقتطف الرمز البرمجي التالي كيفية الإشارة إلى أنّ الأغنية الحالية في عرض التشغيل فاضحة وتم تنزيلها:
Kotlin
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
Java
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
تعديل شريط التقدم في طريقة العرض "التصفّح" أثناء تشغيل المحتوى
كما ذكرنا سابقًا، يمكنك استخدام الرمز DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
الإضافي لعرض شريط تقدم للمحتوى الذي تم تشغيله جزئيًا في ملفه الشخصي في ميزة
عرض التصفّح. ومع ذلك، إذا واصل المستخدم تشغيل المحتوى الذي تم تشغيله جزئيًا
من Android Auto أو نظام التشغيل Android Automotive، يصبح هذا المؤشر
غير دقيق مع مرور الوقت.
بالنسبة إلى Android Auto ونظام التشغيل Android Automotive،
للحفاظ على تحديث شريط التقدم، يمكنك تقديم معلومات إضافية في MediaMetadataCompat
وPlaybackStateCompat
لربط المحتوى الجاري بمواد الوسائط في عرض التصفّح. يجب استيفاء المتطلبات التالية لكي يحتوي
عنصر الوسائط على شريط تقدم يتم تعديله تلقائيًا:
- عند إنشائه، يجب أن يُرسِل
MediaItem
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
في البيانات الإضافية بقيمة تتراوح بين 0.0 و1.0، بما في ذلك. - يجب أن يُرسِل
MediaMetadataCompat
METADATA_KEY_MEDIA_ID
بقيمة سلسلة متساوية مع معرّف الوسائط المُرسَل إلىMediaItem
. - يجب أن يتضمّن العنصر
PlaybackStateCompat
عنصرًا إضافيًا يحتوي على المفتاحPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
الذي يتم ربطه بقيمة سلسلة تساوي معرّف الوسائط المرسَل إلى العنصرMediaItem
.
يوضّح المقتطف التالي من الرمز كيفية الإشارة إلى أنّ المحتوى الذي يتم تشغيله حاليًا مرتبط بمحتوى في طريقة العرض "التصفّح":
Kotlin
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
Java
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
عرض نتائج بحث قابلة للتصفّح
يمكن أن يقدّم تطبيقك نتائج بحث سياقية تظهر للمستخدمين عند بدء طلب بحث. يعرض نظاما التشغيل Android Auto وAndroid Automotive هذه النتائج من خلال واجهات طلبات البحث أو من خلال ميزات تستند إلى طلبات البحث التي تم إجراؤها في وقت سابق من الجلسة. لمعرفة المزيد من المعلومات، يُرجى الاطّلاع على القسم إتاحة المهام الصوتية في هذا الدليل.
لعرض نتائج بحث قابلة للتصفّح، أدرِج المفتاح الثابت
BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
في حِزمة الإضافات لطريقة onGetRoot()
في خدمتك، مع الربط بالقيمة المنطقية true
.
يوضّح المقتطف البرمجي التالي كيفية تفعيل الميزة في الإجراء onGetRoot()
:
Kotlin
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Java
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
لبدء عرض نتائج البحث، يمكنك إلغاء طريقة onSearch()
في خدمة متصفّح الوسائط. يعيد نظاما التشغيل Android Auto وAndroid Automotive توجيه عبارات بحث المستخدم إلى هذه الطريقة كلما استدعى المستخدم واجهة طلب بحث أو عنصر تحكم "نتائج البحث".
يمكنك تنظيم نتائج البحث
من طريقة onSearch()
في خدمتك باستخدام عناصر العناوين
لجعلها أكثر قابلية للتصفّح. على سبيل المثال، إذا كان تطبيقك يشغّل الموسيقى، يمكنك
تنظيم نتائج البحث حسب الألبوم والفنان والأغاني.
يعرض مقتطف الرمز البرمجي التالي تنفيذًا بسيطًا للإجراء onSearch()
:
Kotlin
fun onSearch(query: String, extras: Bundle) { // Detach from results to unblock the caller (if a search is expensive). result.detach() object:AsyncTask() { internal var searchResponse:ArrayList internal var succeeded = false protected fun doInBackground(vararg params:Void):Void { searchResponse = ArrayList() if (doSearch(query, extras, searchResponse)) { succeeded = true } return null } protected fun onPostExecute(param:Void) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse) } else { // This invokes onError() on the search callback. result.sendResult(null) } return null } }.execute() } // Populates resultsToFill with search results. Returns true on success or false on error. private fun doSearch( query: String, extras: Bundle, resultsToFill: ArrayList ): Boolean { // Implement this method. }
Java
@Override public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<MediaItem> searchResponse; boolean succeeded = false; @Override protected Void doInBackground(Void... params) { searchResponse = new ArrayList<MediaItem>(); if (doSearch(query, extras, searchResponse)) { succeeded = true; } return null; } @Override protected void onPostExecute(Void param) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse); } else { // This invokes onError() on the search callback. result.sendResult(null); } } }.execute() } /** Populates resultsToFill with search results. Returns true on success or false on error. */ private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) { // Implement this method. }
إجراءات التصفّح المخصّصة
تتيح لك "إجراءات التصفّح المخصّصة" إضافة رموز و تصنيفات مخصّصة إلى عناصر
MediaItem
تطبيقك في تطبيق الوسائط بالسيارة، ومعالجة تفاعلات المستخدمين مع
هذه الإجراءات. يتيح لك ذلك توسيع وظائف تطبيق الوسائط بطرق مختلفة، مثل إضافة الإجراءات "تنزيل" أو "إضافة إلى "قائمة المحتوى التالي" أو "تشغيل الراديو" أو "إضافة إلى المفضّلة" أو "إزالة".
إذا كانت هناك إجراءات مخصّصة أكثر من تلك التي يسمح المصنّع الأصلي للجهاز بعرضها، سيتم عرض قائمة تضم خيارات إضافية للمستخدم.
كيف تعمل هذه الميزة؟
يتم تحديد كل إجراء تصفّح مخصّص باستخدام:
- معرّف الإجراء (معرّف سلسلة فريد)
- تصنيف الإجراء (النص المعروض للمستخدم)
- معرّف موارد منتظم لرمز الإجراء (متّجه قابل للرسم يمكن تلوينه)
يمكنك تحديد قائمة بإجراءات التصفّح المخصّصة على مستوى الموقع الإلكتروني كجزء من
BrowseRoot
. يمكنك بعد ذلك إرفاق مجموعة فرعية من هذه الإجراءات بمنتجات فردية.
MediaItem.
عندما يتفاعل مستخدم مع إجراء تصفّح مخصّص، يتلقّى تطبيقك طلب استدعاء
في onCustomAction()
. يمكنك بعد ذلك معالجة الإجراء وتعديل قائمة
الإجراءات الخاصة بالMediaItem
إذا لزم الأمر. ويُعدّ ذلك مفيدًا للإجراءات التي تتضمّن حالة،
مثل "مفضّل" و "تنزيل". بالنسبة إلى الإجراءات التي لا تحتاج إلى تعديل، مثل "تشغيل
الراديو"، لن تحتاج إلى تعديل قائمة الإجراءات.
يمكنك أيضًا إرفاق "إجراءات التصفّح المخصّصة" بأحد جذور عقد التصفّح. سيتم عرض هذه الإجراءات في شريط أدوات ثانوي أسفل شريط الأدوات الرئيسي.
كيفية تنفيذ إجراءات التصفّح المخصّصة
في ما يلي خطوات إضافة "إجراءات التصفّح المخصّصة" إلى مشروعك:
- يمكنك إلغاء طريقتَين في عملية تنفيذ
MediaBrowserServiceCompat
: - تحليل حدود الإجراءات أثناء التشغيل:
- في
onGetRoot()
، احصل على الحد الأقصى لعدد الإجراءات المسموح بها لكلMediaItem
باستخدام المفتاحBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
فيrootHints
Bundle
. يشير الحدّ الأدنى 0 إلى أنّ الميزة غير متاحة في النظام.
- في
- أنشئ القائمة الشاملة لإجراءات التصفّح المخصّصة:
- لكل إجراء، أنشئ عنصر
Bundle
باستخدام المفاتيح التالية: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: معرّف الإجراء *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: تصنيف الإجراء *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: عنوان URL لرمز الإجراء * أضِف جميع عناصرBundle
الإجراءات إلى قائمة.
- لكل إجراء، أنشئ عنصر
- أضِف القائمة الشاملة إلى
BrowseRoot
:- في
BrowseRoot
الإضافاتBundle
، أضِف قائمة الإجراءات على هيئةParcelable
Arraylist
باستخدام المفتاحBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- في
- أضِف إجراءات إلى عناصر
MediaItem
:- يمكنك إضافة إجراءات إلى عناصر
MediaItem
فردية من خلال تضمين قائمة معرّفات الإجراءات في الإضافاتMediaDescriptionCompat
باستخدام المفتاحDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. يجب أن تكون هذه القائمة مجموعة فرعية من القائمة الشاملة للإجراءات التي حدّدتها فيBrowseRoot
.
- يمكنك إضافة إجراءات إلى عناصر
- معالجة الإجراءات وعرض مستوى التقدّم أو النتائج:
- في
onCustomAction
، يمكنك معالجة الإجراء استنادًا إلى رقم تعريف الإجراء وأي بيانات أخرى تحتاج إليها. يمكنك الحصول على رقم تعريفMediaItem
الذي أدى إلى تنفيذ الإجراء من الإضافات باستخدام المفتاحEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
.. - يمكنك تعديل قائمة الإجراءات الخاصة بـ
MediaItem
عن طريق تضمين المفتاحEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
في حِزمة التقدّم أو النتائج.
- في
في ما يلي بعض التغييرات التي يمكنك إجراؤها في BrowserServiceCompat
للبدء
باستخدام "إجراءات التصفّح المخصّصة".
إلغاء BrowserServiceCompat
عليك إلغاء الطرق التالية في MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
الحدّ الأقصى لعدد الإجراءات التي يتمّ تحليلها
عليك التحقّق من عدد إجراءات التصفّح المخصّصة المتاحة.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
إنشاء إجراء تصفّح مخصّص
يجب تجميع كل إجراء في Bundle
منفصل.
- معرّف الإجراء
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- تصنيف الإجراء
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- عنوان URL لرمز الإجراء
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
إضافة إجراءات تصفّح مخصّصة إلى Parceable
ArrayList
أضِف جميع عناصر Bundle
"إجراء التصفّح المخصّص" إلى ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
إضافة قائمة إجراءات التصفّح المخصّصة إلى جذر التصفّح
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
إضافة إجراءات إلى MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
إنشاء نتيجة واحدة (onCustomAction
)
- تحليل mediaId من
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- بالنسبة إلى النتائج غير المتزامنة، عليك فصل النتيجة.
result.detach()
- إنشاء حزمة النتائج
- رسالة إلى المستخدم
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- تعديل العنصر(يُستخدَم لتعديل الإجراءات في عنصر)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- افتح "عرض التشغيل".
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- تعديل عقدة التصفّح
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- رسالة إلى المستخدم
- إذا حدث خطأ، يُرجى الاتصال على
result.sendError(resultBundle).
. - إذا كان هناك معلومات جديدة بشأن مستوى التقدّم، يُرجى الاتصال على
result.sendProgressUpdate(resultBundle)
. - يمكنك إنهاء المحادثة من خلال الاتصال بالرقم
result.sendResult(resultBundle)
.
تعديل حالة الإجراء
باستخدام الطريقة result.sendProgressUpdate(resultBundle)
مع المفتاح
EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
، يمكنك تعديل MediaItem
ليعكس الحالة الجديدة للإجراء. يتيح لك ذلك تقديم ملاحظات في الوقت الفعلي للمستخدم حول مستوى التقدّم ونتيجة الإجراء الذي اتّخذه.
مثال: إجراء التنزيل
في ما يلي مثال على كيفية استخدام هذه الميزة لتنفيذ إجراء تنزيل بثلاث حالات:
- تنزيل: هذه هي الحالة الأولية للإجراء. عندما يختار المستخدم
هذا الإجراء، يمكنك استبداله بـ "جارٍ التنزيل" واستدعاء
sendProgressUpdate
لتعديل واجهة المستخدم. - يتم التنزيل: تشير هذه الحالة إلى أنّ عملية التنزيل قيد التقدّم. يمكنك استخدام هذه الحالة لعرض شريط تقدّم أو مؤشر آخر للمستخدم.
- تم تنزيله: تشير هذه الحالة إلى اكتمال عملية التنزيل. عند اكتمال عملية التنزيل، يمكنك استبدال "جارٍ التنزيل" بـ "تم التنزيل" واستدعاء
sendResult
باستخدام المفتاحEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
للإشارة إلى أنّه يجب إعادة تحميل العنصر. بالإضافة إلى ذلك، يمكنك استخدام مفتاحEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
لعرض رسالة نجاح للمستخدم.
يتيح لك هذا النهج تقديم ملاحظات واضحة للمستخدم حول عملية التحميل وحالته الحالية. يمكنك إضافة المزيد من التفاصيل باستخدام الرموز لعرض حالات التنزيل عند اكتمال 25% أو 50% أو 75%.
مثال: الإجراء المفضّل
مثال آخر هو إجراء مفضّل يتضمّن حالتَين:
- مفضّل: يتم عرض هذا الإجراء للعناصر التي ليست في قائمة المفضّلات لدى
المستخدم. عندما يختار المستخدم هذا الإجراء، يمكنك تبديله
بـ "المفضّلة" واستدعاء
sendResult
باستخدام مفتاحEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
لتعديل واجهة المستخدم. - مُفضّلة: يتم عرض هذا الإجراء للعناصر المدرَجة في قائمة
المفضّلات لدى المستخدم. عندما يختار المستخدم هذا الإجراء، يمكنك تبديله برمز
"العنصر المفضّل" واستدعاء
sendResult
باستخدام مفتاحEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
لتعديل واجهة المستخدم.
يقدّم هذا الأسلوب طريقة واضحة ومتسقة للمستخدمين لإدارة العناصر المفضّلة لديهم.
توضِّح هذه الأمثلة مرونة "إجراءات التصفّح المخصّصة" وكيفية استخدامها لتنفيذ مجموعة متنوعة من الوظائف مع الحصول على ملاحظات في الوقت الفعلي لتوفير تجربة محسّنة للمستخدم في تطبيق الوسائط بالسيارة.
للاطّلاع على مثال كامل لتنفيذ هذه الميزة، يمكنك الرجوع إلى project
TestMediaApp
.
تفعيل عناصر التحكّم في التشغيل
يرسل Android Auto ونظام التشغيل Android Automotive أوامر التحكّم في التشغيل من خلال
MediaSessionCompat
في خدمتك.
يجب تسجيل جلسة وتنفيذ طرق الاستدعاء المرتبطة بها.
تسجيل جلسة وسائط
في طريقة onCreate()
لخدمة متصفّح الوسائط، أنشئ MediaSessionCompat
،
ثم سجِّل جلسة الوسائط من خلال الاتصال بـ setSessionToken()
.
يوضّح المقتطف البرمجي التالي كيفية إنشاء جلسة وسائط وتسجيلها:
Kotlin
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. setCallback(MyMediaSessionCallback()) } sessionToken = session.sessionToken ... }
Java
public void onCreate() { super.onCreate(); ... // Start a new MediaSession. MediaSessionCompat session = new MediaSessionCompat(this, "session tag"); setSessionToken(session.getSessionToken()); // Set a callback object that implements MediaSession.Callback // to handle play control requests. session.setCallback(new MyMediaSessionCallback()); ... }
عند إنشاء عنصر جلسة الوسائط، يمكنك ضبط عنصر طلب استدعاء يتم استخدامه
لمعالجة طلبات التحكّم في التشغيل. يمكنك إنشاء عنصر معاودة الاتصال هذا من خلال
توفير تنفيذ لفئة MediaSessionCompat.Callback
لتطبيقك. ويتناول القسم التالي كيفية تنفيذ هذا العنصر.
تنفيذ أوامر التشغيل
عندما يطلب مستخدم تشغيل عنصر وسائط من تطبيقك، يستخدم نظام التشغيل Android Automotive
وAndroid Auto فئة MediaSessionCompat.Callback
من عنصر MediaSessionCompat
في تطبيقك الذي حصلوا عليه من خدمة متصفّح الوسائط في تطبيقك. عندما يريد مستخدم
التحكّم في تشغيل المحتوى، مثل إيقاف التشغيل مؤقتًا أو الانتقال إلى الأغنية التالية، يستدعي Android Auto ونظام التشغيل Android Automotive إحدى طُرق عنصر callback.
لتشغيل المحتوى، يجب أن ينشئ تطبيقك فئة MediaSessionCompat.Callback
مجردة وأن ينفذ الطرق التي يتوافق معها.
نفِّذ جميع طرق الاستدعاء التالية المناسبة لنوع المحتوى الذي يوفّره تطبيقك:
onPrepare()
- يتمّ استدعاؤه عند تغيير مصدر الوسائط. يستدعي نظام التشغيل Android Automotive هذه الطريقة أيضًا بعد التشغيل مباشرةً. يجب أن ينفذ تطبيق الوسائط هذه الطريقة.
onPlay()
- يتم تشغيله إذا اختار المستخدم تشغيل المحتوى بدون اختيار عنصر معيّن. يجب أن يشغِّل
تطبيقك المحتوى التلقائي، أو أن يستأنف المحتوى في حال توقّف مؤقتًا باستخدام الرمز
onPause()
.ملاحظة: يجب ألّا يبدأ تطبيقك تشغيل الموسيقى تلقائيًا عند اتصال نظام التشغيل Android Automotive أو Android Auto بخدمة متصفح الوسائط. لمزيد من المعلومات، يُرجى الاطّلاع على القسم المتعلق بموضوع ضبط حالة التشغيل الأولية.
onPlayFromMediaId()
- يتمّ تشغيله عندما يختار المستخدِم تشغيل عنصر معيّن. يتم تمرير الطريقة مع رقم التعريف الذي حدّدته خدمة متصفّح الوسائط لعنصر الوسائط في التسلسل الهرمي للمحتوى.
onPlayFromSearch()
- يتم تشغيله عندما يختار المستخدم تشغيل المحتوى من طلب بحث. يجب أن يُجري التطبيق اختيارًا مناسبًا استنادًا إلى سلسلة البحث التي تم تمريرها.
onPause()
- يتم تشغيله عندما يختار المستخدم إيقاف التشغيل مؤقتًا.
onSkipToNext()
- يتمّ تشغيله عندما يختار المستخدِم التخطّي إلى العنصر التالي.
onSkipToPrevious()
- يتمّ استدعاؤه عندما يختار المستخدِم التخطّي إلى العنصر السابق.
onStop()
- يتم تشغيله عندما يختار المستخدم إيقاف التشغيل.
يمكنك إلغاء هذه الطرق في تطبيقك لتوفير أي وظيفة مطلوبة. ليس عليك تنفيذ طريقة إذا لم تكن وظائفها متاحة في تطبيقك. مثلاً، إذا كان تطبيقك يشغّل بثًا مباشرًا، مثل بث رياضة، ليس عليك تنفيذ طريقة onSkipToNext()
. يمكنك استخدام التنفيذ التلقائي
لـ onSkipToNext()
بدلاً من ذلك.
لا يحتاج تطبيقك إلى أي منطق خاص لتشغيل المحتوى من خلال مكبّرات صوت السيارة. عندما يتلقّى تطبيقك طلبًا لتشغيل محتوى، يمكنه تشغيل الصوت بالطريقة نفسها التي يشغّل بها المحتوى من خلال مكبّرات صوت الهاتف أو سمّاعات المستخدم. يُرسِل Android Auto ونظام التشغيل Android Automotive المحتوى الصوتي تلقائيًا إلى نظام السيارة لتشغيله على مكبّرات صوت السيارة.
لمزيد من المعلومات عن تشغيل المحتوى الصوتي، اطّلِع على نظرة عامة على MediaPlayer ونظرة عامة على تطبيق الصوت ونظرة عامة على ExoPlayer.
ضبط إجراءات التشغيل العادية
يعرض Android Auto ونظام التشغيل Android Automotive عناصر التحكّم في التشغيل استنادًا إلى
الإجراءات المفعّلة في عنصر PlaybackStateCompat
.
يجب أن يتيح تطبيقك الإجراءات التالية تلقائيًا:
يمكن أن يتيح تطبيقك أيضًا الإجراءات التالية إذا كانت ذات صلة بمحتوى التطبيق:
بالإضافة إلى ذلك، يمكنك إنشاء قائمة تشغيل يمكن عرضها
للمستخدم، ولكن هذا الخيار غير مطلوب. لإجراء ذلك، استخدِم الطريقتَين setQueue()
وsetQueueTitle()
، وفعِّل الإجراء ACTION_SKIP_TO_QUEUE_ITEM
، وحدِّد طلب إعادة الاتصال onSkipToQueueItem()
.
أضِف أيضًا رمز المحتوى الذي يتم تشغيله الآن، وهو مؤشر على المحتوى الذي يتم
تشغيله حاليًا. لإجراء ذلك، استخدِم الطريقة setActiveQueueItemId()
وقدِّم رقم تعريف المحتوى الذي يتم تشغيله حاليًا في "قائمة المحتوى التالي". يجب تعديل setActiveQueueItemId()
عند حدوث تغيير في قائمة الانتظار.
يعرض Android Auto ونظام التشغيل Android Automotive أزرارًا لكل إجراء مفعَّل، بالإضافة إلى قائمة المحتوى التالي. عند النقر على الأزرار، يستدعي النظام طلب معاودة الاتصال المقابل من
MediaSessionCompat.Callback
.
حجز مساحة غير مستخدَمة
يحتجز Android Auto ونظام التشغيل Android Automotive مساحة في واجهة المستخدم لإجراءات
ACTION_SKIP_TO_PREVIOUS
وACTION_SKIP_TO_NEXT
. إذا كان تطبيقك
لا يتيح استخدام إحدى هاتين الدالتَين، يستخدم Android Auto ونظام التشغيل Android Automotive
المساحة لعرض أي إجراءات مخصّصة تنشئها.
إذا كنت لا تريد ملء هذه المساحات بإجراءات مخصّصة، يمكنك حجز
هذه المساحات لكي يترك نظاما التشغيل Android Auto وAndroid Automotive المساحة فارغة
عندما لا يتيح تطبيقك الوظيفة المقابلة. لإجراء ذلك، يمكنك استدعاء setExtras()
باستخدام حِزمة إضافات تحتوي على ثوابت تتوافق مع الدوال المُخصّصة.
يتوافق الرمز SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
مع الرمز ACTION_SKIP_TO_NEXT
، ويتوافق الرمز
SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
مع الرمز ACTION_SKIP_TO_PREVIOUS
. استخدِم هذه الثوابت كمفاتيح في
الحزمة، واستخدِم القيمة المنطقية true
لقيم هذه الثوابت.
ضبط PlaybackState الأولي
عندما يتواصل Android Auto ونظام التشغيل Android Automotive مع خدمة متصفّح الوسائط، تُرسِل جلسة الوسائط حالة تشغيل المحتوى باستخدام PlaybackStateCompat
.
من المفترض ألا يبدأ تطبيقك تشغيل الموسيقى تلقائيًا عند اتصال نظام التشغيل Android Automotive
أو Android Auto بخدمة متصفّح الوسائط. بدلاً من ذلك، يمكنك الاعتماد على Android
Auto ونظام التشغيل Android Automotive لإعادة تشغيل المحتوى أو بدؤه استنادًا إلى حالة
السيارة أو إجراءات المستخدم.
ولإجراء ذلك، اضبط القيمة الأولية لـ PlaybackStateCompat
لجلسة الوسائط على
STATE_STOPPED
أو
STATE_PAUSED
أو
STATE_NONE
أو
STATE_ERROR
.
لا تستمر جلسات الوسائط في Android Auto ونظام التشغيل Android Automotive إلا مدّة الرحلة، لذا يبدأ المستخدمون هذه الجلسات ويوقفونها بشكل متكرّر. لتوفير تجربة سلسة بين محركات الأقراص، يمكنك تتبُّع حالة جلسة المستخدم السابقة، بحيث يتمكّن المستخدم من مواصلة المحتوى الذي كان يستمع إليه تلقائيًا عندما يتلقّى تطبيق الوسائط طلبًا لإعادة البدء، مثل المحتوى الذي تم تشغيله آخر مرة وPlaybackStateCompat
والقائمة.
إضافة إجراءات تشغيل مخصّصة
يمكنك إضافة إجراءات تشغيل مخصّصة لعرض إجراءات إضافية يتيحها
تطبيق الوسائط. إذا كانت المساحة تسمح بذلك (وليست محجوزة)، يضيف Android
الإجراءات المخصّصة إلى عناصر التحكّم في النقل. وإلّا، يتم عرض الإجراءات المخصّصة
في القائمة الكاملة. يتم عرض الإجراءات المخصّصة بالترتيب الذي تتم فيه إضافتها إلى PlaybackStateCompat
.
استخدِم الإجراءات المخصّصة لتقديم سلوك مختلف عن الإجراءات العادية. لا تستخدِمها لاستبدال الخطوات المعتادة أو تكرارها.
يمكنك إضافة إجراءات مخصّصة باستخدام الطريقة addCustomAction()
في فئة PlaybackStateCompat.Builder
.
يوضّح المقتطف التالي من الرمز كيفية إضافة إجراء مخصّص "بدء قناة إذاعية":
Kotlin
val customActionExtras = Bundle() customActionExtras.putInt( androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, androidx.media3.session.CommandButton.ICON_RADIO) stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon // or R.drawable.media3_icon_radio ).run { setExtras(customActionExtras) build() } )
Java
Bundle customActionExtras = new Bundle(); customActionExtras.putInt( androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, androidx.media3.session.CommandButton.ICON_RADIO); stateBuilder.addCustomAction( new PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon) // or R.drawable.media3_icon_radio .setExtras(customActionExtras) .build());
للحصول على مثال أكثر تفصيلاً لهذه الطريقة، اطّلِع على طريقة setCustomAction()
في نموذج تطبيق Universal Android Music Player على GitHub.
بعد إنشاء الإجراء المخصّص، يمكن لجلسة الوسائط الاستجابة للإجراء
من خلال إلغاء طريقة onCustomAction()
.
يوضّح مقتطف الرمز البرمجي التالي كيفية استجابة تطبيقك لحدث "بدء قناة راديو":
Kotlin
override fun onCustomAction(action: String, extras: Bundle?) { when(action) { CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> { ... } } }
Java
@Override public void onCustomAction(@NonNull String action, Bundle extras) { if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) { ... } }
للحصول على مثال أكثر تفصيلاً لهذه الطريقة، يُرجى الاطّلاع على طريقة onCustomAction
في نموذج تطبيق Universal Android Music Player على GitHub.
رموز الإجراءات المخصّصة
يتطلّب كل إجراء مخصّص تنشئه رمزًا.
إذا كان وصف هذا الرمز يتطابق مع أحد الثوابت CommandButton.ICON_
، يجب ضبط هذه القيمة الصحيحة لمفتاح EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT
في
ملحقات الإجراء المخصّص. في الأنظمة المتوافقة، سيؤدي ذلك إلى إلغاء رمز
المورد الذي تم تمريره إلى CustomAction.Builder
، ما يسمح لمكوّنات النظام بعرض الإجراء
وإجراءات التشغيل الأخرى بنمط متسق.
يجب أيضًا تحديد مورد رمز. يمكن تشغيل التطبيقات في السيارات على العديد من أحجام الشاشات وكثافتها المختلفة، لذا يجب أن تكون الرموز التي تقدّمها رسومًا قابلة للاستخدام في ملفات رسومات متجاوبة. تتيح لك الرسومات المتجهّة إمكانية تغيير حجم مواد العرض بدون فقدان التفاصيل. يسهِّل استخدام ملف قابل للدمج باستخدام متّجهات أيضًا محاذاة الحواف والأركان مع حدود وحدات البكسل عند استخدام دقة متناهية الصغر.
إذا كان الإجراء المخصّص يعتمد على الحالة، على سبيل المثال، يشغّل إعدادًا للتشغيل أو يوقفه، يجب توفير رموز مختلفة للحالات المختلفة، حتى يتمكّن المستخدمون من ملاحظة التغيير عند اختيار الإجراء.
توفير أنماط رموز بديلة للإجراءات غير المفعّلة
عندما لا يكون الإجراء المخصّص متاحًا للسياق الحالي، استبدِل رمز الإجراء المخصّص برمز بديل يشير إلى أنّ الإجراء مُعطل.
الإشارة إلى تنسيق الصوت
للإشارة إلى أنّ الوسائط التي يتم تشغيلها حاليًا تستخدم تنسيقًا صوتيًا خاصًا،
يمكنك تحديد الرموز التي يتم عرضها في السيارات المزوّدة بهذه الميزة. يمكنك
ضبط
KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
و
KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
في حِزمة الإضافات الخاصة بعنصر الوسائط الذي يتم تشغيله حاليًا (يتم تمريرها إلى
MediaSession.setMetadata()
). احرص على ضبط كلا
الإضافات، لاستيعاب التنسيقات المختلفة.
بالإضافة إلى ذلك، يمكنك ضبط الإعداد الإضافي KEY_IMMERSIVE_AUDIO
لإعلام المصنّعين الأصليّين للسيارات بأنّ هذا محتوى صوتي محيطي، ويجب أن يكونوا حذرين جدًا عند اتخاذ قرار بشأن تطبيق تأثيرات صوتية قد تتداخل مع المحتوى المحيطي.
إضافة روابط من المحتوى الذي يتم تشغيله حاليًا
يمكنك ضبط ملف الوسائط الذي يتم تشغيله حاليًا بحيث يكون الترجمة والشرح أو الوصف أو كليهما روابط تؤدي إلى ملفات وسائط أخرى. يتيح ذلك للمستخدم الانتقال بسرعة إلى العناصر ذات الصلة، على سبيل المثال، يمكنه الانتقال إلى أغانٍ أخرى للفنان نفسه، أو حلقات أخرى من البودكاست هذا، وما إلى ذلك. إذا كانت السيارة تتيح هذه الميزة، يمكن للمستخدمين النقر على الرابط للانتقال إلى هذا المحتوى.
لإضافة روابط، عليك ضبط البيانات الوصفية
KEY_SUBTITLE_LINK_MEDIA_ID
(لإنشاء رابط من الترجمة) أو
KEY_DESCRIPTION_LINK_MEDIA_ID
(لإنشاء رابط من
الوصف). للاطّلاع على التفاصيل، يُرجى الاطّلاع على المستندات المرجعية لحقول metadata
هذه.
إتاحة الإجراءات الصوتية
يجب أن يتيح تطبيق الوسائط الإجراءات الصوتية للمساعدة في توفير تجربة آمنة ومريحة للسائقين تقلّل من مصادر التشتيت. على سبيل المثال، إذا كان تطبيقك يشغّل ملف وسائط واحدًا، يمكن للمستخدم قول"تشغيل [عنوان الأغنية]" لطلب تشغيل أغنية مختلفة من تطبيقك بدون النظر إلى شاشة السيارة أو لمسها. يمكن للمستخدمين بدء طلبات البحث من خلال النقر على الأزرار المناسبة في عجلة القيادة أو قول الكلمات الرئيسية "Ok Google".
.عندما يرصد Android Auto أو نظام التشغيل Android Automotive أحد
الإجراءات الصوتية ويفسّره، يتم إرسال هذا الإجراء الصوتي إلى التطبيق من خلال
onPlayFromSearch()
.
عند تلقّي هذا الطلب، يعثر التطبيق على محتوى يتطابق مع سلسلة query
ويبدأ تشغيله.
يمكن للمستخدمين تحديد فئات مختلفة من العبارات في طلب البحث: النوع أو الفنان أو
الألبوم أو اسم الأغنية أو محطة الراديو أو قائمة التشغيل، وغيرها. عند إنشاء
دعم للبحث، يجب مراعاة جميع الفئات المناسبة لتطبيقك.
إذا رصد Android Auto أو نظام التشغيل Android Automotive أنّ طلب بحث معيّنًا يندرج ضمن
فئات معيّنة، يتم إلحاق عناصر إضافية في المَعلمة extras
. يمكن إرسال المحتوى التالي:
يجب مراعاة سلسلة query
الفارغة التي يمكن إرسالها من قِبل
Android Auto أو نظام التشغيل Android Automotive إذا لم يحدّد المستخدم عبارات البحث.
على سبيل المثال، إذا قال المستخدم "تشغيل بعض الموسيقى". في هذه الحالة، قد يختار تطبيقك
بدء تشغيل مقطع صوتي تم تشغيله مؤخرًا أو تم اقتراحه حديثًا.
إذا تعذّر معالجة طلب بحث بسرعة، لا تحظر المحتوى في onPlayFromSearch()
.
بدلاً من ذلك، اضبط حالة التشغيل على STATE_CONNECTING
وأدخِل طلب البحث في سلسلة محادثات غير متزامنة.
بعد بدء التشغيل، ننصحك بملء قائمة المحتوى التالي في جلسة الوسائط بمحتوى مرتبط. على سبيل المثال، إذا طلب المستخدم تشغيل ألبوم، قد يملأ تطبيقك قائمة المحتوى التالي بقائمة أغانٍ الألبوم. ننصحك أيضًا بتوفير نتائج بحث قابلة للتصفّح حتى يتمكّن المستخدم من اختيار مقطع صوتي مختلف يتطابق مع طلب البحث.
بالإضافة إلى طلبات البحث التي تتضمّن كلمة "تشغيل"، يتعرّف Android Auto ونظام التشغيل Android Automotive
على طلبات البحث الصوتية للتحكّم في التشغيل، مثل "أوقِف الموسيقى مؤقتًا" و "الأغنية التالية
"، ويطابق هذه الأوامر مع طلبات الاستدعاء المناسبة لجلسة الوسائط،
مثل onPause()
وonSkipToNext()
.
للحصول على مثال مفصّل عن كيفية تنفيذ إجراءات التشغيل المفعَّلة بالصوت في تطبيقك، يُرجى الاطّلاع على مقالة "مساعد Google" وتطبيقات الوسائط.
تنفيذ تدابير الوقاية من التشتيت
بما أنّ هاتف المستخدم متصل بمكبّرات صوت سيارته أثناء استخدام Android Auto، عليك اتّخاذ احتياطات إضافية للمساعدة في منع تشتيت انتباه السائق.
إيقاف المنبّهات في السيارة
يجب ألا تبدأ تطبيقات الوسائط في Android Auto تشغيل الصوت من خلال مكبّرات صوت السيارة ما لم يبدأ المستخدم التشغيل من خلال الضغط على زر التشغيل مثلاً. حتى المنبّه الذي يضبطه المستخدم من تطبيق الوسائط يجب ألا يبدأ بتشغيل الموسيقى من خلال مكبّرات صوت السيارة.
لاستيفاء هذا الشرط، يمكن لتطبيقك استخدام CarConnection
كإشارة قبل تشغيل أي صوت. يمكن لتطبيقك التحقّق مما إذا كان الهاتف يُعرِض المحتوى على شاشة السيارة من خلال رصد LiveData
لنوع اتصال السيارة
والتحقّق مما إذا كان يساوي
CONNECTION_TYPE_PROJECTION
.
إذا كان هاتف المستخدم يعرض محتوى، يجب أن تُجري تطبيقات الوسائط المتوافقة مع المنبّهات أحد الإجراءين التاليين:
- أوقِف المنبّه.
- تشغيل المنبّه على
STREAM_ALARM
وتوفير واجهة مستخدم على شاشة الهاتف لإيقاف المنبّه
التعامل مع إعلانات الوسائط
يعرض Android Auto تلقائيًا إشعارًا عند تغيير البيانات الوصفية للوسائط
أثناء جلسة تشغيل الصوت. عندما ينتقل تطبيق وسائط من تشغيل الموسيقى
إلى عرض إعلان، يُعدّ عرض
إشعار للمستخدم أمرًا مشتتًا للانتباه. لمنع Android Auto من عرض إشعار
في هذه الحالة، يجب ضبط مفتاح البيانات الوصفية للوسائط
METADATA_KEY_IS_ADVERTISEMENT
على
METADATA_VALUE_ATTRIBUTE_PRESENT
،
كما هو موضّح في المقتطف التالي من الرمز:
Kotlin
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
Java
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
التعامل مع الأخطاء العامة
عندما يواجه التطبيق خطأ، اضبط حالة التشغيل على STATE_ERROR
وقدِّم رسالة خطأ باستخدام الطريقة setErrorMessage()
. اطّلِع على PlaybackStateCompat
للحصول على قائمة برموز الخطأ التي يمكنك استخدامها عند ضبط رسالة الخطأ.
يجب أن تكون رسائل الخطأ موجّهة للمستخدم وأن تكون مترجَمة باستخدام لغة العميل
الحالية. بعد ذلك، يمكن لنظامَي التشغيل Android Auto وAndroid Automotive عرض رسالة الخطأ
للمستخدم.
على سبيل المثال، إذا لم يكن المحتوى متاحًا في المنطقة الحالية للمستخدم، يمكنك
استخدام رمز الخطأ ERROR_CODE_NOT_AVAILABLE_IN_REGION
عند ضبط رسالة الخطأ.
Kotlin
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
Java
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
لمزيد من المعلومات عن حالات الخطأ، يُرجى الاطّلاع على مقالة استخدام جلسة وسائط: الحالات والأخطاء.
إذا كان مستخدم Android Auto بحاجة إلى فتح تطبيق الهاتف لحلّ خطأ، قدِّم هذه المعلومات للمستخدم في رسالتك. على سبيل المثال، قد تظهر رسالة الخطأ التالية: "تسجيل الدخول إلى [اسم تطبيقك]" بدلاً من "يُرجى تسجيل الدخول".