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

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