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() লাইফসাইকেল পদ্ধতি অথবা মিডিয়া সেশন ও তার সাথে যুক্ত প্লেয়ারের মালিকানা থাকা Service-এর onCreate() পদ্ধতি।

মিডিয়া সেশন তৈরি করতে, 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()-এর স্যাম্পেল ইমপ্লিমেন্টেশন সেশন ডেমো অ্যাপে পাওয়া যাবে।

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

প্রতিটি কন্ট্রোলার, যেমন সিস্টেম 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));
  }
}

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

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

সব ডিফল্ট কমান্ড ও স্টেট হ্যান্ডলিং Player-এ ডেলিগেট করা হয় যা MediaSession-এ থাকে। Player ইন্টারফেসে সংজ্ঞায়িত কমান্ডের আচরণ কাস্টমাইজ করতে, যেমন play() বা seekToNext(), 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.Listener.onError প্রয়োগ করার মাধ্যমে MediaController-এ একটি নন-ফেটাল সমস্যা দেখা দেয়:

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