Android Auto ו-Android Automotive OS עוזרים לכם להציג את התוכן של אפליקציית המדיה שלכם למשתמשים ברכב. אפליקציית מדיה למכוניות חייבת לספק שירות של דפדפן מדיה כדי ש-Android Auto ו-Android Automotive OS, או אפליקציה אחרת עם דפדפן מדיה, יוכלו למצוא את התוכן שלכם ולהציג אותו.
במדריך הזה אנו מתייחסים לאפליקציית מדיה שכבר מותקנת בטלפון ומשמשת להפעלת אודיו, ושעומדת בדרישות של ארכיטקטורת אפליקציות המדיה ב-Android.
במדריך הזה מתוארים הרכיבים הנדרשים של MediaBrowserService
ו-MediaSession
שנחוצים לאפליקציה כדי לפעול ב-Android Auto או ב-Android Automotive OS. אחרי שמשלימים את תשתית המדיה המרכזית, אפשר להוסיף תמיכה ב-Android Auto ולהוסיף לאפליקציית המדיה תמיכה ב-Android Automotive OS.
לפני שמתחילים
- כאן אפשר לקרוא את המסמכים של Android media API.
- במאמר יצירת אפליקציות מדיה מפורטות הנחיות לעיצוב.
- כדאי לעיין במונחים ובמושגים המרכזיים שמפורטים בקטע הזה.
מונחים ומושגים מרכזיים
- שירות דפדפן מדיה
- שירות Android שמוטמע באפליקציית המדיה שלכם ועונה על דרישות ה-API של
MediaBrowserServiceCompat
. האפליקציה שלכם משתמשת בשירות הזה כדי לחשוף את התוכן שלה. - דפדפן מדיה
- ממשק 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 לזהות את השירות ולהתחבר לאפליקציה, צריך להצהיר על שירות דפדפן המדיה במניפסט.
בקטע הקוד הבא מוסבר איך להצהיר על שירות דפדפן המדיה במניפסט. כוללים את הקוד הזה בקובץ המניפסט של מודול 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 כדי לייצג את האפליקציה בממשק המשתמש של המערכת. יש צורך בשני סוגים של סמלים:
- סמל מרכז האפליקציות
- סמל השיוך (Attribution)
סמל מרכז האפליקציות
סמל מרכז האפליקציות מייצג את האפליקציה בממשק המשתמש של המערכת, למשל במרכז האפליקציות ובמגש הסמלים. כדי לציין שאתם רוצים להשתמש בסמל של האפליקציה לנייד כדי לייצג את אפליקציית המדיה ברכב, תוכלו להשתמש בהצהרת המניפסט הבאה:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
כדי להשתמש בסמל שונה מהסמל של האפליקציה לנייד, מגדירים את המאפיין android:icon
ברכיב <service>
של שירות דפדפן המדיה במניפסט:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
סמל השיוך (Attribution)
סמל השיוך משמש במקומות שבהם תוכן המדיה מקבל עדיפות, כמו בכרטיסי מדיה. כדאי להשתמש שוב בסמל הקטן המשמש להתראות. הסמל הזה חייב להיות מונוכרומטי. אפשר לציין סמל שמייצג את האפליקציה באמצעות הצהרת המניפסט הבאה:
<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 Auto פועלים יחד עם שירות דפדפן המדיה במהלך תהליך עבודה אופייני של משתמש.
- המשתמש מפעיל את האפליקציה ב-Android Automotive OS או ב-Android Auto.
- Android Automotive OS או Android Auto יוצרים קשר עם שירות דפדפן המדיה באפליקציה באמצעות השיטה
onCreate()
. בהטמעה של methodonCreate()
, צריך ליצור ולרשום אובייקטMediaSessionCompat
ואת אובייקט ה-callback שלו. - Android Automotive OS או Android Auto מפעילים את השיטה
onGetRoot()
של השירות כדי לקבל את פריט המדיה הבסיסי בהיררכיית התוכן. פריט המדיה ברמה הבסיסית לא מוצג, אלא משמש לאחזור תוכן נוסף מהאפליקציה. - Android Automotive OS או Android Auto קוראים לשיטה
onLoadChildren()
של השירות כדי לקבל את הצאצאים של פריט המדיה ברמה הבסיסית. Android Automotive OS ו-Android Auto מציגים את פריטי המדיה האלה ברמה העליונה של פריטי תוכן. בקטע מבנה התפריט ברמה הבסיסית בדף הזה מוסבר מה המערכת מצפה לראות ברמה הזו. - אם המשתמש בוחר פריט מדיה שאפשר לעיין בו, המערכת קוראת שוב ל-method
onLoadChildren()
של השירות כדי לאחזר את הפריטים הצאצאים של פריט התפריט שנבחר. - אם המשתמש בוחר פריט מדיה שאפשר להפעיל, מערכת Android Automotive OS או Android Auto מפעילה את השיטה המתאימה לקריאה חוזרת (callback) של סשן במדיה כדי לבצע את הפעולה הזו.
- אם האפליקציה תומכת בכך, המשתמש יוכל גם לחפש את התוכן שלכם. במקרה כזה, Android Automotive OS או Android Auto קוראים לשיטה
onSearch()
של השירות.
יצירת היררכיית התוכן
Android Auto ו-Android Automotive OS קוראים לשירות דפדפן המדיה של האפליקציה כדי לברר איזה תוכן זמין. כדי לתמוך בכך, צריך להטמיע שתי שיטות בשירות דפדפן המדיה: onGetRoot()
ו-onLoadChildren()
יישום ב-GetRoot
השיטה onGetRoot()
של השירות מחזירה מידע על צומת הבסיס של היררכיית התוכן.
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); }
לדוגמה מפורטת יותר של השיטה הזו, אפשר לעיין ב-method onGetRoot()
באפליקציה לדוגמה של Universal Android Music Player ב-GitHub.
הוספת אימות חבילה ל-onGetRoot()
כשמתבצעת קריאה ל-method onGetRoot()
של השירות, החבילה של מבצע הקריאה מעבירה מידע מזהה לשירות. השירות יכול להשתמש במידע הזה כדי להחליט אם לחבילה תהיה גישה לתוכן שלכם. לדוגמה, אפשר להגביל את הגישה לתוכן של האפליקציה לרשימה של
חבילות שאושרו על ידי השוואה בין clientPackageName
לרשימת ההיתרים ואימות האישור שמשמש לחתימה על ה-APK של החבילה. אם אי אפשר לאמת את החבילה, צריך להחזיר את הערך null
כדי לדחות את הגישה לתוכן.
כדי לספק אפליקציות מערכת, כמו Android Auto ו-Android Automotive OS, עם גישה לתוכן, השירות תמיד צריך להחזיר BrowserRoot
שהוא לא null כשאפליקציות המערכת האלה מבצעות קריאה ל-method 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()
:
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); }
דוגמה מלאה לשימוש בשיטה הזו מופיעה ב-method onLoadChildren()
באפליקציית Universal Android Music Player לדוגמה ב-GitHub.
מבנה התפריט ברמה הבסיסית
ל-Android Auto ול-Android Automotive OS יש אילוצים ספציפיים לגבי המבנה של תפריט הבסיס. הם מועברים ל-MediaBrowserService
דרך רמזים ברמה הבסיסית, שאפשר לקרוא דרך הארגומנט Bundle
שמוענק ל-onGetRoot()
.
מעקב אחר הרמזים האלה מאפשר למערכת להציג את תוכן הבסיס באופן אופטימלי ככרטיסיות ניווט. אם לא תפעלו לפי ההנחיות האלה, יכול להיות שחלק מהתוכן ברמה הבסיסית יוסר או שהמערכת תתקשה למצוא אותו. נשלחות שתי רמזים:
- מגבלה על מספר הצאצאים של הבסיס: ברוב המקרים, המספר הזה הוא ארבעה. המשמעות היא שלא ניתן להציג יותר מארבעה כרטיסיות.
- דגלים נתמכים בצאצאים של הבסיס: הערך הצפוי הוא
MediaItem#FLAG_BROWSABLE
. המשמעות היא שרק פריטים שאפשר לעיין בהם – ולא פריטים שניתן להפעיל – יכולים להופיע ככרטיסיות.
אפשר להשתמש בקוד הבא כדי לקרוא את הטיפים הרלוונטיים ברמה הבסיסית (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()
באמצעות אחד מהמפתחות הבאים:
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
MediaMetadataCompat.METADATA_KEY_ART_URI
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
בשלבים הבאים מוסבר איך להוריד פריטי גרפיקה מ-URI של אינטרנט ולחשוף אותם באמצעות URI מקומי. לדוגמה מלאה יותר, אפשר לעיין בהטמעה של openFile()
ובשיטות שמסביב באפליקציית Universal Android Music Player לדוגמה.
יוצרים URI של
content://
שתואם ל-URI של האתר. שירות דפדפן המדיה וסשן המדיה מעבירים את ה-URI של התוכן הזה ל-Android Auto ול-Android Automotive OS.Kotlin
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
Java
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
בהטמעה של
ContentProvider.openFile()
, בודקים אם קיים קובץ ל-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_KEY_CONTENT_STYLE_BROWSABLE
: מציין רמז להצגה של כל הפריטים שאפשר לעיין בהם בעץ העיון.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: מציין רמז להצגה של כל הפריטים שניתן להפעיל בתוך עץ העיון.
כדי להשפיע על הצגת הפריטים האלה, אפשר למפות את המפתחות לערכי המשתנים הקבועים הבאים:
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: הפריטים התואמים מוצגים כפריטי רשימה.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: הפריטים התואמים מוצגים בתצוגת רשת.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: הפריטים התואמים מוצגים כפריטים ברשימה 'קטגוריה'. הפריטים האלה זהים לפריטים רגילים ברשימה, אבל השוליים מוחלים מסביב לסמלים של הפריטים, כי הסמלים נראים טוב יותר כשהם קטנים. הסמלים חייבים להיות פריטים גרפיים וקטורים שניתן לשרטוט ולצבוע. יש לספק את הרמז הזה רק עבור פריטים שניתן לעיין בהם.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: הפריטים התואמים מוצגים כפריטים ברשת 'קטגוריה'. אלה פריטים זהים לפריטים רגילים בתצוגת רשת, מלבד העובדה שמוצגים שוליים סביב הסמלים של הפריטים, כי הסמלים נראים טוב יותר כשהם קטנים. הסמלים חייבים להיות פריטים שניתנים להזזה בווקטורים. ההצעה הזו אמורה להופיע רק לפריטים שאפשר לעיין בהם.
בקטע הקוד הבא מוצג איך להגדיר את סגנון ברירת המחדל של התוכן לפריטים שניתנים לגלילה כמרשתות, ולפריטים שניתן להפעיל כרשימות:
Kotlin
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
Java
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
הגדרת סגנונות תוכן לכל פריט
באמצעות Content Style API אפשר לשנות את סגנון ברירת המחדל של התוכן לכל הצאצאים של פריט מדיה שניתן לעיון, וגם לפריט המדיה עצמו.
כדי לשנות את ברירת המחדל של הצאצאים של פריט מדיה שניתן לעיון, יוצרים חבילת פריטים נוספים ב-MediaDescription
של פריט המדיה ומוסיפים את אותן הנחיות שצוינו למעלה. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
חל על הצאצאים של הפריט שאפשר לשחק בהם, ואילו DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
חל על הצאצאים של הפריט הזה שניתנים לעיון.
כדי לשנות את ברירת המחדל של פריט מדיה מסוים בעצמו, ולא של הצאצאים שלו, יוצרים חבילת תכנים נוספים ב-MediaDescription
של פריט המדיה ומוסיפים רמז עם המפתח DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
.
משתמשים באותם ערכים שתוארו למעלה כדי לציין את הצגת הפריט.
בקטע הקוד הבא מוצג איך ליצור MediaItem
שניתן לעיון, שמבטל את סגנון ברירת המחדל של התוכן גם עבור עצמו וגם עבור הצאצאים שלו. הוא מוגדר כפריט ברשימה של קטגוריה, צאצאיו שאפשר לעיין בהם כפריטים ברשימה וצאצאיו שאפשר להפעיל כפריטים בתצוגת רשת:
Kotlin
import androidx.media.utils.MediaConstants private fun createBrowsableMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Java
import androidx.media.utils.MediaConstants; private MediaBrowser.MediaItem createBrowsableMediaItem( String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
קיבוץ פריטים באמצעות רמזים לשמות
כדי לקבץ יחד פריטים קשורים של מדיה, משתמשים בהנחיה לכל פריט. כל פריט מדיה בקבוצה צריך להצהיר על חבילת תכנים נוספים ב-MediaDescription
שלו, שכוללת מיפוי עם המפתח DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
וערך מחרוזת זהה. מבצעים לוקליזציה של המחרוזת הזו, שתשמש בתור השם של הקבוצה.
קטע הקוד הבא מסביר איך ליצור MediaItem
עם כותרת משנה של "Songs"
:
Kotlin
import androidx.media.utils.MediaConstants private fun createMediaItem( mediaId: String, folderName: String, iconUri: Uri ): MediaBrowser.MediaItem { val mediaDescriptionBuilder = MediaDescription.Builder() mediaDescriptionBuilder.setMediaId(mediaId) mediaDescriptionBuilder.setTitle(folderName) mediaDescriptionBuilder.setIconUri(iconUri) val extras = Bundle() extras.putString( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Java
import androidx.media.utils.MediaConstants; private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) { MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder(); mediaDescriptionBuilder.setMediaId(mediaId); mediaDescriptionBuilder.setTitle(folderName); mediaDescriptionBuilder.setIconUri(iconUri); Bundle extras = new Bundle(); extras.putString( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
האפליקציה צריכה להעביר את כל פריטי המדיה שרוצים לקבץ יחד כבלוק רציף. לדוגמה, נניח שאתם רוצים להציג שתי קבוצות של פריטי מדיה, 'שירים' ו'אלבומים', בסדר הזה, והאפליקציה מעבירה חמישה פריטי מדיה בסדר הבא:
- פריט מדיה א' עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- פריט מדיה ב' עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
- פריט מדיה ג' עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- פריט מדיה ד' עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- פריט מדיה E עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
מכיוון שפריטי המדיה בקבוצה 'שירים' ובקבוצה 'אלבומים' לא נשמרים יחד בבלוק רציף, מערכת Android Auto ומערכת Android Automotive OS מפרשות אותם כארבעת הקבוצות הבאות:
- קבוצה 1 שנקראת 'שירים' ומכילה את פריט המדיה א'
- קבוצה 2 שנקראת 'אלבומים' ומכילה את פריט המדיה ב'
- קבוצה 3 שנקראת 'שירים' ומכילה את פריטי המדיה C ו-D
- קבוצה 4 שנקראת 'אלבומים' ומכילה את פריט המדיה E
כדי להציג את הפריטים האלה בשתי קבוצות, האפליקציה צריכה להעביר את פריטי המדיה במקום זאת בסדר הבא:
- פריט מדיה א' עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- פריט מדיה C עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- פריט מדיה D עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- פריט מדיה ב' עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
- פריט מדיה E עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
הצגת אינדיקטורים נוספים של מטא-נתונים
אתם יכולים לכלול אינדיקטורים נוספים של מטא-נתונים כדי לספק מידע בקצרה על התוכן בעץ של דפדפן המדיה ובמהלך ההפעלה. בעץ העיון, Android Auto ו-Android Automotive OS קוראים את הנתונים הנוספים שמשויכים לפריט ומחפשים ערכי קבועים מסוימים כדי לקבוע אילו אינדיקטורים יוצגו. במהלך הפעלה של מדיה, Android Auto ו-Android Automotive OS קוראים את המטא-נתונים של סשן המדיה ומחפשים קבועים מסוימים כדי לקבוע את המדדים להצגה.
אפשר להשתמש בקבועים הבאים גם בתוספות של תיאור MediaItem
וגם בתוספות MediaMetadata
:
EXTRA_DOWNLOAD_STATUS
: מציין את סטטוס ההורדה של פריט. משתמשים בערך הקבוע הזה בתור המפתח. הערכים האפשריים הם הקבועים הארוכים הבאים:STATUS_DOWNLOADED
: הפריט יורד במלואו.STATUS_DOWNLOADING
: מתבצעת הורדה של הפריט.STATUS_NOT_DOWNLOADED
: הפריט לא הורדה.
METADATA_KEY_IS_EXPLICIT
: מציין אם הפריט מכיל תוכן בוטה. כדי לציין שפריט מסוים הוא בוטה, משתמשים בקבוע הזה כמפתח ובערך הארוךMETADATA_VALUE_ATTRIBUTE_PRESENT
בתור הערך.
אפשר להשתמש בערכי הקבועים הבאים רק בתוספים של תיאור MediaItem
:
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: מציין את מצב ההשלמה של תכנים ארוכים, כמו פרקים של פודקאסטים או ספרי אודיו. משתמשים בקבוע הזה בתור המפתח. הערכים האפשריים הם הקבועים הבאים של מספרים שלמים:DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: הפריט לא הופעל בכלל.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: התוכן הופעל חלקית והמיקום הנוכחי נמצא באמצע.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: הפריט הושלם.
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: הערך הזה מציין את מידת ההתקדמות בהשלמת תוכן ארוך כערך כפול בין 0.0 ל-1.0, כולל. האפשרות הנוספת הזו מספקת מידע נוסף על המצבPARTIALLY_PLAYING
, כדי שמערכת Android Auto או Android Automotive OS תציג מדד התקדמות משמעותי יותר, כמו סרגל התקדמות. אם אתם משתמשים בתכונה הזו, כדאי לעיין בקטע עדכון סרגל ההתקדמות בתצוגת העיון בזמן שהתוכן פועל במדריך הזה כדי ללמוד איך לשמור על עדכניות של האינדיקטור הזה אחרי ההצגה הראשונית שלו.
כדי להציג אינדיקטורים שמופיעים בזמן שהמשתמש גולש בעץ העיון של המדיה, יוצרים חבילה של פריטים נוספים שכוללת אחת או יותר מהקבועות האלה ומעבירים את החבילה הזו ל-method MediaDescription.Builder.setExtras()
.
קטע הקוד הבא מראה איך להציג אינדיקטורים לפריט מדיה בוטה בשלמותו:
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
כדי לקשר את התוכן הנוכחי לפריטי המדיה בתצוגת העיון. כדי שסרגל ההתקדמות יתעדכן באופן אוטומטי בפריט המדיה, צריך לעמוד בדרישות הבאות:
- כשיוצרים את ה-
MediaItem
, צריך לשלוח אתDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
בתוספים שלו עם ערך בין 0.0 ל-1.0, כולל. - ה-
MediaMetadataCompat
צריך לשלוח אתMETADATA_KEY_MEDIA_ID
עם ערך מחרוזת שווה ל-media ID שהועברו ל-MediaItem
. - השדה
PlaybackStateCompat
חייב לכלול תוספת עם המפתחPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
, שממופה לערך מחרוזת ששווה למזהה המדיה שמועבר ל-MediaItem
.
קטע הקוד הבא מראה איך לציין שהפריט שמופעל כרגע מקושר לפריט בתצוגת העיון:
Kotlin
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
Java
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
הצגת תוצאות חיפוש שניתן לעיין בהן
האפליקציה יכולה לספק תוצאות חיפוש לפי הקשר שיוצגו למשתמשים כשהם יפעילו שאילתה לחיפוש. התוצאות האלה מוצגות ב-Android Auto וב-Android Automotive OS דרך ממשקי שאילתות חיפוש או דרך תכונות שמתבססות על שאילתות שבוצעו מוקדם יותר בסשן. מידע נוסף מופיע בקטע פעולות קוליות של תמיכה במדריך הזה.
כדי להציג תוצאות חיפוש שניתן לעיין בהן, צריך לכלול את המפתח הקבוע BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
בחבילת התוספים של שיטת onGetRoot()
של השירות, ולמפות אותו ל-boolean true
.
בקטע הקוד הבא מוסבר איך להפעיל תמיכה ב-method onGetRoot()
:
Kotlin
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Java
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
כדי להתחיל לספק תוצאות חיפוש, צריך לשנות את השיטה onSearch()
בשירות דפדפן המדיה. Android Auto ו-Android Automotive OS
מעבירים את מונחי החיפוש של המשתמשים לשיטה הזו בכל פעם שמשתמש מפעיל ממשק שאילתות חיפוש או מחיר מיוחד ל'תוצאות חיפוש'.
כדי להקל על העיון בתוצאות החיפוש מהשיטה onSearch()
של השירות, אפשר לארגן אותן באמצעות פריטי כותרת. לדוגמה, אם האפליקציה מאפשרת להפעיל מוזיקה, תוכלו לארגן את תוצאות החיפוש לפי אלבום, אומן ושירים.
קטע הקוד הבא מציג הטמעה פשוטה של השיטה onSearch()
:
Kotlin
fun onSearch(query: String, extras: Bundle) { // Detach from results to unblock the caller (if a search is expensive). result.detach() object:AsyncTask() { internal var searchResponse:ArrayList internal var succeeded = false protected fun doInBackground(vararg params:Void):Void { searchResponse = ArrayList() if (doSearch(query, extras, searchResponse)) { succeeded = true } return null } protected fun onPostExecute(param:Void) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse) } else { // This invokes onError() on the search callback. result.sendResult(null) } return null } }.execute() } // Populates resultsToFill with search results. Returns true on success or false on error. private fun doSearch( query: String, extras: Bundle, resultsToFill: ArrayList ): Boolean { // Implement this method. }
Java
@Override public void onSearch(final String query, final Bundle extras, Result<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<MediaItem> searchResponse; boolean succeeded = false; @Override protected Void doInBackground(Void... params) { searchResponse = new ArrayList<MediaItem>(); if (doSearch(query, extras, searchResponse)) { succeeded = true; } return null; } @Override protected void onPostExecute(Void param) { if (succeeded) { // Sending an empty List informs the caller that there were no results. result.sendResult(searchResponse); } else { // This invokes onError() on the search callback. result.sendResult(null); } } }.execute() } /** Populates resultsToFill with search results. Returns true on success or false on error. */ private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) { // Implement this method. }
פעולות גלישה בהתאמה אישית
פעולות גלישה בהתאמה אישית מאפשרות לכם להוסיף סמלים ותוויות בהתאמה אישית לאובייקטים MediaItem
של האפליקציה באפליקציית המדיה של הרכב, ולנהל את האינטראקציות של המשתמשים עם הפעולות האלה. כך תוכלו להרחיב את הפונקציונליות של אפליקציית המדיה בדרכים שונות, כמו הוספת הפעולות 'הורדה', 'הוספה לתור', 'הפעלת רדיו', 'הוספה למועדפים' או 'הסרה'.
אם יש יותר פעולות בהתאמה אישית ממה ש-OEM מאפשר להציג, יוצג למשתמש תפריט Overflow.
איך הם פועלים?
כל פעולת גלישה בהתאמה אישית מוגדרת באמצעות:
- מזהה פעולה (מזהה מחרוזת ייחודי)
- תווית פעולה (הטקסט שמוצג למשתמש)
- URI של סמל פעולה (קובץ וקטורי שניתן להזזה עם גוון)
מגדירים רשימה של פעולות גלישה בהתאמה אישית באופן גלובלי כחלק מ-BrowseRoot
. לאחר מכן תוכלו לצרף קבוצת משנה של הפעולות האלה לMediaItem.
ספציפיים.
כשמשתמש יוצר אינטראקציה עם פעולת גלישה מותאמת אישית, האפליקציה מקבלת קריאה חוזרת (callback) ב-onCustomAction()
. לאחר מכן תוכלו לטפל בפעולה ולעדכן את רשימת הפעולות של MediaItem
לפי הצורך. כדאי להשתמש באפשרות הזו לפעולות עם מצב (stateful), כמו 'הוספה למועדפים' ו'הורדה'. לגבי פעולות שלא צריך לעדכן, כמו 'הפעלת רדיו', אין צורך לעדכן את רשימת הפעולות.
אפשר גם לצרף פעולות גלישה בהתאמה אישית לשורש של צומת גלישה. הפעולות האלה יוצגו בסרגל כלים משני מתחת לסרגל הכלים הראשי.
איך מטמיעים פעולות גלישה בהתאמה אישית
כך מוסיפים לפרויקט פעולות גלישה בהתאמה אישית:
- משנים את ברירת המחדל של שתי שיטות בהטמעה של
MediaBrowserServiceCompat
: - לנתח את מגבלות הפעולות בזמן הריצה:
- ב-
onGetRoot()
, מקבלים את המספר המקסימלי של הפעולות שמותר לבצע לכלMediaItem
באמצעות המפתחBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
ב-rootHints
Bundle
. אם הערך של המגבלה הוא 0, המשמעות היא שהמערכת לא תומכת בתכונה.
- ב-
- יוצרים את הרשימה הגלובלית של פעולות גלישה בהתאמה אישית:
- לכל פעולה, יוצרים אובייקט
Bundle
עם המפתחות הבאים: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: מזהה הפעולה *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: תווית הפעולה *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: סמל הפעולה URI * מוסיפים את כל האובייקטים של הפעולהBundle
לרשימה.
- לכל פעולה, יוצרים אובייקט
- מוסיפים את הרשימה הגלובלית אל
BrowseRoot
:- בתוספות של
BrowseRoot
,Bundle
, מוסיפים את רשימת הפעולות כ-Parcelable
Arraylist
באמצעות המפתחBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- בתוספות של
- מוסיפים פעולות לאובייקטים של
MediaItem
:- כדי להוסיף פעולות לאובייקטים ספציפיים מסוג
MediaItem
, צריך לכלול את רשימת מזהי הפעולות בפרטים הנוספים שלMediaDescriptionCompat
באמצעות המפתחDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. הרשימה הזו חייבת להיות קבוצת משנה של הרשימה הגלובלית של הפעולות שהגדרתם ב-BrowseRoot
.
- כדי להוסיף פעולות לאובייקטים ספציפיים מסוג
- טיפול בפעולות והחזרת התקדמות או תוצאות:
- ב-
onCustomAction
, מטפלים בפעולה על סמך מזהה הפעולה וכל נתון אחר שנחוץ לכם. אפשר לקבל את המזהה של ה-MediaItem
שהפעיל את הפעולה מהתכונות הנוספות באמצעות המפתחEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. - כדי לעדכן את רשימת הפעולות של
MediaItem
, צריך לכלול את המפתחEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
בחבילת ההתקדמות או התוצאה.
- ב-
ריכזנו כאן כמה שינויים שאפשר לבצע ב-BrowserServiceCompat
כדי להתחיל להשתמש בפעולות גלישה בהתאמה אישית.
שינוי של BrowserServiceCompat
צריך לשנות את השיטות הבאות ב-MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
מגבלה על פעולות ניתוח
כדאי לבדוק כמה פעולות גלישה בהתאמה אישית נתמכות.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
יצירה של פעולת גלישה בהתאמה אישית
כל פעולה צריכה להיות ארוזת ב-Bundle
נפרד.
- מזהה פעולה
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- תווית הפעולה
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- URI של סמל פעולה
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
הוספת פעולות גלישה בהתאמה אישית אל Parceable
ArrayList
מוסיפים את כל האובייקטים מסוג Bundle
של פעולות גלישה בהתאמה אישית ל-ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
הוספת רשימת פעולות גלישה בהתאמה אישית לשורש הגלישה
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
הוספת פעולות ל-MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
תוצאת build 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)
.
עדכון מצב הפעולה
באמצעות השיטה result.sendProgressUpdate(resultBundle)
עם המפתח EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
, אפשר לעדכן את MediaItem
כך שישקף את המצב החדש של הפעולה. כך תוכלו לספק למשתמש משוב בזמן אמת על ההתקדמות והתוצאה של הפעולה שלו.
דוגמה: פעולת הורדה
הנה דוגמה לאופן שבו אפשר להשתמש בתכונה הזו כדי להטמיע פעולת הורדה עם שלושה מצבים:
- הורדה: זהו המצב הראשוני של הפעולה. כשהמשתמש בוחר בפעולה הזו, אפשר להחליף אותה ב'הורדה' ולקרוא ל-
sendProgressUpdate
כדי לעדכן את ממשק המשתמש. - הורדה: מצב זה מציין שההורדה מתבצעת. אפשר להשתמש במצב הזה כדי להציג למשתמש סרגל התקדמות או אינדיקטור אחר.
- בוצעה הורדה: מצב זה מציין שההורדה הושלמה. כשההורדה תושלם, תוכלו להחליף את 'Downloading' (הורדה) ב-'Downloaded' (הורדנו) ולקרוא ל-
sendResult
באמצעות המקשEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
כדי לציין שצריך לרענן את הפריט. בנוסף, אפשר להשתמש במפתחEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
כדי להציג למשתמש הודעת אישור.
כך תוכלו לספק למשתמשים משוב ברור על תהליך ההורדה והמצב הנוכחי שלו. אפשר להוסיף עוד פרטים באמצעות סמלים שמייצגים את שלבי ההורדה: 25%, 50% ו-75%.
דוגמה: פעולה מועדפת
דוגמה נוספת היא פעולה מועדפת עם שני מצבים:
- מועדפים: הפעולה הזו מוצגת לפריטים שלא נמצאים ברשימת המועדפים של המשתמש. כשהמשתמש בוחר בפעולה הזו, אפשר להחליף אותה ב-Favorited [מועדפים] ולקרוא ל-
sendResult
במפתחEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
כדי לעדכן את ממשק המשתמש. - הוספה למועדפים: הפעולה הזו מוצגת לפריטים שנמצאים ברשימת המועדפים של המשתמש. כשהמשתמש בוחר בפעולה הזו, אפשר להחליף אותה ב'מועדף' ולקרוא ל-
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
לאפליקציה. בקטע הבא מוסבר איך מטמיעים את האובייקט הזה.
הטמעת פקודות הפעלה
כשמשתמש מבקש הפעלה של פריט מדיה מהאפליקציה, מערכת ההפעלה של Android Automotive ו-Android Auto משתמשות בכיתה MediaSessionCompat.Callback
מהאובייקט MediaSessionCompat
של האפליקציה, שהן קיבלו משירות דפדפן המדיה של האפליקציה. כשמשתמש רוצה לשלוט בהפעלת התוכן, למשל להשהות את ההפעלה או לדלג למסלול הבא, Android Auto ו-Android Automotive OS מפעילים אחת מהשיטות של אובייקט ה-callback.
כדי לטפל בהפעלת תוכן, האפליקציה צריכה להרחיב את הכיתה המופשטת 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
.
כברירת מחדל, האפליקציה צריכה לתמוך בפעולות הבאות:
בנוסף, האפליקציה יכולה לתמוך בפעולות הבאות אם הן רלוונטיות לתוכן של האפליקציה:
בנוסף, יש אפשרות ליצור תור הפעלה שיוצג למשתמשים, אבל זו לא חובה. לשם כך, צריך לקרוא ל-methods setQueue()
ו-setQueueTitle()
, להפעיל את הפעולה ACTION_SKIP_TO_QUEUE_ITEM
ולהגדיר את פונקציית ה-callback onSkipToQueueItem()
.
בנוסף, מוסיפים תמיכה בסמל מה שומעים עכשיו?, שמציין את התוכן שמושמע כרגע. כדי לעשות זאת, צריך לבצע קריאה ל-method setActiveQueueItemId()
ולהעביר את המזהה של הפריט שמופעל כרגע בתור. צריך לעדכן את setActiveQueueItemId()
בכל פעם שיש שינוי בתור.
ב-Android Auto וב-Android Automotive OS מוצגים לחצנים לכל פעולה מופעלת, וגם תור ההפעלה. כשלוחצים על הלחצנים, המערכת מפעילה את פונקציית ה-callback המתאימה מ-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 ישאירו את המרחב ריק בכל פעם שהאפליקציה שלכם לא תומכת בפונקציה המתאימה. כדי לעשות את זה, קוראים ל-method setExtras()
עם חבילת תוספות שמכילה קבועים שתואמים לפונקציות השמורות.
SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
תואם ל-ACTION_SKIP_TO_NEXT
, ו-SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
תואם ל-ACTION_SKIP_TO_PREVIOUS
. משתמשים בערכי הקבועים האלה כמפתחות בחבילה, ובערך הבוליאני true
לערכים שלהם.
הגדרת PlaybackState ראשוני
כש-Android Auto ו-Android Automotive 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
.
אפשר להשתמש בפעולות בהתאמה אישית כדי ליצור התנהגות שונה מפעולות רגילות. אל תשתמשו בהן כדי להחליף או לשכפל פעולות רגילות.
אפשר להוסיף פעולות מותאמות אישית באמצעות method addCustomAction()
במחלקה PlaybackStateCompat.Builder
.
קטע הקוד הבא מראה איך להוסיף פעולה מותאמת אישית של 'הפעלת ערוץ רדיו':
Kotlin
stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon ).run { setExtras(customActionExtras) build() } )
Java
stateBuilder.addCustomAction( new PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon) .setExtras(customActionExtras) .build());
דוגמה מפורטת יותר לשיטה הזו מופיעה ב-method 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)) { ... } }
דוגמה מפורטת יותר לשיטה הזו מופיעה ב-method onCustomAction
באפליקציית Universal Android Music Player לדוגמה ב-GitHub.
סמלים של פעולות בהתאמה אישית
לכל פעולה בהתאמה אישית שיוצרים נדרש משאב סמל. אפליקציות במכוניות יכולות לפעול במגוון גדלים ודחיסות של מסכים, ולכן הסמלים שאתם מספקים צריכים להיות פריטים שניתן לצייר בצורה וקטורית. כלי גרפי וקטורי מאפשר להתאים את גודל הנכסים בלי לאבד את הפרטים. אפשר גם ליישר בקלות קצוות ופינות לגבולות של פיקסלים ברזולוציות קטנות יותר, באמצעות שרטוט בווקטור.
אם פעולה מותאמת אישית היא עם שמירת מצב, למשל, היא מפעילה או משביתה הגדרות הפעלה – צריך לספק סמלים שונים למצבים השונים, כדי שהמשתמשים יוכלו לראות שינוי כשהם בוחרים בפעולה.
לספק סגנונות סמלים חלופיים לפעולות מושבתות
כשאין פעולה מותאמת אישית זמינה בהקשר הנוכחי, מחליפים את סמל הפעולה המותאמת אישית בסמל חלופי שמראה שהפעולה מושבתת.
ציון פורמט האודיו
כדי לציין שמדיה שמופעלת כרגע משתמשת בפורמט אודיו מיוחד, אפשר לציין סמלים שמעובדים במכוניות שתומכות בתכונה הזו. אפשר להגדיר את הערכים של 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 אם המשתמש לא מציין מונחי חיפוש.
לדוגמה, אם המשתמש אומר "Play some music". במקרה כזה, יכול להיות שהאפליקציה תתחיל נגינה של טראק שהושמעו לאחרונה או של טראק חדש שהוצעה.
אם אי אפשר לעבד חיפוש במהירות, אין לחסום אותו ב-onPlayFromSearch()
.
במקום זאת, מגדירים את מצב ההפעלה ל-STATE_CONNECTING
ומבצעים את החיפוש בשרשור אסינכררוני.
לאחר שההפעלה מתחילה, כדאי לאכלס את התור של סשן המדיה בתוכן קשור. לדוגמה, אם משתמש מבקש להשמיע אלבום, האפליקציה עשויה למלא את התור ברשימת הטראקים של האלבום. מומלץ גם להטמיע תמיכה בתוצאות חיפוש שניתן לעיין בהן כדי שהמשתמשים יוכלו לבחור טראק אחר שתואם לשאילתה שלהם.
בנוסף לשאילתות הפעלה, מערכות Android Auto ו-Android Automotive OS גם מזהות שאילתות קוליות כדי לשלוט בהפעלה כמו pause music ו-Next
song, ולהתאים את הפקודות האלה לקריאות החוזרות (callback) המתאימות של סשן המדיה, כמו onPause()
ו-onSkipToNext()
.
דוגמה מפורטת להטמעת פעולות הפעלה מבוססות-קול באפליקציה מופיעה במאמר Google Assistant ואפליקציות מדיה.
הטמעת אמצעי הגנה מפני הסחות דעת
הטלפון של המשתמש מחובר לרמקולים של הרכב בזמן השימוש ב-Android Auto, לכן צריך לנקוט אמצעי זהירות נוספים כדי למנוע הסחת דעת של הנהג.
השבתת ההתראות ברכב
אפליקציות מדיה ב-Android Auto לא יכולות להתחיל להשמיע אודיו דרך הרמקולים ברכב, אלא אם המשתמש מתחיל את ההפעלה, למשל על ידי לחיצה על לחצן הפעלה. גם אם אתם מגדירים התראה שתוזמנה על ידי המשתמש מאפליקציית המדיה, אסור שהיא תתחיל להשמיע מוזיקה דרך הרמקולים של הרכב.
כדי לעמוד בדרישה הזו, האפליקציה יכולה להשתמש ב-CarConnection
כאות לפני הפעלת אודיו. האפליקציה יכולה לבדוק אם הטלפון מקרין למסך הרכב על ידי בדיקת הערך של LiveData
עבור סוג החיבור לרכב, ובדיקה אם הוא שווה ל-CONNECTION_TYPE_PROJECTION
.
אם הטלפון של המשתמש מקרין, אפליקציות מדיה שתומכות בהתראות צריכות לבצע אחת מהפעולות הבאות:
- משביתים את ההתראה.
- להפעיל את ההתראה דרך
STREAM_ALARM
ולהציג ממשק משתמש במסך הטלפון כדי להשבית את ההתראה.
טיפול במודעות מדיה
כברירת מחדל, Android Auto מציג התראה כשהמטא-נתונים של המדיה משתנים במהלך סשן של הפעלת אודיו. כשאפליקציית מדיה עוברת מהפעלת מוזיקה להפעלת פרסומת, היא מסיחה את דעת המשתמש ומוצגת לו התראה. כדי למנוע מ-Android Auto להציג התראה במקרה כזה, צריך להגדיר את מפתח המטא-נתונים של המדיה METADATA_KEY_IS_ADVERTISEMENT
לערך METADATA_VALUE_ATTRIBUTE_PRESENT
, כפי שמוצג בקטע הקוד הבא:
Kotlin
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
Java
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
טיפול בשגיאות כלליות
כשמתרחשת שגיאה באפליקציה, מגדירים את מצב ההפעלה ל-STATE_ERROR
ומספקים הודעת שגיאה באמצעות השיטה setErrorMessage()
. ב-PlaybackStateCompat
תוכלו למצוא רשימה של קודי שגיאה שאפשר להשתמש בהם כשמגדירים את הודעת השגיאה.
הודעות השגיאה צריכות להיות גלויות למשתמש ולעבור תהליך לוקליזציה בהתאם לאזור הזמן הנוכחי של המשתמש. לאחר מכן, Android Auto ו-Android Automotive 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 צריך לפתוח את האפליקציה בטלפון כדי לפתור שגיאה, צריך לציין את המידע הזה בהודעה למשתמש. לדוגמה, הודעת השגיאה עשויה להופיע כ "כניסה אל [שם האפליקציה]" במקום כ "יש להיכנס לחשבון".