অডিও ফোকাস পরিচালনা করুন

দুই বা ততোধিক অ্যান্ড্রয়েড অ্যাপ একই সাথে একই আউটপুট স্ট্রিমে অডিও চালাতে পারে এবং সিস্টেম সবকিছু একসাথে মিশিয়ে ফেলে। যদিও এটি প্রযুক্তিগতভাবে চমৎকার, তবে এটি একজন ব্যবহারকারীর জন্য খুবই বিরক্তিকর হতে পারে। সব মিউজিক অ্যাপ যাতে একই সময়ে না বাজে, তা এড়াতে অ্যান্ড্রয়েড ‘অডিও ফোকাস’ ধারণাটি নিয়ে এসেছে। একবারে শুধুমাত্র একটি অ্যাপই অডিও ফোকাস ধরে রাখতে পারে।

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

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

একটি সু-পরিকল্পিত অডিও অ্যাপের নিম্নলিখিত সাধারণ নির্দেশিকা অনুসারে অডিও ফোকাস পরিচালনা করা উচিত:

  • প্লে করা শুরু করার ঠিক আগে requestAudioFocus() কল করুন এবং যাচাই করুন যে কলটি AUDIOFOCUS_REQUEST_GRANTED রিটার্ন করছে। আপনার মিডিয়া সেশনের onPlay() কলব্যাকে requestAudioFocus() কলটি করুন।

  • যখন অন্য কোনো অ্যাপ অডিও ফোকাস পায়, তখন প্লে করা বন্ধ বা পজ করুন, অথবা ভলিউম কমিয়ে দিন (অর্থাৎ, ভলিউম কমিয়ে দিন)।

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

  • আপনার অ্যাপটি কোন ধরনের অডিও প্লে করছে তা বর্ণনা করতে AudioAttributes ব্যবহার করুন। উদাহরণস্বরূপ, যে অ্যাপগুলো স্পিচ প্লে করে, সেগুলোর জন্য CONTENT_TYPE_SPEECH উল্লেখ করুন।

অ্যান্ড্রয়েডের কোন সংস্করণ চলছে তার উপর নির্ভর করে অডিও ফোকাস ভিন্নভাবে পরিচালিত হয়:

অ্যান্ড্রয়েড ১২ (এপিআই লেভেল ৩১) বা তার পরবর্তী সংস্করণ
অডিও ফোকাস সিস্টেম দ্বারা পরিচালিত হয়। যখন অন্য কোনো অ্যাপ অডিও ফোকাসের জন্য অনুরোধ করে, তখন সিস্টেম একটি অ্যাপের অডিও প্লেব্যাককে ধীরে ধীরে বন্ধ করে দেয়। এছাড়াও, ইনকামিং কল এলে সিস্টেম অডিও প্লেব্যাক মিউট করে দেয়।
অ্যান্ড্রয়েড ৮.০ (এপিআই লেভেল ২৬) থেকে অ্যান্ড্রয়েড ১১ (এপিআই লেভেল ৩০) পর্যন্ত
অডিও ফোকাস সিস্টেম দ্বারা পরিচালিত হয় না, তবে এতে অ্যান্ড্রয়েড ৮.০ (এপিআই লেভেল ২৬) থেকে প্রবর্তিত কিছু পরিবর্তন অন্তর্ভুক্ত করা হয়েছে।
অ্যান্ড্রয়েড ৭.১ (এপিআই লেভেল ২৫) এবং এর নিচের সংস্করণ
অডিও ফোকাস সিস্টেম দ্বারা পরিচালিত হয় না, এবং অ্যাপগুলো requestAudioFocus()abandonAudioFocus() ব্যবহার করে অডিও ফোকাস নিয়ন্ত্রণ করে।

অ্যান্ড্রয়েড ১২ এবং তার পরবর্তী সংস্করণগুলিতে অডিও ফোকাস

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

নিম্নলিখিত শর্তগুলো পূরণ হলে এই ধীরে ধীরে মিলিয়ে যাওয়ার আচরণটি ঘটে:

  1. প্রথম, বর্তমানে চালু থাকা অ্যাপটি এই সমস্ত শর্ত পূরণ করে:

  2. একটি দ্বিতীয় অ্যাপ AudioManager.AUDIOFOCUS_GAIN ব্যবহার করে অডিও ফোকাসের জন্য অনুরোধ করে।

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

বিদ্যমান অডিও ফোকাস আচরণ

অডিও ফোকাসের পরিবর্তনের সাথে জড়িত এই অন্যান্য বিষয়গুলো সম্পর্কেও আপনার সচেতন থাকা উচিত।

স্বয়ংক্রিয় হাঁস

অটোমেটিক ডাকিং (একটি অ্যাপের অডিও লেভেল সাময়িকভাবে কমিয়ে দেওয়া, যাতে অন্য অ্যাপটি স্পষ্টভাবে শোনা যায়) অ্যান্ড্রয়েড ৮.০ (এপিআই লেভেল ২৬)-এ চালু করা হয়েছিল।

সিস্টেমে ডাকিং প্রয়োগ করা থাকলে, আপনাকে আপনার অ্যাপে ডাকিং প্রয়োগ করতে হবে না।

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

নিম্নলিখিত শর্তগুলো পূরণ হলে স্বয়ংক্রিয়ভাবে মাথা নিচু করা ঘটে:

  1. প্রথম, বর্তমানে চালু থাকা অ্যাপটি এই সমস্ত শর্ত পূরণ করে:

    • অ্যাপটি যেকোনো ধরনের ফোকাস গেইন সহ অডিও ফোকাসের জন্য সফলভাবে অনুরোধ করেছে।
    • অ্যাপটি AudioAttributes.CONTENT_TYPE_SPEECH কন্টেন্ট টাইপের অডিও প্লে করছে না।
    • অ্যাপটি AudioFocusRequest.Builder.setWillPauseWhenDucked(true) সেট করেনি।
  2. একটি দ্বিতীয় অ্যাপ AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ব্যবহার করে অডিও ফোকাসের জন্য অনুরোধ করে।

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

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

ইনকামিং ফোন কলের জন্য বর্তমান অডিও প্লেব্যাক মিউট করুন

কিছু অ্যাপ ঠিকমতো কাজ করে না এবং ফোন কলের সময়ও অডিও চলতে থাকে। এই পরিস্থিতিতে ব্যবহারকারীকে কলটি শোনার জন্য সমস্যা সৃষ্টিকারী অ্যাপটি খুঁজে বের করে মিউট বা বন্ধ করতে হয়। এটি প্রতিরোধ করার জন্য, ইনকামিং কল আসার সময় সিস্টেম অন্যান্য অ্যাপের অডিও মিউট করে দিতে পারে। যখন একটি ইনকামিং ফোন কল আসে এবং কোনো অ্যাপ নিম্নলিখিত শর্তগুলো পূরণ করে, তখন সিস্টেম এই ফিচারটি চালু করে:

  • অ্যাপটিতে AudioAttributes.USAGE_MEDIA অথবা AudioAttributes.USAGE_GAME ইউসেজ অ্যাট্রিবিউট রয়েছে।
  • অ্যাপটি সফলভাবে অডিও ফোকাসের (যেকোনো ফোকাস গেইন) জন্য অনুরোধ করেছে এবং অডিও প্লে করছে।

কল চলাকালীন কোনো অ্যাপ চলতে থাকলে, কল শেষ না হওয়া পর্যন্ত সেটির প্লেব্যাক মিউট হয়ে যায়। তবে, কল চলাকালীন কোনো অ্যাপ চালু হলে, সেই প্লেয়ারটি মিউট হয় না, এই ধরে নিয়ে যে ব্যবহারকারী ইচ্ছাকৃতভাবে প্লেব্যাক শুরু করেছেন।

অ্যান্ড্রয়েড ৮.০ থেকে অ্যান্ড্রয়েড ১১ পর্যন্ত অডিও ফোকাস

অ্যান্ড্রয়েড ৮.০ (এপিআই লেভেল ২৬) থেকে শুরু করে, যখন আপনি requestAudioFocus() কল করবেন, তখন আপনাকে অবশ্যই একটি AudioFocusRequest প্যারামিটার সরবরাহ করতে হবে। AudioFocusRequest এ আপনার অ্যাপের অডিও কনটেক্সট এবং ক্যাপাবিলিটি সম্পর্কিত তথ্য থাকে। সিস্টেম এই তথ্য ব্যবহার করে স্বয়ংক্রিয়ভাবে অডিও ফোকাসের লাভ ও ক্ষতি পরিচালনা করে। অডিও ফোকাস রিলিজ করতে, abandonAudioFocusRequest() মেথডটি কল করুন, যা আর্গুমেন্ট হিসেবে একটি AudioFocusRequest গ্রহণ করে। ফোকাস অনুরোধ এবং পরিত্যাগ—উভয় ক্ষেত্রেই একই AudioFocusRequest ইনস্ট্যান্স ব্যবহার করুন।

একটি AudioFocusRequest তৈরি করতে, AudioFocusRequest.Builder ব্যবহার করুন। যেহেতু একটি ফোকাস রিকোয়েস্টে সর্বদা রিকোয়েস্টের ধরন উল্লেখ করতে হয়, তাই বিল্ডারের কনস্ট্রাক্টরে ধরনটি অন্তর্ভুক্ত করা থাকে। রিকোয়েস্টের অন্যান্য ফিল্ডগুলো সেট করতে বিল্ডারের মেথডগুলো ব্যবহার করুন।

FocusGain ফিল্ডটি আবশ্যক; বাকি সব ফিল্ড ঐচ্ছিক।

পদ্ধতি নোট
setFocusGain() প্রতিটি অনুরোধে এই ফিল্ডটি আবশ্যক। এটি requestAudioFocus() ফাংশনের প্রি-অ্যান্ড্রয়েড ৮.০ সংস্করণে ব্যবহৃত ` durationHint মতোই মান গ্রহণ করে: AUDIOFOCUS_GAIN , AUDIOFOCUS_GAIN_TRANSIENT , AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK , অথবা AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
setAudioAttributes() AudioAttributes আপনার অ্যাপের জন্য এর ব্যবহারের ক্ষেত্র বর্ণনা করে। যখন কোনো অ্যাপ অডিও ফোকাস পায় বা হারায়, তখন সিস্টেম এগুলো দেখে। অ্যাট্রিবিউটগুলো স্ট্রিম টাইপের ধারণাকে প্রতিস্থাপন করে। অ্যান্ড্রয়েড ৮.০ (এপিআই লেভেল ২৬) এবং তার পরবর্তী সংস্করণগুলোতে, ভলিউম কন্ট্রোল ছাড়া অন্য যেকোনো অপারেশনের জন্য স্ট্রিম টাইপ অপ্রচলিত (deprecated) করা হয়েছে। ফোকাস রিকোয়েস্টে সেই একই অ্যাট্রিবিউটগুলো ব্যবহার করুন যা আপনি আপনার অডিও প্লেয়ারে ব্যবহার করেন (এই টেবিলের পরের উদাহরণে যেমন দেখানো হয়েছে)।

প্রথমে অ্যাট্রিবিউটগুলো নির্দিষ্ট করার জন্য AudioAttributes.Builder ব্যবহার করুন, তারপর রিকোয়েস্টে অ্যাট্রিবিউটগুলো অ্যাসাইন করতে এই মেথডটি ব্যবহার করুন।

নির্দিষ্ট করে না দেওয়া হলে, AudioAttributes ডিফল্টরূপে AudioAttributes.USAGE_MEDIA হিসেবে গণ্য হয়।

setWillPauseWhenDucked() যখন অন্য কোনো অ্যাপ AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ব্যবহার করে ফোকাসের জন্য অনুরোধ করে, তখন যে অ্যাপটিতে ফোকাস থাকে সেটি সাধারণত onAudioFocusChange() কলব্যাক পায় না, কারণ সিস্টেম নিজেই ভলিউম কমানো-বাড়ানোর কাজটি করতে পারে। ভলিউম কমানো-বাড়ানোর পরিবর্তে যখন প্লেব্যাক থামানোর প্রয়োজন হয়, তখন setWillPauseWhenDucked(true) কল করুন এবং অটোমেটিক ডাকিং- এ বর্ণিত পদ্ধতি অনুযায়ী একটি OnAudioFocusChangeListener তৈরি ও সেট করুন।
setAcceptsDelayedFocusGain() অন্য কোনো অ্যাপ দ্বারা ফোকাস লক করা থাকলে অডিও ফোকাসের অনুরোধ ব্যর্থ হতে পারে। এই পদ্ধতিটি বিলম্বিত ফোকাস লাভ সক্ষম করে: অর্থাৎ, ফোকাস উপলব্ধ হওয়ার সাথে সাথে অ্যাসিঙ্ক্রোনাসভাবে তা অর্জন করার ক্ষমতা।

মনে রাখবেন যে, বিলম্বিত ফোকাস লাভ তখনই কাজ করে যদি আপনি অডিও অনুরোধে একটি AudioManager.OnAudioFocusChangeListener ও নির্দিষ্ট করেন, কারণ ফোকাস মঞ্জুর হয়েছে কিনা তা জানার জন্য আপনার অ্যাপকে কলব্যাকটি গ্রহণ করতে হয়।

setOnAudioFocusChangeListener() অনুরোধে যদি willPauseWhenDucked(true) অথবা setAcceptsDelayedFocusGain(true) উল্লেখ করা থাকে, শুধুমাত্র তখনই একটি OnAudioFocusChangeListener প্রয়োজন হবে।

লিসেনার সেট করার দুটি পদ্ধতি আছে: একটি হ্যান্ডলার আর্গুমেন্ট সহ এবং অন্যটি হ্যান্ডলার আর্গুমেন্ট ছাড়া। হ্যান্ডলার হলো সেই থ্রেড যেখানে লিসেনারটি চলে। যদি আপনি কোনো হ্যান্ডলার নির্দিষ্ট না করেন, তবে মূল Looper সাথে যুক্ত হ্যান্ডলারটি ব্যবহৃত হয়।

নিম্নলিখিত উদাহরণটি দেখায় কিভাবে একটি AudioFocusRequest.Builder ব্যবহার করে একটি AudioFocusRequest তৈরি করতে হয় এবং অডিও ফোকাসের জন্য অনুরোধ ও তা পরিত্যাগ করতে হয়:

কোটলিন

// initializing variables for audio focus and playback management
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
    setAudioAttributes(AudioAttributes.Builder().run {
        setUsage(AudioAttributes.USAGE_GAME)
        setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        build()
    })
    setAcceptsDelayedFocusGain(true)
    setOnAudioFocusChangeListener(afChangeListener, handler)
    build()
}
val focusLock = Any()

var playbackDelayed = false
var playbackNowAuthorized = false

// requesting audio focus and processing the response
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
    playbackNowAuthorized = when (res) {
        AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false
        AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
            playbackNow()
            true
        }
        AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> {
            playbackDelayed = true
            false
        }
        else -> false
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
override fun onAudioFocusChange(focusChange: Int) {
    when (focusChange) {
        AudioManager.AUDIOFOCUS_GAIN ->
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false
                    resumeOnFocusGain = false
                }
                playbackNow()
            }
        AudioManager.AUDIOFOCUS_LOSS -> {
            synchronized(focusLock) {
                resumeOnFocusGain = false
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying()
                playbackDelayed = false
            }
            pausePlayback()
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // ... pausing or ducking depends on your app
        }
    }
}

জাভা

// initializing variables for audio focus and playback management
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);
playbackAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_GAME)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
        .setAudioAttributes(playbackAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(afChangeListener, handler)
        .build();
final Object focusLock = new Object();

boolean playbackDelayed = false;
boolean playbackNowAuthorized = false;

// requesting audio focus and processing the response
int res = audioManager.requestAudioFocus(focusRequest);
synchronized(focusLock) {
    if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
        playbackNowAuthorized = false;
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        playbackNowAuthorized = true;
        playbackNow();
    } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
        playbackDelayed = true;
        playbackNowAuthorized = false;
    }
}

// implementing OnAudioFocusChangeListener to react to focus changes
@Override
public void onAudioFocusChange(int focusChange) {
    switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            if (playbackDelayed || resumeOnFocusGain) {
                synchronized(focusLock) {
                    playbackDelayed = false;
                    resumeOnFocusGain = false;
                }
                playbackNow();
            }
            break;
        case AudioManager.AUDIOFOCUS_LOSS:
            synchronized(focusLock) {
                resumeOnFocusGain = false;
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            synchronized(focusLock) {
                // only resume if playback is being interrupted
                resumeOnFocusGain = isPlaying();
                playbackDelayed = false;
            }
            pausePlayback();
            break;
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
            // ... pausing or ducking depends on your app
            break;
        }
    }
}

স্বয়ংক্রিয় হাঁস

অ্যান্ড্রয়েড ৮.০ (এপিআই লেভেল ২৬)-এ, যখন অন্য কোনো অ্যাপ AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ব্যবহার করে ফোকাসের জন্য অনুরোধ করে, তখন সিস্টেম অ্যাপটির onAudioFocusChange() কলব্যাককে কল না করেই ভলিউমকে স্বাভাবিক অবস্থায় ফিরিয়ে আনতে পারে।

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

যদি আপনি চান যে আপনার অ্যাপটি ভলিউম কমানোর পরিবর্তে ডাকিং (ducking) করার জন্য বলা হলে পজ (pause) হোক, তাহলে একটি OnAudioFocusChangeListener তৈরি করুন। এর মধ্যে একটি onAudioFocusChange() কলব্যাক মেথড থাকবে যা কাঙ্ক্ষিত পজ/রিজিউম আচরণটি ইমপ্লিমেন্ট করবে। লিসেনারটি রেজিস্টার করার জন্য setOnAudioFocusChangeListener() কল করুন, এবং সিস্টেমকে অটোমেটিক ডাকিং না করে আপনার কলব্যাকটি ব্যবহার করতে বলার জন্য setWillPauseWhenDucked(true) কল করুন।

বিলম্বিত ফোকাস লাভ

কখনও কখনও সিস্টেম অডিও ফোকাসের অনুরোধ মঞ্জুর করতে পারে না, কারণ ফোকাসটি অন্য কোনো অ্যাপ দ্বারা "লক" করা থাকে, যেমন ফোন কলের সময়। এই ক্ষেত্রে, requestAudioFocus() AUDIOFOCUS_REQUEST_FAILED রিটার্ন করে। এমনটা ঘটলে, আপনার অ্যাপের অডিও প্লেব্যাক চালিয়ে যাওয়া উচিত নয়, কারণ এটি ফোকাস পায়নি।

setAcceptsDelayedFocusGain(true) মেথডটি আপনার অ্যাপকে অ্যাসিঙ্ক্রোনাসভাবে ফোকাসের জন্য একটি অনুরোধ পরিচালনা করতে দেয়। এই ফ্ল্যাগটি সেট করা থাকলে, ফোকাস লক থাকা অবস্থায় করা একটি অনুরোধ AUDIOFOCUS_REQUEST_DELAYED রিটার্ন করে। যখন অডিও ফোকাস লক করে রাখা শর্তটি আর বিদ্যমান থাকে না, যেমন একটি ফোন কল শেষ হলে, সিস্টেমটি অপেক্ষারত ফোকাসের অনুরোধটি মঞ্জুর করে এবং আপনার অ্যাপকে অবহিত করার জন্য onAudioFocusChange() কল করে।

বিলম্বিত ফোকাস লাভ সামাল দেওয়ার জন্য, আপনাকে অবশ্যই একটি OnAudioFocusChangeListener তৈরি করতে হবে, যার মধ্যে একটি onAudioFocusChange() কলব্যাক মেথড থাকবে যা কাঙ্ক্ষিত আচরণটি বাস্তবায়ন করে এবং setOnAudioFocusChangeListener() কল করার মাধ্যমে লিসেনারটি রেজিস্টার করতে হবে।

অ্যান্ড্রয়েড ৭.১ এবং এর নিচের সংস্করণগুলিতে অডিও ফোকাস

যখন আপনি requestAudioFocus() কল করেন, তখন আপনাকে অবশ্যই একটি সময়কালের ইঙ্গিত নির্দিষ্ট করতে হবে, যা বর্তমানে ফোকাস ধরে রাখা এবং প্লে করা অন্য কোনো অ্যাপ দ্বারাও গৃহীত হতে পারে:

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

requestAudioFocus() মেথডটির জন্য একটি AudioManager.OnAudioFocusChangeListener ও প্রয়োজন। এই লিসেনারটি সেই একই অ্যাক্টিভিটি বা সার্ভিসে তৈরি করা উচিত, যেটি আপনার মিডিয়া সেশনের মালিক। এটি onAudioFocusChange() কলব্যাকটি ইমপ্লিমেন্ট করে, যা আপনার অ্যাপ তখন পায় যখন অন্য কোনো অ্যাপ অডিও ফোকাস অর্জন করে বা ছেড়ে দেয়।

নিম্নলিখিত কোড স্নিপেটটি STREAM_MUSIC স্ট্রিমে স্থায়ী অডিও ফোকাসের জন্য অনুরোধ করে এবং পরবর্তী অডিও ফোকাস পরিবর্তনগুলি পরিচালনা করার জন্য একটি OnAudioFocusChangeListener রেজিস্টার করে। (এই চেঞ্জ লিসেনারটি “অডিও ফোকাস পরিবর্তনের প্রতিক্রিয়া” অংশে আলোচনা করা হয়েছে।)

কোটলিন

audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener

...
// Request audio focus for playback
val result: Int = audioManager.requestAudioFocus(
        afChangeListener,
        // Use the music stream.
        AudioManager.STREAM_MUSIC,
        // Request permanent focus.
        AudioManager.AUDIOFOCUS_GAIN
)

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

জাভা

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener afChangeListener;

...
// Request audio focus for playback
int result = audioManager.requestAudioFocus(afChangeListener,
                             // Use the music stream.
                             AudioManager.STREAM_MUSIC,
                             // Request permanent focus.
                             AudioManager.AUDIOFOCUS_GAIN);

if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    // Start playback
}

প্লেব্যাক শেষ হলে, abandonAudioFocus() কল করুন।

কোটলিন

audioManager.abandonAudioFocus(afChangeListener)

জাভা

// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);

এটি সিস্টেমকে জানিয়ে দেয় যে আপনার আর ফোকাসের প্রয়োজন নেই এবং সংশ্লিষ্ট OnAudioFocusChangeListener টিকে অনিবন্ধিত করে দেয়। আপনি যদি ট্রানজিয়েন্ট ফোকাসের অনুরোধ করে থাকেন, তবে এটি পজ বা ডাক করা কোনো অ্যাপকে জানিয়ে দেবে যে সেটি প্লে করা চালিয়ে যেতে পারে বা তার ভলিউম পুনরুদ্ধার করতে পারে।

অডিও ফোকাস পরিবর্তনের প্রতিক্রিয়া

যখন কোনো অ্যাপ অডিও ফোকাস অর্জন করে, তখন অন্য কোনো অ্যাপ নিজের জন্য অডিও ফোকাস অনুরোধ করলে সেটিকে অবশ্যই ফোকাসটি ছেড়ে দিতে সক্ষম হতে হবে। যখন এটি ঘটে, তখন আপনার অ্যাপটি requestAudioFocus() কল করার সময় নির্দিষ্ট করা AudioFocusChangeListener এর onAudioFocusChange() মেথডে একটি কল পায়।

onAudioFocusChange() ফাংশনে পাস করা focusChange প্যারামিটারটি কী ধরনের পরিবর্তন ঘটছে তা নির্দেশ করে। এটি ফোকাস অর্জনকারী অ্যাপের ব্যবহৃত সময়কাল নির্দেশকের (duration hint) সাথে সঙ্গতিপূর্ণ। আপনার অ্যাপের যথাযথভাবে সাড়া দেওয়া উচিত।

ক্ষণস্থায়ী মনোযোগের অভাব
যদি ফোকাস পরিবর্তনটি ক্ষণস্থায়ী হয় ( AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK বা AUDIOFOCUS_LOSS_TRANSIENT ), তাহলে আপনার অ্যাপটিকে (যদি আপনি স্বয়ংক্রিয় ডাকিং-এর উপর নির্ভর না করেন) হয় ডাক করতে হবে অথবা প্লে করা থামিয়ে দিতে হবে, কিন্তু অন্যথায় একই অবস্থা বজায় রাখতে হবে।

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

মনোযোগের স্থায়ী ক্ষতি
যদি অডিও ফোকাস স্থায়ীভাবে নষ্ট হয়ে যায় ( AUDIOFOCUS_LOSS ), তার মানে অন্য কোনো অ্যাপ অডিও প্লে করছে। আপনার অ্যাপের উচিত অবিলম্বে প্লেব্যাক থামিয়ে দেওয়া, কারণ এটি আর কখনও AUDIOFOCUS_GAIN কলব্যাক পাবে না। প্লেব্যাক পুনরায় শুরু করতে, ব্যবহারকারীকে একটি সুস্পষ্ট পদক্ষেপ নিতে হবে, যেমন নোটিফিকেশন বা অ্যাপ UI-তে থাকা প্লে ট্রান্সপোর্ট কন্ট্রোলটি চাপা।

নিম্নলিখিত কোড স্নিপেটটি OnAudioFocusChangeListener এবং এর onAudioFocusChange() কলব্যাক কীভাবে প্রয়োগ করতে হয় তা প্রদর্শন করে। অডিও ফোকাস স্থায়ীভাবে হারিয়ে গেলে স্টপ কলব্যাককে বিলম্বিত করার জন্য একটি Handler এর ব্যবহার লক্ষ্য করুন।

কোটলিন

private val handler = Handler()
private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
    when (focusChange) {
        AudioManager.AUDIOFOCUS_LOSS -> {
            // Permanent loss of audio focus
            // Pause playback immediately
            mediaController.transportControls.pause()
            // Wait 30 seconds before stopping playback
            handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30))
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
            // Pause playback
        }
        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
            // Lower the volume, keep playing
        }
        AudioManager.AUDIOFOCUS_GAIN -> {
            // Your app has been granted audio focus again
            // Raise volume to normal, restart playback if necessary
        }
    }
}

জাভা

private Handler handler = new Handler();
AudioManager.OnAudioFocusChangeListener afChangeListener =
  new AudioManager.OnAudioFocusChangeListener() {
    public void onAudioFocusChange(int focusChange) {
      if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
        // Permanent loss of audio focus
        // Pause playback immediately
        mediaController.getTransportControls().pause();
        // Wait 30 seconds before stopping playback
        handler.postDelayed(delayedStopRunnable,
          TimeUnit.SECONDS.toMillis(30));
      }
      else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
        // Pause playback
      } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
        // Lower the volume, keep playing
      } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
        // Your app has been granted audio focus again
        // Raise volume to normal, restart playback if necessary
      }
    }
  };

হ্যান্ডলারটি একটি Runnable ব্যবহার করে যা দেখতে এইরকম:

কোটলিন

private var delayedStopRunnable = Runnable {
    mediaController.transportControls.stop()
}

জাভা

private Runnable delayedStopRunnable = new Runnable() {
    @Override
    public void run() {
        getMediaController().getTransportControls().stop();
    }
};

ব্যবহারকারী প্লেব্যাক পুনরায় শুরু করলে বিলম্বিত স্টপ যেন কার্যকর না হয়, তা নিশ্চিত করতে যেকোনো স্টেট পরিবর্তনের প্রতিক্রিয়ায় mHandler.removeCallbacks(mDelayedStopRunnable) কল করুন। উদাহরণস্বরূপ, আপনার Callback-এর onPlay() , onSkipToNext() , ইত্যাদিতে removeCallbacks() কল করুন। আপনার সার্ভিসের ব্যবহৃত রিসোর্সগুলো পরিষ্কার করার সময় সার্ভিসের onDestroy() কলব্যাকেও এই মেথডটি কল করা উচিত।