গাড়ির জন্য মিডিয়া অ্যাপস তৈরি করুন

Android Auto এবং Android Automotive OS আপনাকে তাদের গাড়িতে থাকা ব্যবহারকারীদের কাছে আপনার মিডিয়া অ্যাপের সামগ্রী আনতে সাহায্য করে। গাড়ির জন্য একটি মিডিয়া অ্যাপকে অবশ্যই একটি মিডিয়া ব্রাউজার পরিষেবা প্রদান করতে হবে যাতে Android Auto এবং Android Automotive OS বা মিডিয়া ব্রাউজার সহ অন্য কোনও অ্যাপ আপনার সামগ্রী আবিষ্কার করতে এবং প্রদর্শন করতে পারে৷

এই নির্দেশিকাটি অনুমান করে যে আপনার কাছে ইতিমধ্যেই একটি মিডিয়া অ্যাপ রয়েছে যা ফোনে অডিও চালায় এবং আপনার মিডিয়া অ্যাপটি Android মিডিয়া অ্যাপ আর্কিটেকচারের সাথে সামঞ্জস্যপূর্ণ।

এই নির্দেশিকাটি একটি MediaBrowserService এবং MediaSession এর প্রয়োজনীয় উপাদানগুলি বর্ণনা করে যা আপনার অ্যাপের Android Auto বা Android Automotive OS-এ কাজ করার জন্য প্রয়োজন৷ আপনি মূল মিডিয়া পরিকাঠামো সম্পন্ন করার পরে, আপনি Android Auto-এর জন্য সমর্থন যোগ করতে পারেন এবং আপনার মিডিয়া অ্যাপে Android Automotive OS-এর জন্য সমর্থন যোগ করতে পারেন

আপনি শুরু করার আগে

  1. অ্যান্ড্রয়েড মিডিয়া API ডকুমেন্টেশন পর্যালোচনা করুন।
  2. ডিজাইন গাইডেন্সের জন্য মিডিয়া অ্যাপ তৈরি করুন পর্যালোচনা করুন।
  3. এই বিভাগে তালিকাভুক্ত মূল শর্তাবলী এবং ধারণাগুলি পর্যালোচনা করুন।

মূল শর্তাবলী এবং ধারণা

মিডিয়া ব্রাউজার পরিষেবা
আপনার মিডিয়া অ্যাপ দ্বারা বাস্তবায়িত একটি Android পরিষেবা যা MediaBrowserServiceCompat API-এর সাথে সম্মতি দেয়। আপনার অ্যাপ এর বিষয়বস্তু প্রকাশ করতে এই পরিষেবাটি ব্যবহার করে।
মিডিয়া ব্রাউজার
মিডিয়া ব্রাউজার পরিষেবাগুলি আবিষ্কার করতে এবং তাদের সামগ্রী প্রদর্শন করতে মিডিয়া অ্যাপগুলি দ্বারা ব্যবহৃত একটি API৷ Android Auto এবং Android Automotive OS আপনার অ্যাপের মিডিয়া ব্রাউজার পরিষেবা খুঁজে পেতে একটি মিডিয়া ব্রাউজার ব্যবহার করে।
মিডিয়া আইটেম

মিডিয়া ব্রাউজার MediaItem অবজেক্টের একটি ট্রিতে এর বিষয়বস্তু সংগঠিত করে। একটি মিডিয়া আইটেমে নিম্নলিখিত পতাকাগুলির মধ্যে একটি বা উভয়ই থাকতে পারে:

  • FLAG_PLAYABLE : নির্দেশ করে যে আইটেমটি বিষয়বস্তুর গাছের একটি পাতা। আইটেমটি একটি একক সাউন্ড স্ট্রিম প্রতিনিধিত্ব করে, যেমন একটি অ্যালবামের একটি গান, একটি অডিও বইয়ের একটি অধ্যায়, বা একটি পডকাস্টের একটি পর্ব৷
  • FLAG_BROWSABLE : নির্দেশ করে যে আইটেমটি বিষয়বস্তু গাছের একটি নোড এবং এতে শিশু রয়েছে৷ উদাহরণস্বরূপ, আইটেমটি একটি অ্যালবামের প্রতিনিধিত্ব করে এবং এর বাচ্চারা অ্যালবামের গান।

একটি মিডিয়া আইটেম যা ব্রাউজ করা যায় এবং খেলার যোগ্য উভয়ই একটি প্লেলিস্টের মতো কাজ করে৷ আপনি আইটেমটি নিজেই এর সমস্ত বাচ্চাদের খেলার জন্য নির্বাচন করতে পারেন, অথবা আপনি এর বাচ্চাদের ব্রাউজ করতে পারেন।

যানবাহন-অপ্টিমাইজড

Android Automotive OS অ্যাপের জন্য একটি কার্যকলাপ যা Android Automotive OS ডিজাইন নির্দেশিকা মেনে চলে। এই ক্রিয়াকলাপের জন্য ইন্টারফেসটি Android Automotive OS দ্বারা আঁকা হয় না, তাই আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনার অ্যাপটি ডিজাইন নির্দেশিকা মেনে চলে। সাধারণত, এর মধ্যে বড় ট্যাপ লক্ষ্য এবং ফন্টের আকার, দিন এবং রাতের মোডগুলির জন্য সমর্থন এবং উচ্চতর বৈসাদৃশ্য অনুপাত অন্তর্ভুক্ত থাকে।

গাড়ি ব্যবহারকারীর অভিজ্ঞতা সীমাবদ্ধতা (CUXRs) কার্যকর না হলে গাড়ি-অপ্টিমাইজ করা ইউজার ইন্টারফেসগুলি শুধুমাত্র প্রদর্শিত হওয়ার অনুমতি দেওয়া হয়, কারণ এই ইন্টারফেসগুলির জন্য ব্যবহারকারীর কাছ থেকে বর্ধিত মনোযোগ বা মিথস্ক্রিয়া প্রয়োজন হতে পারে। CUXRগুলি যখন গাড়ি থামানো বা পার্ক করা হয় তখন কার্যকর হয় না কিন্তু গাড়ি চলাকালীন সর্বদা কার্যকর হয়৷

আপনাকে অ্যান্ড্রয়েড অটোর জন্য ক্রিয়াকলাপ ডিজাইন করার দরকার নেই, কারণ অ্যান্ড্রয়েড অটো আপনার মিডিয়া ব্রাউজার পরিষেবা থেকে তথ্য ব্যবহার করে নিজস্ব গাড়ি-অপ্টিমাইজ করা ইন্টারফেস আঁকে৷

আপনার অ্যাপের ম্যানিফেস্ট ফাইল কনফিগার করুন

আপনি আপনার মিডিয়া ব্রাউজার পরিষেবা তৈরি করার আগে, আপনাকে আপনার অ্যাপের ম্যানিফেস্ট ফাইলগুলি কনফিগার করতে হবে৷

আপনার মিডিয়া ব্রাউজার পরিষেবা ঘোষণা করুন

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 সিস্টেম UI-তে আপনার অ্যাপকে উপস্থাপন করতে ব্যবহার করতে পারে। দুটি আইকন প্রকার প্রয়োজন:

  • লঞ্চার আইকন
  • অ্যাট্রিবিউশন আইকন

লঞ্চার আইকন

লঞ্চার আইকনটি আপনার অ্যাপটিকে সিস্টেম UI-তে উপস্থাপন করে, যেমন লঞ্চারে এবং আইকনগুলির ট্রেতে। আপনি উল্লেখ করতে পারেন যে আপনি নিম্নলিখিত ম্যানিফেস্ট ঘোষণা ব্যবহার করে আপনার গাড়ি মিডিয়া অ্যাপের প্রতিনিধিত্ব করতে আপনার মোবাইল অ্যাপ থেকে আইকনটি ব্যবহার করতে চান:

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

আপনার মোবাইল অ্যাপের থেকে আলাদা আইকন ব্যবহার করতে, ম্যানিফেস্টে আপনার মিডিয়া ব্রাউজার পরিষেবার <service> উপাদানে android:icon বৈশিষ্ট্য সেট করুন:

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

অ্যাট্রিবিউশন আইকন

চিত্র 1. মিডিয়া কার্ডে অ্যাট্রিবিউশন আইকন।

অ্যাট্রিবিউশন আইকনটি এমন জায়গায় ব্যবহার করা হয় যেখানে মিডিয়া বিষয়বস্তু প্রাধান্য পায়, যেমন মিডিয়া কার্ডে। বিজ্ঞপ্তিগুলির জন্য ব্যবহৃত ছোট আইকনটি পুনরায় ব্যবহার করার কথা বিবেচনা করুন৷ এই আইকনটি একরঙা হতে হবে। আপনি নিম্নলিখিত ম্যানিফেস্ট ঘোষণা ব্যবহার করে আপনার অ্যাপের প্রতিনিধিত্ব করতে ব্যবহৃত একটি আইকন নির্দিষ্ট করতে পারেন:

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

আপনার মিডিয়া ব্রাউজার পরিষেবা তৈরি করুন

আপনি MediaBrowserServiceCompat ক্লাস প্রসারিত করে একটি মিডিয়া ব্রাউজার পরিষেবা তৈরি করুন৷ অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস উভয়ই নিম্নলিখিতগুলি করতে আপনার পরিষেবা ব্যবহার করতে পারে:

  • ব্যবহারকারীর কাছে একটি মেনু উপস্থাপন করতে আপনার অ্যাপের বিষয়বস্তু অনুক্রম ব্রাউজ করুন।
  • অডিও প্লেব্যাক নিয়ন্ত্রণ করতে আপনার অ্যাপের MediaSessionCompat অবজেক্টের জন্য টোকেন পান।

অন্য ক্লায়েন্টদের আপনার অ্যাপ থেকে মিডিয়া সামগ্রী অ্যাক্সেস করতে দেওয়ার জন্য আপনি আপনার মিডিয়া ব্রাউজার পরিষেবা ব্যবহার করতে পারেন। এই মিডিয়া ক্লায়েন্টগুলি ব্যবহারকারীর ফোনে অন্যান্য অ্যাপ হতে পারে, অথবা তারা অন্য দূরবর্তী ক্লায়েন্ট হতে পারে।

মিডিয়া ব্রাউজার সার্ভিস ওয়ার্কফ্লো

এই বিভাগটি বর্ণনা করে যে কীভাবে Android Automotive OS এবং Android Auto একটি সাধারণ ব্যবহারকারীর কর্মপ্রবাহের সময় আপনার মিডিয়া ব্রাউজার পরিষেবার সাথে ইন্টারঅ্যাক্ট করে।

  1. ব্যবহারকারী আপনার অ্যাপটি Android Automotive OS বা Android Auto-এ চালু করেন।
  2. Android Automotive OS বা Android Auto onCreate() পদ্ধতি ব্যবহার করে আপনার অ্যাপের মিডিয়া ব্রাউজার পরিষেবার সাথে যোগাযোগ করে। আপনার onCreate() পদ্ধতির বাস্তবায়নে, আপনাকে অবশ্যই একটি MediaSessionCompat অবজেক্ট এবং এর কলব্যাক অবজেক্ট তৈরি এবং নিবন্ধন করতে হবে।
  3. Android Automotive OS বা Android Auto আপনার বিষয়বস্তু অনুক্রমের রুট মিডিয়া আইটেম পেতে আপনার পরিষেবার onGetRoot() পদ্ধতিতে কল করে। রুট মিডিয়া আইটেম প্রদর্শিত হয় না; পরিবর্তে, এটি আপনার অ্যাপ থেকে আরও সামগ্রী পুনরুদ্ধার করতে ব্যবহৃত হয়।
  4. Android Automotive OS বা Android Auto আপনার পরিষেবার onLoadChildren() পদ্ধতিতে রুট মিডিয়া আইটেমের বাচ্চা পেতে কল করে। অ্যান্ড্রয়েড অটোমোটিভ ওএস এবং অ্যান্ড্রয়েড অটো এই মিডিয়া আইটেমগুলিকে বিষয়বস্তুর আইটেমগুলির শীর্ষ স্তর হিসাবে প্রদর্শন করে৷ সিস্টেম এই স্তরে কি আশা করে সে সম্পর্কে আরও তথ্যের জন্য এই পৃষ্ঠায় রুট মেনু গঠন দেখুন।
  5. ব্যবহারকারী যদি একটি ব্রাউজযোগ্য মিডিয়া আইটেম নির্বাচন করে, তাহলে নির্বাচিত মেনু আইটেমের বাচ্চাদের পুনরুদ্ধার করতে আপনার পরিষেবার onLoadChildren() পদ্ধতিটি আবার কল করা হবে।
  6. যদি ব্যবহারকারী একটি প্লেযোগ্য মিডিয়া আইটেম নির্বাচন করে, তাহলে Android Automotive OS বা Android Auto সেই ক্রিয়া সম্পাদনের জন্য উপযুক্ত মিডিয়া সেশন কলব্যাক পদ্ধতিতে কল করে।
  7. আপনার অ্যাপ দ্বারা সমর্থিত হলে, ব্যবহারকারী আপনার সামগ্রী অনুসন্ধান করতে পারেন। এই ক্ষেত্রে, Android Automotive OS বা Android Auto আপনার পরিষেবার onSearch() পদ্ধতিতে কল করুন৷

আপনার বিষয়বস্তু অনুক্রম তৈরি করুন

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস আপনার অ্যাপের মিডিয়া ব্রাউজার পরিষেবাতে কল করে কোন সামগ্রী উপলব্ধ তা খুঁজে বের করতে। এটি সমর্থন করার জন্য আপনাকে আপনার মিডিয়া ব্রাউজার পরিষেবাতে দুটি পদ্ধতি প্রয়োগ করতে হবে: onGetRoot() এবং onLoadChildren()

গেটরুট প্রয়োগ করুন

আপনার পরিষেবার onGetRoot() পদ্ধতি আপনার বিষয়বস্তু অনুক্রমের রুট নোড সম্পর্কে তথ্য প্রদান করে। Android Auto এবং Android Automotive OS onLoadChildren() পদ্ধতি ব্যবহার করে আপনার বাকি সামগ্রীর অনুরোধ করতে এই রুট নোড ব্যবহার করে।

নিম্নলিখিত কোড স্নিপেট onGetRoot() পদ্ধতির একটি সহজ বাস্তবায়ন দেখায়:

কোটলিন

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)

জাভা

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

এই পদ্ধতির আরও বিস্তারিত উদাহরণের জন্য, GitHub-এ ইউনিভার্সাল অ্যান্ড্রয়েড মিউজিক প্লেয়ার নমুনা অ্যাপে onGetRoot() পদ্ধতিটি দেখুন।

onGetRoot() এর জন্য প্যাকেজ বৈধতা যোগ করুন

যখন আপনার পরিষেবার onGetRoot() পদ্ধতিতে একটি কল করা হয়, কলিং প্যাকেজটি আপনার পরিষেবাতে সনাক্তকারী তথ্য প্রেরণ করে৷ আপনার পরিষেবা এই তথ্যটি ব্যবহার করে সিদ্ধান্ত নিতে পারে যে প্যাকেজটি আপনার সামগ্রী অ্যাক্সেস করতে পারে কিনা৷ উদাহরণ স্বরূপ, আপনি clientPackageName আপনার অনুমোদিত তালিকার সাথে তুলনা করে এবং প্যাকেজের APK সাইন করার জন্য ব্যবহৃত শংসাপত্র যাচাই করে অনুমোদিত প্যাকেজের তালিকায় আপনার অ্যাপের সামগ্রীতে অ্যাক্সেস সীমাবদ্ধ করতে পারেন। যদি প্যাকেজটি যাচাই করা না যায়, তাহলে আপনার সামগ্রীতে অ্যাক্সেস অস্বীকার করতে null ফেরত দিন।

আপনার সামগ্রীতে অ্যাক্সেস সহ Android Auto এবং Android Automotive OS-এর মতো সিস্টেম অ্যাপগুলি প্রদান করতে, এই সিস্টেম অ্যাপগুলি onGetRoot() পদ্ধতিতে কল করলে আপনার পরিষেবাকে অবশ্যই একটি নন-নাল BrowserRoot ফেরত দিতে হবে। 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
}

এই কোড স্নিপেটটি GitHub-এ ইউনিভার্সাল অ্যান্ড্রয়েড মিউজিক প্লেয়ার নমুনা অ্যাপের PackageValidator ক্লাসের একটি অংশ। আপনার পরিষেবার onGetRoot() পদ্ধতির জন্য প্যাকেজ বৈধতা কীভাবে প্রয়োগ করবেন তার আরও বিশদ উদাহরণের জন্য সেই ক্লাসটি দেখুন।

সিস্টেম অ্যাপ্লিকেশানগুলিকে অনুমতি দেওয়ার পাশাপাশি, আপনাকে অবশ্যই Google সহকারীকে আপনার MediaBrowserService এর সাথে সংযোগ করতে দিতে হবে৷ মনে রাখবেন যে Google সহকারীর ফোনের জন্য আলাদা প্যাকেজের নাম রয়েছে, যার মধ্যে Android Auto এবং Android Automotive OS-এর জন্য রয়েছে।

LoadChildren() এর উপর প্রয়োগ করুন

আপনার রুট নোড অবজেক্ট পাওয়ার পর, Android Auto এবং Android Automotive OS এর বাচ্চাদের পেতে রুট নোড অবজেক্টে onLoadChildren() কল করে একটি শীর্ষ-স্তরের মেনু তৈরি করে। ক্লায়েন্ট অ্যাপগুলি চাইল্ড নোড অবজেক্ট ব্যবহার করে একই পদ্ধতিতে কল করে সাবমেনু তৈরি করে।

আপনার বিষয়বস্তু অনুক্রমের প্রতিটি নোড একটি MediaBrowserCompat.MediaItem অবজেক্ট দ্বারা প্রতিনিধিত্ব করা হয়। এই মিডিয়া আইটেমগুলির প্রতিটি একটি অনন্য আইডি স্ট্রিং দ্বারা চিহ্নিত করা হয়। ক্লায়েন্ট অ্যাপগুলি এই আইডি স্ট্রিংগুলিকে অস্বচ্ছ টোকেন হিসাবে বিবেচনা করে৷ যখন একটি ক্লায়েন্ট অ্যাপ একটি সাবমেনুতে ব্রাউজ করতে চায়, বা একটি মিডিয়া আইটেম চালাতে চায়, তখন এটি টোকেন পাস করে। আপনার অ্যাপ উপযুক্ত মিডিয়া আইটেমের সাথে টোকেন সংযুক্ত করার জন্য দায়ী।

নিম্নলিখিত কোড স্নিপেট onLoadChildren() পদ্ধতির একটি সহজ বাস্তবায়ন দেখায়:

কোটলিন

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

জাভা

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

এই পদ্ধতির একটি সম্পূর্ণ উদাহরণের জন্য, GitHub-এ ইউনিভার্সাল অ্যান্ড্রয়েড মিউজিক প্লেয়ার নমুনা অ্যাপে onLoadChildren() পদ্ধতিটি দেখুন।

রুট মেনু গঠন করুন

চিত্র 2. রুট বিষয়বস্তু নেভিগেশনাল ট্যাব হিসাবে প্রদর্শিত হয়।

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস-এর রুট মেনুর গঠন সম্পর্কে নির্দিষ্ট সীমাবদ্ধতা রয়েছে। এগুলি রুট ইঙ্গিতগুলির মাধ্যমে MediaBrowserService এর সাথে যোগাযোগ করা হয়, যা onGetRoot() এ পাস করা Bundle আর্গুমেন্টের মাধ্যমে পড়া যেতে পারে। এই ইঙ্গিতগুলি অনুসরণ করলে সিস্টেমটি ন্যাভিগেশনাল ট্যাব হিসাবে সর্বোত্তমভাবে রুট বিষয়বস্তু প্রদর্শন করতে দেয়। আপনি যদি এই ইঙ্গিতগুলি অনুসরণ না করেন, তাহলে সিস্টেমের দ্বারা কিছু রুট সামগ্রী বাদ দেওয়া বা কম আবিষ্কারযোগ্য করা হতে পারে৷ দুটি ইঙ্গিত পাঠানো হয়:

প্রাসঙ্গিক রুট ইঙ্গিত পড়তে নিম্নলিখিত কোড ব্যবহার করুন:

কোটলিন

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...
}

জাভা

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...
}

আপনি এই ইঙ্গিতগুলির মানগুলির উপর ভিত্তি করে আপনার বিষয়বস্তু অনুক্রমের কাঠামোর জন্য যুক্তির শাখা বেছে নিতে পারেন, বিশেষ করে যদি আপনার অনুক্রমটি Android Auto এবং Android Automotive OS এর বাইরে MediaBrowser ইন্টিগ্রেশনের মধ্যে পরিবর্তিত হয়। উদাহরণস্বরূপ, যদি আপনি সাধারণত একটি রুট খেলার যোগ্য আইটেম দেখান, তাহলে সমর্থিত পতাকা ইঙ্গিতের মানের কারণে আপনি এটিকে একটি রুট ব্রাউজযোগ্য আইটেমের অধীনে নেস্ট করতে চাইতে পারেন।

রুট ইঙ্গিতগুলি ছাড়াও, ট্যাবগুলি সর্বোত্তমভাবে রেন্ডার করতে সহায়তা করার জন্য অনুসরণ করার জন্য কয়েকটি অতিরিক্ত নির্দেশিকা রয়েছে:

  • প্রতিটি ট্যাব আইটেমের জন্য একরঙা, বিশেষত সাদা, আইকন সরবরাহ করুন।
  • প্রতিটি ট্যাব আইটেমের জন্য সংক্ষিপ্ত কিন্তু অর্থপূর্ণ লেবেল সরবরাহ করুন। লেবেলগুলি ছোট রাখলে স্ট্রিংগুলি কেটে যাওয়ার সম্ভাবনা কমে যায়৷

মিডিয়া আর্টওয়ার্ক প্রদর্শন করুন

মিডিয়া আইটেমগুলির জন্য আর্টওয়ার্ক অবশ্যই একটি স্থানীয় URI হিসাবে ContentResolver.SCHEME_CONTENT বা ContentResolver.SCHEME_ANDROID_RESOURCE ব্যবহার করে পাস করতে হবে। এই স্থানীয় ইউআরআইকে অবশ্যই একটি বিটম্যাপ বা অ্যাপ্লিকেশনের সংস্থানগুলিতে আঁকাযোগ্য ভেক্টরের সমাধান করতে হবে। MediaDescriptionCompat বস্তুর জন্য বিষয়বস্তু অনুক্রমের আইটেম প্রতিনিধিত্ব করে, setIconUri() মাধ্যমে URI পাস করুন। MediaMetadataCompat বস্তুর জন্য যা বর্তমানে বাজানো আইটেমের প্রতিনিধিত্ব করে, নিচের যে কোনো একটি ব্যবহার করে putString() এর মাধ্যমে URI পাস করুন:

নিম্নলিখিত ধাপগুলি বর্ণনা করে কিভাবে একটি ওয়েব URI থেকে আর্ট ডাউনলোড করতে হয় এবং একটি স্থানীয় URI-এর মাধ্যমে এটিকে প্রকাশ করতে হয়। আরও সম্পূর্ণ উদাহরণের জন্য, ইউনিভার্সাল অ্যান্ড্রয়েড মিউজিক প্লেয়ার নমুনা অ্যাপে openFile() এর বাস্তবায়ন এবং আশেপাশের পদ্ধতিগুলি দেখুন।

  1. ওয়েব ইউআরআই-এর সাথে সম্পর্কিত একটি content:// URI তৈরি করুন। মিডিয়া ব্রাউজার পরিষেবা এবং মিডিয়া সেশন এই কন্টেন্ট URIকে Android Auto এবং Android Automotive OS-এ পাস করে।

    কোটলিন

    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()
    }

    জাভা

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
  2. আপনার ContentProvider.openFile() এর বাস্তবায়নে, সংশ্লিষ্ট URI-এর জন্য একটি ফাইল বিদ্যমান কিনা তা পরীক্ষা করুন। যদি না হয়, ডাউনলোড করুন এবং ইমেজ ফাইল ক্যাশে. নিম্নলিখিত কোড স্নিপেট গ্লাইড ব্যবহার করে।

    কোটলিন

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

    জাভা

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

বিষয়বস্তু প্রদানকারীদের সম্পর্কে আরো বিস্তারিত জানার জন্য, একটি বিষয়বস্তু প্রদানকারী তৈরি করা দেখুন।

বিষয়বস্তুর শৈলী প্রয়োগ করুন

ব্রাউজযোগ্য বা খেলার যোগ্য আইটেম ব্যবহার করে আপনার বিষয়বস্তু অনুক্রম তৈরি করার পরে, আপনি সামগ্রী শৈলী প্রয়োগ করতে পারেন যা নির্ধারণ করে যে সেই আইটেমগুলি গাড়িতে কীভাবে প্রদর্শিত হবে।

আপনি নিম্নলিখিত বিষয়বস্তু শৈলী ব্যবহার করতে পারেন:

তালিকা আইটেম

এই বিষয়বস্তুর শৈলীটি ছবির উপর শিরোনাম এবং মেটাডেটাকে অগ্রাধিকার দেয়।

গ্রিড আইটেম

এই বিষয়বস্তু শৈলী শিরোনাম এবং মেটাডেটার চেয়ে চিত্রগুলিকে অগ্রাধিকার দেয়৷

ডিফল্ট সামগ্রী শৈলী সেট করুন

আপনার পরিষেবার onGetRoot() পদ্ধতির BrowserRoot অতিরিক্ত বান্ডিলে নির্দিষ্ট ধ্রুবকগুলি অন্তর্ভুক্ত করে আপনার মিডিয়া আইটেমগুলি কীভাবে প্রদর্শিত হবে তার জন্য আপনি বিশ্বব্যাপী ডিফল্ট সেট করতে পারেন। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস এই বান্ডিলটি পড়ে এবং উপযুক্ত শৈলী নির্ধারণ করতে সেই ধ্রুবকগুলি সন্ধান করে৷

নিম্নলিখিত অতিরিক্তগুলি বান্ডেলে কী হিসাবে ব্যবহার করা যেতে পারে:

  • 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 : সংশ্লিষ্ট আইটেমগুলিকে "বিভাগ" গ্রিড আইটেম হিসাবে উপস্থাপন করা হয়েছে৷ এগুলি সাধারণ গ্রিড আইটেমগুলির মতোই, আইটেমগুলির আইকনগুলির চারপাশে মার্জিন প্রয়োগ করা ব্যতীত, যেহেতু আইকনগুলি ছোট হলে আরও ভাল দেখায়৷ আইকনগুলি অবশ্যই টিন্টেবল ভেক্টর অঙ্কনযোগ্য হতে হবে। এই ইঙ্গিতটি শুধুমাত্র ব্রাউজযোগ্য আইটেমগুলির জন্য প্রদান করা হবে বলে আশা করা হচ্ছে।

নিম্নলিখিত কোড স্নিপেটটি দেখায় কিভাবে ব্রাউজযোগ্য আইটেমগুলির জন্য গ্রিডগুলিতে এবং প্লেযোগ্য আইটেমগুলিকে তালিকার জন্য ডিফল্ট সামগ্রী শৈলী সেট করতে হয়:

কোটলিন

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

জাভা

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

প্রতি আইটেম বিষয়বস্তু শৈলী সেট করুন

বিষয়বস্তু শৈলী API আপনাকে যেকোনো ব্রাউজযোগ্য মিডিয়া আইটেমের বাচ্চাদের জন্য ডিফল্ট সামগ্রী শৈলীকে ওভাররাইড করতে দেয়, সেইসাথে যেকোনো মিডিয়া আইটেম নিজেই।

একটি ব্রাউজযোগ্য মিডিয়া আইটেমের বাচ্চাদের জন্য ডিফল্ট ওভাররাইড করতে, মিডিয়া আইটেমের MediaDescription এ একটি অতিরিক্ত বান্ডিল তৈরি করুন এবং একই পূর্বে উল্লেখিত ইঙ্গিত যোগ করুন। DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE সেই আইটেমটির খেলার যোগ্য শিশুদের জন্য প্রযোজ্য, যখন DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE সেই আইটেমটির ব্রাউজযোগ্য শিশুদের জন্য প্রযোজ্য৷

একটি নির্দিষ্ট মিডিয়া আইটেমের জন্য ডিফল্টটিকে ওভাররাইড করতে , এর বাচ্চাদের নয়, মিডিয়া আইটেমের MediaDescription একটি অতিরিক্ত বান্ডেল তৈরি করুন এবং DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM কীটির সাথে একটি ইঙ্গিত যোগ করুন। সেই আইটেমের উপস্থাপনা নির্দিষ্ট করতে পূর্বে বর্ণিত একই মানগুলি ব্যবহার করুন৷

নিম্নলিখিত কোড স্নিপেট দেখায় কিভাবে একটি ব্রাউজযোগ্য MediaItem তৈরি করতে হয় যা নিজের এবং তার বাচ্চাদের উভয়ের জন্য ডিফল্ট সামগ্রী শৈলীকে ওভাররাইড করে। এটি নিজেকে একটি বিভাগ তালিকা আইটেম হিসাবে শৈলী করে, এটির ব্রাউজযোগ্য শিশুদের তালিকা আইটেম হিসাবে এবং এটির খেলার যোগ্য শিশুদের গ্রিড আইটেম হিসাবে:

কোটলিন

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

জাভা

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 কী এবং একটি অভিন্ন স্ট্রিং মান সহ একটি ম্যাপিং অন্তর্ভুক্ত থাকে। এই স্ট্রিংটি স্থানীয়করণ করুন, যা গ্রুপের শিরোনাম হিসাবে ব্যবহৃত হয়।

নিম্নলিখিত কোড স্নিপেট দেখায় কিভাবে "Songs" এর একটি উপগোষ্ঠী শিরোনাম সহ একটি MediaItem তৈরি করতে হয়:

কোটলিন

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*/)
}

জাভা

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) {
   MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
   mediaDescriptionBuilder.setMediaId(mediaId);
   mediaDescriptionBuilder.setTitle(folderName);
   mediaDescriptionBuilder.setIconUri(iconUri);
   Bundle extras = new Bundle();
   extras.putString(
       MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
       "Songs");
   mediaDescriptionBuilder.setExtras(extras);
   return new MediaBrowser.MediaItem(
       mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
}

আপনার অ্যাপকে অবশ্যই সমস্ত মিডিয়া আইটেমগুলি পাস করতে হবে যা আপনি একটি সংলগ্ন ব্লক হিসাবে একসাথে গ্রুপ করতে চান৷ উদাহরণস্বরূপ, ধরুন আপনি মিডিয়া আইটেমগুলির দুটি গ্রুপ প্রদর্শন করতে চান, "গান" এবং "অ্যালবাম" সেই ক্রমে, এবং আপনার অ্যাপটি নিম্নলিখিত ক্রমে পাঁচটি মিডিয়া আইটেম পাস করে:

  1. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" ) সহ মিডিয়া আইটেম A
  2. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" ) সহ মিডিয়া আইটেম B
  3. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" ) সহ মিডিয়া আইটেম C
  4. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" ) সহ মিডিয়া আইটেম D
  5. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" ) সহ মিডিয়া আইটেম E

যেহেতু "গান" গ্রুপ এবং "অ্যালবাম" গোষ্ঠীর মিডিয়া আইটেমগুলি সংলগ্ন ব্লকগুলিতে একসাথে রাখা হয় না, তাই Android Auto এবং Android Automotive OS এটিকে নিম্নলিখিত চারটি গ্রুপ হিসাবে ব্যাখ্যা করে:

  • গ্রুপ 1 মিডিয়া আইটেম A ধারণকারী "গান" বলা হয়
  • গ্রুপ 2 মিডিয়া আইটেম B ধারণকারী "অ্যালবাম" বলে
  • গ্রুপ 3 মিডিয়া আইটেম C এবং D ধারণকারী "গান" বলা হয়
  • গ্রুপ 4 মিডিয়া আইটেম E ধারণকারী "অ্যালবাম" বলা হয়

দুটি গ্রুপে এই আইটেমগুলি প্রদর্শন করতে, আপনার অ্যাপটিকে অবশ্যই মিডিয়া আইটেমগুলিকে নিম্নলিখিত ক্রমে পাস করতে হবে:

  1. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" ) সহ মিডিয়া আইটেম A
  2. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" ) সহ মিডিয়া আইটেম C
  3. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" ) সহ মিডিয়া আইটেম D
  4. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" ) সহ মিডিয়া আইটেম B
  5. extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" ) সহ মিডিয়া আইটেম E

অতিরিক্ত মেটাডেটা সূচক প্রদর্শন করুন

আপনি মিডিয়া ব্রাউজার ট্রি এবং প্লেব্যাকের সময় বিষয়বস্তুর জন্য এক নজরে তথ্য প্রদান করতে অতিরিক্ত মেটাডেটা সূচক অন্তর্ভুক্ত করতে পারেন। ব্রাউজ ট্রির মধ্যে, অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস একটি আইটেমের সাথে সম্পর্কিত অতিরিক্তগুলি পড়ে এবং কোন সূচকগুলি প্রদর্শন করতে হবে তা নির্ধারণ করতে নির্দিষ্ট ধ্রুবকগুলি সন্ধান করে৷ মিডিয়া প্লেব্যাকের সময়, অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস মিডিয়া সেশনের মেটাডেটা পড়ে এবং প্রদর্শনের জন্য সূচক নির্ধারণ করতে নির্দিষ্ট ধ্রুবকগুলি সন্ধান করে৷

চিত্র 3. গান এবং শিল্পী সনাক্তকারী মেটাডেটা সহ প্লেব্যাক ভিউ পাশাপাশি একটি আইকন যা স্পষ্ট বিষয়বস্তু নির্দেশ করে৷

চিত্র 4. প্রথম আইটেমটিতে প্লে না করা বিষয়বস্তুর জন্য একটি বিন্দু এবং দ্বিতীয় আইটেমের আংশিকভাবে খেলা সামগ্রীর জন্য একটি অগ্রগতি বার সহ ভিউ ব্রাউজ করুন৷

নিম্নলিখিত ধ্রুবকগুলি 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_KEY_COMPLETION_PERCENTAGE : 0.0 এবং 1.0 এর মধ্যে দ্বিগুণ হিসাবে দীর্ঘ-ফর্ম সামগ্রীতে সমাপ্তির অগ্রগতির পরিমাণ নির্দেশ করে, অন্তর্ভুক্ত। এই অতিরিক্তটি PARTIALLY_PLAYING অবস্থা সম্পর্কে আরও তথ্য প্রদান করে যাতে Android Auto বা Android Automotive OS একটি আরও অর্থপূর্ণ অগ্রগতি সূচক প্রদর্শন করে, যেমন একটি অগ্রগতি বার৷ আপনি যদি এটি অতিরিক্ত ব্যবহার করেন, তাহলে এই নির্দেশিকায় প্রাথমিক ইম্প্রেশনের পরে কীভাবে এই সূচকটিকে আপ টু ডেট রাখতে হয় তা জানতে ব্রাউজ ভিউতে প্রগ্রেস বার আপডেট করার বিষয়ে বিভাগটি দেখুন।

ব্যবহারকারী মিডিয়া ব্রাউজ ট্রি ব্রাউজ করার সময় প্রদর্শিত সূচকগুলি প্রদর্শন করতে, একটি অতিরিক্ত বান্ডিল তৈরি করুন যাতে এই ধ্রুবকগুলির এক বা একাধিক অন্তর্ভুক্ত থাকে এবং সেই বান্ডিলটিকে MediaDescription.Builder.setExtras() পদ্ধতিতে পাস করুন৷

নিম্নলিখিত কোড স্নিপেট দেখায় কিভাবে একটি সুস্পষ্ট মিডিয়া আইটেমের জন্য সূচক প্রদর্শন করতে হয় যা 70% সম্পূর্ণ:

কোটলিন

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 */)

জাভা

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 */);

বর্তমানে চালানো হচ্ছে এমন একটি মিডিয়া আইটেমের জন্য সূচক প্রদর্শন করতে, আপনি আপনার mediaSession MediaMetadataCompatMETADATA_KEY_IS_EXPLICIT বা EXTRA_DOWNLOAD_STATUS এর জন্য Long মান ঘোষণা করতে পারেন। আপনি প্লেব্যাক ভিউতে DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS বা DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE সূচকগুলি প্রদর্শন করতে পারবেন না৷

নিম্নলিখিত কোড স্নিপেট দেখায় যে প্লেব্যাক ভিউতে বর্তমান গানটি স্পষ্ট এবং ডাউনলোড করা হয়েছে তা কীভাবে নির্দেশ করতে হয়:

কোটলিন

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())

জাভা

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 অবশ্যই তার অতিরিক্তগুলিতে 0.0 এবং 1.0 এর মধ্যে একটি মান সহ DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE পাঠাতে হবে।
  • MediaMetadataCompat অবশ্যই METADATA_KEY_MEDIA_ID পাঠাতে হবে একটি স্ট্রিং মান সহ MediaItem এ পাস করা মিডিয়া আইডির সমান।
  • PlaybackStateCompat PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID কী সহ একটি অতিরিক্ত অন্তর্ভুক্ত করতে হবে যা MediaItem এ পাস করা মিডিয়া আইডির সমান একটি স্ট্রিং মানের মানচিত্র করে।

নিচের কোড স্নিপেটটি দেখায় কিভাবে নির্দেশ করা যায় যে বর্তমানে বাজানো আইটেমটি ব্রাউজ ভিউতে একটি আইটেমের সাথে লিঙ্ক করা হয়েছে:

কোটলিন

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())

জাভা

import androidx.media.utils.MediaConstants;

// When the MediaItem is constructed to show in the browse view.
// Suppose the item was 25% complete when the user launched the browse view.
Bundle mediaItemExtras = new Bundle();
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters.
        .build();
return MediaBrowserCompat.MediaItem(description, /* flags */);

// Elsewhere, when the user has selected MediaItem for playback.
mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters.
        .build());

Bundle playbackStateExtras = new Bundle();
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id");
mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters.
        .build());

চিত্র 5. ব্যবহারকারীর ভয়েস অনুসন্ধানের সাথে সম্পর্কিত মিডিয়া আইটেমগুলি দেখার জন্য একটি "অনুসন্ধান ফলাফল" বিকল্পের সাথে প্লেব্যাক দৃশ্য।

আপনার অ্যাপটি প্রাসঙ্গিক অনুসন্ধান ফলাফল সরবরাহ করতে পারে যা ব্যবহারকারীরা যখন একটি অনুসন্ধান ক্যোয়ারী শুরু করে তখন তাদের কাছে প্রদর্শিত হয়। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস এই ফলাফলগুলি অনুসন্ধান ক্যোয়ারী ইন্টারফেসের মাধ্যমে বা সামর্থ্যের মাধ্যমে দেখায় যা সেশনের আগে করা প্রশ্নগুলিতে পিভট করে৷ আরও জানতে, এই নির্দেশিকায় সমর্থন ভয়েস অ্যাকশন বিভাগটি দেখুন।

ব্রাউজযোগ্য অনুসন্ধান ফলাফল প্রদর্শন করতে, আপনার পরিষেবার onGetRoot() পদ্ধতির অতিরিক্ত বান্ডেলে BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED ধ্রুবক কী অন্তর্ভুক্ত করুন, বুলিয়ান true ম্যাপিং করুন।

নিম্নলিখিত কোড স্নিপেট দেখায় কিভাবে onGetRoot() পদ্ধতিতে সমর্থন সক্ষম করতে হয়:

কোটলিন

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

জাভা

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() পদ্ধতিটি ওভাররাইড করুন৷ অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস এই পদ্ধতিতে ব্যবহারকারীর অনুসন্ধান শব্দগুলিকে ফরোয়ার্ড করে যখনই কোনও ব্যবহারকারী একটি অনুসন্ধান ক্যোয়ারী ইন্টারফেস বা "অনুসন্ধান ফলাফল" সামর্থ্যের জন্য আহ্বান করে।

আপনি শিরোনাম আইটেমগুলিকে আরও ব্রাউজযোগ্য করতে আপনার পরিষেবার onSearch() পদ্ধতি থেকে অনুসন্ধান ফলাফলগুলিকে সংগঠিত করতে পারেন৷ উদাহরণস্বরূপ, যদি আপনার অ্যাপ সঙ্গীত বাজায়, আপনি অ্যালবাম, শিল্পী এবং গান দ্বারা অনুসন্ধান ফলাফলগুলি সংগঠিত করতে পারেন৷

নিম্নলিখিত কোড স্নিপেট onSearch() পদ্ধতির একটি সহজ বাস্তবায়ন দেখায়:

কোটলিন

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.
}

জাভা

@Override
public void onSearch(final String query, final Bundle extras,
                        Result<List<MediaItem>> result) {

  // Detach from results to unblock the caller (if a search is expensive).
  result.detach();

  new AsyncTask<Void, Void, Void>() {
    List<MediaItem> searchResponse;
    boolean succeeded = false;
    @Override
    protected Void doInBackground(Void... params) {
      searchResponse = new ArrayList<MediaItem>();
      if (doSearch(query, extras, searchResponse)) {
        succeeded = true;
      }
      return null;
    }

    @Override
    protected void onPostExecute(Void param) {
      if (succeeded) {
       // Sending an empty List informs the caller that there were no results.
       result.sendResult(searchResponse);
      } else {
        // This invokes onError() on the search callback.
        result.sendResult(null);
      }
    }
  }.execute()
}

/** Populates resultsToFill with search results. Returns true on success or false on error. */
private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) {
    // Implement this method.
}

কাস্টম ব্রাউজ অ্যাকশন

একটি একক কাস্টম ব্রাউজ কর্ম.

চিত্র 6. একক কাস্টম ব্রাউজ অ্যাকশন

কাস্টম ব্রাউজ অ্যাকশনগুলি আপনাকে গাড়ির মিডিয়া অ্যাপে আপনার অ্যাপের MediaItem অবজেক্টগুলিতে কাস্টম আইকন এবং লেবেল যোগ করতে এবং এই ক্রিয়াগুলির সাথে ব্যবহারকারীর ইন্টারঅ্যাকশন পরিচালনা করতে দেয়৷ এটি আপনাকে মিডিয়া অ্যাপের কার্যকারিতা বিভিন্ন উপায়ে প্রসারিত করতে দেয়, যেমন "ডাউনলোড", "সারি যোগ করুন", "রেডিও চালান", "প্রিয়", বা "সরান" অ্যাকশন যোগ করা।

একটি কাস্টম ব্রাউজ কর্ম ওভারফ্লো মেনু.

চিত্র 7. কাস্টম ব্রাউজ অ্যাকশন ওভারফ্লো

যদি OEM প্রদর্শনের অনুমতি দেয় তার চেয়ে বেশি কাস্টম অ্যাকশন থাকে, ব্যবহারকারীর কাছে একটি ওভারফ্লো মেনু উপস্থাপন করা হবে।

তারা কিভাবে কাজ করে?

প্রতিটি কাস্টম ব্রাউজ অ্যাকশন এর সাথে সংজ্ঞায়িত করা হয়েছে:

  • একটি অ্যাকশন আইডি (একটি অনন্য স্ট্রিং শনাক্তকারী)
  • একটি অ্যাকশন লেবেল (ব্যবহারকারীর কাছে প্রদর্শিত পাঠ্য)
  • একটি অ্যাকশন আইকন URI (একটি ভেক্টর অঙ্কনযোগ্য যা টিন্ট করা যেতে পারে)

আপনি আপনার BrowseRoot এর অংশ হিসাবে বিশ্বব্যাপী কাস্টম ব্রাউজ ক্রিয়াগুলির একটি তালিকা সংজ্ঞায়িত করেন। তারপর আপনি পৃথক MediaItem.

যখন একজন ব্যবহারকারী একটি কাস্টম ব্রাউজ অ্যাকশনের সাথে ইন্টারঅ্যাক্ট করেন, তখন আপনার অ্যাপ onCustomAction() এ একটি কলব্যাক পায়। তারপরে আপনি ক্রিয়াটি পরিচালনা করতে পারেন এবং প্রয়োজনে MediaItem এর জন্য কর্মের তালিকা আপডেট করতে পারেন। এটি "প্রিয়" এবং "ডাউনলোড" এর মতো রাষ্ট্রীয় ক্রিয়াগুলির জন্য দরকারী। যে ক্রিয়াগুলি আপডেট করার প্রয়োজন নেই, যেমন "প্লে রেডিও", আপনাকে অ্যাকশনের তালিকা আপডেট করতে হবে না৷

একটি ব্রাউজ নোড রুটে কাস্টম ব্রাউজ কর্ম।

চিত্র 8. কাস্টম ব্রাউজ অ্যাকশন টুলবার

আপনি একটি ব্রাউজ নোড রুটে কাস্টম ব্রাউজ অ্যাকশন সংযুক্ত করতে পারেন। এই ক্রিয়াগুলি প্রধান টুলবারের অধীনে একটি সেকেন্ডারি টুলবারে প্রদর্শিত হবে।

কাস্টম ব্রাউজ অ্যাকশন কিভাবে বাস্তবায়ন করবেন

আপনার প্রজেক্টে কাস্টম ব্রাউজ অ্যাকশন যোগ করার ধাপগুলি এখানে রয়েছে:

  1. আপনার MediaBrowserServiceCompat বাস্তবায়নে দুটি পদ্ধতি ওভাররাইড করুন:
  2. রানটাইমে কর্ম সীমা পার্স করুন:
    • onGetRoot() -এ, rootHints Bundle BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT কী ব্যবহার করে প্রতিটি MediaItem জন্য অনুমোদিত সর্বাধিক সংখ্যক অ্যাকশন পান। 0 এর একটি সীমা নির্দেশ করে যে বৈশিষ্ট্যটি সিস্টেম দ্বারা সমর্থিত নয়৷
  3. কাস্টম ব্রাউজ অ্যাকশনের বিশ্বব্যাপী তালিকা তৈরি করুন:
    • প্রতিটি কর্মের জন্য, নিম্নলিখিত কীগুলির সাহায্যে একটি Bundle অবজেক্ট তৈরি করুন: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID : অ্যাকশন আইডি * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL : অ্যাকশন লেবেল * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI একটি অ্যাকশনের তালিকায় একটি আইকন অবজেক্ট যোগ করুন * সমস্ত Bundle তালিকায় যোগ করুন।
  4. আপনার BrowseRoot বিশ্বব্যাপী তালিকা যোগ করুন:
  5. আপনার MediaItem অবজেক্টে কর্ম যোগ করুন:
    • আপনি DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST কী ব্যবহার করে MediaDescriptionCompat অতিরিক্ত অ্যাকশন আইডিগুলির তালিকা অন্তর্ভুক্ত করে পৃথক MediaItem অবজেক্টে অ্যাকশন যোগ করতে পারেন। এই তালিকাটি অবশ্যই BrowseRoot এ সংজ্ঞায়িত কর্মের বিশ্বব্যাপী তালিকার একটি উপসেট হতে হবে।
  6. ক্রিয়াগুলি পরিচালনা করুন এবং অগ্রগতি বা ফলাফল ফিরিয়ে দিন:
    • onCustomAction এ, অ্যাকশন আইডি এবং আপনার প্রয়োজনীয় অন্যান্য ডেটার উপর ভিত্তি করে অ্যাকশন পরিচালনা করুন। EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID কী ব্যবহার করে আপনি MediaItem এর ID পেতে পারেন যা অতিরিক্ত থেকে ক্রিয়াটি ট্রিগার করেছে। .
    • আপনি অগ্রগতি বা ফলাফল বান্ডেলে কী EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM অন্তর্ভুক্ত করে একটি MediaItem এর জন্য কর্মের তালিকা আপডেট করতে পারেন।

কাস্টম ব্রাউজ অ্যাকশনের সাথে শুরু করতে আপনি আপনার 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>")
  • অ্যাকশন আইকন উরি
    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);

onCustomAction ফলাফল তৈরি করুন

  • 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) কল করে শেষ করুন।

আপডেট অ্যাকশন স্টেট

EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM কী সহ result.sendProgressUpdate(resultBundle) পদ্ধতিটি ব্যবহার করে আপনি ক্রিয়াকলাপের নতুন অবস্থা প্রতিফলিত করতে MediaItem আপডেট করতে পারেন। এটি আপনাকে তাদের ক্রিয়াকলাপের অগ্রগতি এবং ফলাফল সম্পর্কে ব্যবহারকারীকে রিয়েল-টাইম প্রতিক্রিয়া সরবরাহ করতে দেয়।

উদাহরণ: ক্রিয়া ডাউনলোড করুন

আপনি তিনটি রাজ্যের সাথে ডাউনলোড অ্যাকশন বাস্তবায়নে কীভাবে এই বৈশিষ্ট্যটি ব্যবহার করতে পারেন তার একটি উদাহরণ এখানে:

  1. ডাউনলোড: এটি ক্রিয়াটির প্রাথমিক অবস্থা। যখন ব্যবহারকারী এই ক্রিয়াটি নির্বাচন করেন, আপনি এটি "ডাউনলোডিং" দিয়ে অদলবদল করতে পারেন এবং ইউআই আপডেট করতে sendProgressUpdate কল করতে পারেন।
  2. ডাউনলোডিং: এই রাষ্ট্রটি ইঙ্গিত দেয় যে ডাউনলোডটি চলছে। আপনি এই রাজ্যটি ব্যবহারকারীর কাছে একটি অগ্রগতি বার বা অন্য সূচক দেখানোর জন্য ব্যবহার করতে পারেন।
  3. ডাউনলোড: এই রাষ্ট্রটি ইঙ্গিত দেয় যে ডাউনলোডটি সম্পূর্ণ। ডাউনলোড শেষ হয়ে গেলে, আপনি "ডাউনলোডিং" দিয়ে "ডাউনলোডিং" অদলবদল করতে পারেন এবং আইটেমটি রিফ্রেশ করা উচিত তা বোঝাতে EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM কী দিয়ে sendResult কল করতে পারেন। অতিরিক্তভাবে, আপনি ব্যবহারকারীর কাছে সাফল্যের বার্তা প্রদর্শন করতে EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE কী ব্যবহার করতে পারেন।

এই পদ্ধতিটি আপনাকে ডাউনলোড প্রক্রিয়া এবং এর বর্তমান অবস্থা সম্পর্কে ব্যবহারকারীকে সুস্পষ্ট প্রতিক্রিয়া সরবরাহ করতে দেয়। 25%, 50%, 75%ডাউনলোড রাজ্যগুলি দেখানোর জন্য আপনি আইকনগুলির সাথে আরও বিশদ যুক্ত করতে পারেন।

উদাহরণ: প্রিয় ক্রিয়া

আর একটি উদাহরণ দুটি রাজ্যের সাথে একটি প্রিয় ক্রিয়া:

  1. প্রিয়: এই ক্রিয়াটি এমন আইটেমগুলির জন্য প্রদর্শিত হয় যা ব্যবহারকারীর পছন্দের তালিকায় নেই। যখন ব্যবহারকারী এই ক্রিয়াটি নির্বাচন করেন, আপনি এটি "পছন্দসই" দিয়ে অদলবদল করতে পারেন এবং ইউআই আপডেট করার জন্য EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM কী দিয়ে sendResult কল করতে পারেন।
  2. পছন্দসই: এই ক্রিয়াটি ব্যবহারকারীর পছন্দের তালিকায় থাকা আইটেমগুলির জন্য প্রদর্শিত হয়। যখন ব্যবহারকারী এই ক্রিয়াটি নির্বাচন করেন, আপনি এটি "প্রিয়" দিয়ে অদলবদল করতে পারেন এবং ইউআই আপডেট করার জন্য EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM কী দিয়ে sendResult কল করতে পারেন।

এই পদ্ধতির ব্যবহারকারীদের তাদের পছন্দের আইটেমগুলি পরিচালনা করার জন্য একটি পরিষ্কার এবং ধারাবাহিক উপায় সরবরাহ করে।

এই উদাহরণগুলি কাস্টম ব্রাউজ ক্রিয়াগুলির নমনীয়তা এবং আপনি কীভাবে গাড়ির মিডিয়া অ্যাপ্লিকেশনটিতে বর্ধিত ব্যবহারকারীর অভিজ্ঞতার জন্য রিয়েল-টাইম প্রতিক্রিয়া সহ বিভিন্ন কার্যকারিতা প্রয়োগ করতে সেগুলি ব্যবহার করতে পারেন তা প্রদর্শন করে।

এই বৈশিষ্ট্যটির সম্পূর্ণ উদাহরণ বাস্তবায়নের জন্য, আপনি TestMediaApp প্রকল্পটি উল্লেখ করতে পারেন।

প্লেব্যাক নিয়ন্ত্রণ সক্ষম করুন

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস আপনার পরিষেবার MediaSessionCompat মাধ্যমে প্লেব্যাক কন্ট্রোল কমান্ডগুলি প্রেরণ করুন। আপনাকে অবশ্যই একটি সেশন নিবন্ধন করতে হবে এবং সম্পর্কিত কলব্যাক পদ্ধতিগুলি প্রয়োগ করতে হবে।

একটি মিডিয়া সেশন নিবন্ধন করুন

আপনার মিডিয়া ব্রাউজার সার্ভিসের onCreate() পদ্ধতিতে একটি MediaSessionCompat তৈরি করুন, তারপরে setSessionToken() কল করে মিডিয়া সেশনটি নিবন্ধ করুন।

নিম্নলিখিত কোড স্নিপেট দেখায় যে কীভাবে একটি মিডিয়া সেশন তৈরি এবং নিবন্ধন করা যায়:

কোটলিন

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
    ...
}

জাভা

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 শ্রেণীর একটি বাস্তবায়ন সরবরাহ করে এই কলব্যাক অবজেক্টটি তৈরি করুন। পরবর্তী বিভাগে এই অবজেক্টটি কীভাবে প্রয়োগ করা যায় তা নিয়ে আলোচনা করে।

প্লে কমান্ডগুলি প্রয়োগ করুন

যখন কোনও ব্যবহারকারী আপনার অ্যাপ্লিকেশন থেকে কোনও মিডিয়া আইটেমের জন্য প্লেব্যাকের জন্য অনুরোধ করেন, অ্যান্ড্রয়েড অটোমোটিভ ওএস এবং অ্যান্ড্রয়েড অটো আপনার অ্যাপ্লিকেশনটির মিডিয়া ব্রাউজার পরিষেবা থেকে প্রাপ্ত অ্যাপ্লিকেশনটির MediaSessionCompat অবজেক্ট থেকে MediaSessionCompat.Callback ক্লাসটি ব্যবহার করুন। যখন কোনও ব্যবহারকারী সামগ্রী প্লেব্যাক নিয়ন্ত্রণ করতে চান, যেমন প্লেব্যাক বিরতি দেওয়া বা পরবর্তী ট্র্যাকটিতে এড়িয়ে যাওয়া, অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস কলব্যাক অবজেক্টের একটি পদ্ধতি অনুরোধ করে।

সামগ্রী প্লেব্যাক পরিচালনা করতে, আপনার অ্যাপ্লিকেশনটিকে অবশ্যই অ্যাবস্ট্রাক্ট MediaSessionCompat.Callback ক্লাসটি প্রসারিত করতে হবে এবং আপনার অ্যাপ্লিকেশনটি সমর্থন করে এমন পদ্ধতিগুলি প্রয়োগ করতে হবে।

নিম্নলিখিত সমস্ত কলব্যাক পদ্ধতিগুলি প্রয়োগ করুন যা আপনার অ্যাপ্লিকেশনটি যে ধরণের সামগ্রী সরবরাহ করে তা বোঝায়:

onPrepare()
মিডিয়া উত্স পরিবর্তন করা হলে অনুরোধ করা হয়। অ্যান্ড্রয়েড অটোমোটিভ ওএস বুট করার পরে অবিলম্বে এই পদ্ধতিটি অনুরোধ করে। আপনার মিডিয়া অ্যাপ্লিকেশন অবশ্যই এই পদ্ধতিটি বাস্তবায়ন করতে হবে।
onPlay()
যদি ব্যবহারকারী কোনও নির্দিষ্ট আইটেম চয়ন না করে খেলতে পছন্দ করে তবে অনুরোধ করা হয়েছে। আপনার অ্যাপ্লিকেশনটিকে অবশ্যই এর ডিফল্ট সামগ্রী খেলতে হবে বা, যদি প্লেব্যাকটি onPause() দিয়ে বিরতি দেওয়া হয় তবে আপনার অ্যাপ্লিকেশনটি প্লেব্যাক পুনরায় শুরু করে।

দ্রষ্টব্য: অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটো যখন আপনার মিডিয়া ব্রাউজার পরিষেবাতে সংযুক্ত হয় তখন আপনার অ্যাপ্লিকেশনটি স্বয়ংক্রিয়ভাবে সংগীত বাজানো শুরু করা উচিত নয়। আরও তথ্যের জন্য, প্রাথমিক প্লেব্যাক অবস্থা সেট করার বিষয়ে বিভাগটি দেখুন।

onPlayFromMediaId()
ব্যবহারকারী যখন কোনও নির্দিষ্ট আইটেম খেলতে পছন্দ করে তখন অনুরোধ করা হয়। আপনার মিডিয়া ব্রাউজার পরিষেবাটি আপনার বিষয়বস্তু শ্রেণিবিন্যাসে মিডিয়া আইটেমকে নির্ধারিত আইডিটি পাস করা হয়েছে।
onPlayFromSearch()
যখন ব্যবহারকারী কোনও অনুসন্ধান ক্যোয়ারী থেকে খেলতে পছন্দ করে তখন অনুরোধ করা হয়। অ্যাপ্লিকেশনটি পাস হওয়া অনুসন্ধান স্ট্রিংয়ের উপর ভিত্তি করে একটি উপযুক্ত পছন্দ করতে হবে।
onPause()
যখন ব্যবহারকারী প্লেব্যাক বিরতি দিতে পছন্দ করেন তখন অনুরোধ করা হয়।
onSkipToNext()
যখন ব্যবহারকারী পরবর্তী আইটেমটিতে এড়িয়ে যেতে পছন্দ করে তখন অনুরোধ করা হয়।
onSkipToPrevious()
যখন ব্যবহারকারী পূর্ববর্তী আইটেমটিতে এড়িয়ে যেতে পছন্দ করে তখন অনুরোধ করা হয়।
onStop()
ব্যবহারকারী যখন প্লেব্যাক বন্ধ করতে পছন্দ করেন তখন অনুরোধ করা হয়।

কোনও পছন্দসই কার্যকারিতা সরবরাহ করতে আপনার অ্যাপ্লিকেশনটিতে এই পদ্ধতিগুলি ওভাররাইড করুন। কোনও পদ্ধতি যদি আপনার অ্যাপ্লিকেশন দ্বারা সমর্থিত না হয় তবে আপনার প্রয়োগ করার দরকার নেই। উদাহরণস্বরূপ, যদি আপনার অ্যাপ্লিকেশনটি লাইভ স্ট্রিম বাজায়, যেমন একটি স্পোর্টস সম্প্রচার, আপনার onSkipToNext() পদ্ধতিটি প্রয়োগ করার দরকার নেই। আপনি পরিবর্তে onSkipToNext() এর ডিফল্ট বাস্তবায়ন ব্যবহার করতে পারেন।

আপনার অ্যাপ্লিকেশনটির স্পিকারের মাধ্যমে সামগ্রী খেলতে কোনও বিশেষ যুক্তির প্রয়োজন নেই। যখন আপনার অ্যাপ্লিকেশনটি সামগ্রী খেলার জন্য একটি অনুরোধ গ্রহণ করে, এটি কোনও ব্যবহারকারীর ফোন স্পিকার বা হেডফোনের মাধ্যমে সামগ্রী বাজায় এমনভাবে অডিও খেলতে পারে। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস স্বয়ংক্রিয়ভাবে গাড়ির স্পিকারগুলিতে খেলতে অডিও সামগ্রীটি গাড়ির সিস্টেমে প্রেরণ করে।

অডিও সামগ্রী বাজানোর বিষয়ে আরও তথ্যের জন্য, মিডিয়াপ্লেয়ার ওভারভিউ , অডিও অ্যাপ ওভারভিউ এবং এক্সোপ্লেয়ার ওভারভিউ দেখুন।

স্ট্যান্ডার্ড প্লেব্যাক ক্রিয়া সেট করুন

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস প্রদর্শন প্লেব্যাক কন্ট্রোলগুলি PlaybackStateCompat অবজেক্টে সক্ষম করা ক্রিয়াগুলির উপর ভিত্তি করে প্লেব্যাক নিয়ন্ত্রণগুলি প্রদর্শন করে।

ডিফল্টরূপে, আপনার অ্যাপ্লিকেশন অবশ্যই নিম্নলিখিত ক্রিয়াগুলি সমর্থন করবে:

আপনার অ্যাপ্লিকেশনটি যদি অ্যাপ্লিকেশনটির সামগ্রীর সাথে প্রাসঙ্গিক হয় তবে নিম্নলিখিত ক্রিয়াকলাপগুলিকে অতিরিক্তভাবে সমর্থন করতে পারে:

এছাড়াও, আপনার কাছে একটি প্লে সারি তৈরি করার বিকল্প রয়েছে যা ব্যবহারকারীর জন্য প্রদর্শিত হতে পারে তবে এটির প্রয়োজন হয় না। এটি করার জন্য, setQueue() এবং setQueueTitle() পদ্ধতিগুলি কল করুন, ACTION_SKIP_TO_QUEUE_ITEM অ্যাকশন সক্ষম করুন এবং কলব্যাক onSkipToQueueItem() সংজ্ঞায়িত করুন।

এছাড়াও, এখন প্লে আইকনটির জন্য সমর্থন যুক্ত করুন, যা বর্তমানে যা খেলছে তার একটি সূচক। এটি করতে, setActiveQueueItemId() পদ্ধতিতে কল করুন এবং সারিটিতে বর্তমানে প্লে আইটেমটির আইডি পাস করুন। যখনই কোনও সারি পরিবর্তন হয় তখন আপনাকে setActiveQueueItemId() আপডেট করতে হবে।

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস ডিসপ্লে বোতামগুলির জন্য প্রতিটি সক্ষম ক্রিয়াকলাপের পাশাপাশি প্লেব্যাক সারি। বোতামগুলি ক্লিক করা হলে, সিস্টেমটি MediaSessionCompat.Callback থেকে তাদের সংশ্লিষ্ট কলব্যাকটি অনুরোধ করে।

অব্যবহৃত স্থান সংরক্ষণ করুন

ACTION_SKIP_TO_PREVIOUS এবং ACTION_SKIP_TO_NEXT ক্রিয়াকলাপের জন্য ইউআইতে অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস রিজার্ভ স্পেস। যদি আপনার অ্যাপ্লিকেশনটি এই ফাংশনগুলির মধ্যে একটি সমর্থন না করে তবে অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস আপনার তৈরি কোনও কাস্টম ক্রিয়া প্রদর্শন করতে স্থানটি ব্যবহার করুন।

আপনি যদি সেই স্পেসগুলিকে কাস্টম ক্রিয়াকলাপ দিয়ে পূরণ করতে না চান তবে আপনি সেগুলি সংরক্ষণ করতে পারেন যাতে অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস যখনই আপনার অ্যাপটি সম্পর্কিত ফাংশনটিকে সমর্থন না করে তখন জায়গাটি ফাঁকা ছেড়ে দেয়। এটি করার জন্য, একটি অতিরিক্ত বান্ডিল সহ 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 ব্যবহার করুন।

প্রাথমিক প্লেব্যাকস্টেট সেট করুন

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস আপনার মিডিয়া ব্রাউজার পরিষেবার সাথে যোগাযোগ করার সাথে সাথে আপনার মিডিয়া সেশনটি PlaybackStateCompat ব্যবহার করে সামগ্রী প্লেব্যাকের স্থিতি যোগাযোগ করে। অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটো যখন আপনার মিডিয়া ব্রাউজার পরিষেবাতে সংযুক্ত হয় তখন আপনার অ্যাপ্লিকেশনটি স্বয়ংক্রিয়ভাবে সংগীত বাজানো শুরু করা উচিত নয়। পরিবর্তে, গাড়ির রাজ্য বা ব্যবহারকারীর ক্রিয়াকলাপের ভিত্তিতে প্লেব্যাক পুনরায় শুরু করতে বা শুরু করতে অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএসের উপর নির্ভর করুন।

এটি সম্পাদন করার জন্য, আপনার মিডিয়া সেশনের প্রাথমিক PlaybackStateCompat STATE_STOPPED , STATE_PAUSED , STATE_NONE বা STATE_ERROR সেট করুন।

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএসের মধ্যে মিডিয়া সেশনগুলি কেবল ড্রাইভের সময়কালের জন্য স্থায়ী হয়, তাই ব্যবহারকারীরা এই সেশনগুলি প্রায়শই শুরু করে এবং বন্ধ করে দেয়। ড্রাইভগুলির মধ্যে একটি বিরামবিহীন অভিজ্ঞতার প্রচার করতে, ব্যবহারকারীর পূর্ববর্তী সেশন অবস্থার উপর নজর রাখুন, যাতে মিডিয়া অ্যাপ্লিকেশনটি যখন পুনরায় শুরু করার অনুরোধটি পায়, ব্যবহারকারী স্বয়ংক্রিয়ভাবে যেখানে তারা ছেড়ে গেছে সেখানে বেছে নিতে পারে - উদাহরণস্বরূপ, সর্বশেষ খেলানো মিডিয়া আইটেম, PlaybackStateCompat , এবং সারি।

কাস্টম প্লেব্যাক ক্রিয়া যুক্ত করুন

আপনার মিডিয়া অ্যাপ সমর্থন করে এমন অতিরিক্ত ক্রিয়া প্রদর্শন করতে আপনি কাস্টম প্লেব্যাক ক্রিয়া যুক্ত করতে পারেন। যদি স্পেস অনুমতি দেয় (এবং সংরক্ষিত না হয়) , অ্যান্ড্রয়েড পরিবহন নিয়ন্ত্রণগুলিতে কাস্টম ক্রিয়া যুক্ত করে। অন্যথায়, কাস্টম ক্রিয়াগুলি ওভারফ্লো মেনুতে প্রদর্শিত হয়। কাস্টম অ্যাকশনগুলি ক্রমে প্রদর্শিত হয় সেগুলি PlaybackStateCompat যুক্ত করা হয়।

মানক ক্রিয়া থেকে পৃথক আচরণ সরবরাহ করতে কাস্টম ক্রিয়া ব্যবহার করুন। এগুলি স্ট্যান্ডার্ড ক্রিয়াগুলি প্রতিস্থাপন বা সদৃশ করতে ব্যবহার করবেন না।

আপনি PlaybackStateCompat.Builder ক্লাসে addCustomAction() পদ্ধতি ব্যবহার করে কাস্টম ক্রিয়া যুক্ত করতে পারেন।

নিম্নলিখিত কোড স্নিপেট দেখায় যে কীভাবে একটি কাস্টম "একটি রেডিও চ্যানেল শুরু করুন" ক্রিয়া যুক্ত করবেন:

কোটলিন

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

জাভা

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .setExtras(customActionExtras)
    .build());

এই পদ্ধতির আরও বিশদ উদাহরণের জন্য, গিটহাবের ইউনিভার্সাল অ্যান্ড্রয়েড মিউজিক প্লেয়ার নমুনা অ্যাপ্লিকেশনটিতে setCustomAction() পদ্ধতিটি দেখুন।

আপনার কাস্টম অ্যাকশন তৈরি করার পরে, আপনার মিডিয়া সেশন onCustomAction() পদ্ধতিটি ওভাররাইড করে ক্রিয়াটির প্রতিক্রিয়া জানাতে পারে।

নিম্নলিখিত কোড স্নিপেট দেখায় যে আপনার অ্যাপ্লিকেশনটি কীভাবে একটি "রেডিও চ্যানেল শুরু করুন" ক্রিয়াতে প্রতিক্রিয়া জানাতে পারে:

কোটলিন

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

জাভা

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

এই পদ্ধতির আরও বিশদ উদাহরণের জন্য, গিটহাবের ইউনিভার্সাল অ্যান্ড্রয়েড সংগীত প্লেয়ার নমুনা অ্যাপ্লিকেশনটিতে onCustomAction পদ্ধতিটি দেখুন।

কাস্টম ক্রিয়াকলাপের জন্য আইকন

আপনি তৈরি করেন এমন প্রতিটি কাস্টম ক্রিয়াটির জন্য একটি আইকন রিসোর্স প্রয়োজন। গাড়িতে অ্যাপ্লিকেশনগুলি বিভিন্ন স্ক্রিনের আকার এবং ঘনত্বগুলিতে চলতে পারে, সুতরাং আপনি যে আইকনগুলি সরবরাহ করেন সেগুলি অবশ্যই ভেক্টর ড্রেবল হতে হবে। একটি ভেক্টর অঙ্কনযোগ্য আপনাকে বিশদটি না হারিয়ে সম্পদ স্কেল করতে দেয়। একটি ভেক্টর অঙ্কনযোগ্য ছোট রেজোলিউশনে পিক্সেল সীমানায় প্রান্ত এবং কোণগুলি সারিবদ্ধ করা সহজ করে তোলে।

যদি কোনও কাস্টম ক্রিয়াটি রাষ্ট্রীয় হয় - উদাহরণস্বরূপ, এটি বিভিন্ন রাজ্যের জন্য বিভিন্ন আইকন সরবরাহ করে বা বন্ধ করে একটি প্লেব্যাক সেটিং টগল করে, যাতে ব্যবহারকারীরা ক্রিয়াটি নির্বাচন করার সময় কোনও পরিবর্তন দেখতে পারেন।

অক্ষম ক্রিয়াকলাপের জন্য বিকল্প আইকন শৈলী সরবরাহ করুন

যখন বর্তমান প্রসঙ্গে একটি কাস্টম ক্রিয়া অনুপলব্ধ থাকে, তখন একটি বিকল্প আইকন দিয়ে কাস্টম অ্যাকশন আইকনটি অদলবদল করে যা দেখায় যে ক্রিয়াটি অক্ষম রয়েছে।

চিত্র 6. অফ স্টাইলের কাস্টম অ্যাকশন আইকনগুলির নমুনা।

অডিও ফর্ম্যাট নির্দেশ করুন

বর্তমানে মিডিয়া প্লে করা একটি বিশেষ অডিও ফর্ম্যাট ব্যবহার করে তা নির্দেশ করতে, আপনি এই বৈশিষ্ট্যটিকে সমর্থন করে এমন গাড়িগুলিতে রেন্ডার করা আইকনগুলি নির্দিষ্ট করতে পারেন। আপনি বর্তমানে বাজানো মিডিয়া আইটেমের অতিরিক্ত বান্ডিল ( MediaSession.setMetadata() ) এর অতিরিক্ত বান্ডলে KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI এবং KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI সেট করতে পারেন। বিভিন্ন লেআউটগুলিকে সামঞ্জস্য করার জন্য, এই উভয় অতিরিক্ত সেট সেট করার বিষয়টি নিশ্চিত করুন।

তদতিরিক্ত, আপনি গাড়ি ওএমএসকে বলার জন্য KEY_IMMERSIVE_AUDIO অতিরিক্ত সেট করতে পারেন যে এটি নিমজ্জনিত অডিও, এবং অডিও প্রভাবগুলি প্রয়োগ করতে হবে যা নিমজ্জনিত সামগ্রীতে হস্তক্ষেপ করতে পারে কিনা তা সিদ্ধান্ত নেওয়ার সময় তাদের খুব সতর্ক হওয়া উচিত।

আপনি বর্তমানে খেলানো মিডিয়া আইটেমটি কনফিগার করতে পারেন যাতে এর সাবটাইটেল, বিবরণ বা উভয়ই অন্যান্য মিডিয়া আইটেমের লিঙ্ক। এটি ব্যবহারকারীকে সম্পর্কিত আইটেমগুলিতে দ্রুত ঝাঁপিয়ে পড়ে; উদাহরণস্বরূপ, তারা একই শিল্পী, সেই পডকাস্টের অন্যান্য এপিসোড ইত্যাদির দ্বারা অন্যান্য গানে ঝাঁপিয়ে পড়তে পারে যদি গাড়িটি এই বৈশিষ্ট্যটিকে সমর্থন করে তবে ব্যবহারকারীরা সেই সামগ্রীতে ব্রাউজ করতে লিঙ্কটি ট্যাপ করতে পারেন।

লিঙ্কগুলি যুক্ত করতে, KEY_SUBTITLE_LINK_MEDIA_ID মেটাডেটা (সাবটাইটেল থেকে লিঙ্ক করতে) বা KEY_DESCRIPTION_LINK_MEDIA_ID (বিবরণ থেকে লিঙ্ক করতে) কনফিগার করুন। বিশদগুলির জন্য, সেই মেটাডেটা ক্ষেত্রগুলির জন্য রেফারেন্স ডকুমেন্টেশন দেখুন।

ভয়েস ক্রিয়া সমর্থন

আপনার মিডিয়া অ্যাপকে অবশ্যই ড্রাইভারদের একটি নিরাপদ এবং সুবিধাজনক অভিজ্ঞতা সরবরাহ করতে সহায়তা করতে ভয়েস ক্রিয়াকলাপগুলিকে সমর্থন করতে হবে যা বিভ্রান্তি হ্রাস করে। উদাহরণস্বরূপ, যদি আপনার অ্যাপ্লিকেশনটি একটি মিডিয়া আইটেম খেলছে তবে ব্যবহারকারী আপনার অ্যাপ্লিকেশনটিকে গাড়ির ডিসপ্লে না দেখে বা স্পর্শ না করে আলাদা গান বাজানোর জন্য " খেলুন [গানের শিরোনাম] " বলতে পারেন users ব্যবহারকারীরা উপযুক্ত বোতামগুলিতে ক্লিক করে প্রশ্ন শুরু করতে পারেন তাদের স্টিয়ারিং হুইলে বা হটওয়ার্ডস " ওকে গুগল " কথা বলা।

যখন অ্যান্ড্রয়েড অটো বা অ্যান্ড্রয়েড অটোমোটিভ ওএস একটি ভয়েস অ্যাকশন সনাক্ত করে এবং ব্যাখ্যা করে, তখন সেই ভয়েস অ্যাকশনটি onPlayFromSearch() মাধ্যমে অ্যাপ্লিকেশনটিতে সরবরাহ করা হয়। এই কলব্যাকটি পাওয়ার পরে, অ্যাপ্লিকেশনটি query স্ট্রিংয়ের সাথে মিল রেখে সামগ্রী খুঁজে পায় এবং প্লেব্যাক শুরু করে।

ব্যবহারকারীরা তাদের ক্যোয়ারিতে বিভিন্ন বিভাগের শর্তাদি নির্দিষ্ট করতে পারেন: জেনার, শিল্পী, অ্যালবাম, গানের নাম, রেডিও স্টেশন বা প্লেলিস্ট, অন্যদের মধ্যে। অনুসন্ধানের জন্য সমর্থন তৈরি করার সময়, আপনার অ্যাপ্লিকেশনটির জন্য অর্থবোধ করে এমন সমস্ত বিভাগের জন্য অ্যাকাউন্ট করুন। যদি অ্যান্ড্রয়েড অটো বা অ্যান্ড্রয়েড অটোমোটিভ ওএস সনাক্ত করে যে কোনও প্রদত্ত ক্যোয়ারী নির্দিষ্ট বিভাগগুলিতে ফিট করে তবে এটি extras প্যারামিটারে অতিরিক্ত সংযোজন করে। নিম্নলিখিত অতিরিক্ত পাঠানো যেতে পারে:

একটি খালি query স্ট্রিংয়ের জন্য অ্যাকাউন্ট, যা ব্যবহারকারী যদি অনুসন্ধানের শর্তাদি নির্দিষ্ট না করে তবে অ্যান্ড্রয়েড অটো বা অ্যান্ড্রয়েড অটোমোটিভ ওএস দ্বারা প্রেরণ করা যেতে পারে। উদাহরণস্বরূপ, যদি ব্যবহারকারী " কিছু সংগীত খেলুন " বলে। সেক্ষেত্রে আপনার অ্যাপ্লিকেশনটি সম্প্রতি প্লে বা নতুন প্রস্তাবিত ট্র্যাকটি শুরু করতে পছন্দ করতে পারে।

যদি কোনও অনুসন্ধান দ্রুত প্রক্রিয়া করা যায় না, onPlayFromSearch() এ ব্লক করবেন না। পরিবর্তে, প্লেব্যাক স্টেটটি STATE_CONNECTING সেট করুন এবং একটি অ্যাসিঙ্ক থ্রেডে অনুসন্ধান সম্পাদন করুন।

একবার প্লেব্যাক শুরু হয়ে গেলে, সম্পর্কিত সামগ্রীর সাথে মিডিয়া সেশনের সারিটিকে পপুলেশন করার বিষয়টি বিবেচনা করুন। উদাহরণস্বরূপ, যদি ব্যবহারকারী কোনও অ্যালবাম খেলার জন্য অনুরোধ করে তবে আপনার অ্যাপ্লিকেশনটি অ্যালবামের ট্র্যাকলিস্টটি দিয়ে সারিটি পূরণ করতে পারে। ব্রাউজেবল অনুসন্ধান ফলাফলের জন্য সমর্থন বাস্তবায়নের বিষয়টিও বিবেচনা করুন যাতে কোনও ব্যবহারকারী তাদের প্রশ্নের সাথে মেলে এমন একটি আলাদা ট্র্যাক চয়ন করতে পারে।

" প্লে " ক্যোয়ারী ছাড়াও, অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস " বিরতি সংগীত " এবং " নেক্সট গান " এর মতো প্লেব্যাক নিয়ন্ত্রণ করতে ভয়েস ক্যোয়ারীগুলি স্বীকৃতি দেয় এবং এই কমান্ডগুলি onPause() এবং onSkipToNext() এর মতো উপযুক্ত মিডিয়া সেশন কলব্যাকগুলিতে মেলে।

আপনার অ্যাপ্লিকেশনটিতে ভয়েস-সক্ষম প্লেব্যাক ক্রিয়াগুলি কীভাবে বাস্তবায়ন করবেন সে সম্পর্কে বিশদ উদাহরণের জন্য, গুগল সহকারী এবং মিডিয়া অ্যাপ্লিকেশনগুলি দেখুন।

ডিস্ট্রাকশন সেফগার্ডগুলি প্রয়োগ করুন

যেহেতু কোনও ব্যবহারকারীর ফোন অ্যান্ড্রয়েড অটো ব্যবহার করার সময় তাদের গাড়ির স্পিকারের সাথে সংযুক্ত থাকে, তাই ড্রাইভার বিভ্রান্তি রোধে সহায়তা করতে আপনাকে অবশ্যই অতিরিক্ত সতর্কতা অবলম্বন করতে হবে।

গাড়িতে অ্যালার্ম দমন করুন

অ্যান্ড্রয়েড অটো মিডিয়া অ্যাপ্লিকেশনগুলি অবশ্যই গাড়ি স্পিকারের মাধ্যমে অডিও খেলতে শুরু করবে না যদি না ব্যবহারকারী প্লেব্যাক শুরু করে, উদাহরণস্বরূপ, কোনও প্লে বোতাম টিপুন। এমনকি আপনার মিডিয়া অ্যাপ্লিকেশন থেকে ব্যবহারকারী-নির্ধারিত অ্যালার্মটি অবশ্যই গাড়ি স্পিকারের মাধ্যমে সংগীত বাজানো শুরু করবে না।

এই প্রয়োজনীয়তাটি পূরণ করতে, আপনার অ্যাপ্লিকেশন কোনও অডিও খেলার আগে সংকেত হিসাবে CarConnection ব্যবহার করতে পারে। আপনার অ্যাপ্লিকেশনটি গাড়ী সংযোগের ধরণের জন্য LiveData পর্যবেক্ষণ করে এবং এটি CONNECTION_TYPE_PROJECTION সমান কিনা তা পরীক্ষা করে ফোনটি কোনও গাড়ির স্ক্রিনে প্রজেক্ট করছে কিনা তা যাচাই করতে পারে।

যদি ব্যবহারকারীর ফোনটি প্রজেক্ট করা হয়, তবে মিডিয়া অ্যাপ্লিকেশনগুলি যা অ্যালার্ম সমর্থন করে তাদের অবশ্যই নিম্নলিখিত একটি কাজ করতে হবে:

  • অ্যালার্ম অক্ষম করুন।
  • STREAM_ALARM উপরে অ্যালার্মটি খেলুন এবং অ্যালার্মটি অক্ষম করতে ফোনের স্ক্রিনে একটি ইউআই সরবরাহ করুন।

মিডিয়া বিজ্ঞাপনগুলি পরিচালনা করুন

ডিফল্টরূপে, অ্যান্ড্রয়েড অটো যখন একটি অডিও প্লেব্যাক সেশনের সময় মিডিয়া মেটাডেটা পরিবর্তিত হয় তখন একটি বিজ্ঞপ্তি প্রদর্শন করে। যখন কোনও মিডিয়া অ্যাপ্লিকেশন সংগীত বাজানো থেকে কোনও বিজ্ঞাপন চালাতে স্যুইচ করে, তখন এটি ব্যবহারকারীর কাছে একটি বিজ্ঞপ্তি প্রদর্শন করতে বিভ্রান্ত হয়। অ্যান্ড্রয়েড অটোকে এই ক্ষেত্রে একটি বিজ্ঞপ্তি প্রদর্শন করা থেকে বিরত রাখতে, আপনাকে অবশ্যই মিডিয়া মেটাডেটা METADATA_KEY_IS_ADVERTISEMENT METADATA_VALUE_ATTRIBUTE_PRESENT সেট করতে হবে, যেমনটি নিম্নলিখিত কোড স্নিপেটে দেখানো হয়েছে:

কোটলিন

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())
    }
}

জাভা

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 দেখুন যা ত্রুটি বার্তা সেট করার সময় আপনি ব্যবহার করতে পারেন। ত্রুটি বার্তাগুলি অবশ্যই ব্যবহারকারীর মুখোমুখি হতে হবে এবং ব্যবহারকারীর বর্তমান লোকেলের সাথে স্থানীয়করণ করা উচিত। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস তারপরে ত্রুটি বার্তাটি ব্যবহারকারীর কাছে প্রদর্শন করতে পারে।

উদাহরণস্বরূপ, যদি ব্যবহারকারীর বর্তমান অঞ্চলে সামগ্রী উপলভ্য না হয় তবে ত্রুটি বার্তাটি নির্ধারণের সময় আপনি ERROR_CODE_NOT_AVAILABLE_IN_REGION ত্রুটি কোডটি ব্যবহার করতে পারেন।

কোটলিন

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())

জাভা

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());

ত্রুটি রাষ্ট্রগুলি সম্পর্কে আরও তথ্যের জন্য, একটি মিডিয়া সেশন ব্যবহার করে দেখুন: রাজ্য এবং ত্রুটি

যদি কোনও অ্যান্ড্রয়েড অটো ব্যবহারকারীর কোনও ত্রুটি সমাধানের জন্য আপনার ফোন অ্যাপটি খুলতে হয় তবে আপনার বার্তায় ব্যবহারকারীকে সেই তথ্য সরবরাহ করুন। উদাহরণস্বরূপ, আপনার ত্রুটি বার্তাটি "দয়া করে সাইন ইন করুন" এর পরিবর্তে "[আপনার অ্যাপের নাম] এ সাইন ইন করুন" বলতে পারে।

অন্যান্য সম্পদ

,

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস আপনাকে আপনার মিডিয়া অ্যাপ্লিকেশন সামগ্রী তাদের গাড়িতে ব্যবহারকারীদের কাছে আনতে সহায়তা করে। গাড়িগুলির জন্য একটি মিডিয়া অ্যাপ্লিকেশন অবশ্যই একটি মিডিয়া ব্রাউজার পরিষেবা সরবরাহ করতে হবে যাতে অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস, বা মিডিয়া ব্রাউজারের সাথে অন্য কোনও অ্যাপ্লিকেশন আপনার সামগ্রীটি আবিষ্কার করতে এবং প্রদর্শন করতে পারে।

এই গাইডটি ধরে নিয়েছে যে আপনার কাছে ইতিমধ্যে একটি মিডিয়া অ্যাপ্লিকেশন রয়েছে যা কোনও ফোনে অডিও বাজায় এবং আপনার মিডিয়া অ্যাপ্লিকেশনটি অ্যান্ড্রয়েড মিডিয়া অ্যাপ আর্কিটেকচারের সাথে সামঞ্জস্য করে।

এই গাইডটি অ্যান্ড্রয়েড অটো বা অ্যান্ড্রয়েড অটোমোটিভ ওএসে কাজ করার জন্য আপনার অ্যাপ্লিকেশনটির প্রয়োজন এমন একটি MediaBrowserService এবং MediaSession প্রয়োজনীয় উপাদানগুলি বর্ণনা করে। আপনি মূল মিডিয়া অবকাঠামো শেষ করার পরে, আপনি অ্যান্ড্রয়েড অটোর জন্য সমর্থন যুক্ত করতে পারেন এবং আপনার মিডিয়া অ্যাপে অ্যান্ড্রয়েড অটোমোটিভ ওএসের জন্য সমর্থন যুক্ত করতে পারেন

আপনি শুরু করার আগে

  1. অ্যান্ড্রয়েড মিডিয়া এপিআই ডকুমেন্টেশন পর্যালোচনা করুন।
  2. পর্যালোচনা ডিজাইন গাইডেন্সের জন্য মিডিয়া অ্যাপ্লিকেশন তৈরি করুন
  3. এই বিভাগে তালিকাভুক্ত মূল শর্তাদি এবং ধারণাগুলি পর্যালোচনা করুন।

মূল শর্তাবলী এবং ধারণা

মিডিয়া ব্রাউজার পরিষেবা
আপনার মিডিয়া অ্যাপ্লিকেশন দ্বারা প্রয়োগ করা একটি অ্যান্ড্রয়েড পরিষেবা যা MediaBrowserServiceCompat এপিআইয়ের সাথে সম্মতি জানায়। আপনার অ্যাপ্লিকেশনটি এর সামগ্রীটি প্রকাশ করতে এই পরিষেবাটি ব্যবহার করে।
মিডিয়া ব্রাউজার
মিডিয়া অ্যাপ্লিকেশনগুলি মিডিয়া ব্রাউজার পরিষেবাগুলি আবিষ্কার করতে এবং তাদের সামগ্রী প্রদর্শন করতে ব্যবহৃত একটি এপিআই। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস আপনার অ্যাপের মিডিয়া ব্রাউজার পরিষেবাটি খুঁজে পেতে একটি মিডিয়া ব্রাউজার ব্যবহার করে।
মিডিয়া আইটেম

মিডিয়া ব্রাউজার MediaItem অবজেক্টের একটি গাছে তার সামগ্রী সংগঠিত করে। একটি মিডিয়া আইটেমের উভয় বা নিম্নলিখিত উভয় পতাকা থাকতে পারে:

  • FLAG_PLAYABLE : নির্দেশ করে যে আইটেমটি সামগ্রী গাছের একটি পাতা। আইটেমটি একটি একক শব্দ প্রবাহকে উপস্থাপন করে, যেমন কোনও অ্যালবামের গান, একটি অডিও বইয়ের একটি অধ্যায় বা একটি পডকাস্টের একটি পর্ব।
  • FLAG_BROWSABLE : নির্দেশ করে যে আইটেমটি সামগ্রী গাছের একটি নোড এবং এতে বাচ্চা রয়েছে। উদাহরণস্বরূপ, আইটেমটি একটি অ্যালবাম উপস্থাপন করে এবং এর শিশুরা অ্যালবামের গান।

একটি মিডিয়া আইটেম যা উভয়ই ব্রাউজযোগ্য এবং প্লেযোগ্য প্লেলিস্টের মতো কাজ করে। আপনি তার সমস্ত বাচ্চাদের খেলতে নিজেই আইটেমটি নির্বাচন করতে পারেন, বা আপনি এর বাচ্চাদের ব্রাউজ করতে পারেন।

যানবাহন-অনুকূলিত

অ্যান্ড্রয়েড অটোমোটিভ ওএস অ্যাপ্লিকেশনটির জন্য একটি ক্রিয়াকলাপ যা অ্যান্ড্রয়েড অটোমোটিভ ওএস ডিজাইনের নির্দেশিকাগুলি মেনে চলে। এই ক্রিয়াকলাপগুলির জন্য ইন্টারফেসটি অ্যান্ড্রয়েড অটোমোটিভ ওএস দ্বারা আঁকা নয়, সুতরাং আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনার অ্যাপ্লিকেশনটি ডিজাইনের নির্দেশিকাগুলি মেনে চলে। সাধারণত, এর মধ্যে বৃহত্তর ট্যাপ লক্ষ্যগুলি এবং ফন্ট আকারগুলি, দিন এবং রাতের মোডগুলির জন্য সমর্থন এবং উচ্চতর বিপরীতে অনুপাত অন্তর্ভুক্ত রয়েছে।

যানবাহন-অনুকূলিত ব্যবহারকারী ইন্টারফেসগুলি কেবল তখনই প্রদর্শিত হওয়ার অনুমতি দেওয়া হয় যখন গাড়ি ব্যবহারকারীর অভিজ্ঞতার সীমাবদ্ধতা (সিএইচএক্সআর) কার্যকর হয় না, কারণ এই ইন্টারফেসগুলি ব্যবহারকারীর কাছ থেকে বর্ধিত মনোযোগ বা মিথস্ক্রিয়া প্রয়োজন হতে পারে। গাড়িটি থামানো বা পার্ক করা হলে CUXRS কার্যকর হয় না তবে গাড়িটি যখন গতিতে থাকে তখন সর্বদা কার্যকর হয়।

অ্যান্ড্রয়েড অটোর জন্য আপনার ক্রিয়াকলাপগুলি ডিজাইন করার দরকার নেই, কারণ অ্যান্ড্রয়েড অটো আপনার মিডিয়া ব্রাউজার পরিষেবা থেকে তথ্য ব্যবহার করে নিজস্ব যানবাহন-অনুকূলিত ইন্টারফেস আঁকেন।

আপনার অ্যাপ্লিকেশনটির ম্যানিফেস্ট ফাইলগুলি কনফিগার করুন

আপনি আপনার মিডিয়া ব্রাউজার পরিষেবা তৈরি করার আগে আপনাকে আপনার অ্যাপ্লিকেশনটির ম্যানিফেস্ট ফাইলগুলি কনফিগার করতে হবে।

আপনার মিডিয়া ব্রাউজার পরিষেবা ঘোষণা করুন

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস উভয়ই মিডিয়া আইটেমগুলি ব্রাউজ করতে আপনার মিডিয়া ব্রাউজার পরিষেবার মাধ্যমে আপনার অ্যাপ্লিকেশনটিতে সংযুক্ত হন। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস পরিষেবাটি আবিষ্কার করতে এবং আপনার অ্যাপ্লিকেশনটির সাথে সংযোগ স্থাপন করতে আপনার ম্যানিফেস্টে আপনার মিডিয়া ব্রাউজার পরিষেবাটি ঘোষণা করুন।

নিম্নলিখিত কোড স্নিপেটটি দেখায় যে কীভাবে আপনার ম্যানিফেস্টে আপনার মিডিয়া ব্রাউজার পরিষেবাটি ঘোষণা করবেন। আপনার অ্যান্ড্রয়েড অটোমোটিভ ওএস মডিউলটির জন্য ম্যানিফেস্ট ফাইলটিতে এবং আপনার ফোন অ্যাপের জন্য ম্যানিফেস্ট ফাইলে এই কোডটি অন্তর্ভুক্ত করুন।

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

অ্যাপ্লিকেশন আইকনগুলি নির্দিষ্ট করুন

আপনাকে অ্যাপ্লিকেশন আইকনগুলি নির্দিষ্ট করতে হবে যে অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস সিস্টেম ইউআইতে আপনার অ্যাপ্লিকেশনটি উপস্থাপন করতে ব্যবহার করতে পারে। দুটি আইকন প্রকারের প্রয়োজন:

  • লঞ্চার আইকন
  • অ্যাট্রিবিউশন আইকন

লঞ্চার আইকন

লঞ্চার আইকনটি সিস্টেম ইউআইতে আপনার অ্যাপ্লিকেশনটিকে উপস্থাপন করে, যেমন লঞ্চার এবং আইকনগুলির ট্রেতে। আপনি নির্দিষ্ট করতে পারেন যে আপনি নিম্নলিখিত ম্যানিফেস্ট ঘোষণাটি ব্যবহার করে আপনার গাড়ী মিডিয়া অ্যাপ্লিকেশনটি উপস্থাপন করতে আপনার মোবাইল অ্যাপ্লিকেশন থেকে আইকনটি ব্যবহার করতে চান:

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

আপনার মোবাইল অ্যাপের চেয়ে আলাদা আইকন ব্যবহার করতে, android:icon আপনার মিডিয়া ব্রাউজার পরিষেবার <service> উপাদানটিতে আইকন সম্পত্তিটি ম্যানিফেস্টে:

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

অ্যাট্রিবিউশন আইকন

চিত্র 1. মিডিয়া কার্ডে অ্যাট্রিবিউশন আইকন।

মিডিয়া সামগ্রী যেমন মিডিয়া কার্ডগুলিতে যেমন অগ্রাধিকার নেয় সেখানে অ্যাট্রিবিউশন আইকনটি ব্যবহৃত হয়। বিজ্ঞপ্তিগুলির জন্য ব্যবহৃত ছোট আইকনটি পুনরায় ব্যবহার করার বিষয়টি বিবেচনা করুন। এই আইকনটি অবশ্যই একরঙা হতে হবে। আপনি এমন একটি আইকন নির্দিষ্ট করতে পারেন যা নিম্নলিখিত ম্যানিফেস্ট ঘোষণাটি ব্যবহার করে আপনার অ্যাপ্লিকেশনটির প্রতিনিধিত্ব করতে ব্যবহৃত হয়:

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

আপনার মিডিয়া ব্রাউজার পরিষেবা তৈরি করুন

আপনি MediaBrowserServiceCompat ক্লাসটি প্রসারিত করে একটি মিডিয়া ব্রাউজার পরিষেবা তৈরি করেন। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস উভয়ই নিম্নলিখিতগুলি করতে আপনার পরিষেবাটি ব্যবহার করতে পারে:

  • ব্যবহারকারীর কাছে একটি মেনু উপস্থাপন করতে আপনার অ্যাপ্লিকেশনটির সামগ্রী শ্রেণিবিন্যাস ব্রাউজ করুন।
  • অডিও প্লেব্যাক নিয়ন্ত্রণ করতে আপনার অ্যাপের MediaSessionCompat অবজেক্টের জন্য টোকেন পান।

অন্যান্য ক্লায়েন্টদের আপনার অ্যাপ্লিকেশন থেকে মিডিয়া সামগ্রী অ্যাক্সেস করতে আপনি আপনার মিডিয়া ব্রাউজার পরিষেবাটিও ব্যবহার করতে পারেন। এই মিডিয়া ক্লায়েন্টগুলি কোনও ব্যবহারকারীর ফোনে অন্যান্য অ্যাপ্লিকেশন হতে পারে বা তারা অন্য দূরবর্তী ক্লায়েন্ট হতে পারে।

মিডিয়া ব্রাউজার পরিষেবা কর্মপ্রবাহ

এই বিভাগটি বর্ণনা করে যে কীভাবে অ্যান্ড্রয়েড অটোমোটিভ ওএস এবং অ্যান্ড্রয়েড অটো একটি সাধারণ ব্যবহারকারীর কর্মপ্রবাহের সময় আপনার মিডিয়া ব্রাউজার পরিষেবার সাথে ইন্টারঅ্যাক্ট করে।

  1. ব্যবহারকারী আপনার অ্যাপ্লিকেশনটি অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটোতে চালু করে।
  2. অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটো আপনার অ্যাপ্লিকেশনটির মিডিয়া ব্রাউজার পরিষেবাটি onCreate() পদ্ধতি ব্যবহার করে যোগাযোগ করে। আপনার onCreate() পদ্ধতি বাস্তবায়নে আপনাকে অবশ্যই একটি MediaSessionCompat অবজেক্ট এবং এর কলব্যাক অবজেক্ট তৈরি করতে হবে এবং নিবন্ধন করতে হবে।
  3. অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটো আপনার সামগ্রীর শ্রেণিবিন্যাসে রুট মিডিয়া আইটেমটি পেতে আপনার পরিষেবার onGetRoot() পদ্ধতিটিকে কল করে। রুট মিডিয়া আইটেম প্রদর্শিত হয় না; পরিবর্তে, এটি আপনার অ্যাপ্লিকেশন থেকে আরও সামগ্রী পুনরুদ্ধার করতে ব্যবহৃত হয়।
  4. অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটো রুট মিডিয়া আইটেমের বাচ্চাদের পেতে আপনার পরিষেবার onLoadChildren() পদ্ধতিটিকে কল করে। অ্যান্ড্রয়েড অটোমোটিভ ওএস এবং অ্যান্ড্রয়েড অটো এই মিডিয়া আইটেমগুলিকে সামগ্রী আইটেমগুলির শীর্ষ স্তর হিসাবে প্রদর্শন করে। সিস্টেমটি এই স্তরে কী প্রত্যাশা করে সে সম্পর্কে আরও তথ্যের জন্য এই পৃষ্ঠায় রুট মেনুটি কাঠামো দেখুন।
  5. যদি ব্যবহারকারী কোনও ব্রাউজেবল মিডিয়া আইটেম নির্বাচন করে তবে আপনার পরিষেবার onLoadChildren() পদ্ধতিটি নির্বাচিত মেনু আইটেমের বাচ্চাদের পুনরুদ্ধার করতে আবার কল করা হয়।
  6. যদি ব্যবহারকারী কোনও প্লেযোগ্য মিডিয়া আইটেম নির্বাচন করে তবে অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটো সেই ক্রিয়াটি সম্পাদন করার জন্য উপযুক্ত মিডিয়া সেশন কলব্যাক পদ্ধতিটিকে কল করে।
  7. যদি আপনার অ্যাপ্লিকেশন দ্বারা সমর্থিত হয় তবে ব্যবহারকারী আপনার সামগ্রীও অনুসন্ধান করতে পারে। এই ক্ষেত্রে, অ্যান্ড্রয়েড অটোমোটিভ ওএস বা অ্যান্ড্রয়েড অটো আপনার পরিষেবার onSearch() পদ্ধতিতে কল করুন।

আপনার সামগ্রী শ্রেণিবিন্যাস তৈরি করুন

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস আপনার অ্যাপের মিডিয়া ব্রাউজার পরিষেবাটিতে কল করুন কী কী সামগ্রী উপলভ্য তা জানতে। এটি সমর্থন করার জন্য আপনাকে আপনার মিডিয়া ব্রাউজার পরিষেবাতে দুটি পদ্ধতি বাস্তবায়ন করতে হবে: onGetRoot() এবং onLoadChildren()

অনেটরুট বাস্তবায়ন করুন

আপনার পরিষেবার onGetRoot() পদ্ধতিটি আপনার সামগ্রী শ্রেণিবিন্যাসের মূল নোড সম্পর্কে তথ্য দেয়। অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস এই রুট নোডটি onLoadChildren() পদ্ধতিটি ব্যবহার করে আপনার বাকী সামগ্রীর জন্য অনুরোধ করতে এই রুট নোডটি ব্যবহার করুন।

নিম্নলিখিত কোড স্নিপেটটি onGetRoot() পদ্ধতির একটি সাধারণ বাস্তবায়ন দেখায়:

কোটলিন

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)

জাভা

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {

    // Verify that the specified package is allowed to access your
    // content. You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        return null;
    }

    return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null);
}

এই পদ্ধতির আরও বিশদ উদাহরণের জন্য, গিটহাবের ইউনিভার্সাল অ্যান্ড্রয়েড সংগীত প্লেয়ার নমুনা অ্যাপ্লিকেশনটিতে onGetRoot() পদ্ধতিটি দেখুন।

Ongetroot () এর জন্য প্যাকেজ বৈধতা যুক্ত করুন

যখন আপনার পরিষেবার onGetRoot() পদ্ধতিতে কোনও কল করা হয়, তখন কলিং প্যাকেজটি আপনার পরিষেবাতে তথ্য সনাক্ত করে পাস করে। আপনার পরিষেবা এই তথ্যটি ব্যবহার করতে পারে যে সেই প্যাকেজটি আপনার সামগ্রী অ্যাক্সেস করতে পারে কিনা তা সিদ্ধান্ত নিতে। উদাহরণস্বরূপ, আপনি clientPackageName সাথে আপনার অনুমোদনের সাথে তুলনা করে এবং প্যাকেজের এপিকে স্বাক্ষর করতে ব্যবহৃত শংসাপত্রটি যাচাই করে আপনার অ্যাপের সামগ্রীতে অ্যাক্সেসকে অনুমোদিত প্যাকেজগুলির তালিকায় সীমাবদ্ধ করতে পারেন। যদি প্যাকেজটি যাচাই করা যায় না, তবে আপনার সামগ্রীতে অ্যাক্সেস অস্বীকার করতে null ফিরিয়ে দিন।

আপনার সামগ্রীতে অ্যাক্সেস সহ অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএসের মতো সিস্টেম অ্যাপ্লিকেশনগুলি সরবরাহ করতে, যখন এই সিস্টেম অ্যাপ্লিকেশনগুলি onGetRoot() পদ্ধতি কল করে তখন আপনার পরিষেবাটি সর্বদা একটি নন-নাল BrowserRoot ফিরিয়ে দিতে হবে। অ্যান্ড্রয়েড অটোমোটিভ ওএস সিস্টেম অ্যাপ্লিকেশনটির স্বাক্ষরটি গাড়ির তৈরি এবং মডেলের উপর নির্ভর করে পরিবর্তিত হতে পারে, সুতরাং অ্যান্ড্রয়েড অটোমোটিভ ওএসকে দৃ ust ়তার সাথে সমর্থন করার জন্য আপনাকে সমস্ত সিস্টেম অ্যাপ্লিকেশন থেকে সংযোগের অনুমতি দিতে হবে।

নিম্নলিখিত কোড স্নিপেট দেখায় যে আপনার পরিষেবাটি কীভাবে বৈধতা দিতে পারে যে কলিং প্যাকেজটি একটি সিস্টেম অ্যাপ্লিকেশন:

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 শ্রেণীর একটি অংশ। আপনার পরিষেবার onGetRoot() পদ্ধতির জন্য প্যাকেজ বৈধতা কীভাবে প্রয়োগ করতে হয় তার আরও বিশদ উদাহরণের জন্য সেই শ্রেণিটি দেখুন।

সিস্টেম অ্যাপ্লিকেশনগুলির অনুমতি দেওয়ার পাশাপাশি আপনাকে অবশ্যই গুগল সহকারীকে আপনার MediaBrowserService সাথে সংযুক্ত হতে হবে। নোট করুন যে গুগল সহকারীটিতে ফোনের জন্য পৃথক প্যাকেজ নাম রয়েছে, যার মধ্যে অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএসের জন্য রয়েছে।

অনলডচিল্ডেন প্রয়োগ করুন ()

আপনার রুট নোড অবজেক্টটি পাওয়ার পরে, অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএস এর বাচ্চাদের পেতে রুট নোড অবজেক্টে onLoadChildren() কল করে একটি শীর্ষ-স্তরের মেনু তৈরি করে। ক্লায়েন্ট অ্যাপ্লিকেশনগুলি শিশু নোড অবজেক্টগুলি ব্যবহার করে এই একই পদ্ধতিতে কল করে সাবমেনাস তৈরি করে।

আপনার সামগ্রীর শ্রেণিবিন্যাসের প্রতিটি নোড একটি MediaBrowserCompat.MediaItem অবজেক্ট দ্বারা প্রতিনিধিত্ব করা হয়। এই মিডিয়া আইটেমগুলির প্রতিটি একটি অনন্য আইডি স্ট্রিং দ্বারা চিহ্নিত করা হয়। ক্লায়েন্ট অ্যাপ্লিকেশনগুলি এই আইডি স্ট্রিংগুলিকে অস্বচ্ছ টোকেন হিসাবে বিবেচনা করে। যখন কোনও ক্লায়েন্ট অ্যাপ্লিকেশন কোনও সাবমেনুতে ব্রাউজ করতে বা মিডিয়া আইটেম খেলতে চায়, তখন এটি টোকেনটি পাস করে। আপনার অ্যাপ্লিকেশনটি উপযুক্ত মিডিয়া আইটেমের সাথে টোকেন যুক্ত করার জন্য দায়বদ্ধ।

নিম্নলিখিত কোড স্নিপেটটি onLoadChildren() পদ্ধতির একটি সাধারণ বাস্তবায়ন দেখায়:

কোটলিন

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

জাভা

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaBrowserCompat.MediaItem>> result) {

    // Assume for example that the music catalog is already loaded/cached.

    List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();

    // Check whether this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {

        // Build the MediaItem objects for the top level
        // and put them in the mediaItems list.
    } else {

        // Examine the passed parentMediaId to see which submenu we're at
        // and put the children of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems);
}

এই পদ্ধতির সম্পূর্ণ উদাহরণের জন্য, গিটহাবের ইউনিভার্সাল অ্যান্ড্রয়েড সংগীত প্লেয়ার নমুনা অ্যাপ্লিকেশনটিতে onLoadChildren() পদ্ধতিটি দেখুন।

মূল মেনু কাঠামো

চিত্র 2. ন্যাভিগেশনাল ট্যাব হিসাবে প্রদর্শিত মূল সামগ্রী।

অ্যান্ড্রয়েড অটো এবং অ্যান্ড্রয়েড অটোমোটিভ ওএসের মূল মেনুর কাঠামো সম্পর্কে নির্দিষ্ট সীমাবদ্ধতা রয়েছে। এগুলি রুট ইঙ্গিতগুলির মাধ্যমে MediaBrowserService যোগাযোগ করা হয়, যা onGetRoot() এ পাস করা Bundle আর্গুমেন্টের মাধ্যমে পড়া যায়। এই ইঙ্গিতগুলি অনুসরণ করে সিস্টেমটিকে মূল সামগ্রীটি ন্যাভিগেশনাল ট্যাব হিসাবে সর্বোত্তমভাবে প্রদর্শন করতে দেয়। আপনি যদি এই ইঙ্গিতগুলি অনুসরণ না করেন তবে কিছু মূল সামগ্রী সিস্টেম দ্বারা বাদ দেওয়া বা কম আবিষ্কারযোগ্য করা যেতে পারে। দুটি ইঙ্গিত প্রেরণ করা হয়:

প্রাসঙ্গিক মূলের ইঙ্গিতগুলি পড়তে নিম্নলিখিত কোডটি ব্যবহার করুন:

কোটলিন

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...
}

জাভা

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 ইন্টিগ্রেশনগুলির মধ্যে পরিবর্তিত হয়। For example, if you normally show a root playable item, you might want to nest it under a root browsable item instead due to the value of the supported flags hint.

Apart from the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:

  • Supply monochrome, preferably white, icons for each tab item.
  • Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.

Display media artwork

Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT or ContentResolver.SCHEME_ANDROID_RESOURCE . This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat objects representing items in the content hierarchy, pass the URI through setIconUri() . For MediaMetadataCompat objects representing the currently playing item, pass the URI through putString() , using any of the following keys:

The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile() and the surrounding methods in the Universal Android Music Player sample app.

  1. Build a content:// URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.

    কোটলিন

    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()
    }

    জাভা

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
  2. In your implementation of ContentProvider.openFile() , check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .

    কোটলিন

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

    জাভা

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

For more details about content providers, refer to Creating a content provider .

Apply content styles

After building your content hierarchy using browsable or playable items, you can apply content styles that determine how those items display in the car.

You can use the following content styles:

তালিকা আইটেম

This content style prioritizes titles and metadata over images.

গ্রিড আইটেম

This content style prioritizes images over titles and metadata.

Set default content styles

You can set global defaults for how your media items are displayed by including certain constants in the BrowserRoot extras bundle of your service's onGetRoot() method. Android Auto and Android Automotive OS read this bundle and look for those constants to determine the appropriate style.

The following extras can be used as keys in the bundle:

The keys can map to the following integer constant values to influence the presentation of those items:

The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:

কোটলিন

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

জাভা

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

Set per-item content styles

The Content Style API lets you override the default content style for any browsable media item's children, as well as any media item itself.

To override the default for a browsable media item's children , create an extras bundle in the MediaDescription of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE applies to that item's browsable children.

To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM . Use the same values described previously to specify that item's presentation.

The following code snippet shows how to create a browsable MediaItem that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:

কোটলিন

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

জাভা

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

Group items using title hints

To group related media items together, you use a per-item hint. Every media item in a group needs to declare an extras bundle in their MediaDescription that includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE and an identical string value. Localize this string, which is used as the title of the group.

The following code snippet shows how to create a MediaItem with a subgroup heading of "Songs" :

কোটলিন

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*/)
}

জাভা

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*/);
}

Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:

  1. Media item A with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Media item B with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  3. Media item C with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Media item D with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  5. Media item E with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Because the media items for the "Songs" group and "Albums" group are not kept together in contiguous blocks, Android Auto and Android Automotive OS interprets this as the following four groups:

  • Group 1 called "Songs" containing media item A
  • Group 2 called "Albums" containing media item B
  • Group 3 called "Songs" containing media items C and D
  • Group 4 called "Albums" containing media item E

To display these items in two groups, your app must pass the media items in the following order instead:

  1. Media item A with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Media item C with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  3. Media item D with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Media item B with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  5. Media item E with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Display additional metadata indicators

You can include additional metadata indicators to provide at-a-glance information for content in the media browser tree and during playback. Within the browse tree, Android Auto and Android Automotive OS read the extras associated with an item and look for certain constants to determine which indicators to display. During media playback, Android Auto and Android Automotive OS read the metadata for the media session and look for certain constants to determine indicators to display.

Figure 3. Playback view with metadata identifying the song and artist as well as an icon indicating explicit content.

Figure 4. Browse view with a dot for unplayed content on the first item and a progress bar for partially played content on the second item.

The following constants can be used in both MediaItem description extras and MediaMetadata extras:

The following constants can only be used in MediaItem description extras:

To display indicators that appear while the user is browsing the media browse tree, create an extras bundle that includes one or more of these constants and pass that bundle to the MediaDescription.Builder.setExtras() method.

The following code snippet shows how to display indicators for an explicit media item that is 70% complete:

কোটলিন

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 */)

জাভা

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 */);

To display indicators for a media item that is currently being played, you can declare Long values for METADATA_KEY_IS_EXPLICIT or EXTRA_DOWNLOAD_STATUS in the MediaMetadataCompat of your mediaSession . You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE indicators on the playback view.

The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:

কোটলিন

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())

জাভা

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());

Update the progress bar in the browse view as content is playing

As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.

For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat and PlaybackStateCompat to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:

The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:

কোটলিন

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())

জাভা

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());

Figure 5. Playback view with a “Search results” option for viewing media items related to the user's voice search.

Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.

To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED in the extras bundle of your service's onGetRoot() method, mapping to the boolean true .

The following code snippet shows how to enable support in the onGetRoot() method:

কোটলিন

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

জাভা

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

To start providing search results, override the onSearch() method in your media browser service. Android Auto and Android Automotive OS forward the user's search terms to this method whenever a user invokes a search query interface or “Search results” affordance.

You can organize the search results from your service's onSearch() method using title items to make them more browsable. For example, if your app plays music, you might organize search results by album, artist, and songs.

The following code snippet shows a simple implementation of the onSearch() method:

কোটলিন

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.
}

জাভা

@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.
}

Custom Browse Actions

A single custom browse action.

Figure 6. Single Custom Browse Action

Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.

A custom browse actions overflow menu.

Figure 7. Custom Browse Action overflow

If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.

তারা কিভাবে কাজ করে?

Each Custom Browse Action is defined with:

  • An Action ID (a unique string identifier)
  • An Action Label (the text displayed to the user)
  • An Action Icon URI (a vector drawable that can be tinted)

You define a list of Custom Browse Actions globally as part of your BrowseRoot . Then you can attach a subset of these actions to individual MediaItem.

When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction() . You can then handle the action and update the list of actions for the MediaItem if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.

Custom browse actions in a browse node root.

Figure 8. Custom Browse Action toolbar

You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.

How to implement Custom Browse Actions

Here are the steps to add Custom Browse Actions to your project:

  1. Override two methods in your MediaBrowserServiceCompat implementation:
  2. Parse the action limits at runtime:
  3. Build the global list of Custom Browse Actions:
    • For each action, create a Bundle object with the following keys: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID : The action ID * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL : The action label * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI : The action icon URI * Add all the action Bundle objects to a list.
  4. Add the global list to your BrowseRoot :
  5. Add actions to your MediaItem objects:
    • You can add actions to individual MediaItem objects by including the list of action IDs in the MediaDescriptionCompat extras using the key DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST . This list must be a subset of the global list of actions you defined in the BrowseRoot .
  6. Handle actions and return progress or results:

Here are some changes you can make in your BrowserServiceCompat to get started with Custom Browse Actions.

Override BrowserServiceCompat

You need to override the following methods in MediaBrowserServiceCompat .

public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

Parse actions limit

You should check to see how many Custom Browse Actions are supported.

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
    rootHints.getInt(
            MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
}

Build a Custom Browse Action

Each action needs to be packed into a separate Bundle .

  • অ্যাকশন আইডি
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
  • অ্যাকশন লেবেল
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
  • Action Icon URI
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")

Add Custom Browse Actions to Parceable ArrayList

Add all Custom Browse Action Bundle objects into an 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;
}

Add Custom Browse Action list to the browse root

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

Add actions to a 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 result

  • Parse mediaId from 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);
    }
  • For asynchronous results detach result. result.detach()
  • Build result bundle
    • Message to user
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
    • Update item(use to update actions in an item)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
    • Open Playback view
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
    • Update Browse Node
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
  • If an error, call result.sendError(resultBundle).
  • If progress update, call result.sendProgressUpdate(resultBundle) .
  • Finish by calling result.sendResult(resultBundle) .

Update Action State

By using the result.sendProgressUpdate(resultBundle) method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key, you can update the MediaItem to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.

Example: Download Action

Here's an example of how you can use this feature to implement a download action with three states:

  1. Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call sendProgressUpdate to update the UI.
  2. Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
  3. Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to indicate that the item should be refreshed. Additionally, you can use the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE key to display a success message to the user.

This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.

Example: Favorite Action

Another example is a favorite action with two states:

  1. Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to update the UI.
  2. Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to update the UI.

This approach provides a clear and consistent way for users to manage their favorite items.

These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.

For a complete example implementation of this feature, you can refer to the TestMediaApp project.

Enable playback control

Android Auto and Android Automotive OS send playback control commands through your service's MediaSessionCompat . You must register a session and implement the associated callback methods.

Register a media session

In your media browser service's onCreate() method, create a MediaSessionCompat , then register the media session by calling setSessionToken() .

The following code snippet shows how to create and register a media session:

কোটলিন

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
    ...
}

জাভা

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());
    ...
}

When you create the media session object, you set a callback object that is used to handle playback control requests. You create this callback object by providing an implementation of the MediaSessionCompat.Callback class for your app. The next section discusses how to implement this object.

Implement play commands

When a user requests playback for a media item from your app, Android Automotive OS and Android Auto use the MediaSessionCompat.Callback class from your app's MediaSessionCompat object that they obtained from your app's media browser service. When a user wants to control content playback, such as pausing playback or skipping to the next track, Android Auto and Android Automotive OS invoke one of the callback object's methods.

To handle content playback, your app must extend the abstract MediaSessionCompat.Callback class and implement the methods that your app supports.

Implement all the following callback methods that make sense for the type of content that your app offers:

onPrepare()
Invoked when the media source is changed. Android Automotive OS also invokes this method immediately after booting. Your media app must implement this method.
onPlay()
Invoked if the user chooses play without choosing a specific item. Your app must play its default content or, if playback was paused with onPause() , your app resumes playback.

Note: Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. For more information, see the section about setting the initial playback state .

onPlayFromMediaId()
Invoked when the user chooses to play a specific item. The method is passed the ID that your media browser service assigned to the media item in your content hierarchy.
onPlayFromSearch()
Invoked when the user chooses to play from a search query. The app must make an appropriate choice based on the search string that was passed in.
onPause()
Invoked when the user chooses to pause playback.
onSkipToNext()
Invoked when the user chooses to skip to the next item.
onSkipToPrevious()
Invoked when the user chooses to skip to the previous item.
onStop()
Invoked when the user chooses to stop playback.

Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext() method. You can use the default implementation of onSkipToNext() instead.

Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays content through a user's phone speakers or headphones. Android Auto and Android Automotive OS automatically send the audio content to the car's system to play over the car's speakers.

For more information about playing audio content, see MediaPlayer overview , Audio app overview , and the ExoPlayer overview .

Set standard playback actions

Android Auto and Android Automotive OS display playback controls based on the actions that are enabled in the PlaybackStateCompat object.

By default, your app must support the following actions:

Your app can additionally support the following actions if they are relevant to the app's content:

In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue() and setQueueTitle() methods, enable the ACTION_SKIP_TO_QUEUE_ITEM action, and define the callback onSkipToQueueItem() .

Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId() method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId() whenever there is a queue change.

Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback .

Reserve unused space

Android Auto and Android Automotive OS reserve space in the UI for the ACTION_SKIP_TO_PREVIOUS and ACTION_SKIP_TO_NEXT actions. If your app does not support one of these functions, Android Auto and Android Automotive OS use the space to display any custom actions you create.

If you don't want to fill those spaces with custom actions, you can reserve them so that Android Auto and Android Automotive OS leave the space blank whenever your app does not support the corresponding function. To do this, call the setExtras() method with an extras bundle that contains constants that correspond to the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT corresponds to ACTION_SKIP_TO_NEXT , and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV corresponds to ACTION_SKIP_TO_PREVIOUS . Use these constants as keys in the bundle, and use the boolean true for their values.

Set initial PlaybackState

As Android Auto and Android Automotive OS communicate with your media browser service, your media session communicates the status of content playback using the PlaybackStateCompat . Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. Instead, rely on Android Auto and Android Automotive OS to resume or start playback based on the car's state or user actions.

To accomplish this, set the initial PlaybackStateCompat of your media session to STATE_STOPPED , STATE_PAUSED , STATE_NONE , or STATE_ERROR .

Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat , and the queue.

Add custom playback actions

You can add custom playback actions to display additional actions that your media app supports. If space permits (and is not reserved) , Android adds the custom actions to the transport controls. Otherwise, the custom actions display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat .

Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.

You can add custom actions using the addCustomAction() method in the PlaybackStateCompat.Builder class.

The following code snippet shows how to add a custom “Start a radio channel” action:

কোটলিন

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

জাভা

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .setExtras(customActionExtras)
    .build());

For a more detailed example of this method, see the setCustomAction() method in the Universal Android Music Player sample app on GitHub.

After creating your custom action, your media session can respond to the action by overriding the onCustomAction() method.

The following code snippet shows how your app might respond to a “Start a radio channel” action:

কোটলিন

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

জাভা

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

For a more detailed example of this method, see the onCustomAction method in the Universal Android Music Player sample app on GitHub.

Icons for custom actions

Each custom action that you create requires an icon resource. Apps in cars can run on many different screen sizes and densities, so icons that you provide must be vector drawables . A vector drawable allows you to scale assets without losing the detail. A vector drawable also makes it easy to align edges and corners to pixel boundaries at smaller resolutions.

If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.

Provide alternative icon styles for disabled actions

When a custom action is unavailable for the current context, swap the custom action icon with an alternative icon that shows that the action is disabled.

Figure 6. Samples of off-style custom action icons.

Indicate audio format

To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata() ). Make sure to set both of those extras, to accommodate different layouts.

In addition, you can set the KEY_IMMERSIVE_AUDIO extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.

You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.

To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID (to link from the description). For details, see the reference documentation for those metadata fields.

Support voice actions

Your media app must support voice actions to help provide drivers with a safe and convenient experience that minimizes distractions. For example, if your app is playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."

When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch() . On receiving this callback, the app finds content matching the query string and starts playback.

Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras parameter. The following extras can be sent:

Account for an empty query string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.

If a search cannot be processed quickly, do not block in onPlayFromSearch() . Instead, set the playback state to STATE_CONNECTING and perform the search on an async thread.

Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.

In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause() and onSkipToNext() .

For a detailed example on how to implement voice-enabled playback actions in your app, see Google Assistant and media apps .

Implement distraction safeguards

Because a user's phone is connected to their car's speakers while using Android Auto, you must take additional precautions to help prevent driver distraction.

Suppress alarms in the car

Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.

To fulfill this requirement, your app can use CarConnection as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION .

If the user's phone is projecting, media apps that support alarms must do one of the following things:

  • Disable the alarm.
  • Play the alarm over STREAM_ALARM and provide a UI on the phone screen to disable the alarm.

Handle media advertisements

By default, Android Auto displays a notification when the media metadata changes during an audio playback session. When a media app switches from playing music to running an advertisement, it is distracting to display a notification to the user. To prevent Android Auto from displaying a notification in this case, you must set the media metadata key METADATA_KEY_IS_ADVERTISEMENT to METADATA_VALUE_ATTRIBUTE_PRESENT , as shown in the following code snippet:

কোটলিন

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())
    }
}

জাভা

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

Handle general errors

When the app experiences an error, set the playback state to STATE_ERROR and provide an error message using the setErrorMessage() method. See PlaybackStateCompat for a list of error codes that you can use when setting the error message. Error messages must be user-facing and localized with the user's current locale. Android Auto and Android Automotive OS can then display the error message to the user.

For example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION error code when setting the error message.

কোটলিন

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())

জাভা

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());

For more information about error states, see Using a media session: States and errors .

If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."

অন্যান্য সম্পদ

,

Android Auto and Android Automotive OS help you bring your media app content to users in their car. A media app for cars must provide a media browser service so that Android Auto and Android Automotive OS, or another app with a media browser, can discover and display your content.

This guide assumes that you already have a media app that plays audio on a phone and that your media app conforms to the Android media app architecture .

This guide describes the required components of a MediaBrowserService and MediaSession that your app needs in order to work on Android Auto or Android Automotive OS. After you have completed the core media infrastructure, you can add support for Android Auto and add support for Android Automotive OS to your media app.

আপনি শুরু করার আগে

  1. Review the Android media API documentation .
  2. Review Create media apps for design guidance.
  3. Review the key terms and concepts listed in this section.

মূল শর্তাবলী এবং ধারণা

Media browser service
An Android service implemented by your media app that complies with the MediaBrowserServiceCompat API. Your app uses this service to expose its content.
মিডিয়া ব্রাউজার
An API used by media apps to discover media browser services and display their content. Android Auto and Android Automotive OS use a media browser to find your app's media browser service.
Media item

The media browser organizes its content in a tree of MediaItem objects. A media item can have either or both of the following flags:

  • FLAG_PLAYABLE : indicates that the item is a leaf on the content tree. The item represents a single sound stream, such as a song on an album, a chapter in an audio book, or an episode of a podcast.
  • FLAG_BROWSABLE : indicates that the item is a node on the content tree and it has children. For example, the item represents an album, and its children are the songs on the album.

A media item that is both browsable and playable acts like a playlist. You can select the item itself to play all of its children, or you can browse its children.

Vehicle-optimized

An activity for an Android Automotive OS app that adheres to the Android Automotive OS design guidelines . The interface for these activities is not drawn by Android Automotive OS, so you must ensure that your app adheres to the design guidelines. Typically, this includes larger tap targets and font sizes, support for day and night modes, and higher contrast ratios.

Vehicle-optimized user interfaces are only allowed to be displayed when Car User Experience Restrictions (CUXRs) are not in effect, because these interfaces can require extended attention or interaction from the user. CUXRs are not in effect when the car is stopped or parked but are always in effect when the car is in motion.

You don't need to design activities for Android Auto, because Android Auto draws its own vehicle-optimized interface using the information from your media browser service.

Configure your app's manifest files

Before you can create your media browser service, you need to configure your app's manifest files .

Declare your media browser service

Both Android Auto and Android Automotive OS connect to your app through your media browser service to browse media items. Declare your media browser service in your manifest to let Android Auto and Android Automotive OS discover the service and connect to your app.

The following code snippet shows how to declare your media browser service in your manifest. Include this code in the manifest file for your Android Automotive OS module and in the manifest file for your phone app.

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

Specify app icons

You need to specify app icons that Android Auto and Android Automotive OS can use to represent your app in the system UI. Two icon types are required:

  • লঞ্চার আইকন
  • অ্যাট্রিবিউশন আইকন

লঞ্চার আইকন

The launcher icon represents your app in the system UI, such as on the launcher and in the tray of icons. You can specify that you want to use the icon from your mobile app to represent your car media app using the following manifest declaration:

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

To use a different icon than your mobile app's, set the android:icon property on your media browser service's <service> element in the manifest:

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

অ্যাট্রিবিউশন আইকন

Figure 1. Attribution icon on media card.

The attribution icon is used in places where media content takes precedence, such as on media cards. Consider reusing the small icon used for notifications. This icon must be monochrome. You can specify an icon that is used to represent your app using the following manifest declaration:

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

Create your media browser service

You create a media browser service by extending the MediaBrowserServiceCompat class. Both Android Auto and Android Automotive OS can then use your service to do the following:

  • Browse your app's content hierarchy to present a menu to the user.
  • Get the token for your app's MediaSessionCompat object to control audio playback.

You can also use your media browser service to let other clients access media content from your app. These media clients might be other apps on a user's phone, or they can be other remote clients.

Media browser service workflow

This section describes how Android Automotive OS and Android Auto interact with your media browser service during a typical user workflow.

  1. The user launches your app on Android Automotive OS or Android Auto.
  2. Android Automotive OS or Android Auto contacts your app's media browser service using the onCreate() method. In your implementation of the onCreate() method, you must create and register a MediaSessionCompat object and its callback object.
  3. Android Automotive OS or Android Auto calls your service's onGetRoot() method to get the root media item in your content hierarchy. The root media item is not displayed; instead, it's used to retrieve more content from your app.
  4. Android Automotive OS or Android Auto calls your service's onLoadChildren() method to get the children of the root media item. Android Automotive OS and Android Auto display these media items as the top level of content items. See Structure the root menu on this page for more information on what the system expects at this level.
  5. If the user selects a browsable media item, your service's onLoadChildren() method is called again to retrieve the children of the selected menu item.
  6. If the user selects a playable media item, Android Automotive OS or Android Auto calls the appropriate media session callback method to perform that action.
  7. If supported by your app, the user can also search your content. In this case, Android Automotive OS or Android Auto call your service's onSearch() method.

Build your content hierarchy

Android Auto and Android Automotive OS call your app's media browser service to find out what content is available. You need to implement two methods in your media browser service to support this: onGetRoot() and onLoadChildren()

Implement onGetRoot

Your service's onGetRoot() method returns information about the root node of your content hierarchy. Android Auto and Android Automotive OS use this root node to request the rest of your content using the onLoadChildren() method.

The following code snippet shows a simple implementation of the onGetRoot() method:

কোটলিন

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)

জাভা

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

For a more detailed example of this method, see the onGetRoot() method in the Universal Android Music Player sample app on GitHub.

Add package validation for onGetRoot()

When a call is made to your service's onGetRoot() method, the calling package passes identifying information to your service. Your service can use this information to decide whether that package can access your content. For example, you can restrict access to your app's content to a list of approved packages by comparing the clientPackageName to your allowlist and verifying the certificate used to sign the package's APK. If the package can't be verified, return null to deny access to your content.

To provide system apps, such as Android Auto and Android Automotive OS, with access to your content, your service must always return a non-null BrowserRoot when these system apps call the onGetRoot() method. The signature of the Android Automotive OS system app can vary depending on the make and model of the car, so you need to permit connections from all system apps to support Android Automotive OS robustly.

The following code snippet shows how your service can validate that the calling package is a system app:

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
}

This code snippet is an excerpt from the PackageValidator class in the Universal Android Music Player sample app on GitHub. See that class for a more detailed example of how to implement package validation for your service's onGetRoot() method.

In addition to allowing system apps, you must let the Google Assistant connect to your MediaBrowserService . Note that the Google Assistant has separate package names for the phone, which includes Android Auto, and for Android Automotive OS.

Implement onLoadChildren()

After receiving your root node object, Android Auto and Android Automotive OS build a top-level menu by calling onLoadChildren() on the root node object to get its children. Client apps build submenus by calling this same method using child node objects.

Each node in your content hierarchy is represented by a MediaBrowserCompat.MediaItem object. Each of these media items is identified by a unique ID string. Client apps treat these ID strings as opaque tokens. When a client app wants to browse to a submenu, or play a media item, it passes the token. Your app is responsible for associating the token with the appropriate media item.

The following code snippet shows a simple implementation of onLoadChildren() method:

কোটলিন

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

জাভা

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

For a complete example of this method, see the onLoadChildren() method in the Universal Android Music Player sample app on GitHub.

Structure the root menu

Figure 2. Root content displayed as navigational tabs.

Android Auto and Android Automotive OS have specific constraints about the structure of the root menu. These are communicated to the MediaBrowserService through root hints, which can be read through the Bundle argument passed into onGetRoot() . Following these hints lets the system display the root content optimally as navigational tabs. If you don't follow these hints, some root content might be dropped or made less discoverable by the system. Two hints are sent:

Use the following code to read the relevant root hints:

কোটলিন

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...
}

জাভা

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...
}

You can choose to branch the logic for the structure of your content hierarchy based on the values of these hints, particularly if your hierarchy varies among MediaBrowser integrations outside of Android Auto and Android Automotive OS. For example, if you normally show a root playable item, you might want to nest it under a root browsable item instead due to the value of the supported flags hint.

Apart from the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:

  • Supply monochrome, preferably white, icons for each tab item.
  • Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.

Display media artwork

Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT or ContentResolver.SCHEME_ANDROID_RESOURCE . This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat objects representing items in the content hierarchy, pass the URI through setIconUri() . For MediaMetadataCompat objects representing the currently playing item, pass the URI through putString() , using any of the following keys:

The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile() and the surrounding methods in the Universal Android Music Player sample app.

  1. Build a content:// URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.

    কোটলিন

    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()
    }

    জাভা

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
  2. In your implementation of ContentProvider.openFile() , check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .

    কোটলিন

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

    জাভা

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

For more details about content providers, refer to Creating a content provider .

Apply content styles

After building your content hierarchy using browsable or playable items, you can apply content styles that determine how those items display in the car.

You can use the following content styles:

তালিকা আইটেম

This content style prioritizes titles and metadata over images.

গ্রিড আইটেম

This content style prioritizes images over titles and metadata.

Set default content styles

You can set global defaults for how your media items are displayed by including certain constants in the BrowserRoot extras bundle of your service's onGetRoot() method. Android Auto and Android Automotive OS read this bundle and look for those constants to determine the appropriate style.

The following extras can be used as keys in the bundle:

The keys can map to the following integer constant values to influence the presentation of those items:

The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:

কোটলিন

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

জাভা

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

Set per-item content styles

The Content Style API lets you override the default content style for any browsable media item's children, as well as any media item itself.

To override the default for a browsable media item's children , create an extras bundle in the MediaDescription of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE applies to that item's browsable children.

To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM . Use the same values described previously to specify that item's presentation.

The following code snippet shows how to create a browsable MediaItem that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:

কোটলিন

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

জাভা

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

Group items using title hints

To group related media items together, you use a per-item hint. Every media item in a group needs to declare an extras bundle in their MediaDescription that includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE and an identical string value. Localize this string, which is used as the title of the group.

The following code snippet shows how to create a MediaItem with a subgroup heading of "Songs" :

কোটলিন

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*/)
}

জাভা

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*/);
}

Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:

  1. Media item A with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Media item B with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  3. Media item C with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Media item D with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  5. Media item E with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Because the media items for the "Songs" group and "Albums" group are not kept together in contiguous blocks, Android Auto and Android Automotive OS interprets this as the following four groups:

  • Group 1 called "Songs" containing media item A
  • Group 2 called "Albums" containing media item B
  • Group 3 called "Songs" containing media items C and D
  • Group 4 called "Albums" containing media item E

To display these items in two groups, your app must pass the media items in the following order instead:

  1. Media item A with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Media item C with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  3. Media item D with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Media item B with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  5. Media item E with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Display additional metadata indicators

You can include additional metadata indicators to provide at-a-glance information for content in the media browser tree and during playback. Within the browse tree, Android Auto and Android Automotive OS read the extras associated with an item and look for certain constants to determine which indicators to display. During media playback, Android Auto and Android Automotive OS read the metadata for the media session and look for certain constants to determine indicators to display.

Figure 3. Playback view with metadata identifying the song and artist as well as an icon indicating explicit content.

Figure 4. Browse view with a dot for unplayed content on the first item and a progress bar for partially played content on the second item.

The following constants can be used in both MediaItem description extras and MediaMetadata extras:

The following constants can only be used in MediaItem description extras:

To display indicators that appear while the user is browsing the media browse tree, create an extras bundle that includes one or more of these constants and pass that bundle to the MediaDescription.Builder.setExtras() method.

The following code snippet shows how to display indicators for an explicit media item that is 70% complete:

কোটলিন

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 */)

জাভা

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 */);

To display indicators for a media item that is currently being played, you can declare Long values for METADATA_KEY_IS_EXPLICIT or EXTRA_DOWNLOAD_STATUS in the MediaMetadataCompat of your mediaSession . You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE indicators on the playback view.

The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:

কোটলিন

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())

জাভা

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());

Update the progress bar in the browse view as content is playing

As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.

For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat and PlaybackStateCompat to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:

The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:

কোটলিন

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())

জাভা

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());

Figure 5. Playback view with a “Search results” option for viewing media items related to the user's voice search.

Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.

To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED in the extras bundle of your service's onGetRoot() method, mapping to the boolean true .

The following code snippet shows how to enable support in the onGetRoot() method:

কোটলিন

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

জাভা

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

To start providing search results, override the onSearch() method in your media browser service. Android Auto and Android Automotive OS forward the user's search terms to this method whenever a user invokes a search query interface or “Search results” affordance.

You can organize the search results from your service's onSearch() method using title items to make them more browsable. For example, if your app plays music, you might organize search results by album, artist, and songs.

The following code snippet shows a simple implementation of the onSearch() method:

কোটলিন

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.
}

জাভা

@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.
}

Custom Browse Actions

A single custom browse action.

Figure 6. Single Custom Browse Action

Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.

A custom browse actions overflow menu.

Figure 7. Custom Browse Action overflow

If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.

তারা কিভাবে কাজ করে?

Each Custom Browse Action is defined with:

  • An Action ID (a unique string identifier)
  • An Action Label (the text displayed to the user)
  • An Action Icon URI (a vector drawable that can be tinted)

You define a list of Custom Browse Actions globally as part of your BrowseRoot . Then you can attach a subset of these actions to individual MediaItem.

When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction() . You can then handle the action and update the list of actions for the MediaItem if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.

Custom browse actions in a browse node root.

Figure 8. Custom Browse Action toolbar

You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.

How to implement Custom Browse Actions

Here are the steps to add Custom Browse Actions to your project:

  1. Override two methods in your MediaBrowserServiceCompat implementation:
  2. Parse the action limits at runtime:
  3. Build the global list of Custom Browse Actions:
    • For each action, create a Bundle object with the following keys: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID : The action ID * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL : The action label * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI : The action icon URI * Add all the action Bundle objects to a list.
  4. Add the global list to your BrowseRoot :
  5. Add actions to your MediaItem objects:
    • You can add actions to individual MediaItem objects by including the list of action IDs in the MediaDescriptionCompat extras using the key DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST . This list must be a subset of the global list of actions you defined in the BrowseRoot .
  6. Handle actions and return progress or results:

Here are some changes you can make in your BrowserServiceCompat to get started with Custom Browse Actions.

Override BrowserServiceCompat

You need to override the following methods in MediaBrowserServiceCompat .

public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

Parse actions limit

You should check to see how many Custom Browse Actions are supported.

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
    rootHints.getInt(
            MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
}

Build a Custom Browse Action

Each action needs to be packed into a separate Bundle .

  • অ্যাকশন আইডি
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
  • অ্যাকশন লেবেল
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
  • Action Icon URI
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")

Add Custom Browse Actions to Parceable ArrayList

Add all Custom Browse Action Bundle objects into an 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;
}

Add Custom Browse Action list to the browse root

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

Add actions to a 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 result

  • Parse mediaId from 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);
    }
  • For asynchronous results detach result. result.detach()
  • Build result bundle
    • Message to user
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
    • Update item(use to update actions in an item)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
    • Open Playback view
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
    • Update Browse Node
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
  • If an error, call result.sendError(resultBundle).
  • If progress update, call result.sendProgressUpdate(resultBundle) .
  • Finish by calling result.sendResult(resultBundle) .

Update Action State

By using the result.sendProgressUpdate(resultBundle) method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key, you can update the MediaItem to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.

Example: Download Action

Here's an example of how you can use this feature to implement a download action with three states:

  1. Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call sendProgressUpdate to update the UI.
  2. Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
  3. Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to indicate that the item should be refreshed. Additionally, you can use the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE key to display a success message to the user.

This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.

Example: Favorite Action

Another example is a favorite action with two states:

  1. Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to update the UI.
  2. Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to update the UI.

This approach provides a clear and consistent way for users to manage their favorite items.

These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.

For a complete example implementation of this feature, you can refer to the TestMediaApp project.

Enable playback control

Android Auto and Android Automotive OS send playback control commands through your service's MediaSessionCompat . You must register a session and implement the associated callback methods.

Register a media session

In your media browser service's onCreate() method, create a MediaSessionCompat , then register the media session by calling setSessionToken() .

The following code snippet shows how to create and register a media session:

কোটলিন

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
    ...
}

জাভা

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());
    ...
}

When you create the media session object, you set a callback object that is used to handle playback control requests. You create this callback object by providing an implementation of the MediaSessionCompat.Callback class for your app. The next section discusses how to implement this object.

Implement play commands

When a user requests playback for a media item from your app, Android Automotive OS and Android Auto use the MediaSessionCompat.Callback class from your app's MediaSessionCompat object that they obtained from your app's media browser service. When a user wants to control content playback, such as pausing playback or skipping to the next track, Android Auto and Android Automotive OS invoke one of the callback object's methods.

To handle content playback, your app must extend the abstract MediaSessionCompat.Callback class and implement the methods that your app supports.

Implement all the following callback methods that make sense for the type of content that your app offers:

onPrepare()
Invoked when the media source is changed. Android Automotive OS also invokes this method immediately after booting. Your media app must implement this method.
onPlay()
Invoked if the user chooses play without choosing a specific item. Your app must play its default content or, if playback was paused with onPause() , your app resumes playback.

Note: Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. For more information, see the section about setting the initial playback state .

onPlayFromMediaId()
Invoked when the user chooses to play a specific item. The method is passed the ID that your media browser service assigned to the media item in your content hierarchy.
onPlayFromSearch()
Invoked when the user chooses to play from a search query. The app must make an appropriate choice based on the search string that was passed in.
onPause()
Invoked when the user chooses to pause playback.
onSkipToNext()
Invoked when the user chooses to skip to the next item.
onSkipToPrevious()
Invoked when the user chooses to skip to the previous item.
onStop()
Invoked when the user chooses to stop playback.

Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext() method. You can use the default implementation of onSkipToNext() instead.

Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays content through a user's phone speakers or headphones. Android Auto and Android Automotive OS automatically send the audio content to the car's system to play over the car's speakers.

For more information about playing audio content, see MediaPlayer overview , Audio app overview , and the ExoPlayer overview .

Set standard playback actions

Android Auto and Android Automotive OS display playback controls based on the actions that are enabled in the PlaybackStateCompat object.

By default, your app must support the following actions:

Your app can additionally support the following actions if they are relevant to the app's content:

In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue() and setQueueTitle() methods, enable the ACTION_SKIP_TO_QUEUE_ITEM action, and define the callback onSkipToQueueItem() .

Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId() method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId() whenever there is a queue change.

Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback .

Reserve unused space

Android Auto and Android Automotive OS reserve space in the UI for the ACTION_SKIP_TO_PREVIOUS and ACTION_SKIP_TO_NEXT actions. If your app does not support one of these functions, Android Auto and Android Automotive OS use the space to display any custom actions you create.

If you don't want to fill those spaces with custom actions, you can reserve them so that Android Auto and Android Automotive OS leave the space blank whenever your app does not support the corresponding function. To do this, call the setExtras() method with an extras bundle that contains constants that correspond to the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT corresponds to ACTION_SKIP_TO_NEXT , and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV corresponds to ACTION_SKIP_TO_PREVIOUS . Use these constants as keys in the bundle, and use the boolean true for their values.

Set initial PlaybackState

As Android Auto and Android Automotive OS communicate with your media browser service, your media session communicates the status of content playback using the PlaybackStateCompat . Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. Instead, rely on Android Auto and Android Automotive OS to resume or start playback based on the car's state or user actions.

To accomplish this, set the initial PlaybackStateCompat of your media session to STATE_STOPPED , STATE_PAUSED , STATE_NONE , or STATE_ERROR .

Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat , and the queue.

Add custom playback actions

You can add custom playback actions to display additional actions that your media app supports. If space permits (and is not reserved) , Android adds the custom actions to the transport controls. Otherwise, the custom actions display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat .

Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.

You can add custom actions using the addCustomAction() method in the PlaybackStateCompat.Builder class.

The following code snippet shows how to add a custom “Start a radio channel” action:

কোটলিন

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

জাভা

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .setExtras(customActionExtras)
    .build());

For a more detailed example of this method, see the setCustomAction() method in the Universal Android Music Player sample app on GitHub.

After creating your custom action, your media session can respond to the action by overriding the onCustomAction() method.

The following code snippet shows how your app might respond to a “Start a radio channel” action:

কোটলিন

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

জাভা

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

For a more detailed example of this method, see the onCustomAction method in the Universal Android Music Player sample app on GitHub.

Icons for custom actions

Each custom action that you create requires an icon resource. Apps in cars can run on many different screen sizes and densities, so icons that you provide must be vector drawables . A vector drawable allows you to scale assets without losing the detail. A vector drawable also makes it easy to align edges and corners to pixel boundaries at smaller resolutions.

If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.

Provide alternative icon styles for disabled actions

When a custom action is unavailable for the current context, swap the custom action icon with an alternative icon that shows that the action is disabled.

Figure 6. Samples of off-style custom action icons.

Indicate audio format

To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata() ). Make sure to set both of those extras, to accommodate different layouts.

In addition, you can set the KEY_IMMERSIVE_AUDIO extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.

You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.

To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID (to link from the description). For details, see the reference documentation for those metadata fields.

Support voice actions

Your media app must support voice actions to help provide drivers with a safe and convenient experience that minimizes distractions. For example, if your app is playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."

When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch() . On receiving this callback, the app finds content matching the query string and starts playback.

Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras parameter. The following extras can be sent:

Account for an empty query string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.

If a search cannot be processed quickly, do not block in onPlayFromSearch() . Instead, set the playback state to STATE_CONNECTING and perform the search on an async thread.

Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.

In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause() and onSkipToNext() .

For a detailed example on how to implement voice-enabled playback actions in your app, see Google Assistant and media apps .

Implement distraction safeguards

Because a user's phone is connected to their car's speakers while using Android Auto, you must take additional precautions to help prevent driver distraction.

Suppress alarms in the car

Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.

To fulfill this requirement, your app can use CarConnection as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION .

If the user's phone is projecting, media apps that support alarms must do one of the following things:

  • Disable the alarm.
  • Play the alarm over STREAM_ALARM and provide a UI on the phone screen to disable the alarm.

Handle media advertisements

By default, Android Auto displays a notification when the media metadata changes during an audio playback session. When a media app switches from playing music to running an advertisement, it is distracting to display a notification to the user. To prevent Android Auto from displaying a notification in this case, you must set the media metadata key METADATA_KEY_IS_ADVERTISEMENT to METADATA_VALUE_ATTRIBUTE_PRESENT , as shown in the following code snippet:

কোটলিন

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())
    }
}

জাভা

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

Handle general errors

When the app experiences an error, set the playback state to STATE_ERROR and provide an error message using the setErrorMessage() method. See PlaybackStateCompat for a list of error codes that you can use when setting the error message. Error messages must be user-facing and localized with the user's current locale. Android Auto and Android Automotive OS can then display the error message to the user.

For example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION error code when setting the error message.

কোটলিন

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())

জাভা

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());

For more information about error states, see Using a media session: States and errors .

If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."

অন্যান্য সম্পদ

,

Android Auto and Android Automotive OS help you bring your media app content to users in their car. A media app for cars must provide a media browser service so that Android Auto and Android Automotive OS, or another app with a media browser, can discover and display your content.

This guide assumes that you already have a media app that plays audio on a phone and that your media app conforms to the Android media app architecture .

This guide describes the required components of a MediaBrowserService and MediaSession that your app needs in order to work on Android Auto or Android Automotive OS. After you have completed the core media infrastructure, you can add support for Android Auto and add support for Android Automotive OS to your media app.

আপনি শুরু করার আগে

  1. Review the Android media API documentation .
  2. Review Create media apps for design guidance.
  3. Review the key terms and concepts listed in this section.

মূল শর্তাবলী এবং ধারণা

Media browser service
An Android service implemented by your media app that complies with the MediaBrowserServiceCompat API. Your app uses this service to expose its content.
মিডিয়া ব্রাউজার
An API used by media apps to discover media browser services and display their content. Android Auto and Android Automotive OS use a media browser to find your app's media browser service.
Media item

The media browser organizes its content in a tree of MediaItem objects. A media item can have either or both of the following flags:

  • FLAG_PLAYABLE : indicates that the item is a leaf on the content tree. The item represents a single sound stream, such as a song on an album, a chapter in an audio book, or an episode of a podcast.
  • FLAG_BROWSABLE : indicates that the item is a node on the content tree and it has children. For example, the item represents an album, and its children are the songs on the album.

A media item that is both browsable and playable acts like a playlist. You can select the item itself to play all of its children, or you can browse its children.

Vehicle-optimized

An activity for an Android Automotive OS app that adheres to the Android Automotive OS design guidelines . The interface for these activities is not drawn by Android Automotive OS, so you must ensure that your app adheres to the design guidelines. Typically, this includes larger tap targets and font sizes, support for day and night modes, and higher contrast ratios.

Vehicle-optimized user interfaces are only allowed to be displayed when Car User Experience Restrictions (CUXRs) are not in effect, because these interfaces can require extended attention or interaction from the user. CUXRs are not in effect when the car is stopped or parked but are always in effect when the car is in motion.

You don't need to design activities for Android Auto, because Android Auto draws its own vehicle-optimized interface using the information from your media browser service.

Configure your app's manifest files

Before you can create your media browser service, you need to configure your app's manifest files .

Declare your media browser service

Both Android Auto and Android Automotive OS connect to your app through your media browser service to browse media items. Declare your media browser service in your manifest to let Android Auto and Android Automotive OS discover the service and connect to your app.

The following code snippet shows how to declare your media browser service in your manifest. Include this code in the manifest file for your Android Automotive OS module and in the manifest file for your phone app.

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

Specify app icons

You need to specify app icons that Android Auto and Android Automotive OS can use to represent your app in the system UI. Two icon types are required:

  • লঞ্চার আইকন
  • অ্যাট্রিবিউশন আইকন

লঞ্চার আইকন

The launcher icon represents your app in the system UI, such as on the launcher and in the tray of icons. You can specify that you want to use the icon from your mobile app to represent your car media app using the following manifest declaration:

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

To use a different icon than your mobile app's, set the android:icon property on your media browser service's <service> element in the manifest:

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

অ্যাট্রিবিউশন আইকন

Figure 1. Attribution icon on media card.

The attribution icon is used in places where media content takes precedence, such as on media cards. Consider reusing the small icon used for notifications. This icon must be monochrome. You can specify an icon that is used to represent your app using the following manifest declaration:

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

Create your media browser service

You create a media browser service by extending the MediaBrowserServiceCompat class. Both Android Auto and Android Automotive OS can then use your service to do the following:

  • Browse your app's content hierarchy to present a menu to the user.
  • Get the token for your app's MediaSessionCompat object to control audio playback.

You can also use your media browser service to let other clients access media content from your app. These media clients might be other apps on a user's phone, or they can be other remote clients.

Media browser service workflow

This section describes how Android Automotive OS and Android Auto interact with your media browser service during a typical user workflow.

  1. The user launches your app on Android Automotive OS or Android Auto.
  2. Android Automotive OS or Android Auto contacts your app's media browser service using the onCreate() method. In your implementation of the onCreate() method, you must create and register a MediaSessionCompat object and its callback object.
  3. Android Automotive OS or Android Auto calls your service's onGetRoot() method to get the root media item in your content hierarchy. The root media item is not displayed; instead, it's used to retrieve more content from your app.
  4. Android Automotive OS or Android Auto calls your service's onLoadChildren() method to get the children of the root media item. Android Automotive OS and Android Auto display these media items as the top level of content items. See Structure the root menu on this page for more information on what the system expects at this level.
  5. If the user selects a browsable media item, your service's onLoadChildren() method is called again to retrieve the children of the selected menu item.
  6. If the user selects a playable media item, Android Automotive OS or Android Auto calls the appropriate media session callback method to perform that action.
  7. If supported by your app, the user can also search your content. In this case, Android Automotive OS or Android Auto call your service's onSearch() method.

Build your content hierarchy

Android Auto and Android Automotive OS call your app's media browser service to find out what content is available. You need to implement two methods in your media browser service to support this: onGetRoot() and onLoadChildren()

Implement onGetRoot

Your service's onGetRoot() method returns information about the root node of your content hierarchy. Android Auto and Android Automotive OS use this root node to request the rest of your content using the onLoadChildren() method.

The following code snippet shows a simple implementation of the onGetRoot() method:

কোটলিন

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)

জাভা

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

For a more detailed example of this method, see the onGetRoot() method in the Universal Android Music Player sample app on GitHub.

Add package validation for onGetRoot()

When a call is made to your service's onGetRoot() method, the calling package passes identifying information to your service. Your service can use this information to decide whether that package can access your content. For example, you can restrict access to your app's content to a list of approved packages by comparing the clientPackageName to your allowlist and verifying the certificate used to sign the package's APK. If the package can't be verified, return null to deny access to your content.

To provide system apps, such as Android Auto and Android Automotive OS, with access to your content, your service must always return a non-null BrowserRoot when these system apps call the onGetRoot() method. The signature of the Android Automotive OS system app can vary depending on the make and model of the car, so you need to permit connections from all system apps to support Android Automotive OS robustly.

The following code snippet shows how your service can validate that the calling package is a system app:

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
}

This code snippet is an excerpt from the PackageValidator class in the Universal Android Music Player sample app on GitHub. See that class for a more detailed example of how to implement package validation for your service's onGetRoot() method.

In addition to allowing system apps, you must let the Google Assistant connect to your MediaBrowserService . Note that the Google Assistant has separate package names for the phone, which includes Android Auto, and for Android Automotive OS.

Implement onLoadChildren()

After receiving your root node object, Android Auto and Android Automotive OS build a top-level menu by calling onLoadChildren() on the root node object to get its children. Client apps build submenus by calling this same method using child node objects.

Each node in your content hierarchy is represented by a MediaBrowserCompat.MediaItem object. Each of these media items is identified by a unique ID string. Client apps treat these ID strings as opaque tokens. When a client app wants to browse to a submenu, or play a media item, it passes the token. Your app is responsible for associating the token with the appropriate media item.

The following code snippet shows a simple implementation of onLoadChildren() method:

কোটলিন

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

জাভা

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

For a complete example of this method, see the onLoadChildren() method in the Universal Android Music Player sample app on GitHub.

Structure the root menu

Figure 2. Root content displayed as navigational tabs.

Android Auto and Android Automotive OS have specific constraints about the structure of the root menu. These are communicated to the MediaBrowserService through root hints, which can be read through the Bundle argument passed into onGetRoot() . Following these hints lets the system display the root content optimally as navigational tabs. If you don't follow these hints, some root content might be dropped or made less discoverable by the system. Two hints are sent:

Use the following code to read the relevant root hints:

কোটলিন

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...
}

জাভা

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...
}

You can choose to branch the logic for the structure of your content hierarchy based on the values of these hints, particularly if your hierarchy varies among MediaBrowser integrations outside of Android Auto and Android Automotive OS. For example, if you normally show a root playable item, you might want to nest it under a root browsable item instead due to the value of the supported flags hint.

Apart from the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:

  • Supply monochrome, preferably white, icons for each tab item.
  • Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.

Display media artwork

Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT or ContentResolver.SCHEME_ANDROID_RESOURCE . This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat objects representing items in the content hierarchy, pass the URI through setIconUri() . For MediaMetadataCompat objects representing the currently playing item, pass the URI through putString() , using any of the following keys:

The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile() and the surrounding methods in the Universal Android Music Player sample app.

  1. Build a content:// URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.

    কোটলিন

    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()
    }

    জাভা

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
  2. In your implementation of ContentProvider.openFile() , check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .

    কোটলিন

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

    জাভা

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

For more details about content providers, refer to Creating a content provider .

Apply content styles

After building your content hierarchy using browsable or playable items, you can apply content styles that determine how those items display in the car.

You can use the following content styles:

তালিকা আইটেম

This content style prioritizes titles and metadata over images.

গ্রিড আইটেম

This content style prioritizes images over titles and metadata.

Set default content styles

You can set global defaults for how your media items are displayed by including certain constants in the BrowserRoot extras bundle of your service's onGetRoot() method. Android Auto and Android Automotive OS read this bundle and look for those constants to determine the appropriate style.

The following extras can be used as keys in the bundle:

The keys can map to the following integer constant values to influence the presentation of those items:

The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:

কোটলিন

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

জাভা

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

Set per-item content styles

The Content Style API lets you override the default content style for any browsable media item's children, as well as any media item itself.

To override the default for a browsable media item's children , create an extras bundle in the MediaDescription of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE applies to that item's browsable children.

To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM . Use the same values described previously to specify that item's presentation.

The following code snippet shows how to create a browsable MediaItem that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:

কোটলিন

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

জাভা

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

Group items using title hints

To group related media items together, you use a per-item hint. Every media item in a group needs to declare an extras bundle in their MediaDescription that includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE and an identical string value. Localize this string, which is used as the title of the group.

The following code snippet shows how to create a MediaItem with a subgroup heading of "Songs" :

কোটলিন

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*/)
}

জাভা

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*/);
}

Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:

  1. Media item A with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Media item B with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  3. Media item C with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Media item D with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  5. Media item E with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Because the media items for the "Songs" group and "Albums" group are not kept together in contiguous blocks, Android Auto and Android Automotive OS interprets this as the following four groups:

  • Group 1 called "Songs" containing media item A
  • Group 2 called "Albums" containing media item B
  • Group 3 called "Songs" containing media items C and D
  • Group 4 called "Albums" containing media item E

To display these items in two groups, your app must pass the media items in the following order instead:

  1. Media item A with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  2. Media item C with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  3. Media item D with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
  4. Media item B with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
  5. Media item E with extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )

Display additional metadata indicators

You can include additional metadata indicators to provide at-a-glance information for content in the media browser tree and during playback. Within the browse tree, Android Auto and Android Automotive OS read the extras associated with an item and look for certain constants to determine which indicators to display. During media playback, Android Auto and Android Automotive OS read the metadata for the media session and look for certain constants to determine indicators to display.

Figure 3. Playback view with metadata identifying the song and artist as well as an icon indicating explicit content.

Figure 4. Browse view with a dot for unplayed content on the first item and a progress bar for partially played content on the second item.

The following constants can be used in both MediaItem description extras and MediaMetadata extras:

The following constants can only be used in MediaItem description extras:

To display indicators that appear while the user is browsing the media browse tree, create an extras bundle that includes one or more of these constants and pass that bundle to the MediaDescription.Builder.setExtras() method.

The following code snippet shows how to display indicators for an explicit media item that is 70% complete:

কোটলিন

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 */)

জাভা

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 */);

To display indicators for a media item that is currently being played, you can declare Long values for METADATA_KEY_IS_EXPLICIT or EXTRA_DOWNLOAD_STATUS in the MediaMetadataCompat of your mediaSession . You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE indicators on the playback view.

The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:

কোটলিন

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())

জাভা

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());

Update the progress bar in the browse view as content is playing

As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.

For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat and PlaybackStateCompat to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:

The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:

কোটলিন

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())

জাভা

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());

Figure 5. Playback view with a “Search results” option for viewing media items related to the user's voice search.

Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.

To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED in the extras bundle of your service's onGetRoot() method, mapping to the boolean true .

The following code snippet shows how to enable support in the onGetRoot() method:

কোটলিন

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

জাভা

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

To start providing search results, override the onSearch() method in your media browser service. Android Auto and Android Automotive OS forward the user's search terms to this method whenever a user invokes a search query interface or “Search results” affordance.

You can organize the search results from your service's onSearch() method using title items to make them more browsable. For example, if your app plays music, you might organize search results by album, artist, and songs.

The following code snippet shows a simple implementation of the onSearch() method:

কোটলিন

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.
}

জাভা

@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.
}

Custom Browse Actions

A single custom browse action.

Figure 6. Single Custom Browse Action

Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.

A custom browse actions overflow menu.

Figure 7. Custom Browse Action overflow

If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.

তারা কিভাবে কাজ করে?

Each Custom Browse Action is defined with:

  • An Action ID (a unique string identifier)
  • An Action Label (the text displayed to the user)
  • An Action Icon URI (a vector drawable that can be tinted)

You define a list of Custom Browse Actions globally as part of your BrowseRoot . Then you can attach a subset of these actions to individual MediaItem.

When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction() . You can then handle the action and update the list of actions for the MediaItem if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.

Custom browse actions in a browse node root.

Figure 8. Custom Browse Action toolbar

You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.

How to implement Custom Browse Actions

Here are the steps to add Custom Browse Actions to your project:

  1. Override two methods in your MediaBrowserServiceCompat implementation:
  2. Parse the action limits at runtime:
  3. Build the global list of Custom Browse Actions:
    • For each action, create a Bundle object with the following keys: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID : The action ID * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL : The action label * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI : The action icon URI * Add all the action Bundle objects to a list.
  4. Add the global list to your BrowseRoot :
  5. Add actions to your MediaItem objects:
    • You can add actions to individual MediaItem objects by including the list of action IDs in the MediaDescriptionCompat extras using the key DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST . This list must be a subset of the global list of actions you defined in the BrowseRoot .
  6. Handle actions and return progress or results:

Here are some changes you can make in your BrowserServiceCompat to get started with Custom Browse Actions.

Override BrowserServiceCompat

You need to override the following methods in MediaBrowserServiceCompat .

public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

Parse actions limit

You should check to see how many Custom Browse Actions are supported.

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
    rootHints.getInt(
            MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
}

Build a Custom Browse Action

Each action needs to be packed into a separate Bundle .

  • অ্যাকশন আইডি
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
  • অ্যাকশন লেবেল
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
  • Action Icon URI
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")

Add Custom Browse Actions to Parceable ArrayList

Add all Custom Browse Action Bundle objects into an 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;
}

Add Custom Browse Action list to the browse root

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

Add actions to a 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 result

  • Parse mediaId from 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);
    }
  • For asynchronous results detach result. result.detach()
  • Build result bundle
    • Message to user
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
    • Update item(use to update actions in an item)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
    • Open Playback view
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
    • Update Browse Node
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
  • If an error, call result.sendError(resultBundle).
  • If progress update, call result.sendProgressUpdate(resultBundle) .
  • Finish by calling result.sendResult(resultBundle) .

Update Action State

By using the result.sendProgressUpdate(resultBundle) method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key, you can update the MediaItem to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.

Example: Download Action

Here's an example of how you can use this feature to implement a download action with three states:

  1. Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call sendProgressUpdate to update the UI.
  2. Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
  3. Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to indicate that the item should be refreshed. Additionally, you can use the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE key to display a success message to the user.

This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.

Example: Favorite Action

Another example is a favorite action with two states:

  1. Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to update the UI.
  2. Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call sendResult with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM key to update the UI.

This approach provides a clear and consistent way for users to manage their favorite items.

These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.

For a complete example implementation of this feature, you can refer to the TestMediaApp project.

Enable playback control

Android Auto and Android Automotive OS send playback control commands through your service's MediaSessionCompat . You must register a session and implement the associated callback methods.

Register a media session

In your media browser service's onCreate() method, create a MediaSessionCompat , then register the media session by calling setSessionToken() .

The following code snippet shows how to create and register a media session:

কোটলিন

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
    ...
}

জাভা

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());
    ...
}

When you create the media session object, you set a callback object that is used to handle playback control requests. You create this callback object by providing an implementation of the MediaSessionCompat.Callback class for your app. The next section discusses how to implement this object.

Implement play commands

When a user requests playback for a media item from your app, Android Automotive OS and Android Auto use the MediaSessionCompat.Callback class from your app's MediaSessionCompat object that they obtained from your app's media browser service. When a user wants to control content playback, such as pausing playback or skipping to the next track, Android Auto and Android Automotive OS invoke one of the callback object's methods.

To handle content playback, your app must extend the abstract MediaSessionCompat.Callback class and implement the methods that your app supports.

Implement all the following callback methods that make sense for the type of content that your app offers:

onPrepare()
Invoked when the media source is changed. Android Automotive OS also invokes this method immediately after booting. Your media app must implement this method.
onPlay()
Invoked if the user chooses play without choosing a specific item. Your app must play its default content or, if playback was paused with onPause() , your app resumes playback.

Note: Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. For more information, see the section about setting the initial playback state .

onPlayFromMediaId()
Invoked when the user chooses to play a specific item. The method is passed the ID that your media browser service assigned to the media item in your content hierarchy.
onPlayFromSearch()
Invoked when the user chooses to play from a search query. The app must make an appropriate choice based on the search string that was passed in.
onPause()
Invoked when the user chooses to pause playback.
onSkipToNext()
Invoked when the user chooses to skip to the next item.
onSkipToPrevious()
Invoked when the user chooses to skip to the previous item.
onStop()
Invoked when the user chooses to stop playback.

Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext() method. You can use the default implementation of onSkipToNext() instead.

Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays content through a user's phone speakers or headphones. Android Auto and Android Automotive OS automatically send the audio content to the car's system to play over the car's speakers.

For more information about playing audio content, see MediaPlayer overview , Audio app overview , and the ExoPlayer overview .

Set standard playback actions

Android Auto and Android Automotive OS display playback controls based on the actions that are enabled in the PlaybackStateCompat object.

By default, your app must support the following actions:

Your app can additionally support the following actions if they are relevant to the app's content:

In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue() and setQueueTitle() methods, enable the ACTION_SKIP_TO_QUEUE_ITEM action, and define the callback onSkipToQueueItem() .

Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId() method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId() whenever there is a queue change.

Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback .

Reserve unused space

Android Auto and Android Automotive OS reserve space in the UI for the ACTION_SKIP_TO_PREVIOUS and ACTION_SKIP_TO_NEXT actions. If your app does not support one of these functions, Android Auto and Android Automotive OS use the space to display any custom actions you create.

If you don't want to fill those spaces with custom actions, you can reserve them so that Android Auto and Android Automotive OS leave the space blank whenever your app does not support the corresponding function. To do this, call the setExtras() method with an extras bundle that contains constants that correspond to the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT corresponds to ACTION_SKIP_TO_NEXT , and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV corresponds to ACTION_SKIP_TO_PREVIOUS . Use these constants as keys in the bundle, and use the boolean true for their values.

Set initial PlaybackState

As Android Auto and Android Automotive OS communicate with your media browser service, your media session communicates the status of content playback using the PlaybackStateCompat . Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. Instead, rely on Android Auto and Android Automotive OS to resume or start playback based on the car's state or user actions.

To accomplish this, set the initial PlaybackStateCompat of your media session to STATE_STOPPED , STATE_PAUSED , STATE_NONE , or STATE_ERROR .

Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat , and the queue.

Add custom playback actions

You can add custom playback actions to display additional actions that your media app supports. If space permits (and is not reserved) , Android adds the custom actions to the transport controls. Otherwise, the custom actions display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat .

Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.

You can add custom actions using the addCustomAction() method in the PlaybackStateCompat.Builder class.

The following code snippet shows how to add a custom “Start a radio channel” action:

কোটলিন

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

জাভা

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .setExtras(customActionExtras)
    .build());

For a more detailed example of this method, see the setCustomAction() method in the Universal Android Music Player sample app on GitHub.

After creating your custom action, your media session can respond to the action by overriding the onCustomAction() method.

The following code snippet shows how your app might respond to a “Start a radio channel” action:

কোটলিন

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

জাভা

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

For a more detailed example of this method, see the onCustomAction method in the Universal Android Music Player sample app on GitHub.

Icons for custom actions

Each custom action that you create requires an icon resource. Apps in cars can run on many different screen sizes and densities, so icons that you provide must be vector drawables . A vector drawable allows you to scale assets without losing the detail. A vector drawable also makes it easy to align edges and corners to pixel boundaries at smaller resolutions.

If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.

Provide alternative icon styles for disabled actions

When a custom action is unavailable for the current context, swap the custom action icon with an alternative icon that shows that the action is disabled.

Figure 6. Samples of off-style custom action icons.

Indicate audio format

To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata() ). Make sure to set both of those extras, to accommodate different layouts.

In addition, you can set the KEY_IMMERSIVE_AUDIO extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.

You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.

To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID (to link from the description). For details, see the reference documentation for those metadata fields.

Support voice actions

Your media app must support voice actions to help provide drivers with a safe and convenient experience that minimizes distractions. For example, if your app is playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."

When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch() . On receiving this callback, the app finds content matching the query string and starts playback.

Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras parameter. The following extras can be sent:

Account for an empty query string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.

If a search cannot be processed quickly, do not block in onPlayFromSearch() . Instead, set the playback state to STATE_CONNECTING and perform the search on an async thread.

Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.

In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause() and onSkipToNext() .

For a detailed example on how to implement voice-enabled playback actions in your app, see Google Assistant and media apps .

Implement distraction safeguards

Because a user's phone is connected to their car's speakers while using Android Auto, you must take additional precautions to help prevent driver distraction.

Suppress alarms in the car

Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.

To fulfill this requirement, your app can use CarConnection as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION .

If the user's phone is projecting, media apps that support alarms must do one of the following things:

  • Disable the alarm.
  • Play the alarm over STREAM_ALARM and provide a UI on the phone screen to disable the alarm.

Handle media advertisements

By default, Android Auto displays a notification when the media metadata changes during an audio playback session. When a media app switches from playing music to running an advertisement, it is distracting to display a notification to the user. To prevent Android Auto from displaying a notification in this case, you must set the media metadata key METADATA_KEY_IS_ADVERTISEMENT to METADATA_VALUE_ATTRIBUTE_PRESENT , as shown in the following code snippet:

কোটলিন

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())
    }
}

জাভা

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

Handle general errors

When the app experiences an error, set the playback state to STATE_ERROR and provide an error message using the setErrorMessage() method. See PlaybackStateCompat for a list of error codes that you can use when setting the error message. Error messages must be user-facing and localized with the user's current locale. Android Auto and Android Automotive OS can then display the error message to the user.

For example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION error code when setting the error message.

কোটলিন

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())

জাভা

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());

For more information about error states, see Using a media session: States and errors .

If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."

অন্যান্য সম্পদ