com.limegroup.gnutella.gui.mp3.BasicPlayer.java Source code

Java tutorial

Introduction

Here is the source code for com.limegroup.gnutella.gui.mp3.BasicPlayer.java

Source

package com.limegroup.gnutella.gui.mp3;

/**
 * BasicPlayer.
 *
 *-----------------------------------------------------------------------
 *   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.
 *----------------------------------------------------------------------
 */
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

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 javax.swing.SwingUtilities;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.limegroup.gnutella.gui.GUIMediator;
import com.limegroup.gnutella.metadata.AudioMetaData;
import com.limegroup.gnutella.util.ManagedThread;

/**
 * This code was lifted from the jlGui project (below).  Made some custom
 * changes to fit into our framework.  This version of BasicPlayer can handle
 * very low bitrate (LBR, 16-74) files.
 *
 * BasicPlayer implements basics features of a player. The playback is done
 * with a thread.
 * BasicPlayer is the result of jlGui 0.5 from JavaZOOM and BaseAudioStream
 * from Matthias Pfisterer JavaSound examples.
 *
 * @author   E.B from JavaZOOM
 *
 * Homepage : http://www.javazoom.net
 *
 */
public class BasicPlayer extends AbstractAudioPlayer implements Runnable {

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

    private static final int EXTERNAL_BUFFER_SIZE = 4000 * 4;

    private Thread m_thread = null;
    private Object m_dataSource;
    private AudioInputStream m_audioInputStream;
    private AudioFileFormat m_audioFileFormat;
    private SourceDataLine m_line;
    private FloatControl m_gainControl;
    private FloatControl m_panControl;

    /**
     * These variables are used to distinguish stopped, paused, playing states.
     * We need them to control Thread.
     */
    public static final int PAUSED = 1;
    public static final int PLAYING = 0;
    public static final int STOPPED = 2;
    public static final int READY = 3;
    private int m_status = READY;
    private long doSeek = -1;
    private File _file = null;

    // used to keep track of frames read...
    private int m_framesRead = 0;

    /**
     * Constructs a Basic Player.
     */
    public BasicPlayer() {
        m_dataSource = null;
        m_audioInputStream = null;
        m_audioFileFormat = null;
        m_line = null;
        m_gainControl = null;
        m_panControl = null;
    }

    /*****----------------------------
     * HOW TO BE A ABSTRACTAUDIOPLAYER
     *****----------------------------/
        
    /**
    * Returns BasicPlayer status.
    */
    public int getStatus() {
        switch (m_status) {
        case PAUSED:
            return STATUS_PAUSED;
        case PLAYING:
            return STATUS_PLAYING;
        case STOPPED:
            return STATUS_STOPPED;
        default:
            return STATUS_STOPPED;
        }
    }

    public void unpause() {
        resumePlayback();
    }

    public void pause() {
        pausePlayback();
    }

    public void stop() {
        stopPlayback();
    }

    public boolean play(final File toPlay) throws IOException {
        String reason;
        try {
            setDataSource(toPlay);
            return startPlayback();
        } catch (UnsupportedAudioFileException ignored) {
            reason = "UNSUPPORTED";
        } catch (LineUnavailableException ignored) {
            reason = "UNAVAILABLE";
        } catch (FileNotFoundException ignored) {
            reason = "MISSING";
        } catch (EOFException ignored) {
            reason = "CORRUPT";
        } catch (IOException ignored) {
            reason = "UNKNOWN";
        } catch (StringIndexOutOfBoundsException ignored) {
            reason = "PARSE_PROBLEM";
        }
        final String raisin = reason;
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                GUIMediator.showError("PLAYLIST_CANNOT_PLAY_FILE", toPlay, "PLAYLIST_FILE_" + raisin);
            }
        });
        return false;
    }

    public int getFrameSeek() {
        return 0;
    }

    public void refresh() {
        if (getStatus() == STATUS_PLAYING) {
            fireAudioPositionUpdated(m_framesRead);
        }
    }

    /*****--------------------------------
     * HOW TO BE A ABSTRACTAUDIOPLAYER END
     *****-------------------------------/
        
        
    /**
    * Sets the data source as a file.
    */
    private void setDataSource(File file)
            throws UnsupportedAudioFileException, LineUnavailableException, IOException {
        if (file != null) {
            m_dataSource = file;
            initAudioInputStream();
        }
    }

    /**
     * Sets the data source as an url.
     */
    private void setDataSource(URL url)
            throws UnsupportedAudioFileException, LineUnavailableException, IOException {
        if (url != null) {
            m_dataSource = url;
            initAudioInputStream();
        }
    }

    /**
     * Inits Audio ressources from the data source.<br>
     * - AudioInputStream <br>
     * - AudioFileFormat
     */
    private void initAudioInputStream()
            throws UnsupportedAudioFileException, LineUnavailableException, IOException {
        if (m_dataSource instanceof URL) {
            initAudioInputStream((URL) m_dataSource);
        } else if (m_dataSource instanceof File) {
            initAudioInputStream((File) m_dataSource);
        }
    }

    /**
     * Inits Audio ressources from file.
     */
    private void initAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
        _file = file;
        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 AudioSystem.<br>
     * DateSource must be present.
     */
    protected void initLine() throws LineUnavailableException {
        if (m_line == null) {
            createLine();
            LOG.trace("Create Line OK ");
            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 Volume 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 {
        if (m_line == null) {
            AudioFormat sourceFormat = m_audioInputStream.getFormat();
            if (LOG.isDebugEnabled())
                LOG.debug("Source format : " + sourceFormat);
            AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                    sourceFormat.getSampleRate(), 16, sourceFormat.getChannels(), sourceFormat.getChannels() * 2,
                    sourceFormat.getSampleRate(), false);

            if (LOG.isDebugEnabled())
                LOG.debug("Target format: " + targetFormat);
            m_audioInputStream = AudioSystem.getAudioInputStream(targetFormat, m_audioInputStream);
            AudioFormat audioFormat = m_audioInputStream.getFormat();
            if (LOG.isDebugEnabled())
                LOG.debug("Create Line : " + audioFormat);
            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++) {
                if (LOG.isDebugEnabled())
                    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);
                if (LOG.isDebugEnabled())
                    LOG.debug("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);
                if (LOG.isDebugEnabled())
                    LOG.debug("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();
            if (LOG.isDebugEnabled())
                LOG.debug("AudioFormat : " + audioFormat);
            m_line.open(audioFormat, m_line.getBufferSize());
        }
    }

    /**
     * Stops the playback.<br>
     *
     * Player Status = STOPPED.<br>
     * Thread should free Audio ressources.
     */
    private void stopPlayback() {
        if ((m_status == PLAYING) || (m_status == PAUSED)) {
            if (m_line != null)
                m_line.flush();
            if (m_line != null)
                m_line.stop();
            m_status = STOPPED;
            LOG.debug("Stop called");
        }
    }

    /**
     * Pauses the playback.<br>
     *
     * Player Status = PAUSED.
     */
    private void pausePlayback() {
        if (m_line != null) {
            if (m_status == PLAYING) {
                m_line.flush();
                m_line.stop();
                m_status = PAUSED;
                LOG.debug("Pause called");
            }
        }
    }

    /**
     * Resumes the playback.<br>
     *
     * Player Status = PLAYING.
     */
    private void resumePlayback() {
        if (m_line != null) {
            if (m_status == PAUSED) {
                m_line.start();
                m_status = PLAYING;
                LOG.debug("Resume called");
            }
        }
    }

    /**
     * Starts playback.
     */
    private boolean startPlayback() {
        if ((m_status == STOPPED) || (m_status == READY)) {
            LOG.debug("Start called");
            if (!(m_thread == null || !m_thread.isAlive())) {
                LOG.debug("WARNING: old thread still running!!");
                int cnt = 0;
                while (m_status != READY) {
                    try {
                        if (m_thread != null) {
                            cnt++;
                            m_thread.sleep(1000);
                            if (cnt > 2)
                                m_thread.interrupt();
                        }
                    } catch (Exception e) {
                        LOG.debug("Waiting Error", e);
                    }
                    if (LOG.isDebugEnabled())
                        LOG.debug("Waiting ... " + cnt);
                }
            }
            try {
                initLine();
            } catch (Exception e) {
                LOG.debug("cannot init Line", e);
                //e.printStackTrace();
                return false;
            }
            LOG.trace("Creating new ManagedThread");
            m_thread = new ManagedThread(this, "BasicPlayer");
            m_thread.setDaemon(true);
            m_thread.start();
            if (m_line != null) {
                m_line.start();
                return true;
            }
        }
        return false;
    }

    /**
     * Main loop.
     *
     * Player Status == STOPPED => 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.debug("Thread Running");
        //if (m_audioInputStream.markSupported()) m_audioInputStream.mark(m_audioFileFormat.getByteLength());
        //else trace(1,getClass().getName(), "Mark not supported");
        int nBytesRead = 1;
        m_status = PLAYING;
        int nBytesCursor = 0;
        byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
        float nFrameSize = (float) m_line.getFormat().getFrameSize();
        float nFrameRate = m_line.getFormat().getFrameRate();
        float bytesPerSecond = nFrameSize * nFrameRate;
        int secondsTotal = Math.round((float) m_audioFileFormat.getByteLength() / bytesPerSecond);
        try {
            AudioMetaData amd = AudioMetaData.parseAudioFile(_file);
            if (amd != null)
                secondsTotal = amd.getLength();
        } catch (IOException ignored) {
        }

        fireSeekSetupRequired(secondsTotal);
        try {
            while ((nBytesRead != -1) && (m_status != STOPPED)) {
                if (m_status == PLAYING) {
                    try {
                        if (doSeek > -1) {
                            // Seek implementation. WAV format only !
                            if ((getAudioFileFormat() != null)
                                    && (getAudioFileFormat().getType().toString().startsWith("WAV"))) {
                                if ((secondsTotal != AudioSystem.NOT_SPECIFIED) && (secondsTotal > 0)) {
                                    m_line.flush();
                                    m_line.stop();
                                    //m_audioInputStream.reset();
                                    m_audioInputStream.close();
                                    m_audioInputStream = AudioSystem.getAudioInputStream(_file);
                                    nBytesCursor = 0;
                                    if (m_audioFileFormat.getByteLength() - doSeek < abData.length)
                                        doSeek = m_audioFileFormat.getByteLength() - abData.length;
                                    doSeek = doSeek - doSeek % 4;
                                    int toSkip = (int) doSeek;
                                    // skip(...) instead of read(...) runs out of memory ?!
                                    while ((toSkip > 0) && (nBytesRead > 0)) {
                                        if (toSkip > abData.length)
                                            nBytesRead = m_audioInputStream.read(abData, 0, abData.length);
                                        else
                                            nBytesRead = m_audioInputStream.read(abData, 0, toSkip);
                                        toSkip = toSkip - nBytesRead;
                                        nBytesCursor = nBytesCursor + nBytesRead;
                                    }
                                    m_line.start();
                                } else {
                                    if (LOG.isDebugEnabled())
                                        LOG.debug("Seek not supported for this InputStream : " + secondsTotal);
                                }
                            } else {
                                if (LOG.isDebugEnabled())
                                    LOG.debug("Seek not supported for this InputStream : " + secondsTotal);
                            }
                            doSeek = -1;
                        }
                        nBytesRead = m_audioInputStream.read(abData, 0, abData.length);
                    } catch (Exception e) {
                        if (LOG.isDebugEnabled())
                            LOG.debug("InputStream error : (" + nBytesRead + ")", e);
                        e.printStackTrace();
                        m_status = STOPPED;
                    }
                    if (nBytesRead >= 0) {
                        // make sure that you are writing an integral number of the
                        // frame size (nFrameSize).  i think this may skip a few
                        // frames but probably not a big deal.
                        if (nBytesRead % nFrameSize != 0)
                            nBytesRead -= (nBytesRead % nFrameSize);
                        int nBytesWritten = m_line.write(abData, 0, nBytesRead);
                        nBytesCursor = nBytesCursor + nBytesWritten;
                        m_framesRead = ((int) Math.round((float) nBytesCursor / bytesPerSecond));
                    }
                } else {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        LOG.debug("can't sleep", e);
                    }
                }
            }

        } finally {
            // close the file and free the audio line.
            try {
                if (m_line != null) {
                    try {
                        m_line.drain();
                        m_line.stop();
                    } finally {
                        try {
                            m_line.close();
                        } catch (SecurityException ignored) {
                            LOG.trace("Cannot Free Audio ressources", ignored);
                        }
                        m_line = null;
                    }
                }
            } finally {
                if (m_audioInputStream != null)
                    try {
                        m_audioInputStream.close();
                    } catch (IOException ignored) {
                    }
            }
        }
        LOG.trace("Thread Stopped");
        firePlayComplete();
        m_status = READY;
    }

    /*----------------------------------------------*/
    /*--               Gain Control               --*/
    /*----------------------------------------------*/

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

    /**
     * Sets Gain value.
     * Linear scale 0.0  <-->  1.0
     * Threshold Coef. : 1/2 to avoid saturation.
     */
    public void setGain(double fGain) {
        if (hasGainControl()) {
            double minGainDB = getMinimum();
            double ampGainDB = ((10.0f / 20.0f) * getMaximum()) - getMinimum();
            double cste = Math.log(10.0) / 20;
            double valueDB = minGainDB + (1 / cste) * Math.log(1 + (Math.exp(cste * ampGainDB) - 1) * fGain);
            //trace(1,getClass().getName(), "Gain : "+valueDB);
            m_gainControl.setValue((float) valueDB);
        }
    }

    /**
     * Returns Gain value.
     */
    public float getGain() {
        if (hasGainControl()) {
            return m_gainControl.getValue();
        } else {
            return 0.0F;
        }
    }

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

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

    /*----------------------------------------------*/
    /*--               Pan Control                --*/
    /*----------------------------------------------*/

    /**
     * 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();
        } else {
            return 0.0F;
        }
    }

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

    /**
     * Sets Pan value.
     * Linear scale : -1.0 <--> +1.0
     */
    public void setPan(float fPan) {
        if (hasPanControl()) {
            //trace(1,getClass().getName(), "Pan : "+fPan);
            m_panControl.setValue(fPan);
        }
    }

    /*----------------------------------------------*/
    /*--                   Seek                   --*/
    /*----------------------------------------------*/

    /**
     * Sets Seek value.
     * Linear scale : 0.0 <--> +1.0
     */
    public void setSeek(double seek) throws IOException {
        double length = -1;
        if ((m_audioFileFormat != null) && (m_audioFileFormat.getByteLength() != AudioSystem.NOT_SPECIFIED))
            length = (double) m_audioFileFormat.getByteLength();
        long newPos = (long) Math.round(seek * length);
        doSeek = newPos;
    }

    /*----------------------------------------------*/
    /*--               Audio Format               --*/
    /*----------------------------------------------*/

    /**
     * Returns source AudioFormat.
     */
    public AudioFormat getAudioFormat() {
        if (m_audioFileFormat != null) {
            return m_audioFileFormat.getFormat();
        } else
            return null;
    }

    /**
     * Returns source AudioFileFormat.
     */
    public AudioFileFormat getAudioFileFormat() {
        if (m_audioFileFormat != null) {
            return m_audioFileFormat;
        } else
            return null;
    }

    /**
     * Gets an InputStream from File.
     */
    protected InputStream openInput(File file) throws IOException {
        InputStream fileIn = new FileInputStream(file);
        BufferedInputStream bufIn = new BufferedInputStream(fileIn);
        return bufIn;
    }

    /*----------------------------------------------*/
    /*--                   Misc                   --*/
    /*----------------------------------------------*/
}