Поддержка воспроизведения MP3 и WAV форматов в Java
Знакомство
Здравствуй, дорогой читатель! Хочу донести до тебя информацию о том, что данная статья не является единственным и стопроцентно правильным решением проблемы одновременного проигрывания mp3 и wav файлов. В статье всего лишь описан способ, которым получилось решить данную проблему. Я опишу шаги, которые мне пришлось сделать для реализации поставленной задачи, а так же опишу трудности, которые пришлось преодолеть, ошибки, которые пришлось совершить (я нечаянно, честное слово 🙂 ). Так же отмечу, что это моя первая статья такого рода, так что прошу не судить строго. И так, приступим…
Задача и проблематика
Передо мной была поставлена задача – сделать одновременное проигрывание mp3 и wav файлов в java-апплете. При этом звуковые файлы должны присылаться с сервера в виде потока байтов (файлы должны быть закодированы в base64, но это не имеет значения 🙂 ).
Проблема в том, что стандартными средствами java невозможно проигрывать mp3 файлы (разработка велась на java 6). Единственное подходящее стандартное средство для проигрывания mp3 – Java Media Framework, которое мне не подходило, потому как требовало дополнительной установки, а это лишние действия для пользователя.
Поиски решения
Для проигрывания mp3 нужно было найти библиотеку, главный критерий поиска – размер. Так как апплет – это приложение, которое открывается в браузере, то он постоянно загружается с сервера, соответственно размер апплета должен быть минимальным, для его быстрой загрузки.
Скитаясь по страницам гугла, я наткнулся на несколько библиотек, но самой маленькой и к тому же самой популярной из них оказался проект JLayer. Размер jar-ника в 100кб меня вполне устроил, к тому же внутри него есть ещё и конвертер, и декодер mp3, которые можно использовать… ну или удалить их, тем самым, сделав библиотеку ещё легковеснее :).
Начало реализации
Пришедшие с сервера звуковые файлы я сохранял во временную директорию, которая чистилась после закрытия апплета, с рандомным именем. Использовал я такой подход потому, что мне не было известно, какой тип файл приходит: wav или mp3.
Далее я подключил библиотеку к maven проекту апплета:
<dependencies>
<dependency>
<groupId>javazoom</groupId>
<artifactId>jlayer</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
Добавил данную библиотеку в jar-ник апплета с помощью maven-assembly-plugin:
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
assembly.xml
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-
plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-
assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/
assembly-1.1.0.xsd">
<id>with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>true</unpack>
<useTransitiveDependencies>false
</useTransitiveDependencies>
<excludes>
<exclude>org.easytesting:*</exclude>
<exclude>junit:*</exclude>
<exclude>org.apache.maven.plugins:maven-surefire-
report-plugin</exclude>
<exclude>net.sourceforge.jexcelapi:*</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>
Эта библиотека оказалось очень простой в использовании, нужно было всего лишь создать объект класса javazoom.jl.player.Player и далее вызвать у него метод play().
FileInputStream stream = new FileInputStream(soundFile.getPath());
Player player = new Player(stream);
player.play();
Отмечу, что проигрывание звука происходит в том же потоке, в котором запущен метод play(). Соответственно поток, в котором вызван данный метод блокируется во время проигрывания звука. Это позволяет удобно отслеживать окончание проигрывания звука, если необходимо выполнять какие-либо действия после проигрывания звука.
Для тестирования я использовал mp3 файл с записанным “мяуканьем” котёнка и wav файл с записанным “гавканьем” собаки. Радости моей не было предела, когда использовав вышеприведённый код я услышал столь ожидаемое заветное “мяуканье”. Но радоваться пришлось не долго, так как “гавканья” я не услышал :(. Соответственно библиотека не может воспроизводить wav файлы . Пришлось обратиться к гуглу…
Пляски с бубном
Изначально я подумал: “Как одна из самых популярных библиотек по проигрыванию mp3 на java не может поддерживать wav?”. Но после нескольких минут поисков в гугле я понял, что мой вопрос останется без ответа… Единственное, что в данной ситуации могло помочь – это перекодирование wav в mp3, что средствами JLayer можно было сделать. Но от этого решения я отказался сразу, так как перекодирование – процесс довольно ресурсозатратный, а для апплета, который должен работать на самых слабых машинах, это было недопустимо.
Я решил всё же не отказываться от библиотеки, а использовать для проигрывания wav стандартные средства java.
AudioClip audioClip = applet.getAudioClip(getMediaFile().toURL());
audioClip.play();
Данный код отлично проигрывает wav файлы. Правда, процесс проигрывания менее контролируемый, чем при использовании библиотеки, так как звук в данном случае проигрывается в отдельном потоке, и для определения “окончания проигрывания” звука придётся использовать какие-нибудь другие способы. В эту сторону я больше не размышлял, так как для wav-файлов действий после проигрывания у меня не было.
Теперь осталось лишь заставить проигрывать mp3 с помощью библиотеки, а wav стандартными средствами. Для этого нужно определить, какой файл является mp3, а какой wav…
Кто есть кто?
Изначально у меня появилась идея залезть в файл, открыть его как строку, и посмотреть какое начало у разных типов звуков. Но почему-то это решение мне показалось очень “костыльным”, а хотелось сделать что-то более изящное…в дальнейшем я об этом пожалею, но обо всём по порядку 🙂
Я решил воспользоваться декодером из библиотеки JLayer.
FileInputStream stream = new FileInputStream(audioFile.getPath());
Header header = new Bitstream(stream).readFrame();
if (header.toString().contains("Layer III")) {
final Player player = new Player(stream);
// play mp3
player.play();
} else {
// play wav file
}
Решение тоже не совсем красивое, но, как мне казалось на тот момент, верное. Попробовав такую реализацию получилось отделить wav от mp3. Но начав тестировать данную проверку с разными mp3-файлами выяснилось, что она работает не всегда корректно. Возможно, дело в различных mp3-кодеках… в это я вдаваться не стал, а решил для экономии времени реализовать способ, который планировал использовать изначально.
Открыв mp3 файл я не обнаружил там ничего уникального, возможно плохо искал, а вот в wav почти в самом начале файла в любом случае пишется слово “WAVE” – это я и решил использовать. Проверка получилась проще, чем предполагалось:
soundString.substring(0, 50).contains("WAVE")
Так как слово “WAVE” находится в начале файла, то я решил не проверять его наличие во всей “звуковой строке”, а ограничился 50-ю первыми символами.
Теперь у меня было всё, чтобы сделать окончательную реализацию…
Окончательная реализация
Окончательная реализация получилась следующей:
public static void playSound(File soundFile) throws IOException,
JavaLayerException {
if (isWave(FileUtils.readFileToString(soundFile))) {
//mp3 file playes
final FileInputStream stream = new FileInputStream(
soundFile.getPath());
final Player player = new Player(stream);
player.play();
} else {
// wav file playes
AudioClip audioClip = AppletManager.applet.getAudioClip(
soundFile.toURL());
audioClip.play();
}
}
private static boolean isWave(String fileString) {
return fileString.substring(0, 50).contains("WAVE");
}
В итоге конечно получилось грубовато, но зато действенно. Для получения строки из файла я использую библиотеку commons-io, но можно сделать это вручную, в гугле по этой теме очень много информации.
А может всё было зря?
Спустя некоторое время я задумался о лучших способах реализации данной задачи. Погуглив, я нашел несколько вариантов, но сразу скажу, что ни один из них я не пробовал, так как не было на это времени. Представлю некоторые из них.
Оказалось, что в Java 7 есть поддержка mp3 файлов:
String bip = "example.mp3";
Media hit = new Media(bip);
MediaPlayer mediaPlayer = new MediaPlayer(hit);
mediaPlayer.play();
Можно было выдрать из JMF библиотечку mp3plugin.jar, и использовать её для проигрывания. Раньше она была доступна для свободной загрузки на сайте java, теперь же oracle просит скачать весь JMF, но ведь нам это не помеха? :). Весит библиотечка даже меньше, чем JLayer, всего 80кб. Возможно, этот метод был бы даже лучше описанного мною в данной статье, но что сделано – то сделано.
Закончим…
Надеюсь время, потраченное на прочтение данной статьи не было потрачено вами зря, и знания, полученные из статьи пригодятся в дальнейшем.
Подписывание Java-апплетов Вышла новая версия 1.28 продукта XML2Selenium
Comments are currently closed.
One thought on “Поддержка воспроизведения MP3 и WAV форматов в Java”
private static boolean isWave(String fileString) {
return fileString.substring(0, 50).contains(“WAVE”);
} Вы решили весь wav загрузить в память? А если он гига так на 3 этот ваш wav. Используйте InputStream и read.