net.sourceforge.subsonic.service.jukebox.AudioPlayer.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.subsonic.service.jukebox.AudioPlayer.java

Source

/*
 This file is part of Subsonic.
    
 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 Subsonic 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 General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
    
 Copyright 2009 (C) Sindre Mehus
 */
package net.sourceforge.subsonic.service.jukebox;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicReference;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;

import org.apache.commons.io.IOUtils;

import net.sourceforge.subsonic.Logger;
import net.sourceforge.subsonic.service.JukeboxService;

import static net.sourceforge.subsonic.service.jukebox.AudioPlayer.State.*;

/**
 * A simple wrapper for playing sound from an input stream.
 * <p/>
 * Supports pause and resume, but not restarting.
 *
 * @author Sindre Mehus
 * @version $Id$
 */
public class AudioPlayer {

    private static final Logger LOG = Logger.getLogger(JukeboxService.class);

    private final InputStream in;
    private final Listener listener;
    private final SourceDataLine line;
    private final AtomicReference<State> state = new AtomicReference<State>(PAUSED);
    private FloatControl gainControl;

    public AudioPlayer(InputStream in, Listener listener) throws Exception {
        this.in = new BufferedInputStream(in);
        this.listener = listener;

        AudioFormat format = AudioSystem.getAudioFileFormat(this.in).getFormat();
        line = AudioSystem.getSourceDataLine(format);
        line.open(format);
        LOG.debug("Opened line " + line);

        if (line.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
            gainControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
            setGain(0.5f);
        }
        new AudioDataWriter();
    }

    /**
     * Starts (or resumes) the player.  This only has effect if the current state is
     * {@link State#PAUSED}.
     */
    public synchronized void play() {
        if (state.get() == PAUSED) {
            line.start();
            setState(PLAYING);
        }
    }

    /**
     * Pauses the player.  This only has effect if the current state is
     * {@link State#PLAYING}.
     */
    public synchronized void pause() {
        if (state.get() == PLAYING) {
            setState(PAUSED);
            line.stop();
            line.flush();
        }
    }

    /**
     * Closes the player, releasing all resources. After this the player state is
     * {@link State#CLOSED} (unless the current state is {@link State#EOM}).
     */
    public synchronized void close() {
        if (state.get() != CLOSED && state.get() != EOM) {
            setState(CLOSED);
        }

        try {
            line.stop();
        } catch (Throwable x) {
            LOG.warn("Failed to stop player: " + x, x);
        }
        try {
            if (line.isOpen()) {
                line.close();
                LOG.debug("Closed line " + line);
            }
        } catch (Throwable x) {
            LOG.warn("Failed to close player: " + x, x);
        }
        IOUtils.closeQuietly(in);
    }

    /**
     * Returns the player state.
     */
    public State getState() {
        return state.get();
    }

    /**
     * Sets the gain.
     *
     * @param gain The gain between 0.0 and 1.0.
     */
    public void setGain(float gain) {
        if (gainControl != null) {

            double minGainDB = gainControl.getMinimum();
            double maxGainDB = gainControl.getMaximum();
            double ampGainDB = 0.5f * maxGainDB - minGainDB;
            double cste = Math.log(10.0) / 20;
            double valueDB = minGainDB + (1 / cste) * Math.log(1 + (Math.exp(cste * ampGainDB) - 1) * gain);

            valueDB = Math.min(valueDB, maxGainDB);
            valueDB = Math.max(valueDB, minGainDB);

            gainControl.setValue((float) valueDB);
        }
    }

    /**
     * Returns the position in seconds.
     */
    public int getPosition() {
        return (int) (line.getMicrosecondPosition() / 1000000L);
    }

    private void setState(State state) {
        if (this.state.getAndSet(state) != state && listener != null) {
            listener.stateChanged(this, state);
        }
    }

    private class AudioDataWriter implements Runnable {

        public AudioDataWriter() {
            new Thread(this).start();
        }

        public void run() {
            try {
                byte[] buffer = new byte[8192];

                while (true) {

                    switch (state.get()) {
                    case CLOSED:
                    case EOM:
                        return;
                    case PAUSED:
                        Thread.sleep(250);
                        break;
                    case PLAYING:
                        int n = in.read(buffer);
                        if (n == -1) {
                            setState(EOM);
                            return;
                        }
                        line.write(buffer, 0, n);
                        break;
                    }
                }
            } catch (Throwable x) {
                LOG.warn("Error when copying audio data: " + x, x);
            } finally {
                close();
            }
        }
    }

    public interface Listener {
        void stateChanged(AudioPlayer player, State state);
    }

    public static enum State {
        PAUSED, PLAYING, CLOSED, EOM
    }
}