مدیریت تنظیم کانون صوتی

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

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

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

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

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

  • وقتی برنامه دیگری تمرکز صوتی را به‌دست می‌آورد، پخش را متوقف یا موقتاً متوقف کنید، یا صدا را کاهش دهید.

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

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

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

Android 12 (سطح API‏ ۳۱) یا بالاتر
کانون صوتی توسط سیستم مدیریت می‌شود. وقتی برنامه دیگری درخواست تمرکز صوتی می‌کند، سیستم بازپخش صوتی را از برنامه مجبور به محو شدن می‌کند. همچنین، سیستم هنگام دریافت تماس ورودی، بازپخش صدا را بی‌صدا می‌کند.
Android 8.0 (سطح میانای برنامه‌سازی کاربردی ۲۶) تا Android 11 (سطح میانای برنامه‌سازی کاربردی ۳۰)
تمرکز صوتی توسط سیستم مدیریت نمی‌شود، اما شامل برخی تغییرات است که از Android 8.0 (سطح API 26) معرفی شده‌اند.
‫Android 7.1 (سطح API‏ ۲۵) و پایین‌تر
کانون صوتی توسط سیستم مدیریت نمی‌شود و برنامه‌ها کانون صوتی را بااستفاده از requestAudioFocus() و abandonAudioFocus() مدیریت می‌کنند.

تمرکز صوتی در Android 12 و بالاتر

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

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

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

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

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

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

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

کاهش خودکار صدای پس‌زمینه

کاهش خودکار صدا (کاهش موقت سطح صدای یک برنامه به‌طوری‌که برنامه دیگر به‌وضوح شنیده شود) در Android 8.0 (سطح میانای برنامه‌سازی کاربردی ۲۶) معرفی شد.

با پیاده‌سازی کاهش موقت صدا توسط سیستم، نیازی به پیاده‌سازی کاهش موقت صدا در برنامه‌تان ندارید.

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

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

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

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

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

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

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

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

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

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

تمرکز صوتی در Android 8.0 تا Android 11

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

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

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

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

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

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

setWillPauseWhenDucked() وقتی برنامه دیگری با AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK درخواست تمرکز می‌کند، برنامه‌ای که تمرکز دارد معمولاً فراخوانی برگشتی onAudioFocusChange() دریافت نمی‌کند زیرا سیستم می‌تواند خودش کاهش صدا را انجام دهد. وقتی لازم است به‌جای کاهش صدا، بازپخش را موقتاً متوقف کنید، setWillPauseWhenDucked(true) را فراخوانی کنید و OnAudioFocusChangeListener را ایجاد و تنظیم کنید، همان‌طور که در کاهش خودکار صدا توضیح داده شده است.
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 نسخه ۸.۰ (سطح میانای برنامه‌سازی کاربردی ۲۶)، وقتی برنامه دیگری با AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK درخواست تمرکز می‌کند، سیستم می‌تواند بدون فراخوانی فراخوان برگشتی onAudioFocusChange() برنامه، صدا را کاهش دهد و بازیابی کند.

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

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

تأخیر در تمرکز

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

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

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

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