মিডিয়া নিয়ন্ত্রণ

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

  • স্ট্রিমগুলি ফোনে স্থানীয়ভাবে বাজছে৷
  • দূরবর্তী স্ট্রীম, যেমন বহিরাগত ডিভাইস বা কাস্ট সেশনে সনাক্ত করা হয়
  • পূর্ববর্তী পুনঃসূচনাযোগ্য সেশন, যে ক্রমে সেগুলি শেষ খেলা হয়েছিল

অ্যান্ড্রয়েড 13 (API লেভেল 33) থেকে শুরু করে, ব্যবহারকারীরা যাতে মিডিয়া প্লে করা অ্যাপগুলির জন্য মিডিয়া কন্ট্রোলের একটি সমৃদ্ধ সেট অ্যাক্সেস করতে পারে তা নিশ্চিত করতে, মিডিয়া কন্ট্রোলের অ্যাকশন বোতামগুলি Player স্টেট থেকে নেওয়া হয়েছে।

এইভাবে, আপনি মিডিয়া নিয়ন্ত্রণের একটি সামঞ্জস্যপূর্ণ সেট এবং ডিভাইস জুড়ে আরও পালিশ মিডিয়া নিয়ন্ত্রণের অভিজ্ঞতা উপস্থাপন করতে পারেন।

চিত্র 1 যথাক্রমে একটি ফোন এবং ট্যাবলেট ডিভাইসে এটি কীভাবে দেখায় তার একটি উদাহরণ দেখায়।

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

নিম্নলিখিত টেবিলে বর্ণিত Player অবস্থার উপর ভিত্তি করে সিস্টেমটি পাঁচটি পর্যন্ত অ্যাকশন বোতাম প্রদর্শন করে। কমপ্যাক্ট মোডে, শুধুমাত্র প্রথম তিনটি অ্যাকশন স্লট প্রদর্শিত হয়। এটি অটো, অ্যাসিস্ট্যান্ট এবং Wear OS-এর মতো অন্যান্য Android প্ল্যাটফর্মগুলিতে মিডিয়া নিয়ন্ত্রণগুলি কীভাবে রেন্ডার করা হয় তার সাথে সারিবদ্ধ করে।

স্লট মানদণ্ড অ্যাকশন
1 playWhenReady মিথ্যা বা বর্তমান প্লেব্যাক অবস্থা STATE_ENDED খেলা
playWhenReady সত্য এবং বর্তমান প্লেব্যাকের অবস্থা হল STATE_BUFFERING স্পিনার লোড হচ্ছে
playWhenReady সত্য এবং বর্তমান প্লেব্যাক অবস্থা STATE_READY বিরতি
2 প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ৷ আগের
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
3 প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ৷ পরবর্তী
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
4 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম
5 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম

কাস্টম কমান্ডগুলি কাস্টম লেআউটে যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়।

কমান্ড বোতাম কাস্টমাইজ করুন

Jetpack Media3 এর সাথে সিস্টেম মিডিয়া কন্ট্রোল কাস্টমাইজ করতে, আপনি সেশনের কাস্টম লেআউট এবং সেই অনুযায়ী কন্ট্রোলারের উপলব্ধ কমান্ড সেট করতে পারেন, যখন একটি MediaSessionService প্রয়োগ করা হয় :

  1. onCreate() এ, একটি MediaSession তৈরি করুন এবং কমান্ড বোতামগুলির কাস্টম বিন্যাস সংজ্ঞায়িত করুন

  2. MediaSession.Callback.onConnect() এ, ConnectionResultকাস্টম কমান্ড সহ তাদের উপলব্ধ কমান্ডগুলিকে সংজ্ঞায়িত করে কন্ট্রোলারদের অনুমোদন করুন।

  3. MediaSession.Callback.onCustomCommand() এ, ব্যবহারকারীর দ্বারা নির্বাচিত কাস্টম কমান্ডের প্রতিক্রিয়া জানান।

কোটলিন

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

জাভা

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

আপনার MediaSession কনফিগার করার বিষয়ে আরও জানতে যাতে সিস্টেমের মতো ক্লায়েন্টরা আপনার মিডিয়া অ্যাপের সাথে সংযোগ করতে পারে, দেখুন অন্যান্য ক্লায়েন্টদের নিয়ন্ত্রণ করুন

Jetpack Media3 এর সাথে, আপনি যখন একটি MediaSession প্রয়োগ করেন, তখন আপনার PlaybackState স্বয়ংক্রিয়ভাবে মিডিয়া প্লেয়ারের সাথে আপ-টু-ডেট থাকে। একইভাবে, আপনি যখন একটি MediaSessionService প্রয়োগ করেন, লাইব্রেরি স্বয়ংক্রিয়ভাবে আপনার জন্য একটি MediaStyle বিজ্ঞপ্তি প্রকাশ করে এবং এটি আপ-টু-ডেট রাখে।

অ্যাকশন বোতামে সাড়া দিন

যখন কোনো ব্যবহারকারী সিস্টেম মিডিয়া কন্ট্রোলে একটি অ্যাকশন বোতামে ট্যাপ করে, তখন সিস্টেমের MediaController আপনার MediaSession এ একটি প্লেব্যাক কমান্ড পাঠায়। MediaSession তারপর সেই কমান্ডগুলি প্লেয়ারের কাছে অর্পণ করে। Media3 এর Player ইন্টারফেসে সংজ্ঞায়িত কমান্ডগুলি মিডিয়া সেশন দ্বারা স্বয়ংক্রিয়ভাবে পরিচালনা করা হয়।

একটি কাস্টম কমান্ডে কীভাবে প্রতিক্রিয়া জানাতে হয় তার নির্দেশনার জন্য কাস্টম কমান্ড যুক্ত করুন দেখুন।

প্রি-অ্যান্ড্রয়েড 13 আচরণ

পশ্চাদগামী সামঞ্জস্যের জন্য, সিস্টেম UI একটি বিকল্প লেআউট প্রদান করে যা অ্যাপ্লিকেশানগুলির জন্য বিজ্ঞপ্তি অ্যাকশন ব্যবহার করে যেগুলি Android 13 টার্গেট করার জন্য আপডেট হয় না, বা যেগুলি PlaybackState তথ্য অন্তর্ভুক্ত করে না। অ্যাকশন বোতামগুলি MediaStyle বিজ্ঞপ্তির সাথে সংযুক্ত Notification.Action তালিকা থেকে প্রাপ্ত। যে ক্রমানুসারে সেগুলি যোগ করা হয়েছিল সেই ক্রমে সিস্টেমটি পাঁচটি পর্যন্ত ক্রিয়া প্রদর্শন করে৷ কমপ্যাক্ট মোডে, তিনটি পর্যন্ত বোতাম দেখানো হয়, setShowActionsInCompactView() এ পাস করা মান দ্বারা নির্ধারিত হয়।

কাস্টম অ্যাকশনগুলি PlaybackState -এ যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়৷

নিম্নলিখিত কোড উদাহরণটি ব্যাখ্যা করে কিভাবে MediaStyle বিজ্ঞপ্তিতে অ্যাকশন যোগ করতে হয়:

কোটলিন

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

জাভা

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

মিডিয়া পুনঃসূচনা সমর্থন

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

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

মিডিয়া পুনরুদ্ধার সমর্থন করা সহজ করার জন্য Media3 API-গুলি অফার করে৷ এই বৈশিষ্ট্যটি বাস্তবায়নের নির্দেশনার জন্য Media3 ডকুমেন্টেশন সহ প্লেব্যাক পুনঃসূচনা দেখুন।

লিগ্যাসি মিডিয়া API ব্যবহার করে

এই বিভাগটি ব্যাখ্যা করে কিভাবে লিগ্যাসি MediaCompat API ব্যবহার করে সিস্টেম মিডিয়া কন্ট্রোলের সাথে একীভূত করা যায়।

সিস্টেমটি MediaSession এর MediaMetadata থেকে নিম্নলিখিত তথ্য পুনরুদ্ধার করে, এবং যখন এটি উপলব্ধ থাকে তখন এটি প্রদর্শন করে:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (যদি সময়কাল সেট না করা হয় তবে অনুসন্ধান বার অগ্রগতি দেখায় না)

আপনার কাছে একটি বৈধ এবং সঠিক মিডিয়া কন্ট্রোল বিজ্ঞপ্তি আছে তা নিশ্চিত করতে, METADATA_KEY_TITLE বা METADATA_KEY_DISPLAY_TITLE মেটাডেটার মান বর্তমানে প্লে করা মিডিয়ার শিরোনামে সেট করুন৷

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার জন্য অতিবাহিত সময় দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়।

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার অগ্রগতি দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়। সিক বার ব্যবহারকারীদের অবস্থান পরিবর্তন করতে দেয় এবং মিডিয়া আইটেমের জন্য অতিবাহিত সময় প্রদর্শন করে। সন্ধান বার সক্ষম করার জন্য, আপনাকে অবশ্যই PlaybackState.Builder#setActions প্রয়োগ করতে হবে এবং ACTION_SEEK_TO অন্তর্ভুক্ত করতে হবে।

স্লট অ্যাকশন মানদণ্ড
1 খেলা PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
স্পিনার লোড হচ্ছে PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_CONNECTING
  • STATE_BUFFERING
বিরতি PlaybackState বর্তমান অবস্থা উপরের কোনটি নয়।
2 আগের PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_PREVIOUS
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_PREVIOUS অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
3 পরবর্তী PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_NEXT
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_NEXT অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT কী-এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
4 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।
5 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।

স্ট্যান্ডার্ড অ্যাকশন যোগ করুন

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে PlaybackState স্ট্যান্ডার্ড এবং কাস্টম অ্যাকশন যোগ করতে হয় তা ব্যাখ্যা করে।

প্লে, পজ, পূর্ববর্তী এবং পরবর্তী, মিডিয়া সেশনের জন্য PlaybackState এই ক্রিয়াগুলি সেট করুন৷

কোটলিন

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

জাভা

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

আপনি যদি আগের বা পরবর্তী স্লটে কোনো বোতাম না চান, তাহলে ACTION_SKIP_TO_PREVIOUS বা ACTION_SKIP_TO_NEXT যোগ করবেন না এবং পরিবর্তে সেশনে অতিরিক্ত যোগ করুন:

কোটলিন

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

জাভা

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

কাস্টম অ্যাকশন যোগ করুন

আপনি মিডিয়া কন্ট্রোলে দেখাতে চান এমন অন্যান্য ক্রিয়াগুলির জন্য, আপনি একটি PlaybackStateCompat.CustomAction তৈরি করতে পারেন এবং পরিবর্তে PlaybackState যোগ করতে পারেন৷ এই ক্রিয়াগুলি যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে দেখানো হয়েছে৷

কোটলিন

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

জাভা

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

প্লেব্যাকস্টেট অ্যাকশনের প্রতিক্রিয়া

যখন একজন ব্যবহারকারী একটি বোতামে ট্যাপ করে, তখন MediaSession এ একটি কমান্ড ফেরত পাঠাতে SystemUI MediaController.TransportControls ব্যবহার করে। আপনাকে একটি কলব্যাক নিবন্ধন করতে হবে যা এই ইভেন্টগুলিতে সঠিকভাবে প্রতিক্রিয়া জানাতে পারে।

কোটলিন

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

জাভা

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

মিডিয়া পুনঃসূচনা

আপনার প্লেয়ার অ্যাপটিকে দ্রুত সেটিং সেটিংস এলাকায় উপস্থিত করতে, আপনাকে অবশ্যই একটি বৈধ MediaSession টোকেন সহ একটি MediaStyle বিজ্ঞপ্তি তৈরি করতে হবে৷

MediaStyle বিজ্ঞপ্তির শিরোনাম প্রদর্শন করতে, NotificationBuilder.setContentTitle() ব্যবহার করুন।

মিডিয়া প্লেয়ারের জন্য ব্র্যান্ড আইকন প্রদর্শন করতে, NotificationBuilder.setSmallIcon() ব্যবহার করুন।

প্লেব্যাক পুনরুদ্ধার সমর্থন করতে, অ্যাপগুলিকে অবশ্যই একটি MediaBrowserService এবং MediaSession প্রয়োগ করতে হবে৷ আপনার MediaSession অবশ্যই onPlay() কলব্যাক বাস্তবায়ন করবে।

MediaBrowserService বাস্তবায়ন

ডিভাইস বুট হওয়ার পরে, সিস্টেমটি সবচেয়ে সাম্প্রতিক ব্যবহৃত পাঁচটি মিডিয়া অ্যাপের সন্ধান করে এবং প্রতিটি অ্যাপ থেকে খেলা পুনরায় চালু করতে ব্যবহার করা যেতে পারে এমন নিয়ন্ত্রণ প্রদান করে।

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

SystemUI থেকে সংযোগগুলিকে প্যাকেজের নাম com.android.systemui এবং স্বাক্ষর ব্যবহার করে সনাক্ত ও যাচাই করা যেতে পারে। SystemUI প্ল্যাটফর্ম স্বাক্ষর সহ স্বাক্ষরিত হয়। প্ল্যাটফর্ম স্বাক্ষরের বিপরীতে কীভাবে পরীক্ষা করা যায় তার একটি উদাহরণ UAMP অ্যাপে পাওয়া যাবে।

প্লেব্যাক পুনরুদ্ধার সমর্থন করার জন্য, আপনার MediaBrowserService অবশ্যই এই আচরণগুলি বাস্তবায়ন করবে:

  • onGetRoot() দ্রুত একটি নন-নাল রুট ফেরত দিতে হবে। অন্যান্য জটিল যুক্তি onLoadChildren() এ পরিচালনা করা উচিত

  • যখন onLoadChildren() রুট মিডিয়া আইডিতে কল করা হয়, ফলাফলে অবশ্যই একটি FLAG_PLAYABLE চাইল্ড থাকতে হবে।

  • MediaBrowserService একটি EXTRA_RECENT ক্যোয়ারী পেলে অতি সাম্প্রতিক প্লে করা মিডিয়া আইটেমটি ফেরত দেওয়া উচিত৷ ফেরত দেওয়া মানটি জেনেরিক ফাংশনের পরিবর্তে একটি প্রকৃত মিডিয়া আইটেম হওয়া উচিত।

  • MediaBrowserService একটি অ-খালি শিরোনাম এবং সাবটাইটেল সহ একটি উপযুক্ত MediaDescription প্রদান করতে হবে৷ এটি একটি আইকন URI বা একটি আইকন বিটম্যাপও সেট করা উচিত।

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে onGetRoot() বাস্তবায়ন করতে হয় তা ব্যাখ্যা করে।

কোটলিন

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

জাভা

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}
,

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

  • স্ট্রিমগুলি ফোনে স্থানীয়ভাবে বাজছে৷
  • দূরবর্তী স্ট্রীম, যেমন বহিরাগত ডিভাইস বা কাস্ট সেশনে সনাক্ত করা হয়
  • পূর্ববর্তী পুনঃসূচনাযোগ্য সেশন, যে ক্রমে সেগুলি শেষ খেলা হয়েছিল

অ্যান্ড্রয়েড 13 (API লেভেল 33) থেকে শুরু করে, ব্যবহারকারীরা যাতে মিডিয়া প্লে করা অ্যাপগুলির জন্য মিডিয়া কন্ট্রোলের একটি সমৃদ্ধ সেট অ্যাক্সেস করতে পারে তা নিশ্চিত করতে, মিডিয়া কন্ট্রোলের অ্যাকশন বোতামগুলি Player স্টেট থেকে নেওয়া হয়েছে।

এইভাবে, আপনি মিডিয়া নিয়ন্ত্রণের একটি সামঞ্জস্যপূর্ণ সেট এবং ডিভাইস জুড়ে আরও পালিশ মিডিয়া নিয়ন্ত্রণের অভিজ্ঞতা উপস্থাপন করতে পারেন।

চিত্র 1 যথাক্রমে একটি ফোন এবং ট্যাবলেট ডিভাইসে এটি কীভাবে দেখায় তার একটি উদাহরণ দেখায়।

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

নিম্নলিখিত টেবিলে বর্ণিত Player অবস্থার উপর ভিত্তি করে সিস্টেমটি পাঁচটি পর্যন্ত অ্যাকশন বোতাম প্রদর্শন করে। কমপ্যাক্ট মোডে, শুধুমাত্র প্রথম তিনটি অ্যাকশন স্লট প্রদর্শিত হয়। এটি অটো, অ্যাসিস্ট্যান্ট এবং Wear OS-এর মতো অন্যান্য Android প্ল্যাটফর্মগুলিতে মিডিয়া নিয়ন্ত্রণগুলি কীভাবে রেন্ডার করা হয় তার সাথে সারিবদ্ধ করে।

স্লট মানদণ্ড অ্যাকশন
1 playWhenReady মিথ্যা বা বর্তমান প্লেব্যাক অবস্থা STATE_ENDED খেলা
playWhenReady সত্য এবং বর্তমান প্লেব্যাকের অবস্থা হল STATE_BUFFERING স্পিনার লোড হচ্ছে
playWhenReady সত্য এবং বর্তমান প্লেব্যাক অবস্থা STATE_READY বিরতি
2 প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ৷ আগের
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
3 প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ৷ পরবর্তী
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
4 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম
5 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম

কাস্টম কমান্ডগুলি কাস্টম লেআউটে যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়।

কমান্ড বোতাম কাস্টমাইজ করুন

Jetpack Media3 এর সাথে সিস্টেম মিডিয়া কন্ট্রোল কাস্টমাইজ করতে, আপনি সেশনের কাস্টম লেআউট এবং সেই অনুযায়ী কন্ট্রোলারের উপলব্ধ কমান্ড সেট করতে পারেন, যখন একটি MediaSessionService প্রয়োগ করা হয় :

  1. onCreate() এ, একটি MediaSession তৈরি করুন এবং কমান্ড বোতামগুলির কাস্টম বিন্যাস সংজ্ঞায়িত করুন

  2. MediaSession.Callback.onConnect() এ, ConnectionResultকাস্টম কমান্ড সহ তাদের উপলব্ধ কমান্ডগুলিকে সংজ্ঞায়িত করে কন্ট্রোলারদের অনুমোদন করুন।

  3. MediaSession.Callback.onCustomCommand() এ, ব্যবহারকারীর দ্বারা নির্বাচিত কাস্টম কমান্ডের প্রতিক্রিয়া জানান।

কোটলিন

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

জাভা

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

আপনার MediaSession কনফিগার করার বিষয়ে আরও জানতে যাতে সিস্টেমের মতো ক্লায়েন্টরা আপনার মিডিয়া অ্যাপের সাথে সংযোগ করতে পারে, দেখুন অন্যান্য ক্লায়েন্টদের নিয়ন্ত্রণ করুন

Jetpack Media3 এর সাথে, আপনি যখন একটি MediaSession প্রয়োগ করেন, তখন আপনার PlaybackState স্বয়ংক্রিয়ভাবে মিডিয়া প্লেয়ারের সাথে আপ-টু-ডেট থাকে। একইভাবে, আপনি যখন একটি MediaSessionService প্রয়োগ করেন, লাইব্রেরি স্বয়ংক্রিয়ভাবে আপনার জন্য একটি MediaStyle বিজ্ঞপ্তি প্রকাশ করে এবং এটি আপ-টু-ডেট রাখে।

অ্যাকশন বোতামে সাড়া দিন

যখন কোনো ব্যবহারকারী সিস্টেম মিডিয়া কন্ট্রোলে একটি অ্যাকশন বোতামে ট্যাপ করে, তখন সিস্টেমের MediaController আপনার MediaSession এ একটি প্লেব্যাক কমান্ড পাঠায়। MediaSession তারপর সেই কমান্ডগুলি প্লেয়ারের কাছে অর্পণ করে। Media3 এর Player ইন্টারফেসে সংজ্ঞায়িত কমান্ডগুলি মিডিয়া সেশন দ্বারা স্বয়ংক্রিয়ভাবে পরিচালনা করা হয়।

একটি কাস্টম কমান্ডে কীভাবে প্রতিক্রিয়া জানাতে হয় তার নির্দেশনার জন্য কাস্টম কমান্ড যুক্ত করুন দেখুন।

প্রি-অ্যান্ড্রয়েড 13 আচরণ

পশ্চাদগামী সামঞ্জস্যের জন্য, সিস্টেম UI একটি বিকল্প লেআউট প্রদান করে যা অ্যাপ্লিকেশানগুলির জন্য বিজ্ঞপ্তি অ্যাকশন ব্যবহার করে যেগুলি Android 13 টার্গেট করার জন্য আপডেট হয় না, বা যেগুলি PlaybackState তথ্য অন্তর্ভুক্ত করে না। অ্যাকশন বোতামগুলি MediaStyle বিজ্ঞপ্তির সাথে সংযুক্ত Notification.Action তালিকা থেকে প্রাপ্ত। যে ক্রমানুসারে সেগুলি যোগ করা হয়েছিল সেই ক্রমে সিস্টেমটি পাঁচটি পর্যন্ত ক্রিয়া প্রদর্শন করে৷ কমপ্যাক্ট মোডে, তিনটি পর্যন্ত বোতাম দেখানো হয়, setShowActionsInCompactView() এ পাস করা মান দ্বারা নির্ধারিত হয়।

কাস্টম অ্যাকশনগুলি PlaybackState -এ যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়৷

নিম্নলিখিত কোড উদাহরণটি ব্যাখ্যা করে কিভাবে MediaStyle বিজ্ঞপ্তিতে অ্যাকশন যোগ করতে হয়:

কোটলিন

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

জাভা

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

মিডিয়া পুনঃসূচনা সমর্থন

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

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

মিডিয়া পুনরুদ্ধার সমর্থন করা সহজ করার জন্য Media3 API-গুলি অফার করে৷ এই বৈশিষ্ট্যটি বাস্তবায়নের নির্দেশনার জন্য Media3 ডকুমেন্টেশন সহ প্লেব্যাক পুনঃসূচনা দেখুন।

লিগ্যাসি মিডিয়া API ব্যবহার করে

এই বিভাগটি ব্যাখ্যা করে কিভাবে লিগ্যাসি MediaCompat API ব্যবহার করে সিস্টেম মিডিয়া কন্ট্রোলের সাথে একীভূত করা যায়।

সিস্টেমটি MediaSession এর MediaMetadata থেকে নিম্নলিখিত তথ্য পুনরুদ্ধার করে, এবং যখন এটি উপলব্ধ থাকে তখন এটি প্রদর্শন করে:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (যদি সময়কাল সেট না করা হয় তবে অনুসন্ধান বার অগ্রগতি দেখায় না)

আপনার কাছে একটি বৈধ এবং সঠিক মিডিয়া কন্ট্রোল বিজ্ঞপ্তি আছে তা নিশ্চিত করতে, METADATA_KEY_TITLE বা METADATA_KEY_DISPLAY_TITLE মেটাডেটার মান বর্তমানে প্লে করা মিডিয়ার শিরোনামে সেট করুন৷

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার জন্য অতিবাহিত সময় দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়।

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার অগ্রগতি দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়। সিক বার ব্যবহারকারীদের অবস্থান পরিবর্তন করতে দেয় এবং মিডিয়া আইটেমের জন্য অতিবাহিত সময় প্রদর্শন করে। সন্ধান বার সক্ষম করার জন্য, আপনাকে অবশ্যই PlaybackState.Builder#setActions প্রয়োগ করতে হবে এবং ACTION_SEEK_TO অন্তর্ভুক্ত করতে হবে।

স্লট অ্যাকশন মানদণ্ড
1 খেলা PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
স্পিনার লোড হচ্ছে PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_CONNECTING
  • STATE_BUFFERING
বিরতি PlaybackState বর্তমান অবস্থা উপরের কোনটি নয়।
2 আগের PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_PREVIOUS
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_PREVIOUS অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
3 পরবর্তী PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_NEXT
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_NEXT অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT কী-এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
4 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।
5 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।

স্ট্যান্ডার্ড অ্যাকশন যোগ করুন

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে PlaybackState স্ট্যান্ডার্ড এবং কাস্টম অ্যাকশন যোগ করতে হয় তা ব্যাখ্যা করে।

প্লে, পজ, পূর্ববর্তী এবং পরবর্তী, মিডিয়া সেশনের জন্য PlaybackState এই ক্রিয়াগুলি সেট করুন৷

কোটলিন

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

জাভা

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

আপনি যদি আগের বা পরবর্তী স্লটে কোনো বোতাম না চান, তাহলে ACTION_SKIP_TO_PREVIOUS বা ACTION_SKIP_TO_NEXT যোগ করবেন না এবং পরিবর্তে সেশনে অতিরিক্ত যোগ করুন:

কোটলিন

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

জাভা

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

কাস্টম অ্যাকশন যোগ করুন

আপনি মিডিয়া কন্ট্রোলে দেখাতে চান এমন অন্যান্য ক্রিয়াগুলির জন্য, আপনি একটি PlaybackStateCompat.CustomAction তৈরি করতে পারেন এবং পরিবর্তে PlaybackState যোগ করতে পারেন৷ এই ক্রিয়াগুলি যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে দেখানো হয়েছে৷

কোটলিন

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

জাভা

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

প্লেব্যাকস্টেট অ্যাকশনের প্রতিক্রিয়া

যখন একজন ব্যবহারকারী একটি বোতামে ট্যাপ করে, তখন MediaSession এ একটি কমান্ড ফেরত পাঠাতে SystemUI MediaController.TransportControls ব্যবহার করে। আপনাকে একটি কলব্যাক নিবন্ধন করতে হবে যা এই ইভেন্টগুলিতে সঠিকভাবে প্রতিক্রিয়া জানাতে পারে।

কোটলিন

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

জাভা

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

মিডিয়া পুনঃসূচনা

আপনার প্লেয়ার অ্যাপটিকে দ্রুত সেটিং সেটিংস এলাকায় উপস্থিত করতে, আপনাকে অবশ্যই একটি বৈধ MediaSession টোকেন সহ একটি MediaStyle বিজ্ঞপ্তি তৈরি করতে হবে৷

MediaStyle বিজ্ঞপ্তির শিরোনাম প্রদর্শন করতে, NotificationBuilder.setContentTitle() ব্যবহার করুন।

মিডিয়া প্লেয়ারের জন্য ব্র্যান্ড আইকন প্রদর্শন করতে, NotificationBuilder.setSmallIcon() ব্যবহার করুন।

প্লেব্যাক পুনরুদ্ধার সমর্থন করতে, অ্যাপগুলিকে অবশ্যই একটি MediaBrowserService এবং MediaSession প্রয়োগ করতে হবে৷ আপনার MediaSession অবশ্যই onPlay() কলব্যাক বাস্তবায়ন করবে।

MediaBrowserService বাস্তবায়ন

ডিভাইস বুট হওয়ার পরে, সিস্টেমটি সবচেয়ে সাম্প্রতিক ব্যবহৃত পাঁচটি মিডিয়া অ্যাপের সন্ধান করে এবং প্রতিটি অ্যাপ থেকে খেলা পুনরায় চালু করতে ব্যবহার করা যেতে পারে এমন নিয়ন্ত্রণ প্রদান করে।

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

SystemUI থেকে সংযোগগুলিকে প্যাকেজের নাম com.android.systemui এবং স্বাক্ষর ব্যবহার করে সনাক্ত ও যাচাই করা যেতে পারে। SystemUI প্ল্যাটফর্ম স্বাক্ষর সহ স্বাক্ষরিত হয়। প্ল্যাটফর্ম স্বাক্ষরের বিপরীতে কীভাবে পরীক্ষা করা যায় তার একটি উদাহরণ UAMP অ্যাপে পাওয়া যাবে।

প্লেব্যাক পুনরুদ্ধার সমর্থন করার জন্য, আপনার MediaBrowserService অবশ্যই এই আচরণগুলি বাস্তবায়ন করবে:

  • onGetRoot() দ্রুত একটি নন-নাল রুট ফেরত দিতে হবে। অন্যান্য জটিল যুক্তি onLoadChildren() এ পরিচালনা করা উচিত

  • যখন onLoadChildren() রুট মিডিয়া আইডিতে কল করা হয়, ফলাফলে অবশ্যই একটি FLAG_PLAYABLE চাইল্ড থাকতে হবে।

  • MediaBrowserService একটি EXTRA_RECENT ক্যোয়ারী পেলে অতি সাম্প্রতিক প্লে করা মিডিয়া আইটেমটি ফেরত দেওয়া উচিত৷ ফেরত দেওয়া মানটি জেনেরিক ফাংশনের পরিবর্তে একটি প্রকৃত মিডিয়া আইটেম হওয়া উচিত।

  • MediaBrowserService একটি অ-খালি শিরোনাম এবং সাবটাইটেল সহ একটি উপযুক্ত MediaDescription প্রদান করতে হবে৷ এটি একটি আইকন URI বা একটি আইকন বিটম্যাপও সেট করা উচিত।

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে onGetRoot() বাস্তবায়ন করতে হয় তা ব্যাখ্যা করে।

কোটলিন

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

জাভা

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}
,

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

  • স্ট্রিমগুলি ফোনে স্থানীয়ভাবে বাজছে৷
  • দূরবর্তী স্ট্রীম, যেমন বহিরাগত ডিভাইস বা কাস্ট সেশনে সনাক্ত করা হয়
  • পূর্ববর্তী পুনঃসূচনাযোগ্য সেশন, যে ক্রমে সেগুলি শেষ খেলা হয়েছিল

অ্যান্ড্রয়েড 13 (API লেভেল 33) থেকে শুরু করে, ব্যবহারকারীরা যাতে মিডিয়া প্লে করা অ্যাপগুলির জন্য মিডিয়া কন্ট্রোলের একটি সমৃদ্ধ সেট অ্যাক্সেস করতে পারে তা নিশ্চিত করতে, মিডিয়া কন্ট্রোলের অ্যাকশন বোতামগুলি Player স্টেট থেকে নেওয়া হয়েছে।

এইভাবে, আপনি মিডিয়া নিয়ন্ত্রণের একটি সামঞ্জস্যপূর্ণ সেট এবং ডিভাইস জুড়ে আরও পালিশ মিডিয়া নিয়ন্ত্রণের অভিজ্ঞতা উপস্থাপন করতে পারেন।

চিত্র 1 যথাক্রমে একটি ফোন এবং ট্যাবলেট ডিভাইসে এটি কীভাবে দেখায় তার একটি উদাহরণ দেখায়।

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

নিম্নলিখিত টেবিলে বর্ণিত Player অবস্থার উপর ভিত্তি করে সিস্টেমটি পাঁচটি পর্যন্ত অ্যাকশন বোতাম প্রদর্শন করে। কমপ্যাক্ট মোডে, শুধুমাত্র প্রথম তিনটি অ্যাকশন স্লট প্রদর্শিত হয়। এটি অটো, অ্যাসিস্ট্যান্ট এবং Wear OS-এর মতো অন্যান্য Android প্ল্যাটফর্মগুলিতে মিডিয়া নিয়ন্ত্রণগুলি কীভাবে রেন্ডার করা হয় তার সাথে সারিবদ্ধ করে।

স্লট মানদণ্ড অ্যাকশন
1 playWhenReady মিথ্যা বা বর্তমান প্লেব্যাক অবস্থা STATE_ENDED খেলা
playWhenReady সত্য এবং বর্তমান প্লেব্যাকের অবস্থা হল STATE_BUFFERING স্পিনার লোড হচ্ছে
playWhenReady সত্য এবং বর্তমান প্লেব্যাক অবস্থা STATE_READY বিরতি
2 প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ৷ আগের
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
3 প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ৷ পরবর্তী
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
4 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম
5 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম

কাস্টম কমান্ডগুলি কাস্টম লেআউটে যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়।

কমান্ড বোতাম কাস্টমাইজ করুন

Jetpack Media3 এর সাথে সিস্টেম মিডিয়া কন্ট্রোল কাস্টমাইজ করতে, আপনি সেশনের কাস্টম লেআউট এবং সেই অনুযায়ী কন্ট্রোলারের উপলব্ধ কমান্ড সেট করতে পারেন, যখন একটি MediaSessionService প্রয়োগ করা হয় :

  1. onCreate() এ, একটি MediaSession তৈরি করুন এবং কমান্ড বোতামগুলির কাস্টম বিন্যাস সংজ্ঞায়িত করুন

  2. MediaSession.Callback.onConnect() এ, ConnectionResultকাস্টম কমান্ড সহ তাদের উপলব্ধ কমান্ডগুলিকে সংজ্ঞায়িত করে কন্ট্রোলারদের অনুমোদন করুন।

  3. MediaSession.Callback.onCustomCommand() এ, ব্যবহারকারীর দ্বারা নির্বাচিত কাস্টম কমান্ডের প্রতিক্রিয়া জানান।

কোটলিন

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

জাভা

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

আপনার MediaSession কনফিগার করার বিষয়ে আরও জানতে যাতে সিস্টেমের মতো ক্লায়েন্টরা আপনার মিডিয়া অ্যাপের সাথে সংযোগ করতে পারে, দেখুন অন্যান্য ক্লায়েন্টদের নিয়ন্ত্রণ করুন

Jetpack Media3 এর সাথে, আপনি যখন একটি MediaSession প্রয়োগ করেন, তখন আপনার PlaybackState স্বয়ংক্রিয়ভাবে মিডিয়া প্লেয়ারের সাথে আপ-টু-ডেট থাকে। একইভাবে, আপনি যখন একটি MediaSessionService প্রয়োগ করেন, লাইব্রেরি স্বয়ংক্রিয়ভাবে আপনার জন্য একটি MediaStyle বিজ্ঞপ্তি প্রকাশ করে এবং এটি আপ-টু-ডেট রাখে।

অ্যাকশন বোতামে সাড়া দিন

যখন কোনো ব্যবহারকারী সিস্টেম মিডিয়া কন্ট্রোলে একটি অ্যাকশন বোতামে ট্যাপ করে, তখন সিস্টেমের MediaController আপনার MediaSession এ একটি প্লেব্যাক কমান্ড পাঠায়। MediaSession তারপর সেই কমান্ডগুলি প্লেয়ারের কাছে অর্পণ করে। Media3 এর Player ইন্টারফেসে সংজ্ঞায়িত কমান্ডগুলি মিডিয়া সেশন দ্বারা স্বয়ংক্রিয়ভাবে পরিচালনা করা হয়।

একটি কাস্টম কমান্ডে কীভাবে প্রতিক্রিয়া জানাতে হয় তার নির্দেশনার জন্য কাস্টম কমান্ড যুক্ত করুন দেখুন।

প্রি-অ্যান্ড্রয়েড 13 আচরণ

পশ্চাদগামী সামঞ্জস্যের জন্য, সিস্টেম UI একটি বিকল্প লেআউট প্রদান করে যা অ্যাপ্লিকেশানগুলির জন্য বিজ্ঞপ্তি অ্যাকশন ব্যবহার করে যেগুলি Android 13 টার্গেট করার জন্য আপডেট হয় না, বা যেগুলি PlaybackState তথ্য অন্তর্ভুক্ত করে না। অ্যাকশন বোতামগুলি MediaStyle বিজ্ঞপ্তির সাথে সংযুক্ত Notification.Action তালিকা থেকে প্রাপ্ত। যে ক্রমানুসারে সেগুলি যোগ করা হয়েছিল সেই ক্রমে সিস্টেমটি পাঁচটি পর্যন্ত ক্রিয়া প্রদর্শন করে৷ কমপ্যাক্ট মোডে, তিনটি পর্যন্ত বোতাম দেখানো হয়, setShowActionsInCompactView() এ পাস করা মান দ্বারা নির্ধারিত হয়।

কাস্টম অ্যাকশনগুলি PlaybackState -এ যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়৷

নিম্নলিখিত কোড উদাহরণটি ব্যাখ্যা করে কিভাবে MediaStyle বিজ্ঞপ্তিতে অ্যাকশন যোগ করতে হয়:

কোটলিন

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

জাভা

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

মিডিয়া পুনঃসূচনা সমর্থন

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

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

মিডিয়া পুনরুদ্ধার সমর্থন করা সহজ করার জন্য Media3 API-গুলি অফার করে৷ এই বৈশিষ্ট্যটি বাস্তবায়নের নির্দেশনার জন্য Media3 ডকুমেন্টেশন সহ প্লেব্যাক পুনঃসূচনা দেখুন।

লিগ্যাসি মিডিয়া API ব্যবহার করে

এই বিভাগটি ব্যাখ্যা করে কিভাবে লিগ্যাসি MediaCompat API ব্যবহার করে সিস্টেম মিডিয়া কন্ট্রোলের সাথে একীভূত করা যায়।

সিস্টেমটি MediaSession এর MediaMetadata থেকে নিম্নলিখিত তথ্য পুনরুদ্ধার করে, এবং যখন এটি উপলব্ধ থাকে তখন এটি প্রদর্শন করে:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (যদি সময়কাল সেট না করা হয় তবে অনুসন্ধান বার অগ্রগতি দেখায় না)

আপনার কাছে একটি বৈধ এবং সঠিক মিডিয়া কন্ট্রোল বিজ্ঞপ্তি আছে তা নিশ্চিত করতে, METADATA_KEY_TITLE বা METADATA_KEY_DISPLAY_TITLE মেটাডেটার মান বর্তমানে প্লে করা মিডিয়ার শিরোনামে সেট করুন৷

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার জন্য অতিবাহিত সময় দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়।

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার অগ্রগতি দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়। সিক বার ব্যবহারকারীদের অবস্থান পরিবর্তন করতে দেয় এবং মিডিয়া আইটেমের জন্য অতিবাহিত সময় প্রদর্শন করে। সন্ধান বার সক্ষম করার জন্য, আপনাকে অবশ্যই PlaybackState.Builder#setActions প্রয়োগ করতে হবে এবং ACTION_SEEK_TO অন্তর্ভুক্ত করতে হবে।

স্লট অ্যাকশন মানদণ্ড
1 খেলা PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
স্পিনার লোড হচ্ছে PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_CONNECTING
  • STATE_BUFFERING
বিরতি PlaybackState বর্তমান অবস্থা উপরের কোনটি নয়।
2 আগের PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_PREVIOUS
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_PREVIOUS অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
3 পরবর্তী PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_NEXT
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_NEXT অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT কী-এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
4 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।
5 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।

স্ট্যান্ডার্ড অ্যাকশন যোগ করুন

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে PlaybackState স্ট্যান্ডার্ড এবং কাস্টম অ্যাকশন যোগ করতে হয় তা ব্যাখ্যা করে।

প্লে, পজ, পূর্ববর্তী এবং পরবর্তী, মিডিয়া সেশনের জন্য PlaybackState এই ক্রিয়াগুলি সেট করুন৷

কোটলিন

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

জাভা

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

আপনি যদি আগের বা পরবর্তী স্লটে কোনো বোতাম না চান, তাহলে ACTION_SKIP_TO_PREVIOUS বা ACTION_SKIP_TO_NEXT যোগ করবেন না এবং পরিবর্তে সেশনে অতিরিক্ত যোগ করুন:

কোটলিন

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

জাভা

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

কাস্টম অ্যাকশন যোগ করুন

আপনি মিডিয়া কন্ট্রোলে দেখাতে চান এমন অন্যান্য ক্রিয়াগুলির জন্য, আপনি একটি PlaybackStateCompat.CustomAction তৈরি করতে পারেন এবং পরিবর্তে PlaybackState যোগ করতে পারেন৷ এই ক্রিয়াগুলি যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে দেখানো হয়েছে৷

কোটলিন

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

জাভা

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

প্লেব্যাকস্টেট অ্যাকশনের প্রতিক্রিয়া

যখন একজন ব্যবহারকারী একটি বোতামে ট্যাপ করে, তখন MediaSession এ একটি কমান্ড ফেরত পাঠাতে SystemUI MediaController.TransportControls ব্যবহার করে। আপনাকে একটি কলব্যাক নিবন্ধন করতে হবে যা এই ইভেন্টগুলিতে সঠিকভাবে প্রতিক্রিয়া জানাতে পারে।

কোটলিন

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

জাভা

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

মিডিয়া পুনঃসূচনা

আপনার প্লেয়ার অ্যাপটিকে দ্রুত সেটিং সেটিংস এলাকায় উপস্থিত করতে, আপনাকে অবশ্যই একটি বৈধ MediaSession টোকেন সহ একটি MediaStyle বিজ্ঞপ্তি তৈরি করতে হবে৷

MediaStyle বিজ্ঞপ্তির শিরোনাম প্রদর্শন করতে, NotificationBuilder.setContentTitle() ব্যবহার করুন।

মিডিয়া প্লেয়ারের জন্য ব্র্যান্ড আইকন প্রদর্শন করতে, NotificationBuilder.setSmallIcon() ব্যবহার করুন।

প্লেব্যাক পুনরুদ্ধার সমর্থন করতে, অ্যাপগুলিকে অবশ্যই একটি MediaBrowserService এবং MediaSession প্রয়োগ করতে হবে৷ আপনার MediaSession অবশ্যই onPlay() কলব্যাক বাস্তবায়ন করবে।

MediaBrowserService বাস্তবায়ন

ডিভাইস বুট হওয়ার পরে, সিস্টেমটি সবচেয়ে সাম্প্রতিক ব্যবহৃত পাঁচটি মিডিয়া অ্যাপের সন্ধান করে এবং প্রতিটি অ্যাপ থেকে খেলা পুনরায় চালু করতে ব্যবহার করা যেতে পারে এমন নিয়ন্ত্রণ প্রদান করে।

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

SystemUI থেকে সংযোগগুলিকে প্যাকেজের নাম com.android.systemui এবং স্বাক্ষর ব্যবহার করে সনাক্ত ও যাচাই করা যেতে পারে। SystemUI প্ল্যাটফর্ম স্বাক্ষর সহ স্বাক্ষরিত হয়। প্ল্যাটফর্ম স্বাক্ষরের বিপরীতে কীভাবে পরীক্ষা করা যায় তার একটি উদাহরণ UAMP অ্যাপে পাওয়া যাবে।

প্লেব্যাক পুনরুদ্ধার সমর্থন করার জন্য, আপনার MediaBrowserService অবশ্যই এই আচরণগুলি বাস্তবায়ন করবে:

  • onGetRoot() দ্রুত একটি নন-নাল রুট ফেরত দিতে হবে। অন্যান্য জটিল যুক্তি onLoadChildren() এ পরিচালনা করা উচিত

  • যখন onLoadChildren() রুট মিডিয়া আইডিতে কল করা হয়, ফলাফলে অবশ্যই একটি FLAG_PLAYABLE চাইল্ড থাকতে হবে।

  • MediaBrowserService একটি EXTRA_RECENT ক্যোয়ারী পেলে অতি সাম্প্রতিক প্লে করা মিডিয়া আইটেমটি ফেরত দেওয়া উচিত৷ ফেরত দেওয়া মানটি জেনেরিক ফাংশনের পরিবর্তে একটি প্রকৃত মিডিয়া আইটেম হওয়া উচিত।

  • MediaBrowserService একটি অ-খালি শিরোনাম এবং সাবটাইটেল সহ একটি উপযুক্ত MediaDescription প্রদান করতে হবে৷ এটি একটি আইকন URI বা একটি আইকন বিটম্যাপও সেট করা উচিত।

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে onGetRoot() বাস্তবায়ন করতে হয় তা ব্যাখ্যা করে।

কোটলিন

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

জাভা

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}
,

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

  • স্ট্রিমগুলি ফোনে স্থানীয়ভাবে বাজছে৷
  • দূরবর্তী স্ট্রীম, যেমন বহিরাগত ডিভাইস বা কাস্ট সেশনে সনাক্ত করা হয়
  • পূর্ববর্তী পুনঃসূচনাযোগ্য সেশন, যে ক্রমে সেগুলি শেষ খেলা হয়েছিল

অ্যান্ড্রয়েড 13 (API লেভেল 33) থেকে শুরু করে, ব্যবহারকারীরা যাতে মিডিয়া প্লে করা অ্যাপগুলির জন্য মিডিয়া কন্ট্রোলের একটি সমৃদ্ধ সেট অ্যাক্সেস করতে পারে তা নিশ্চিত করতে, মিডিয়া কন্ট্রোলের অ্যাকশন বোতামগুলি Player স্টেট থেকে নেওয়া হয়েছে।

এইভাবে, আপনি মিডিয়া নিয়ন্ত্রণের একটি সামঞ্জস্যপূর্ণ সেট এবং ডিভাইস জুড়ে আরও পালিশ মিডিয়া নিয়ন্ত্রণের অভিজ্ঞতা উপস্থাপন করতে পারেন।

চিত্র 1 যথাক্রমে একটি ফোন এবং ট্যাবলেট ডিভাইসে এটি কীভাবে দেখায় তার একটি উদাহরণ দেখায়।

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

নিম্নলিখিত টেবিলে বর্ণিত Player অবস্থার উপর ভিত্তি করে সিস্টেমটি পাঁচটি পর্যন্ত অ্যাকশন বোতাম প্রদর্শন করে। কমপ্যাক্ট মোডে, শুধুমাত্র প্রথম তিনটি অ্যাকশন স্লট প্রদর্শিত হয়। এটি অটো, অ্যাসিস্ট্যান্ট এবং Wear OS-এর মতো অন্যান্য Android প্ল্যাটফর্মগুলিতে মিডিয়া নিয়ন্ত্রণগুলি কীভাবে রেন্ডার করা হয় তার সাথে সারিবদ্ধ করে।

স্লট মানদণ্ড অ্যাকশন
1 playWhenReady মিথ্যা বা বর্তমান প্লেব্যাক অবস্থা STATE_ENDED খেলা
playWhenReady সত্য এবং বর্তমান প্লেব্যাকের অবস্থা হল STATE_BUFFERING স্পিনার লোড হচ্ছে
playWhenReady সত্য এবং বর্তমান প্লেব্যাক অবস্থা STATE_READY বিরতি
2 প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ৷ আগের
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_PREVIOUS বা COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
3 প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ৷ পরবর্তী
কোন প্লেয়ার কমান্ড COMMAND_SEEK_TO_NEXT বা COMMAND_SEEK_TO_NEXT_MEDIA_ITEM উপলব্ধ নেই, এবং কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লটটি পূরণ করার জন্য উপলব্ধ৷ কাস্টম
(এখনও মিডিয়া3 এর সাথে সমর্থিত নয়) PlaybackState অতিরিক্তগুলি EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে। খালি
4 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম
5 কাস্টম লেআউট থেকে একটি কাস্টম কমান্ড যা এখনও স্থাপন করা হয়নি স্লট পূরণ করার জন্য উপলব্ধ। কাস্টম

কাস্টম কমান্ডগুলি কাস্টম লেআউটে যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়।

কমান্ড বোতাম কাস্টমাইজ করুন

Jetpack Media3 এর সাথে সিস্টেম মিডিয়া কন্ট্রোল কাস্টমাইজ করতে, আপনি সেশনের কাস্টম লেআউট এবং সেই অনুযায়ী কন্ট্রোলারের উপলব্ধ কমান্ড সেট করতে পারেন, যখন একটি MediaSessionService প্রয়োগ করা হয় :

  1. onCreate() এ, একটি MediaSession তৈরি করুন এবং কমান্ড বোতামগুলির কাস্টম বিন্যাস সংজ্ঞায়িত করুন

  2. MediaSession.Callback.onConnect() এ, ConnectionResultকাস্টম কমান্ড সহ তাদের উপলব্ধ কমান্ডগুলিকে সংজ্ঞায়িত করে কন্ট্রোলারদের অনুমোদন করুন।

  3. MediaSession.Callback.onCustomCommand() এ, ব্যবহারকারীর দ্বারা নির্বাচিত কাস্টম কমান্ডের প্রতিক্রিয়া জানান।

কোটলিন

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

জাভা

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

আপনার MediaSession কনফিগার করার বিষয়ে আরও জানতে যাতে সিস্টেমের মতো ক্লায়েন্টরা আপনার মিডিয়া অ্যাপের সাথে সংযোগ করতে পারে, দেখুন অন্যান্য ক্লায়েন্টদের নিয়ন্ত্রণ করুন

Jetpack Media3 এর সাথে, আপনি যখন একটি MediaSession প্রয়োগ করেন, তখন আপনার PlaybackState স্বয়ংক্রিয়ভাবে মিডিয়া প্লেয়ারের সাথে আপ-টু-ডেট থাকে। একইভাবে, আপনি যখন একটি MediaSessionService প্রয়োগ করেন, লাইব্রেরি স্বয়ংক্রিয়ভাবে আপনার জন্য একটি MediaStyle বিজ্ঞপ্তি প্রকাশ করে এবং এটি আপ-টু-ডেট রাখে।

অ্যাকশন বোতামে সাড়া দিন

যখন কোনো ব্যবহারকারী সিস্টেম মিডিয়া কন্ট্রোলে একটি অ্যাকশন বোতামে ট্যাপ করে, তখন সিস্টেমের MediaController আপনার MediaSession এ একটি প্লেব্যাক কমান্ড পাঠায়। MediaSession তারপর সেই কমান্ডগুলি প্লেয়ারের কাছে অর্পণ করে। Media3 এর Player ইন্টারফেসে সংজ্ঞায়িত কমান্ডগুলি মিডিয়া সেশন দ্বারা স্বয়ংক্রিয়ভাবে পরিচালনা করা হয়।

একটি কাস্টম কমান্ডে কীভাবে প্রতিক্রিয়া জানাতে হয় তার নির্দেশনার জন্য কাস্টম কমান্ড যুক্ত করুন দেখুন।

প্রি-অ্যান্ড্রয়েড 13 আচরণ

পশ্চাদগামী সামঞ্জস্যের জন্য, সিস্টেম UI একটি বিকল্প লেআউট প্রদান করে যা অ্যাপ্লিকেশানগুলির জন্য বিজ্ঞপ্তি অ্যাকশন ব্যবহার করে যেগুলি Android 13 টার্গেট করার জন্য আপডেট হয় না, বা যেগুলি PlaybackState তথ্য অন্তর্ভুক্ত করে না। অ্যাকশন বোতামগুলি MediaStyle বিজ্ঞপ্তির সাথে সংযুক্ত Notification.Action তালিকা থেকে প্রাপ্ত। যে ক্রমানুসারে সেগুলি যোগ করা হয়েছিল সেই ক্রমে সিস্টেমটি পাঁচটি পর্যন্ত ক্রিয়া প্রদর্শন করে৷ কমপ্যাক্ট মোডে, তিনটি পর্যন্ত বোতাম দেখানো হয়, setShowActionsInCompactView() এ পাস করা মান দ্বারা নির্ধারিত হয়।

কাস্টম অ্যাকশনগুলি PlaybackState -এ যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে স্থাপন করা হয়৷

নিম্নলিখিত কোড উদাহরণটি ব্যাখ্যা করে কিভাবে MediaStyle বিজ্ঞপ্তিতে অ্যাকশন যোগ করতে হয়:

কোটলিন

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

জাভা

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

মিডিয়া পুনঃসূচনা সমর্থন

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

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

মিডিয়া পুনরুদ্ধার সমর্থন করা সহজ করার জন্য Media3 API-গুলি অফার করে৷ এই বৈশিষ্ট্যটি বাস্তবায়নের নির্দেশনার জন্য Media3 ডকুমেন্টেশন সহ প্লেব্যাক পুনঃসূচনা দেখুন।

লিগ্যাসি মিডিয়া API ব্যবহার করে

এই বিভাগটি ব্যাখ্যা করে কিভাবে লিগ্যাসি MediaCompat API ব্যবহার করে সিস্টেম মিডিয়া কন্ট্রোলের সাথে একীভূত করা যায়।

সিস্টেমটি MediaSession এর MediaMetadata থেকে নিম্নলিখিত তথ্য পুনরুদ্ধার করে, এবং যখন এটি উপলব্ধ থাকে তখন এটি প্রদর্শন করে:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (যদি সময়কাল সেট না করা হয় তবে অনুসন্ধান বার অগ্রগতি দেখায় না)

আপনার কাছে একটি বৈধ এবং সঠিক মিডিয়া কন্ট্রোল বিজ্ঞপ্তি আছে তা নিশ্চিত করতে, METADATA_KEY_TITLE বা METADATA_KEY_DISPLAY_TITLE মেটাডেটার মান বর্তমানে প্লে করা মিডিয়ার শিরোনামে সেট করুন৷

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার জন্য অতিবাহিত সময় দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়।

মিডিয়া প্লেয়ার বর্তমানে বাজানো মিডিয়ার অগ্রগতি দেখায়, সাথে একটি সিক বার যা MediaSession PlaybackState এ ম্যাপ করা হয়। সিক বার ব্যবহারকারীদের অবস্থান পরিবর্তন করতে দেয় এবং মিডিয়া আইটেমের জন্য অতিবাহিত সময় প্রদর্শন করে। সন্ধান বার সক্ষম করার জন্য, আপনাকে অবশ্যই PlaybackState.Builder#setActions প্রয়োগ করতে হবে এবং ACTION_SEEK_TO অন্তর্ভুক্ত করতে হবে।

স্লট অ্যাকশন মানদণ্ড
1 খেলা PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
স্পিনার লোড হচ্ছে PlaybackState বর্তমান অবস্থা নিম্নলিখিতগুলির মধ্যে একটি:
  • STATE_CONNECTING
  • STATE_BUFFERING
বিরতি PlaybackState বর্তমান অবস্থা উপরের কোনটি নয়।
2 আগের PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_PREVIOUS
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_PREVIOUS অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV কী এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
3 পরবর্তী PlaybackState অ্যাকশনের মধ্যে রয়েছে ACTION_SKIP_TO_NEXT
কাস্টম PlaybackState অ্যাকশন ACTION_SKIP_TO_NEXT অন্তর্ভুক্ত করে না এবং PlaybackState কাস্টম অ্যাকশনগুলি এমন একটি কাস্টম অ্যাকশন অন্তর্ভুক্ত করে যা এখনও স্থাপন করা হয়নি।
খালি PlaybackState অতিরিক্ত SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT কী-এর জন্য একটি true বুলিয়ান মান অন্তর্ভুক্ত করে।
4 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।
5 কাস্টম PlaybackState কাস্টম অ্যাকশনগুলির মধ্যে একটি কাস্টম অ্যাকশন রয়েছে যা এখনও স্থাপন করা হয়নি।

স্ট্যান্ডার্ড অ্যাকশন যোগ করুন

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে PlaybackState স্ট্যান্ডার্ড এবং কাস্টম অ্যাকশন যোগ করতে হয় তা ব্যাখ্যা করে।

প্লে, পজ, পূর্ববর্তী এবং পরবর্তী, মিডিয়া সেশনের জন্য PlaybackState এই ক্রিয়াগুলি সেট করুন৷

কোটলিন

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

জাভা

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

আপনি যদি আগের বা পরবর্তী স্লটে কোনো বোতাম না চান, তাহলে ACTION_SKIP_TO_PREVIOUS বা ACTION_SKIP_TO_NEXT যোগ করবেন না এবং পরিবর্তে সেশনে অতিরিক্ত যোগ করুন:

কোটলিন

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

জাভা

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

কাস্টম অ্যাকশন যোগ করুন

আপনি মিডিয়া কন্ট্রোলে দেখাতে চান এমন অন্যান্য ক্রিয়াগুলির জন্য, আপনি একটি PlaybackStateCompat.CustomAction তৈরি করতে পারেন এবং পরিবর্তে PlaybackState যোগ করতে পারেন৷ এই ক্রিয়াগুলি যে ক্রমে যুক্ত করা হয়েছিল সেই ক্রমে দেখানো হয়েছে৷

কোটলিন

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

জাভা

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

প্লেব্যাকস্টেট অ্যাকশনের প্রতিক্রিয়া

যখন একজন ব্যবহারকারী একটি বোতামে ট্যাপ করে, তখন MediaSession এ একটি কমান্ড ফেরত পাঠাতে SystemUI MediaController.TransportControls ব্যবহার করে। আপনাকে একটি কলব্যাক নিবন্ধন করতে হবে যা এই ইভেন্টগুলিতে সঠিকভাবে প্রতিক্রিয়া জানাতে পারে।

কোটলিন

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

জাভা

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

মিডিয়া পুনঃসূচনা

আপনার প্লেয়ার অ্যাপটিকে দ্রুত সেটিং সেটিংস এলাকায় উপস্থিত করতে, আপনাকে অবশ্যই একটি বৈধ MediaSession টোকেন সহ একটি MediaStyle বিজ্ঞপ্তি তৈরি করতে হবে৷

MediaStyle বিজ্ঞপ্তির শিরোনাম প্রদর্শন করতে, NotificationBuilder.setContentTitle() ব্যবহার করুন।

মিডিয়া প্লেয়ারের জন্য ব্র্যান্ড আইকন প্রদর্শন করতে, NotificationBuilder.setSmallIcon() ব্যবহার করুন।

প্লেব্যাক পুনরুদ্ধার সমর্থন করতে, অ্যাপগুলিকে অবশ্যই একটি MediaBrowserService এবং MediaSession প্রয়োগ করতে হবে৷ আপনার MediaSession অবশ্যই onPlay() কলব্যাক বাস্তবায়ন করবে।

MediaBrowserService বাস্তবায়ন

ডিভাইস বুট হওয়ার পরে, সিস্টেমটি সবচেয়ে সাম্প্রতিক ব্যবহৃত পাঁচটি মিডিয়া অ্যাপের সন্ধান করে এবং প্রতিটি অ্যাপ থেকে খেলা পুনরায় চালু করতে ব্যবহার করা যেতে পারে এমন নিয়ন্ত্রণ প্রদান করে।

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

SystemUI থেকে সংযোগগুলিকে প্যাকেজের নাম com.android.systemui এবং স্বাক্ষর ব্যবহার করে সনাক্ত ও যাচাই করা যেতে পারে। SystemUI প্ল্যাটফর্ম স্বাক্ষর সহ স্বাক্ষরিত হয়। প্ল্যাটফর্ম স্বাক্ষরের বিপরীতে কীভাবে পরীক্ষা করা যায় তার একটি উদাহরণ UAMP অ্যাপে পাওয়া যাবে।

প্লেব্যাক পুনরুদ্ধার সমর্থন করার জন্য, আপনার MediaBrowserService অবশ্যই এই আচরণগুলি বাস্তবায়ন করবে:

  • onGetRoot() দ্রুত একটি নন-নাল রুট ফেরত দিতে হবে। অন্যান্য জটিল যুক্তি onLoadChildren() এ পরিচালনা করা উচিত

  • যখন onLoadChildren() রুট মিডিয়া আইডিতে কল করা হয়, ফলাফলে অবশ্যই একটি FLAG_PLAYABLE চাইল্ড থাকতে হবে।

  • MediaBrowserService একটি EXTRA_RECENT ক্যোয়ারী পেলে অতি সাম্প্রতিক প্লে করা মিডিয়া আইটেমটি ফেরত দেওয়া উচিত৷ ফেরত দেওয়া মানটি জেনেরিক ফাংশনের পরিবর্তে একটি প্রকৃত মিডিয়া আইটেম হওয়া উচিত।

  • MediaBrowserService একটি অ-খালি শিরোনাম এবং সাবটাইটেল সহ একটি উপযুক্ত MediaDescription প্রদান করতে হবে৷ এটি একটি আইকন URI বা একটি আইকন বিটম্যাপও সেট করা উচিত।

নিম্নলিখিত কোড উদাহরণগুলি কীভাবে onGetRoot() বাস্তবায়ন করতে হয় তা ব্যাখ্যা করে।

কোটলিন

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

জাভা

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}