MediaSession ব্যবহার করে প্লেব্যাক কন্ট্রোল ও বিজ্ঞাপন দেওয়া

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

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

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

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

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

এটি অনেক ব্যবহারের ক্ষেত্রে খুব উপযোগী। বিশেষ করে, এইসব ক্ষেত্রে MediaSession ব্যবহার করার কথা আপনাকে অবশ্যই বিবেচনা করতে হবে:

  • আপনি সিনেমা বা লাইভ টিভির মতো বড় দৈর্ঘ্যের ভিডিও কন্টেন্ট স্ট্রিম করছেন।
  • আপনি পডকাস্ট বা মিউজিক প্লেলিস্টের মতো দীর্ঘমেয়াদী অডিও কন্টেন্ট স্ট্রিম করছেন।
  • আপনি একটি TV অ্যাপ তৈরি করছেন।

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

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

আপনার ব্যবহারিক ক্ষেত্র উপরে তালিকাভুক্ত কোনওটির সাথে না মিললে, ব্যবহারকারী কন্টেন্টের সাথে অ্যাক্টিভভাবে এনগেজ না থাকলেও আপনার অ্যাপে প্লেব্যাক চালিয়ে যাওয়া হলে আপনার কোনও অসুবিধা হবে কিনা তা বিবেচনা করুন। উত্তর 'হ্যাঁ' হলে, আপনি সম্ভবত MediaSession বিকল্পটি বেছে নিতে চান। উত্তর 'না' হলে, আপনি সম্ভবত Player ব্যবহার করতে চান।

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

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

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

Kotlin

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 লাইব্রেরি অটোমেটিক প্লেয়ারের স্টেট ব্যবহার করে মিডিয়া সেশন আপডেট করে। তাই, আপনাকে প্লেয়ার থেকে সেশনে ম্যাপিং ম্যানুয়ালি হ্যান্ডেল করতে হবে না।

এটি প্ল্যাটফর্ম মিডিয়া সেশন থেকে আলাদা, যেখানে আপনাকে প্লেয়ার থেকে আলাদাভাবে PlaybackState তৈরি ও ম্যানেজ করতে হয়, যেমন কোনও সমস্যা নির্দেশ করার জন্য।

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

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

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

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

অন্যান্য ক্লায়েন্টকে কন্ট্রোল করার অনুমতি দেওয়া

প্লেব্যাক কন্ট্রোল করার জন্য মিডিয়া সেশন হল মূল বিষয়। এটি আপনাকে এক্সটার্নাল সোর্স থেকে প্লেয়ারে কমান্ড রাউট করতে দেয়, যা আপনার মিডিয়া চালানোর কাজ করে। এইসব সোর্স ফিজিক্যাল বোতাম হতে পারে, যেমন হেডসেট বা টিভি রিমোট কন্ট্রোলে থাকা প্লে বোতাম অথবা পরোক্ষ কমান্ড হতে পারে, যেমন Google Assistant-কে "পজ করো" নির্দেশ দেওয়া। একইভাবে, বিজ্ঞপ্তি ও লক স্ক্রিন কন্ট্রোল সহজ করতে আপনি Android সিস্টেমকে অ্যাক্সেস দিতে পারেন অথবা Wear OS ওয়াচকে অ্যাক্সেস দিতে পারেন যাতে আপনি ওয়াচফেস থেকে প্লেব্যাক কন্ট্রোল করতে পারেন। এক্সটার্নাল ক্লায়েন্ট আপনার মিডিয়া অ্যাপকে প্লেব্যাক কমান্ড ইস্যু করতে মিডিয়া কন্ট্রোলার ব্যবহার করতে পারে। এগুলি আপনার মিডিয়া সেশন দ্বারা গ্রহণ করা হয়, যা অবশেষে মিডিয়া প্লেয়ারকে কমান্ড প্রেরণ করে।

MediaSession ও MediaController-এর মধ্যে ইন্টার‍্যাকশন ডেমোনস্ট্রেট করা একটি ডায়াগ্রাম।
ছবি ১: মিডিয়া কন্ট্রোলার, মিডিয়া সেশনে এক্সটার্নাল সোর্স থেকে কমান্ড পাস করার সুবিধা দেয়।
দেখুন।

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

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

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

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

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

প্লেলিস্টে নতুন আইটেম যোগ করার সময়, প্লেয়ারকে সাধারণত MediaItem ইনস্ট্যান্সের সাথে নির্ধারিত URI প্রদান করতে হয়, যাতে সেগুলি চালানো যায়। ডিফল্ট হিসেবে, নতুন যোগ করা আইটেম অটোমেটিক ফরওয়ার্ড করা হয় প্লেয়ার মেথডে, যেমন player.addMediaItem, যদি সেটির URI নির্দিষ্ট করা থাকে।

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

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

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

মিডিয়া বোতাম সংক্রান্ত পছন্দ ম্যানেজ করা

প্রতিটি কন্ট্রোলার, যেমন System UI, Android Auto বা Wear OS, ব্যবহারকারীকে কোন বোতাম দেখাবে সেই সম্পর্কে নিজস্ব সিদ্ধান্ত নিতে পারে। ব্যবহারকারীকে কোন প্লেব্যাক কন্ট্রোল দেখাতে চান তা বোঝাতে, আপনি MediaSession-এ মিডিয়া বোতাম প্রিফারেন্স নির্দিষ্ট করতে পারেন। এইসব পছন্দের মধ্যে CommandButton ইনস্ট্যান্সের একটি সাজানো তালিকা থাকে, যার প্রত্যেকটি ইউজার ইন্টারফেসে একটি বোতামের জন্য পছন্দ নির্ধারণ করে।

কমান্ড বোতামের সংজ্ঞা দিন

মিডিয়া বোতাম সংক্রান্ত পছন্দ নির্ধারণ করতে CommandButton ইনস্ট্যান্স ব্যবহার করা হয়। প্রতিটি বোতাম, কাঙ্ক্ষিত UI এলিমেন্টের তিনটি দিককে সংজ্ঞায়িত করে:

  1. আইকন, যা ভিজ্যুয়াল অ্যাপিয়ারেন্স নির্ধারণ করে। CommandButton.Builder তৈরি করার সময় আইকনটিকে অবশ্যই আগে থেকে নির্ধারিত কনস্ট্যান্টের মধ্যে একটিতে সেট করতে হবে। মনে রাখবেন যে এটি কোনও আসল বিটম্যাপ বা ছবির রিসোর্স নয়। জেনেরিক কনস্ট্যান্ট কন্ট্রোলারদের তাদের নিজস্ব UI-এর মধ্যে একটি সামঞ্জস্যপূর্ণ লুক অ্যান্ড ফিল এর জন্য উপযুক্ত রিসোর্স বেছে নিতে সাহায্য করে। আগে থেকে সংজ্ঞায়িত আইকন কনস্ট্যান্টের কোনওটি আপনার ব্যবহারের ক্ষেত্রে উপযুক্ত না হলে, আপনি এর পরিবর্তে setCustomIconResId ব্যবহার করতে পারেন।
  2. কমান্ড, ব্যবহারকারী বোতামের সাথে ইন্টার‍্যাক্ট করলে ট্রিগার করা অ্যাকশনকে সংজ্ঞায়িত করে। আপনি Player.Command-এর জন্য setPlayerCommand অথবা আগে থেকে নির্দিষ্ট করা বা কাস্টম SessionCommand-এর জন্য setSessionCommand ব্যবহার করতে পারেন।
  3. স্লট, কন্ট্রোলার UI-তে বোতামটি কোথায় প্লেস করা হবে তা নির্ধারণ করে। এই ফিল্ডটি ঐচ্ছিক এবং আইকন ও কমান্ডের উপর ভিত্তি করে অটোমেটিক সেট করা হয়। যেমন, এটি নির্দিষ্ট করতে দেয় যে একটি বোতামকে ডিফল্ট 'ওভারফ্লো' এরিয়ার পরিবর্তে UI-এর 'এগিয়ে যান' নেভিগেশন এরিয়ায় দেখানো উচিত।

Kotlin

val button =
  CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
    .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
    .setSlots(CommandButton.SLOT_FORWARD)
    .build()

জাভা

CommandButton button =
    new CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD_15)
        .setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
        .setSlots(CommandButton.SLOT_FORWARD)
        .build();

মিডিয়া বোতাম সংক্রান্ত পছন্দ সংক্রান্ত সমস্যার সমাধান হয়ে গেলে, নিম্নলিখিত অ্যালগরিদম প্রয়োগ করা হয়:

  1. মিডিয়া বোতামের পছন্দ-এর মধ্যে থাকা প্রতিটি CommandButton-এর জন্য, বোতামটি প্রথম উপলভ্য ও অনুমোদিত স্লটে রাখুন।
  2. মাঝখানের, সামনের ও পিছনের স্লটগুলির কোনও একটিতে বোতাম না থাকলে, এই স্লটের জন্য ডিফল্ট বোতাম যোগ করুন।

UI ডিসপ্লে সংক্রান্ত সীমাবদ্ধতার উপর নির্ভর করে কীভাবে মিডিয়া বোতাম সংক্রান্ত পছন্দ সমাধান করা হবে, তার প্রিভিউ জেনারেট করতে আপনি CommandButton.DisplayConstraints ব্যবহার করতে পারবেন।

মিডিয়া বোতামের পছন্দ সেট করা

মিডিয়া বোতামের পছন্দ সেট করার সবচেয়ে সহজ উপায় হল MediaSessionবিল্ড করার সময় তালিকাটি সংজ্ঞায়িত করা। অথবা, আপনি ওভাররাইড করতে পারেন MediaSession.Callback.onConnect প্রতিটি কানেক্ট করা কন্ট্রোলারের জন্য মিডিয়া বোতামের পছন্দ কাস্টমাইজ করতে।

Kotlin

val mediaSession =
  MediaSession.Builder(context, player)
    .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
    .build()

জাভা

MediaSession mediaSession =
    new MediaSession.Builder(context, player)
        .setMediaButtonPreferences(ImmutableList.of(likeButton, favoriteButton))
        .build();

ব্যবহারকারীর ইন্টার‍্যাকশনের পরে মিডিয়া বোতামের পছন্দ আপডেট করা

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

Kotlin

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton))

জাভা

// Handle "favoritesButton" action, replace by opposite button
mediaSession.setMediaButtonPreferences(ImmutableList.of(likeButton, removeFromFavoritesButton));

কাস্টম কমান্ড যোগ করা এবং ডিফল্ট আচরণ কাস্টমাইজ করা

কাস্টম কমান্ডের মাধ্যমে উপলভ্য প্লেয়ার কমান্ডের সংখ্যা বাড়ানো যেতে পারে এবং এছাড়াও, ইনকামিং প্লেয়ার কমান্ড ও মিডিয়া বোতাম ইন্টারসেপ্ট করে ডিফল্ট আচরণ পরিবর্তন করা সম্ভব।

কাস্টম কমান্ড ঘোষণা ও ম্যানেজ করা

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

ডিফাইন করা স্ট্যান্ডার্ড কন্ট্রোলগুলির মধ্যে একটি পরিবর্তন করতে কাস্টম কমান্ড ব্যবহার করবেন না।

কাস্টম কমান্ড নির্ধারণ করতে, আপনাকে MediaSession.Callback.onConnect() ওভাররাইড করতে হবে যাতে প্রতিটি কানেক্ট করা কন্ট্রোলারের জন্য উপলভ্য কাস্টম কমান্ড সেট করা যায়।

Kotlin

private class CustomMediaSessionCallback : MediaSession.Callback {

  // Configure commands available to the controller in onConnect()
  override fun onConnectAsync(
    session: MediaSession,
    controller: ControllerInfo,
  ): ListenableFuture<ConnectionResult> {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(SessionCommand(SAVE_TO_FAVORITES, Bundle.EMPTY))
        .build()
    return Futures.immediateFuture(
      AcceptedResultBuilder(session, controller)
        .setAvailableSessionCommands(sessionCommands)
        .build()
    )
  }
}

জাভা

private static class CustomMediaSessionCallback implements MediaSession.Callback {

  // Configure commands available to the controller in onConnect()
  @Override
  public ListenableFuture<ConnectionResult> onConnectAsync(
      MediaSession session, ControllerInfo controller) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(new SessionCommand(SAVE_TO_FAVORITES, new Bundle()))
            .build();
    return Futures.immediateFuture(
        new AcceptedResultBuilder(session, controller)
            .setAvailableSessionCommands(sessionCommands)
            .build());
  }
}

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

Kotlin

private class CustomCallback : MediaSession.Callback {
  // ...
  override fun onCustomCommand(
    session: MediaSession,
    controller: 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))
    }
    // ...
    return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
  }
}

জাভা

private static class CustomCallback 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));
    }
    // ...
    return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
  }
}

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

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

সব ডিফল্ট কমান্ড ও স্টেট ম্যানেজমেন্ট Player-এর কাছে হস্তান্তর করা হয়, যা MediaSession-এ থাকে। play() বা seekToNext()-এর মতো Player ইন্টারফেসে সংজ্ঞায়িত কমান্ডের আচরণ কাস্টমাইজ করতে, MediaSession-এ পাস করার আগে আপনার Player-কে ForwardingSimpleBasePlayer-এ র‍্যাপ করুন:

Kotlin

val forwardingPlayer =
  object : ForwardingSimpleBasePlayer(player) {
    // Customizations
  }

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

জাভা

ForwardingSimpleBasePlayer forwardingPlayer = new ForwardingSimpleBasePlayer(player) {
      // Customizations
    };

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

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

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

MediaController-এর মাধ্যমে Player মেথডে কল করা হলে, আপনি MediaSession.controllerForCurrentRequest-এর মাধ্যমে সোর্স শনাক্ত করতে এবং বর্তমান অনুরোধের জন্য ControllerInfo পেতে পারবেন:

Kotlin

private class CallerAwarePlayer(player: Player) : ForwardingSimpleBasePlayer(player) {
  private lateinit var session: MediaSession

  override fun handleSeek(
    mediaItemIndex: Int,
    positionMs: Long,
    seekCommand: Int,
  ): ListenableFuture<*> {
    Log.d(
      "caller",
      "seek operation from package ${session.controllerForCurrentRequest?.packageName}",
    )
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand)
  }
}

জাভা

private static final class CallerAwarePlayer extends ForwardingSimpleBasePlayer {
  private MediaSession session;

  public CallerAwarePlayer(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSeek(int mediaItemIndex, long positionMs, int seekCommand) {
    Log.d(
        "caller",
        "seek operation from package: "
            + session.getControllerForCurrentRequest().getPackageName());
    return super.handleSeek(mediaItemIndex, positionMs, seekCommand);
  }
}

মিডিয়া বোতাম হ্যান্ডেল করা কাস্টমাইজ করা

মিডিয়া বোতাম হল হার্ডওয়্যার বোতাম যা Android ডিভাইস ও অন্যান্য পেরিফেরাল ডিভাইসে থাকে, যেমন ব্লুটুথ হেডসেটে প্লে/পজ বোতাম। সেশনে পৌঁছানোর পরে Media3 আপনার জন্য মিডিয়া বোতাম ইভেন্ট ম্যানেজ করে এবং সেশন প্লেয়ারে উপযুক্ত Player মেথড কল করে।

সংশ্লিষ্ট Player মেথডে আগত সব মিডিয়া বোতাম ইভেন্ট হ্যান্ডেল করার জন্য সাজেস্ট করা হয়। আরও উন্নত ব্যবহারের ক্ষেত্রে, মিডিয়া বোতামের ইভেন্ট MediaSession.Callback.onMediaButtonEvent(Intent)-এ ইন্টারসেপ্ট করা যেতে পারে।

সমস্যা ম্যানেজ করা ও রিপোর্ট করা

সেশন থেকে দুই ধরনের সমস্যা নির্গত হয় এবং কন্ট্রোলারকে রিপোর্ট করা হয়। মারাত্মক সমস্যা সেশন প্লেয়ারের টেকনিক্যাল প্লেব্যাক সংক্রান্ত সমস্যার রিপোর্ট করে যা প্লেব্যাককে বাধা দেয়। মারাত্মক সমস্যা হলে, তা কন্ট্রোলারের কাছে অটোমেটিক রিপোর্ট করা হয়। নন-ফেটাল সমস্যা হল নন-টেকনিক্যাল বা নীতি সংক্রান্ত সমস্যা যা প্লেব্যাককে বাধা দেয় না এবং অ্যাপ্লিকেশন ম্যানুয়ালি কন্ট্রোলারকে পাঠায়।

প্লেব্যাক সংক্রান্ত মারাত্মক সমস্যা

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

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

কাস্টম প্লেয়ার সেট করা সংক্রান্ত সমস্যা

প্লেয়ারের মাধ্যমে রিপোর্ট করা মারাত্মক সমস্যা ছাড়াও, কোনও অ্যাপ্লিকেশন MediaSession.setPlaybackException(PlaybackException) ব্যবহার করে MediaSession লেভেলে কাস্টম PlaybackException সেট করতে পারে। এটি কানেক্ট করা কন্ট্রোলারকে অ্যাপ্লিকেশন থেকে একটি সমস্যার স্ট্যাটাস সিগন্যাল পাঠাতে দেয়। কানেক্ট করা সব কন্ট্রোলারের জন্য অথবা নির্দিষ্ট ControllerInfo-এর জন্য ব্যতিক্রম সেট করা যেতে পারে।

কোনও অ্যাপ এই API ব্যবহার করে PlaybackException সেট করলে:

  • কানেক্ট করা MediaController ইনস্ট্যান্সকে বিজ্ঞপ্তি পাঠানো হবে। কন্ট্রোলারের Listener.onPlayerError(PlaybackException) এবং Listener.onPlayerErrorChanged(@Nullable PlaybackException) কলব্যাক, প্রদত্ত ব্যতিক্রম সহ আহ্বান করা হবে।

  • MediaController.getPlayerError() পদ্ধতিটি অ্যাপ্লিকেশনের সেট করা PlaybackException ফেরত দেবে।

  • প্রভাবিত কন্ট্রোলারের প্লেব্যাক স্ট্যাটাস পরিবর্তন হয়ে Player.STATE_IDLE হয়ে যাবে।

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

আগে সেট করা কাস্টম PlaybackException মুছে ফেলতে এবং সাধারণ প্লেয়ারের স্ট্যাটাস রিপোর্টিং রিস্টোর করতে, কোনও অ্যাপ MediaSession.setPlaybackException(/* playbackException= */ null) বা MediaSession.setPlaybackException(ControllerInfo, /* playbackException= */ null) কল করতে পারে।

মারাত্মক সমস্যার কাস্টমাইজেশন

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

Kotlin

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

জাভা

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

ফরওয়ার্ডিং প্লেয়ার ForwardingSimpleBasePlayer ব্যবহার করে ত্রুটি ইন্টারসেপ্ট করতে এবং ত্রুটি কোড, মেসেজ বা অতিরিক্ত তথ্য কাস্টমাইজ করতে পারে। একইভাবে, আপনি এমন নতুন সমস্যাও তৈরি করতে পারেন যা আসল প্লেয়ারে নেই:

Kotlin

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

  override fun getState(): State {
    var state = super.getState()
    if (state.playerError != null) {
      state =
        state.buildUpon().setPlayerError(customizePlaybackException(state.playerError!!)).build()
    }
    return state
  }

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

জাভা

private static class ErrorForwardingPlayer extends ForwardingSimpleBasePlayer {

  private final Context context;

  public ErrorForwardingPlayer(Context context, Player player) {
    super(player);
    this.context = context;
  }

  @Override
  protected State getState() {
    State state = super.getState();
    if (state.playerError != null) {
      state =
          state.buildUpon().setPlayerError(customizePlaybackException(state.playerError)).build();
    }
    return state;
  }

  private PlaybackException customizePlaybackException(PlaybackException error) {
    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;
      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);
  }
}

অগুরুতর সমস্যা

টেকনিক্যাল ব্যতিক্রম থেকে উদ্ভূত নয় এমন মারাত্মক নয়, এমন সমস্যা কোনও অ্যাপের মাধ্যমে সব কন্ট্রোলার বা নির্দিষ্ট কন্ট্রোলারে পাঠানো যেতে পারে:

Kotlin

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

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

// Option 2: Sending a nonfatal error to the media notification controller only
// 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));

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

// Option 2: Sending a nonfatal error to the media notification controller only
// 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);
}

মিডিয়া বিজ্ঞপ্তি কন্ট্রোলারে কোনও নন-ফ্যাটাল সমস্যা পাঠানো হলে, প্ল্যাটফর্ম মিডিয়া সেশনে সমস্যা কোড ও সমস্যা মেসেজ রেপ্লিকেট করা হয়, তবে PlaybackState.state পরিবর্তন করে STATE_ERROR করা হয় না।

গুরুতর নয় এমন সমস্যা সংক্রান্ত মেসেজ পাওয়া

MediaController-এ MediaController.Listener.onError প্রয়োগ করার ফলে একটি অমারাত্মক সমস্যা হয়েছে:

Kotlin

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