একটি MediaSession ব্যবহার করে প্লেব্যাক নিয়ন্ত্রণ এবং বিজ্ঞাপন দিন

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

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

কখন মিডিয়া সেশন বেছে নেবেন

আপনি যখন MediaSession প্রয়োগ করেন, আপনি ব্যবহারকারীদের প্লেব্যাক নিয়ন্ত্রণ করার অনুমতি দেন:

  • তাদের হেডফোনের মাধ্যমে। মিডিয়া প্লে বা পজ করতে বা পরবর্তী বা পূর্ববর্তী ট্র্যাকে যাওয়ার জন্য ব্যবহারকারীরা তাদের হেডফোনে প্রায়শই বোতাম বা টাচ ইন্টারঅ্যাকশন করতে পারেন।
  • গুগল সহকারীর সাথে কথা বলে। একটি সাধারণ প্যাটার্ন হল "ওকে গুগল, পজ" বলার জন্য যে কোনো মিডিয়া বর্তমানে ডিভাইসে চলছে।
  • তাদের Wear OS ঘড়ির মাধ্যমে। এটি তাদের ফোনে খেলার সময় সবচেয়ে সাধারণ প্লেব্যাক নিয়ন্ত্রণগুলিতে সহজে অ্যাক্সেসের অনুমতি দেয়।
  • মিডিয়া নিয়ন্ত্রণের মাধ্যমে। এই ক্যারাউজেল প্রতিটি চলমান মিডিয়া সেশনের জন্য নিয়ন্ত্রণ দেখায়।
  • টেলিভিশনে । ফিজিক্যাল প্লেব্যাক বোতাম, প্ল্যাটফর্ম প্লেব্যাক কন্ট্রোল এবং পাওয়ার ম্যানেজমেন্ট সহ অ্যাকশনের অনুমতি দেয় (উদাহরণস্বরূপ যদি টিভি, সাউন্ডবার বা A/V রিসিভার বন্ধ হয়ে যায় বা ইনপুট সুইচ করা হয়, প্লেব্যাক অ্যাপে বন্ধ করা উচিত)।
  • এবং অন্য কোনো বাহ্যিক প্রক্রিয়া যা প্লেব্যাককে প্রভাবিত করতে হবে।

এটি অনেক ব্যবহারের ক্ষেত্রে দুর্দান্ত। বিশেষ করে, আপনার MediaSession ব্যবহার করার বিষয়ে দৃঢ়ভাবে বিবেচনা করা উচিত যখন:

  • আপনি সিনেমা বা লাইভ টিভির মতো দীর্ঘ-ফর্মের ভিডিও সামগ্রী স্ট্রিম করছেন৷
  • আপনি পডকাস্ট বা সঙ্গীত প্লেলিস্টের মতো দীর্ঘ-ফর্মের অডিও সামগ্রী স্ট্রিম করছেন৷
  • আপনি একটি টিভি অ্যাপ তৈরি করছেন।

যাইহোক, সমস্ত ব্যবহারের ক্ষেত্রে MediaSession সাথে ভালভাবে মানানসই নয়। আপনি নিম্নলিখিত ক্ষেত্রে শুধুমাত্র Player ব্যবহার করতে চাইতে পারেন:

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

যদি আপনার ব্যবহারের ক্ষেত্রে উপরে তালিকাভুক্ত কোনটির সাথে মানানসই না হয়, তাহলে ব্যবহারকারী যখন সক্রিয়ভাবে বিষয়বস্তুর সাথে জড়িত না হয় তখন আপনার অ্যাপ চালিয়ে যাওয়া প্লেব্যাক আপনি ঠিক আছেন কিনা তা বিবেচনা করুন। উত্তর যদি হ্যাঁ হয়, আপনি সম্ভবত MediaSession বেছে নিতে চান। উত্তর না হলে, আপনি সম্ভবত Player ব্যবহার করতে চান।

একটি মিডিয়া সেশন তৈরি করুন

একটি মিডিয়া সেশন প্লেয়ারের সাথে থাকে যা এটি পরিচালনা করে। আপনি একটি Context এবং একটি Player অবজেক্ট সহ একটি মিডিয়া সেশন তৈরি করতে পারেন। আপনার প্রয়োজন হলে একটি মিডিয়া সেশন তৈরি এবং আরম্ভ করা উচিত, যেমন onStart() বা onResume() লাইফসাইকেল পদ্ধতি Activity বা Fragment , অথবা Service onCreate() পদ্ধতি যা মিডিয়া সেশন এবং এর সাথে সম্পর্কিত প্লেয়ারের মালিক।

একটি মিডিয়া সেশন তৈরি করতে, একটি Player শুরু করুন এবং এটিকে MediaSession.Builder এ এইভাবে সরবরাহ করুন:

কোটলিন

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

জাভা

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

স্বয়ংক্রিয় রাষ্ট্র পরিচালনা

Media3 লাইব্রেরি স্বয়ংক্রিয়ভাবে প্লেয়ারের অবস্থা ব্যবহার করে মিডিয়া সেশন আপডেট করে। যেমন, আপনাকে প্লেয়ার থেকে সেশনে ম্যাপিং ম্যানুয়ালি পরিচালনা করতে হবে না।

এটি হল লিগ্যাসি পদ্ধতির একটি বিরতি যেখানে আপনাকে PlaybackStateCompat তৈরি এবং বজায় রাখার জন্য প্লেয়ার থেকে স্বাধীনভাবে প্রয়োজন ছিল, উদাহরণস্বরূপ কোনো ত্রুটি নির্দেশ করার জন্য।

অনন্য সেশন আইডি

ডিফল্টরূপে, MediaSession.Builder সেশন আইডি হিসাবে একটি খালি স্ট্রিং সহ একটি সেশন তৈরি করে। এটি যথেষ্ট যদি একটি অ্যাপ শুধুমাত্র একটি একক সেশনের উদাহরণ তৈরি করতে চায়, যা সবচেয়ে সাধারণ ক্ষেত্রে।

যদি একটি অ্যাপ একই সময়ে একাধিক সেশন ইনস্ট্যান্স পরিচালনা করতে চায়, তবে অ্যাপটিকে নিশ্চিত করতে হবে যে প্রতিটি সেশনের সেশন আইডি অনন্য। MediaSession.Builder.setId(String id) দিয়ে সেশন তৈরি করার সময় সেশন আইডি সেট করা যেতে পারে।

আপনি যদি দেখেন একটি IllegalStateException ত্রুটি বার্তা সহ আপনার অ্যাপ ক্র্যাশ করছে IllegalStateException: Session ID must be unique. ID= তাহলে সম্ভবত একই আইডি সহ পূর্বে তৈরি করা ইন্সট্যান্স রিলিজ হওয়ার আগে একটি সেশন অপ্রত্যাশিতভাবে তৈরি হয়েছে। একটি প্রোগ্রামিং ত্রুটি দ্বারা ফাঁস করা সেশন এড়াতে, এই ধরনের কেস সনাক্ত করা হয় এবং একটি ব্যতিক্রম নিক্ষেপ করে বিজ্ঞপ্তি দেওয়া হয়।

অন্যান্য ক্লায়েন্টদের নিয়ন্ত্রণ মঞ্জুর করুন

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

একটি ডায়াগ্রাম যা একটি MediaSession এবং MediaController এর মধ্যে মিথস্ক্রিয়া প্রদর্শন করে।
চিত্র 1 : মিডিয়া কন্ট্রোলার বাহ্যিক উত্স থেকে মিডিয়া সেশনে কমান্ড পাস করার সুবিধা দেয়৷

যখন একটি নিয়ামক আপনার মিডিয়া সেশনের সাথে সংযোগ করতে চলেছে, onConnect() পদ্ধতিটি বলা হয়। অনুরোধ গ্রহণ বা প্রত্যাখ্যান করার সিদ্ধান্ত নিতে আপনি প্রদত্ত ControllerInfo ব্যবহার করতে পারেন। উপলব্ধ কমান্ড ঘোষণা বিভাগে একটি সংযোগ অনুরোধ গ্রহণ করার একটি উদাহরণ দেখুন।

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

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

প্লেলিস্ট পরিবর্তন করুন

প্লেলিস্টের জন্য ExoPlayer গাইডে ব্যাখ্যা করা একটি মিডিয়া সেশন সরাসরি তার প্লেয়ারের প্লেলিস্ট পরিবর্তন করতে পারে। যদি COMMAND_SET_MEDIA_ITEM বা COMMAND_CHANGE_MEDIA_ITEMS কন্ট্রোলারের কাছে উপলব্ধ থাকে তাহলে কন্ট্রোলাররাও প্লেলিস্ট সংশোধন করতে সক্ষম৷

প্লেলিস্টে নতুন আইটেম যোগ করার সময়, প্লেয়ারের সাধারণত একটি সংজ্ঞায়িত URI সহ MediaItem দৃষ্টান্ত প্রয়োজন হয় যাতে সেগুলিকে প্লে করা যায়৷ ডিফল্টরূপে, নতুন যোগ করা আইটেমগুলি প্লেয়ার পদ্ধতিতে স্বয়ংক্রিয়ভাবে ফরোয়ার্ড হয় যেমন player.addMediaItem যদি তাদের একটি URI সংজ্ঞায়িত থাকে।

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

  • MediaItem.id : একটি জেনেরিক আইডি যা মিডিয়াকে চিহ্নিত করে।
  • MediaItem.RequestMetadata.mediaUri : একটি অনুরোধ URI যা একটি কাস্টম স্কিমা ব্যবহার করতে পারে এবং প্লেয়ার দ্বারা সরাসরি খেলার যোগ্য নয়৷
  • MediaItem.RequestMetadata.searchQuery : একটি পাঠ্য অনুসন্ধান ক্যোয়ারী, উদাহরণস্বরূপ Google সহকারী থেকে।
  • MediaItem.MediaMetadata : কাঠামোবদ্ধ মেটাডেটা যেমন 'শিরোনাম' বা 'শিল্পী'।

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

কাস্টম লেআউট এবং কাস্টম কমান্ড পরিচালনা করুন

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

সেশনের কাস্টম বিন্যাস সংজ্ঞায়িত করুন

ক্লায়েন্ট অ্যাপগুলিকে নির্দেশ করতে আপনি কোন প্লেব্যাক নিয়ন্ত্রণগুলি ব্যবহারকারীর কাছে প্রকাশ করতে চান, আপনার পরিষেবার onCreate() পদ্ধতিতে MediaSession তৈরি করার সময় সেশনের কাস্টম বিন্যাস সেট করুন৷

কোটলিন

override fun onCreate() {
  super.onCreate()

  val likeButton = CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build()
  val favoriteButton = CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(SessionCommand(SAVE_TO_FAVORITES, Bundle()))
    .build()

  session =
    MediaSession.Builder(this, player)
      .setCallback(CustomMediaSessionCallback())
      .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
      .build()
}

জাভা

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

  CommandButton likeButton = new CommandButton.Builder()
    .setDisplayName("Like")
    .setIconResId(R.drawable.like_icon)
    .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING))
    .build();
  CommandButton favoriteButton = new CommandButton.Builder()
    .setDisplayName("Save to favorites")
    .setIconResId(R.drawable.favorite_icon)
    .setSessionCommand(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
    .build();

  Player player = new ExoPlayer.Builder(this).build();
  mediaSession =
      new MediaSession.Builder(this, player)
          .setCallback(new CustomMediaSessionCallback())
          .setCustomLayout(ImmutableList.of(likeButton, favoriteButton))
          .build();
}

উপলব্ধ প্লেয়ার এবং কাস্টম কমান্ড ঘোষণা করুন

মিডিয়া অ্যাপ্লিকেশনগুলি কাস্টম কমান্ডগুলিকে সংজ্ঞায়িত করতে পারে যা উদাহরণস্বরূপ একটি কাস্টম লেআউটে ব্যবহার করা যেতে পারে। উদাহরণস্বরূপ, আপনি এমন বোতামগুলি প্রয়োগ করতে ইচ্ছুক হতে পারেন যা ব্যবহারকারীকে পছন্দের আইটেমগুলির একটি তালিকায় একটি মিডিয়া আইটেম সংরক্ষণ করতে দেয়। MediaController কাস্টম কমান্ড পাঠায় এবং MediaSession.Callback সেগুলি গ্রহণ করে।

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

কোটলিন

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  override fun onConnect(
    session: MediaSession,
    controller: MediaSession.ControllerInfo
  ): MediaSession.ConnectionResult {
    val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build()
  }
}

জাভা

class CustomMediaSessionCallback implements MediaSession.Callback {
  // Configure commands available to the controller in onConnect()
  @Override
  public ConnectionResult onConnect(
    MediaSession session,
    ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
}

একটি MediaController থেকে কাস্টম কমান্ডের অনুরোধ পেতে, Callback onCustomCommand() পদ্ধতিটি ওভাররাইড করুন।

কোটলিন

private inner class CustomMediaSessionCallback: MediaSession.Callback {
  ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: MediaSession.ControllerInfo,
    customCommand: SessionCommand,
    args: Bundle
  ): ListenableFuture<SessionResult> {
    if (customCommand.customAction == SAVE_TO_FAVORITES) {
      // Do custom logic here
      saveToFavorites(session.player.currentMediaItem)
      return Futures.immediateFuture(
        SessionResult(SessionResult.RESULT_SUCCESS)
      )
    }
    ...
  }
}

জাভা

class CustomMediaSessionCallback implements MediaSession.Callback {
  ...
  @Override
  public ListenableFuture<SessionResult> onCustomCommand(
    MediaSession session, 
    ControllerInfo controller,
    SessionCommand customCommand,
    Bundle args
  ) {
    if(customCommand.customAction.equals(SAVE_TO_FAVORITES)) {
      // Do custom logic here
      saveToFavorites(session.getPlayer().getCurrentMediaItem());
      return Futures.immediateFuture(
        new SessionResult(SessionResult.RESULT_SUCCESS)
      );
    }
    ...
  }
}

আপনি MediaSession.ControllerInfo অবজেক্টের packageName প্রপার্টি ব্যবহার করে কোন মিডিয়া কন্ট্রোলার একটি অনুরোধ করছে তা ট্র্যাক করতে পারেন যা Callback পদ্ধতিতে পাস করা হয়। এটি আপনাকে একটি প্রদত্ত কমান্ডের প্রতিক্রিয়া হিসাবে আপনার অ্যাপের আচরণকে উপযোগী করার অনুমতি দেয় যদি এটি সিস্টেম, আপনার নিজের অ্যাপ বা অন্যান্য ক্লায়েন্ট অ্যাপ থেকে উদ্ভূত হয়।

ব্যবহারকারীর ইন্টারঅ্যাকশনের পর কাস্টম লেআউট আপডেট করুন

আপনার প্লেয়ারের সাথে একটি কাস্টম কমান্ড বা অন্য কোনো মিথস্ক্রিয়া পরিচালনা করার পরে, আপনি কন্ট্রোলার UI এ প্রদর্শিত লেআউট আপডেট করতে চাইতে পারেন। একটি সাধারণ উদাহরণ হল একটি টগল বোতাম যা এই বোতামের সাথে যুক্ত ক্রিয়াটি ট্রিগার করার পরে এটির আইকন পরিবর্তন করে। লেআউট আপডেট করতে, আপনি MediaSession.setCustomLayout ব্যবহার করতে পারেন:

কোটলিন

val removeFromFavoritesButton = CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(SessionCommand(REMOVE_FROM_FAVORITES, Bundle()))
  .build()
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton))

জাভা

CommandButton removeFromFavoritesButton = new CommandButton.Builder()
  .setDisplayName("Remove from favorites")
  .setIconResId(R.drawable.favorite_remove_icon)
  .setSessionCommand(new SessionCommand(REMOVE_FROM_FAVORITES, new Bundle()))
  .build();
mediaSession.setCustomLayout(ImmutableList.of(likeButton, removeFromFavoritesButton));

প্লেব্যাক কমান্ড আচরণ কাস্টমাইজ করুন

Player ইন্টারফেসে সংজ্ঞায়িত একটি কমান্ডের আচরণ কাস্টমাইজ করতে, যেমন play() বা seekToNext() , আপনার Player একটি ForwardingPlayer মোড়ানো।

কোটলিন

val player = ExoPlayer.Builder(context).build()

val forwardingPlayer = object : ForwardingPlayer(player) {
  override fun play() {
    // Add custom logic
    super.play()
  }

  override fun setPlayWhenReady(playWhenReady: Boolean) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady)
  }
}

val mediaSession = MediaSession.Builder(context, forwardingPlayer).build()

জাভা

ExoPlayer player = new ExoPlayer.Builder(context).build();

ForwardingPlayer forwardingPlayer = new ForwardingPlayer(player) {
  @Override
  public void play() {
    // Add custom logic
    super.play();
  }

  @Override
  public void setPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    super.setPlayWhenReady(playWhenReady);
  }
};

MediaSession mediaSession =
  new MediaSession.Builder(context, forwardingPlayer).build();

ForwardingPlayer সম্পর্কে আরও তথ্যের জন্য, কাস্টমাইজেশনের উপর ExoPlayer গাইড দেখুন।

একটি প্লেয়ার কমান্ডের অনুরোধকারী নিয়ামক সনাক্ত করুন

যখন একটি Player পদ্ধতিতে একটি কল একটি MediaController দ্বারা উদ্ভূত হয়, তখন আপনি MediaSession.controllerForCurrentRequest এর মাধ্যমে উত্সের উত্স সনাক্ত করতে পারেন এবং বর্তমান অনুরোধের জন্য ControllerInfo অর্জন করতে পারেন:

কোটলিন

class CallerAwareForwardingPlayer(player: Player) :
  ForwardingPlayer(player) {

  override fun seekToNext() {
    Log.d(
      "caller",
      "seekToNext called from package ${session.controllerForCurrentRequest?.packageName}"
    )
    super.seekToNext()
  }
}

জাভা

public class CallerAwareForwardingPlayer extends ForwardingPlayer {
  public CallerAwareForwardingPlayer(Player player) {
    super(player);
  }

  @Override
  public void seekToNext() {
    Log.d(
        "caller",
        "seekToNext called from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    super.seekToNext();
  }
}

মিডিয়া বোতামে সাড়া দিন

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

একটি অ্যাপ MediaSession.Callback.onMediaButtonEvent(Intent) ওভাররাইড করে ডিফল্ট আচরণকে ওভাররাইড করতে পারে। এই ধরনের ক্ষেত্রে অ্যাপটি নিজেই সমস্ত API স্পেসিফিকেশন পরিচালনা করতে পারে/প্রয়োজন।

ত্রুটি হ্যান্ডলিং এবং রিপোর্টিং

একটি অধিবেশন নির্গত এবং নিয়ন্ত্রকদের রিপোর্ট যে দুটি ধরনের ত্রুটি আছে. মারাত্মক ত্রুটিগুলি সেশন প্লেয়ারের একটি প্রযুক্তিগত প্লেব্যাক ব্যর্থতার রিপোর্ট করে যা প্লেব্যাকে বাধা দেয়। মারাত্মক ত্রুটিগুলি যখন ঘটে তখন স্বয়ংক্রিয়ভাবে নিয়ামককে রিপোর্ট করা হয়। অপ্রয়োজনীয় ত্রুটিগুলি অ-প্রযুক্তিগত বা নীতিগত ত্রুটি যা প্লেব্যাকে বাধা দেয় না এবং ম্যানুয়ালি অ্যাপ্লিকেশন দ্বারা নিয়ন্ত্রকদের কাছে পাঠানো হয়।

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

প্লেয়ারের দ্বারা সেশনে একটি মারাত্মক প্লেব্যাক ত্রুটি রিপোর্ট করা হয় এবং তারপরে Player.Listener.onPlayerError(PlaybackException) এবং Player.Listener.onPlayerErrorChanged(@Nullable PlaybackException) এর মাধ্যমে কল করার জন্য কন্ট্রোলারদের রিপোর্ট করা হয়।

এই ধরনের ক্ষেত্রে, প্লেব্যাক স্টেটটি STATE_IDLE তে স্থানান্তরিত হয় এবং MediaController.getPlaybackError() PlaybackException প্রদান করে যা স্থানান্তর ঘটায়। ত্রুটির কারণ সম্পর্কে তথ্য পেতে একজন নিয়ামক PlayerException.errorCode পরিদর্শন করতে পারেন।

আন্তঃঅপারেবিলিটির জন্য, একটি মারাত্মক ত্রুটি প্ল্যাটফর্ম সেশনের PlaybackStateCompat কম্প্যাটে প্রতিলিপি করা হয় STATE_ERROR এ রূপান্তরিত করে এবং PlaybackException অনুযায়ী ত্রুটি কোড এবং বার্তা সেট করে।

একটি মারাত্মক ত্রুটির কাস্টমাইজেশন

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

কোটলিন

val forwardingPlayer = ErrorForwardingPlayer(player)
val session = MediaSession.Builder(context, forwardingPlayer).build()

জাভা

Player forwardingPlayer = new ErrorForwardingPlayer(player);
MediaSession session =
    new MediaSession.Builder(context, forwardingPlayer).build();

ফরোয়ার্ডিং প্লেয়ার একজন Player.Listener রেজিস্টার করে৷ প্রকৃত প্লেয়ারের শ্রোতা এবং কলব্যাকগুলিকে বাধা দেয় যা একটি ত্রুটি রিপোর্ট করে৷ একটি কাস্টমাইজড PlaybackException তারপর ফরওয়ার্ডিং প্লেয়ারে নিবন্ধিত শ্রোতাদের কাছে অর্পণ করা হয়। এটি কাজ করার জন্য, ফরওয়ার্ডিং প্লেয়ারটি Player.addListener এবং Player.removeListener কে ওভাররাইড করে যাতে শ্রোতাদের অ্যাক্সেস থাকে যার মাধ্যমে কাস্টমাইজড ত্রুটি কোড, বার্তা বা অতিরিক্ত পাঠাতে হয়:

কোটলিন

class ErrorForwardingPlayer(private val context: Context, player: Player) :
  ForwardingPlayer(player) {

  private val listeners: MutableList<Player.Listener> = mutableListOf()

  private var customizedPlaybackException: PlaybackException? = null

  init {
    player.addListener(ErrorCustomizationListener())
  }

  override fun addListener(listener: Player.Listener) {
    listeners.add(listener)
  }

  override fun removeListener(listener: Player.Listener) {
    listeners.remove(listener)
  }

  override fun getPlayerError(): PlaybackException? {
    return customizedPlaybackException
  }

  private inner class ErrorCustomizationListener : Player.Listener {

    override fun onPlayerErrorChanged(error: PlaybackException?) {
      customizedPlaybackException = error?.let { customizePlaybackException(it) }
      listeners.forEach { it.onPlayerErrorChanged(customizedPlaybackException) }
    }

    override fun onPlayerError(error: PlaybackException) {
      listeners.forEach { it.onPlayerError(customizedPlaybackException!!) }
    }

    private fun customizePlaybackException(
      error: PlaybackException,
    ): PlaybackException {
      val buttonLabel: String
      val errorMessage: String
      when (error.errorCode) {
        PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW -> {
          buttonLabel = context.getString(R.string.err_button_label_restart_stream)
          errorMessage = context.getString(R.string.err_msg_behind_live_window)
        }
        // Apps can customize further error messages by adding more branches.
        else -> {
          buttonLabel = context.getString(R.string.err_button_label_ok)
          errorMessage = context.getString(R.string.err_message_default)
        }
      }
      val extras = Bundle()
      extras.putString("button_label", buttonLabel)
      return PlaybackException(errorMessage, error.cause, error.errorCode, extras)
    }

    override fun onEvents(player: Player, events: Player.Events) {
      listeners.forEach {
        it.onEvents(player, events)
      }
    }
    // Delegate all other callbacks to all listeners without changing arguments like onEvents.
  }
}

জাভা

private static class ErrorForwardingPlayer extends ForwardingPlayer {

  private final Context context;
  private List<Player.Listener> listeners;
  @Nullable private PlaybackException customizedPlaybackException;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
    listeners = new ArrayList<>();
    player.addListener(new ErrorCustomizationListener());
  }

  @Override
  public void addListener(Player.Listener listener) {
    listeners.add(listener);
  }

  @Override
  public void removeListener(Player.Listener listener) {
    listeners.remove(listener);
  }

  @Nullable
  @Override
  public PlaybackException getPlayerError() {
    return customizedPlaybackException;
  }

  private class ErrorCustomizationListener implements Listener {

    @Override
    public void onPlayerErrorChanged(@Nullable PlaybackException error) {
      customizedPlaybackException =
          error != null ? customizePlaybackException(error, context) : null;
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onPlayerErrorChanged(customizedPlaybackException);
      }
    }

    @Override
    public void onPlayerError(PlaybackException error) {
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onPlayerError(checkNotNull(customizedPlaybackException));
      }
    }

    private PlaybackException customizePlaybackException(
        PlaybackException error, Context context) {
      String buttonLabel;
      String errorMessage;
      switch (error.errorCode) {
        case PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW:
          buttonLabel = context.getString(R.string.err_button_label_restart_stream);
          errorMessage = context.getString(R.string.err_msg_behind_live_window);
          break;
        // Apps can customize further error messages by adding more case statements.
        default:
          buttonLabel = context.getString(R.string.err_button_label_ok);
          errorMessage = context.getString(R.string.err_message_default);
          break;
      }
      Bundle extras = new Bundle();
      extras.putString("button_label", buttonLabel);
      return new PlaybackException(errorMessage, error.getCause(), error.errorCode, extras);
    }

    @Override
    public void onEvents(Player player, Events events) {
      for (int i = 0; i < listeners.size(); i++) {
        listeners.get(i).onEvents(player, events);
      }
    }
    // Delegate all other callbacks to all listeners without changing arguments like onEvents.
  }
}

অপ্রত্যাশিত ত্রুটি

প্রযুক্তিগত ব্যতিক্রম থেকে উদ্ভূত নয় এমন অপ্রত্যাশিত ত্রুটিগুলি একটি অ্যাপ দ্বারা সকলকে বা একটি নির্দিষ্ট নিয়ামকের কাছে পাঠানো যেতে পারে:

কোটলিন

val sessionError = SessionError(
  SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
  context.getString(R.string.error_message_authentication_expired),
)

// Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError)

// Interoperability: Sending a nonfatal error to the media notification controller to set the
// error code and error message in the playback state of the platform media session.
mediaSession.mediaNotificationControllerInfo?.let {
  mediaSession.sendError(it, sessionError)
}

জাভা

SessionError sessionError = new SessionError(
    SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED,
    context.getString(R.string.error_message_authentication_expired));

// Sending a nonfatal error to all controllers.
mediaSession.sendError(sessionError);

// Interoperability: Sending a nonfatal error to the media notification controller to set the
// error code and error message in the playback state of the platform media session.
ControllerInfo mediaNotificationControllerInfo =
    mediaSession.getMediaNotificationControllerInfo();
if (mediaNotificationControllerInfo != null) {
  mediaSession.sendError(mediaNotificationControllerInfo, sessionError);
}

মিডিয়া নোটিফিকেশন কন্ট্রোলারে পাঠানো একটি মারাত্মক ত্রুটি প্ল্যাটফর্ম সেশনের PlaybackStateCompat কম্প্যাটে প্রতিলিপি করা হয়েছে। এইভাবে, শুধুমাত্র ত্রুটি কোড এবং ত্রুটি বার্তাটি সেই অনুযায়ী PlaybackStateCompat সেট করা হয়েছে, যখন PlaybackStateCompat.state STATE_ERROR এ পরিবর্তন করা হয়নি।

মারাত্মক ত্রুটিগুলি পান

একটি MediaController MediaController.Listener.onError প্রয়োগ করে একটি অপ্রত্যাশিত ত্রুটি পায়:

কোটলিন

val future = MediaController.Builder(context, sessionToken)
  .setListener(object : MediaController.Listener {
    override fun onError(controller: MediaController, sessionError: SessionError) {
      // Handle nonfatal error.
    }
  })
  .buildAsync()

জাভা

MediaController.Builder future =
    new MediaController.Builder(context, sessionToken)
        .setListener(
            new MediaController.Listener() {
              @Override
              public void onError(MediaController controller, SessionError sessionError) {
                // Handle nonfatal error.
              }
            });