একটি মিডিয়া ব্রাউজার পরিষেবা তৈরি করা

আপনার অ্যাপের ম্যানিফেস্টে অবশ্যই একটি ইন্টেন্ট-ফিল্টার সহ MediaBrowserService ডিক্লেয়ার করতে হবে। আপনি আপনার নিজের সার্ভিসের নাম বেছে নিতে পারেন। নিচের উদাহরণে, নির্বাচিত সার্ভিসের নাম হলো MediaPlaybackService

<service android:name=".MediaPlaybackService">
  <intent-filter>
    <action android:name="android.media.browse.MediaBrowserService" />
  </intent-filter>
</service>

মিডিয়া সেশন শুরু করুন

যখন সার্ভিসটি onCreate() লাইফসাইকেল কলব্যাক মেথডটি গ্রহণ করে, তখন তার এই ধাপগুলো সম্পাদন করা উচিত:

নিম্নলিখিত onCreate() কোডটি এই ধাপগুলো প্রদর্শন করে:

কোটলিন

private const val MY_MEDIA_ROOT_ID = "media_root_id"
private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"

class MediaPlaybackService : MediaBrowserServiceCompat() {

    private var mediaSession: MediaSessionCompat? = null
    private lateinit var stateBuilder: PlaybackStateCompat.Builder

    override fun onCreate() {
        super.onCreate()

        // Create a MediaSessionCompat
        mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply {

            // Enable callbacks from MediaButtons and TransportControls
            setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
                    or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
            )

            // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
            stateBuilder = PlaybackStateCompat.Builder()
                    .setActions(PlaybackStateCompat.ACTION_PLAY
                                    or PlaybackStateCompat.ACTION_PLAY_PAUSE
                    )
            setPlaybackState(stateBuilder.build())

            // MySessionCallback() has methods that handle callbacks from a media controller
            setCallback(MySessionCallback())

            // Set the session's token so that client activities can communicate with it.
            setSessionToken(sessionToken)
        }
    }
}

জাভা

public class MediaPlaybackService extends MediaBrowserServiceCompat {
    private static final String MY_MEDIA_ROOT_ID = "media_root_id";
    private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";

    private MediaSessionCompat mediaSession;
    private PlaybackStateCompat.Builder stateBuilder;

    @Override
    public void onCreate() {
        super.onCreate();

        // Create a MediaSessionCompat
        mediaSession = new MediaSessionCompat(context, LOG_TAG);

        // Enable callbacks from MediaButtons and TransportControls
        mediaSession.setFlags(
              MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
              MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

        // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
        stateBuilder = new PlaybackStateCompat.Builder()
                            .setActions(
                                PlaybackStateCompat.ACTION_PLAY |
                                PlaybackStateCompat.ACTION_PLAY_PAUSE);
        mediaSession.setPlaybackState(stateBuilder.build());

        // MySessionCallback() has methods that handle callbacks from a media controller
        mediaSession.setCallback(new MySessionCallback());

        // Set the session's token so that client activities can communicate with it.
        setSessionToken(mediaSession.getSessionToken());
    }
}

ক্লায়েন্ট সংযোগগুলি পরিচালনা করুন

একটি MediaBrowserService দুটি মেথড রয়েছে যা ক্লায়েন্ট সংযোগ পরিচালনা করে: onGetRoot() সার্ভিসটিতে অ্যাক্সেস নিয়ন্ত্রণ করে, এবং onLoadChildren() একটি ক্লায়েন্টকে MediaBrowserService এর কন্টেন্ট হায়ারার্কির একটি মেনু তৈরি ও প্রদর্শন করার ক্ষমতা প্রদান করে।

onGetRoot() ব্যবহার করে ক্লায়েন্ট সংযোগ নিয়ন্ত্রণ করা

onGetRoot() মেথডটি কন্টেন্ট হায়ারার্কির রুট নোডটি রিটার্ন করে। যদি মেথডটি null রিটার্ন করে, তাহলে কানেকশনটি প্রত্যাখ্যান করা হয়।

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

ক্লায়েন্টদের ব্রাউজ না করেই আপনার MediaSession-এ সংযোগ করার অনুমতি দিতে, onGetRoot() কে অবশ্যই একটি নন-নাল BrowserRoot রিটার্ন করতে হবে, কিন্তু রুট আইডিটি একটি খালি কন্টেন্ট হায়ারার্কি নির্দেশ করবে।

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

কোটলিন

override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot {

    // (Optional) Control the level of access for the specified package name.
    // You'll need to write your own logic to do this.
    return if (allowBrowsing(clientPackageName, clientUid)) {
        // Returns a root ID that clients can use with onLoadChildren() to retrieve
        // the content hierarchy.
        MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)
    } else {
        // Clients can connect, but this BrowserRoot is an empty hierarchy
        // so onLoadChildren returns nothing. This disables the ability to browse for content.
        MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null)
    }
}

জাভা

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

    // (Optional) Control the level of access for the specified package name.
    // You'll need to write your own logic to do this.
    if (allowBrowsing(clientPackageName, clientUid)) {
        // Returns a root ID that clients can use with onLoadChildren() to retrieve
        // the content hierarchy.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    } else {
        // Clients can connect, but this BrowserRoot is an empty hierarchy
        // so onLoadChildren returns nothing. This disables the ability to browse for content.
        return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null);
    }
}

কিছু ক্ষেত্রে, আপনি হয়তো নিয়ন্ত্রণ করতে চাইতে পারেন যে কারা আপনার MediaBrowserService এর সাথে সংযোগ করতে পারবে। এর একটি উপায় হলো অ্যাক্সেস কন্ট্রোল লিস্ট (ACL) ব্যবহার করা, যা নির্দিষ্ট করে দেয় কোন সংযোগগুলোর অনুমতি আছে, অথবা কোন সংযোগগুলো নিষিদ্ধ করা উচিত তার তালিকা তৈরি করে। নির্দিষ্ট সংযোগের অনুমতি দেয় এমন একটি ACL কীভাবে প্রয়োগ করতে হয় তার উদাহরণের জন্য, Universal Android Music Player স্যাম্পল অ্যাপের PackageValidator ক্লাসটি দেখুন।

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

onLoadChildren() এর সাথে বিষয়বস্তু যোগাযোগ করা

ক্লায়েন্ট সংযুক্ত হওয়ার পর, UI-এর একটি স্থানীয় উপস্থাপনা তৈরি করার জন্য এটি MediaBrowserCompat.subscribe() মেথডে বারবার কল করার মাধ্যমে কন্টেন্ট হায়ারার্কি অতিক্রম করতে পারে। subscribe() মেথডটি সার্ভিসে onLoadChildren() কলব্যাক পাঠায়, যা MediaBrowser.MediaItem অবজেক্টের একটি তালিকা ফেরত দেয়।

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

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

কোটলিন

override fun onLoadChildren(
        parentMediaId: String,
        result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>
) {
    //  Browsing not allowed
    if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) {
        result.sendResult(null)
        return
    }

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

    val mediaItems = emptyList<MediaBrowserCompat.MediaItem>()

    // Check if 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<MediaItem>> result) {

    //  Browsing not allowed
    if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) {
        result.sendResult(null);
        return;
    }

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

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

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

দ্রষ্টব্য: MediaBrowserService দ্বারা সরবরাহ করা MediaItem অবজেক্টগুলিতে আইকন বিটম্যাপ থাকা উচিত নয়। এর পরিবর্তে, প্রতিটি আইটেমের জন্য MediaDescription তৈরি করার সময় setIconUri() কল করে একটি Uri ব্যবহার করুন।

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

মিডিয়া ব্রাউজার পরিষেবা জীবনচক্র

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

যখন অন্য কোনো অ্যাক্টিভিটিতে চলমান একটি MediaBrowser , একটি MediaBrowserService এর সাথে সংযুক্ত হয়, তখন এটি অ্যাক্টিভিটিটিকে সার্ভিসটির সাথে বাইন্ড করে, ফলে সার্ভিসটি বাইন্ডেড হয় (কিন্তু স্টার্ট হয় না)। এই ডিফল্ট আচরণটি MediaBrowserServiceCompat ক্লাসের মধ্যে অন্তর্নির্মিত থাকে।

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

এই কারণে, প্লে শুরু করার সময় startService() কল করে সার্ভিসটি চালু আছে কিনা তা আপনাকে নিশ্চিত করতে হবে। একটি চালু সার্ভিসকে অবশ্যই স্পষ্টভাবে বন্ধ করতে হবে, সেটি বাইন্ড করা থাকুক বা না থাকুক। এটি নিশ্চিত করে যে নিয়ন্ত্রণকারী UI অ্যাক্টিভিটি আনবাইন্ড হয়ে গেলেও আপনার প্লেয়ারটি চলতে থাকবে।

চালু থাকা কোনো সার্ভিস বন্ধ করতে, Context.stopService() বা stopSelf() কল করুন। সিস্টেম যত দ্রুত সম্ভব সার্ভিসটি বন্ধ করে দেয় এবং ধ্বংস করে দেয়। তবে, যদি এক বা একাধিক ক্লায়েন্ট তখনও সার্ভিসটির সাথে সংযুক্ত থাকে, তাহলে সার্ভিসটি বন্ধ করার কলটি ততক্ষণ পর্যন্ত বিলম্বিত হয়, যতক্ষণ না এর সমস্ত ক্লায়েন্ট সংযোগ বিচ্ছিন্ন করে।

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

  • সার্ভিসটি তৈরি হয় যখন এটি কোনো মিডিয়া বাটনের প্রতিক্রিয়ায় চালু হয় অথবা যখন কোনো অ্যাক্টিভিটি (তার MediaBrowser মাধ্যমে সংযোগ করার পর) এর সাথে বাইন্ড হয়।
  • মিডিয়া সেশনের onPlay() কলব্যাকে startService() কল করার কোড অন্তর্ভুক্ত থাকা উচিত। এটি নিশ্চিত করে যে, সার্ভিসটি চালু থাকে এবং চলতে থাকে, এমনকি যখন এর সাথে সংযুক্ত সমস্ত UI MediaBrowser অ্যাক্টিভিটি সংযোগ বিচ্ছিন্ন হয়ে যায় তখনও।
  • onStop() কলব্যাকে stopSelf() কল করা উচিত। যদি সার্ভিসটি চালু করা হয়ে থাকে, তবে এটি সেটিকে বন্ধ করে দেয়। এছাড়াও, যদি কোনো অ্যাক্টিভিটি এর সাথে সংযুক্ত না থাকে, তবে সার্ভিসটি ধ্বংস হয়ে যায়। অন্যথায়, এর সমস্ত অ্যাক্টিভিটি সংযোগ বিচ্ছিন্ন না হওয়া পর্যন্ত সার্ভিসটি সংযুক্ত থাকে। (সার্ভিসটি ধ্বংস হওয়ার আগে যদি পরবর্তী কোনো startService() কল আসে, তবে অপেক্ষারত বন্ধ হওয়ার প্রক্রিয়াটি বাতিল হয়ে যায়।)

নিম্নলিখিত ফ্লোচার্টটি দেখায় কিভাবে একটি সার্ভিসের লাইফসাইকেল পরিচালিত হয়। ‘কাউন্টার’ ভেরিয়েবলটি সংযুক্ত ক্লায়েন্টের সংখ্যা ট্র্যাক করে:

Service Lifecycle

ফোরগ্রাউন্ড সার্ভিসের সাথে মিডিয়াস্টাইল নোটিফিকেশন ব্যবহার করা

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

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

প্লেয়ার প্লে হওয়া শুরু করলে নোটিফিকেশনটি তৈরি ও প্রদর্শন করুন। এটি করার জন্য সবচেয়ে ভালো জায়গা হলো MediaSessionCompat.Callback.onPlay() মেথডের ভেতরে।

নীচের উদাহরণটিতে NotificationCompat.MediaStyle ব্যবহার করা হয়েছে, যা মিডিয়া অ্যাপের জন্য ডিজাইন করা হয়েছে। এতে দেখানো হয়েছে কীভাবে মেটাডেটা এবং ট্রান্সপোর্ট কন্ট্রোল প্রদর্শন করে এমন একটি নোটিফিকেশন তৈরি করতে হয়। getController() নামক সুবিধাজনক মেথডটি আপনাকে সরাসরি আপনার মিডিয়া সেশন থেকে একটি মিডিয়া কন্ট্রোলার তৈরি করার সুযোগ দেয়।

কোটলিন

// Given a media session and its context (usually the component containing the session)
// Create a NotificationCompat.Builder

// Get the session's metadata
val controller = mediaSession.controller
val mediaMetadata = controller.metadata
val description = mediaMetadata.description

val builder = NotificationCompat.Builder(context, channelId).apply {
    // Add the metadata for the currently playing track
    setContentTitle(description.title)
    setContentText(description.subtitle)
    setSubText(description.description)
    setLargeIcon(description.iconBitmap)

    // Enable launching the player by clicking the notification
    setContentIntent(controller.sessionActivity)

    // Stop the service when the notification is swiped away
    setDeleteIntent(
            MediaButtonReceiver.buildMediaButtonPendingIntent(
                    context,
                    PlaybackStateCompat.ACTION_STOP
            )
    )

    // Make the transport controls visible on the lockscreen
    setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

    // Add an app icon and set its accent color
    // Be careful about the color
    setSmallIcon(R.drawable.notification_icon)
    color = ContextCompat.getColor(context, R.color.primaryDark)

    // Add a pause button
    addAction(
            NotificationCompat.Action(
                    R.drawable.pause,
                    getString(R.string.pause),
                    MediaButtonReceiver.buildMediaButtonPendingIntent(
                            context,
                            PlaybackStateCompat.ACTION_PLAY_PAUSE
                    )
            )
    )

    // Take advantage of MediaStyle features
    setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle()
            .setMediaSession(mediaSession.sessionToken)
            .setShowActionsInCompactView(0)

            // Add a cancel button
            .setShowCancelButton(true)
            .setCancelButtonIntent(
                    MediaButtonReceiver.buildMediaButtonPendingIntent(
                            context,
                            PlaybackStateCompat.ACTION_STOP
                    )
            )
    )
}

// Display the notification and place the service in the foreground
startForeground(id, builder.build())

জাভা

// Given a media session and its context (usually the component containing the session)
// Create a NotificationCompat.Builder

// Get the session's metadata
MediaControllerCompat controller = mediaSession.getController();
MediaMetadataCompat mediaMetadata = controller.getMetadata();
MediaDescriptionCompat description = mediaMetadata.getDescription();

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);

builder
    // Add the metadata for the currently playing track
    .setContentTitle(description.getTitle())
    .setContentText(description.getSubtitle())
    .setSubText(description.getDescription())
    .setLargeIcon(description.getIconBitmap())

    // Enable launching the player by clicking the notification
    .setContentIntent(controller.getSessionActivity())

    // Stop the service when the notification is swiped away
    .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
       PlaybackStateCompat.ACTION_STOP))

    // Make the transport controls visible on the lockscreen
    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

    // Add an app icon and set its accent color
    // Be careful about the color
    .setSmallIcon(R.drawable.notification_icon)
    .setColor(ContextCompat.getColor(context, R.color.primaryDark))

    // Add a pause button
    .addAction(new NotificationCompat.Action(
        R.drawable.pause, getString(R.string.pause),
        MediaButtonReceiver.buildMediaButtonPendingIntent(context,
            PlaybackStateCompat.ACTION_PLAY_PAUSE)))

    // Take advantage of MediaStyle features
    .setStyle(new MediaStyle()
        .setMediaSession(mediaSession.getSessionToken())
        .setShowActionsInCompactView(0)

        // Add a cancel button
       .setShowCancelButton(true)
       .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context,
           PlaybackStateCompat.ACTION_STOP)));

// Display the notification and place the service in the foreground
startForeground(id, builder.build());

MediaStyle নোটিফিকেশন ব্যবহার করার সময়, এই NotificationCompat সেটিংসগুলোর আচরণ সম্পর্কে সচেতন থাকুন:

  • আপনি যখন setContentIntent() ব্যবহার করেন, তখন নোটিফিকেশনে ক্লিক করা হলে আপনার সার্ভিসটি স্বয়ংক্রিয়ভাবে চালু হয়, যা একটি সুবিধাজনক বৈশিষ্ট্য।
  • লকস্ক্রিনের মতো একটি "অবিশ্বস্ত" পরিস্থিতিতে, নোটিফিকেশনের বিষয়বস্তুর ডিফল্ট ভিজিবিলিটি হলো VISIBILITY_PRIVATE । আপনি সম্ভবত লকস্ক্রিনে ট্রান্সপোর্ট কন্ট্রোলগুলো দেখতে চান, তাই VISIBILITY_PUBLIC ব্যবহার করাই শ্রেয়।
  • ব্যাকগ্রাউন্ড কালার সেট করার সময় সতর্ক থাকুন। অ্যান্ড্রয়েড ভার্সন ৫.০ বা তার পরবর্তী সংস্করণে একটি সাধারণ নোটিফিকেশনে, রঙটি শুধুমাত্র ছোট অ্যাপ আইকনের ব্যাকগ্রাউন্ডে প্রয়োগ করা হয়। কিন্তু অ্যান্ড্রয়েড ৭.০-এর আগের মিডিয়াস্টাইল নোটিফিকেশনের ক্ষেত্রে, রঙটি পুরো নোটিফিকেশন ব্যাকগ্রাউন্ডে ব্যবহৃত হয়। আপনার ব্যাকগ্রাউন্ড কালারটি পরীক্ষা করে নিন। চোখের জন্য সহনীয় রঙ বেছে নিন এবং অতিরিক্ত উজ্জ্বল বা ফ্লুরোসেন্ট রঙ এড়িয়ে চলুন।

এই সেটিংসগুলো শুধুমাত্র তখনই পাওয়া যাবে যখন আপনি NotificationCompat.MediaStyle ব্যবহার করছেন:

  • নোটিফিকেশনটিকে আপনার সেশনের সাথে যুক্ত করতে setMediaSession() ব্যবহার করুন। এর ফলে থার্ড-পার্টি অ্যাপ এবং সহযোগী ডিভাইসগুলো সেশনটি অ্যাক্সেস ও নিয়ন্ত্রণ করতে পারে।
  • নোটিফিকেশনের স্ট্যান্ডার্ড-আকারের কন্টেন্টভিউতে দেখানোর জন্য সর্বোচ্চ ৩টি অ্যাকশন যোগ করতে setShowActionsInCompactView() ব্যবহার করুন। (এখানে পজ বাটনটি নির্দিষ্ট করা হয়েছে।)
  • অ্যান্ড্রয়েড ৫.০ (এপিআই লেভেল ২১) এবং এর পরবর্তী সংস্করণগুলিতে, সার্ভিসটি ফোরগ্রাউন্ডে চালু না থাকলে আপনি নোটিফিকেশনটি সোয়াইপ করে প্লেয়ারটি বন্ধ করতে পারেন। এর আগের সংস্করণগুলিতে আপনি এটি করতে পারবেন না। অ্যান্ড্রয়েড ৫.০ (এপিআই লেভেল ২১)-এর আগে ব্যবহারকারীদের নোটিফিকেশনটি সরিয়ে প্লেব্যাক বন্ধ করার সুযোগ দিতে, আপনি setShowCancelButton(true) এবং setCancelButtonIntent() কল করে নোটিফিকেশনের উপরের-ডান কোণায় একটি ক্যানসেল বাটন যোগ করতে পারেন।

যখন আপনি পজ এবং ক্যানসেল বাটন যোগ করবেন, তখন প্লেব্যাক অ্যাকশনের সাথে সংযুক্ত করার জন্য আপনার একটি PendingIntent-এর প্রয়োজন হবে। MediaButtonReceiver.buildMediaButtonPendingIntent() মেথডটি একটি PlaybackState অ্যাকশনকে PendingIntent-এ রূপান্তর করার কাজটি করে।

AVRCP মিডিয়া ব্রাউজিং সক্ষম করুন

অ্যান্ড্রয়েড অটো-র মতো কাস্টম অ্যাপ ছাড়াও, সিস্টেমের ব্লুটুথ লেয়ারটি আপনার MediaBrowserService এর ক্লায়েন্ট হিসেবে কাজ করে ওয়্যারলেস রিমোট ক্যাটালগ ব্রাউজিং (AVRCP) সহজতর করে।

অ্যান্ড্রয়েড ১৬ এবং অ্যান্ড্রয়েড ১৭-তে, যে অ্যাপগুলো Media3 ব্যবহার করে না, ব্রাউজিংয়ের জন্য বৈধতা পেতে সেগুলোকে একটি ইন্টেন্ট ফিল্টারসহ নির্দিষ্ট অ্যাক্টিভিটি প্রকাশ করতে হয়।

আপনার AndroidManifest.xml এর একটি এক্সপোর্টেড অ্যাক্টিভিটিতে এই নির্দিষ্ট ইন্টেন্ট ফিল্টারটি যোগ করুন। উল্লেখ্য যে, লোকাল অডিও ফাইলের জন্য সাধারণ "Open with" মেনুতে আপনার অ্যাপটি যাতে প্রদর্শিত না হয়, সেজন্য CATEGORY_DEFAULT ইচ্ছাকৃতভাবে বাদ দেওয়া হয়েছে:

<activity
    android:name=".BluetoothValidationActivity"
    android:exported="true"
    android:theme="@android:style/Theme.NoDisplay"
    android:excludeFromRecents="true"
    android:noHistory="true">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="content" />
    <data android:host="media" />
    <!-- Specific path check used by Bluetooth stack for validation -->
    <data android:pathPrefix="/internal/audio/media/" />
    <data android:mimeType="audio/*" />
  </intent-filter>
</activity>