Два или более приложений Android могут одновременно воспроизводить аудио в одном и том же выходном потоке, и система микширует всё вместе. Хотя технически это впечатляет, для пользователя это может быть очень неудобно. Чтобы избежать одновременного воспроизведения всех музыкальных приложений, Android вводит концепцию аудиофокуса . Только одно приложение может удерживать аудиофокус одновременно.
Когда вашему приложению требуется вывести звук, оно должно запросить аудиофокус. Получив фокус, оно может воспроизводить звук. Однако после получения аудиофокуса вы, возможно, не сможете удержать его, пока не закончите воспроизведение. Другое приложение может запросить фокус, что перехватит ваш аудиофокус. В этом случае вашему приложению следует приостановить воспроизведение или уменьшить громкость, чтобы пользователи могли лучше слышать новый источник звука.
До Android 12 (уровень API 31) аудиофокус не управлялся системой. Поэтому, хотя разработчикам приложений рекомендуется соблюдать правила аудиофокуса, если приложение продолжает громко воспроизводиться даже после потери аудиофокуса на устройстве под управлением Android 11 (уровень API 30) или ниже, система не может этого предотвратить. Однако такое поведение приложения приводит к негативному пользовательскому опыту и часто может привести к удалению проблемного приложения.
Хорошо спроектированное аудиоприложение должно управлять аудиофокусом в соответствии со следующими общими рекомендациями:
Вызовите функцию
requestAudioFocus()
непосредственно перед началом воспроизведения и убедитесь, что она возвращаетAUDIOFOCUS_REQUEST_GRANTED
. Вызовите функциюrequestAudioFocus()
в обратном вызовеonPlay()
вашего медиасеанса.Когда другое приложение получает аудиофокус, остановите или приостановите воспроизведение или приглушите (то есть уменьшите) громкость.
Когда воспроизведение останавливается (например, когда приложению больше нечего воспроизводить), снимите аудиофокус. Ваше приложение не обязано снимать аудиофокус, если пользователь приостанавливает воспроизведение, но может возобновить его позже.
Используйте
AudioAttributes
для описания типа звука, воспроизводимого вашим приложением. Например, для приложений, воспроизводящих речь, укажитеCONTENT_TYPE_SPEECH
.
Аудиофокус обрабатывается по-разному в зависимости от версии Android:
- Android 12 (уровень API 31) или более поздняя версия
- Система управляет аудиофокусом. Система принудительно затухает воспроизведение звука из приложения, когда другое приложение запрашивает аудиофокус. Система также отключает воспроизведение звука при входящем вызове.
- Android 8.0 (уровень API 26) – Android 11 (уровень API 30)
- Аудиофокус не управляется системой, но включает в себя некоторые изменения, введенные начиная с Android 8.0 (уровень API 26).
- Android 7.1 (уровень API 25) и ниже
- Аудиофокус не управляется системой, а приложения управляют аудиофокусом с помощью
requestAudioFocus()
иabandonAudioFocus()
.
Аудиофокус в Android 12 и выше
Медиа- или игровое приложение, использующее аудиофокус, не должно воспроизводить звук после потери фокуса. В Android 12 (уровень API 31) и более поздних версиях система принудительно применяет это поведение. Когда приложение запрашивает аудиофокус, когда другое приложение находится в фокусе и воспроизводит аудио, система принудительно затухает звук в воспроизводимом приложении. Добавление функции затухания обеспечивает более плавный переход между приложениями.
Такое затухание происходит при соблюдении следующих условий:
Первое, воспроизводимое в данный момент приложение соответствует всем этим критериям:
- Приложение имеет атрибут использования
AudioAttributes.USAGE_MEDIA
илиAudioAttributes.USAGE_GAME
. - Приложение успешно запросило аудиофокус с помощью
AudioManager.AUDIOFOCUS_GAIN
. - Приложение не воспроизводит аудио с типом контента
AudioAttributes.CONTENT_TYPE_SPEECH
.
- Приложение имеет атрибут использования
Второе приложение запрашивает аудиофокус с помощью
AudioManager.AUDIOFOCUS_GAIN
.
При выполнении этих условий аудиосистема постепенно затухает звук в первом приложении. По завершении процесса постепенного затухания система уведомляет первое приложение о потере фокуса. Звук в проигрывателях приложения остаётся отключенным до тех пор, пока приложение снова не запросит аудиофокус.
Существующие модели поведения аудиофокуса
Вам также следует знать о других случаях, связанных с переключением аудиофокуса.
Автоматическое приседание
Автоматическое приглушение (временное снижение уровня звука одного приложения, чтобы другое можно было отчетливо слышать) было введено в Android 8.0 (уровень API 26).
Если система реализует функцию приглушения, вам не придется реализовывать ее в своем приложении.
Автоматическое приглушение звука также происходит, когда звуковое уведомление захватывает фокус воспроизводимого приложения. Начало воспроизведения уведомления синхронизируется с окончанием приглушения звука.
Автоматическое приседание происходит при соблюдении следующих условий:
Первое, воспроизводимое в данный момент приложение соответствует всем этим критериям:
- Приложение успешно запросило аудиофокус с любым типом усиления фокуса .
- Приложение не воспроизводит аудио с типом контента
AudioAttributes.CONTENT_TYPE_SPEECH
. - Приложение не установило
AudioFocusRequest.Builder.setWillPauseWhenDucked(true)
.
Второе приложение запрашивает аудиофокус с помощью
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
.
При выполнении этих условий аудиосистема приглушает все активные проигрыватели первого приложения, пока второе приложение находится в фокусе. Когда второе приложение теряет фокус, она отключает их. Первое приложение не получает уведомления о потере фокуса, поэтому ему не нужно ничего делать.
Обратите внимание, что автоматическое приглушение звука не выполняется, когда пользователь слушает речевой контент, поскольку пользователь может пропустить часть программы. Например, голосовые подсказки о маршруте проезда не приглушаются.
Отключить текущее воспроизведение звука для входящих телефонных звонков
Некоторые приложения ведут себя некорректно и продолжают воспроизводить аудио во время телефонных звонков. В этой ситуации пользователю приходится искать и отключать звук или закрывать проблемное приложение, чтобы услышать свой звонок. Чтобы предотвратить это, система может отключать звук других приложений во время входящего звонка. Система активирует эту функцию при получении входящего звонка и соблюдении следующих условий:
- Приложение имеет атрибут использования
AudioAttributes.USAGE_MEDIA
илиAudioAttributes.USAGE_GAME
. - Приложение успешно запросило аудиофокус (любое усиление фокуса) и воспроизводит аудио.
Если приложение продолжает воспроизведение во время разговора, звук отключается до завершения разговора. Однако, если приложение начинает воспроизведение во время разговора, звук этого проигрывателя не отключается, поскольку предполагается, что пользователь включил воспроизведение намеренно.
Аудиофокус в Android 8.0 – Android 11
Начиная с Android 8.0 (уровень API 26), при вызове метода 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 (уровень API 26) и более поздних версиях типы потоков для любых операций, кроме управления громкостью, устарели. Используйте в запросе фокуса те же атрибуты, что и в аудиоплеере (как показано в примере после этой таблицы). Сначала используйте Если не указано иное, по умолчанию |
setWillPauseWhenDucked() | Когда другое приложение запрашивает фокус с помощью AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK , приложение, находящееся в фокусе, обычно не получает обратный вызов onAudioFocusChange() поскольку система может выполнить приглушение самостоятельно . Если вам нужно приостановить воспроизведение, а не приглушить звук, вызовите setWillPauseWhenDucked(true) и создайте и настройте OnAudioFocusChangeListener , как описано в разделе «Автоматическое приглушение» . |
setAcceptsDelayedFocusGain() | Запрос на аудиофокус может быть отклонен, если фокус заблокирован другим приложением. Этот метод обеспечивает отложенное получение фокуса : возможность асинхронного захвата фокуса, когда он становится доступен. Обратите внимание, что отложенное получение фокуса работает только в том случае, если вы также указали |
setOnAudioFocusChangeListener() | OnAudioFocusChangeListener требуется только в том случае, если в запросе также указано willPauseWhenDucked(true) или setAcceptsDelayedFocusGain(true) . Существует два способа настройки прослушивателя: с аргументом обработчика и без него. Обработчик — это поток, в котором работает прослушиватель. Если обработчик не указан, используется обработчик, связанный с основным |
В следующем примере показано, как использовать 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)
чтобы указать системе использовать ваш обратный вызов вместо автоматического приглушения звука.
Отложенное усиление фокуса
Иногда система не может выполнить запрос на аудиофокус, поскольку фокус «заблокирован» другим приложением, например, во время телефонного звонка. В этом случае 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
для задержки обратного вызова stop при постоянной потере аудиофокуса.
Котлин
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()
вашего сервиса при очистке ресурсов, используемых вашим сервисом.