How to Play Sound in Java

K. Macharia Feb 02, 2024
  1. Play Sound Using Clip in Java
  2. Play Sound Using SourceDataLine in Java
How to Play Sound in Java

Java applications will sometimes be required to play audio files. Given that sound is time-based data and must be delivered at the correct rate for it to be rendered for the user’s perception. An altercation of the rate at which data is delivered will distort the sound being played. Java Sound API’s objective is to ensure that sound data is delivered at the correct rate and continuously over when the sound is being played. It achieves this by playing sound through a line to ensure it remains smooth and consistent. The two kinds of lines that Java provides are Clip and SourceDataLine.

The difference between the two is in the approach of specifying the sound data. With Clip, all the sound data is specified once before the playback process, while in SourceDataLine, there is continuous buffer writing throughout the playback process. These two methods only support audio files in the following formats: AIFF, AIFC, WAVE, AU, and SND. There are many scenarios where a developer can use either of the two and expect to get the same outcome, but there are also scenarios where one gives better results than the other. Below is an explanation on how to choose the most effective line for your application’s needs.

  1. Clip

It is more effective when you want to read and play a short sound file as a clip more than one time. The Clip’s full functionality strength is best experienced when the user wants to loop the sound being played. This feature also allows the user to select the place where they want to start playback randomly. The clip is faster because the sound file is loaded and thus doesn’t require more buffering after being looped or after the user selects to move to a random file position.

  1. SourceDataLine

This is more effective when a user wants to optimize the memory while playing a large sound file. It is also the best option when the developer doesn’t know the sound that will be played. This method is also more effective when there is a need for sound transformation because it requires the sound data to be continuously updated by the application during playback.

Play Sound Using Clip in Java

Clip is available in javax.sound.sampled package and was introduced in Java 7.

In this example, we shall cover start, pause, resume, stop, restart and start at a random position.

Below are the steps involved:

  • The first step is to create an object of the audio input stream. This step converts the audio file into an input stream that the app can use.
  • The second step is to use Audio System to create an object for clip reference
  • The third step is now to load the clip object with audio data from the audio input stream that was created in step 1.
  • The next step is to set the clip’s required properties such as loop, position, and microsecond position
  • You can then start the clip.
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class SoundPlayer {
  // define storage for start position
  Long nowFrame;
  Clip clip;

  // get the clip status
  String thestatus;

  AudioInputStream audioStream;
  static String thePath;

  // initialize both the clip and streams
  public SoundPlayer() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    // the input stream object
    audioStream = AudioSystem.getAudioInputStream(new File(thePath).getAbsoluteFile());

    // the reference to the clip
    clip = AudioSystem.getClip();

    clip.open(audioStream);

    clip.loop(Clip.LOOP_CONTINUOUSLY);
  }

  public static void main(String[] args) {
    try {
      // add the path to the audio file
      thePath = "add the path to the audio file here";

      SoundPlayer simpleSoundPlayer = new SoundPlayer();

      simpleSoundPlayer.play();
      Scanner scanned = new Scanner(System.in);

      // show the options
      while (true) {
        System.out.println("1. pause");
        System.out.println("2. resume");
        System.out.println("3. restart");
        System.out.println("4. stop");
        System.out.println("5. Jump to specific time");
        int a = scanned.nextInt();
        simpleSoundPlayer.gotoChoice(a);
        if (a == 4)
          break;
      }
      scanned.close();
    }

    catch (Exception e) {
      System.out.println("Experienced an error while playing sound.");
      e.printStackTrace();
    }
  }

  // operation is now as per the user's choice

  private void gotoChoice(int a)
      throws IOException, LineUnavailableException, UnsupportedAudioFileException {
    switch (a) {
      case 1:
        pause();
        break;
      case 2:
        resumeAudio();
        break;
      case 3:
        restart();
        break;
      case 4:
        stop();
        break;
      case 5:
        System.out.println("Selected time (" + 0 + ", " + clip.getMicrosecondLength() + ")");
        Scanner scan = new Scanner(System.in);
        long cc = scan.nextLong();
        jump(cc);
        break;
    }
  }

  // play
  public void play() {
    // start the clip
    clip.start();

    thestatus = "play";
  }

  // Pause audio
  public void pause() {
    if (thestatus.equals("paused")) {
      System.out.println("audio is already paused");
      return;
    }
    this.nowFrame = this.clip.getMicrosecondPosition();
    clip.stop();
    thestatus = "paused";
  }

  // resume audio
  public void resumeAudio()
      throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    if (thestatus.equals("play")) {
      System.out.println("The audio is"
          + "being played");
      return;
    }
    clip.close();
    resetAudioStream();
    clip.setMicrosecondPosition(nowFrame);
    this.play();
  }

  // restart audio
  public void restart()
      throws IOException, LineUnavailableException, UnsupportedAudioFileException {
    clip.stop();
    clip.close();
    resetAudioStream();
    nowFrame = 0L;
    clip.setMicrosecondPosition(0);
    this.play();
  }

  // stop audio
  public void stop() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    nowFrame = 0L;
    clip.stop();
    clip.close();
  }

  // jump to a selected point
  public void jump(long a)
      throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    if (a > 0 && a < clip.getMicrosecondLength()) {
      clip.stop();
      clip.close();
      resetAudioStream();
      nowFrame = a;
      clip.setMicrosecondPosition(a);
      this.play();
    }
  }

  // reset the audio stream
  public void resetAudioStream()
      throws UnsupportedAudioFileException, IOException, LineUnavailableException {
    audioStream = AudioSystem.getAudioInputStream(new File(thePath).getAbsoluteFile());
    clip.open(audioStream);
    clip.loop(Clip.LOOP_CONTINUOUSLY);
  }
}

This program uses an audio stream obtained from the sound data using the AudioInputStream. This must be the first step because the program only recognizes the data as a stream which has to be reset if it is to be reused.

Operations Explanation

Pause: To successfully pause the player, the current frame must be stored in an object after the player stops. The frame is stored to ensure that the player can resume when the resume method is called. Pause method uses the clip.getMicrosecondPosition() to capture the pause point.

Resume: When the resume method is called, it uses the frame stored in an object to know where to continue from. Resume uses the clip.setMicrosecondPosition(nowFrame) to reset the audio stream to the position it was at when the pause method was called.

Stop: The stop method closes and stops the clip. When this method is called, the user cannot resume their position before because the frame was not stored. This is the technical difference between pause and stop.

It is always advisable that before closing the program, the open streams should be closed. While using the above programs, proper checks are implemented by stopping the clip before playing it again. This ensures that the sound remains consistent and the resources are efficiently utilized while playback is ongoing. As a rule in Java, a stream, in this case, the audio stream cannot be reused before being reset. Failure to reset it before reusing it will lead to the program giving an error.

Play Sound Using SourceDataLine in Java

The SourceDataLine is found in javax.sound.sampled.SourceDataLine. To implement SourceDataLine sound play, we follow the following steps.

  • The first step is to create an object of the audio input stream. This step converts the audio file into an input stream that the app can use.
  • The second step is to open a line using the AudioSystem.getLine() method.
  • The third step is to repeatedly read the specified chunks of the audio input stream created in step 1 and forward it to SourceDataLine’s buffer. This is repeated until the end of the audio stream.
  • After the read and buffer have been completed, the resources are freed by closing the line.
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class simpleSoundPlayer {
  // defining the byte buffer
  private static final int BUFFER_SIZE = 4096;

  void play(String filePath) {
    File soundFile = new File(filePath);
    try {
      // convering the audio file to a stream
      AudioInputStream sampleStream = AudioSystem.getAudioInputStream(soundFile);

      AudioFormat formatAudio = sampleStream.getFormat();

      DataLine.Info info = new DataLine.Info(SourceDataLine.class, formatAudio);

      SourceDataLine theAudioLine = (SourceDataLine) AudioSystem.getLine(info);

      theAudioLine.open(formatAudio);

      theAudioLine.start();

      System.out.println("Audio Player Started.");

      byte[] bufferBytes = new byte[BUFFER_SIZE];
      int readBytes = -1;

      while ((readBytes = sampleStream.read(bufferBytes)) != -1) {
        theAudioLine.write(bufferBytes, 0, readBytes);
      }

      theAudioLine.drain();
      theAudioLine.close();
      sampleStream.close();

      System.out.println("Playback has been finished.");

    } catch (UnsupportedAudioFileException e) {
      System.out.println("Unsupported file.");
      e.printStackTrace();
    } catch (LineUnavailableException e) {
      System.out.println("Line not found.");
      e.printStackTrace();
    } catch (IOException e) {
      System.out.println("Experienced an error.");
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    String thePath = "path to your audio file here";
    simpleSoundPlayer player = new simpleSoundPlayer();
    player.play(thePath);
  }
}

The program above is most effective when the user wants to read a large file without giving up a lot of memory space. This can also be applied when the user is streaming real-time sound data and does not want to have unnecessary lag-time. When well implemented, this method of playing sound can lead to a user getting smooth and consistent sound with very low memory usage. However, failure to close the line can lead to memory clogging and, ultimately, sound distortion.

SoundDataLine is limited in the following ways:

  1. A user can’t start playing from an arbitrary position
  2. It is not possible to loop the sound
  3. It is not possible to pause and resume as you would with a clip
  4. A user cannot know the duration of the selected audio file before they play it

This limits the scenarios where a user can effectively benefit from the SoundDataLine program to large files or online streaming services.