مدیریت فوکوس صوتی

دو یا چند برنامه اندروید می‌توانند به طور همزمان صدا را در یک جریان خروجی پخش کنند و سیستم همه چیز را با هم مخلوط می‌کند. اگرچه این از نظر فنی چشمگیر است، اما می‌تواند برای کاربر بسیار آزاردهنده باشد. برای جلوگیری از پخش همزمان هر برنامه موسیقی، اندروید ایده فوکوس صوتی را معرفی می‌کند. فقط یک برنامه می‌تواند فوکوس صوتی را در یک زمان نگه دارد.

وقتی برنامه شما نیاز به خروجی صدا دارد، باید درخواست فوکوس صوتی کند. وقتی فوکوس دارد، می‌تواند صدا را پخش کند. با این حال، پس از اینکه فوکوس صوتی را به دست آوردید، ممکن است نتوانید آن را تا زمانی که بازی‌تان تمام نشده است، نگه دارید. برنامه دیگری می‌تواند درخواست فوکوس کند، که این امر مانع از نگه داشتن فوکوس صوتی توسط شما می‌شود. در این صورت، برنامه شما باید پخش را متوقف کند یا صدای آن را کاهش دهد تا کاربران بتوانند منبع صوتی جدید را راحت‌تر بشنوند.

قبل از اندروید ۱۲ (سطح API 31)، فوکوس صوتی توسط سیستم مدیریت نمی‌شد. بنابراین، اگرچه به توسعه‌دهندگان برنامه توصیه می‌شود که از دستورالعمل‌های فوکوس صوتی پیروی کنند، اما اگر یک برنامه حتی پس از از دست دادن فوکوس صوتی در دستگاهی که اندروید ۱۱ (سطح API 30) یا پایین‌تر را اجرا می‌کند، همچنان با صدای بلند پخش شود، سیستم نمی‌تواند از آن جلوگیری کند. با این حال، این رفتار برنامه منجر به یک تجربه کاربری بد می‌شود و اغلب می‌تواند کاربران را به حذف برنامه نامناسب سوق دهد.

یک برنامه صوتی خوب طراحی شده باید فوکوس صوتی را طبق این دستورالعمل‌های کلی مدیریت کند:

  • بلافاصله قبل از شروع پخش، تابع requestAudioFocus() را فراخوانی کنید و مطمئن شوید که خروجی آن AUDIOFOCUS_REQUEST_GRANTED است. تابع requestAudioFocus() را در تابع onPlay() مربوط به جلسه رسانه خود فراخوانی کنید.

  • وقتی برنامه‌ی دیگری فوکوس صوتی پیدا می‌کند، پخش را متوقف یا متوقف کنید، یا صدا را کم کنید (یعنی صدا را کم کنید).

  • وقتی پخش متوقف می‌شود (مثلاً وقتی برنامه دیگر چیزی برای پخش ندارد)، فوکوس صوتی را رها کنید. اگر کاربر پخش را متوقف کند، برنامه شما مجبور به رها کردن فوکوس صوتی نیست، اما ممکن است بعداً پخش را از سر بگیرد.

  • از AudioAttributes برای توصیف نوع صدایی که برنامه شما پخش می‌کند استفاده کنید. برای مثال، برای برنامه‌هایی که گفتار پخش می‌کنند، CONTENT_TYPE_SPEECH مشخص کنید.

فوکوس صوتی بسته به نسخه اندروید در حال اجرا، به صورت متفاوتی مدیریت می‌شود:

اندروید ۱۲ (سطح API 31) یا بالاتر
فوکوس صوتی توسط سیستم مدیریت می‌شود. سیستم، پخش صدا از یک برنامه را هنگامی که برنامه دیگری درخواست فوکوس صوتی می‌کند، به طور خودکار محو می‌کند. همچنین، سیستم هنگام دریافت تماس ورودی، پخش صدا را بی‌صدا می‌کند.
اندروید ۸.۰ (سطح API ۲۶) تا اندروید ۱۱ (سطح API ۳۰)
فوکوس صوتی توسط سیستم مدیریت نمی‌شود، اما شامل تغییراتی است که از اندروید ۸.۰ (سطح API ۲۶) معرفی شده‌اند.
اندروید ۷.۱ (سطح API ۲۵) و پایین‌تر
فوکوس صوتی توسط سیستم مدیریت نمی‌شود و برنامه‌ها با استفاده از requestAudioFocus() و abandonAudioFocus() فوکوس صوتی را مدیریت می‌کنند.

فوکوس صوتی در اندروید ۱۲ و بالاتر

یک برنامه رسانه‌ای یا بازی که از فوکوس صوتی استفاده می‌کند، نباید پس از از دست دادن فوکوس، صدا را پخش کند. در اندروید ۱۲ (سطح API ۳۱) و بالاتر، سیستم این رفتار را اجباری می‌کند. وقتی یک برنامه درخواست فوکوس صوتی می‌کند در حالی که برنامه دیگری فوکوس را دارد و در حال پخش است، سیستم برنامه در حال پخش را مجبور به محو شدن می‌کند. اضافه شدن محو شدن، انتقال نرم‌تری را هنگام رفتن از یک برنامه به برنامه دیگر فراهم می‌کند.

این رفتار محو شدن زمانی اتفاق می‌افتد که شرایط زیر برقرار باشد:

  1. اولین برنامه که در حال پخش است، تمام این معیارها را دارد:

  2. برنامه دوم درخواست فوکوس صوتی با AudioManager.AUDIOFOCUS_GAIN را می‌دهد.

وقتی این شرایط برآورده شوند، سیستم صوتی برنامه اول را محو می‌کند. در پایان محو شدن، سیستم به برنامه اول از دست دادن فوکوس را اطلاع می‌دهد. پخش‌کننده‌های برنامه تا زمانی که برنامه دوباره درخواست فوکوس صوتی کند، بی‌صدا می‌مانند.

رفتارهای فوکوس صوتی موجود

همچنین باید از این موارد دیگر که شامل تغییر در فوکوس صوتی می‌شوند، آگاه باشید.

جابجایی خودکار

قابلیت «کاهش خودکار صدا» (کاهش موقت سطح صدای یک برنامه به طوری که برنامه دیگر به وضوح شنیده شود) در اندروید ۸.۰ (سطح API ۲۶) معرفی شد.

با پیاده‌سازی سیستمِ «کنار زدن دکمه‌ها»، شما مجبور نیستید «کنار زدن دکمه‌ها» را در برنامه‌تان پیاده‌سازی کنید.

همچنین، زمانی که یک اعلان صوتی، فوکوس را از یک برنامه در حال پخش می‌گیرد، تغییر مکان خودکار صفحه نمایش اتفاق می‌افتد. شروع پخش اعلان با پایان شیب تغییر مکان همزمان می‌شود.

جابجایی خودکار زمانی اتفاق می‌افتد که شرایط زیر برقرار باشد:

  1. اولین برنامه در حال پخش، تمام این معیارها را دارد:

  2. برنامه دوم درخواست فوکوس صوتی با AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK را می‌دهد.

وقتی این شرایط برآورده شوند، سیستم صوتی تمام پخش‌کننده‌های فعال برنامه اول را در حالی که برنامه دوم فوکوس دارد، غیرفعال می‌کند. وقتی برنامه دوم فوکوس را رها می‌کند، آنها را نیز غیرفعال می‌کند. وقتی برنامه اول فوکوس را از دست می‌دهد، به آن اطلاع داده نمی‌شود، بنابراین لازم نیست کاری انجام دهد.

توجه داشته باشید که وقتی کاربر به محتوای گفتاری گوش می‌دهد، جابجایی خودکار بین دو پرده انجام نمی‌شود، زیرا ممکن است کاربر بخشی از برنامه را از دست بدهد. برای مثال، راهنمای صوتی برای مسیرهای رانندگی جابجا نمی‌شود.

قطع کردن پخش صدای فعلی برای تماس‌های تلفنی ورودی

برخی از برنامه‌ها به درستی رفتار نمی‌کنند و در حین تماس‌های تلفنی به پخش صدا ادامه می‌دهند. این وضعیت کاربر را مجبور می‌کند تا برنامه‌ی مشکل‌ساز را پیدا کرده و آن را بی‌صدا کند یا از آن خارج شود تا بتواند تماس خود را بشنود. برای جلوگیری از این امر، سیستم می‌تواند صدای برنامه‌های دیگر را در حین تماس ورودی بی‌صدا کند. سیستم این ویژگی را زمانی فعال می‌کند که یک تماس تلفنی ورودی دریافت شود و یک برنامه شرایط زیر را داشته باشد:

  • این برنامه یا ویژگی استفاده‌ی AudioAttributes.USAGE_MEDIA یا AudioAttributes.USAGE_GAME را دارد.
  • برنامه با موفقیت درخواست فوکوس صوتی (هرگونه افزایش فوکوس) را داد و در حال پخش صدا است.

اگر برنامه‌ای در حین تماس به پخش ادامه دهد، پخش آن تا پایان تماس بی‌صدا می‌شود. با این حال، اگر برنامه‌ای در حین تماس شروع به پخش کند، با فرض اینکه کاربر عمداً پخش را شروع کرده باشد، پخش‌کننده‌ی آن بی‌صدا نمی‌شود.

فوکوس صوتی در اندروید ۸.۰ تا اندروید ۱۱

از اندروید ۸.۰ (سطح API ۲۶)، هنگام فراخوانی تابع requestAudioFocus() باید یک پارامتر AudioFocusRequest ارائه دهید. AudioFocusRequest حاوی اطلاعاتی در مورد زمینه صوتی و قابلیت‌های برنامه شما است. سیستم از این اطلاعات برای مدیریت خودکار افزایش و کاهش فوکوس صوتی استفاده می‌کند. برای آزاد کردن فوکوس صوتی، متد abandonAudioFocusRequest() را فراخوانی کنید که یک AudioFocusRequest را نیز به عنوان آرگومان خود دریافت می‌کند. از همان نمونه AudioFocusRequest هم هنگام درخواست و هم هنگام رها کردن فوکوس استفاده کنید.

برای ایجاد یک AudioFocusRequest ، از AudioFocusRequest.Builder استفاده کنید. از آنجایی که یک درخواست focus همیشه باید نوع درخواست را مشخص کند، نوع در سازنده‌ی سازنده گنجانده شده است. از متدهای سازنده برای تنظیم سایر فیلدهای درخواست استفاده کنید.

فیلد FocusGain الزامی است؛ سایر فیلدها اختیاری هستند.

روش یادداشت‌ها
setFocusGain() این فیلد در هر درخواستی الزامی است. همان مقادیر durationHint استفاده شده در فراخوانی requestAudioFocus() نسخه‌های قبل از اندروید ۸.۰ را می‌گیرد: AUDIOFOCUS_GAIN ، AUDIOFOCUS_GAIN_TRANSIENT ، AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ، یا AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE .
setAudioAttributes() AudioAttributes مورد استفاده برای برنامه شما را توصیف می‌کند. سیستم وقتی یک برنامه فوکوس صوتی را به دست می‌آورد و از دست می‌دهد، به آنها نگاه می‌کند. Attributes جایگزین مفهوم نوع جریان می‌شوند. در اندروید ۸.۰ (سطح API ۲۶) و بالاتر، انواع جریان برای هر عملیاتی غیر از کنترل‌های صدا منسوخ شده‌اند. از همان Attributes که در پخش‌کننده صوتی خود استفاده می‌کنید، در درخواست فوکوس استفاده کنید (همانطور که در مثال زیر این جدول نشان داده شده است).

ابتدا از AudioAttributes.Builder برای مشخص کردن ویژگی‌ها استفاده کنید، سپس از این متد برای اختصاص ویژگی‌ها به درخواست استفاده کنید.

اگر مشخص نشده باشد، AudioAttributes به صورت پیش‌فرض روی AudioAttributes.USAGE_MEDIA تنظیم می‌شود.

setWillPauseWhenDucked() وقتی برنامه‌ی دیگری با استفاده از AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK درخواست فوکوس می‌کند، برنامه‌ای که فوکوس روی آن است معمولاً فراخوانی onAudioFocusChange() را دریافت نمی‌کند، زیرا سیستم می‌تواند خودش این کار را انجام دهد . وقتی می‌خواهید پخش را متوقف کنید به جای اینکه صدا را کم کنید، setWillPauseWhenDucked(true) را فراخوانی کنید و یک OnAudioFocusChangeListener ایجاد و تنظیم کنید، همانطور که در automatic ducking توضیح داده شده است.
setAcceptsDelayedFocusGain() درخواست فوکوس صوتی می‌تواند زمانی که فوکوس توسط برنامه دیگری قفل شده باشد، با شکست مواجه شود. این روش امکان افزایش فوکوس با تأخیر را فراهم می‌کند: توانایی دریافت فوکوس به صورت غیرهمزمان در صورت در دسترس بودن.

توجه داشته باشید که افزایش فوکوس با تأخیر فقط در صورتی کار می‌کند که شما یک AudioManager.OnAudioFocusChangeListener نیز در درخواست صوتی مشخص کنید، زیرا برنامه شما برای اینکه بداند فوکوس اعطا شده است، باید callback را دریافت کند.

setOnAudioFocusChangeListener() یک OnAudioFocusChangeListener فقط در صورتی مورد نیاز است که willPauseWhenDucked(true) یا setAcceptsDelayedFocusGain(true) نیز در درخواست مشخص کنید.

دو روش برای تنظیم شنونده وجود دارد: یکی با آرگومان کنترل‌کننده و دیگری بدون آن. کنترل‌کننده، نخی است که شنونده روی آن اجرا می‌شود. اگر کنترل‌کننده‌ای مشخص نکنید، از کنترل‌کننده مرتبط با 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;
        }
    }
}

جابجایی خودکار

در اندروید ۸.۰ (سطح API ۲۶)، وقتی برنامه‌ی دیگری با AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK درخواست فوکوس می‌کند، سیستم می‌تواند بدون فراخوانی تابع onAudioFocusChange() برنامه، فوکوس را متوقف کرده و صدا را بازیابی کند.

اگرچه تغییر خودکار فاصله بین دو پرده نمایش برای برنامه‌های پخش موسیقی و ویدیو قابل قبول است، اما هنگام پخش محتوای گفتاری، مانند یک برنامه کتاب صوتی، مفید نیست. در این حالت، برنامه باید مکث کند.

اگر می‌خواهید برنامه‌تان هنگام درخواست کاهش صدا، به جای کاهش آن، مکث کند، یک 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 مربوطه را لغو ثبت می‌کند. اگر درخواست فوکوس موقت داده باشید، این به برنامه‌ای که مکث کرده یا از پخش خارج شده اطلاع می‌دهد که می‌تواند پخش را ادامه دهد یا صدای خود را بازیابی کند.

پاسخ به تغییر فوکوس صوتی

وقتی یک برنامه فوکوس صوتی را به دست می‌آورد، باید بتواند آن را هنگامی که برنامه دیگری فوکوس صوتی را برای خود درخواست می‌کند، آزاد کند. وقتی این اتفاق می‌افتد، برنامه شما فراخوانی به متد onAudioFocusChange() در AudioFocusChangeListener دریافت می‌کند که شما هنگام فراخوانی requestAudioFocus() توسط برنامه، آن را مشخص کرده‌اید.

پارامتر focusChange که به onAudioFocusChange() ارسال می‌شود، نوع تغییری که اتفاق می‌افتد را نشان می‌دهد. این پارامتر با مدت زمانی که برنامه در حال دریافت focus استفاده می‌کند، مطابقت دارد. برنامه شما باید به طور مناسب پاسخ دهد.

از دست دادن گذرای تمرکز
اگر تغییر فوکوس گذرا باشد ( AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK یا AUDIOFOCUS_LOSS_TRANSIENT )، برنامه شما باید (اگر به تغییر فوکوس خودکار متکی نیستید) جمع شود یا پخش را متوقف کند، اما در غیر این صورت همان وضعیت را حفظ کند.

در طول از دست دادن موقت فوکوس صوتی، باید به نظارت بر تغییرات فوکوس صوتی ادامه دهید و آماده باشید تا پس از بازیابی فوکوس، پخش عادی را از سر بگیرید. هنگامی که برنامه مسدودکننده فوکوس را رها می‌کند، یک فراخوانی ( AUDIOFOCUS_GAIN ) دریافت می‌کنید. در این مرحله، می‌توانید صدا را به سطح عادی برگردانید یا پخش را مجدداً شروع کنید.

از دست دادن دائمی تمرکز
اگر از دست دادن فوکوس صدا دائمی باشد ( AUDIOFOCUS_LOSS )، برنامه دیگری در حال پخش صدا است. برنامه شما باید فوراً پخش را متوقف کند، زیرا هرگز فراخوانی AUDIOFOCUS_GAIN را دریافت نخواهد کرد. برای شروع مجدد پخش، کاربر باید یک اقدام صریح انجام دهد، مانند فشار دادن کنترل پخش در یک اعلان یا رابط کاربری برنامه.

قطعه کد زیر نحوه پیاده‌سازی 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) را فراخوانی کنید. برای مثال، تابع removeCallbacks() در فراخوانی‌های onPlay() ، onSkipToNext() و غیره فراخوانی کنید. همچنین باید هنگام پاکسازی منابع مورد استفاده توسط سرویس خود، این متد را در فراخوانی onDestroy() سرویس خود فراخوانی کنید.