Pull to refresh

Аудиофокус — управление доступом к звуковой подсистеме

Reading time7 min
Views20K
Это перевод статьи Respecting Audio Focus Kristan Uccello, Google Developer Relations

Считается грубым перебивать во время доклада, это показывает неуважение к докладчику и раздражает аудиторию. Если ваше приложение не учитывает правила работы с аудиофокусом, значит, оно не уважает остальные приложения и раздражает пользователя. Если Вы никогда не слышали об аудиофокусе, стоит обратить внимание на документацию Android developer training material.
Когда несколько приложений могут воспроизводить аудио важно думать о том, как они будут взаимодействовать. Чтобы избежать ситуации когда все плееры играют одновременно Андроид использует понятие аудиофокуса для контроля воспроизведения звуков: ваше приложение должно воспроизводить аудио только тогда, когда оно получило аудиофокус. В этой статье описаны несколько советов о том, как правильно и наилучшим для пользователя образом обрабатывать изменения состояния аудиофокуса.

Запрос аудиофокуса


Не надо быть жадным и запрашивать аудиофокус прямо в момент старта приложения; лучше подождать, пока приложение не начнет что-то делать с аудиопотоком. При получении аудиофокуса через сервис AudioManager, можно воспользоваться константами AUDIOFOCUS_GAIN* для обозначения необходимого режима фокуса.

Пример запроса фокуса
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int result = am.requestAudioFocus(mOnAudioFocusChangeListener,
    // Hint: the music stream.
    AudioManager.STREAM_MUSIC,
    // Request permanent focus.
    AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
  mState.audioFocusGranted = true;
} else if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
  mState.audioFocusGranted = false;
}

В примере мы запрашиваем постоянный аудиофокус. Вместо этого, мы могли бы запросить временный (AUDIOFOCUS_GAIN_TRANSIENT) фокус, который подходит для воспроизведения звуков длительностью до 45 секунд.
Еще приложение может использовать режим “крякания” (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) для ситуаций, когда допустимо совместное использование аудиоподсистемы вместе с другими приложениями (например, для фразы “жги еще” в фитнес-приложении, ожидая, что фоновая музыка не будет прерываться). Приложение, запрашивающее фокус в режиме “крякания”, не должно использовать аудиоподсистему дольше 15 секунд подряд.

Обрабатываем изменения состояния аудиофокуса


Для обработки событий изменения состояния аудиофокуса приложение должно создать экземпляр OnAudioFocusChangeListener. В этом обработчике необходимо обработать события AUDIOFOCUS_GAIN* и AUDIOFOCUS_LOSS*. Стоит заметить, что событие AUDIOFOCUS_GAIN имеет несколько особенностей, описанных во втором примере.

Пример обработки событий
mOnAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {  
 
@Override
public void onAudioFocusChange(int focusChange) {
  switch (focusChange) {
  case AudioManager.AUDIOFOCUS_GAIN:
    mState.audioFocusGranted = true;
       
    if(mState.released) {
      initializeMediaPlayer();
    }
    switch(mState.lastKnownAudioFocusState) {
    case UNKNOWN:
      if(mState.state == PlayState.PLAY && !mPlayer.isPlaying()) {
        mPlayer.start();
      }
      break;
    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
      if(mState.wasPlayingWhenTransientLoss) {
        mPlayer.start();
      }
      break;
    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
      restoreVolume();
      break;
    }
       
    break;
  case AudioManager.AUDIOFOCUS_LOSS:
    mState.userInitiatedState = false;
    mState.audioFocusGranted = false;
    teardown();
    break;
  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
    mState.userInitiatedState = false;
    mState.audioFocusGranted = false;
    mState.wasPlayingWhenTransientLoss = mPlayer.isPlaying();
    mPlayer.pause();
    break;
  case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
    mState.userInitiatedState = false;
    mState.audioFocusGranted = false;
    lowerVolume();
    break;
  }
  mState.lastKnownAudioFocusState = focusChange;
  }
};

Константа AUDIOFOCUS_GAIN используется в коде в двух различных ролях. Во-первых, для получения аудиофокуса как в примере 1. При этом не происходит событие обработчика OnAudioFocusChangeListener, то есть при успешном запросе (и получении) аудиофокуса обработчик НЕ получит соответствующее событие AUDIOFOCUS_GAIN.
AUDIOFOCUS_GAIN также используется в реализации OnAudioFocusChangeListener как вариант события. Как указано ранее, событие AUDIOFOCUS_GAIN не возбуждается при запросе аудиофокуса. Напротив, оно может произойти только после возникновения соответствующего события AUDIOFOCUS_LOSS*. AUDIOFOCUS_GAIN — единственная константа, которая используется в обеих ситуациях.
Существуют четыре ситуации, которые необходимо учитывать в обработчике события изменения состояния аудиофокуса. Когда приложение получает событие AUDIOFOCUS_LOSS, это обычно означает, что обратно аудиофокус оно не получит. В этом случае приложение должно освободить ресурсы, связанные с аудиоподсистемой, и остановить воспроизведение. В качестве примера, представьте, что пользователь слушает музыку через ваше приложение, а затем запускает игру, которая забирает аудиофокус у аудиоплеера. Невозможно предсказать, через сколько времени пользователь закроет игру. Скорее всего, он перейдет на главный экран (оставив игру в фоне) и запустит еще одно приложение. Или он вернется в аудиоплеер, возобновив его работу, что потребует нового запроса аудиофокуса в onResume.
Однако есть другой случай, достойный обсуждения. Существует разница между потерей аудиофокуса навсегда (как в примере выше) или временно. Когда приложение получает событие AUDIOFOCUS_LOSS_TRANSIENT, ожидается, что приложение приостановит использование аудио до тех пор, пока оно не получит событие AUDIOFOCUS_GAIN. Когда возникает событие  AUDIOFOCUS_LOSS_TRANSIENT приложение должно запомнить, что потеря фокуса временная, для того, чтобы при возврате фокуса разобраться, какое поведение корректно. (см. пример 2).
Иногда приложение теряет аудиофокус (т.е. получает AUDIOFOCUS_LOSS), а прервавшее приложение завершается, или каким-то другим образом теряет аудиофокус.  В этой ситуации последнее приложение, которое имело аудиофокус, может получить событие AUDIOFOCUS_GAIN.
В последующем событии AUDIOFOCUS_GAIN приложение должно понять, получило ли оно аудиофокус после временной потери и должно просто возобновить проигрывание, либо восстановиться и настроить воспроизведение после полной потери фокуса.
Если приложение использует аудио только на короткое время (не более 45 секунд), оно должно запрашивать аудиофокус в режиме AUDIOFOCUS_GAIN_TRANSIENT и отпускать его сразу после завершения воспроизведения или записи звука. Аудиофокус в системе обрабатывается как стек: фокус получает то приложение, которое владело им последним.
Когда аудиофокус получен, самое время создать MediaPlayer или MediaRecorder и зарезервировать ресурсы. Также когда приложение получает AUDIOFOCUS_LOSS, хорошей практикой является освобождение всех зарезервированных ресурсов. Существует три варианта получения аудиофокуса, соответствующие разным вариантам потери фокуса. Неплохо бы явно обрабатывать все варианты потери фокуса в обработчике OnAudioFocusChangeListener.

Таблица 1. Смысл констант получения и потери аудиофокуса
GAIN
LOSS
AUDIOFOCUS_GAIN
AUDIOFOCUS_LOSS
AUDIOFOCUS_GAIN_TRANSIENT (*)
AUDIOFOCUS_LOSS_TRANSIENT
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK (*)
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
Замечание: константа используется в двух местах. Когда запрашивается аудиофокус, она передается как подсказка AudioManager; она же используется как вариант события в OnAudioFocusChangeListener. Константы получения фокуса, обозначенные (*), используются только при запросе аудиофокуса. Константы потери фокуса используются только в обработчике OnAudioFocusChangeListener.

Таблица 2. Типы аудиопотоков.
Тип
Описание
STREAM_ALARM
Будильник
STREAM_DTMF
Тоновый набор
STREAM_MUSIC
Воспроизведение мультимедиа (музыка, подкасты, видео)
STREAM_NOTIFICATION
Уведомления
STREAM_RING
Телефонный звонок
STREAM_SYSTEM
Системные звуки
Приложение запрашивает аудиофокус у AudioManager (как в примере по ссылке в конце статьи). Параметрами являются необязательный обработчик, подсказка с типом аудиоканала (таблица 2) и тип аудиофокуса из таблицы 1. Любая инициализация аудио должна производиться, только если система разрешила получение аудиофокуса (AudioManager.AUDIOFOCUS_REQUEST_GRANTED).
Замечание: Если происходит телефонный разговор, система не разрешит получение аудиофокуса (AUDIOFOCUS_REQUEST_FAILED) и не отправит приложению событие AUDIOFOCUS_GAIN после завершения звонка.
Краткое описание реакции приложения на события OnAudioFocusChange() описано в таблице 3.
В случае потери аудиофокуса необходимо быть уверенным, что фокус потерян окончательно. Если приложение получает событие AUDIOFOCUS_LOSS_TRANSIENT или AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK, оно может придержать зарезервированные ресурсы (не вызывать release()), т.к. скорее всего скоро произойдет новое событие изменения  аудиофокуса. Стоит сохранять информацию о временной потере фокуса в каком-нибудь флаге или путем перехода в отдельную вершину графа состояний.
Если приложение запрашивало постоянный аудиофокус в режиме AUDIOFOCUS_GAIN и получило событие AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK, подходящей реакцией будет сделать громкость потише (не забыв сохранить старое значение громкости) и затем вернуть громкость при получении события AUDIOFOCUS_GAIN (см. картинку).



Таблица 3. Реакция приложения при изменении состояния аудиофокуса.
Тип смены фокуса
Реакция
AUDIOFOCUS_GAIN
Событие получения после события потери фокуса: Возобновить воспроизведение медиа, если состояние приложения не противоречат этому. Например, пользователь нажал паузу до события потери фокуса.
AUDIOFOCUS_LOSS
Остановить воспроизведение, освободить ресурсы
AUDIOFOCUS_LOSS_TRANSIENT
Приостановить воспроизведение и сохранить флажок о том, что потеря фокуса временная, для того, чтобы при обработке AUDIOFOCUS_GAIN можно было при необходимости возобновить воспроизведение. Не освобождать ресурсы.
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
Сделать громкость тише или приостановить воспроизведение, не забывая отслеживать состояние как в случае с AUDIOFOCUS_LOSS_TRANSIENT. Не освобождать ресурсы.


Заключение и что почитать


Быть “примерным гражданином” в стане аудиоприложений на андроид-устройствах означает уважать правила работы с аудиофокусом и корректно обрабатывать все ситуации. Постарайтесь заставить ваше приложение действовать разумным образом и не преподносить пользователю неприятных сюрпризов. Об аудиоподсистеме андройд можно рассказать еще много интересного, по ссылкам ниже можно прочитать о ней подробнее.

Исходные коды из статьи доступны по ссылке:
https://android.googlesource.com/platform/development/+/master/samples/RandomMusicPlayer
Tags:
Hubs:
+9
Comments0

Articles