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

Два или более приложений 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–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, а также как запрашивать и отменять фокус аудио:

Kotlin

// 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 для обработки последующих изменений фокуса. (Прослушиватель изменений описан в разделе Реакция на изменение аудиофокуса.)

Kotlin

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().

Kotlin

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

Kotlin

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, который выглядит следующим образом:

Kotlin

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