Управление аудиофокусом

Два или более приложений 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) и более поздних версиях система применяет такое поведение. Когда приложение запрашивает фокусировку звука, в то время как другое приложение имеет фокус и воспроизводит звук, система заставляет воспроизводящееся приложение затухать. Добавление плавного исчезновения обеспечивает более плавный переход от одного приложения к другому.

Такое затухание происходит при выполнении следующих условий:

  1. Первое играемое в данный момент приложение соответствует всем этим критериям:

  2. Второе приложение запрашивает фокус звука с помощью AudioManager.AUDIOFOCUS_GAIN .

Когда эти условия выполняются, аудиосистема заглушает первое приложение. По окончании затухания система уведомляет первое приложение о потере фокуса. Проигрыватели приложения остаются отключенными до тех пор, пока приложение снова не запросит фокусировку звука.

Существующие варианты поведения фокуса звука

Вам также следует знать о других случаях, связанных с переключением аудиофокуса.

Автоматическое пригибание

Автоматическое приглушение (временное снижение уровня звука одного приложения, чтобы отчетливо слышать другое) было представлено в Android 8.0 (уровень API 26).

Если система реализует прикрытие, вам не нужно реализовывать прикрытие в вашем приложении.

Автоматическое приглушение также происходит, когда звуковое уведомление захватывает фокус играющего приложения. Начало воспроизведения уведомления синхронизируется с окончанием наклона.

Автоматическое пригибание происходит при выполнении следующих условий:

  1. Первое приложение, воспроизводимое в данный момент, соответствует всем этим критериям:

  2. Второе приложение запрашивает фокусировку звука с помощью 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) и более поздних версиях типы потоков для любых операций, кроме управления громкостью, являются устаревшими. Используйте в запросе фокуса те же атрибуты, которые вы используете в своем аудиоплеере (как показано в примере после этой таблицы).

Используйте 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 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 для задержки обратного вызова остановки при постоянной потере фокуса звука.

Котлин

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() вашего сервиса при очистке ресурсов, используемых вашим сервисом.