অডিও ফোকাস ম্যানেজ করুন

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

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

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

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

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

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

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

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

Android-এর যে ভার্সন চলছে তার উপর নির্ভর করে অডিও ফোকাস আলাদাভাবে ম্যানেজ করা হয়:

Android 12 (API লেভেল 31) বা তার পরবর্তী যেকোনও ভার্সন
সিস্টেমের মাধ্যমে অডিও ফোকাস ম্যানেজ করা হয়। অন্য কোনও অ্যাপ অডিও ফোকাসের অনুরোধ করলে, সিস্টেম কোনও অ্যাপ থেকে অডিও প্লেব্যাককে ফেড আউট করতে বাধ্য করে। এছাড়াও, ইনকামিং কল এলে সিস্টেম অডিও প্লেব্যাক মিউট করে দেয়।
Android 8.0 (API লেভেল ২৬) থেকে Android 11 (API লেভেল ৩০)
অডিও ফোকাস সিস্টেম দ্বারা ম্যানেজ করা হয় না, তবে এতে কিছু পরিবর্তন অন্তর্ভুক্ত রয়েছে যা Android 8.0 (API লেভেল ২৬) থেকে শুরু করে চালু করা হয়েছে।
Android 7.1 (API লেভেল ২৫) এবং তার আগের যেকোনও ভার্সন
সিস্টেমের মাধ্যমে অডিও ফোকাস ম্যানেজ করা হয় না এবং অ্যাপ requestAudioFocus() এবং abandonAudioFocus() ব্যবহার করে অডিও ফোকাস ম্যানেজ করে।

Android 12 ও তার পরবর্তী যেকোনও ভার্সনে অডিও ফোকাস

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

নিম্নলিখিত শর্ত পূরণ হলে এই ফেড আউট আচরণ ঘটে:

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

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

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

আগে থেকে থাকা অডিও ফোকাস সংক্রান্ত আচরণ

এছাড়াও, অডিও ফোকাস পরিবর্তন করার সাথে জড়িত অন্যান্য পরিস্থিতি সম্পর্কেও আপনাকে সচেতন থাকতে হবে।

অটোমেটিক ডাকিং

অটোমেটিক ডাকিং (সাময়িকভাবে একটি অ্যাপের অডিও লেভেল কমানো যাতে অন্যটি স্পষ্টভাবে শোনা যায়) Android 8.0 (API লেভেল ২৬)-এ চালু করা হয়েছে।

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

এছাড়াও, কোনও অডিও বিজ্ঞপ্তি কোনও অ্যাপ থেকে ফোকাস সরিয়ে নিলে অটোমেটিক ডাকিং হয়। বিজ্ঞপ্তি প্লেব্যাক শুরু হওয়ার সময় ডাকিং র‍্যাম্প শেষ হওয়ার সময়ের সাথে সিঙ্ক করা হয়।

নিম্নলিখিত শর্ত পূরণ হলে অটোমেটিক ডাকিং হয়:

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

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

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

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

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

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

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

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

Android 8.0 থেকে Android 11-এ অডিও ফোকাস

Android 8.0 (API লেভেল ২৬) থেকে শুরু করে, আপনি যখন requestAudioFocus() কল করেন, তখন আপনাকে অবশ্যই একটি AudioFocusRequest প্যারামিটার প্রদান করতে হবে। AudioFocusRequest-এ আপনার অ্যাপের অডিও প্রসঙ্গ এবং ক্ষমতা সম্পর্কে তথ্য রয়েছে। সিস্টেম এই তথ্য ব্যবহার করে অডিও ফোকাসের লাভ এবং ক্ষতি স্বয়ংক্রিয়ভাবে ম্যানেজ করে। অডিও ফোকাস রিলিজ করতে, abandonAudioFocusRequest() মেথড কল করুন, যা আর্গুমেন্ট হিসেবে AudioFocusRequest-কেও নেয়। আপনি যখন ফোকাস করার অনুরোধ করেন এবং ফোকাস করা বন্ধ করেন, উভয় ক্ষেত্রেই একই AudioFocusRequest ইনস্ট্যান্স ব্যবহার করুন।

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

FocusGain ফিল্ডটি পূরণ করতে হবে; বাকি সব ফিল্ড ঐচ্ছিক।

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

প্রথমে অ্যাট্রিবিউট নির্দিষ্ট করতে 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 তৈরি করা যায় এবং অডিও ফোকাসের জন্য অনুরোধ করা ও তা ছেড়ে দেওয়া যায়:

Kotlin

// 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;
        }
    }
}

অটোমেটিক ডাকিং

Android 8.0 (API লেভেল ২৬)-এ, অন্য কোনও অ্যাপ AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK-এর মাধ্যমে ফোকাস করার অনুরোধ করলে, সিস্টেম অ্যাপের onAudioFocusChange() কলব্যাক চালু না করেই ভলিউম কমাতে এবং রিস্টোর করতে পারে।

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

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

বিলম্বিত ফোকাস গেইন

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

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

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

Android 7.1 এবং তার আগের যেকোনও ভার্সনে অডিও ফোকাস

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

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

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

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

Kotlin

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() নম্বরে কল করুন।

Kotlin

audioManager.abandonAudioFocus(afChangeListener)

জাভা

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

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

অডিও ফোকাস পরিবর্তনের উত্তর দেওয়া হচ্ছে

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

onAudioFocusChange()-এ পাস করা focusChange প্যারামিটারটি কী ধরনের পরিবর্তন হচ্ছে তা নির্দেশ করে। এটি ফোকাস অর্জনকারী অ্যাপের ব্যবহার করা সময়সীমা সংক্রান্ত হিন্টের সাথে সম্পর্কিত। আপনার অ্যাপকে যথাযথভাবে উত্তর দিতে হবে।

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

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

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

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

Kotlin

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 ব্যবহার করে যা দেখতে এইরকম:

Kotlin

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() কলব্যাকেও এই পদ্ধতি কল করা উচিত।