Easy Audio Focus with ExoPlayer
Managing audio focus is a vital part of being a good citizen in the Android media ecosystem.
What is audio focus? The docs describe it like this:
Two or more Android apps can play audio to the same output stream simultaneously. The system mixes everything together. While this is technically impressive, it can be very aggravating to a user. To avoid every music app playing at the same time, Android introduces the idea of audio focus. Only one app can hold audio focus at a time.
When your app needs to output audio, it should request audio focus. When it has focus, it can play sound. However, after you acquire audio focus you may not be able to keep it until you’re done playing. Another app can request focus, which preempts your hold on audio focus. If that happens your app should pause playing or lower its volume to let users hear the new audio source more easily.
Knowing when to play and when to pause playback is the key to ensuring a happy user versus one who feels like they’re caught in a shouting match.
In a continuing effort to live up to its name, we’ve enhanced SimpleExoPlayer in ExoPlayer 2.9 so that it manages audio focus automatically.
How to use it
Making use of this new feature is very simple. First, create an instance of com.google.android.exoplayer2.audio.AudioAttributes that matches your use case. For example if you’re playing a movie:
Then call setAudioAttributes with the second parameter set to true .
Now, SimpleExoPlayer will automatically manage audio focus for you. Your app should not include any code for requesting or responding to audio focus changes.
Pausing versus ducking
By default, when your app is playing audio and the device plays certain notifications, such as when a message arrives, or for turn by turn directions, your app’s playback volume will be reduced while the notification is playing.
That’s usually OK. But if the app is playing a podcast or an audiobook it would be better to pause than to continue playing softer, which could be distracting.
For these cases, you can setContentType to CONTENT_TYPE_SPEECH , and SimpleExoPlayer will pause while the notification is playing and will automatically resume afterwards.
Audio focus and player state
When you let ExoPlayer manage audio focus automatically, the behavior of two functions that return the state of playback needs a little explaining.
getPlayWhenReady
The value returned by getPlayWhenReady may surprise you in one case:
When playback temporarily pauses in response to a transient loss of audio focus, getPlayWhenReady() continues to return true. The reasoning is that the content will continue playing when audio focus is regained.
getVolume
When the volume is temporarily ducked due to a transient loss of audio focus, getVoume() returns the last value that you set (with setVolume() ) — not the volume that you hear. This means setVolume and getVolume always agree. What you set is what you get… but not necessarily what’s heard.
Limitations
Audio focus can be permanent ( AUDIOFOCUS_GAIN ) or transient ( AUDIOFOCUS_GAIN_TRANSIENT_* ). For now, this automatic feature only works for usages that request permanent focus ( USAGE_MEDIA or USAGE_GAME ). If you setUsage to any other value, then setAudioAttributes will throw an IllegalArgumentException .
We are looking forward to seeing how the automatic handling of audio focus works in your app, and are eager to hear your feedback. File feedback and feature request on our GitHub Issue tracker. We’re keen to learn more about your requirements, and how we could make handling audio focus even easier!
Источник
Аудиофокус — управление доступом к звуковой подсистеме
Это перевод статьи Respecting Audio Focus Kristan Uccello, Google Developer Relations
Считается грубым перебивать во время доклада, это показывает неуважение к докладчику и раздражает аудиторию. Если ваше приложение не учитывает правила работы с аудиофокусом, значит, оно не уважает остальные приложения и раздражает пользователя. Если Вы никогда не слышали об аудиофокусе, стоит обратить внимание на документацию Android developer training material.
Когда несколько приложений могут воспроизводить аудио важно думать о том, как они будут взаимодействовать. Чтобы избежать ситуации когда все плееры играют одновременно Андроид использует понятие аудиофокуса для контроля воспроизведения звуков: ваше приложение должно воспроизводить аудио только тогда, когда оно получило аудиофокус. В этой статье описаны несколько советов о том, как правильно и наилучшим для пользователя образом обрабатывать изменения состояния аудиофокуса.
Запрос аудиофокуса
Не надо быть жадным и запрашивать аудиофокус прямо в момент старта приложения; лучше подождать, пока приложение не начнет что-то делать с аудиопотоком. При получении аудиофокуса через сервис AudioManager, можно воспользоваться константами AUDIOFOCUS_GAIN* для обозначения необходимого режима фокуса.
В примере мы запрашиваем постоянный аудиофокус. Вместо этого, мы могли бы запросить временный (AUDIOFOCUS_GAIN_TRANSIENT) фокус, который подходит для воспроизведения звуков длительностью до 45 секунд.
Еще приложение может использовать режим “крякания” (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) для ситуаций, когда допустимо совместное использование аудиоподсистемы вместе с другими приложениями (например, для фразы “жги еще” в фитнес-приложении, ожидая, что фоновая музыка не будет прерываться). Приложение, запрашивающее фокус в режиме “крякания”, не должно использовать аудиоподсистему дольше 15 секунд подряд.
Обрабатываем изменения состояния аудиофокуса
Для обработки событий изменения состояния аудиофокуса приложение должно создать экземпляр OnAudioFocusChangeListener. В этом обработчике необходимо обработать события AUDIOFOCUS_GAIN* и AUDIOFOCUS_LOSS*. Стоит заметить, что событие AUDIOFOCUS_GAIN имеет несколько особенностей, описанных во втором примере.
Константа 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.
Источник
Полный список
— используем Audio Focus
Наверняка вы замечали, что при прослушивании музыки, если срабатывает уведомление, то на время звучания уведомления звук музыки или прерывается или становится тише. Это можно реализовать с помощью аудио-фокуса.
Попробую сначала объяснить схему движения фокуса на словах. Если рассматривать пример музыки и уведомления, то пусть музыку играет некое приложение_1, а уведомления выдает некое приложение_2. Приложение_1, когда начинает воспроизведение, запрашивает аудио-фокус, получает его и играет музыку. Далее приходит смс или письмо, и приложение_2 хочет воспроизвести звук уведомления. Оно также запрашивает аудио-фокус и получает его. Но при этом система видит, что фокус сейчас у приложения_1. Система сообщает приложению_1, что фокус оно пока что потеряло. Звук уведомления воспроизводится, приложение_2 отдает фокус, а приложению_1 сообщают, что фокус снова его. Когда приложение_1 заканчивает играть музыку, оно отдает фокус. Т.е. приложение должно не только запрашивать фокус при необходимости, но и явно отдавать его, когда он более не нужен. Для этого есть специальные методы, мы их рассмотрим дальше.
Тут еще важно понимать, что эти сообщения от системы к приложениям о том, что фокус потерян/восстановлен являются просто уведомительными. И разработчик приложения сам решает, как он будет это обрабатывать: проигнорит, убавит звук или приостановит воспроизведение. Например, я протестировал два плеера на своем планшете. На одном включил музыку и свернул его, музыка продолжала играть в фоне. В другом плеере я запустил просмотр фильма. В результате я слышал и фильм и музыку. Аудио-фокус позволяет избежать этого.
Можно провести аналогию с человеком. Допустим, какой-то человек громко говорит. Его просят говорить потише, а еще лучше совсем заткнуться, т.к. он мешает остальным и вообще достал, и все хотят послушать другого человека. Вот это и есть потеря аудио-фокуса первым человеком. Но ведь это вовсе не означает, что этот человек тут же замолчит. Ему просто поступило уведомление, что другой человек хочет говорить. И первый человек поступает так, как считает нужным: либо продолжает громко говорить, либо будет говорить потише, либо замолчит. Это остается на его усмотрение, особенно если он наглый, сильный или быстро бегает )
Напишем приложение, в котором реализуем пример с музыкой и звуком. При нажатии на одну кнопку будем запускать проигрывание музыки, а при нажатии на другую – воспроизводить короткий звук. И привяжем к этой схеме аудио-фокус.
Project name: P1281_AudioFocus
Build Target: Android 2.3.3
Application name: AudioFocus
Package name: ru.startandroid.develop.p1281audiofocus
Create Activity: MainActivity
Добавим строки в strings.xml:
Кнопка Music будет запускать музыку, а три другие кнопки – звук. Их три, потому что есть три разных типа фокуса, которые может запросить приложение. Мы протестируем все три.
В папку mnt/sdcard/Music/ поместите какой-нить файл с именем music.mp3. Например, его можно взять здесь. В папку res/raw поместите файл explosion.mp3, например отсюда.
В onCreate мы просто получаем AudioManager. Именно через него мы будем запрашивать фокус.
onClickMusic срабатывает при нажатии кнопки Music. Здесь мы создаем MediaPlayer и даем ему путь к файлу с музыкой. Методом setOnCompletionListener устанавливаем Activity, как получателя уведомления о окончании воспроизведения. Далее идет работа с фокусом. afListenerMusic – это слушатель (реализующий интерфейс OnAudioFocusChangeListener), который будет получать сообщения о потере/восстановлении фокуса. Он является экземпляром класса AFListener, который мы рассмотрим чуть дальше.
Фокус запрашивается с помощью метода requestAudioFocus. На вход необходимо передать:
— слушателя, который будет получать сообщения о фокусе
— тип потока
— тип фокуса
Тип фокуса говорит о том, насколько долго приложение собирается воспроизводить свой звук и насколько важно, чтобы другое приложение при этом замолчало. Всего есть три типа фокуса:
AUDIOFOCUS_GAIN – приложение дает понять, что оно собирается долго воспроизводить свой звук, и текущее воспроизведение должно приостановиться на это время
AUDIOFOCUS_GAIN_TRANSIENT – воспроизведение будет коротким, и текущее воспроизведение должно приостановиться на это время
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK – воспроизведение будет коротким, но текущее воспроизведение может просто на это время убавить звук и продолжать играть
Итак, мы запрашиваем фокус и говорим, что это надолго — AUDIOFOCUS_GAIN. Метод requestAudioFocus возвращает статус:
AUDIOFOCUS_REQUEST_FAILED = 0 – фокус не получен
AUDIOFOCUS_REQUEST_GRANTED = 1 – фокус получен
После того, как получили фокус, стартуем воспроизведение.
Метод onClickSound срабатывает при нажатии на любую из трех кнопок Sound. Здесь мы определяем, какая из трех кнопок была нажата. Тем самым мы в переменную durationHint пишем тип аудио-фокуса, который будем запрашивать. Далее создаем MediaPlayer, который будет воспроизводить наш звук взрыва из папки raw. Присваиваем ему слушателя окончания воспроизведения. Запрашиваем фокус с типом, который определили выше. Стартуем воспроизведение.
Метод onCompletion, срабатывает по окончании воспроизведения. Мы определяем, какой именно MediaPlayer закончил играть и методом abandonAudioFocus сообщаем системе, что больше не претендуем на аудио-фокус. На вход методу передаем того же слушателя, который давали при запросе фокуса.
В onDestroy освобождаем ресурсы и отпускаем фокус.
Класс AFListener реализует интерфейс OnAudioFocusChangeListener и является получателем сообщений о потере/восстановлении фокуса. При создании мы даем ему соответствующий MediaPlayer (позже станет понятно зачем) и текст, который нам понадобится для логов.
Метод onAudioFocusChange получает на вход статус фокуса этого приложения. Тут 4 варианта:
AUDIOFOCUS_LOSS – фокус потерян в результате того, что другое приложение запросило фокус AUDIOFOCUS_GAIN. Т.е. нам дают понять, что другое приложение собирается воспроизводить что-то долгое и просит нас пока приостановить наше воспроизведение.
AUDIOFOCUS_LOSS_TRANSIENT — фокус потерян в результате того, что другое приложение запросило фокус AUDIOFOCUS_GAIN_TRANSIENT. Нам дают понять, что другое приложение собирается воспроизводить что-то небольшое и просит нас пока приостановить наше воспроизведение
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK — фокус потерян в результате того, что другое приложение запросило фокус AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK. Нам дают понять, что другое приложение собирается воспроизводить что-то небольшое, и мы можем просто убавить звук, не приостанавливая воспроизведение
AUDIOFOCUS_GAIN – другое приложение закончило воспроизведение, звук снова наш
Пока что мы просто будем выводить в лог всю эту информацию, чтобы увидеть схему взаимодействия двух приложений с аудио-фокусом.
Все сохраним и запустим приложение. Жмем Music, воспроизведение музыки началось. В логах видим.
Т.е. музыка запросила фокус и получила его (статус = 1).
Жмем Sound G, чтобы воспроизвести звук взрыва и запросить фокус AUDIOFOCUS_GAIN.
Sound request focus, result: 1
Music onAudioFocusChange: AUDIOFOCUS_LOSS
Фокус запрошен и получен взрывом. А музыка получила уведомление о том, что фокус она потеряла (AUDIOFOCUS_LOSS).
Слышим звук взрыва. После того как звук взрыва закончился:
Sound: abandon focus
Music onAudioFocusChange: AUDIOFOCUS_GAIN
Срабатывает метод onCompletion, в котором взрыв отдает фокус (abandon focus). И, следовательно, музыка получает сообщение о том, что фокус снова ее (AUDIOFOCUS_GAIN).
Если дождаться, когда закончится музыка увидим такое сообщение.
Music: abandon focus
Музыка отдала фокус.
Как вы заметили, музыка все это время играла и никуда не делась. То, что она теряла фокус – не означает автоматически, что она остановится. Повторюсь, фокус – это только уведомление. А как приложение отреагирует на это уведомление – решать вам, как разработчику.
Кнопки Sound GT и Sound GTD срабатывают аналогично, я уже не буду их нажимать. Отличие будет в том, что взрыв будет запрашивать фокусы соответственно AUDIOFOCUS_GAIN_TRANSIENT и AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK. А музыка будет получать статусы AUDIOFOCUS_LOSS_TRANSIENT и AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK.
Т.е. мы увидели как одно приложение запрашивает определенный тип фокуса, а другое приложение видит этот тип и должно принимать соответствующие меры. Кстати о мерах. Давайте кроме логов реализуем и эти меры.
Перепишем метод onAudioFocusChange класса AFListener:
При потерях фокуса AUDIOFOCUS_LOSS и AUDIOFOCUS_LOSS_TRANSIENT ставим паузу. А при AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK – просто уменьшаем громкость. При получении же фокуса (AUDIOFOCUS_GAIN) возобновляем воспроизведение, если оно было приостановлено, и ставим громкость на максимум.
Я выбрал самые простые меры, чтобы не усложнять урок. Но их можно улучшить. Например, при потере фокуса надолго (AUDIOFOCUS_LOSS) можно освобождать ресурсы, и снова создавать MediaPlayer при получении фокуса. Либо можно вообще полностью отдать фокус (abandon), и тогда пользователю надо будет явно вернуться в ваше приложение, чтобы возобновить воспроизведение.
Когда вы запрашиваете фокус, метод requestFocus возвращает вам ответ, получилось захватить фокус или нет. Хелп рекомендует учитывать этот параметр и стартовать воспроизведение только при положительном результате (AUDIOFOCUS_REQUEST_GRANTED). Я, правда, не знаю как тут можно получить отрицательный результат. Если у кого есть соображения на этот счет – пишите на форуме.
На следующем уроке:
— пишем звук с помощью MediaRecorder
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник