Playing sound android studio

Полный список

— работаем с SoundPool

На прошлом уроке мы рассмотрели MediaPlayer, который подходит для проигрывания продолжительных треков. SoundPool же подходит для случая, когда вам необходимо многократное воспроизведение небольших файлов. Самый простой пример – игры. Когда есть короткий звук какого-либо действия (прыжка, выстрела и т.п.) и этот звук достаточно часто необходимо воспроизводить. SoundPool один раз загружает этот звук в память и оттуда его воспроизводит, что, конечно, неплохо в плане производительности.

Класс SoundPool несложен. Позволяет загрузить звук в память, проиграть его, ставить паузу, регулировать громкость (левый и правый каналы), менять скорость воспроизведения, ограничивать кол-во одновременно воспроизводимых звуков, использовать приоритеты.

Напишем простое приложение, в котором рассмотрим все эти возможности.

Project name: P1271_SoundPool
Build Target: Android 2.3.3
Application name: SoundPool
Package name: ru.startandroid.develop.p1271soundpool
Create Activity: MainActivity

Добавим строки в strings.xml:

Только кнопка для запуска воспроизведения.

Для тестов нам понадобится пара звуков. Я буду использовать explosion.ogg (взрыв) и shot.ogg (выстрел). Поместим explosion.ogg в папку assets, а shot.ogg в папку res/raw.

Сначала создаем SoundPool. На вход ему передаем:
— максимальное кол-во одновременно воспроизводимых файлов
— аудио-поток, который будет использоваться
— некий параметр качества, который пока что игнорируется системой. Рекомендуется передавать туда 0

Методом setOnLoadCompleteListener мы устанавливаем слушателя загрузки. Загрузка аудио-файлов происходит асинхронно, и по ее окончании срабатывает метод onLoadComplete этого слушателя.

Далее загружаем файлы. Для этого используются различные варианты метода load. Чтобы загрузить файл из raw, необходимо указать Context, ID raw-файла и приоритет. Приоритет пока что также игнорируется системой, рекомендуется передавать туда 1.

Чтобы загрузить файл из assets используем другую реализацию метода load, которая на вход требует AssetFileDescriptor и приоритет. AssetFileDescriptor можно получить, используя метод openFd класса AssetManager, указав имя файла. Приоритет снова никакой роли не играет, передаем 1.

У метода load есть несколько реализаций, я специально показал две разных для наглядности. В хелпе вы можете найти ту, которая вам более подходит.

Метод load возвращает нам ID загруженного файла. Используя этот ID, мы будем проигрывать файл.

В методе onClick запускаем воспроизведение файлов. Для этого используется метод play. На вход он требует ряд параметров:
— ID файла. Тот самый, который мы получили от метода load.
— громкость левого канала (от 0.0 до 1.0)
— громкость правого канала (от 0.0 до 1.0)
— приоритет. Этот приоритет уже не декоративный, а вполне себе используемый. Далее увидим, где он нужен.
— количество повторов. Т.е. файл будет воспроизведен один раз точно + то количество раз, которое вы здесь укажете
— скорость воспроизведения. Можно задать от половины нормальной скорости до двойной (0.5 — 2).

Метод play возвращает ID потока, используемого для проигрывания файла. Этот ID можно использовать для дальнейшего изменения настроек в процессе проигрывания файла, а также для паузы.

Метод onLoadComplete слушателя OnLoadCompleteListener выполняется, когда SoundPool загружает файл. На вход вы получаете сам SoundPool, ID файла (тот же, что и load возвращал) и статус (0, если успешно)

Все сохраним и запустим приложение.

08:38:12.726: D/myLogs(333): soundIdShot = 1
08:38:12.726: D/myLogs(333): soundIdExplosion = 2
08:38:12.855: D/myLogs(333): onLoadComplete, sampleId = 1, status = 0
08:38:12.976: D/myLogs(333): onLoadComplete, sampleId = 2, status = 0

Видно, что загрузка звуков заняла определенное время. Жмем Play и слышим, как приложение играет оба звука одновременно.

Параметры метода play

Давайте поменяем громкость. Перепишем onClick:

Теперь один звук более слышен слева, а другой справа.

Поменяем кол-во повторов:

Теперь звук выстрела воспроизведен 6 раз (1 + 5 повторов), а звук взрыва – 3 раза (1 + 2 повтора).

Звук выстрела стал в два раза медленнее, а звук взрыва – в два раза быстрее.

Приоритет

Теперь давайте разберемся с приоритетом и кол-вом одновременно проигрываемых звуков. Хелп гласит:

«Priority runs low to high, i.e. higher numbers are higher priority. Priority is used when a call to play() would cause the number of active streams to exceed the value established by the maxStreams parameter when the SoundPool was created. In this case, the stream allocator will stop the lowest priority stream. If there are multiple streams with the same low priority, it will choose the oldest stream to stop. In the case where the priority of the new stream is lower than all the active streams, the new sound will not play and the play() function will return a streamID of zero.«

Читайте также:  Тень объекта android studio

Т.е. приоритет используется, когда кол-во звуков, которое необходимо воспроизводить в данный момент, превышает максимально допустимое кол-во (мы его указывали при создании SoundPool, константа MAX_STREAMS). В этом случае звуки с низким приоритетом будут остановлены. Если есть несколько звуков с одинаково низким приоритетом, то останавливаются более старые. Если максимальное количество звуков уже достигнуто, и вы пытаетесь воспроизвести звук с низким приоритетом, то он не будет проигран, а метод play вернет 0 вместо ID потока.

Давайте проведем тесты и понаблюдаем все это в действии.

Звука у нас всего два. Поэтому, чтобы добиться конфликта, давайте для нашего SoundPool поставим кол-во одновременно воспроизводимых звуков равное одному:

onClick перепишем так:

Я, конечно, понимаю, что ставить паузу в основном потоке это #непофэншуюивообщенекомильфо, но в данном уроке я считаю это допустимым, чтобы не городить лишний код. Так что не обращайте на это внимание )

Мы начнем проигрывать звук выстрела 11 раз, а спустя пару секунд запустим звук взрыва. Выстрелы к тому моменту еще не успеют отзвучать, поэтому поглядим, как поведет себя система в данной ситуации. Напоминаю, что у нас сейчас установлен лимит на кол-во звуков = 1.

Заодно будем выводить в лог ID потоков, которые возвращает нам метод play.

Все сохраняем, запускаем, жмем Play. Я слышу 4 выстрела, потом взрыв. Т.е. при попытке воспроизвести взрыв система видит, что надо одновременно играть два звука: уже звучащий — выстрел и новый — взрыв. А лимит позволяет в один момент времени играть только один звук. Приоритеты у звуков одинаковые. Следовательно, система останавливает более старый звук, в нашем случае это выстрелы.

09:24:35.035: D/myLogs(754): streamIDShot = 1
09:24:37.092: D/myLogs(754): streamIDExplosion = 2

Разница в две секунды. ID потоков получены по обоим звукам.

Попробуем выставить для выстрелов приоритет более высокий, чем у взрыва.

Нарисуем у выстрела приоритет = 1, а у взрыва остался 0.

Сохраняем, запускаем, жмем Play.

Прога воспроизводит все выстрелы. Звук взрыва вообще не воспроизводится. Как и в прошлый раз у системы был выбор, но в этот раз она отменила взрыв и продолжила играть выстрелы, т.к. приоритеты выстрелов выше.

09:28:32.906: D/myLogs(788): streamIDShot = 1
09:28:34.934: D/myLogs(788): streamIDExplosion = 0

Видим, что для взрыва метод play вернул 0 вместо ID потока. Это значит, что звук не воспроизводился.

Пауза

Давайте посмотрим на возможности паузы. Мы можем приостановить какие-то определенные звуки, а можем все сразу.

Увеличим снова лимит звуков:

Мы запускаем проигрывание 10 выстрелов и 6 взрывов. Через 2 секунды приостанавливаем выстрелы, а еще через 3 секунды возобновляем. Для этого используются методы pause и resume, на вход которым даем ID потока из play.

Сохраняем, запускаем, жмем Play. Слышим выстрелы и взрывы. Затем выстрелы стихают, остаются только взрывы. Затем выстрелы снова возобновляются и все стихает. Counter-terrorist win )

Попробуем приостановить все звуки.

Для этого используем методы autoPause и autoResume. При проигрывании у нас будет пауза в три секунды, потом все возобновится.

Меняем уже играющий звук

Пока звук воспроизводится, вы можете повлиять на его характеристики, которые были заданы в методе play:

На вход методам идет ID потока (который вернул play) и необходимое вам значение параметра.

Например, поработаем с громкостью:

Выстрелы начнут играть в одном канале, взрывы — в другом. А через две секунды они поменяются каналами.

Выгрузка

Осталось упомянуть про еще два метода.

unload – выгрузит указанный звук из памяти. На вход требует ID звука, который возвращал метод load.

release – освобождает все ресурсы SoundPool, более он не сможет быть используемым и ссылку на него необходимо об-null-ить.

На следующем уроке:

— используем Audio Focus

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

Читайте также:  Что лучше для планшета ios или android

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Android и звук: как делать правильно

В статье рассматривается архитектура и API для создания приложений, воспроизводящих музыку. Мы напишем простое приложение, которое будет проигрывать небольшой заранее заданный плейлист, но «по-взрослому» — с использованием официально рекомендуемых практик. Мы применим MediaSession и MediaController для организации единой точки доступа к медиаплееру, и MediaBrowserService для поддержки Android Auto. А также оговорим ряд шагов, которые обязательны, если мы не хотим вызвать ненависти пользователя.

В первом приближении задача выглядит просто: в activity создаем MediaPlayer, при нажатии кнопки Play начинаем воспроизведение, а Stop — останавливаем. Все прекрасно работает ровно до тех пор, пока пользователь не выйдет из activity. Очевидным решением будет перенос MediaPlayer в сервис. Однако теперь у нас встают вопросы организации доступа к плееру из UI. Нам придется реализовать binded-сервис, придумать для него API, который позволил бы управлять плеером и получать от него события. Но это только половина дела: никто, кроме нас, не знает API сервиса, соответственно, наша activity будет единственным средством управления. Пользователю придется зайти в приложение и нажать Pause, если он хочет позвонить. В идеале нам нужен унифицированный способ сообщить Android, что наше приложение является плеером, им можно управлять и что в настоящий момент мы играем такой-то трек из такого-то альбома. Чтобы система со своей стороны подсобила нам с UI. В Lollipop (API 21) был представлен такой механизм в виде классов MediaSession и MediaController. Немногим позже в support library появились их близнецы MediaSessionCompat и MediaControllerCompat.

Следует сразу отметить, что MediaSession не имеет отношения к воспроизведению звука, он только об управлении плеером и его метаданными.

MediaSession

Итак, мы создаем экземпляр MediaSession в сервисе, заполняем его сведениями о нашем плеере, его состоянии и отдаем MediaSession.Callback, в котором определены методы onPlay, onPause, onStop, onSkipToNext и прочие. В эти методы мы помещаем код управления MediaPlayer (в примере воспользуемся ExoPlayer). Наша цель, чтобы события и от аппаратных кнопок, и из окна блокировки, и с часов под Android Wear вызывали эти методы.

Полностью рабочий код доступен на GitHub (ветка master). В статьи приводятся только переработанные выдержки из него.

Для доступа извне к MediaSession требуется токен. Для этого научим сервис его отдавать

и пропишем в манифест

MediaController

Теперь реализуем activity с кнопками управления. Создаем экземпляр MediaController и передаем в конструктор полученный из сервиса токен.

MediaController предоставляет как методы управления плеером play, pause, stop, так и коллбэки onPlaybackStateChanged(PlaybackState state) и onMetadataChanged(MediaMetadata metadata). К одному MediaSession могут подключиться несколько MediaController, таким образом можно легко обеспечить консистентность состояний кнопок во всех окнах.

Наша activity работает, но ведь идея исходно была, чтобы из окна блокировки тоже можно было управлять. И тут мы приходим к важному моменту: в API 21 полностью переделали окно блокировки, теперь там отображаются уведомления и кнопки управления плеером надо делать через уведомления. К этому мы вернемся позже, давайте пока рассмотрим старое окно блокировки.

Как только мы вызываем mediaSession.setActive(true), система магическим образом присоединяется без всяких токенов к MediaSession и показывает кнопки управления на фоне картинки из метаданных.

Однако в силу исторических причин события о нажатии кнопок приходят не напрямую в MediaSession, а в виде бродкастов. Соответственно, нам надо еще подписаться на эти бродкасты и перебросить их в MediaSession.

MediaButtonReceiver

Для этого разработчики Android любезно предлагают нам воспользоваться готовым ресивером MediaButtonReceiver.

Добавим его в манифест

MediaButtonReceiver при получении события ищет в приложении сервис, который также принимает «android.intent.action.MEDIA_BUTTON» и перенаправляет его туда. Поэтому добавим аналогичный интент-фильтр в сервис

Если подходящий сервис не найден или их несколько, будет выброшен IllegalStateException.

Теперь в сервис добавим

Метод handleIntent анализирует коды кнопок из intent и вызывает соответствующие коллбэки в mediaSession. Получилось немного плясок с бубном, но зато почти без написания кода.

На системах с API >= 21 система не использует бродкасты для отправки событий нажатия на кнопки и вместо этого напрямую обращается в MediaSession. Однако, если наш MediaSession неактивен (setActive(false)), его пробудят бродкастом. И для того, чтобы этот механизм работал, надо сообщить MediaSession, в какой ресивер отправлять бродкасты.
Добавим в onCreate сервиса

Читайте также:  G сенсор для android

На системах с API Так это выглядит

Android 4.4

MIUI 8 (базируется на Android 6, то есть теоретически окно блокировки не должно отображать наш трек, но здесь уже сказывается кастомизация MIUI).

Уведомления

Однако, как ранее упоминалось, начиная с API 21 окно блокировки научилось отображать уведомления. И по этому радостному поводу, вышеописанный механизм был выпилен. Так что теперь давайте еще формировать уведомления. Это не только требование современных систем, но и просто удобно, поскольку пользователю не придется выключать и включать экран, чтобы просто нажать паузу. Заодно применим это уведомление для перевода сервиса в foreground-режим.

Нам не придется рисовать кастомное уведомление, поскольку Android предоставляет специальный стиль для плееров — Notification.MediaStyle.

Добавим в сервис два метода

И добавим вызов refreshNotificationAndForegroundStatus(int playbackState) во все коллбэки MediaSession.

Android 4.4

Android 7.1.1

Android Wear

Started service

В принципе у нас уже все работает, но есть засада: наша activity запускает сервис через binding. Соответственно, после того, как activity отцепится от сервиса, он будет уничтожен и музыка остановится. Поэтому нам надо в onPlay добавить

Никакой обработки в onStartCommand не надо, наша цель не дать системе убить сервис после onUnbind.

А в onStop добавить

В случае, если к сервису привязаны клиенты, stopSelf ничего не делает, только взводит флаг, что после onUnbind сервис можно уничтожить. Так что это вполне безопасно.

ACTION_AUDIO_BECOMING_NOISY

Продолжаем полировать сервис. Допустим пользователь слушает музыку в наушниках и выдергивает их. Если эту ситуацию специально не обработать, звук переключится на динамик телефона и его услышат все окружающие. Было бы хорошо в этом случае встать на паузу.
Для этого в Android есть специальный бродкаст AudioManager.ACTION_AUDIO_BECOMING_NOISY.
Добавим в onPlay

В onPause и onStop

И по факту события встаем на паузу

Android Auto

Начиная с API 21 появилась возможность интегрировать телефон с экраном в автомобиле. Для этого необходимо поставить приложение Android Auto и подключить телефон к совместимому автомобилю. На экран автомобиля будет выведены крупные контролы для управления навигацией, сообщениями и музыкой. Давайте предложим Android Auto наше приложение в качестве поставщика музыки.

Если у вас под рукой нет совместимого автомобиля, что, согласитесь, иногда бывает, можно просто запустить приложение и экран самого телефона будет работать в качестве автомобильного.

Исходный код выложен на GitHub (ветка MediaBrowserService).

Прежде всего надо указать в манифесте, что наше приложение совместимо с Android Auto.
Добавим в манифест

Здесь automotive_app_desc — это ссылка на файл automotive_app_desc.xml, который надо создать в папке xml

Преобразуем наш сервис в MediaBrowserService. Его задача, помимо всего ранее сделанного, отдавать токен в Android Auto и предоставлять плейлисты.

Поправим декларацию сервиса в манифесте

Во-первых, теперь наш сервис экспортируется, поскольку к нему будут подсоединяться снаружи.

И, во-вторых, добавлен интент-фильтр android.media.browse.MediaBrowserService.

Меняем родительский класс на MediaBrowserServiceCompat.

Поскольку теперь сервис должен отдавать разные IBinder в зависимости от интента, поправим onBind

Имплементируем два абстрактных метода, возвращающие плейлисты

И, наконец, имплементируем новый коллбэк MediaSession

Здесь mediaId — это тот, который мы отдали в setMediaId в onLoadChildren.

Плейлист

Трек

UPDATE от 27.10.2017: Пример на GitHub переведен на targetSdkVersion=26. Из релевантных теме статьи изменений необходимо отметить следующее:

  • android.support.v7.app.NotificationCompat.MediaStyle теперь deprecated. Вместо него следует использовать android.support.v4.media.app.NotificationCompat.MediaStyle. Соответственно, больше нет необходимости использовать android.support.v7.app.NotificationCompat, теперь можно использовать android.support.v4.app.NotificationCompat
  • Метод AudioManager.requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) теперь тоже deprecated. Вместо него надо использовать AudioManager.requestAudioFocus(AudioFocusRequest focusRequest). AudioFocusRequest — новый класс, добавленный с API 26, поэтому не забывайте проверять на API level.
    Создание AudioFocusRequest выглядит следующим образом

Теперь запрашиваем фокус

Разумеется, все вышеописанные изменения вносить необязательно, старые методы работать не перестали.

Вот мы и добрались до конца. В целом тема эта довольно запутанная. Плюс отличия реализаций на разных API level и у разных производителей. Очень надеюсь, что я ничего не упустил. Но если у вас есть, что исправить и добавить, с удовольствием внесу изменения в статью.

Еще очень рекомендую к просмотру доклад Ian Lake. Доклад от 2015 года, но вполне актуален.

Источник

Оцените статью