Два или более приложений 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) в запросе. Существует два способа установки обработчика: один с аргументом `handler`, другой без него. Обработчиком называется поток, в котором выполняется обработчик. Если вы не указываете обработчик, используется обработчик, связанный с основным |
В следующем примере показано, как использовать 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 } } }
Java
// 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 }
Java
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)
Java
// 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 } } }
Java
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() }
Java
private Runnable delayedStopRunnable = new Runnable() { @Override public void run() { getMediaController().getTransportControls().stop(); } };
Чтобы гарантировать, что отложенная остановка не сработает, если пользователь перезапустит воспроизведение, вызывайте метод mHandler.removeCallbacks(mDelayedStopRunnable) в ответ на любые изменения состояния. Например, вызывайте removeCallbacks() в методах onPlay() , onSkipToNext() и т. д. вашего коллбэка. Также следует вызывать этот метод в методе onDestroy() вашего сервиса при очистке ресурсов, используемых сервисом.