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()
. בהטמעה של השיטהonCreate()
, צריך ליצור ולרשום אובייקט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()
הטמעה של onGetRoot
השיטה 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()
כשמתבצעת קריאה לשיטה onGetRoot()
של השירות, החבילה הקוראת מעבירה מידע מזהה לשירות. השירות יכול להשתמש במידע הזה כדי להחליט אם לחבילה הזו תהיה גישה לתוכן שלכם. לדוגמה, אפשר להגביל את הגישה לתוכן של האפליקציה לרשימת חבילות שאושרו, על ידי השוואת clientPackageName
לרשימת ההיתרים ואימות האישור ששימש לחתימה על קובץ ה-APK של החבילה. אם לא ניתן לאמת את החבילה, צריך להחזיר את הערך null
כדי לדחות את הגישה לתוכן.
כדי לתת לאפליקציות מערכת, כמו Android Auto ו-Android Automotive OS, גישה לתוכן שלכם, השירות שלכם תמיד צריך להחזיר ערך BrowserRoot
שאינו null כשאפליקציות המערכת האלה קוראות לשיטה 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()
אחרי קבלת אובייקט הצומת ברמה הבסיסית, מערכת Android Auto ומערכת Android Automotive OS יוצרות תפריט ברמה העליונה באמצעות קריאה ל-onLoadChildren()
באובייקט הצומת ברמה הבסיסית כדי לקבל את הצאצאים שלו. אפליקציות לקוח יוצרות תפריטי משנה על ידי קריאה לאותה שיטה באמצעות אובייקטים של צמתים צאצאים.
כל צומת בהיררכיית התוכן מיוצג באובייקט MediaBrowserCompat.MediaItem
. כל אחד מפריטי המדיה האלה מזוהה באמצעות מחרוזת מזהה ייחודית. אפליקציות לקוח מתייחסות למחרוזות המזהה האלה כאסימונים אטומים. כשאפליקציית לקוח רוצה לעבור לתפריט משנה או להפעיל פריט מדיה, היא מעבירה את האסימון. האפליקציה שלכם אחראית לשייך את האסימון לפריט המדיה המתאים.
קטע הקוד הבא מציג הטמעה פשוטה של השיטה onLoadChildren()
:
Kotlin
override fun onLoadChildren( parentMediaId: String, result: Result<List<MediaBrowserCompat.MediaItem>> ) { // Assume for example that the music catalog is already loaded/cached. val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf() // Check whether this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // Build the MediaItem objects for the top level // and put them in the mediaItems list. } else { // Examine the passed parentMediaId to see which submenu we're at // and put the children of that menu in the mediaItems list. } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaBrowserCompat.MediaItem>> result) { // Assume for example that the music catalog is already loaded/cached. List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>(); // Check whether this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level // and put them in the mediaItems list. } else { // Examine the passed parentMediaId to see which submenu we're at // and put the children of that menu in the mediaItems list. } result.sendResult(mediaItems); }
דוגמה מלאה לשימוש בשיטה הזו מופיעה ב-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")
- פריט מדיה C עם
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
- פריט מדיה D עם
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()
.
בקטע הקוד הבא מוסבר איך להציג אינדיקטורים לגבי פריט מדיה בוטה שהושלם ב-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
כדי לקשר את התוכן הנוכחי לפריטי המדיה בתצוגת העיון. כדי שסרגל ההתקדמות יתעדכן באופן אוטומטי בפריט המדיה, צריך לעמוד בדרישות הבאות:
- כשיוצרים את ה-
MediaItem
, צריך לשלוח אתDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
בתוספים שלו עם ערך בין 0.0 ל-1.0, כולל. - ה-
MediaMetadataCompat
צריך לשלוח אתMETADATA_KEY_MEDIA_ID
עם ערך מחרוזת שווה למזהה המדיה שהוענק ל-MediaItem
. - ה-
PlaybackStateCompat
חייב לכלול פריט נוסף עם המפתחPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
שממופה לערך מחרוזת שווה ל-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
.
בקטע הקוד הבא מוסבר איך מפעילים את התמיכה בשיטה 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
extrasBundle
, מוסיפים את רשימת הפעולות בתור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()
- יצירת חבילה של תוצאות פיתוח
- שליחת הודעה למשתמש
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%.
דוגמה: פעולה מועדפת
דוגמה נוספת היא פעולה מועדפת עם שני מצבים:
- מועדף: הפעולה הזו מוצגת לפריטים שלא נמצאים ברשימת המועדפים של המשתמש. כשהמשתמש בוחר בפעולה הזו, אפשר להחליף אותה באפשרות 'הוספה למועדפים' ולהפעיל את
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()); ... }
כשיוצרים את אובייקט סשן המדיה, מגדירים אובייקט קריאה חוזרת (callback) שמשמש לטיפול בבקשות לבקרת ההפעלה. כדי ליצור את אובייקט הקריאה החוזרת, צריך לספק הטמעה של הכיתה MediaSessionCompat.Callback
לאפליקציה. בקטע הבא מוסבר איך מטמיעים את האובייקט הזה.
הטמעת פקודות הפעלה
כשמשתמש מבקש הפעלה של פריט מדיה מהאפליקציה, מערכת ההפעלה של Android Automotive ו-Android Auto משתמשות בכיתה MediaSessionCompat.Callback
מהאובייקט MediaSessionCompat
של האפליקציה, שהן קיבלו משירות דפדפן המדיה של האפליקציה. כשמשתמש רוצה לשלוט בהפעלת התוכן, למשל להשהות את ההפעלה או לדלג למסלול הבא, Android Auto ו-Android Automotive OS מפעילים אחת מהשיטות של אובייקט ה-callback.
כדי לטפל בהפעלת תוכן, האפליקציה צריכה להרחיב את הכיתה המופשטת MediaSessionCompat.Callback
ולהטמיע את השיטות שהאפליקציה תומכת בהן.
מטמיעים את כל שיטות ה-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 ישאירו את המרחב ריק בכל פעם שהאפליקציה שלכם לא תומכת בפונקציה המתאימה. לשם כך, צריך להפעיל את השיטה 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
.
אפשר להשתמש בפעולות בהתאמה אישית כדי ליצור התנהגות שונה מפעולות רגילות. אל תשתמשו בהן כדי להחליף או לשכפל פעולות רגילות.
אפשר להוסיף פעולות בהתאמה אישית באמצעות השיטה addCustomAction()
בכיתה PlaybackStateCompat.Builder
.
קטע הקוד הבא מראה איך מוסיפים פעולה בהתאמה אישית מסוג 'הפעלת ערוץ רדיו':
Kotlin
val customActionExtras = Bundle() customActionExtras.putInt( androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, androidx.media3.session.CommandButton.ICON_RADIO) stateBuilder.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon // or R.drawable.media3_icon_radio ).run { setExtras(customActionExtras) build() } )
Java
Bundle customActionExtras = new Bundle(); customActionExtras.putInt( androidx.media3.session.MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, androidx.media3.session.CommandButton.ICON_RADIO); stateBuilder.addCustomAction( new PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_START_RADIO_FROM_MEDIA, resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon) // or R.drawable.media3_icon_radio .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.
סמלים של פעולות בהתאמה אישית
לכל פעולה בהתאמה אישית שיוצרים צריך סמל.
אם התיאור של הסמל הזה תואם לאחד מהקבועים CommandButton.ICON_
, צריך להגדיר את ערך המספר השלם הזה למפתח EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT
של התוספים של הפעולה בהתאמה אישית. במערכות נתמכות, הפעולה הזו תגביל את המשאב של הסמל שמוענק ל-CustomAction.Builder
, וכך רכיבי המערכת יוכלו ליצור את הפעולה שלכם ואת פעולות ההפעלה האחרות בסגנון עקבי.
צריך לציין גם משאב של סמל. אפליקציות ברכב יכולות לפעול במסכים בגדלים ובדחיסות שונים, ולכן הסמלים שאתם מספקים צריכים להיות קובצי וקטור ניתנים לציור. קובץ drawable וקטורי מאפשר לשנות את הגודל של נכסים בלי לאבד את הפרטים. גרפיקה וקטורית גם מאפשרת ליישר בקלות קצוות ופינות לגבולות פיקסלים ברזולוציות קטנות יותר.
אם פעולה מותאמת אישית היא סטטוסית – לדוגמה, היא מפעילה או משביתה הגדרת הפעלה – צריך לספק סמלים שונים למצבים השונים, כדי שהמשתמשים יוכלו לראות שינוי כשהם בוחרים את הפעולה.
לספק סגנונות סמלים חלופיים לפעולות מושבתות
כשפעולה מותאמת אישית לא זמינה בהקשר הנוכחי, מחליפים את הסמל של הפעולה המותאמת אישית בסמל חלופי שמציין שהפעולה מושבתת.
לציין את פורמט האודיו
כדי לציין שהמדיה שמושמעת כרגע משתמשת בפורמט אודיו מיוחד, אפשר לציין סמלים שיוצגו ברכב שתומך בתכונה הזו. אפשר להגדיר את הערכים של 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
ומבצעים את החיפוש בשרשור אסינכררוני.
אחרי שההפעלה מתחילה, מומלץ לאכלס את התור של סשן המדיה בתוכן קשור. לדוגמה, אם המשתמש מבקש להפעיל אלבום, האפליקציה עשויה למלא את התור ברשימת הטראקים של האלבום. מומלץ גם להטמיע תמיכה בתוצאות חיפוש שניתן לעיין בהן כדי שהמשתמשים יוכלו לבחור טראק אחר שתואם לשאילתה שלהם.
בנוסף לשאילתות מסוג play, Android Auto ו-Android Automotive OS מזהים שאילתות קוליות לצורך בקרה על ההפעלה, כמו pause music ו-next song, ומתאימים את הפקודות האלה להחזרות קריאה מתאימות של סשן המדיה, כמו 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 צריך לפתוח את האפליקציה בטלפון כדי לפתור שגיאה, צריך לציין את המידע הזה בהודעה. לדוגמה, הודעת השגיאה עשויה להופיע כ "כניסה אל [שם האפליקציה]" במקום כ "יש להיכנס לחשבון".