Полный список
MediaPlayer – класс, который позволит вам проигрывать аудио/видео файлы с возможностью сделать паузу и перемотать в нужную позицию. MediaPlayer умеет работать с различными источниками, это может быть: путь к файлу (на SD или в инете), адрес потока, Uri или файл из папки res/raw.
Напишем небольшое приложение аудио-плеер и используем в нем все эти возможности.
Project name: P1261_MediaPlayer
Build Target: Android 2.3.3
Application name: MediaPlayer
Package name: ru.startandroid.develop.p1261mediaplayer
Create Activity: MainActivity
Добавляем строки в strings.xml:
Так это выглядит на экране
Кнопки верхнего ряда запускают проигрывание треков из различных источников. Кнопки среднего ряда – это пауза, возобновление, стоп и чекбокс повторения трека. А в нижнем ряду кнопки перемотки назад/вперед и вывод в лог текущей информации.
Создайте папку res/raw и положите в нее какой-нить звуковой файл с именем explosion.mp3. Например, его можно скачать здесь — http://dl.dropboxusercontent.com/u/6197740/explosion.mp3.
Разбираемся. Сначала создаем константы-пути, которые будет использовать проигрыватель. Это файл в инете (DATA_HTTP), поток в инете (DATA_STREAM), файл на флэшке (DATA_SD) и Uri на мелодию из системы (DATA_URI). Для SD и Uri укажите ваши значения, чтобы такие файлы существовали. (По получению Uri в конце урока есть вспомогательный код)
В onCreate получаем AudioManager, находим на экране чекбокс и настраиваем так, чтобы он включал/выключал режим повтора для плеера.
onClickStart – метод для обработки нажатий на кнопки верхнего ряда. Сначала мы освобождаем ресурсы текущего проигрывателя. Затем в зависимости от нажатой кнопки стартуем проигрывание. Какие методы для этого используются?
setDataSource – задает источник данных для проигрывания
setAudioStreamType – задает аудио-поток, который будет использован для проигрывания. Их существует несколько: STREAM_MUSIC, STREAM_NOTIFICATION и п. Подробнее их можно посмотреть в доках по AudioManager. Предполагаю, что созданы они для того, чтобы можно было задавать разные уровни громкости, например, играм, звонкам и уведомлениям. Этот метод можно и пропустить, если вам не надо явно указывать какой-то поток. Насколько я понял, по умолчанию используется STREAM_MUSIC.
Далее используется метод prepare или prepareAsync (в паре с OnPreparedListener). Эти методы подготавливают плеер к проигрыванию. И, как понятно из названия, prepareAsync делает это асинхронно, и, когда все сделает, сообщит об этом слушателю из метода setOnPreparedListener. А метод prepare работает синхронно. Соотвественно, если хотим прослушать файл из инета, то используем prepareAsync, иначе наше приложение повесится, т.к. заблокируется основной поток, который обслуживает UI.
Ну и метод start запускает проигрывание.
В случае с raw-файлом мы используем метод create. В нем уже будет выполнен метод prepare и нам остается только выполнить start.
Далее мы для плеера включаем/выключаем повтор (setLooping) в зависимости от текущего значения чекбокса. И вешаем слушателя (setOnCompletionListener), который получит уведомление, когда проигрывание закончится.
В методе releaseMP мы выполняем метод release. Он освобождает используемые проигрывателем ресурсы, его рекомендуется вызывать когда вы закончили работу с плеером. Более того, хелп рекомендует вызывать этот метод и при onPause/onStop, если нет острой необходимости держать объект.
В методе onClick мы обрабатываем нажатия на кнопки управления проигрывателем. Какие здесь используются методы?
start – возобновляет проигрывание
seekTo – переход к определенной позиции трека (в милисекундах)
getCurrentPosition – получить текущую позицию (в милисекундах)
getDuration – общая продолжительность трека
isLooping – включен ли режим повтора
getStreamVolume – получить уровень громкости указанного потока
Далее идут методы
onPrepared – метод слушателя OnPreparedListener. Вызывается, когда плеер готов к проигрыванию.
onCompletion – метод слушателя OnCompletionListener. Вызывается, когда достигнут конец проигрываемого содержимого.
В методе onDestroy обязательно освобождаем ресурсы проигрывателя.
В манифесте добавляем права на интернет — android.permission.INTERNET.
Все сохраняем, запускаем приложение. Дизайн получившегося плеера, конечно, не ахти какой :), но нас сейчас интересует функционал.
Еще раз перечислю возможные действия. Нажимая верхние кнопки, мы запускаем проигрывание из различных источников. Кнопки среднего ряда позволят нам поставить паузу, возобновить/остановить проигрывание и включить режим повтора. Кнопки нижнего ряда перематывают назад/вперед на 3 сек (3000 мсек) и выводят инфу в лог.
Я включу проигрывание файла с SD и выведу инфу в лог (кнопка Info).
start SD
Playing true
Time 4702 / 170588
Looping false
Volume 10
Проигрывание идет, текущая позиция – 4-я секунда из 170, режим повтора выключен, громкость — 10.
Уменьшу громкость (кнопками устройства или эмулятора), включу режим повтора (чекбокс Loop), поставлю паузу (кнопка Pause) и снова выведу инфу в лог:
Playing false
Time 46237 / 170588
Looping true
Volume 6
Видим, что проигрывание остановилось, текущая позиция уже 46 секунд, режим повтора включен, а громкость уменьшилась до 6.
Теперь включу проигрывание потока (кнопка Stream). Смотрим лог:
08:49:13.799: D/myLogs(18805): start Stream
08:49:13.809: D/myLogs(18805): prepareAsync
08:49:27.589: D/myLogs(18805): onPrepared
Обратите внимание, сколько прошло времени с начала (prepareAsync) до завершения (onPrepared) подготовки проигрывателя – 14 секунд. Если бы мы использовали метод prepare, а не prepareAsync, то наше приложение было бы недоступно все это время.
Расскажу еще про несколько методов, которые я не использовал в примере, но о которых стоит знать.
Метод reset – сбрасывает плеер в начальное состояние, после него необходимо снова вызвать setDataSource и prepare. Похож на onRelease, но позволяет продолжить работу с этим же объектом. А вот после onRelease надо создавать новый объект MediaPlayer.
Метод setOnBufferingUpdateListener устанавливает слушателя буферизации проигрываемого потока. По идее слушатель будет получать процент буферизации, но у меня оно как-то странно работает — показывает или 0 или 100.
Метод setOnErrorListener устанавливает слушателя для получения ошибок. Особенно это полезно при методe prepareAsync. Если в ходе этого метода возникнут ошибки, то их можно поймать только так.
Метод setWakeMode позволяет ставить стандартную (PowerManager.WakeLock) блокировку на время проигрывания, указав тип блокировки. Не забудьте в манифесте добавить права на WAKE_LOCK.
По поводу видео. Метод setDisplay позволяет указать плееру, куда выводить изображение. Размещаете на экране компонент SurfaceView (вкладка Advanced), вызываете его метод getHolder и полученный объект передаете в setDisplay. Плеер выведет изображение на этот компонент.
Чтобы узнать размер проигрываемого изображения можно использовать методы getVideoHeight и getVideoWidth.
В хелпе класса MediaPlayer есть хорошая схема состояний плеера. Она кажется запутанной, но если посидеть и поразбираться, то вполне можно все понять. Схема полезная, советую вникнуть.
А здесь можно посмотреть какие форматы поддерживаются системой.
Также хелп любезно предоставляет нам код, который позволит просмотреть существующие медиа-файлы в системе:
На следующем уроке:
— работаем с SoundPool
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Android MediaPlayer. Расширяем возможности с помощью прокси
Реализация прокси для стандартного компонента MediaPlayer несёт в себе гораздо больше преимуществ, чем может показаться на первый взгляд. В этой статье подробно рассказывается, как это всё работает и о перспективах развития подобной технологии.
Долгое время я искал наиболее удачную реализацию медиаплеера под андроид для своего приложения Media Library. Я испытывал ExoPlayer. У него нет банальной поддержки flv. На что разработчики сказали, что нет времени на добавление поддержки этого формата. Далее я стал использовать Vitamio. И Vitamio показался сначала более менее подходящим решением, но пообщавшись с его разработчиком Crossle я узнал, что помощи от него не ждать. Баги он не исправляет и вообще уже почти никто не занимается этим проектом. Так как багов было очень много, я вернулся на стандартный плеер. Он мне показался наиболее быстрым и безглючным. Конечно в нём нет столько функций, как в ExoPlayer или Vitamio. Поэтому я начал изучать, как добавить их самостоятельно.
VideoView пользоваться я не стал и сделал кастомный класс плеера. В него я добавил много своих Event-ов на все случаи жизни. Плеер основан на textureView. Искал способ, как заставить плеер играть в фоновом режиме и при пересоздании Activity. С SurfaceView такого не получилось. TextureView — работает отлично. Верти, крути… а музыка играет. В сервис переводить плеер я так и не нашёл причины. А зачем, когда и так всё хорошо работает. Как только причина появиться — сервис будет создан.
Задачи было две простых — создать кеш для плеера и заставить его играть из FTP. Об этом меня попросил один пользователь моего плеера. Ему хочется слушать музыку со своего NASа, который поддерживает FTP протокол.
Как программисту мне понятно, что сначала надо было написать прокси и через это прокси организовать конвертацию HTTP<>FTP запросов и передачу данных. Что я и сделал в своём проекте ImmortalPlayer.
В перспективе эту прокси можно использовать с VItamio (у которого с кэшем всё не так хорошо) и тогда получится совсем комбаин, который играет все форматы и поддерживает все протоколы)), но не рекомендую так делать. Так как разнообразие форматов — это не только польза, но и зло. Для плеера нужна поддержка основных форматов и некоторых второстепенных, а не всех подряд. Это приведёт к чистоте и повышению качества материала у пользователей. Кроме этого поддержка форматов должна быть на системном уровне, встроенными кодеками, а не на уровне плеера. Vitamio — имеет множество багов с рассинхронизацией звука и видео, низкую производительность и увеличивает размер приложения apk на 10-15 мегабайт.
Определяем права доступа:
AndroidManifest.xml
Wake_Lock — для того что бы плеер не прекращал играть, когда устройство находится в спящем режиме.
WRITE_EXTERNAL_STORAGE — для записи кэша на внешний носитель устройства.
Присваиваем новый экземпляр класса переменной proxy. И задаём параметры:
proxy.setPaths(папка куда сохранять кэш, сама ссылка на файл в интернете, размер кэша, количество файлов в кэше, контекст, удалять ли не докаченные файлы?, логин ФТП, пароль ФТП, режим совместимости FTP?);
Получаем путь прокси для плеера String proxyUrl = proxy.getLocalURL();
Стартуем плеер textureView.setVideoPath(proxyUrl);
Прокси стартует уже со строки proxy = new HttpGetProxy();. Есть команда на остановку proxy.stopProxy(). По этой команде закрывается локальный сокет, ожидающий запросов от плеера и после этого завершается Thread прокси. Грубо, но пока не нашёл более мягкого выхода.
Не сложно догадаться, как сделать прокси только для чтения. Что бы ничего не писалось на карту памяти. Пока это не реализовано, но можно поставить по нулям значения BUFFER_SIZE, NUM_FILES и все файлы будут удаляться.
Алгоритм работы плеера:
Написав первую версию прокси я столкнулся с особенностями плеера. Не знаю с чем это связано, но проблемы возникают с большими flac файлами. Плеер не совсем рассчитан на такого рода файлы и поэтому имеет ещё баги в алгоритме их воспроизведения.
Типичное воспроизведение начинается с двух запросов. Почему не с одного для меня до сих пор загадка. Т.е. плеер запрашивает файл целиком, читает первые 50кб-100кб и сбрасывает подключение. Запрашивает файл целиком второй раз и начинает читать опять с начала файл. Далее запросы становятся с указанием начального байта.
Пробовал сделать двух поточную прокси и думал, что он пытается читать в двух потоках, но плеер упорно не хотел присылать два запроса одновременно. Только последовательно. Причём первое соединение он рвёт почти сразу.
Так вот это не все проблемы. По какой-то странной особенности после первой порции файла, плеер начинает читать конец файла. Выбирает какую-то точку перед концом и читает до конца. Например, последние 200кб. Видимо ищет какие-то данные, которые не нашёл в начале или проверяет, что-то. Затем продолжает читать сначала.
Проблемы с flac файлами — это низкая скорость прокси при перемотке и плохой алгоритм. При перемотке flac плеер ищет подходящее начало потока. Т.е. он не с любого байта может начать играть. Поэтому делает 1-6 запросов. Видимо в этот момент задействуются какие-то таймауты, что бы плеер работал быстро, поэтому я увеличил скорость работы сокетов таймаутом 1500ms… Без таймаута сокеты подолгу висят в бездействии и вместо 1-6 запросов происходит только 1-2. Что снижает вероятность того что плеер найдёт начало. А если плеер не находит начало, то выдаёт ошибку о не поддерживаемом формате. Таймаут полностью решает проблему зависания сокетов, но не решает проблему работы алгоритма плеера. Так как даже без прокси при перемотке flac файлов плеер периодически выдаёт ошибку «не поддерживаемый формат».
Проверено — плеер делает одинаковое число запросов, как с прокси, так и без. С прокси запросы выполняются немного медленней. Примерно на 50-200мс… Из-за различных преобразований и открытия\закрытия сокетов, что было максимально уменьшено в последних версиях.
Алгоритм работы HTTP, FTP:
Именно процедура закрытия сокетов или потоков отнимает больше всего времени. Именно от этих процедур и можно отказаться в некоторых случаях для увеличения быстродействия.
С помощью FTP команды RETR происходит получение файла. Команда ABOR необходима для корректного прерывания передачи файла. Да, это правильно и хорошо. Только ведь некоторые FTP сервера могут работать сразу принимая новую команду. Т.е. без команды ABOR — сразу REST, RETR и уже получаем новый файл с указанного в REST байта. Отправка команды ABOR занимает около 100мс… Это ухудшает комфорт и скорость прокси. Для этого и создан «режим совместимости FTP». В этом режиме происходит отправка команды ABOR. Как я ни пытался автоматизировать выбор режима работы — не получилось. Поэтому сделал вручную. Без команды ABOR тоже должно работать. Просто будет происходить новая авторизация, вместо прерывания и может возникнуть проблема с сессиями. Необходимо смотреть лог FTP — если авторизация происходит почти каждый раз, то лучше включить режим.
Настройки FTP сервера:
Особо тестов было не много. Точно должно работать в пассивном режиме с открытом 21 портом для авторизации и 1000 портов (может меньше) для передачи данных. 1000 портов должно быть открыто на фаерволе и указано в фтп сервере. Число одновременных сессий 10. Таймаут сессии 10 минут. Таймаут подключения 1 минута.
Алгоритм работы HTTP прокси:
Цифрами обозначена последовательность действий.
Для FTP сервера последовательность будет немного другой. В прокси есть ещё алгоритмы перехода на чтение с локального файла, если недоступен интернет или нет ответа от сервера. Местами алгоритмы очень сложные. Я максимально старался создавать короткие конструкции. Думаю ещё можно что-то уменьшить.
Для реализации FTP протокола я использовал библиотеку Apache Commons Net. Она поддерживает и некоторые другие протоколы: FTPS,FTP over HTTP (experimental),NNTP,SMTP(S),POP3(S),IMAP(S),Telnet,TFTP,Finger,Whois,rexec/rcmd/rlogin,Time (rdate) and Daytime,Echo,Discard,NTP/SNTP.
Думаю на основе тех из них, которые поддерживают приём данных из удалённого сервера и старт с определённого байта можно сделать прокси для плеера и играть мультимедиа.
Алгоритм сохранения файла:
Нуждается в доработке, но даже сейчас работает прекрасно. Суть проста — если вы прослушали весь файл, то он переименовывается в оригинальное расширение и имя. Проверка осуществляется с точностью до байта в момент окончания чтения файла. Если переименовывать не расширение, а имя файла, то мультимедиа сканер устройства поймёт что это какое-то мультимедиа и начнёт его сканировать. И если вдруг файл окажется битым вы получите 100% загрузку процессора до перезагрузки устройства. Возможно в разных андроид системах что-то по разному, но у меня на 4.4.2 так.
Проблема сейчас в том что кэшируется только то, что запрашивает и читает плеер. Нужна организация какой-то докачки в то время, когда плеер ничего не читает, что бы не нагружать канал и прекращение её без задержки для запроса плеера. Пока не получается уловить момент, когда плеер ничего не читает из интернета.
Источник