একটি MediaSessionService সহ পটভূমি প্লেব্যাক

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

একটি MediaSessionService ব্যবহার করুন

ব্যাকগ্রাউন্ড প্লেব্যাক সক্ষম করতে, আপনার একটি পৃথক পরিষেবার মধ্যে Player এবং MediaSession থাকা উচিত। এটি আপনার অ্যাপটি অগ্রভাগে না থাকা সত্ত্বেও ডিভাইসটিকে মিডিয়া পরিবেশন চালিয়ে যেতে দেয়৷

MediaSessionService মিডিয়া সেশনকে অ্যাপের কার্যকলাপ থেকে আলাদাভাবে চালানোর অনুমতি দেয়
চিত্র 1 : MediaSessionService মিডিয়া সেশনকে অ্যাপের কার্যকলাপ থেকে আলাদাভাবে চালানোর অনুমতি দেয়

একটি পরিষেবার মধ্যে একটি প্লেয়ার হোস্ট করার সময়, আপনার একটি MediaSessionService ব্যবহার করা উচিত৷ এটি করার জন্য, একটি ক্লাস তৈরি করুন যা MediaSessionService `কে প্রসারিত করে এবং এর ভিতরে আপনার মিডিয়া সেশন তৈরি করুন।

MediaSessionService ব্যবহার করলে আপনার অ্যাপের UI অ্যাক্টিভিটি অ্যাক্সেস না করেই Google অ্যাসিস্ট্যান্ট, সিস্টেম মিডিয়া কন্ট্রোল বা Wear OS-এর মতো সঙ্গী ডিভাইসের মতো বাহ্যিক ক্লায়েন্টদের জন্য আপনার পরিষেবা আবিষ্কার করা, এতে সংযোগ করা এবং প্লেব্যাক নিয়ন্ত্রণ করা সম্ভব হয়। প্রকৃতপক্ষে, একই সময়ে একই MediaSessionService এর সাথে সংযুক্ত একাধিক ক্লায়েন্ট অ্যাপ থাকতে পারে, প্রতিটি অ্যাপের নিজস্ব MediaController সহ।

সেবা জীবনচক্র বাস্তবায়ন

আপনাকে আপনার পরিষেবার তিনটি জীবনচক্র পদ্ধতি প্রয়োগ করতে হবে:

  • onCreate() বলা হয় যখন প্রথম কন্ট্রোলার সংযোগ করতে চলেছে এবং পরিষেবাটি তাত্ক্ষণিকভাবে চালু করা হয়। Player এবং MediaSession তৈরি করার জন্য এটি সেরা জায়গা।
  • onTaskRemoved(Intent) কল করা হয় যখন ব্যবহারকারী অ্যাপটিকে সাম্প্রতিক কাজগুলি থেকে খারিজ করে দেয়। প্লেব্যাক চলমান থাকলে, অ্যাপটি অগ্রভাগে পরিষেবাটি চালু রাখতে বেছে নিতে পারে। প্লেয়ারকে বিরতি দেওয়া হলে, পরিষেবাটি অগ্রভাগে নেই এবং বন্ধ করা প্রয়োজন।
  • onDestroy() বলা হয় যখন পরিষেবা বন্ধ করা হচ্ছে। প্লেয়ার এবং সেশন সহ সমস্ত সংস্থান প্রকাশ করা দরকার।

কোটলিন

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null

  // Create your player and media session in the onCreate lifecycle event
  override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    mediaSession = MediaSession.Builder(this, player).build()
  }

  // The user dismissed the app from the recent tasks
  override fun onTaskRemoved(rootIntent: Intent?) {
    val player = mediaSession?.player!!
    if (!player.playWhenReady
        || player.mediaItemCount == 0
        || player.playbackState == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf()
    }
  }

  // Remember to release the player and media session in onDestroy
  override fun onDestroy() {
    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    super.onDestroy()
  }
}

জাভা

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;

  // Create your Player and MediaSession in the onCreate lifecycle event
  @Override
  public void onCreate() {
    super.onCreate();
    ExoPlayer player = new ExoPlayer.Builder(this).build();
    mediaSession = new MediaSession.Builder(this, player).build();
  }

  // The user dismissed the app from the recent tasks
  @Override
  public void onTaskRemoved(@Nullable Intent rootIntent) {
    Player player = mediaSession.getPlayer();
    if (!player.getPlayWhenReady()
        || player.getMediaItemCount() == 0
        || player.getPlaybackState() == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf();
    }
  }

  // Remember to release the player and media session in onDestroy
  @Override
  public void onDestroy() {
    mediaSession.getPlayer().release();
    mediaSession.release();
    mediaSession = null;
    super.onDestroy();
  }
}

ব্যাকগ্রাউন্ডে প্লেব্যাক চালু রাখার বিকল্প হিসেবে, ব্যবহারকারী অ্যাপটি খারিজ করলে একটি অ্যাপ যেকোনো ক্ষেত্রেই পরিষেবা বন্ধ করতে পারে:

কোটলিন

override fun onTaskRemoved(rootIntent: Intent?) {
  val player = mediaSession.player
  if (player.playWhenReady) {
    // Make sure the service is not in foreground.
    player.pause()
  }
  stopSelf()
}

জাভা

@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
  Player player = mediaSession.getPlayer();
  if (player.getPlayWhenReady()) {
    // Make sure the service is not in foreground.
    player.pause();
  }
  stopSelf();
}

মিডিয়া অধিবেশন অ্যাক্সেস প্রদান

পরিষেবাটি তৈরি করার সময় আপনার মিডিয়া সেশনে অন্যান্য ক্লায়েন্টদের অ্যাক্সেস দেওয়ার জন্য onGetSession() পদ্ধতিটি ওভাররাইড করুন৷

কোটলিন

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null
  // [...] lifecycle methods omitted

  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
    mediaSession
}

জাভা

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;
  // [...] lifecycle methods omitted

  @Override
  public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
    return mediaSession;
  }
}

ম্যানিফেস্টে পরিষেবাটি ঘোষণা করুন

একটি ফোরগ্রাউন্ড পরিষেবা চালানোর জন্য একটি অ্যাপের অনুমতি প্রয়োজন৷ ম্যানিফেস্টে FOREGROUND_SERVICE অনুমতি যোগ করুন এবং আপনি যদি API 34 এবং তার উপরেও লক্ষ্য করেন FOREGROUND_SERVICE_MEDIA_PLAYBACK :

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

আপনাকে অবশ্যই MediaSessionService এর একটি উদ্দেশ্য ফিল্টার সহ ম্যানিফেস্টে আপনার Service শ্রেণী ঘোষণা করতে হবে।

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

যখন আপনার অ্যাপ Android 10 (API লেভেল 29) এবং উচ্চতর ডিভাইসে চলছে তখন আপনাকে একটি foregroundServiceType সংজ্ঞায়িত করতে হবে যাতে mediaPlayback অন্তর্ভুক্ত থাকে।

MediaController ব্যবহার করে প্লেব্যাক নিয়ন্ত্রণ করুন

আপনার প্লেয়ার UI ধারণকারী কার্যকলাপ বা খণ্ডে, আপনি একটি MediaController ব্যবহার করে UI এবং আপনার মিডিয়া সেশনের মধ্যে একটি লিঙ্ক স্থাপন করতে পারেন। সেশনের মধ্যে আপনার UI থেকে প্লেয়ারে কমান্ড পাঠাতে আপনার UI মিডিয়া কন্ট্রোলার ব্যবহার করে। একটি MediaController তৈরি এবং ব্যবহার করার বিষয়ে বিস্তারিত জানার জন্য একটি MediaController তৈরি করুন নির্দেশিকা দেখুন।

UI কমান্ড পরিচালনা করুন

MediaSession তার MediaSession.Callback এর মাধ্যমে কন্ট্রোলার থেকে কমান্ড গ্রহণ করে। একটি MediaSession আরম্ভ করা MediaSession.Callback এর একটি ডিফল্ট বাস্তবায়ন তৈরি করে যা একটি MediaController আপনার প্লেয়ারে পাঠানো সমস্ত কমান্ড স্বয়ংক্রিয়ভাবে পরিচালনা করে।

বিজ্ঞপ্তি

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

উদাহরণস্বরূপ, একটি MediaSessionService ব্যবহার করে একটি মিউজিক স্ট্রিমিং অ্যাপ একটি MediaNotification তৈরি করবে যা আপনার MediaSession কনফিগারেশনের উপর ভিত্তি করে প্লেব্যাক কন্ট্রোলের পাশাপাশি প্লে করা বর্তমান মিডিয়া আইটেমের শিরোনাম, শিল্পী এবং অ্যালবাম আর্ট প্রদর্শন করে।

প্রয়োজনীয় মেটাডেটা মিডিয়াতে সরবরাহ করা যেতে পারে বা নিম্নলিখিত স্নিপেটের মতো মিডিয়া আইটেমের অংশ হিসাবে ঘোষণা করা যেতে পারে:

কোটলিন

val mediaItem =
    MediaItem.Builder()
      .setMediaId("media-1")
      .setUri(mediaUri)
      .setMediaMetadata(
        MediaMetadata.Builder()
          .setArtist("David Bowie")
          .setTitle("Heroes")
          .setArtworkUri(artworkUri)
          .build()
      )
      .build()

mediaController.setMediaItem(mediaItem)
mediaController.prepare()
mediaController.play()

জাভা

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaId("media-1")
        .setUri(mediaUri)
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setArtist("David Bowie")
                .setTitle("Heroes")
                .setArtworkUri(artworkUri)
                .build())
        .build();

mediaController.setMediaItem(mediaItem);
mediaController.prepare();
mediaController.play();

অ্যাপস অ্যান্ড্রয়েড মিডিয়া কন্ট্রোলের কমান্ড বোতাম কাস্টমাইজ করতে পারে। অ্যান্ড্রয়েড মিডিয়া নিয়ন্ত্রণগুলি কাস্টমাইজ করার বিষয়ে আরও পড়ুন

বিজ্ঞপ্তি কাস্টমাইজেশন

বিজ্ঞপ্তিটি কাস্টমাইজ করতে, একটি MediaNotification.Provider তৈরি করুন DefaultMediaNotificationProvider.Builder সহ বা প্রদানকারী ইন্টারফেসের একটি কাস্টম বাস্তবায়ন তৈরি করে৷ setMediaNotificationProvider এর সাথে আপনার MediaSessionService এ আপনার প্রদানকারীকে যোগ করুন।

প্লেব্যাক পুনঃসূচনা

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

Media3 মিডিয়া বোতাম রিসিভার ঘোষণা করুন

Media3 একটি API অন্তর্ভুক্ত করে যাতে ব্যবহারকারীরা একটি অ্যাপ বন্ধ হয়ে যাওয়ার পরে এবং ডিভাইস পুনরায় চালু হওয়ার পরেও প্লেব্যাক পুনরায় শুরু করতে সক্ষম হয়। ডিফল্টরূপে, প্লেব্যাক পুনঃসূচনা বন্ধ করা হয়। এর মানে হল আপনার পরিষেবা চালু না থাকলে ব্যবহারকারী প্লেব্যাক পুনরায় শুরু করতে পারবেন না। অপ্ট-ইন করতে, আপনার ম্যানিফেস্টে MediaButtonReceiver ঘোষণা করে শুরু করুন:

<receiver android:name="androidx.media3.session.MediaButtonReceiver"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.MEDIA_BUTTON" />
  </intent-filter>
</receiver>

প্লেব্যাক পুনঃসূচনা কলব্যাক প্রয়োগ করুন

যখন একটি ব্লুটুথ ডিভাইস বা অ্যান্ড্রয়েড সিস্টেম UI পুনঃসূচনা বৈশিষ্ট্য দ্বারা প্লেব্যাক পুনরায় শুরু করার অনুরোধ করা হয়, onPlaybackResumption() কলব্যাক পদ্ধতি বলা হয়৷

কোটলিন

override fun onPlaybackResumption(
    mediaSession: MediaSession,
    controller: ControllerInfo
): ListenableFuture<MediaItemsWithStartPosition> {
  val settable = SettableFuture.create<MediaItemsWithStartPosition>()
  scope.launch {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    val resumptionPlaylist = restorePlaylist()
    settable.set(resumptionPlaylist)
  }
  return settable
}

জাভা

@Override
public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption(
    MediaSession mediaSession,
    ControllerInfo controller
) {
  SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create();
  settableFuture.addListener(() -> {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist();
    settableFuture.set(resumptionPlaylist);
  }, MoreExecutors.directExecutor());
  return settableFuture;
}

আপনি যদি প্লেব্যাকের গতি, পুনরাবৃত্তি মোড, বা শাফেল মোডের মতো অন্যান্য পরামিতিগুলি সঞ্চয় করে থাকেন, তাহলে Media3 প্লেয়ার প্রস্তুত করার আগে এবং কলব্যাক সম্পূর্ণ হলে প্লেব্যাক শুরু করার আগে এই প্যারামিটারগুলির সাথে প্লেয়ারটিকে কনফিগার করার জন্য onPlaybackResumption() একটি ভাল জায়গা৷

উন্নত নিয়ামক কনফিগারেশন এবং পশ্চাদপদ সামঞ্জস্য

প্লেব্যাক নিয়ন্ত্রণ এবং প্লেলিস্ট প্রদর্শনের জন্য অ্যাপ UI-তে একটি MediaController ব্যবহার করা একটি সাধারণ দৃশ্য। একই সময়ে, সেশনটি বাহ্যিক ক্লায়েন্টদের কাছে উন্মুক্ত করা হয় যেমন অ্যান্ড্রয়েড মিডিয়া কন্ট্রোল এবং মোবাইল বা টিভিতে সহকারী, ঘড়ির জন্য ওয়্যার ওএস এবং গাড়িতে অ্যান্ড্রয়েড অটো। Media3 সেশন ডেমো অ্যাপটি এমন একটি অ্যাপের উদাহরণ যা এই ধরনের দৃশ্যের প্রয়োগ করে।

এই বহিরাগত ক্লায়েন্টরা লিগ্যাসি AndroidX লাইব্রেরির MediaControllerCompat বা Android ফ্রেমওয়ার্কের android.media.session.MediaController এর মতো API ব্যবহার করতে পারে৷ Media3 লিগ্যাসি লাইব্রেরির সাথে সম্পূর্ণ পশ্চাদপদ সামঞ্জস্যপূর্ণ এবং অ্যান্ড্রয়েড ফ্রেমওয়ার্ক API এর সাথে আন্তঃঅপারেবিলিটি প্রদান করে।

মিডিয়া নোটিফিকেশন কন্ট্রোলার ব্যবহার করুন

এটা বোঝা গুরুত্বপূর্ণ যে এই উত্তরাধিকার বা ফ্রেমওয়ার্ক কন্ট্রোলার ফ্রেমওয়ার্ক PlaybackState.getActions() এবং PlaybackState.getCustomActions() থেকে একই মানগুলি পড়ে। ফ্রেমওয়ার্ক সেশনের অ্যাকশন এবং কাস্টম অ্যাকশন নির্ধারণ করতে, একটি অ্যাপ মিডিয়া নোটিফিকেশন কন্ট্রোলার ব্যবহার করতে পারে এবং এর উপলব্ধ কমান্ড এবং কাস্টম লেআউট সেট করতে পারে। পরিষেবাটি মিডিয়া বিজ্ঞপ্তি নিয়ামককে আপনার সেশনের সাথে সংযুক্ত করে, এবং সেশনটি আপনার কলব্যাকের onConnect() দ্বারা প্রত্যাবর্তিত ConnectionResult ব্যবহার করে ফ্রেমওয়ার্ক সেশনের কাজ এবং কাস্টম অ্যাকশন কনফিগার করতে।

শুধুমাত্র-মোবাইল পরিস্থিতির প্রেক্ষিতে, একটি অ্যাপ MediaSession.Callback.onConnect() এর একটি বাস্তবায়ন প্রদান করতে পারে যাতে নিম্নরূপ ফ্রেমওয়ার্ক সেশনের জন্য বিশেষভাবে উপলব্ধ কমান্ড এবং কাস্টম লেআউট সেট করা যায়:

কোটলিন

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  if (session.isMediaNotificationController(controller)) {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(customCommandSeekBackward)
        .add(customCommandSeekForward)
        .build()
    val playerCommands =
      ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
        .remove(COMMAND_SEEK_TO_PREVIOUS)
        .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
        .remove(COMMAND_SEEK_TO_NEXT)
        .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
        .build()
    // Custom layout and available commands to configure the legacy/framework session.
    return AcceptedResultBuilder(session)
      .setCustomLayout(
        ImmutableList.of(
          createSeekBackwardButton(customCommandSeekBackward),
          createSeekForwardButton(customCommandSeekForward))
      )
      .setAvailablePlayerCommands(playerCommands)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout for all other controllers.
  return AcceptedResultBuilder(session).build()
}

জাভা

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  if (session.isMediaNotificationController(controller)) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(customCommandSeekBackward)
            .add(customCommandSeekForward)
            .build();
    Player.Commands playerCommands =
        ConnectionResult.DEFAULT_PLAYER_COMMANDS
            .buildUpon()
            .remove(COMMAND_SEEK_TO_PREVIOUS)
            .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
            .remove(COMMAND_SEEK_TO_NEXT)
            .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
            .build();
    // Custom layout and available commands to configure the legacy/framework session.
    return new AcceptedResultBuilder(session)
        .setCustomLayout(
            ImmutableList.of(
                createSeekBackwardButton(customCommandSeekBackward),
                createSeekForwardButton(customCommandSeekForward)))
        .setAvailablePlayerCommands(playerCommands)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

কাস্টম কমান্ড পাঠাতে Android Auto অনুমোদন করুন

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

কোটলিন

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  val sessionCommands =
    ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
      .add(customCommandSeekBackward)
      .add(customCommandSeekForward)
      .build()
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available session commands to accept incoming custom commands from Auto.
    return AcceptedResultBuilder(session)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout for all other controllers.
  return AcceptedResultBuilder(session).build()
}

জাভা

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  SessionCommands sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS
          .buildUpon()
          .add(customCommandSeekBackward)
          .add(customCommandSeekForward)
          .build();
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available commands to accept incoming custom commands from Auto.
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

সেশন ডেমো অ্যাপটিতে একটি স্বয়ংচালিত মডিউল রয়েছে, যা স্বয়ংচালিত ওএসের জন্য সমর্থন প্রদর্শন করে যার জন্য একটি পৃথক APK প্রয়োজন।