Плавное и своевременное переключение между устройствами аудио вывода — это функция, наличие которой обычно воспринимается как данность, но отсутствие (или проблемы с её работой) — сильно раздражает. Сегодня мы разберём, как реализовать такое переключение в приложениях для звонков под Android, начиная с "ручного" переключения пользователем и заканчивая автоматическим переключением при подключении гарнитур. Заодно затронем тему постановки на паузу всего остального аудио системы на время звонка. Рассматриваемая реализация подходит для практически всех приложений со звонками, так как действует на уровне системы, а не на уровне механизма звонков (например, WebRTC).
Управление устройствами аудио вывода
Все управление устройствами вывода звука в Android реализована через системный `AudioManager`. Для работы с ним необходимо добавить разрешение в `AndroidManifest.xml`:
В первую очередь, когда в нашем приложении начинается звонок, крайне рекомендуется захватить аудио фокус — сообщить системе, что пользователь сейчас с кем-то общается, и его лучше не отвлекать звуками из других приложений. Например, если пользователь слушал музыку, но ему позвонили и он ответил — музыка будет поставлена на паузу на время звонка.
Есть два механизма запроса аудио фокуса — старый уже deprecated, а новый доступен начиная с Android 8.0. Мы реализуем для всех версий системы:
Важно указать наиболее подходящие `ContentType` и `Usage` — исходя из них система определяет, какую из пользовательских настроек громкости использовать (громкость медиа или громкость звонка) и что делать с другими источниками аудио (приглушить, поставить на паузу, или позволить работать как прежде).
Отлично, мы получили аудио фокус. Крайне рекомендуется сразу сохранить исходные настройки AudioManager перед тем, как что-либо менять -- это позволит нам восстановить его предыдущее состояние, когда закончится звонок. Согласитесь, было бы очень неудобно, если бы переключатель громкой связи в одном приложении влиял на все остальные.
Теперь мы можем приступить к установке наших настроек по умолчанию. Они могут зависеть от типа звонка (обычно аудиозвонок идёт через "слуховой динамик", а видео — через громкую связь), от настроек пользователя в приложении или просто от последнего использованного динамика. Наше условное приложение — это приложение для видеосвязи, поэтому мы сразу установим громкую связь:
Отлично, мы применили настройки по умолчанию. Если дизайном приложения предусмотрена кнопка переключения громкой связи, теперь мы можем очень просто реализовать её обработку:
Отслеживание подключения наушников
Мы научились реализовывать переключение громкой связи, но что произойдёт, если подключить наушники? Ничего, так как `audioManager.isSpeakerphoneOn` всё ещё `true`! А пользователь, конечно же, ожидает, что при подключении наушников звук начнёт воспроизводиться именно через них. И наоборот — если у нас видео звонок, то при отключении наушников звук должен начинать идти через громкую связь.
Деваться некуда, нам придётся отслеживать подключение наушников. Скажу сразу — подключение проводных и bluetooth наушников отслеживается по-разному, поэтому нам придётся реализовывать сразу два механизма. Начнём с проводных, вынесем логику в отдельный класс:
В нашем примере мы используем `StateFlow` для реализации подписки на состояние подключения, но вместо этого можно реализовать, например, `HeadsetStateProviderListener`
Теперь достаточно инициализировать этот класс и наблюдать за полем `isHeadsetPlugged`, включая или выключая громкую связь при изменении:
Отслеживание подключения Bluetooth наушников
Теперь реализуем такой же механизм наблюдения для Bluetooth наушников:
Для работы с Bluetooth нам потребуется ещё одно разрешение:
И теперь, чтобы автоматически включать громкую связь, когда не подключены никакие гарнитуры, и наоборот выключать при подключении новой:
Прибираем за собой
Когда звонок окончен, фокус аудио нам больше ни к чему, и мы должны избавиться от него. Восстановим настройки, которые сохранили в самом начале:
И теперь, собственно, отдадим фокус. Опять же, реализация зависит от версии системы:
Ограничения
В приложении можно переключаться между разными устройствами выхода:
- динамик
- наушники (проводные)
- Bluetooth-устройство (например, колонки или беспроводные наушники)
Однако переключиться между двумя Bluetooth-устройствами будет невозможно. Но на Android 11 теперь можно добавить функцию смены девайса в панель уведомлений. Переключатель отображает все доступные устройства, у каждого можно отрегулировать громкость. В общем, это не просто список доступных для подключения каналов выхода, а вполне функциональная фича.
Чтобы добавить такой переключатель, используем Notification.MediaStyle style с подключенным MediaSession:
В Spotify можно переключаться между любыми устройствами. Как это реализовали?
Один из наших читателей заметил, что в Spotify нет никаких ограничений на переключение девайсов. Скорее всего они использовали MediaRouter API. Его обычно используют для бесшовной передачи медиа между устройствами.
Более подробную информацию о переключателе аудиоустройств и о MediaRouter можно узнать в статье и в документации по Media Routing.
Итог
Отлично, вот мы и реализовали идеальный UX переключения между устройствами аудио вывода в нашем приложении. Основной плюс этого подхода в том, что он практически не зависит от конкретной реализации звонков: в любом случае воспроизводимое аудио будет контролироваться `AudioManager`, и мы управляем именно на его уровне!
Комментарии