إنشاء تطبيق وسائط مستند إلى نموذج

تطبيقات الوسائط المستندة إلى نماذج في مرحلة تجريبية
في الوقت الحالي، يمكن لأي مستخدم نشر تطبيقات وسائط مستندة إلى نماذج في مسارات الاختبار الداخلي والاختبار المغلق على "متجر Google Play". سيُسمح بالنشر في المسارات المفتوحة ومسارات الإصدار العلني في تاريخ لاحق.

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

يفترض هذا الدليل أنّ لديك تطبيق وسائط يشغّل الصوت على الهاتف وأنّ تطبيق الوسائط يتوافق مع بنية تطبيقات الوسائط على Android. تتيح لك "مكتبة تطبيقات السيارات" إمكانية استبدال التجربة داخل التطبيق بنماذج بدلاً من تلك التي تم إنشاؤها باستخدام بنية البيانات إنشاء تطبيقات وسائط للسيارات MediaBrowser. سيظل عليك توفير MediaSession لعناصر التحكّم في التشغيل، وMediaBrowserService أو MediaLibraryService، والتي تُستخدم في الاقتراحات والتجارب الذكية الأخرى.

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

بالإضافة إلى الخطوات الموضّحة في مقالة استخدام مكتبة تطبيقات Android للسيارات، يجب استيفاء المتطلبات التالية في تطبيقات الوسائط المستندة إلى نماذج:

تحديد الفئات المتوافقة في البيان

يجب أن يحدّد تطبيقك androidx.car.app.category.MEDIA فئة تطبيق السيارة في فلتر الأهداف الخاص CarAppService.

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.MEDIA"/>
      </intent-filter>
    </service>
    ...
<application>

للوصول إلى MediaPlaybackTemplate، يجب أن يدرج تطبيقك أيضًا الإذن androidx.car.app.MEDIA_TEMPLATES في ملف البيان الخاص به:

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.MEDIA_TEMPLATES"/>
  ...
</manifest>

تحديد الحد الأدنى لمستوى واجهة برمجة التطبيقات لتطبيقات السيارات

لا تتوافق تطبيقات الوسائط التي تستخدم MediaPlaybackTemplate إلا مع الإصدار 8 من واجهة برمجة التطبيقات CAL والإصدارات الأحدث، لذا تأكَّد من ضبط الحد الأدنى Car App API level على 8.

<application ...>
  ...
  <meta-data
    android:name="androidx.car.app.minCarApiLevel"
    android:value="8"/>
  ...
</application>

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

احرِص على إضافة رمز تحديد المصدر لتطبيقات الوسائط التي تم إنشاؤها باستخدام Car App Library.

تحديد إمكانية استخدام التطبيق مع Android Auto

تأكَّد من تضمين ما يلي في بيان تطبيقك:

<application>
  ...
  <meta-data android:name="com.google.android.gms.car.application"
      android:resource="@xml/automotive_app_desc"/>
  ...
</application>

بعد ذلك، أضِف تعريف النموذج إلى automotive_app_desc.xml في موارد xml، ويجب أن يبدو على النحو التالي:

<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android">
 <uses name="media"/>
 <uses name="template"/>
</automotiveApp>

تحديد توافق التطبيق مع نظام التشغيل Android Automotive

يمكنك توزيع تطبيق وسائط متوافق مع "مكتبة تطبيقات السيارة" على نظام التشغيل Android Automotive بطريقتَين مختلفتَين: كحِزمة APK واحدة أو كحِزمتَي APK منفصلتَين. إذا وزّعت حِزمة APK واحدة، ستكون متوافقة مع المركبات التي تم تفعيل نظام التشغيل Android Automotive عليها باستخدام مضيف "مكتبة تطبيقات السيارة"، وسيتم الرجوع إلى تطبيق MediaBrowserService أو MediaLibraryService في حال عدم توفّره، حتى في إصدارات Android القديمة (من Android 10 إلى Android 13). إذا اخترت توزيع حِزمتَي APK منفصلتَين، يمكنك بسهولة أكبر تعديل الإضافات الجديدة إلى إصدار "مكتبة تطبيقات السيارة" بدون الخوف من التأثير في إصدار MediaBrowserService أو MediaLibraryService من تطبيقك.

توزيع حِزمة APK واحدة

عند توزيع حزمة APK واحدة لكل من "مكتبة تطبيقات السيارات" والإصدار MediaBrowserService أو الإصدار MediaLibraryService من تطبيقك، من المهم ضبط قيمة على android:required="false".

<uses-feature android:name="android.software.car.templates_host.media" android:required="false"/>

بعد ذلك، اتّبِع إرشادات "مكتبة تطبيقات السيارات" لنظام التشغيل Android Automotive ‏(AAOS)، وأضِف CarAppActivity قابلاً للتشغيل (أو نشاطًا مؤقتًا). يجب ضبط النشاط على android:enabled="false" في ملف البيان. بعد ذلك، أضِف علامة بيانات وصفية إلى بيان MediaBrowserService تشير إلى مكوِّن CarAppActivity كبديل. اطّلِع على نموذج ملف البيان أدناه:

<service android:name=".media.MyMediaService"
    android:exported="true"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
    </intent-filter>

    <!-- Link to Car App Library Activity -->
    <meta-data
        android:name="androidx.car.app.media.CalMediaActivityComponent" 
        android:value="com.example.mediaapp.LaunchableTrampoline"/>
</service>

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false"> <!-- Set to false -->

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

توزيع Play

يجب تفعيل حزمة APK التي تتضمّن Car App Library وMediaBrowserService أو MediaLibraryService باستخدام رمز إصدار أعلى وminSdk يستهدف الإصدار 14 من نظام التشغيل Android (المستوى 34).

التوزيع باستخدام حزمتَي APK

لتوزيع حزمتَي APK منفصلتَين، إحداهما تستخدم "مكتبة تطبيقات السيارات" والأخرى تستخدم الإصدار MediaBrowserService أو MediaLibraryService، اتّبِع الخطوات التالية لضمان استهداف إمكانات السيارة الصحيحة بشكل سليم.

عند إنشاء حزمة APK منفصلة لإصدار تطبيقك المتوافق مع "مكتبة تطبيقات السيارات"، يجب ضبط android.software.car.templates_host.media على android:required=true. ويضمن ذلك توزيع التطبيق فقط على إصدارات نظام التشغيل Android Automotive المعتمَدة والمتوافقة مع مضيف "مكتبة تطبيقات السيارات".

<uses-feature android:name="android.software.car.templates_host.media" android:required="true"/>

بالإضافة إلى استخدام android.software.car.templates_host.media وضبطه على android:required=true كما هو موضّح أعلاه، اتّبِع الخطوات التالية لتفعيل نظام التشغيل Android Automotive لنشر نشاط "مكتبة تطبيقات السيارات" القابل للتشغيل.

Play Distribution

يجب توزيع حِزمة APK التي تستخدم "مكتبة تطبيقات السيارات" في قناة الإصدار المحدود المخصّصة لنظام التشغيل Automotive OS.

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

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

إنشاء "نموذج التشغيل"

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

يعرض مشغّل موسيقى أغنية &quot;أصوات الربيع&quot; (Sounds of Spring) من تأليف &quot;سمر فيلدينغ&quot; (Summer Fielding) مع صورة مربّعة لامرأة تعزف على الغيتار.

الشكل 1: MediaPlaybackTemplate مع إجراء في العنوان لفتح قائمة الانتظار في أعلى الصفحة

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

val playbackTemplate = MediaPlaybackTemplate.Builder()
      .setHeader(
        Header.Builder()
          .setStartHeaderAction(Action.BACK)
          .addEndHeaderAction(
                Action.Builder()
                  .setTitle(model.context.getString(R.string.queue_button_title))
                  .setIcon(
                    CarIcon.Builder(
                        IconCompat.createWithResource(
                          model.context,
                          R.drawable.gs_queue_music_vd_theme_24,
                        ))
                      .build())
                  .setOnClickListener(showQueueScreen())
                  .build())
          .setTitle(model.context.getString(R.string.media_playback_view_title))
          .build())
      .build()

عند استخدام MediaPlaybackTemplate، عليك تسجيل رمز مميّز MediaSession باستخدام MediaPlaybackManager في CarAppService. وفي حال عدم إجراء ذلك، سيظهر خطأ عند إرسال MediaPlaybackTemplate إلى المضيف.

import androidx.car.app.media.MediaPlaybackManager


override fun onCreateSession(sessionInfo: SessionInfo): Session {
    return object : Session() {
        

        init {
          lifecycle.addObserver(
            LifecycleEventObserver { _, event ->
              if (event == ON_CREATE) {
                val token = ... // MediaSessionCompat.Token
                (carContext.getCarService(CarContext.MEDIA_PLAYBACK_SERVICE) as MediaPlaybackManager)
                  .registerMediaPlaybackToken(token)
              }
              ...
            }
          )
        }
    }
}

.registerMediaPlaybackToken ضروري لعرض معلومات وعناصر تحكّم تشغيل الوسائط في Android Auto، وهو مهم أيضًا لكي ينشئ المضيف إشعارات خاصة بالوسائط.

بالنسبة إلى التطبيقات التي تستخدم مكتبة Media3، والتي تستخدم PlatformToken بدلاً من MediaSessionCompat.Token العادي، عليك تنفيذ SessionCommand مخصّص في MediaLibrarySession.Callback يعرض الرمز المميز الأساسي للنظام الأساسي الخاص بالجلسة: session.platformToken. في CarAppService أرسِل هذا الأمر المخصّص إلى الجلسة. بعد تلقّي الرمز المميّز للمنصة، يمكنك تحويله باستخدام MediaSessionCompat.Token.fromToken(platformToken) وتمرير رمز التوافق هذا إلى Car App Library في .registerMediaPlaybackToken().

تنظيم الوسائط باستخدام النماذج

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

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

الشكل 2: SectionedItemTemplate يحتوي على RowSection متبوعًا بـ GridSection

استخدام SectionedItemTemplate داخل TabTemplate

إحدى الطرق المناسبة لتصنيف الوسائط داخل تطبيقك هي استخدام SectionedItemTemplate داخل TabTemplate.

val template =
      SectionedItemTemplate.Builder()...build();
val tabTemplate = 
      TabTemplate.Builder(tabCallback)
          .setTabContents(TabContents.Builder(template).build)
          .setHeaderAction(Action.APP_ICON)
          
          .build();

مكوّنات وميزات الإصدار 1.9 من "مكتبة تطبيقات السيارات"

توفّر الإصدار 1.9 من Car App Library API مكوّنات مخصّصة تتيح إمكانات تصفّح فريدة، مثل الشرائح وأشرطة التقدم والعناصر المختصرة والعناوين التفاعلية والموسّعة وأقسام "النتائج المميّزة" والبانرات.

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

الشكل 3: SectionedItemTemplate يحتوي على Chips وCondensed Items وInteractive Header وGrid Items وMinimized Control Panel

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

الشكل 4: شاشتا تصفّح وسائط تعرضان الرموز Expanded Header وSpotlight Sections وProgress Bars

لمزيد من التفاصيل حول كيفية تصميم واجهة مستخدم تطبيق الوسائط باستخدام هذه النماذج، يُرجى الاطّلاع على تطبيقات الوسائط.

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

إذا كنت تستخدم SectionedItemTemplate، يمكنك تحقيق ذلك من خلال إضافة زر إجراء ينقلك إلى شاشة تشغيل الوسائط. استخدِم الإجراء Action.MEDIA_PLAYBACK العادي في Car App Library. سيعرض تطبيق موسيقى هذا الإجراء على شكل لوحة تحكّم مصغّرة، وهو أمر ضروري لاستيفاء متطلبات الجودة MFT-1 إذا كنت تستخدم الإصدار 1.9 أو إصدارًا أحدث من Car App Library API. بالنسبة إلى النماذج الأخرى، يشكّل إجراء العنوان طريقة أخرى لتحقيق ذلك.

التعامل مع طلبات تشغيل الوسائط في النظام

يجب توجيه المستخدم إلى MediaPlaybackTemplate عند تشغيل تطبيق من سطح نظام يشغّل الوسائط، مثل بطاقة وسائط. ونشترط أن تتعامل تطبيقات الوسائط مع هذا Intent Action بالترتيب لتوفير تجربة سلسة للمستخدمين.

أضِف الإجراء androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK إلى intent-filter الخاص بمكوّن "مكتبة تطبيقات السيارة" (إما CarAppActivity أو الرمز Activity).

تأكَّد من أنّ نشاطك يستخدم launchMode من singleTask أو singleTop ليتم استدعاء onNewIntent().

<activity
    android:name=".LaunchableTrampoline"
    android:exported="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
    android:launchMode="singleTask"
    android:label="@string/app_name_cal"
    android:enabled="false">

    <meta-data android:name="distractionOptimized" android:value="true" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <action android:name="androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

في فئة Session، يمكنك إلغاء onNewIntent() لتحليل الغرض الوارد. إذا كان إجراء intent الوارد يتطابق مع SHOW_MEDIA_PLAYBACK، انتقِل بالمستخدم إلى شاشة &quot;يتم التشغيل الآن&quot;.

@Override
public void onNewIntent(@NonNull Intent intent) {
    super.onNewIntent(intent);
    if (SHOW_MEDIA_PLAYBACK.equals(intent.getAction())) {
        ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
        // Avoid redundant navigation if already on the playing screen
        if (screenManager.getTop() instanceof MyMediaPlayScreen) {
            return;
        }
        screenManager.push(MyMediaPlayScreen.createScreenFromPlaying(
                getCarContext(), mMediaSessionController));
    }
}

إذا كنت تستخدم نشاطًا مؤقتًا، تحقّق من intent action ضِمن onCreate(). مرِّر هذا الإجراء إلى intent إنشاء CarAppActivity قبل استدعاء finish().

public class LaunchableTrampoline extends AppCompatActivity {
    private static final String SHOW_MEDIA_PLAYBACK = "androidx.car.app.media.action.SHOW_MEDIA_PLAYBACK";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent receivedIntent = getIntent();
        String action;

        if (SHOW_MEDIA_PLAYBACK.equals(receivedIntent.getAction())) {
            action = SHOW_MEDIA_PLAYBACK;
        } else {
            action = Intent.ACTION_MAIN;
        }

        Intent intent = new Intent(action);
        intent.setClassName(getPackageName(), "androidx.car.app.activity.CarAppActivity");
        startActivity(intent);
        finish();
    }
}