aboutsummaryrefslogtreecommitdiff
path: root/subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox
diff options
context:
space:
mode:
authorScott Jackson <daneren2005@gmail.com>2012-07-02 21:24:02 -0700
committerScott Jackson <daneren2005@gmail.com>2012-07-02 21:24:02 -0700
commita1a18f77a50804e0127dfa4b0f5240c49c541184 (patch)
tree19a38880afe505beddb5590379a8134d7730a277 /subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox
parentb61d787706979e7e20f4c3c4f93c1f129d92273f (diff)
downloaddsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.tar.gz
dsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.tar.bz2
dsub-a1a18f77a50804e0127dfa4b0f5240c49c541184.zip
Initial Commit
Diffstat (limited to 'subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox')
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/AudioPlayer.java207
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/PlayerTest.java75
2 files changed, 282 insertions, 0 deletions
diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/AudioPlayer.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/AudioPlayer.java
new file mode 100644
index 00000000..902387be
--- /dev/null
+++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/AudioPlayer.java
@@ -0,0 +1,207 @@
+/*
+ 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
+ }
+}
diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/PlayerTest.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/PlayerTest.java
new file mode 100644
index 00000000..30ed2847
--- /dev/null
+++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/PlayerTest.java
@@ -0,0 +1,75 @@
+package net.sourceforge.subsonic.service.jukebox;
+
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.FileInputStream;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * @author Sindre Mehus
+ * @version $Id$
+ */
+public class PlayerTest implements AudioPlayer.Listener {
+
+ private AudioPlayer player;
+
+ public PlayerTest() throws Exception {
+ player = new AudioPlayer(new FileInputStream("i:\\tmp\\foo.au"), this);
+ createGUI();
+ }
+
+ private void createGUI() {
+ JFrame frame = new JFrame();
+
+ JButton startButton = new JButton("Start");
+ JButton stopButton = new JButton("Stop");
+ JButton resetButton = new JButton("Reset");
+ final JSlider gainSlider = new JSlider(0, 1000);
+
+ startButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ player.play();
+ }
+ });
+ stopButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ player.pause();
+ }
+ });
+ resetButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ player.close();
+ }
+ });
+ gainSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ float gain = (float) gainSlider.getValue() / 1000.0F;
+ player.setGain(gain);
+ }
+ });
+
+ frame.setLayout(new FlowLayout());
+ frame.add(startButton);
+ frame.add(stopButton);
+ frame.add(resetButton);
+ frame.add(gainSlider);
+
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ public static void main(String[] args) throws Exception {
+ new PlayerTest();
+ }
+
+ public void stateChanged(AudioPlayer player, AudioPlayer.State state) {
+ System.out.println(state);
+ }
+}
+