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

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

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

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

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

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

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

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

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

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

Android 12 (سطح API 31) یا جدیدتر
فوکوس صوتی توسط سیستم مدیریت می شود. هنگامی که برنامه دیگری فوکوس صوتی را درخواست می کند، سیستم پخش صدا را از یک برنامه مجبور می کند تا محو شود. این سیستم همچنین پخش صدا را هنگام دریافت تماس دریافتی قطع می کند.
اندروید 8.0 (سطح API 26) تا اندروید 11 (سطح API 30)
فوکوس صوتی توسط سیستم مدیریت نمی‌شود، اما شامل تغییراتی است که با شروع اندروید 8.0 (سطح API 26) معرفی شده‌اند.
اندروید 7.1 (سطح API 25) و پایین تر
فوکوس صوتی توسط سیستم مدیریت نمی‌شود و برنامه‌ها فوکوس صوتی را با استفاده از requestAudioFocus() و abandonAudioFocus() مدیریت می‌کنند.

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

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

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

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

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

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

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

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

اردک خودکار

داکینگ خودکار (به طور موقت سطح صدای یک برنامه را کاهش می دهد تا برنامه دیگر به وضوح شنیده شود) در اندروید 8.0 (سطح API 26) معرفی شد.

با اجرای سیستم ducking، نیازی به پیاده سازی ducking در برنامه خود ندارید.

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

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

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

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

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

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

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

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

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

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

فوکوس صوتی در اندروید 8.0 تا اندروید 11

با شروع Android 8.0 (سطح API 26)، هنگام فراخوانی requestAudioFocus() باید یک پارامتر AudioFocusRequest ارائه دهید. AudioFocusRequest حاوی اطلاعاتی درباره زمینه صوتی و قابلیت های برنامه شما است. سیستم از این اطلاعات برای مدیریت افزایش و از دست دادن فوکوس صوتی به صورت خودکار استفاده می کند. برای انتشار فوکوس صوتی، متد abandonAudioFocusRequest() را فراخوانی کنید که یک AudioFocusRequest را نیز به عنوان آرگومان خود در نظر می گیرد. از همان نمونه AudioFocusRequest هم هنگام درخواست و هم در هنگام رها کردن فوکوس استفاده کنید.

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

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

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

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

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

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

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

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

اردک خودکار

در Android 8.0 (سطح API 26)، هنگامی که برنامه دیگری درخواست فوکوس با AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK را می‌کند، سیستم می‌تواند صدا را بدون فراخوانی پاسخ تماس onAudioFocusChange() آن را کاهش داده و بازیابی کند.

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

اگر می‌خواهید از برنامه‌تان به‌جای کاهش حجم صدا، زمانی که از برنامه‌تان خواسته می‌شود مکث کند، یک OnAudioFocusChangeListener با یک onAudioFocusChange() ایجاد کنید که رفتار مکث/رزومه مورد نظر را پیاده‌سازی می‌کند. برای ثبت شنونده، setOnAudioFocusChangeListener() را صدا کنید و setWillPauseWhenDucked(true) را فراخوانی کنید تا به سیستم بگویید به جای اجرای خودکار ducking از پاسخ تماس شما استفاده کند.

افزایش تمرکز با تاخیر

گاهی اوقات سیستم نمی‌تواند درخواستی برای فوکوس صوتی بدهد زیرا فوکوس توسط برنامه دیگری قفل می‌شود، مثلاً در حین تماس تلفنی. در این مورد، requestAudioFocus() AUDIOFOCUS_REQUEST_FAILED را برمی گرداند. هنگامی که این اتفاق می افتد، برنامه شما نباید به پخش صدا ادامه دهد زیرا تمرکز نمی کند.

روش، setAcceptsDelayedFocusGain(true) ، که به برنامه شما امکان می دهد درخواست فوکوس را به صورت ناهمزمان انجام دهد. با این مجموعه پرچم، درخواستی که وقتی فوکوس قفل است، AUDIOFOCUS_REQUEST_DELAYED را برمی‌گرداند. هنگامی که شرایطی که فوکوس صوتی را قفل کرده است دیگر وجود نداشته باشد، مانند زمانی که یک تماس تلفنی به پایان می رسد، سیستم درخواست فوکوس معلق را می دهد و با onAudioFocusChange() تماس می گیرد تا به برنامه شما اطلاع دهد.

برای کنترل تمرکز تأخیر، باید یک OnAudioFocusChangeListener با متد onAudioFocusChange() ایجاد کنید که رفتار مورد نظر را پیاده سازی کند و شنونده را با فراخوانی setOnAudioFocusChangeListener() ثبت کند.

فوکوس صوتی در اندروید 7.1 و پایین تر

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

از دست دادن گذرا تمرکز
اگر تغییر فوکوس گذرا باشد ( 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() را در Callback onPlay() , onSkipToNext() و غیره فراخوانی کنید. همچنین باید این روش را در هنگام پاکسازی منابع مورد استفاده توسط سرویس خود در onDestroy() سرویس خود فراخوانی کنید.