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

ما زالت تطبيقات الوسائط المستندة إلى نماذج في مرحلة تجريبية
في الوقت الحالي، يمكن لأي مستخدم نشر تطبيقات الوسائط المستندة إلى نماذج في مسارات الاختبار الداخلي والاختبار المغلق على "متجر 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>

توفير رمز الإسناد

يجب إضافة رمز إسناد لتطبيقات الوسائط التي تم إنشاؤها باستخدام "مكتبة تطبيقات السيارات" .

الإفصاح عن دعم 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 و أضِف نشاطًا قابلاً للتشغيل 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 التي تتضمّن "مكتبة تطبيقات السيارات" وMediaBrowserService أو MediaLibraryService باستخدام رمز إصدار أعلى وminSdk الذي يستهدف Android 14 (34).

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

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

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

<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

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

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

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

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

الشكل 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 في "مكتبة تطبيقات السيارات". سيعرض تطبيق موسيقى هذا الإجراء كـ لوحة تحكّم مصغّرة، وهو أمر مطلوب لاستيفاء متطلبات الجودة MFT-1 إذا كنت تستخدم الإصدار 1.9 من واجهة برمجة التطبيقات "مكتبة تطبيقات السيارات" أو إصدارًا أحدث. بالنسبة إلى النماذج الأخرى، يُعدّ إجراء العنوان طريقة أخرى لتحقيق ذلك.

التعامل مع أهداف تشغيل الوسائط في النظام

يجب توجيه المستخدم إلى 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() لتحليل الهدف الوارد. إذا كان إجراء الهدف الوارد يطابق SHOW_MEDIA_PLAYBACK، انتقِل بالمستخدم إلى شاشة "يتم التشغيل الآن".

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

إذا كنت تستخدم نشاطًا مؤقتًا، تحقَّق من إجراء الهدف ضمن onCreate(). مرِّر هذا الإجراء إلى هدف إنشاء 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();
    }
}