মিডিয়া সেশন একটি অডিও বা ভিডিও প্লেয়ারের সাথে ইন্টারঅ্যাক্ট করার একটি সার্বজনীন উপায় প্রদান করে। Media3-তে, ডিফল্ট প্লেয়ার হলো ExoPlayer ক্লাস, যা Player ইন্টারফেসটি ইমপ্লিমেন্ট করে। প্লেয়ারের সাথে মিডিয়া সেশন সংযোগ করার মাধ্যমে একটি অ্যাপ বাহ্যিকভাবে মিডিয়া প্লেব্যাকের বিজ্ঞাপন দিতে এবং বাহ্যিক উৎস থেকে প্লেব্যাক কমান্ড গ্রহণ করতে পারে।
কমান্ডগুলো হেডসেট বা টিভি রিমোট কন্ট্রোলের প্লে বাটনের মতো ফিজিক্যাল বাটন থেকে আসতে পারে। এগুলো মিডিয়া কন্ট্রোলারযুক্ত ক্লায়েন্ট অ্যাপ থেকেও আসতে পারে, যেমন গুগল অ্যাসিস্ট্যান্টকে 'পজ' করার নির্দেশ দেওয়া। মিডিয়া সেশন এই কমান্ডগুলো মিডিয়া অ্যাপের প্লেয়ারের কাছে অর্পণ করে।
কখন একটি মিডিয়া সেশন বেছে নেবেন
যখন আপনি MediaSession প্রয়োগ করেন, তখন আপনি ব্যবহারকারীদের প্লেব্যাক নিয়ন্ত্রণ করার অনুমতি দেন:
- তাদের হেডফোনের মাধ্যমে। প্রায়শই হেডফোনে বাটন বা টাচ ইন্টারঅ্যাকশন থাকে, যা ব্যবহার করে ব্যবহারকারীরা মিডিয়া প্লে বা পজ করতে অথবা পরবর্তী বা পূর্ববর্তী ট্র্যাকে যেতে পারেন।
- গুগল অ্যাসিস্ট্যান্টের সাথে কথা বলে। ডিভাইসে বর্তমানে প্লে হওয়া যেকোনো মিডিয়া পজ করার জন্য একটি সাধারণ পদ্ধতি হলো "ওকে গুগল, পজ" বলা।
- তাদের Wear OS ঘড়ির মাধ্যমে। এর ফলে ফোনে খেলার সময় সবচেয়ে সাধারণ প্লেব্যাক কন্ট্রোলগুলো আরও সহজে ব্যবহার করা যায়।
- মিডিয়া কন্ট্রোলের মাধ্যমে। এই ক্যারোসেলটি প্রতিটি চলমান মিডিয়া সেশনের জন্য কন্ট্রোলগুলো দেখায়।
- টিভিতে ব্যবহারের জন্য: এটি ফিজিক্যাল প্লেব্যাক বাটন, প্ল্যাটফর্ম প্লেব্যাক কন্ট্রোল এবং পাওয়ার ম্যানেজমেন্টের মতো অ্যাকশনগুলো নিয়ন্ত্রণ করতে দেয় (উদাহরণস্বরূপ, যদি টিভি, সাউন্ডবার বা এ/ভি রিসিভার বন্ধ হয়ে যায় বা ইনপুট পরিবর্তন করা হয়, তাহলে অ্যাপে প্লেব্যাক বন্ধ হয়ে যাবে)।
- অ্যান্ড্রয়েড অটো মিডিয়া কন্ট্রোলের মাধ্যমে। এটি গাড়ি চালানোর সময় নিরাপদে প্লেব্যাক নিয়ন্ত্রণ করতে সাহায্য করে।
- এবং অন্য যেকোনো বাহ্যিক প্রক্রিয়া যা প্লেব্যাককে প্রভাবিত করতে পারে।
এটি অনেক ব্যবহারের ক্ষেত্রেই দারুণ। বিশেষ করে, নিম্নলিখিত ক্ষেত্রে আপনার MediaSession ব্যবহার করার কথা গুরুত্ব সহকারে বিবেচনা করা উচিত:
- আপনি সিনেমা বা লাইভ টিভির মতো দীর্ঘ ভিডিও কন্টেন্ট স্ট্রিম করছেন।
- আপনি পডকাস্ট বা মিউজিক প্লেলিস্টের মতো দীর্ঘ অডিও কন্টেন্ট স্ট্রিম করছেন।
- আপনি একটি টিভি অ্যাপ তৈরি করছেন।
তবে, সব ব্যবহারের ক্ষেত্রেই MediaSession ভালোভাবে খাপ খায় না। নিম্নলিখিত ক্ষেত্রগুলিতে আপনি শুধু Player ব্যবহার করতে চাইতে পারেন:
- আপনি সংক্ষিপ্ত আকারের কন্টেন্ট দেখাচ্ছেন, যেখানে কোনো বাহ্যিক নিয়ন্ত্রণ বা ব্যাকগ্রাউন্ড প্লেব্যাকের প্রয়োজন নেই।
- এমন নয় যে একটিমাত্র ভিডিও চালু আছে, যেমন ব্যবহারকারী একটি তালিকা স্ক্রল করছেন এবং একই সাথে স্ক্রিনে একাধিক ভিডিও প্রদর্শিত হচ্ছে ।
- আপনি একটি এককালীন পরিচিতিমূলক বা ব্যাখ্যাধর্মী ভিডিও চালাচ্ছেন, যা আপনি আশা করেন আপনার ব্যবহারকারী কোনো বাহ্যিক প্লেব্যাক কন্ট্রোলের প্রয়োজন ছাড়াই সক্রিয়ভাবে দেখবেন।
- আপনার কন্টেন্টটি গোপনীয়তা-সংবেদনশীল এবং আপনি চান না যে বাহ্যিক কোনো প্রসেস মিডিয়া মেটাডেটা অ্যাক্সেস করুক (উদাহরণস্বরূপ, ব্রাউজারের ইনকগনিটো মোড)।
যদি আপনার ব্যবহারের ক্ষেত্রটি উপরে তালিকাভুক্ত কোনোটির সাথে না মেলে, তবে ভেবে দেখুন যে ব্যবহারকারী যখন সক্রিয়ভাবে কন্টেন্টের সাথে যুক্ত নন, তখনও আপনার অ্যাপটি প্লেব্যাক চালিয়ে গেলে আপনার কোনো অসুবিধা হবে কি না। যদি উত্তর হ্যাঁ হয়, তবে সম্ভবত আপনি MediaSession বেছে নিতে চাইবেন। আর যদি উত্তর না হয়, তবে সম্ভবত আপনি এর পরিবর্তে Player ব্যবহার করতে চাইবেন।
একটি মিডিয়া সেশন তৈরি করুন
একটি মিডিয়া সেশন তার দ্বারা পরিচালিত প্লেয়ারের পাশাপাশি অবস্থান করে। আপনি একটি Context এবং একটি Player অবজেক্ট ব্যবহার করে একটি মিডিয়া সেশন তৈরি করতে পারেন। যখন প্রয়োজন হবে, তখনই আপনার একটি মিডিয়া সেশন তৈরি এবং ইনিশিয়ালাইজ করা উচিত; যেমন Activity বা Fragment এর onStart() বা onResume() লাইফসাইকেল মেথডে, অথবা যে 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 লাইব্রেরি প্লেয়ারের অবস্থা ব্যবহার করে স্বয়ংক্রিয়ভাবে মিডিয়া সেশন আপডেট করে। ফলে, প্লেয়ার থেকে সেশনে ম্যাপিং করার জন্য আপনাকে ম্যানুয়ালি কোনো ব্যবস্থা নিতে হয় না।
এটি প্ল্যাটফর্ম মিডিয়া সেশন থেকে ভিন্ন, যেখানে উদাহরণস্বরূপ কোনো ত্রুটি নির্দেশ করার জন্য আপনাকে প্লেয়ার থেকে স্বাধীনভাবে একটি PlaybackState তৈরি ও রক্ষণাবেক্ষণ করতে হতো।
অনন্য সেশন আইডি
ডিফল্টরূপে, MediaSession.Builder সেশন আইডি হিসেবে একটি খালি স্ট্রিং ব্যবহার করে সেশন তৈরি করে। যদি কোনো অ্যাপ শুধুমাত্র একটি সেশন ইনস্ট্যান্স তৈরি করতে চায়, তবে এটিই যথেষ্ট, এবং সাধারণত এমনটাই ঘটে থাকে।
যদি কোনো অ্যাপ একই সময়ে একাধিক সেশন ইনস্ট্যান্স পরিচালনা করতে চায়, তবে অ্যাপটিকে নিশ্চিত করতে হবে যে প্রতিটি সেশনের সেশন আইডি অনন্য। MediaSession.Builder.setId(String id) ব্যবহার করে সেশন তৈরি করার সময় সেশন আইডি সেট করা যেতে পারে।
যদি আপনি দেখেন যে একটি IllegalStateException কারণে আপনার অ্যাপ ক্র্যাশ করছে এবং এরর মেসেজ হিসেবে লেখা আছে IllegalStateException: Session ID must be unique. ID= তাহলে সম্ভবত একই আইডি দিয়ে আগে থেকে তৈরি করা কোনো ইনস্ট্যান্স রিলিজ হওয়ার আগেই অপ্রত্যাশিতভাবে একটি সেশন তৈরি হয়ে গেছে। প্রোগ্রামিং ত্রুটির কারণে সেশন লিক হওয়া এড়ানোর জন্য, এই ধরনের ঘটনা শনাক্ত করা হয় এবং একটি এক্সেপশন থ্রো করার মাধ্যমে তা জানানো হয়।
অন্যান্য ক্লায়েন্টদের নিয়ন্ত্রণ প্রদান করুন
প্লেব্যাক নিয়ন্ত্রণের মূল চাবিকাঠি হলো মিডিয়া সেশন। এটি আপনাকে বাহ্যিক উৎস থেকে সেই প্লেয়ারে কমান্ড পাঠাতে সক্ষম করে, যা আপনার মিডিয়া চালানোর কাজটি করে। এই উৎসগুলো হতে পারে ফিজিক্যাল বাটন, যেমন হেডসেট বা টিভি রিমোট কন্ট্রোলের প্লে বাটন, অথবা পরোক্ষ কমান্ড, যেমন গুগল অ্যাসিস্ট্যান্টকে "পজ" করার নির্দেশ দেওয়া। একইভাবে, আপনি নোটিফিকেশন এবং লক স্ক্রিন নিয়ন্ত্রণের সুবিধার জন্য অ্যান্ড্রয়েড সিস্টেমকে, অথবা একটি Wear OS ঘড়িকে অ্যাক্সেস দিতে চাইতে পারেন, যাতে আপনি ওয়াচফেস থেকেই প্লেব্যাক নিয়ন্ত্রণ করতে পারেন। বাহ্যিক ক্লায়েন্টরা আপনার মিডিয়া অ্যাপে প্লেব্যাক কমান্ড পাঠানোর জন্য একটি মিডিয়া কন্ট্রোলার ব্যবহার করতে পারে। এই কমান্ডগুলো আপনার মিডিয়া সেশন গ্রহণ করে, যা শেষ পর্যন্ত মিডিয়া প্লেয়ারকে কমান্ডগুলো অর্পণ করে।

যখন কোনো কন্ট্রোলার আপনার মিডিয়া সেশনে সংযোগ স্থাপন করতে যায়, তখন onConnect() মেথডটি কল করা হয়। অনুরোধটি গ্রহণ করবেন নাকি প্রত্যাখ্যান করবেন , তা ঠিক করার জন্য আপনি প্রদত্ত ControllerInfo ব্যবহার করতে পারেন। কাস্টম কমান্ড ঘোষণা (Declare custom commands) অংশে একটি সংযোগ অনুরোধ গ্রহণ করার উদাহরণ দেখুন।
সংযোগ স্থাপনের পর, একটি কন্ট্রোলার সেশনে প্লেব্যাক কমান্ড পাঠাতে পারে। এরপর সেশন সেই কমান্ডগুলো প্লেয়ারের কাছে হস্তান্তর করে। Player ইন্টারফেসে সংজ্ঞায়িত প্লেব্যাক এবং প্লেলিস্ট কমান্ডগুলো সেশন দ্বারা স্বয়ংক্রিয়ভাবে পরিচালিত হয়।
অন্যান্য কলব্যাক মেথডগুলো আপনাকে, উদাহরণস্বরূপ, কাস্টম কমান্ডের অনুরোধ এবং প্লেলিস্ট পরিবর্তনের মতো বিষয়গুলো পরিচালনা করার সুযোগ দেয়। এই কলব্যাকগুলোতেও একইভাবে একটি ControllerInfo অবজেক্ট অন্তর্ভুক্ত থাকে, যাতে আপনি প্রতিটি অনুরোধের প্রতিক্রিয়া কন্ট্রোলার-ভিত্তিক পরিবর্তন করতে পারেন।
প্লেলিস্ট পরিবর্তন করুন
এক্সোপ্লেয়ারের প্লেলিস্ট বিষয়ক নির্দেশিকায় যেমন ব্যাখ্যা করা হয়েছে, একটি মিডিয়া সেশন সরাসরি তার প্লেয়ারের প্লেলিস্ট পরিবর্তন করতে পারে। কন্ট্রোলারগুলোও প্লেলিস্ট পরিবর্তন করতে পারে, যদি কন্ট্রোলারের জন্য COMMAND_SET_MEDIA_ITEM অথবা COMMAND_CHANGE_MEDIA_ITEMS যেকোনো একটি উপলব্ধ থাকে।
প্লেলিস্টে নতুন আইটেম যোগ করার সময়, সেগুলোকে প্লে করার জন্য প্লেয়ারের সাধারণত একটি নির্দিষ্ট URI সহ MediaItem ইনস্ট্যান্সের প্রয়োজন হয়। ডিফল্টরূপে, নতুন যোগ করা আইটেমগুলির যদি একটি URI সংজ্ঞায়িত থাকে, তবে সেগুলি স্বয়ংক্রিয়ভাবে player.addMediaItem মতো প্লেয়ার মেথডগুলিতে ফরোয়ার্ড করা হয়।
আপনি যদি প্লেয়ারে যুক্ত MediaItem ইনস্ট্যান্সগুলো কাস্টমাইজ করতে চান, তাহলে আপনি onAddMediaItems() ওভাররাইড করতে পারেন। এই ধাপটি তখন প্রয়োজন হয় যখন আপনি এমন কন্ট্রোলারদের সমর্থন করতে চান যারা কোনো নির্দিষ্ট URI ছাড়াই মিডিয়ার জন্য অনুরোধ করে। এর পরিবর্তে, অনুরোধ করা মিডিয়া বর্ণনা করার জন্য MediaItem সাধারণত নিম্নলিখিত এক বা একাধিক ফিল্ড সেট করা থাকে:
-
MediaItem.id: মিডিয়া শনাক্তকারী একটি সাধারণ আইডি। -
MediaItem.RequestMetadata.mediaUri: একটি অনুরোধ URI যা একটি কাস্টম স্কিমা ব্যবহার করতে পারে এবং যা প্লেয়ার দ্বারা সরাসরি প্লেযোগ্য নাও হতে পারে। -
MediaItem.RequestMetadata.searchQuery: একটি পাঠ্য অনুসন্ধান প্রশ্ন, উদাহরণস্বরূপ গুগল অ্যাসিস্ট্যান্ট থেকে। -
MediaItem.MediaMetadata: 'শিরোনাম' বা 'শিল্পী'-এর মতো কাঠামোগত মেটাডেটা।
সম্পূর্ণ নতুন প্লেলিস্টের জন্য আরও কাস্টমাইজেশন বিকল্প পেতে, আপনি অতিরিক্তভাবে onSetMediaItems() ওভাররাইড করতে পারেন, যা আপনাকে প্লেলিস্টের শুরুর আইটেম এবং অবস্থান নির্ধারণ করার সুযোগ দেয়। উদাহরণস্বরূপ, আপনি অনুরোধ করা একটি আইটেমকে পুরো প্লেলিস্টে প্রসারিত করতে পারেন এবং প্লেয়ারকে মূলত অনুরোধ করা আইটেমের ইন্ডেক্স থেকে শুরু করার নির্দেশ দিতে পারেন। এই বৈশিষ্ট্যসহ onSetMediaItems() এর একটি নমুনা ইমপ্লিমেন্টেশন সেশন ডেমো অ্যাপে পাওয়া যাবে।
মিডিয়া বোতামের পছন্দগুলি পরিচালনা করুন
প্রতিটি কন্ট্রোলার, যেমন সিস্টেম UI, অ্যান্ড্রয়েড অটো, বা ওয়্যার ওএস, ব্যবহারকারীকে কোন বাটনগুলো দেখানো হবে সে বিষয়ে নিজস্ব সিদ্ধান্ত নিতে পারে। ব্যবহারকারীর কাছে কোন প্লেব্যাক কন্ট্রোলগুলো প্রকাশ করতে চান তা নির্দেশ করার জন্য, আপনি MediaSession এ মিডিয়া বাটন প্রেফারেন্স নির্দিষ্ট করতে পারেন। এই প্রেফারেন্সগুলো CommandButton ইনস্ট্যান্সের একটি ক্রমিক তালিকা নিয়ে গঠিত, যার প্রতিটি ইউজার ইন্টারফেসের একটি বাটনের জন্য একটি প্রেফারেন্স নির্ধারণ করে।
কমান্ড বোতামগুলি সংজ্ঞায়িত করুন
মিডিয়া বাটনের পছন্দসমূহ নির্ধারণ করতে CommandButton ইনস্ট্যান্স ব্যবহার করা হয়। প্রতিটি বাটন কাঙ্ক্ষিত UI এলিমেন্টের তিনটি দিক নির্ধারণ করে:
- আইকন , যা এর বাহ্যিক রূপ নির্ধারণ করে। একটি
CommandButton.Builderতৈরি করার সময় আইকনটিকে অবশ্যই পূর্বনির্ধারিত কনস্ট্যান্টগুলোর মধ্যে একটিতে সেট করতে হবে। মনে রাখবেন, এটি কোনো প্রকৃত বিটম্যাপ বা ইমেজ রিসোর্স নয়। একটি জেনেরিক কনস্ট্যান্ট কন্ট্রোলারদেরকে তাদের নিজস্ব UI-এর মধ্যে একটি সামঞ্জস্যপূর্ণ চেহারা ও অনুভূতি বজায় রাখার জন্য উপযুক্ত রিসোর্স বেছে নিতে সাহায্য করে। যদি পূর্বনির্ধারিত কোনো আইকন কনস্ট্যান্ট আপনার ব্যবহারের ক্ষেত্রে উপযুক্ত না হয়, তবে আপনি এর পরিবর্তেsetCustomIconResIdব্যবহার করতে পারেন। - কমান্ড হলো সেই ক্রিয়া যা ব্যবহারকারী বাটনটির সাথে ইন্টারঅ্যাক্ট করলে সংঘটিত হয়। আপনি
Player.Commandএর জন্যsetPlayerCommand, অথবা পূর্বনির্ধারিত বা কাস্টমSessionCommandজন্যsetSessionCommandব্যবহার করতে পারেন। - স্লট , যা নির্ধারণ করে কন্ট্রোলার UI-তে বাটনটি কোথায় স্থাপন করা হবে। এই ফিল্ডটি ঐচ্ছিক এবং আইকন ও কমান্ডের উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে সেট হয়ে যায়। উদাহরণস্বরূপ, এটি নির্দিষ্ট করে দেয় যে একটি বাটন ডিফল্ট 'ওভারফ্লো' এলাকার পরিবর্তে UI-এর 'ফরোয়ার্ড' নেভিগেশন এলাকায় প্রদর্শিত হবে।
কোটলিন
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();
যখন মিডিয়া বাটনের পছন্দগুলি নিষ্পত্তি করা হয়, তখন নিম্নলিখিত অ্যালগরিদমটি প্রয়োগ করা হয়:
- মিডিয়া বাটন প্রেফারেন্সে থাকা প্রতিটি
CommandButtonজন্য, বাটনটিকে প্রথম উপলব্ধ এবং অনুমোদিত স্লটে রাখুন। - যদি কেন্দ্রীয়, সম্মুখ এবং পশ্চাৎ স্লটগুলোর কোনোটিতে বাটন না থাকে, তাহলে সেই স্লটের জন্য ডিফল্ট বাটন যোগ করুন।
UI ডিসপ্লে সীমাবদ্ধতার উপর নির্ভর করে মিডিয়া বাটনের পছন্দগুলো কীভাবে নির্ধারিত হবে, তার একটি প্রিভিউ তৈরি করতে আপনি CommandButton.DisplayConstraints ব্যবহার করতে পারেন।
মিডিয়া বোতামের পছন্দ সেট করুন
মিডিয়া বাটন প্রেফারেন্স সেট করার সবচেয়ে সহজ উপায় হলো MediaSession তৈরি করার সময় তালিকাটি নির্ধারণ করে দেওয়া। বিকল্পভাবে, আপনি প্রতিটি সংযুক্ত কন্ট্রোলারের জন্য মিডিয়া বাটন প্রেফারেন্স কাস্টমাইজ করতে MediaSession.Callback.onConnect ওভাররাইড করতে পারেন।
কোটলিন
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 ব্যবহার করে সমস্ত কন্ট্রোলারের অথবা একটি নির্দিষ্ট কন্ট্রোলারের প্রেফারেন্স আপডেট করতে পারেন।
কোটলিন
// 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() ওভাররাইড করতে হবে।
কোটলিন
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).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).setAvailableSessionCommands(sessionCommands).build()); } }
একটি MediaController থেকে কাস্টম কমান্ড অনুরোধ গ্রহণ করতে, Callback মধ্যে onCustomCommand() মেথডটি ওভাররাইড করুন।
কোটলিন
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 প্রপার্টি ব্যবহার করে আপনি ট্র্যাক করতে পারেন কোন মিডিয়া কন্ট্রোলার অনুরোধটি করছে। এর ফলে, কোনো কমান্ড সিস্টেম, আপনার নিজের অ্যাপ বা অন্য ক্লায়েন্ট অ্যাপ থেকে আসলে, তার প্রতিক্রিয়ায় আপনি আপনার অ্যাপের আচরণকে প্রয়োজন অনুযায়ী সাজিয়ে নিতে পারেন।
ডিফল্ট প্লেয়ার কমান্ডগুলি কাস্টমাইজ করুন
সমস্ত ডিফল্ট কমান্ড এবং স্টেট হ্যান্ডলিং MediaSession এ থাকা Player এর উপর ন্যস্ত থাকে। Player ইন্টারফেসে সংজ্ঞায়িত কোনো কমান্ডের, যেমন play() বা seekToNext() , আচরণ কাস্টমাইজ করতে, আপনার Player MediaSession এ পাঠানোর আগে একটি ForwardingSimpleBasePlayer মধ্যে র্যাপ করুন:
কোটলিন
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 সংগ্রহ করতে পারেন:
কোটলিন
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); } }
মিডিয়া বাটন হ্যান্ডলিং কাস্টমাইজ করুন
মিডিয়া বাটন হলো অ্যান্ড্রয়েড ডিভাইস এবং অন্যান্য পেরিফেরাল ডিভাইসে থাকা হার্ডওয়্যার বাটন, যেমন ব্লুটুথ হেডসেটের প্লে/পজ বাটন। সেশনে মিডিয়া বাটন ইভেন্টগুলো এলে Media3 আপনার হয়ে সেগুলো পরিচালনা করে এবং সেশন প্লেয়ারের উপযুক্ত Player মেথডটিকে কল করে।
আগত সমস্ত মিডিয়া বাটন ইভেন্ট সংশ্লিষ্ট Player মেথডে হ্যান্ডেল করার পরামর্শ দেওয়া হয়। আরও উন্নত ব্যবহারের ক্ষেত্রে, MediaSession.Callback.onMediaButtonEvent(Intent) -এর মধ্যে মিডিয়া বাটন ইভেন্টগুলো ইন্টারসেপ্ট করা যেতে পারে।
ত্রুটি পরিচালনা এবং প্রতিবেদন
একটি সেশন দুই ধরনের ত্রুটি তৈরি করে এবং কন্ট্রোলারদের কাছে রিপোর্ট করে। মারাত্মক ত্রুটি (Fatal errors) হলো সেশন প্লেয়ারের একটি প্রযুক্তিগত প্লেব্যাক ব্যর্থতা, যা প্লেব্যাককে বাধাগ্রস্ত করে। মারাত্মক ত্রুটিগুলো ঘটার সাথে সাথে স্বয়ংক্রিয়ভাবে কন্ট্রোলারের কাছে রিপোর্ট করা হয়। অমারাত্মক ত্রুটি (Nonfatal errors) হলো প্রযুক্তিগত নয় এমন বা পলিসি-সংক্রান্ত ত্রুটি, যা প্লেব্যাকে বাধা দেয় না এবং অ্যাপ্লিকেশন দ্বারা ম্যানুয়ালি কন্ট্রোলারদের কাছে পাঠানো হয়।
মারাত্মক প্লেব্যাক ত্রুটি
একটি মারাত্মক প্লেব্যাক ত্রুটি প্লেয়ার দ্বারা সেশনে রিপোর্ট করা হয় এবং তারপরে 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 ব্যবহার করে এটি করা সম্ভব:
কোটলিন
val session = MediaSession.Builder(context, ErrorForwardingPlayer(context, player)).build()
জাভা
MediaSession session = new MediaSession.Builder(context, new ErrorForwardingPlayer(context, player)).build();
ফরওয়ার্ডিং প্লেয়ারটি ত্রুটি শনাক্ত করতে এবং এরর কোড, মেসেজ বা অতিরিক্ত বিষয়গুলো কাস্টমাইজ করতে ForwardingSimpleBasePlayer ব্যবহার করতে পারে। একইভাবে, আপনি এমন নতুন ত্রুটিও তৈরি করতে পারেন যা মূল প্লেয়ারে নেই:
কোটলিন
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); } }
মারাত্মক নয় এমন ত্রুটি
যেসব মারাত্মক নয় এমন ত্রুটি কোনো প্রযুক্তিগত ব্যতিক্রম থেকে উদ্ভূত হয় না , সেগুলো একটি অ্যাপ থেকে সমস্ত কন্ট্রোলারে অথবা একটি নির্দিষ্ট কন্ট্রোলারে পাঠানো যেতে পারে:
কোটলিন
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 ইমপ্লিমেন্ট করার মাধ্যমে একটি নন-ফেটাল এরর গ্রহণ করে:
কোটলিন
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. } });