Это перевод статьи 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. Не освобождать ресурсы. |
Заключение и что почитать
Быть “примерным гражданином” в стане аудиоприложений на андроид-устройствах означает уважать правила работы с аудиофокусом и корректно обрабатывать все ситуации. Постарайтесь заставить ваше приложение действовать разумным образом и не преподносить пользователю неприятных сюрпризов. Об аудиоподсистеме андройд можно рассказать еще много интересного, по ссылкам ниже можно прочитать о ней подробнее.
- Managing Audio Focus, урок на ресурсе Android developers.
- Allowing applications to play nice® with each other: Handling remote control buttons, пост в блоге Android Developers Blog.
Исходные коды из статьи доступны по ссылке:
https://android.googlesource.com/platform/development/+/master/samples/RandomMusicPlayer
Автор: tumbler