Support of MP3 and WAV format files playback in Java

    Introduction

    Hello, dear reader! Firstly I want to warn you that this article is not the only and absolutely correct solution to the problem of simultaneous mp3 and wav files playback. This article only tells one of the way of solving this problem. In the article I described the steps I’ve done to fulfill the task, the difficulties I’ve had to overcome and the mistakes I have made (I accidentally, I swear :)). Just note that this is my first article of this kind so please do not judge me too strictly. So let’s start …

    Task and challenges

    My task was to make possible the simultaneous playback of mp3 and wav files in java-applet. Moreover the sound files should be sent from the server as a stream of bytes (files must be encoded in base64, but it does not matter 🙂 ).

    The problem is that the standard java tools cann’t play mp3 files (the software development was designed on java 6). The only suitable standard tool for playing mp3 files is Java Media Framework, which didn’t suit me because it required additional software installation, but it is an extra step for the user.

    The search of the best solution

    I needed to find the library to play mp3 files, the main search criteria was library size. Since the applet is an application that is opened in browser and it is permanently downloaded from the server, then for its fast loading the applet size should be minimal.

    While Googling I came across a number of libraries, but the smallest and also the most popular one is JLayer. project. I was quite satisfied with 100kb jar file size.  Moreover the library contains  mp3 converter and mp3 decoder, which you can use … or delete, thus make the library more lightweight :)).

    Project start

    I have saved the returned from the server audio files in a temporary folder with a random name. This folder was cleaned after the applet have been closed. I used this approach because I did not know the type of returned files: is it wav or mp3.

    Further I connected the library to maven project of applet:

    <dependencies>
        <dependency>
        <groupId>javazoom</groupId>
        <artifactId>jlayer</artifactId>
        <version>1.0.1</version>
        </dependency>
    </dependencies>

    I added the library to jar- file of applet using 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>

    This library was very easy to use, I just had to create javazoom.jl.player.Player  class object and then call play() method in it..

    FileInputStream stream = new FileInputStream(soundFile.getPath());
    Player player = new Player(stream);
    player.play();

    I note that sound playing occurs in the same thread in which play() method is run. Thus the thread in which this method is called is blocked while sound playing. It allows to trace the end of sound playing, if you want to perform any action after sound playing.

    For testing I used mp3 file with the kitten “meow” record and wav file with recorded dog “barks”. I was flushed with joy, when using the above code I heard so expected “meow.” But I would not have long to joy since “barks” I have not heard :(. Hence the library can not play wav files. I had to Google…

    Voodoo programming – Finding solutions

    Initially I thought why one of the most popular libraries for mp3 playback on java can not support wav? But after a few minutes of searching in Google, I realized that my question will remain unanswered … The only thing that could help in this situation was recoding wav to mp3 with JLayer tools. But I immediately gave up on this decision, as recoding is a quite resource-intensive process. However for me it was unacceptable as the applet should work even on the most low-end machines.

    So I have decided not to forego the library and use java standard tools for wav playback.

    AudioClip audioClip = applet.getAudioClip(getMediaFile().toURL());
    audioClip.play();

    This code perfectly plays wav files. Tell the truth the playback process less controlled than while using the library, as in this case the sound is played in a separate thread and I have to use any other ways for the definition of “the end of sound playing”. I no longer reflect on this direction, as I hadn’t any actions for wav-file after its playing.

    Now I need only to make mp3 file be reproduced by the library while wav file should be reproduced by standard java tools. To do this I should be able to determine: is this file mp3 or wav file…

    Who’s who?

    Initially I had the idea to look in the file, open it as a string and analyse what is the beginning of the file for different types of sounds. But I thought for some reason that this decision was workaround while I wanted to do something more elegant… However in the future I will regret about it, but first things first 🙂

    I decided to use the decoder of JLayer library.

    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
    }

    At the time I considered this decision is not quite beautiful, but the right one. To realize it I’ve got wise to differ mp3 file from wav file. But having started test this method I found that it didn’t always work correctly. Maybe the reason is different mp3-codecs … But only God knows. So I decided for saving time to implement the method that was initially planned to use.

    Opening wav file I found out that in its beginning there is the word “WAVE” and I decided to use this fact. The checking turned out to be easier than I expected:

    soundString.substring(0, 50).contains("WAVE")

    Since the word “WAVE” is situated at the beginning of the file, then I decided not to check only first 50 symbols of  the “sound string”.

    At the moment I had everything to complete the task…

    Final realization

    Final realization was looked like:

    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");
    }

    As a result I had a little rough but effective solution. To get string from a file I use commons-io library, but you can do the same manually. There is a lot of information related to this topic in Google.

    My hesitations

    Analyzing the work done I thought about the best way to fulfill this task. Having googled I found a few variants which are different from mine. Familiarize yourself with some of them.

    It turned out that Java 7 has support for mp3 files:

    String bip = "example.mp3";
    Media hit = new Media(bip);
    MediaPlayer mediaPlayer = new MediaPlayer(hit);
    mediaPlayer.play();

    You can extract mp3plugin.jar library from JMF and use it for playback. Previously it was available for free download on java website, but now Oracle asks to download the entire JMF. But I think it won’t be much of a problem for us :). The library weighs even less than JLayer, just 80kb. Perhaps this method would be even better than I have described in this article, but what’s done – is done.

    Conclusion

    I hope your time spent on reading this article you didn’t spend in vain and the knowledge gained from the article will be useful to you in the future.