class Audio {
  private static final Audio INSTANCE = new Audio();

  private static final int MAX_PENDING = 16;

  private int headIndex;

  private int tailIndex;

  private volatile Thread updateThread = null;

  private final PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING];

  // Visible only for testing purposes
  Audio() {

  }

  public static Audio getInstance() {
    return INSTANCE;
  }

  /**
   * This method stops the Update Method's thread and waits till service stops.
   */
  public synchronized void stopService() throws InterruptedException {
    if (updateThread != null) {
      updateThread.interrupt();
    }
    updateThread.join();
    updateThread = null;
  }

  /**
   * This method check the Update Method's thread is started.
   *
   * @return boolean
   */
  public synchronized boolean isServiceRunning() {
    return updateThread != null && updateThread.isAlive();
  }

  /**
   * Starts the thread for the Update Method pattern if it was not started previously. Also when the
   * thread is is ready initializes the indexes of the queue
   */
  public void init() {
    if (updateThread == null) {
      updateThread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
          update();
        }
      });
    }
    startThread();
  }

  /**
   * This is a synchronized thread starter.
   */
  private synchronized void startThread() {
    if (!updateThread.isAlive()) {
      updateThread.start();
      headIndex = 0;
      tailIndex = 0;
    }
  }

  /**
   * This method adds a new audio into the queue.
   *
   * @param stream is the AudioInputStream for the method
   * @param volume is the level of the audio's volume
   */
  public void playSound(AudioInputStream stream, float volume) {
    init();
    // Walk the pending requests.
    for (var i = headIndex; i != tailIndex; i = (i + 1) % MAX_PENDING) {
      var playMessage = getPendingAudio()[i];
      if (playMessage.getStream() == stream) {
        // Use the larger of the two volumes.
        playMessage.setVolume(Math.max(volume, playMessage.getVolume()));

        // Don't need to enqueue.
        return;
      }
    }
    getPendingAudio()[tailIndex] = new PlayMessage(stream, volume);
    tailIndex = (tailIndex + 1) % MAX_PENDING;
  }

  /**
   * This method uses the Update Method pattern. It takes the audio from the queue and plays it
   */
  private void update() {<FILL_FUNCTION_BODY>}

  /**
   * Returns the AudioInputStream of a file.
   *
   * @param filePath is the path of the audio file
   * @return AudioInputStream
   * @throws UnsupportedAudioFileException when the audio file is not supported
   * @throws IOException                   when the file is not readable
   */
  public AudioInputStream getAudioStream(String filePath)
      throws UnsupportedAudioFileException, IOException {
    return AudioSystem.getAudioInputStream(new File(filePath).getAbsoluteFile());
  }

  /**
   * Returns with the message array of the queue.
   *
   * @return PlayMessage[]
   */
  public PlayMessage[] getPendingAudio() {
    return pendingAudio;
  }

}

    // If there are no pending requests, do nothing.
    if (headIndex == tailIndex) {
      return;
    }
    try {
      var audioStream = getPendingAudio()[headIndex].getStream();
      headIndex++;
      var clip = AudioSystem.getClip();
      clip.open(audioStream);
      clip.start();
    } catch (LineUnavailableException e) {
      LOGGER.trace("Error occurred while loading the audio: The line is unavailable", e);
    } catch (IOException e) {
      LOGGER.trace("Input/Output error while loading the audio", e);
    } catch (IllegalArgumentException e) {
      LOGGER.trace("The system doesn't support the sound: " + e.getMessage(), e);
    }
  
<no_super_class>