إنشاء تطبيقات وسائط للسيارات

يساعدك Android Auto ونظام التشغيل Android Automotive في نقل محتوى تطبيق الوسائط إلى للمستخدمين في سيارتهم. يجب أن يوفّر تطبيق الوسائط للسيارات خدمة متصفّح الوسائط حتى تعمل Android Auto وAndroid Automotive أو أي تطبيق آخر به وسائط المتصفح اكتشاف المحتوى الخاص بك وعرضه.

يجب أن يتوفّر لديك تطبيق وسائط يشغّل صوتًا على وأن تطبيق الوسائط يتوافق مع تطبيق الوسائط على Android الهندسة المعمارية.

يصف هذا الدليل المكوّنات المطلوبة لـ MediaBrowserService MediaSession الذي يحتاجه تطبيقك للعمل على Android Auto أو Android Automotive OS. بعد الانتهاء من إنشاء البنية الأساسية للوسائط، يمكنك إضافة دعم لتطبيق Android Auto وإضافة دعم نظام التشغيل Android Automotive إلى الوسائط الخاصة بك التطبيق.

قبل البدء

  1. يمكنك مراجعة مستندات واجهة برمجة تطبيقات وسائط Android.
  2. راجِع إنشاء تطبيقات الوسائط. للحصول على إرشادات التصميم.
  3. راجِع المصطلحات والمفاهيم الرئيسية الواردة في هذا القسم.

المصطلحات والمفاهيم الرئيسية

خدمة متصفح الوسائط
خدمة Android يُنفّذها تطبيق الوسائط وتتوافق مع MediaBrowserServiceCompat واجهة برمجة التطبيقات. ويستخدم تطبيقك هذه الخدمة لكشف محتواها.
متصفح الوسائط
واجهة برمجة تطبيقات تستخدمها تطبيقات الوسائط لاكتشاف خدمات متصفِّح الوسائط وعرضها المحتوى الخاص بهم. يستخدِم Android Auto وAndroid Automotive متصفِّح الوسائط من أجل: العثور على خدمة متصفِّح الوسائط في تطبيقك
عنصر وسائط

ينظّم متصفّح الوسائط المحتوى في شجرة MediaItem. الأخرى. يمكن أن يحتوي عنصر الوسائط على إحدى العلامتين التاليتين أو كلتيهما:

  • FLAG_PLAYABLE: يشير إلى أن العنصر هو ورقة شجر في شجرة المحتوى. يمثّل العنصر بثًا صوتيًا واحدًا، مثل أغنية من ألبوم. فصل في كتاب مسموع أو حلقة من بودكاست.
  • FLAG_BROWSABLE: يشير إلى أن العنصر هو عقدة في شجرة المحتوى لديه أطفال. على سبيل المثال، يمثل العنصر ألبومًا، و عناصره الثانوية للأغاني في الألبوم.

يعمل عنصر الوسائط القابل للتصفح والتشغيل كقائمة تشغيل. يمكنك تحديد العنصر نفسه لتشغيل كل عناصره الثانوية، أو يمكنك تصفُّح الأطفال.

محسَّنة للمركبات

يشير هذا المصطلح إلى نشاط لتطبيق يعمل بنظام التشغيل Android Automotive ويلتزم بما يلي: إرشادات التصميم لنظام التشغيل Android Automotive لا يتم رسم واجهة هذه الأنشطة بواسطة نظام التشغيل Android Automotive، لذا أن يضمن التزام تطبيقك بإرشادات التصميم. وعادةً ما تكون هذه تتضمن أهداف نقر وأحجام أكبر للخطوط، ودعم الوضع النهاري والليلي ونسب تباين أعلى.

لا يُسمح بعرض واجهات المستخدم المحسّنة للمركبة إلا عند تفعيل السيارة يُرجى العِلم أنّ قيود تجربة المستخدم (CUXRs) ليست سارية، لأنّها يمكن أن تتطلب الواجهات اهتمامًا أو تفاعلاً طويلاً من المستخدم. باحثو تجربة المستخدم لا تسري عند توقّف السيارة أو ركنها ولكنها تكون دائمًا سارية. عندما تكون السيارة وهي تتحرك.

لا تحتاج إلى تصميم الأنشطة لتطبيق Android Auto، لأن Android Auto ترسم واجهتها الخاصة المحسّنة للمركبة باستخدام المعلومات من متصفح الوسائط.

ضبط ملفات البيان لتطبيقك

قبل أن تتمكن من إنشاء خدمة متصفح الوسائط، يجب ضبط ملفات بيان التطبيق.

تعريف خدمة متصفح الوسائط

يمكن ربط كل من نظامَي التشغيل Android Auto وAndroid Automotive بتطبيقك من خلال متصفح الوسائط لتصفح ملفات الوسائط. الإفصاح عن الوسائط خدمة متصفّح في ملف البيان للسماح بإضافة Android Auto ونظام التشغيل Android Automotive. اكتشاف الخدمة وربطها بتطبيقك

يعرض مقتطف الرمز التالي كيفية الإشارة إلى خدمة متصفِّح الوسائط في البيان. قم بتضمين هذا الرمز في ملف البيان الخاص وحدة نظام التشغيل Android Automotive وفي ملف البيان الخاص بتطبيق الهاتف

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

رمز تحديد المصدر

الشكل 1. رمز تحديد المصدر على بطاقة الوسائط

يُستخدم رمز تحديد المصدر في المواضع التي تكون فيها الأولوية لمحتوى الوسائط مثل بطاقات الوسائط. يمكنك إعادة استخدام الرمز الصغير المستخدَم للإشعارات. يجب أن يكون هذا الرمز أحادي اللون. يمكنك تحديد رمز يستخدم لتمثيل تطبيقك باستخدام بيان البيان التالي:

<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. تفاعَل تلقائيًا مع خدمة متصفِّح الوسائط أثناء سير عمل المستخدم العادي.

  1. يشغِّل المستخدم تطبيقك على نظام التشغيل Android Automotive أو Android Auto.
  2. يتصل نظام التشغيل Android Automotive أو Android Auto بمتصفّح الوسائط في تطبيقك. خدمة باستخدام onCreate() . في ما يخص تنفيذ onCreate() عليك إنشاء وتسجيل MediaSessionCompat وكائن رد الاتصال التابع له.
  3. يتصل نظام التشغيل Android Automotive أو Android Auto بخدمة onGetRoot(). للحصول على عنصر الوسائط الجذر في التسلسل الهرمي للمحتوى. عنصر الوسائط الجذر لا يتم عرضه؛ بدلاً من ذلك، يتم استخدامه لاسترداد المزيد من المحتوى من تطبيقك.
  4. يعمل نظام التشغيل Android Automotive أو Android Auto على الاتصال بمقدّمي الخدمة. onLoadChildren() للحصول على العناصر الثانوية لعنصر الوسائط الجذر. نظام التشغيل Android Automotive ويعرض Android Auto عناصر الوسائط هذه في أعلى مستوى. عرض يمكنك تنظيم القائمة الجذر في هذه الصفحة للحصول على مزيد من المعلومات. معلومات حول ما يتوقعه النظام في هذا المستوى.
  5. إذا اختار المستخدم عنصر وسائط قابلاً للتصفح، سيتم حساب onLoadChildren() مرة أخرى لاسترداد العناصر الثانوية لعنصر القائمة المحدد.
  6. إذا اختار المستخدم ملف وسائط قابلاً للتشغيل، سواء كان نظام التشغيل Android Automotive أو Android. يتم استدعاء الطريقة المناسبة لمعاودة الاتصال لجلسة الوسائط لتنفيذ هذا الإجراء.
  7. يمكن للمستخدم أيضًا البحث في المحتوى الخاص بك إذا كان تطبيقك يتيح استخدامه. في هذه الدورة، حافظة 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.

تنظيم القائمة الجذر

الشكل 2. محتوى الجذر المعروض في شكل علامات تبويب تنقُّل

تخضع Android Auto وAndroid Automotive لقيود محدَّدة بشأن بنية القائمة الجذر. يتم إرسال هذه المعلومات إلى MediaBrowserService. من خلال تلميحات الجذر، والتي يمكن قراءتها من خلال الوسيطة Bundle التي يتم تمريرها إلى onGetRoot() يتيح اتباع هذه التلميحات للنظام عرض محتوى الجذر بشكل مثالي. كعلامات تبويب للتنقل. إذا لم تتّبع هذه التلميحات، يمكن أن يكون بعض المحتوى الجذر حذفها أو تقليل فرص العثور عليها من قِبل النظام تم إرسال نصيحتين:

استخدِم الرمز التالي لقراءة تلميحات الجذر ذات الصلة:

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. على سبيل المثال، إذا كنت تعرض عادةً عنصرًا جذريًا قابلاً للتشغيل، قد تحتاج إلى دمجه. ضمن عنصر جذر قابل للتصفح بدلاً من ذلك بسبب قيمة العلامات المتوافقة تلميح.

وبالإضافة إلى التلميحات الجذرية، هناك بعض الإرشادات الإضافية التي يجب اتّباعها للمساعدة في ضمان عرض علامات التبويب على النحو الأمثل:

  • يجب توفير رموز أحادية اللون، ويفضل أن تكون بيضاء، لكل عنصر من عناصر علامة التبويب.
  • أضِف تصنيفات قصيرة ومفيدة لكل عنصر من عناصر علامة التبويب. إبقاء التصنيفات قصيرة إلى تقليل فرصة اقتطاع السلاسل.

عرض أعمال فنية للوسائط

يجب تمرير الأعمال الفنية لعناصر الوسائط كمعرّف الموارد المنتظم (URI) المحلي باستخدام ContentResolver.SCHEME_CONTENT أو ContentResolver.SCHEME_ANDROID_RESOURCE يجب أن يتحول عنوان URI المحلي هذا إلى صورة نقطية أو متجه قابل للرسم في الموارد الخاصة بالتطبيق. لـ MediaDescriptionCompat عنصرًا يمثل العناصر في التسلسل الهرمي للمحتوى، مرِّر معرف الموارد المنتظم (URI) خلال setIconUri(). بالنسبة إلى عناصر MediaMetadataCompat التي تمثل العنصر قيد التشغيل حاليًا، مرِّر معرّف الموارد المنتظم (URI) من خلال putString()، باستخدام أي من المفاتيح التالية:

توضح الخطوات التالية كيفية تنزيل الأعمال الفنية من معرف موارد منتظم (URI) على الويب وإظهار من خلال عنوان URI محلي. للحصول على مثال أكثر اكتمالاً، انظر عملية التنفيذ openFile() والطرق المحيطة بها في Universal Android Music نموذج تطبيق المشغّل

  1. أنشِئ معرِّف موارد منتظم (URI) لـ content:// يتوافق مع معرِّف الموارد المنتظم (URI) على الويب. متصفح الوسائط تمرر الخدمة وجلسة الوسائط معرف الموارد المنتظم (URI) هذا للمحتوى إلى 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();
    }
    
  2. أثناء تنفيذ ContentProvider.openFile()، تحقق مما إذا كان الملف لعنوان URI المقابل. إذا لم يكن الأمر كذلك، نزِّل ملف الصورة واحتفظ به في ذاكرة التخزين المؤقت. تشير رسالة الأشكال البيانية يستخدم مقتطف الرمز التالي ميزة التمرير.

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

لمزيد من التفاصيل حول موفّري المحتوى، راجِع مقالة إنشاء محتوى Google Cloud Platform.

تطبيق أنماط المحتوى

بعد إنشاء التسلسل الهرمي للمحتوى باستخدام عناصر قابلة للتصفح أو التشغيل، يمكنك يمكنك تطبيق أنماط المحتوى التي تحدد كيفية عرض هذه العناصر في السيارة.

يمكنك استخدام أنماط المحتوى التالية:

عناصر القائمة

يمنح نمط المحتوى هذا الأولوية للعناوين والبيانات الوصفية على الصور.

عناصر الشبكة

يمنح هذا النمط للمحتوى الأولوية للصور على العناوين والبيانات الوصفية.

ضبط أنماط المحتوى التلقائية

يمكنك ضبط الإعدادات التلقائية العامة لكيفية عرض عناصر الوسائط من خلال تضمين ثوابت معيّنة في حزمة BrowserRoot الإضافية من الخدمة onGetRoot() . اقرأ هذه الحزمة وابحث عن Android Auto وAndroid Automotive. هذه الثوابت لتحديد النمط المناسب.

يمكن استخدام الميزات الإضافية التالية كمفاتيح في الحزمة:

يمكن تعيين المفاتيح إلى القيم الثابتة التالية للأعداد الصحيحة للتأثير في وطريقة عرض هذه العناصر:

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: يتم تقديم العناصر المتجاوبة كعناصر قائمة.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: يتم تقديم العناصر المتجاوبة كعناصر شبكة.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: يتم تقديم العناصر المقابلة على أنها "category" عناصر القائمة. وهي مماثلة لعناصر القائمة العادية باستثناء أنه يتم تطبيق الهوامش حول العناصر الأيقونات، نظرًا لأن الأيقونات تبدو أفضل عندما تكون صغيرة. الرموز أن يكون شكلاً متّجهًا قابل للرسم قابل للرسم. يُتوقَّع تقديم هذا التلميح فقط. للعناصر القابلة للتصفح.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: يتم تقديم العناصر المقابلة على أنها "category" عناصر الشبكة. وهي تمامًا مثل عناصر الشبكة العادية، باستثناء أنه يتم تطبيق الهوامش حول العناصر الأيقونات، نظرًا لأن الأيقونات تبدو أفضل عندما تكون صغيرة. الرموز أن يكون شكلاً متّجهًا قابل للرسم قابل للرسم. يُتوقَّع تقديم هذا التلميح فقط. للعناصر القابلة للتصفح.

يوضح مقتطف الرمز التالي كيفية تعيين نمط المحتوى الافتراضي العناصر القابلة للتصفّح إلى الشبكات والعناصر القابلة للتشغيل إلى القوائم:

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 باستخدام مجموعة فرعية. عنوان "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*/);
}

يجب أن يمرر تطبيقك جميع عناصر الوسائط التي تريد تجميعها معًا متجاورة. على سبيل المثال، لنفترض أنك تريد عرض مجموعتين من ملفات الوسائط، "الأغاني" و"الألبومات" بهذا الترتيب، ويمرر تطبيقك خمسة ملفات وسائط بالترتيب التالي:

  1. عنصر الوسائط "أ" مع "extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")"
  2. عنصر الوسائط "ب" مع "extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")"
  3. عنصر الوسائط C مع extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. عنصر الوسائط D مع extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  5. عنصر الوسائط E مع extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

نظرًا لأن الوسائط الخاصة بـ "الأغاني" المجموعة و"الألبومات" لا يتم الاحتفاظ بالمجموعة معًا في مربّعات متجاورة، Android Auto وAndroid Automotive يفسر هذا على أنه المجموعات الأربع التالية:

  • المجموعة 1 باسم "الأغاني" تحتوي على عنصر الوسائط A
  • المجموعة 2 التي تحمل اسم "الألبومات" يحتوي على عنصر الوسائط B
  • المجموعة 3 التي تحمل اسم "الأغاني" يحتوي على ملفَي الوسائط "ج" و"د"
  • المجموعة 4 التي تحمل اسم "الألبومات" يحتوي على عنصر الوسائط E

لعرض هذه العناصر في مجموعتين، يجب أن يمرر تطبيقك عناصر الوسائط في بالترتيب التالي بدلاً من ذلك:

  1. عنصر الوسائط "أ" مع "extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")"
  2. عنصر الوسائط C مع extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  3. عنصر الوسائط D مع extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. عنصر الوسائط "ب" مع "extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")"
  5. عنصر الوسائط E مع extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

عرض مؤشرات إضافية للبيانات الوصفية

يمكنك تضمين مؤشرات إضافية للبيانات الوصفية لتقديم لمحة سريعة معلومات عن المحتوى في شجرة متصفح الوسائط وأثناء التشغيل. ضمن شجرة التصفّح، Android Auto وAndroid Automotive، قراءة الميزات الإضافية المرتبطة باستخدام عنصر والبحث عن ثوابت معينة لتحديد المؤشرات التي العرض. أثناء تشغيل الوسائط، يقرأ Android Auto ونظام التشغيل Android Automotive بيانات التعريف لجلسة الوسائط والبحث عن ثوابت معينة لتحديد التي سيتم عرضها.

الشكل 3. عرض التشغيل مع بيانات وصفية تحدد الأغنية والفنان بالإضافة إلى رمز يشير إلى المحتوى الفاضح.

الشكل 4. تصفّح طريقة العرض التي تتضمّن نقطة للمحتوى الذي لم يتم تشغيله العنصر الأول وشريط التقدم للمحتوى الذي يتم تشغيله جزئيًا على العنصر الثاني.

يمكن استخدام الثوابت التالية في كل من إضافات الوصف MediaItem MediaMetadata مزايا إضافية:

  • EXTRA_DOWNLOAD_STATUS: يشير إلى حالة تنزيل العنصر. استخدم هذا الثابت باعتباره المفتاح؛ الثوابت الطويلة التالية هي القيم المحتملة:
  • METADATA_KEY_IS_EXPLICIT: يشير إلى ما إذا كان العنصر يتضمّن محتوى فاضحًا. للإشارة إلى أن عنصر ما صريحًا، استخدم هذا الثابت كمفتاح ولون METADATA_VALUE_ATTRIBUTE_PRESENT كقيمة.

يمكن استخدام الثوابت التالية فقط في إضافات الوصف MediaItem:

لعرض المؤشرات التي تظهر أثناء تصفح المستخدم لتصفح الوسائط شجرة، قم بإنشاء حزمة إضافية تشتمل على واحد أو أكثر من هذه الثوابت نقل هذه الحزمة إلى الطريقة 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 لربط المحتوى الحالي الوسائط في طريقة العرض "تصفح". يجب استيفاء المتطلبات التالية ملف وسائط ليظهر لك شريط تقدم يتم تحديثه تلقائيًا:

يوضح مقتطف الرمز التالي كيفية الإشارة إلى أنّ العنصر الذي يتم تشغيله حاليًا يرتبط بعنصر في طريقة عرض التصفح:

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

الشكل 5. عرض التشغيل مع خيار "نتائج البحث" لـ عرض ملفات الوسائط ذات الصلة بالبحث الصوتي الذي يجريه المستخدم

يمكن لتطبيقك تقديم نتائج بحث سياقية يتم عرضها للمستخدمين عند يبدؤون استعلام بحث. عرض 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.
}

إجراءات التصفّح المخصّصة

إجراء تصفُّح مخصَّص واحد.

الشكل 6. إجراء تصفّح مخصّص واحد

تتيح لك إجراءات التصفّح المخصّصة إضافة رموز وتصنيفات مخصّصة إلى MediaItem عناصر في تطبيق الوسائط في السيارة وتعالج تفاعلات المستخدمين مع هذه الإجراءات. يتيح لك هذا توسيع وظائف تطبيق الوسائط في بعدة طرق، مثل إضافة "تنزيل" و"الإضافة إلى قائمة الانتظار" و"تشغيل الراديو" "وضع في المفضلة" أو "إزالة" مناسبة.

قائمة كاملة لإجراءات التصفّح المخصّصة

الشكل 7. القائمة الكاملة لإجراء التصفح المخصّص

إذا كان هناك عدد من الإجراءات المخصصة أكثر مما يسمح به المصنّع الأصلي للجهاز، سيتم عرض القائمة الكاملة للمستخدم.

كيف تعمل هذه الميزة؟

يتمّ تحديد كل إجراء تصفّح مخصّص بما يلي:

  • رقم تعريف إجراء (معرّف سلسلة فريد)
  • تصنيف إجراء (النص المعروض للمستخدم)
  • معرف موارد منتظم (URI) لرمز الإجراء (متّجه قابل للرسم يمكن تلوينه)

يمكنك تحديد قائمة من إجراءات التصفّح المخصّصة عالميًا كجزء من BrowseRoot ثم يمكنك إرفاق مجموعة فرعية من هذه الإجراءات بالإجراءات MediaItem.

عندما يتفاعل مستخدِم مع إجراء تصفّح مخصّص، يتلقّى تطبيقك معاودة الاتصال. في onCustomAction(). يمكنك بعد ذلك التعامل مع الإجراء وتعديل قائمة الإجراءات الخاصة بـ MediaItem إذا لزم الأمر. هذا مفيد للإجراءات ذات الحالة مثل "المفضلة" و"تنزيل". لتنفيذ إجراءات لا تتطلّب تعديلاً، مثلاً "تشغيل الراديو"، لا تحتاج إلى تحديث قائمة الإجراءات.

إجراءات التصفّح المخصّصة في جذر عقدة التصفّح

الشكل 8. شريط أدوات إجراءات التصفّح المخصّصة

ويمكنك أيضًا إرفاق إجراءات التصفّح المخصّصة بجذر عقدة التصفّح. هذه الإجراءات سيتم عرضه في شريط أدوات ثانوي ضمن شريط الأدوات الرئيسي.

كيفية تنفيذ إجراءات التصفّح المخصّصة

في ما يلي خطوات إضافة إجراءات التصفّح المخصّصة إلى مشروعك:

  1. تجاوز طريقتين في MediaBrowserServiceCompat التنفيذ:
  2. تحليل حدود الإجراءات في وقت التشغيل:
    • في onGetRoot()، يمكنك الحصول على أقصى عدد مسموح به من الإجراءات لكل إجراء. MediaItem باستخدام المفتاح BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT في rootHints Bundle. يشير الحد 0 إلى أن العنصر لا يدعمها النظام.
  3. إنشاء قائمة عامة بإجراءات الاستعراض المخصصة:
    • لكل إجراء، أنشِئ كائن Bundle باستخدام المفاتيح التالية: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: معرّف الإجراء * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: تصنيف الإجراء * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: رمز الإجراء عنوان URI * إضافة جميع كائنات Bundle للإجراء إلى قائمة
  4. إضافة القائمة العامة إلى BrowseRoot:
  5. إضافة إجراءات إلى عناصر MediaItem:
    • يمكنك إضافة إجراءات إلى كائنات MediaItem الفردية من خلال تضمين قائمة بمعرّفات الإجراءات في إضافات MediaDescriptionCompat باستخدام المفتاح DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST يجب أن تكون هذه القائمة مجموعة فرعية من قائمة الإجراءات العامة التي حددتها في BrowseRoot.
  6. التعامل مع الإجراءات وعرض مستوى التقدم أو النتائج:
    • في 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>")
    
  • معرّف الموارد المنتظم (URI) لرمز الإجراء
    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"

  • تحليل معرّف الوسائط من 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 ليعكس الحالة الجديدة للإجراء. هذا النمط تتيح لك تقديم ملاحظات في الوقت الفعلي للمستخدم حول التقدم نتيجة لعمله.

مثال: إجراء التنزيل

في ما يلي مثال على كيفية استخدام هذه الميزة لتنفيذ إجراء تنزيل مع ثلاث حالات:

  1. التنزيل: هذه هي الحالة الأولية للإجراء. عندما يختار المستخدم هذا الإجراء، يمكنك تبديله بالزر "تنزيل" والاتصال sendProgressUpdate لتحديث واجهة المستخدم.
  2. التنزيل: تشير هذه الحالة إلى أن التنزيل قيد التقدم. يمكنك استخدام هذه الحالة لإظهار شريط التقدّم أو مؤشر آخر للمستخدم
  3. تم التنزيل: تشير هذه الحالة إلى اكتمال عملية التنزيل. عندما انتهاء التنزيل، يمكنك تبديل "التنزيل" مع عبارة "تم التنزيل" والاتصال sendResult مع EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM للإشارة إلى أنه يجب إعادة تحميل العنصر. بالإضافة إلى ذلك، يمكنك استخدام الـ EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE لعرض رسالة نجاح للمستخدم.

تتيح لك هذه الطريقة تقديم ملاحظات واضحة للمستخدم حول التنزيل وحالتها الحالية. يمكنك إضافة المزيد من التفاصيل باستخدام الرموز لإظهارها حالات التنزيل 25% و50% و75%.

مثال: الإجراء المفضّل

مثال آخر هو الإجراء المفضل بحالتك التالية:

  1. المفضلة: يتم عرض هذا الإجراء للعناصر غير الموجودة في قائمة المفضلة لدى المستخدم. عندما يختار المستخدم هذا الإجراء، يمكنك تبديله. مع "المفضلة" والاتصال بـ sendResult EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM لتحديث واجهة المستخدم.
  2. المفضلة: يتم عرض هذا الإجراء للعناصر الموجودة في مجموعة قائمة القنوات المفضلة. عندما يحدد المستخدم هذا الإجراء، يمكنك تبديله بـ "الإضافة إلى التسجيلات المفضّلة" والاتصال بـ sendResult EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM لتحديث واجهة المستخدم.

ويوفر هذا النهج طريقة واضحة ومتسقة للمستخدمين لإدارة العناصر المفضلة.

توضّح هذه الأمثلة مرونة إجراءات التصفّح المخصّصة وكيف يمكنك وتستخدمها لتنفيذ مجموعة متنوعة من الوظائف مع ملاحظات في الوقت الفعلي تجربة مستخدم محسنة في تطبيق الوسائط في السيارة.

للاطّلاع على مثال كامل لتنفيذ هذه الميزة، يمكنك الرجوع إلى المقالة 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 أحد طلبات معاودة الاتصال. طرق الكائن.

للتعامل مع تشغيل المحتوى، يجب أن يوسِّع تطبيقك الملخّص 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 لقيمها.

ضبط حالة التشغيل الأولية

بما أنّه يتم ربط 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

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

Java

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .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.

رموز الإجراءات المخصّصة

يتطلب كل إجراء مخصص تنشئه موردًا للرموز. يمكن للتطبيقات في السيارات تعمل على العديد من أحجام الشاشات وكثافاتها، وبالتالي فإن الرموز التي توفر يجب أن تكون قابلة للرسم المتّجهات. حاسمة متجه قابل للرسم يتيح لك تغيير حجم الأصول دون فقدان التفاصيل. متجه تسهل محاذاة الحواف والزوايا مع حدود البكسل عند ودرجات الدقة الأصغر.

في حال كان الإجراء المخصّص مرتبطًا بالحالة، على سبيل المثال، يؤدي إلى تفعيل إعداد التشغيل أو إيقاف — توفير رموز مختلفة للحالات المختلفة، حتى يتمكن المستخدمون يمكنهم رؤية تغيير عند تحديد الإجراء.

توفير أنماط رموز بديلة للإجراءات التي تم إيقافها

عند عدم توفُّر إجراء مخصّص للسياق الحالي، بدِّل رمز الإجراء المخصّص مع رمز بديل يوضّح أنّ الإجراء معطل.

الشكل 6. نماذج لرموز إجراءات مخصّصة خارج النمط

الإشارة إلى تنسيق الصوت

للإشارة إلى أن الوسائط التي يتم تشغيلها حاليًا تستخدم تنسيقًا صوتيًا خاصًا، يمكنك تحديد الرموز التي يتم عرضها في السيارات التي تدعم هذه الميزة. إِنْتَ ضبط 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 (للربط من الوصف). لمزيد من التفاصيل، يمكنك مراجعة المستندات المرجعية لتلك المواقع وحقول بيانات التعريف.

دعم الإجراءات الصوتية

يجب أن يتيح تطبيق الوسائط استخدام الإجراءات الصوتية للمساعدة في توفير بيئة آمنة للسائقين وتجربة مريحة تقلل من مصادر التشتيت. على سبيل المثال، إذا كان تطبيقك يشغّل عنصر وسائط واحدًا، فيمكن للمستخدم قول "تشغيل [عنوان الأغنية]" لتوجيه التطبيق إلى تشغيل أغنية مختلفة بدون النظر إلى شاشة السيارة أو لمسها العرض. يمكن للمستخدمين بدء الاستعلامات من خلال النقر على الأزرار المناسبة على أو نطق الكلمات المفتاح "OK Google".

عندما يرصد نظام التشغيل Android Auto أو Android Automotive صوتًا ويفسّره. الإجراء، يتم تسليم الإجراء الصوتي إلى التطبيق من خلال onPlayFromSearch() عند تلقّي معاودة الاتصال هذه، يعثر التطبيق على محتوى يطابق query نصية ويبدأ التشغيل.

يمكن للمستخدمين تحديد فئات مختلفة من المصطلحات في استعلامهم: genre وartist ألبوم أو اسم أغنية أو محطة راديو أو قائمة تشغيل وغير ذلك عند إنشاء الدعم للبحث، مع مراعاة جميع الفئات التي تفيد تطبيقك. في حال رصدت Android Auto أو Android Automotive أن طلب بحث معيَّن يناسب فئات معينة، فإنها تُلحق إضافات في معلمة extras. تشير رسالة الأشكال البيانية يمكن إرسال المزايا الإضافية التالية:

حساب لسلسلة query فارغة يمكن إرسالها من خلال Android Auto أو Android Automotive إذا لم يحدِّد المستخدم عبارات بحث على سبيل المثال، إذا قال المستخدم "تشغيل بعض الموسيقى". في هذه الحالة، قد لا يكون تطبيقك اختيار بدء مقطع صوتي تم تشغيله مؤخرًا أو اقتراحه مؤخرًا

إذا تعذّرت معالجة البحث بسرعة، لا تحظره في onPlayFromSearch(). بدلاً من ذلك، اضبط حالة التشغيل على STATE_CONNECTING وإجراء البحث على سلسلة محادثات غير متزامنة.

بعد بدء التشغيل، يمكنك ملء قائمة انتظار جلسة الوسائط بما يلي: المحتوى ذي الصلة. فعلى سبيل المثال، إذا طلب المستخدم تشغيل ألبوم، فإن أحد التطبيقات قائمة الانتظار بقائمة الأغاني الخاصة بالألبوم. ضع في اعتبارك أيضًا تنفيذ عرض نتائج البحث القابلة للتصفّح كي يتمكّن المستخدم من اختيار مسار مختلف يطابق استعلامهم.

بالإضافة إلى "play" طلبات البحث، Android Auto وAndroid Automotive التعرّف على طلبات البحث الصوتية للتحكّم في التشغيل، مثل "إيقاف الموسيقى مؤقتًا" و"التالي أغنية" ومطابقة هذه الأوامر مع الاستدعاءات المناسبة لجلسة الوسائط، مثل onPause() وonSkipToNext().

للاطّلاع على مثال مفصّل حول كيفية تنفيذ إجراءات التشغيل المستندة إلى الصوت في لتطبيقك، راجِع "مساعد Google" وتطبيقات الوسائط.

تنفيذ إجراءات الوقاية من تشتيت الانتباه

لأنّ هاتف المستخدم متصل بمكبرات الصوت في السيارة أثناء استخدام Android تلقائي، يجب اتخاذ احتياطات إضافية للمساعدة في منع تشتيت السائق.

إيقاف المنبّهات في السيارة

يجب ألّا تبدأ تطبيقات الوسائط في 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 إلى فتح تطبيق الهاتف لإصلاح خطأ، تقديم هذه المعلومات للمستخدم في رسالتك. على سبيل المثال، حدث الخطأ قد تظهر الرسالة "تسجيل الدخول إلى [اسم تطبيقك]" بدلاً من "يُرجى تسجيل الدخول".

مراجع أخرى