פיתוח אפליקציות מדיה לרכבים

Android Auto ו-Android Automotive OS עוזרים להעביר את התוכן של אפליקציית המדיה אל המשתמשים במכונית שלהם. אפליקציית מדיה למכוניות צריכה לספק שירות של דפדפן מדיה כך ש-Android Auto ו-Android Automotive OS, או אפליקציה אחרת עם מדיה בדפדפן, יכולים לגלות ולהציג את התוכן שלך.

במדריך הזה יוצאים מנקודת הנחה שכבר יש לך אפליקציית מדיה שמפעילה אודיו הטלפון ושאפליקציית המדיה שלך תואמת לאפליקציית המדיה ל-Android. של הארכיטקטורה.

במדריך הזה מתוארים הרכיבים הנדרשים של MediaBrowserService MediaSession שהאפליקציה צריכה כדי לפעול ב-Android Auto או ב-Android Automotive OS. אחרי שתשלימו את תשתית הליבה של המדיה, תוכלו הוספת תמיכה ב-Android Auto והוספת תמיכה עבור Android Automotive OS למדיה אפליקציה.

לפני שמתחילים

  1. כדאי לעיין במסמכי התיעוד של Android Media API.
  2. מעיינים במאמר יצירת אפליקציות מדיה לקבלת הנחיות לעיצוב.
  3. קוראים את המונחים והמושגים המרכזיים שמפורטים בקטע הזה.

מונחי מפתח ומושגים מרכזיים

שירות של דפדפן מדיה
שירות Android שהוטמע על ידי אפליקציית המדיה שלך עומד בדרישות של MediaBrowserServiceCompat API. האפליקציה שלך משתמשת בשירות הזה כדי לחשוף את התוכן שלה.
דפדפן המדיה
ממשק API המשמש אפליקציות מדיה לגילוי שירותים ותצוגה של דפדפן מדיה את התוכן שלהם. Android Auto ו-Android Automotive OS משתמשים בדפדפן מדיה כדי למצוא את שירות דפדפן המדיה של האפליקציה.
פריט מדיה

התוכן בדפדפן מאורגן בעץ של MediaItem אובייקטים. פריט מדיה יכול לכלול אחד מהסימונים הבאים, או את שניהם:

  • FLAG_PLAYABLE: מציין שהפריט הוא עלה בעץ התוכן. הפריט מייצג זרם אודיו אחד, כמו שיר באלבום, פרק בספר אודיו או פרק של פודקאסט.
  • FLAG_BROWSABLE: מציין שהפריט הוא צומת בעץ התוכן יש ילדים. לדוגמה, פריט מייצג אלבום, והצאצאים שלו את השירים שבאלבום.

פריט מדיה שניתן לעיין בו וגם להפעיל אותו, פועל כמו פלייליסט. אפשר בחר את הפריט עצמו כדי להפעיל את כל הצאצאים שלו, או שתוכל לעיין לילדים.

מותאם לרכב

פעילות של אפליקציה ל-Android Automotive OS שעומדת בדרישות של הנחיות עיצוב ל-Android Automotive OS הממשק של הפעילויות האלה לא מצויר על ידי Android Automotive OS, כך חייבת להבטיח שהאפליקציה פועלת בהתאם להנחיות העיצוב. בדרך כלל, שכולל יעדי הקשה גדולים יותר וגופנים גדולים יותר, תמיכה במצבי יום ולילה וגם ביחסי ניגודיות גבוהים יותר.

אפשר להציג ממשקי משתמש שמותאמים לרכב רק במצב מכונית ההגבלות על חוויית המשתמש (CUXR) לא בתוקף כי ממשקים עשויים לדרוש תשומת לב נרחבת או אינטראקציה מצד המשתמשים. ספקי CUXR לא יהיו בתוקף כשהמכונית בעצירה או בחנייה, אבל תמיד פעילה כשהמכונית בתנועה.

אין צורך לעצב פעילויות עבור Android Auto, כי Android Auto שכותבת ממשק משלה שעבר אופטימיזציה לרכב באמצעות מידע שירות של דפדפן מדיה.

הגדרת קובצי המניפסט של האפליקציה

לפני שתוכל ליצור את שירות דפדפן המדיה, עליך להגדיר את את קובצי המניפסט של האפליקציה.

הצהרה על שירות דפדפן המדיה

גם Android Auto וגם Android Automotive OS מתחברים לאפליקציה דרך שירות דפדפן מדיה כדי לעיין בקובצי מדיה. להצהיר על זכויות יוצרים במדיה שירות דפדפן במניפסט כדי לאפשר ל-Android Auto ול-Android Automotive OS מגלים את השירות ומתחברים לאפליקציה.

קטע הקוד הבא מראה איך להצהיר (declare) על שירות דפדפן המדיה את המניפסט. צריך לכלול את הקוד הזה בקובץ המניפסט של המודול של Android Automotive OS ובקובץ המניפסט של האפליקציה לטלפון.

<application>
    ...
    <service android:name=".MyMediaBrowserService"
             android:exported="true">
        <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService"/>
        </intent-filter>
    </service>
    ...
</application>

ציון סמלים של אפליקציות

צריך לציין סמלי אפליקציות שאפשר להשתמש בהם ב-Android Auto וב-Android Automotive OS שמשמש לייצוג האפליקציה שלך בממשק המשתמש של המערכת. נדרשים שני סוגי סמלים:

  • סמל מרכז האפליקציות
  • סמל השיוך

סמל מרכז האפליקציות

סמל מרכז האפליקציות מייצג את האפליקציה בממשק המשתמש של המערכת, למשל במרכז האפליקציות ובמגש הסמלים. תוכל לציין שברצונך להשתמש בסמל מ- האפליקציה שלך לנייד כדי לייצג את אפליקציית המדיה לרכב באמצעות המניפסט הבא הצהרה:

<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 OS יוכלו להשתמש בשירות שלך לבצע את הפעולות הבאות:

  • צריך לעיין בהיררכיית התוכן של האפליקציה כדי להציג תפריט למשתמש.
  • קבלת האסימון ל-MediaSessionCompat של האפליקציה שלך כדי לשלוט בהפעלת האודיו.

תוכלו גם להשתמש בשירות דפדפן המדיה כדי לאפשר ללקוחות אחרים גישה לתוכן מדיה מהאפליקציה. לקוחות המדיה האלה עשויים להיות אפליקציות אחרות הטלפון של המשתמש, או שהם יכולים להיות לקוחות מרוחקים אחרים.

תהליך עבודה של שירות דפדפן מדיה

בקטע זה מתואר האופן שבו Android Automotive OS ו-Android אינטראקציה אוטומטית עם שירות דפדפן המדיה במהלך תהליך עבודה של משתמש אופייני.

  1. המשתמש מפעיל את האפליקציה ב-Android Automotive OS או ב-Android Auto.
  2. Android Automotive OS או Android Auto יוצרים קשר עם דפדפן המדיה באפליקציה השירות באמצעות onCreate() . בהטמעה של onCreate() צריך ליצור ולרשום MediaSessionCompat ואת אובייקט הקריאה החוזרת שלו.
  3. תתבצע שיחה מ-Android Automotive OS או מ-Android Auto ל-onGetRoot() של השירות כדי לקבל את פריט המדיה הבסיסי בהיררכיית התוכן. פריט המדיה הבסיסי לא מוצגת, במקום זאת, הוא משמש לאחזור תוכן נוסף מהאפליקציה.
  4. Android Automotive OS או Android Auto יתקשר לשירות onLoadChildren() כדי לקבל את הצאצאים של פריט המדיה הבסיסי. Android Automotive OS וגם פריטי המדיה האלה מוצגים ב-Android Auto כפריטי תוכן ברמה העליונה. צפייה לשינוי המבנה של תפריט הבסיס בדף הזה מידע על הציפיות של המערכת ברמה הזו.
  5. אם המשתמש בוחר בפריט מדיה שניתן לעיין בו, המאפיין של השירות שלך onLoadChildren() תופעל שוב כדי לאחזר את הצאצאים של האפשרות שנבחרה בתפריט.
  6. אם המשתמש בחר פריט מדיה שאפשר להפעיל, Android Automotive OS או Android קוראת אוטומטית לשיטת הקריאה החוזרת (callback) המתאימה של סשן המדיה כדי לבצע את הפעולה הזו.
  7. אם האפליקציה נתמכת, המשתמש יכול גם לחפש בתוכן שלכם. כאן כיסוי, מתקשרים ל-Android Automotive OS או ל-Android Auto onSearch() .

יצירה של היררכיית תוכן

Android Auto ו-Android Automotive OS יפעילו את שירות דפדפן המדיה של האפליקציה אל לבדוק איזה תוכן זמין. צריך ליישם שתי שיטות שירות של דפדפן מדיה שתומך בכך: onGetRoot() וגם onLoadChildren()

יישום ב-GetRoot

onGetRoot() של השירות מחזירה מידע על הצומת הבסיסי (root) של היררכיית התוכן. Android Auto ו-Android Automotive OS משתמשים בצומת הבסיס הזה כדי לבקש את שאר בתוכן שלך באמצעות 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() של השירות method, חבילת הקריאה מעבירה פרטים מזהים לשירות שלכם. שלך השירות יכול להשתמש במידע הזה כדי להחליט אם החבילה יכולה לגשת תוכן. לדוגמה, אפשר להגביל את הגישה לתוכן של האפליקציה לרשימה של חבילות שאושרו על ידי השוואה בין clientPackageName לרשימת ההיתרים שלך שמאמת את האישור ששימש לחתימה על ה-APK של החבילה. אם החבילה לא יכולה אימות, החזרת null כדי לדחות את הגישה לתוכן שלך.

כדי לספק אפליקציות מערכת, כמו Android Auto ו-Android Automotive OS, עם גישה לתוכן שלך, השירות חייב תמיד להחזיר ערך שאינו null BrowserRoot כשאפליקציות המערכת האלה קוראות ל-onGetRoot() . החתימה של אפליקציית המערכת של Android Automotive OS עשויה להשתנות בהתאם היצרן והדגם של הרכב, כך שצריך לאפשר את החיבורים מכל אפליקציות מערכת שתומכות ב-Android Automotive OS באופן יציב.

קטע הקוד הבא מראה איך השירות יכול לאמת חבילת השיחות היא אפליקציית מערכת:

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 Assistant להתחבר אל MediaBrowserService. שימו לב ש-Google Assistant כוללת שמות נפרדים של חבילות בטלפון, כולל Android Auto, וב-Android Automotive OS.

הטמעה של onLoadChildren()

אחרי שמקבלים את אובייקט הצומת של הרמה הבסיסית (root), מערכת Android Auto ו-Android Automotive OS כדי ליצור תפריט ברמה עליונה באמצעות קריאה ל-onLoadChildren() באובייקט הרמה הבסיסית (root) כדי לקבל את הצאצאים שלו. אפליקציות לקוח יוצרות תפריטי משנה לפי קריאה לאותה שיטה באמצעות אובייקטים של צומת צאצא.

כל צומת בהיררכיית התוכן מיוצג על ידי MediaBrowserCompat.MediaItem לאובייקט. כל אחד מפריטי המדיה האלה מזוהה באמצעות מחרוזת מזהה ייחודית. לקוח/ה אפליקציות מתייחסים למחרוזות המזהים האלה כאל אסימונים אטומים. כשאפליקציית לקוח רוצה לגלוש אל תפריט משנה או להפעיל פריט מדיה, הוא מעביר את האסימון. האפליקציה שלך אחראית לצורך שיוך האסימון לפריט המדיה המתאים.

קטע הקוד הבא מציג הטמעה פשוטה של onLoadChildren() method:

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 OS יש מגבלות ספציפיות לגבי של תפריט הבסיס. הפרטים האלה מועברים אל MediaBrowserService באמצעות רמזים בסיסיים (root), שניתן לקרוא אותם באמצעות הארגומנט Bundle שמועבר onGetRoot(). מעקב אחר הרמזים האלה מאפשר למערכת להציג את תוכן הבסיס באופן אופטימלי. ככרטיסיות ניווט. אם לא פועלים לפי הרמזים האלה, חלק מהתוכן הבסיסי (root) עלול או שהן יהיו פחות גלויות למערכת. נשלחים שני רמזים:

בקוד הבא אפשר לקרוא את הרמזים הרלוונטיים לגבי הרמה הבסיסית:

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 OS. לדוגמה, אם בדרך כלל מציגים פריט ברמה הבסיסית שניתן להפעיל, כדאי להציב אותו בתוך הטבלה מתחת לפריט ברמה הבסיסית שניתן לעיין בו, עקב הערך של הדגלים הנתמכים רמז.

חוץ מרמזים בסיסיים, יש עוד כמה הנחיות שצריך לפעול לפיהן כדי להבטיח שהכרטיסיות יעובדו בצורה אופטימלית:

  • יש לספק סמלים מונוכרומטיים, רצוי לבן, עבור כל פריט כרטיסייה.
  • כדאי להוסיף תוויות קצרות אבל בעלות משמעות לכל פריט כרטיסייה. שמירה על תוויות קצרות מפחית את הסיכוי לחיתוך המחרוזות.

הצגת גרפיקה של מדיה

יש להעביר גרפיקה של פריטי מדיה כ-URI מקומי באמצעות ContentResolver.SCHEME_CONTENT או ContentResolver.SCHEME_ANDROID_RESOURCE. ה-URI המקומי הזה חייב להתאים למפת סיביות (bitmap) או לשרטוט וקטורי במשאבים של האפליקציה. בשביל 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 המתאים. אם לא, מורידים את קובץ התמונה ושומרים אותו במטמון. בקטע הקוד הבא נעשה שימוש ב-Glide.

    Kotlin

    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
      val context = this.context ?: return null
      val file = File(context.cacheDir, uri.path)
      if (!file.exists()) {
        val remoteUri = Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.path)
            .build()
        val cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
    
        cacheFile.renameTo(file)
        file = cacheFile
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
    }
    

    Java

    @Nullable
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
      Context context = this.getContext();
      File file = new File(context.getCacheDir(), uri.getPath());
      if (!file.exists()) {
        Uri remoteUri = new Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.getPath())
            .build();
        File cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    
        cacheFile.renameTo(file);
        file = cacheFile;
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }
    

למידע נוסף על ספקי תוכן, עיינו במאמר יצירת תוכן ספק.

החלת סגנונות תוכן

אחרי שיוצרים את היררכיית התוכן באמצעות פריטים שניתנים לעיון או להפעלה, הוא יכול להחיל סגנונות תוכן שקובעים איך הפריטים האלה יוצגו ברכב.

אתם יכולים להשתמש בסגנונות התוכן הבאים:

פריטים ברשימה

סגנון התוכן הזה מתעדף כותרות ומטא-נתונים על פני תמונות.

פריטים בתצוגת רשת

סגנון התוכן הזה מתעדף תמונות על פני כותרות ומטא-נתונים.

הגדרת סגנונות תוכן שמוגדרים כברירת מחדל

ניתן להגדיר ברירות מחדל גלובליות עבור אופן ההצגה של פריטי מדיה על ידי הוספת קבועים מסוימים בחבילת התוספות של BrowserRoot של השירות onGetRoot() . Android Auto ו-Android Automotive OS קוראים את החבילה הזו ומחפשים את הקבועים האלה כדי לקבוע את הסגנון המתאים.

ניתן להשתמש בתוספות הבאות כמפתחות בחבילה:

המפתחות יכולים למפות לערכים הקבועים הבאים של המספרים השלמים כדי להשפיע על האופן שבו הפריטים מוצגים:

  • 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. פריט מדיה ג' עם extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. פריט מדיה ד' עם 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 OS מפרש זאת כארבע הקבוצות הבאות:

  • קבוצה 1 שנקראת "שירים" מכיל את פריט המדיה A
  • קבוצה 2 בשם "אלבומים" מכיל את פריט המדיה B
  • קבוצה 3 שנקראת 'שירים' שמכיל את פריטי המדיה C ו-D
  • קבוצה 4 שנקראת "אלבומים" מכיל פריט מדיה E

כדי להציג את הפריטים האלה בשתי קבוצות, האפליקציה צריכה להעביר את פריטי המדיה בסדר הבא:

  1. פריט מדיה א' עם extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. פריט מדיה ג' עם extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  3. פריט מדיה ד' עם 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 OS קוראות את התוספות המשויכות עם פריט מסוים ולחפש קבועים מסוימים כדי לקבוע אילו אינדיקטורים מסך. במהלך הפעלה של מדיה, Android Auto ו-Android Automotive OS קוראים את מטא-נתונים עבור סשן המדיה ומחפשים קבועים מסוימים כדי לקבוע אינדיקטורים להצגה.

איור 3. תצוגת הפעלה עם מטא-נתונים שמזהים את השיר ואומן וגם סמל שמציין תוכן בוטה.

איור 4. תצוגת דפדוף עם נקודה עבור תוכן שלא הופעל ב- הפריט הראשון וסרגל התקדמות לתוכן שהופעל חלקית הפריט השני.

ניתן להשתמש בקבועים הבאים גם בתוספות תיאור של MediaItem וגם MediaMetadata תוספות:

ניתן להשתמש רק בתוספות תיאור מסוג MediaItem:

להציג אינדיקטורים שמופיעים בזמן שהמשתמש מדפדפים בין המדיה עץ, ליצור חבילת תוספות שכוללת אחד או יותר מהקבועים האלה תעביר את החבילה ל-method 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 OS, האינדיקטור הזה הופך לא מדויק במשך הזמן.

ל-Android Auto ול-Android Automotive OS כדי שסרגל ההתקדמות מעודכן, אפשר לספק מידע נוסף 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 OS את התוצאות האלה דרך ממשקי שאילתות חיפוש או דרך הצעות משתלמות בתגובה לשאילתות שנשלחו מוקדם יותר בסשן. מידע נוסף זמין במאמר הבא: הקטע תמיכה בפעולות קוליות במדריך הזה.

כדי להציג תוצאות חיפוש שניתן לדפדף ביניהן, יש לכלול את המקש הקבוע BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED בחבילת התוספות של onGetRoot() השירות מתבצע מיפוי ל-true הבוליאני.

בקטע הקוד הבא מוסבר איך להפעיל תמיכה ב-onGetRoot() method:

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 OS להעביר את מונחי החיפוש של המשתמש לשיטה הזו בכל פעם שמשתמש מפעיל חיפוש ממשק שאילתות או במחיר של 'תוצאות חיפוש'.

אפשר לארגן את החיפוש תוצאות משיטת onSearch() של השירות באמצעות פריטי שם פריט כדי שיהיה קל יותר לעיין בהם. לדוגמה, אם האפליקציה מפעילה מוזיקה, ייתכן לארגן את תוצאות החיפוש לפי אלבומים, אומנים ושירים.

קטע הקוד הבא מציג הטמעה פשוטה של onSearch() method:

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 אובייקטים באפליקציית המדיה של הרכב ומטפלים באינטראקציות של משתמשים עם את הפעולות האלה. כך תוכלו להרחיב את הפונקציונליות של אפליקציית המדיה דרכים שונות, כמו הוספת "הורדה", "הוספה לתור", "Play Radio", 'מועדפים' או 'הסרה' פעולות.

אפשרויות נוספות של פעולות עיון בהתאמה אישית.

איור 7. אפשרויות נוספות של פעולות גלישה בהתאמה אישית

אם יש יותר פעולות מותאמות אישית ממה שיצרן ה-OEM מאפשר להציג, יוצג למשתמש תפריט אפשרויות נוספות.

איך הם פועלים?

כל פעולת גלישה בהתאמה אישית מוגדרת עם:

  • מזהה פעולה (מזהה מחרוזת ייחודי)
  • תווית פעולה (הטקסט שמוצג למשתמש)
  • URI של סמל פעולה (קובץ וקטורי שניתן להזזה עם גוון)

אתם מגדירים באופן גלובלי רשימה של פעולות גלישה מותאמות אישית כחלק BrowseRoot לאחר מכן תוכלו לצרף קבוצת משנה של הפעולות האלה לכל MediaItem.

כשמשתמש מקיים אינטראקציה עם פעולת עיון בהתאמה אישית, האפליקציה מקבלת קריאה חוזרת (callback) ב-onCustomAction(). לאחר מכן תוכלו לבצע את הפעולה ולעדכן את הרשימה פעולות עבור MediaItem במידת הצורך. האפשרות הזו שימושית לפעולות עם שמירת מצב כמו "מועדפים" ו'הורדה'. לפעולות שאין צורך לעדכן, כמו "הפעלה רדיו", לא צריך לעדכן את רשימת הפעולות.

פעולות גלישה בהתאמה אישית ברמה הבסיסית (root) של צומת הגלישה.

איור 8. סרגל הכלים של פעולת הגלישה בהתאמה אישית

אפשר גם לצרף פעולות גלישה בהתאמה אישית לרמה הבסיסית (root) של צומת הגלישה. הפעולות האלה יוצגו בסרגל הכלים המשני מתחת לסרגל הכלים הראשי.

איך מטמיעים פעולות גלישה בהתאמה אישית

כדי להוסיף לפרויקט פעולות עיון בהתאמה אישית:

  1. לשנות שתי שיטות ב MediaBrowserServiceCompat הטמעה:
  2. ניתוח מגבלות הפעולות בזמן הריצה:
    • בonGetRoot(), קבלת מספר הפעולות המקסימלי המותר בכל פעם MediaItem באמצעות המפתח BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT בBundle של rootHints. מגבלה של 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;
}

הוספת הרשימה של פעולות הגלישה בהתאמה אישית לרמה הבסיסית (root) של הדפדוף

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {
    Bundle browserRootExtras = new Bundle();
    browserRootExtras.putParcelableArrayList(
            BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST,
            createCustomActionsList()));
    mRoot = new BrowserRoot(ROOT_ID, browserRootExtras);
    return mRoot;
}

הוספת פעולות לMediaItem

MediaDescriptionCompat buildDescription (long id, String title, String subtitle,
                String description, Uri iconUri, Uri mediaUri,
                ArrayList<String> browseActionIds) {

    MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
    bob.setMediaId(id);
    bob.setTitle(title);
    bob.setSubtitle(subtitle);
    bob.setDescription(description);
    bob.setIconUri(iconUri);
    bob.setMediaUri(mediaUri);

    Bundle extras = new Bundle();
    extras.putStringArrayList(
          DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST,
          browseActionIds);

    bob.setExtras(extras);
    return bob.build();
}
MediaItem mediaItem = new MediaItem(buildDescription(...), flags);

יצירת תוצאה אחת (onCustomAction)

  • ניתוח mediaId מ-Bundle extras:
    @Override
    public void onCustomAction(
              @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){
      String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID);
    }
    
  • לקבלת תוצאות אסינכרוניות, נתק את התוצאה. result.detach()
  • חבילת תוצאות build
    • הודעה למשתמש
      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).

עדכון מצב הפעולה

משתמשים ב-method 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 OS שולחים פקודות לבקרת הפעלה באמצעות MediaSessionCompat של השירות. עליכם להירשם לסשן ולהטמיע את השיטות המשויכות לקריאה חוזרת (callback).

רישום סשן מדיה

ב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 לכיתה של האפליקציה. בקטע הבא מוסבר איך ליישם את האובייקט הזה.

הטמעת פקודות Play

כשמשתמש מבקש להפעיל פריט מדיה מהאפליקציה שלכם, Android Automotive מערכת ההפעלה ו-Android Auto משתמשות בMediaSessionCompat.Callback כיתה מה-MediaSessionCompat של האפליקציה שלך שהוא קיבל משירות דפדפן המדיה של האפליקציה. כשמשתמש רוצה לשלוט בהפעלת תוכן, כמו להשהות את ההפעלה או לדלג אל הטראק הבא, Android Auto ו-Android Automotive OS מפעילים את אחד הקריאה החוזרת ה-methods של האובייקט.

כדי לטפל בהפעלת תוכן, האפליקציה שלך חייבת להרחיב את המופשט MediaSessionCompat.Callback ולהטמיע את השיטות שהאפליקציה תומכת בהן.

להטמיע את כל השיטות הבאות להתקשרות חזרה, שמתאימות סוג התוכן שהאפליקציה מציעה:

onPrepare()
מופעלת כשמקור המדיה משתנה. מערכת Android Automotive OS מפעילה גם שיטה זו מיד לאחר האתחול. צריך להטמיע את המזהה הזה באפליקציית המדיה .
onPlay()
מופעלת אם המשתמש בוחר להפעיל בלי לבחור פריט ספציפי. שלך חייב להפעיל את תוכן ברירת המחדל שלו, או אם ההפעלה הושהתה באמצעות onPause(), האפליקציה ממשיכה את ההפעלה.

הערה: האפליקציה לא אמורה להתחיל להשמיע מוזיקה באופן אוטומטי כש-Android Automotive OS או Android Auto מתחברים לדפדפן המדיה לאחר השיפור. למידע נוסף, עיינו בקטע שעוסק הגדרת מצב ההפעלה הראשונית.

onPlayFromMediaId()
מופעלת כשהמשתמש בוחר להפעיל פריט מסוים. השיטה עברה המזהה שהוקצה על ידי שירות דפדפן המדיה לפריט המדיה בהיררכיית התוכן.
onPlayFromSearch()
מופעלת כשהמשתמש בוחר להפעיל משאילתת חיפוש. האפליקציה חייבת לבצע בחירה מתאימה על סמך מחרוזת החיפוש שהועברה.
onPause()
מופעלת כשהמשתמש בוחר להשהות את ההפעלה.
onSkipToNext()
מופעלת כשהמשתמש בוחר לדלג לפריט הבא.
onSkipToPrevious()
מופעלת כשהמשתמש בוחר לדלג לפריט הקודם.
onStop()
מופעלת כשהמשתמש בוחר להפסיק את ההפעלה.

כדי לספק את הפונקציונליות הרצויה, צריך לשנות את השיטות האלה באפליקציה. שלך אין צורך להטמיע שיטה אם האפליקציה לא תומכת בפונקציונליות שלה. עבור לדוגמה, אם האפליקציה מפעילה שידור חי, כגון שידור ספורט, לא צריכים להטמיע את השיטה onSkipToNext(). אפשר להשתמש בברירת המחדל יישום של onSkipToNext() במקום זאת.

לא נדרשת שום היגיון מיוחד כדי להפעיל תוכן דרך הרכב וברמקולים. כשהאפליקציה מקבלת בקשה להפעלת תוכן, היא יכולה להשמיע אודיו בדיוק כמו שהוא מציג תוכן מהרמקולים או האוזניות של המשתמש. Android Auto ו-Android Automotive OS שולח באופן אוטומטי את תוכן האודיו למערכת הרכב כדי להשמיע וברמקולים של הרכב.

למידע נוסף על הפעלת תוכן אודיו, אפשר לבקר כאן: סקירה כללית של MediaPlayer, סקירה כללית של אפליקציית האודיו, ובסקירה הכללית של ExoPlayer.

הגדרת פעולות הפעלה רגילות

ב-Android Auto וב-Android Automotive OS מוצגים פקדי ההפעלה שמבוססים על פעולות שמופעלות בדף PlaybackStateCompat לאובייקט.

כברירת מחדל, האפליקציה צריכה לתמוך בפעולות הבאות:

האפליקציה יכולה לתמוך גם בפעולות הבאות, אם הן רלוונטיות תוכן האפליקציה:

בנוסף, יש לך אפשרות ליצור תור הפעלה שיוצג למשתמש, אבל זו לא חובה. כדי לעשות זאת, צריך לקרוא ל-setQueue() ו-setQueueTitle() methods, מפעילים את ACTION_SKIP_TO_QUEUE_ITEM פעולה, ולהגדיר את הקריאה החוזרת onSkipToQueueItem().

כמו כן, הוסיפו תמיכה בסמל מה שומעים עכשיו? מה שומעים כרגע? כדי לעשות זאת, צריך לקרוא ל-setActiveQueueItemId() ומעבירים את המזהה של הפריט שמופעל עכשיו בתור. צריך: לעדכן את setActiveQueueItemId() בכל פעם שיש שינוי בתור.

לחצני התצוגה של Android Auto ו-Android Automotive OS לכל פעולה מופעלת וגם את תור ההפעלה. כשהלחצנים שעליהם לוחצים, המערכת מפעילה את הקריאה החוזרת התואמת MediaSessionCompat.Callback

שמירת שטח אחסון לא בשימוש

ממשק המשתמש של Android Auto ו-Android Automotive OS ששומרים מקום ACTION_SKIP_TO_PREVIOUS ו-ACTION_SKIP_TO_NEXT פעולות. אם האפליקציה לא תומכות באחת מהפונקציות האלה, ב-Android Auto וב-Android Automotive OS כדי להציג את כל הפעולות המותאמות אישית שאתם יוצרים.

אם אתם לא רוצים למלא את המרחבים המשותפים האלה בפעולות מותאמות אישית, אתם יכולים להזמין כך ש-Android Auto ו-Android Automotive OS ישאירו את הרווח ריק כשהאפליקציה לא תומכת בפונקציה המתאימה. לשם כך, התקשרו 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 OS מתקשרים עם דפדפן המדיה השירות, סשן המדיה שלך מעביר את הסטטוס של הפעלת התוכן באמצעות PlaybackStateCompat. האפליקציה לא אמורה להתחיל להשמיע מוזיקה באופן אוטומטי כאשר Android Automotive OS או Android Auto מתחברים לשירות דפדפן המדיה. במקום זאת, הסתמכו על Android Auto ו-Android Automotive OS כדי להמשיך או להתחיל את ההפעלה בהתאם או פעולות המשתמש.

כדי לעשות זאת, צריך להגדיר את PlaybackStateCompat הראשוני של סשן המדיה שלך STATE_STOPPED, STATE_PAUSED, STATE_NONE, או STATE_ERROR.

סשנים של מדיה ב-Android Auto וב-Android Automotive OS נמשכים רק משך הנסיעה, כך שהמשתמשים מתחילים ומפסיקים את הסשנים האלה לעיתים קרובות. שפת תרגום לקדם חוויה חלקה בין נסיעות, לעקוב אחר הנסיעה הקודמת של המשתמש את מצב הסשן, כך שכשאפליקציית המדיה מקבלת המשך הבקשה, המשתמש יכול להמשיך באופן אוטומטי מהמקום שבו עזב כבוי - לדוגמה, פריט המדיה האחרון שהופעל, PlaybackStateCompat, ואת התור.

הוספת פעולות הפעלה בהתאמה אישית

אתם יכולים להוסיף פעולות הפעלה בהתאמה אישית כדי להציג פעולות נוספות שאתם שאפליקציית המדיה תומכת בה. אם המקום מאפשר (ולא שמור מקום), Android מוסיפה את פעולות מותאמות אישית לבקרת התעבורה. אחרת, הפעולות המותאמות אישית יוצגו בתפריט האפשרויות הנוספות. הפעולות המותאמות אישית מוצגות לפי הסדר הם נוספים אל PlaybackStateCompat.

שימוש בפעולות מותאמות אישית כדי לספק התנהגות שונה מהתנהגות רגילה פעולות. אין להשתמש בהם כדי להחליף או לשכפל מודעות רגילות פעולות.

אפשר להוסיף פעולות מותאמות אישית באמצעות addCustomAction() ב-PlaybackStateCompat.Builder בכיתה.

קטע הקוד הבא מראה איך להוסיף ערוץ מותאם אישית מסוג 'פתיחת ערוץ רדיו' action:

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 (לקישור מתוך בתיאור). לקבלת פרטים נוספים, תוכלו לקרוא את מאמרי העזרה של בשדות של מטא-נתונים.

תמיכה בפעולות קוליות

אפליקציית המדיה חייבת לתמוך בפעולות קוליות כדי לספק לנהגים סביבה בטוחה נוחה ולצמצם את הסחות הדעת. לדוגמה, אם האפליקציה מפעיל פריט מדיה אחד, המשתמש יכול לומר "Play [song title]" כדי לבקש מהאפליקציה להשמיע שיר אחר בלי להסתכל על הרכב או לגעת בו מסך. המשתמשים יכולים ליזום שאילתות על ידי לחיצה על הלחצנים המתאימים להשתמש בהגה או לומר את מילות ההפעלה "OK Google".

כש-Android Auto או Android Automotive OS מזהים קול ומנתח אותו פעולה קולית, היא מועברת לאפליקציה דרך onPlayFromSearch() לאחר קבלת הקריאה החוזרת, האפליקציה תמצא תוכן שתואם לquery מחרוזת ומתחילה את ההפעלה.

המשתמשים יכולים לציין קטגוריות שונות של מונחים בשאילתה שלהם: ז'אנר, אומן, אלבום, שם שיר, תחנת רדיו או פלייליסט, בין היתר. בזמן הבנייה תמיכה בחיפוש, צריך להביא בחשבון את כל הקטגוריות שמתאימות לאפליקציה. אם Android Auto או Android Automotive OS מזהים ששאילתה נתונה מתאימה מקטגוריות מסוימות, הוא מוסיף תוספות בפרמטר extras. ניתן לשלוח את התוספות הבאות:

חשבון למחרוזת query ריקה, שאותה ניתן לשלוח באמצעות Android Auto או Android Automotive OS אם המשתמש לא מציין מונחי חיפוש. לדוגמה, אם המשתמש אומר "אני רוצה לשמוע מוזיקה". במקרה כזה, האפליקציה עשויה בוחרים אם להתחיל טראק שהשמעת לאחרונה או הצעה חדשה.

אם לא ניתן לעבד חיפוש במהירות, אין לחסום ב-onPlayFromSearch(). במקום זאת, צריך להגדיר את מצב ההפעלה ל-STATE_CONNECTING ומבצעים את החיפוש בשרשור אסינכרוני.

לאחר שההפעלה מתחילה, כדאי לאכלס את התור של סשן המדיה תוכן קשור. לדוגמה, אם המשתמש מבקש להפעיל אלבום, האפליקציה עשויה למלא את התור ברשימת הטראקים של האלבום. כדאי גם ליישם תמיכה בתוצאות חיפוש שניתן לעיין בהן, כך שמשתמש יכול לבחור טראק אחר שתואם לשאילתה שלהם.

בנוסף ל-'play' שאילתות, Android Auto ו-Android Automotive OS לזהות שאילתות קוליות כדי לשלוט בהפעלה כמו "pause music". וגם "הבא שיר" ולהתאים את הפקודות האלה לקריאות החוזרות (callback) המתאימות של סשן המדיה, כמו onPause() ו-onSkipToNext().

דוגמה מפורטת להטמעה של פעולות הפעלה קוליות ב: האפליקציה שלכם, תוכלו להיעזר במאמר Google Assistant ואפליקציות מדיה.

הטמעת אמצעי הגנה מפני הסחות דעת

כי טלפון של המשתמש מחובר לרמקולים של הרכב בזמן השימוש ב-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 OS הודעה למשתמש.

לדוגמה, אם התוכן לא זמין באזור הנוכחי של המשתמש, תוכלו להשתמש ב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 צריך לפתוח את האפליקציה לטלפון כדי לפתור שגיאה, מספקים את המידע הזה למשתמש בהודעה. לדוגמה, השגיאה שבה נתקלת יכול להיות שתופיע ההודעה "Sign in to [your app name] " במקום 'נא להיכנס'.

מקורות מידע נוספים