From a1a18f77a50804e0127dfa4b0f5240c49c541184 Mon Sep 17 00:00:00 2001 From: Scott Jackson Date: Mon, 2 Jul 2012 21:24:02 -0700 Subject: Initial Commit --- .../subsonic/service/jukebox/AudioPlayer.java | 207 +++++++++++++++++++++ .../subsonic/service/jukebox/PlayerTest.java | 75 ++++++++ 2 files changed, 282 insertions(+) create mode 100644 subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/AudioPlayer.java create mode 100644 subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox/PlayerTest.java (limited to 'subsonic-main/src/main/java/net/sourceforge/subsonic/service/jukebox') 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 . + + 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. + *

+ * 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 = new AtomicReference(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); + } +} + -- cgit v1.2.3