com.player.BasicMP3Player.java Source code

Java tutorial

Introduction

Here is the source code for com.player.BasicMP3Player.java

Source

/**
 * BasicMp3Player.
 *
 *-----------------------------------------------------------------------
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *----------------------------------------------------------------------
 */

package com.player;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Control;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import javazoom.jlgui.basicplayer.BasicController;
import javazoom.jlgui.basicplayer.BasicPlayerEvent;
import javazoom.jlgui.basicplayer.BasicPlayerEventLauncher;
import javazoom.jlgui.basicplayer.BasicPlayerException;
import javazoom.jlgui.basicplayer.BasicPlayerListener;
import javazoom.spi.PropertiesContainer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tritonus.share.sampled.TAudioFormat;
import org.tritonus.share.sampled.file.TAudioFileFormat;

/**
 * BasicPlayer is a threaded audio javazoom.jlgui.player.test.
 */
public class BasicMP3Player extends BasicPlayer implements BasicController, Runnable {
    private static final int EXTERNAL_BUFFER_SIZE = 4000 * 4;

    private static final int SKIP_INACCURACY_SIZE = 1200;

    private Thread m_thread = null;

    private Object m_dataSource;

    private AudioInputStream m_encodedaudioInputStream;

    private int encodedLength = -1;

    private AudioInputStream m_audioInputStream;

    private AudioFileFormat m_audioFileFormat;

    private SourceDataLine m_line;

    private FloatControl m_gainControl;

    private FloatControl m_panControl;

    private int lineBufferSize = -1;

    private long threadSleep = -1;

    private static Log log = LogFactory.getLog(BasicPlayer.class);

    /**
     * These variables are used to distinguish stopped, paused, playing states. We need them to
     * control Thread.
     */
    public static final int UNKNOWN = -1;

    public static final int PLAYING = 0;

    public static final int PAUSED = 1;

    public static final int STOPPED = 2;

    public static final int OPENED = 3;

    public static final int SEEKING = 4;

    private int m_status = UNKNOWN;

    // Listeners to be notified.
    private Collection m_listeners = null;

    private Map empty_map = new HashMap();

    /**
     * Constructs a Basic Player.
     */
    public BasicMP3Player() {
        setSupportedFileTypeExtensions();
        m_dataSource = null;
        m_listeners = new ArrayList();
        reset();
    }

    protected void reset() {
        m_status = UNKNOWN;
        if (m_audioInputStream != null) {
            synchronized (m_audioInputStream) {
                closeStream();
            }
        }
        m_audioInputStream = null;
        m_audioFileFormat = null;
        m_encodedaudioInputStream = null;
        encodedLength = -1;
        if (m_line != null) {
            m_line.stop();
            m_line.close();
            m_line = null;
        }
        m_gainControl = null;
        m_panControl = null;
    }

    public void addBasicPlayerListener(BasicPlayerListener bpl) {
        m_listeners.add(bpl);
    }

    /**
     * Set SourceDataLine buffer size. It affects audio latency. (the delay between line.write(data)
     * and real sound). Minimum value should be over 10000 bytes.
     * 
     * @param size
     *           -1 means maximum buffer size available.
     */
    public void setLineBufferSize(int size) {
        lineBufferSize = size;
    }

    /**
     * Return SourceDataLine buffer size.
     * 
     * @return -1 maximum buffer size.
     */
    public int getLineBufferSize() {
        return lineBufferSize;
    }

    /**
     * Set thread sleep time. Default is -1 (no sleep time).
     * 
     * @param time
     *           in milliseconds.
     */
    public void setSleepTime(long time) {
        threadSleep = time;
    }

    /**
     * Return thread sleep time in milliseconds.
     * 
     * @return -1 means no sleep time.
     */
    public long getSleepTime() {
        return threadSleep;
    }

    /**
     * Returns BasicPlayer status.
     * 
     * @return status
     */
    public int getStatus() {
        return m_status;
    }

    /**
     * Open file to play.
     */
    public void open(File file) throws BasicPlayerException {
        log.info("open(" + file + ")");
        if (file != null) {
            m_dataSource = file;
            initAudioInputStream();
        }
    }

    /**
     * Open URL to play.
     */
    public void open(URL url) throws BasicPlayerException {
        log.info("open(" + url + ")");
        if (url != null) {
            m_dataSource = url;
            initAudioInputStream();
        }
    }

    /**
     * Open inputstream to play.
     */
    public void open(InputStream inputStream) throws BasicPlayerException {
        log.info("open(" + inputStream + ")");
        if (inputStream != null) {
            m_dataSource = inputStream;
            initAudioInputStream();
        }
    }

    /**
     * Inits AudioInputStream and AudioFileFormat from the data source.
     * 
     * @throws BasicPlayerException
     */
    private void initAudioInputStream() throws BasicPlayerException {
        try {
            reset();
            notifyEvent(BasicPlayerEvent.OPENING, getEncodedStreamPosition(), -1, m_dataSource);
            if (m_dataSource instanceof URL) {
                initAudioInputStream((URL) m_dataSource);
            } else if (m_dataSource instanceof File) {
                initAudioInputStream((File) m_dataSource);
            } else if (m_dataSource instanceof InputStream) {
                initAudioInputStream((InputStream) m_dataSource);
            }
            createLine();
            // Notify listeners with AudioFileFormat properties.
            Map properties = null;
            if (m_audioFileFormat instanceof TAudioFileFormat) {
                // Tritonus SPI compliant audio file format.
                properties = ((TAudioFileFormat) m_audioFileFormat).properties();
                // Clone the Map because it is not mutable.
                properties = deepCopy(properties);
            } else
                properties = new HashMap();
            // Add JavaSound properties.
            if (m_audioFileFormat.getByteLength() > 0)
                properties.put("audio.length.bytes", new Integer(m_audioFileFormat.getByteLength()));
            if (m_audioFileFormat.getFrameLength() > 0)
                properties.put("audio.length.frames", new Integer(m_audioFileFormat.getFrameLength()));
            if (m_audioFileFormat.getType() != null)
                properties.put("audio.type", (m_audioFileFormat.getType().toString()));
            // Audio format.
            AudioFormat audioFormat = m_audioFileFormat.getFormat();
            if (audioFormat.getFrameRate() > 0)
                properties.put("audio.framerate.fps", new Float(audioFormat.getFrameRate()));
            if (audioFormat.getFrameSize() > 0)
                properties.put("audio.framesize.bytes", new Integer(audioFormat.getFrameSize()));
            if (audioFormat.getSampleRate() > 0)
                properties.put("audio.samplerate.hz", new Float(audioFormat.getSampleRate()));
            if (audioFormat.getSampleSizeInBits() > 0)
                properties.put("audio.samplesize.bits", new Integer(audioFormat.getSampleSizeInBits()));
            if (audioFormat.getChannels() > 0)
                properties.put("audio.channels", new Integer(audioFormat.getChannels()));
            if (audioFormat instanceof TAudioFormat) {
                // Tritonus SPI compliant audio format.
                Map addproperties = ((TAudioFormat) audioFormat).properties();
                properties.putAll(addproperties);
            }
            Iterator it = m_listeners.iterator();
            while (it.hasNext()) {
                BasicPlayerListener bpl = (BasicPlayerListener) it.next();
                bpl.opened(m_dataSource, properties);
            }
            m_status = OPENED;
            notifyEvent(BasicPlayerEvent.OPENED, getEncodedStreamPosition(), -1, null);
        } catch (LineUnavailableException e) {
            throw new BasicPlayerException(e);
        } catch (UnsupportedAudioFileException e) {
            throw new BasicPlayerException(e);
        } catch (IOException e) {
            throw new BasicPlayerException(e);
        }
    }

    /**
     * Inits Audio ressources from file.
     */
    private void initAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
        m_audioInputStream = AudioSystem.getAudioInputStream(file);
        m_audioFileFormat = AudioSystem.getAudioFileFormat(file);
    }

    /**
     * Inits Audio ressources from URL.
     */
    private void initAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
        m_audioInputStream = AudioSystem.getAudioInputStream(url);
        m_audioFileFormat = AudioSystem.getAudioFileFormat(url);
    }

    /**
     * Inits Audio ressources from InputStream.
     */
    private void initAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException {
        m_audioInputStream = AudioSystem.getAudioInputStream(inputStream);
        m_audioFileFormat = AudioSystem.getAudioFileFormat(inputStream);
    }

    /**
     * Inits Audio ressources from AudioSystem.<br>
     */
    protected void initLine() throws LineUnavailableException {
        log.info("initLine()");
        if (m_line == null)
            createLine();
        if (!m_line.isOpen()) {
            openLine();
        } else {
            AudioFormat lineAudioFormat = m_line.getFormat();
            AudioFormat audioInputStreamFormat = m_audioInputStream == null ? null : m_audioInputStream.getFormat();
            if (!lineAudioFormat.equals(audioInputStreamFormat)) {
                m_line.close();
                openLine();
            }
        }
    }

    /**
     * Inits a DateLine.<br>
     * We check if the line supports Gain and Pan controls. From the AudioInputStream, i.e. from the
     * sound file, we fetch information about the format of the audio data. These information include
     * the sampling frequency, the number of channels and the size of the samples. There information
     * are needed to ask JavaSound for a suitable output line for this audio file. Furthermore, we
     * have to give JavaSound a hint about how big the internal buffer for the line should be. Here,
     * we say AudioSystem.NOT_SPECIFIED, signaling that we don't care about the exact size. JavaSound
     * will use some default value for the buffer size.
     */
    private void createLine() throws LineUnavailableException {
        log.info("Create Line");
        if (m_line == null) {
            AudioFormat sourceFormat = m_audioInputStream.getFormat();
            log.info("Create Line : Source format : " + sourceFormat.toString());
            AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                    sourceFormat.getSampleRate(), 16, sourceFormat.getChannels(), sourceFormat.getChannels() * 2,
                    sourceFormat.getSampleRate(), false);
            log.info("Create Line : Target format: " + targetFormat);
            // Keep a reference on encoded stream to progress notification.
            m_encodedaudioInputStream = m_audioInputStream;
            try {
                // Get total length in bytes of the encoded stream.
                encodedLength = m_encodedaudioInputStream.available();
            } catch (IOException e) {
                log.error("Cannot get m_encodedaudioInputStream.available()", e);
            }
            // Create decoded stream.
            m_audioInputStream = AudioSystem.getAudioInputStream(targetFormat, m_audioInputStream);
            AudioFormat audioFormat = m_audioInputStream.getFormat();
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED);
            m_line = (SourceDataLine) AudioSystem.getLine(info);

            /*-- Display supported controls --*/
            Control[] c = m_line.getControls();
            for (int p = 0; p < c.length; p++) {
                log.debug("Controls : " + c[p].toString());
            }

            /*-- Is Gain Control supported ? --*/
            if (m_line.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
                m_gainControl = (FloatControl) m_line.getControl(FloatControl.Type.MASTER_GAIN);
                log.info("Master Gain Control : [" + m_gainControl.getMinimum() + "," + m_gainControl.getMaximum()
                        + "] " + m_gainControl.getPrecision());
            }

            /*-- Is Pan control supported ? --*/
            if (m_line.isControlSupported(FloatControl.Type.PAN)) {
                m_panControl = (FloatControl) m_line.getControl(FloatControl.Type.PAN);
                log.info("Pan Control : [" + m_panControl.getMinimum() + "," + m_panControl.getMaximum() + "] "
                        + m_panControl.getPrecision());
            }
        }
    }

    /**
     * Opens the line.
     */
    private void openLine() throws LineUnavailableException {
        if (m_line != null) {
            AudioFormat audioFormat = m_audioInputStream.getFormat();
            int buffersize = lineBufferSize;
            if (buffersize <= 0)
                buffersize = m_line.getBufferSize();
            m_line.open(audioFormat, buffersize);
            log.info("Open Line : BufferSize=" + buffersize);
        }
    }

    /**
     * Stops the playback.<br>
     * Player Status = STOPPED.<br>
     * Thread should free Audio ressources.
     */
    protected void stopPlayback() {
        if ((m_status == PLAYING) || (m_status == PAUSED)) {
            if (m_line != null) {
                m_line.flush();
                m_line.stop();
            }
            m_status = STOPPED;
            notifyEvent(BasicPlayerEvent.STOPPED, getEncodedStreamPosition(), -1, null);
            synchronized (m_audioInputStream) {
                closeStream();
            }
            log.info("stopPlayback() completed");
        }
    }

    /**
     * Pauses the playback.<br>
     * Player Status = PAUSED.
     */
    protected void pausePlayback() {
        if (m_line != null) {
            if (m_status == PLAYING) {
                m_line.flush();
                m_line.stop();
                m_status = PAUSED;
                log.info("pausePlayback() completed");
                notifyEvent(BasicPlayerEvent.PAUSED, getEncodedStreamPosition(), -1, null);
            }
        }
    }

    /**
     * Resumes the playback.<br>
     * Player Status = PLAYING.
     */
    protected void resumePlayback() {
        if (m_line != null) {
            if (m_status == PAUSED) {
                m_line.start();
                m_status = PLAYING;
                log.info("resumePlayback() completed");
                notifyEvent(BasicPlayerEvent.RESUMED, getEncodedStreamPosition(), -1, null);
            }
        }
    }

    /**
     * Starts playback.
     */
    protected void startPlayback() throws BasicPlayerException {
        if (m_status == STOPPED)
            initAudioInputStream();
        if (m_status == OPENED) {
            log.info("startPlayback called");
            if (!(m_thread == null || !m_thread.isAlive())) {
                log.info("WARNING: old thread still running!!");
                int cnt = 0;
                while (m_status != OPENED) {
                    try {
                        if (m_thread != null) {
                            log.info("Waiting ... " + cnt);
                            cnt++;
                            Thread.sleep(1000);
                            if (cnt > 2) {
                                m_thread.interrupt();
                            }
                        }
                    } catch (InterruptedException e) {
                        throw new BasicPlayerException("Wait error", e);
                    }
                }
            }
            // Open SourceDataLine.
            try {
                initLine();
            } catch (LineUnavailableException e) {
                throw new BasicPlayerException("Cannot init line", e);
            }

            log.info("Creating new thread");
            m_thread = new Thread(this);
            m_thread.start();
            if (m_line != null) {
                m_line.start();
                m_status = PLAYING;
                notifyEvent(BasicPlayerEvent.PLAYING, getEncodedStreamPosition(), -1, null);
            }
        }
    }

    /**
     * Main loop. Player Status == STOPPED || SEEKING => End of Thread + Freeing Audio Ressources.<br>
     * Player Status == PLAYING => Audio stream data sent to Audio line.<br>
     * Player Status == PAUSED => Waiting for another status.
     */
    public void run() {
        log.info("Thread Running");
        int nBytesRead = 1;
        byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
        // Lock stream while playing.
        synchronized (m_audioInputStream) {
            // Main play/pause loop.
            while ((nBytesRead != -1) && (m_status != STOPPED) && (m_status != SEEKING) && (m_status != UNKNOWN)) {
                if (m_status == PLAYING) {
                    // Play.
                    try {
                        nBytesRead = m_audioInputStream.read(abData, 0, abData.length);
                        if (nBytesRead >= 0) {
                            byte[] pcm = new byte[nBytesRead];
                            System.arraycopy(abData, 0, pcm, 0, nBytesRead);
                            m_line.write(abData, 0, nBytesRead);
                            // Compute position in bytes in encoded stream.
                            int nEncodedBytes = getEncodedStreamPosition();
                            // Notify listeners
                            Iterator it = m_listeners.iterator();
                            while (it.hasNext()) {
                                BasicPlayerListener bpl = (BasicPlayerListener) it.next();
                                if (m_audioInputStream instanceof PropertiesContainer) {
                                    // Pass audio parameters such as instant
                                    // bitrate, ...
                                    Map properties = ((PropertiesContainer) m_audioInputStream).properties();
                                    bpl.progress(nEncodedBytes, m_line.getMicrosecondPosition(), pcm, properties);
                                } else
                                    bpl.progress(nEncodedBytes, m_line.getMicrosecondPosition(), pcm, empty_map);
                            }
                        }
                    } catch (IOException e) {
                        log.error("Thread cannot run()", e);
                        m_status = STOPPED;
                        notifyEvent(BasicPlayerEvent.STOPPED, getEncodedStreamPosition(), -1, null);
                    }
                    // Nice CPU usage.
                    if (threadSleep > 0) {
                        try {
                            Thread.sleep(threadSleep);
                        } catch (InterruptedException e) {
                            log.error("Thread cannot sleep(" + threadSleep + ")", e);
                        }
                    }
                } else {
                    // Pause
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        log.error("Thread cannot sleep(1000)", e);
                    }
                }
            }
            // Free audio resources.
            if (m_line != null) {
                m_line.drain();
                m_line.stop();
                m_line.close();
                m_line = null;
            }
            // Notification of "End Of Media"
            if (nBytesRead == -1) {
                notifyEvent(BasicPlayerEvent.EOM, getEncodedStreamPosition(), -1, null);
            }
            // Close stream.
            closeStream();
        }
        m_status = STOPPED;
        notifyEvent(BasicPlayerEvent.STOPPED, getEncodedStreamPosition(), -1, null);
        log.info("Thread completed");
    }

    /**
     * Skip bytes in the File inputstream. It will skip N frames matching to bytes, so it will never
     * skip given bytes length exactly.
     * 
     * @param bytes
     * @return value>0 for File and value=0 for URL and InputStream
     * @throws BasicPlayerException
     */
    protected long skipBytes(long bytes) throws BasicPlayerException {
        long totalSkipped = 0;
        if (m_dataSource instanceof File) {
            log.info("Bytes to skip : " + bytes);
            int previousStatus = m_status;
            m_status = SEEKING;
            long skipped = 0;
            try {
                synchronized (m_audioInputStream) {
                    notifyEvent(BasicPlayerEvent.SEEKING, getEncodedStreamPosition(), -1, null);
                    initAudioInputStream();
                    if (m_audioInputStream != null) {
                        // Loop until bytes are really skipped.
                        while (totalSkipped < (bytes - SKIP_INACCURACY_SIZE)) {
                            skipped = m_audioInputStream.skip(bytes - totalSkipped);
                            totalSkipped = totalSkipped + skipped;
                            log.info("Skipped : " + totalSkipped + "/" + bytes);
                            if (totalSkipped == -1)
                                throw new BasicPlayerException("Skip not supported");
                        }
                    }
                }
                notifyEvent(BasicPlayerEvent.SEEKED, getEncodedStreamPosition(), -1, null);
                m_status = OPENED;
                if (previousStatus == PLAYING)
                    startPlayback();
                else if (previousStatus == PAUSED) {
                    startPlayback();
                    pausePlayback();
                }
            } catch (IOException e) {
                throw new BasicPlayerException(e);
            }
        }
        return totalSkipped;
    }

    /**
     * Notify listeners about a BasicPlayerEvent.
     * 
     * @param code
     *           event code.
     * @param position
     *           in the stream when the event occurs.
     */
    protected void notifyEvent(int code, int position, double value, Object description) {
        /*
         * Iterator it = m_listeners.iterator(); while (it.hasNext()) { BasicPlayerListener bpl =
         * (BasicPlayerListener) it.next(); bpl.stateUpdated(new
         * BasicPlayerEvent(this,code,position,value,description)); }
         */
        BasicPlayerEventLauncher trigger = new BasicPlayerEventLauncher(code, position, value, description,
                m_listeners, this);
        trigger.start();
    }

    protected int getEncodedStreamPosition() {
        int nEncodedBytes = -1;
        if (m_dataSource instanceof File) {
            try {
                if (m_encodedaudioInputStream != null) {
                    nEncodedBytes = encodedLength - m_encodedaudioInputStream.available();
                }
            } catch (IOException e) {
                // log.debug("Cannot get
                // m_encodedaudioInputStream.available()",e);
            }
        }
        return nEncodedBytes;
    }

    protected void closeStream() {
        // Close stream.
        try {
            if (m_audioInputStream != null) {
                m_audioInputStream.close();
                log.info("Stream closed");
            }

        } catch (IOException e) {
            log.info("Cannot close stream", e);
        }
    }

    /**
     * Returns true if Gain control is supported.
     */
    public boolean hasGainControl() {
        return m_gainControl != null;
    }

    /**
     * Returns Gain value.
     */
    public float getGainValue() {
        if (hasGainControl()) {
            return m_gainControl.getValue();
        }

        return 0.0F;
    }

    /**
     * Gets max Gain value.
     */
    public float getMaximumGain() {
        if (hasGainControl()) {
            return m_gainControl.getMaximum();
        }

        return 0.0F;
    }

    /**
     * Gets min Gain value.
     */
    public float getMinimumGain() {
        if (hasGainControl()) {
            return m_gainControl.getMinimum();
        }

        return 0.0F;
    }

    /**
     * Returns true if Pan control is supported.
     */
    public boolean hasPanControl() {
        return m_panControl != null;
    }

    /**
     * Returns Pan precision.
     */
    public float getPrecision() {
        if (hasPanControl()) {
            return m_panControl.getPrecision();
        }

        return 0.0F;
    }

    /**
     * Returns Pan value.
     */
    public float getPan() {
        if (hasPanControl()) {
            return m_panControl.getValue();
        }

        return 0.0F;
    }

    /**
     * Deep copy of a Map.
     * 
     * @param src
     * @return
     */
    private Map deepCopy(Map src) {
        HashMap map = new HashMap();
        if (src != null) {
            Iterator it = src.keySet().iterator();
            while (it.hasNext()) {
                Object key = it.next();
                Object value = src.get(key);
                map.put(key, value);
            }
        }
        return map;
    }

    /**
     * @see javazoom.jlgui.basicplayer.BasicController#seek(long)
     */
    public long seek(long bytes) throws BasicPlayerException {
        return skipBytes(bytes);
    }

    /**
     * @see javazoom.jlgui.basicplayer.BasicController#play()
     */
    public void play() throws BasicPlayerException {
        startPlayback();
    }

    /**
     * @see javazoom.jlgui.basicplayer.BasicController#stop()
     */
    public void stop() throws BasicPlayerException {
        stopPlayback();
    }

    /**
     * @see javazoom.jlgui.basicplayer.BasicController#pause()
     */
    public void pause() throws BasicPlayerException {
        pausePlayback();
    }

    /**
     * @see javazoom.jlgui.basicplayer.BasicController#resume()
     */
    public void resume() throws BasicPlayerException {
        resumePlayback();
    }

    /**
     * Sets Pan value. Linear scale : -1.0 <--> +1.0
     */
    public void setPan(double fPan) throws BasicPlayerException {
        if (hasPanControl()) {
            log.debug("Pan : " + fPan);
            m_panControl.setValue((float) fPan);
            notifyEvent(BasicPlayerEvent.PAN, getEncodedStreamPosition(), fPan, null);
        } else
            throw new BasicPlayerException("Pan control not supported");
    }

    /**
     * Sets Gain value. Linear scale 0.0 <--> 1.0 Threshold Coef. : 1/2 to avoid saturation.
     */
    public void setGain(double fGain) throws BasicPlayerException {
        if (hasGainControl()) {
            double minGainDB = getMinimumGain();
            double ampGainDB = ((10.0f / 20.0f) * getMaximumGain()) - getMinimumGain();
            double cste = Math.log(10.0) / 20;
            double valueDB = minGainDB + (1 / cste) * Math.log(1 + (Math.exp(cste * ampGainDB) - 1) * fGain);
            log.debug("Gain : " + valueDB);
            m_gainControl.setValue((float) valueDB);
            notifyEvent(BasicPlayerEvent.GAIN, getEncodedStreamPosition(), fGain, null);
        } else
            throw new BasicPlayerException("Gain control not supported");
    }

    public void setSupportedFileTypeExtensions() {
        supportedFileTypeExtensions = new String[] { ".mp3", ".ogg" };
    }
}