diff --git a/src/main/java/com/goxr3plus/streamplayer/application/AnotherDemoApplication.java b/src/main/java/com/goxr3plus/streamplayer/application/AnotherDemoApplication.java index 5bd49e8..c47c260 100644 --- a/src/main/java/com/goxr3plus/streamplayer/application/AnotherDemoApplication.java +++ b/src/main/java/com/goxr3plus/streamplayer/application/AnotherDemoApplication.java @@ -3,6 +3,7 @@ import com.goxr3plus.streamplayer.enums.Status; import com.goxr3plus.streamplayer.stream.StreamPlayer; import com.goxr3plus.streamplayer.stream.StreamPlayerEvent; +import com.goxr3plus.streamplayer.stream.StreamPlayerInterface; import com.goxr3plus.streamplayer.stream.StreamPlayerListener; import java.io.File; @@ -16,10 +17,10 @@ public class AnotherDemoApplication { private final String audioFileName = "Logic - Ballin [Bass Boosted].mp3"; - private StreamPlayer streamPlayer; + private StreamPlayerInterface streamPlayer; private StreamPlayerListener listener; - public AnotherDemoApplication(StreamPlayer streamPlayer) { + public AnotherDemoApplication(StreamPlayerInterface streamPlayer) { this.streamPlayer = streamPlayer; this.listener = new AnotherStreamPlayerListener(audioFileName, streamPlayer); diff --git a/src/main/java/com/goxr3plus/streamplayer/application/AnotherMain.java b/src/main/java/com/goxr3plus/streamplayer/application/AnotherMain.java index d8bfdb0..66ede42 100644 --- a/src/main/java/com/goxr3plus/streamplayer/application/AnotherMain.java +++ b/src/main/java/com/goxr3plus/streamplayer/application/AnotherMain.java @@ -1,12 +1,13 @@ package com.goxr3plus.streamplayer.application; import com.goxr3plus.streamplayer.stream.StreamPlayer; +import com.goxr3plus.streamplayer.stream.StreamPlayerInterface; import com.goxr3plus.streamplayer.stream.StreamPlayerListener; public class AnotherMain { public static void main(String[] args) { - final StreamPlayer streamPlayer = new StreamPlayer(); + final StreamPlayerInterface streamPlayer = new StreamPlayer(); final AnotherDemoApplication application = new AnotherDemoApplication(streamPlayer); application.start(); diff --git a/src/main/java/com/goxr3plus/streamplayer/application/AnotherStreamPlayerListener.java b/src/main/java/com/goxr3plus/streamplayer/application/AnotherStreamPlayerListener.java index 2b27805..0cae44c 100644 --- a/src/main/java/com/goxr3plus/streamplayer/application/AnotherStreamPlayerListener.java +++ b/src/main/java/com/goxr3plus/streamplayer/application/AnotherStreamPlayerListener.java @@ -3,6 +3,7 @@ import com.goxr3plus.streamplayer.enums.Status; import com.goxr3plus.streamplayer.stream.StreamPlayer; import com.goxr3plus.streamplayer.stream.StreamPlayerEvent; +import com.goxr3plus.streamplayer.stream.StreamPlayerInterface; import com.goxr3plus.streamplayer.stream.StreamPlayerListener; import java.util.Map; @@ -10,10 +11,10 @@ class AnotherStreamPlayerListener implements StreamPlayerListener { private final String audioFileName; - private StreamPlayer streamPlayer; + private StreamPlayerInterface streamPlayer; - public AnotherStreamPlayerListener(String audioFileName, StreamPlayer streamPlayer) { + public AnotherStreamPlayerListener(String audioFileName, StreamPlayerInterface streamPlayer) { this.audioFileName = audioFileName; this.streamPlayer = streamPlayer; } diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/Outlet.java b/src/main/java/com/goxr3plus/streamplayer/stream/Outlet.java new file mode 100644 index 0000000..efe293e --- /dev/null +++ b/src/main/java/com/goxr3plus/streamplayer/stream/Outlet.java @@ -0,0 +1,224 @@ +package com.goxr3plus.streamplayer.stream; + +import javax.sound.sampled.*; +import java.util.logging.Logger; + +/** + * Owner of the SourceDataLine which is the output line of the player. + * Also owns controls for the SourceDataLine. + * Future goal is to move all handling of the SourceDataLine to here, + * so that the StreamPlayer doesn't have to call {@link #getSourceDataLine()}. + * Another goal is to remove some of the setter and getter methods of this class, + * by moving all code that needs them to this class. + */ +public class Outlet { + + private final Logger logger; + private FloatControl balanceControl; + private FloatControl gainControl; + private BooleanControl muteControl; + private FloatControl panControl; + private SourceDataLine sourceDataLine; + + /** + * @param logger used to log messages + */ + public Outlet(Logger logger) { + this.logger = logger; + } + + + /** + * @return the balance control of the {@link #sourceDataLine} + */ + public FloatControl getBalanceControl() { + return balanceControl; + } + + /** + * @return the gain control of the {@link #sourceDataLine} + */ + public FloatControl getGainControl() { + return gainControl; + } + + /** + * @return the mute control of the {@link #sourceDataLine} + */ + public BooleanControl getMuteControl() { + return muteControl; + } + + /** + * @return the pan control of the {@link #sourceDataLine} + */ + public FloatControl getPanControl() { + return panControl; + } + + /** + * @return the {@link #sourceDataLine}, which is the output audio signal of the player + */ + public SourceDataLine getSourceDataLine() { + return sourceDataLine; + } + + + /** + * @param balanceControl to be set on the {@link #sourceDataLine} + */ + public void setBalanceControl(FloatControl balanceControl) { + this.balanceControl = balanceControl; + } + + /** + * @param gainControl to be set on the {@link #sourceDataLine} + */ + public void setGainControl(FloatControl gainControl) { + this.gainControl = gainControl; + } + + /** + * @param muteControl to be set on the {@link #sourceDataLine} + */ + public void setMuteControl(BooleanControl muteControl) { + this.muteControl = muteControl; + } + + /** + * @param panControl to be set on the {@link #sourceDataLine} + */ + public void setPanControl(FloatControl panControl) { + this.panControl = panControl; + } + + /** + * @param sourceDataLine representing the audio output of the player. + * Usually taken from {@link AudioSystem#getLine(Line.Info)}. + */ + public void setSourceDataLine(SourceDataLine sourceDataLine) { + this.sourceDataLine = sourceDataLine; + } + + + /** + * Check if the Control is Supported by m_line. + * + * @param control the control + * @param component the component + * + * @return true, if successful + */ + public boolean hasControl(final Control.Type control, final Control component) { + return component != null && (sourceDataLine != null) && (sourceDataLine.isControlSupported(control)); + } + + /** + * Returns Gain value. + * + * @return The Gain Value + */ + public float getGainValue() { + + if (hasControl(FloatControl.Type.MASTER_GAIN, getGainControl())) { + return getGainControl().getValue(); + } else { + return 0.0F; + } + } + + /** + * Stop the {@link #sourceDataLine} in a nice way. + * Also nullify it. (Is that necessary?) + */ + void drainStopAndFreeDataLine() { + // Free audio resources. + if (sourceDataLine != null) { + sourceDataLine.drain(); + sourceDataLine.stop(); + sourceDataLine.close(); + this.sourceDataLine = null; // TODO: Is this necessary? Will it not be garbage collected? + } + } + + /** + * Flush and close the {@link #sourceDataLine} in a nice way. + * Also nullify it. (Is that necessary?) + */ + void flushAndFreeDataLine() { + if (sourceDataLine != null) { + sourceDataLine.flush(); + sourceDataLine.close(); + this.sourceDataLine = null; // TODO: Is this necessary? Will it not be garbage collected? + } + } + + /** + * Flush and stop the {@link #sourceDataLine}, if it's running. + */ + void flushAndStop() { + // Flush and stop the source data line + if (sourceDataLine != null && sourceDataLine.isRunning()) { // TODO: Risk for NullPointerException? + sourceDataLine.flush(); + sourceDataLine.stop(); + } + } + + /** + * @return true if the {@link #sourceDataLine} is startable. + */ + boolean isStartable() { + return sourceDataLine != null && !sourceDataLine.isRunning(); + } + + + /** + * Start the {@link #sourceDataLine} + */ + void start() { + sourceDataLine.start(); + } + + /** + * Open the {@link #sourceDataLine}. + * Also create controls for it. + * @param format The wanted audio format. + * @param bufferSize the desired buffer size for the {@link #sourceDataLine} + * @throws LineUnavailableException + */ + void open(AudioFormat format, int bufferSize) throws LineUnavailableException { + logger.info("Entered OpenLine()!:\n"); + + if (sourceDataLine != null) { + sourceDataLine.open(format, bufferSize); + + // opened? + if (sourceDataLine.isOpen()) { + + // Master_Gain Control? + if (sourceDataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) + setGainControl((FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN)); + else setGainControl(null); + + // PanControl? + if (sourceDataLine.isControlSupported(FloatControl.Type.PAN)) + setPanControl((FloatControl) sourceDataLine.getControl(FloatControl.Type.PAN)); + else setPanControl(null); + + // Mute? + BooleanControl muteControl1 = sourceDataLine.isControlSupported(BooleanControl.Type.MUTE) + ? (BooleanControl) sourceDataLine.getControl(BooleanControl.Type.MUTE) + : null; + setMuteControl(muteControl1); + + // Speakers Balance? + FloatControl balanceControl = sourceDataLine.isControlSupported(FloatControl.Type.BALANCE) + ? (FloatControl) sourceDataLine.getControl(FloatControl.Type.BALANCE) + : null; + setBalanceControl(balanceControl); + } + } + logger.info("Exited OpenLine()!:\n"); + } + +} diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index 126adde..76a0c82 100644 --- a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java @@ -34,8 +34,6 @@ import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.BooleanControl; -import javax.sound.sampled.Control; -import javax.sound.sampled.Control.Type; import javax.sound.sampled.DataLine; import javax.sound.sampled.FloatControl; import javax.sound.sampled.Line; @@ -60,14 +58,14 @@ * @author GOXR3PLUS (www.goxr3plus.co.nf) * @author JavaZOOM (www.javazoom.net) */ -public class StreamPlayer implements Callable { +public class StreamPlayer implements StreamPlayerInterface, Callable { /** * Class logger */ private Logger logger; - // -------------------AUDIO--------------------- + // -------------------AUDIO----------------,----- private volatile Status status = Status.NOT_SPECIFIED; @@ -83,26 +81,6 @@ public class StreamPlayer implements Callable { /** The audio file format. */ private AudioFileFormat audioFileFormat; - /** The source data line. */ - private SourceDataLine sourceDataLine; - - // -------------------CONTROLS--------------------- - - /** The gain control. */ - private FloatControl gainControl; - - /** The pan control. */ - private FloatControl panControl; - - /** The balance control. */ - private FloatControl balanceControl; - - /** The sample rate control. */ - // private FloatControl sampleRateControl - - /** The mute control. */ - private BooleanControl muteControl; - // -------------------LOCKS--------------------- /** @@ -159,7 +137,10 @@ public class StreamPlayer implements Callable { // Properties when the File/URL/InputStream is opened. Map audioProperties; - // -------------------BEGIN OF CONSTRUCTOR--------------------- + /** + * Responsible for the output SourceDataLine and the controls that depend on it. + */ + private Outlet outlet; /** * Default parameter less Constructor. A default logger will be used. @@ -190,12 +171,14 @@ public StreamPlayer(Logger logger, ExecutorService streamPlayerExecutorService, this.streamPlayerExecutorService = streamPlayerExecutorService; this.eventsExecutorService = eventsExecutorService; listeners = new ArrayList<>(); + outlet = new Outlet(logger); reset(); } /** * Freeing the resources. */ + @Override public void reset() { // Close the stream @@ -203,12 +186,7 @@ public void reset() { closeStream(); } - // Source Data Line - if (sourceDataLine != null) { - sourceDataLine.flush(); - sourceDataLine.close(); - sourceDataLine = null; - } + outlet.flushAndFreeDataLine(); // AudioFile audioInputStream = null; @@ -217,10 +195,9 @@ public void reset() { encodedAudioLength = -1; // Controls - gainControl = null; - panControl = null; - balanceControl = null; - // sampleRateControl = null + outlet.setGainControl(null); + outlet.setPanControl(null); + outlet.setBalanceControl(null); // Notify the Status status = Status.NOT_SPECIFIED; @@ -253,6 +230,7 @@ private String generateEvent(final Status status, final int encodedStreamPositio * * @param streamPlayerListener the listener */ + @Override public void addStreamPlayerListener(final StreamPlayerListener streamPlayerListener) { listeners.add(streamPlayerListener); } @@ -262,6 +240,7 @@ public void addStreamPlayerListener(final StreamPlayerListener streamPlayerListe * * @param streamPlayerListener the listener */ + @Override public void removeStreamPlayerListener(final StreamPlayerListener streamPlayerListener) { if (listeners != null) listeners.remove(streamPlayerListener); @@ -275,6 +254,7 @@ public void removeStreamPlayerListener(final StreamPlayerListener streamPlayerLi * * @throws StreamPlayerException the stream player exception */ + @Override public void open(final Object object) throws StreamPlayerException { logger.info(() -> "open(" + object + ")\n"); @@ -381,10 +361,10 @@ private void determineProperties() { audioProperties.putAll(audioFormat.properties()); // Add SourceDataLine - audioProperties.put("basicplayer.sourcedataline", sourceDataLine); + audioProperties.put("basicplayer.sourcedataline", outlet.getSourceDataLine()); // Keep this final reference for the lambda expression - final Map audioPropertiesCopy = audioProperties; + final Map audioPropertiesCopy = audioProperties; // TODO: Remove, it's meaningless. // Notify all registered StreamPlayerListeners listeners.forEach(listener -> listener.opened(dataSource, audioPropertiesCopy)); @@ -403,13 +383,18 @@ private void initLine() throws LineUnavailableException, StreamPlayerException { logger.info("Initiating the line..."); - if (sourceDataLine == null) + if (outlet.getSourceDataLine() == null) createLine(); - if (!sourceDataLine.isOpen()) - openLine(); - else if (!sourceDataLine.getFormat().equals(audioInputStream == null ? null : audioInputStream.getFormat())) { - sourceDataLine.close(); - openLine(); + if (!outlet.getSourceDataLine().isOpen()) { + currentLineBufferSize = lineBufferSize >= 0 ? lineBufferSize : outlet.getSourceDataLine().getBufferSize(); + openLine(audioInputStream.getFormat(), currentLineBufferSize); + } else { + AudioFormat format = audioInputStream == null ? null : audioInputStream.getFormat(); + if (!outlet.getSourceDataLine().getFormat().equals(format)) { // TODO: Check if bug, does equals work as intended? + outlet.getSourceDataLine().close(); + currentLineBufferSize = lineBufferSize >= 0 ? lineBufferSize : outlet.getSourceDataLine().getBufferSize(); + openLine(audioInputStream.getFormat(), currentLineBufferSize); + } } } @@ -423,6 +408,7 @@ else if (!sourceDataLine.getFormat().equals(audioInputStream == null ? null : au * * @param speedFactor speedFactor */ + @Override public void setSpeedFactor(final double speedFactor) { this.speedFactor = speedFactor; @@ -447,7 +433,7 @@ private void createLine() throws LineUnavailableException, StreamPlayerException logger.info("Entered CreateLine()!:\n"); - if (sourceDataLine != null) + if (outlet.getSourceDataLine() != null) logger.warning("Warning Source DataLine is not null!\n"); else { final AudioFormat sourceFormat = audioInputStream.getFormat(); @@ -494,19 +480,19 @@ private void createLine() throws LineUnavailableException, StreamPlayerException // Continue final Mixer mixer = getMixer(mixerName); if (mixer == null) { - sourceDataLine = (SourceDataLine) AudioSystem.getLine(lineInfo); + outlet.setSourceDataLine((SourceDataLine) AudioSystem.getLine(lineInfo)); mixerName = null; } else { logger.info("Mixer: " + mixer.getMixerInfo()); - sourceDataLine = (SourceDataLine) mixer.getLine(lineInfo); + outlet.setSourceDataLine((SourceDataLine) mixer.getLine(lineInfo)); } - sourceDataLine = (SourceDataLine) AudioSystem.getLine(lineInfo); + outlet.setSourceDataLine((SourceDataLine) AudioSystem.getLine(lineInfo)); // -------------------------------------------------------------------------------- - logger.info(() -> "Line : " + sourceDataLine); - logger.info(() -> "Line Info : " + sourceDataLine.getLineInfo()); - logger.info(() -> "Line AudioFormat: " + sourceDataLine.getFormat() + "\n"); + logger.info(() -> "Line : " + outlet.getSourceDataLine()); + logger.info(() -> "Line Info : " + outlet.getSourceDataLine().getLineInfo()); + logger.info(() -> "Line AudioFormat: " + outlet.getSourceDataLine().getFormat() + "\n"); logger.info("Exited CREATELINE()!:\n"); } } @@ -515,54 +501,11 @@ private void createLine() throws LineUnavailableException, StreamPlayerException * Open the line. * * @throws LineUnavailableException the line unavailable exception + * @param audioFormat + * @param currentLineBufferSize */ - private void openLine() throws LineUnavailableException { - - logger.info("Entered OpenLine()!:\n"); - - if (sourceDataLine != null) { - final AudioFormat audioFormat = audioInputStream.getFormat(); - currentLineBufferSize = lineBufferSize >= 0 ? lineBufferSize : sourceDataLine.getBufferSize(); - sourceDataLine.open(audioFormat, currentLineBufferSize); - - // opened? - if (sourceDataLine.isOpen()) { - // logger.info(() -> "Open Line Buffer Size=" + bufferSize + "\n"); - - /*-- Display supported controls --*/ - // Control[] c = m_line.getControls() - - // Master_Gain Control? - if (sourceDataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) - gainControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN); - else gainControl = null; - - // PanControl? - if (sourceDataLine.isControlSupported(FloatControl.Type.PAN)) - panControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.PAN); - else panControl = null; - - // SampleRate? - // if (sourceDataLine.isControlSupported(FloatControl.Type.SAMPLE_RATE)) - // sampleRateControl = (FloatControl) - // sourceDataLine.getControl(FloatControl.Type.SAMPLE_RATE); - // else - // sampleRateControl = null - - // Mute? - muteControl = sourceDataLine.isControlSupported(BooleanControl.Type.MUTE) - ? (BooleanControl) sourceDataLine.getControl(BooleanControl.Type.MUTE) - : null; - - // Speakers Balance? - balanceControl = sourceDataLine.isControlSupported(FloatControl.Type.BALANCE) - ? (FloatControl) sourceDataLine.getControl(FloatControl.Type.BALANCE) - : null; - } - - } - - logger.info("Exited OpenLine()!:\n"); + private void openLine(AudioFormat audioFormat, int currentLineBufferSize) throws LineUnavailableException { + outlet.open(audioFormat, currentLineBufferSize); } /** @@ -570,6 +513,7 @@ private void openLine() throws LineUnavailableException { * * @throws StreamPlayerException the stream player exception */ + @Override public void play() throws StreamPlayerException { if (status == Status.STOPPED) initAudioInputStream(); @@ -587,8 +531,8 @@ public void play() throws StreamPlayerException { } // Open the sourceDataLine - if (sourceDataLine != null && !sourceDataLine.isRunning()) { - sourceDataLine.start(); + if (outlet.isStartable()) { + outlet.start(); // Proceed only if we have not problems logger.info("Submitting new StreamPlayer Thread"); @@ -607,8 +551,9 @@ public void play() throws StreamPlayerException { * * @return true, if successful */ + @Override public boolean pause() { - if (sourceDataLine == null || status != Status.PLAYING) + if (outlet.getSourceDataLine() == null || status != Status.PLAYING) return false; status = Status.PAUSED; logger.info("pausePlayback() completed"); @@ -622,6 +567,7 @@ public boolean pause() { * Player Status = STOPPED.
* Thread should free Audio resources. */ + @Override public void stop() { if (status == Status.STOPPED) return; @@ -639,10 +585,11 @@ public void stop() { * * @return False if failed(so simple...) */ + @Override public boolean resume() { - if (sourceDataLine == null || status != Status.PAUSED) + if (outlet.getSourceDataLine() == null || status != Status.PAUSED) return false; - sourceDataLine.start(); + outlet.start(); status = Status.PLAYING; generateEvent(Status.RESUMED, getEncodedStreamPosition(), null); logger.info("resumePlayback() completed"); @@ -700,6 +647,7 @@ private void awaitTermination() { * * @throws StreamPlayerException the stream player exception */ + @Override public long seekBytes(final long bytes) throws StreamPlayerException { long totalSkipped = 0; @@ -762,6 +710,7 @@ else if (previousStatus == Status.PAUSED) { * * @param seconds Seconds to Skip */ + @Override //todo not finished needs more validations public long seekSeconds(int seconds) throws StreamPlayerException { int durationInSeconds = this.getDurationInSeconds(); @@ -795,6 +744,7 @@ public long seekSeconds(int seconds) throws StreamPlayerException { * * @param seconds Seconds to Skip */ + @Override public long seekTo(int seconds) throws StreamPlayerException { int durationInSeconds = this.getDurationInSeconds(); @@ -809,17 +759,6 @@ public long seekTo(int seconds) throws StreamPlayerException { return seekBytes(bytes); } -// /** -// * Go to X time of the Audio -// * See {@link #seek(long)} -// * -// * @param pattern A string in the format (HH:MM:SS) WHERE h = HOURS , M = minutes , S = seconds -// */ -// public void seekTo(String pattern) throws StreamPlayerException { -// long bytes = 0; -// -// seek(bytes); -// } private void validateSeconds(int seconds, int durationInSeconds) { if (seconds < 0) { @@ -829,6 +768,8 @@ private void validateSeconds(int seconds, int durationInSeconds) { } } + + @Override public int getDurationInSeconds() { // Audio resources from file||URL||inputStream. @@ -864,15 +805,6 @@ public Void call() { // Main play/pause loop. while ((nBytesRead != -1) && status != Status.STOPPED && status != Status.NOT_SPECIFIED && status != Status.SEEKING) { - // if (status == Status.SEEKING) { - // try { - // System.out.println("Audio Seeking ..."); - // Thread.sleep(50); - // } catch (InterruptedException e) { - // e.printStackTrace(); - // } - // continue; - // } try { // Playing? @@ -888,9 +820,9 @@ public Void call() { toRead)) != -1; toRead -= nBytesRead, totalRead += nBytesRead) // Check for under run - if (sourceDataLine.available() >= sourceDataLine.getBufferSize()) - logger.info(() -> "Underrun> Available=" + sourceDataLine.available() - + " , SourceDataLineBuffer=" + sourceDataLine.getBufferSize()); + if (outlet.getSourceDataLine().available() >= outlet.getSourceDataLine().getBufferSize()) + logger.info(() -> "Underrun> Available=" + outlet.getSourceDataLine().available() + + " , SourceDataLineBuffer=" + outlet.getSourceDataLine().getBufferSize()); // Check if anything has been read if (totalRead > 0) { @@ -904,43 +836,31 @@ public Void call() { } // Writes audio data to the mixer via this source data line - sourceDataLine.write(trimBuffer, 0, totalRead); + outlet.getSourceDataLine().write(trimBuffer, 0, totalRead); // Compute position in bytes in encoded stream. final int nEncodedBytes = getEncodedStreamPosition(); - // System.err.println(trimBuffer[0] + " , Data Length :" + trimBuffer.length) - // Notify all registered Listeners listeners.forEach(listener -> { if (audioInputStream instanceof PropertiesContainer) { // Pass audio parameters such as instant // bit rate, ... - listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), + listener.progress(nEncodedBytes, outlet.getSourceDataLine().getMicrosecondPosition(), trimBuffer, ((PropertiesContainer) audioInputStream).properties()); } else // Pass audio parameters - listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), + listener.progress(nEncodedBytes, outlet.getSourceDataLine().getMicrosecondPosition(), trimBuffer, emptyMap); }); } } else if (status == Status.PAUSED) { - // Flush and stop the source data line - if (sourceDataLine != null && sourceDataLine.isRunning()) { - sourceDataLine.flush(); - sourceDataLine.stop(); - } - try { - while (status == Status.PAUSED) { - Thread.sleep(50); - } - } catch (final InterruptedException ex) { - Thread.currentThread().interrupt(); - logger.warning("Thread cannot sleep.\n" + ex); - } + outlet.flushAndStop(); + goOutOfPause(); + } } catch (final IOException ex) { logger.log(Level.WARNING, "\"Decoder Exception: \" ", ex); @@ -948,14 +868,8 @@ public Void call() { generateEvent(Status.STOPPED, getEncodedStreamPosition(), null); } } - // Free audio resources. - if (sourceDataLine != null) { - sourceDataLine.drain(); - sourceDataLine.stop(); - sourceDataLine.close(); - sourceDataLine = null; - } + outlet.drainStopAndFreeDataLine(); // Close stream. closeStream(); @@ -975,6 +889,17 @@ public Void call() { return null; } + private void goOutOfPause() { + try { + while (status == Status.PAUSED) { + Thread.sleep(50); + } + } catch (final InterruptedException ex) { + Thread.currentThread().interrupt(); + logger.warning("Thread cannot sleep.\n" + ex); + } + } + /** * Calculates the current position of the encoded audio based on
* nEncodedBytes = encodedAudioLength - @@ -982,6 +907,7 @@ public Void call() { * * @return The Position of the encoded stream in term of bytes */ + @Override public int getEncodedStreamPosition() { int position = -1; if (dataSource instanceof File && encodedAudioInputStream != null) @@ -1013,6 +939,7 @@ private void closeStream() { * * @return -1 maximum buffer size. */ + @Override public int getLineBufferSize() { return lineBufferSize; } @@ -1022,6 +949,7 @@ public int getLineBufferSize() { * * @return The current line buffer size */ + @Override public int getLineCurrentBufferSize() { return currentLineBufferSize; } @@ -1031,6 +959,7 @@ public int getLineCurrentBufferSize() { * * @return A List of available Mixers */ + @Override public List getMixers() { final List mixers = new ArrayList<>(); @@ -1076,30 +1005,14 @@ private Mixer getMixer(final String name) { return mixer; } - /** - * Check if the Control is Supported by m_line. - * - * @param control the control - * @param component the component - * - * @return true, if successful - */ - private boolean hasControl(final Type control, final Control component) { - return component != null && (sourceDataLine != null) && (sourceDataLine.isControlSupported(control)); - } - /** * Returns Gain value. * * @return The Gain Value */ + @Override public float getGainValue() { - - if (hasControl(FloatControl.Type.MASTER_GAIN, gainControl)) { - return gainControl.getValue(); - } else { - return 0.0F; - } + return outlet.getGainValue(); } /** @@ -1107,8 +1020,9 @@ public float getGainValue() { * * @return The Maximum Gain Value */ + @Override public float getMaximumGain() { - return !hasControl(FloatControl.Type.MASTER_GAIN, gainControl) ? 0.0F : gainControl.getMaximum(); + return !outlet.hasControl(FloatControl.Type.MASTER_GAIN, outlet.getGainControl()) ? 0.0F : outlet.getGainControl().getMaximum(); } @@ -1117,9 +1031,10 @@ public float getMaximumGain() { * * @return The Minimum Gain Value */ + @Override public float getMinimumGain() { - return !hasControl(FloatControl.Type.MASTER_GAIN, gainControl) ? 0.0F : gainControl.getMinimum(); + return !outlet.hasControl(FloatControl.Type.MASTER_GAIN, outlet.getGainControl()) ? 0.0F : outlet.getGainControl().getMinimum(); } @@ -1128,8 +1043,9 @@ public float getMinimumGain() { * * @return The Precision Value */ + @Override public float getPrecision() { - return !hasControl(FloatControl.Type.PAN, panControl) ? 0.0F : panControl.getPrecision(); + return !outlet.hasControl(FloatControl.Type.PAN, outlet.getPanControl()) ? 0.0F : outlet.getPanControl().getPrecision(); } @@ -1138,8 +1054,9 @@ public float getPrecision() { * * @return The Pan Value */ + @Override public float getPan() { - return !hasControl(FloatControl.Type.PAN, panControl) ? 0.0F : panControl.getValue(); + return !outlet.hasControl(FloatControl.Type.PAN, outlet.getPanControl()) ? 0.0F : outlet.getPanControl().getValue(); } @@ -1148,8 +1065,9 @@ public float getPan() { * * @return True if muted , False if not */ + @Override public boolean getMute() { - return hasControl(BooleanControl.Type.MUTE, muteControl) && muteControl.getValue(); + return outlet.hasControl(BooleanControl.Type.MUTE, outlet.getMuteControl()) && outlet.getMuteControl().getValue(); } /** @@ -1157,8 +1075,9 @@ public boolean getMute() { * * @return The Balance Value */ + @Override public float getBalance() { - return !hasControl(FloatControl.Type.BALANCE, balanceControl) ? 0f : balanceControl.getValue(); + return !outlet.hasControl(FloatControl.Type.BALANCE, outlet.getBalanceControl()) ? 0f : outlet.getBalanceControl().getValue(); } /**** @@ -1166,23 +1085,15 @@ public float getBalance() { * * @return encodedAudioLength */ + @Override public long getTotalBytes() { return encodedAudioLength; } - /** - * @return - */ - // public int getByteLength() { - // return audioProperties == null || - // !audioProperties.containsKey("audio.length.bytes") ? - // AudioSystem.NOT_SPECIFIED - // : ((Integer) audioProperties.get("audio.length.bytes")).intValue(); - // } - /** * @return BytePosition */ + @Override public int getPositionByte() { final int positionByte = AudioSystem.NOT_SPECIFIED; if (audioProperties != null) { @@ -1194,13 +1105,9 @@ public int getPositionByte() { return positionByte; } - /** - * Gets the source data line. - * - * @return The SourceDataLine - */ - public SourceDataLine getSourceDataLine() { - return sourceDataLine; + /** The source data line. */ + public Outlet getOutlet() { + return outlet; } /** @@ -1208,6 +1115,7 @@ public SourceDataLine getSourceDataLine() { * * @return The Player Status */ + @Override public Status getStatus() { return status; } @@ -1232,6 +1140,7 @@ private Map deepCopy(final Map map) { * * @param size -1 means maximum buffer size available. */ + @Override public void setLineBufferSize(final int size) { lineBufferSize = size; } @@ -1242,12 +1151,13 @@ public void setLineBufferSize(final int size) { * * @param fPan the new pan */ + @Override public void setPan(final double fPan) { - if (!hasControl(FloatControl.Type.PAN, panControl) || fPan < -1.0 || fPan > 1.0) + if (!outlet.hasControl(FloatControl.Type.PAN, outlet.getPanControl()) || fPan < -1.0 || fPan > 1.0) return; logger.info(() -> "Pan : " + fPan); - panControl.setValue((float) fPan); + outlet.getPanControl().setValue((float) fPan); generateEvent(Status.PAN, getEncodedStreamPosition(), null); } @@ -1258,16 +1168,18 @@ public void setPan(final double fPan) { * * @param fGain The new gain value */ + @Override public void setGain(final double fGain) { - if (isPlaying() || isPaused() && hasControl(FloatControl.Type.MASTER_GAIN, gainControl)) { + if (isPlaying() || isPaused() && outlet.hasControl(FloatControl.Type.MASTER_GAIN, outlet.getGainControl())) { final double logScaleGain = 20 * Math.log10(fGain); - gainControl.setValue((float) logScaleGain); + outlet.getGainControl().setValue((float) logScaleGain); } } + @Override public void setLogScaleGain(final double logScaleGain) { - if (isPlaying() || isPaused() && hasControl(FloatControl.Type.MASTER_GAIN, gainControl)) { - gainControl.setValue((float) logScaleGain); + if (isPlaying() || isPaused() && outlet.hasControl(FloatControl.Type.MASTER_GAIN, outlet.getGainControl())) { + outlet.getGainControl().setValue((float) logScaleGain); } } @@ -1276,9 +1188,10 @@ public void setLogScaleGain(final double logScaleGain) { * * @param mute True to mute the audio of False to unmute it */ + @Override public void setMute(final boolean mute) { - if (hasControl(BooleanControl.Type.MUTE, muteControl) && muteControl.getValue() != mute) - muteControl.setValue(mute); + if (outlet.hasControl(BooleanControl.Type.MUTE, outlet.getMuteControl()) && outlet.getMuteControl().getValue() != mute) + outlet.getMuteControl().setValue(mute); } /** @@ -1288,9 +1201,10 @@ public void setMute(final boolean mute) { * * @param fBalance the new balance */ + @Override public void setBalance(final float fBalance) { - if (hasControl(FloatControl.Type.BALANCE, balanceControl) && fBalance >= -1.0 && fBalance <= 1.0) - balanceControl.setValue(fBalance); + if (outlet.hasControl(FloatControl.Type.BALANCE, outlet.getBalanceControl()) && fBalance >= -1.0 && fBalance <= 1.0) + outlet.getBalanceControl().setValue(fBalance); else try { throw new StreamPlayerException(PlayerException.BALANCE_CONTROL_NOT_SUPPORTED); @@ -1305,6 +1219,7 @@ public void setBalance(final float fBalance) { * @param array the array * @param stop the stop */ + @Override public void setEqualizer(final float[] array, final int stop) { if (!isPausedOrPlaying() || !(audioInputStream instanceof PropertiesContainer)) return; @@ -1320,6 +1235,7 @@ public void setEqualizer(final float[] array, final int stop) { * @param value the value * @param key the key */ + @Override public void setEqualizerKey(final float value, final int key) { if (!isPausedOrPlaying() || !(audioInputStream instanceof PropertiesContainer)) return; @@ -1332,6 +1248,7 @@ public void setEqualizerKey(final float value, final int key) { /** * @return The Speech Factor of the Audio */ + @Override public double getSpeedFactor() { return this.speedFactor; } @@ -1341,6 +1258,7 @@ public double getSpeedFactor() { * * @return If Status==STATUS.UNKNOWN. */ + @Override public boolean isUnknown() { return status == Status.NOT_SPECIFIED; } @@ -1350,6 +1268,7 @@ public boolean isUnknown() { * * @return true if player is playing ,false if not. */ + @Override public boolean isPlaying() { return status == Status.PLAYING; } @@ -1359,6 +1278,7 @@ public boolean isPlaying() { * * @return true if player is paused ,false if not. */ + @Override public boolean isPaused() { return status == Status.PAUSED; } @@ -1368,6 +1288,7 @@ public boolean isPaused() { * * @return true if player is paused/playing,false if not */ + @Override public boolean isPausedOrPlaying() { return isPlaying() || isPaused(); } @@ -1377,6 +1298,7 @@ public boolean isPausedOrPlaying() { * * @return true if player is stopped ,false if not */ + @Override public boolean isStopped() { return status == Status.STOPPED; } @@ -1386,6 +1308,7 @@ public boolean isStopped() { * * @return true if player is opened ,false if not */ + @Override public boolean isOpened() { return status == Status.OPENED; } @@ -1395,6 +1318,7 @@ public boolean isOpened() { * * @return true if player is seeking ,false if not */ + @Override public boolean isSeeking() { return status == Status.SEEKING; } @@ -1402,4 +1326,9 @@ public boolean isSeeking() { Logger getLogger() { return logger; } + + @Override + public SourceDataLine getSourceDataLine() { + return outlet.getSourceDataLine(); + } } diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayerInterface.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayerInterface.java new file mode 100644 index 0000000..6fc309c --- /dev/null +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayerInterface.java @@ -0,0 +1,327 @@ +package com.goxr3plus.streamplayer.stream; + +import com.goxr3plus.streamplayer.enums.Status; + +import javax.sound.sampled.SourceDataLine; +import java.util.List; +import java.util.concurrent.Callable; + +public interface StreamPlayerInterface { + /** + * Freeing the resources. + */ + void reset(); + + /** + * Add a listener to be notified. + * + * @param streamPlayerListener the listener + */ + void addStreamPlayerListener(StreamPlayerListener streamPlayerListener); + + /** + * Remove registered listener. + * + * @param streamPlayerListener the listener + */ + void removeStreamPlayerListener(StreamPlayerListener streamPlayerListener); + + /** + * Open the specific object which can be File,URL or InputStream. + * + * @param object the object [File or URL or InputStream ] + * + * @throws StreamPlayerException the stream player exception + */ + void open(Object object) throws StreamPlayerException; + + /** + * Change the Speed Rate of the Audio , this variable affects the Sample Rate , + * for example 1.0 is normal , 0.5 is half the speed and 2.0 is double the speed + * Note that you have to restart the audio for this to take effect + * + * @param speedFactor speedFactor + */ + void setSpeedFactor(double speedFactor); + + /** + * Starts the play back. + * + * @throws StreamPlayerException the stream player exception + */ + void play() throws StreamPlayerException; + + /** + * Pauses the play back.
+ *

+ * Player Status = PAUSED. * @return False if failed(so simple...) + * + * @return true, if successful + */ + boolean pause(); + + /** + * Stops the play back.
+ *

+ * Player Status = STOPPED.
+ * Thread should free Audio resources. + */ + void stop(); + + /** + * Resumes the play back.
+ *

+ * Player Status = PLAYING* + * + * @return False if failed(so simple...) + */ + boolean resume(); + + /** + * Skip bytes in the File input stream. It will skip N frames matching to bytes, + * so it will never skip given bytes len + * + * @param bytes the bytes + * + * @return value bigger than 0 for File and value = 0 for URL and InputStream + * + * @throws StreamPlayerException the stream player exception + */ + long seekBytes(long bytes) throws StreamPlayerException; + + /** + * Skip x seconds of audio + * See {@link #seekBytes(long)} + * + * @param seconds Seconds to Skip + */ + //todo not finished needs more validations + long seekSeconds(int seconds) throws StreamPlayerException; + + /** + * Go to X time of the Audio + * See {@link #seekBytes(long)} + * + * @param seconds Seconds to Skip + */ + long seekTo(int seconds) throws StreamPlayerException; + + int getDurationInSeconds(); + + /** + * Calculates the current position of the encoded audio based on
+ * nEncodedBytes = encodedAudioLength - + * encodedAudioInputStream.available(); + * + * @return The Position of the encoded stream in term of bytes + */ + int getEncodedStreamPosition(); + + /** + * Return SourceDataLine buffer size. + * + * @return -1 maximum buffer size. + */ + int getLineBufferSize(); + + /** + * Return SourceDataLine current buffer size. + * + * @return The current line buffer size + */ + int getLineCurrentBufferSize(); + + /** + * Returns all available mixers. + * + * @return A List of available Mixers + */ + List getMixers(); + + /** + * Returns Gain value. + * + * @return The Gain Value + */ + float getGainValue(); + + /** + * Returns maximum Gain value. + * + * @return The Maximum Gain Value + */ + float getMaximumGain(); + + /** + * Returns minimum Gain value. + * + * @return The Minimum Gain Value + */ + float getMinimumGain(); + + /** + * Returns Pan precision. + * + * @return The Precision Value + */ + float getPrecision(); + + /** + * Returns Pan value. + * + * @return The Pan Value + */ + float getPan(); + + /** + * Return the mute Value(true || false). + * + * @return True if muted , False if not + */ + boolean getMute(); + + /** + * Return the balance Value. + * + * @return The Balance Value + */ + float getBalance(); + + /** + * Return the total size of this file in bytes. + * + * @return encodedAudioLength + */ + long getTotalBytes(); + + /** + * @return BytePosition + */ + int getPositionByte(); + + /** + * Gets the source data line. + * + * @return The SourceDataLine + */ + SourceDataLine getSourceDataLine(); + + /** + * This method will return the status of the player + * + * @return The Player Status + */ + Status getStatus(); + + /** + * 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. + */ + void setLineBufferSize(int size); + + /** + * Sets Pan value. Line should be opened before calling this method. Linear + * scale : -1.0 ... +1.0 + * + * @param fPan the new pan + */ + void setPan(double fPan); + + /** + * Sets Gain value. Line should be opened before calling this method. Linear + * scale 0.0 ... 1.0 Threshold Coef. : 1/2 to avoid saturation. + * + * @param fGain The new gain value + */ + void setGain(double fGain); + + void setLogScaleGain(double logScaleGain); + + /** + * Set the mute of the Line. Note that mute status does not affect gain. + * + * @param mute True to mute the audio of False to unmute it + */ + void setMute(boolean mute); + + /** + * Represents a control for the relative balance of a stereo signal between two + * stereo speakers. The valid range of values is -1.0 (left channel only) to 1.0 + * (right channel only). The default is 0.0 (centered). + * + * @param fBalance the new balance + */ + void setBalance(float fBalance); + + /** + * Changes specific values from equalizer. + * + * @param array the array + * @param stop the stop + */ + void setEqualizer(float[] array, int stop); + + /** + * Changes a value from equalizer. + * + * @param value the value + * @param key the key + */ + void setEqualizerKey(float value, int key); + + /** + * @return The Speech Factor of the Audio + */ + double getSpeedFactor(); + + /** + * Checks if is unknown. + * + * @return If Status==STATUS.UNKNOWN. + */ + boolean isUnknown(); + + /** + * Checks if is playing. + * + * @return true if player is playing ,false if not. + */ + boolean isPlaying(); + + /** + * Checks if is paused. + * + * @return true if player is paused ,false if not. + */ + boolean isPaused(); + + /** + * Checks if is paused or playing. + * + * @return true if player is paused/playing,false if not + */ + boolean isPausedOrPlaying(); + + /** + * Checks if is stopped. + * + * @return true if player is stopped ,false if not + */ + boolean isStopped(); + + /** + * Checks if is opened. + * + * @return true if player is opened ,false if not + */ + boolean isOpened(); + + /** + * Checks if is seeking. + * + * @return true if player is seeking ,false if not + */ + boolean isSeeking(); +} diff --git a/src/test/java/com/goxr3plus/streamplayer/stream/SourceDataLineTest.java b/src/test/java/com/goxr3plus/streamplayer/stream/SourceDataLineTest.java new file mode 100644 index 0000000..e7ec583 --- /dev/null +++ b/src/test/java/com/goxr3plus/streamplayer/stream/SourceDataLineTest.java @@ -0,0 +1,192 @@ +package com.goxr3plus.streamplayer.stream; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; + +import javax.sound.sampled.SourceDataLine; +import java.io.File; +import java.util.logging.Logger; + +import static java.lang.Math.log10; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.booleanThat; +import static org.mockito.Mockito.mock; + +public class SourceDataLineTest { + + StreamPlayer player; + private File audioFile; + + @BeforeEach + void setup() { + final Logger logger = mock(Logger.class); + player = new StreamPlayer(logger); + audioFile = new File("Logic - Ballin [Bass Boosted].mp3"); + } + + @AfterEach + void tearDown() { + player.stop(); + } + + @Test + void gain() throws StreamPlayerException, InterruptedException { + // Setup + final double gain1 = 0.83; + final double gain2 = 0.2; + final double delta = 0.05; + final boolean listen = false; + + // Exercise + final float initialGain = player.getGainValue(); + player.open(audioFile); + player.seekTo(30); + player.play(); + player.setGain(gain1); + final float actualGain1First = player.getGainValue(); + if (listen) Thread.sleep(2000); + final float actualGain1 = player.getGainValue(); + + player.setGain(gain2); + if (listen) Thread.sleep(2000); + final float actualGain2 = player.getGainValue(); + + player.setGain(gain1); + if (listen) Thread.sleep(2000); + + player.stop(); + + // Verify + assertEquals(0, initialGain); + assertEquals(actualGain1First, actualGain1); + assertEquals(20*log10(gain1), actualGain1, delta); // TODO: Investigate probable bug. + // fail("Test not done"); + } + + /** + * Plays music if "listen" is true. + * Varies the gain, and checks that it can be read back. + * If listen is true, it plays for 2 seconds per gain level. + * + * @throws StreamPlayerException + * @throws InterruptedException + */ + @Test + void logScaleGain() throws StreamPlayerException, InterruptedException { + // Setup + final boolean listen = false; + + // Exercise + + player.open(audioFile); + player.seekTo(30); + player.play(); + + assertGainCanBeSetTo(-10, listen); + assertGainCanBeSetTo(-75, listen); + assertGainCanBeSetTo(0, listen); + assertGainCanBeSetTo(6, listen); + + player.stop(); + } + + private void assertGainCanBeSetTo(double gain, boolean listen) throws InterruptedException { + final float atGain = playAtGain(listen, gain); + assertEquals(gain, atGain, 0.01); + } + + private float playAtGain(boolean listen, double gain) throws InterruptedException { + player.setLogScaleGain(gain); + if (listen) { + Thread.sleep(2000); + } + return player.getGainValue(); + } + + @Test + void balance() throws StreamPlayerException { + // Setup + final float wantedBalance = 0.5f; + + //Exercise + player.open(audioFile); + player.play(); // Necessary to be able to set the balance + + final float initialBalance = player.getBalance(); + player.setBalance(wantedBalance); + player.stop(); // Probably not needed, but cleanup is good. + final float actualBalance = player.getBalance(); // Can be made before or after stop() + + // Verify + assertEquals(0, initialBalance); + assertEquals(wantedBalance, actualBalance); + } + + @Test + void pan() throws StreamPlayerException { + double delta = 1e-6; + final float initialPan = player.getPan(); + assertEquals(0, initialPan); + + player.open(audioFile); + player.play(); + + double pan = -0.9; + player.setPan(pan); + assertEquals(pan, player.getPan(), delta); + + double outsideRange = 1.1; + player.setPan(outsideRange); + assertEquals(pan, player.getPan(), delta); + } + + @Test + void mute() throws StreamPlayerException { + assertFalse(player.getMute()); + player.setMute(true); + assertFalse(player.getMute()); + player.open(audioFile); + player.setMute(true); + assertFalse(player.getMute()); + + player.play(); + player.setMute(true); + assertTrue(player.getMute()); // setMute works only after play() has been called. + + + player.setMute(false); + assertFalse(player.getMute()); + } + + @Test + void sourceDataLine() throws StreamPlayerException { + assertNull(player.getSourceDataLine()); + + player.open(audioFile); + assertNotNull(player.getSourceDataLine()); + + player.play(); + + assertNotNull(player.getSourceDataLine()); + } + + @Test + void playAndPause() throws StreamPlayerException, InterruptedException { + boolean listen = true; + player.open(audioFile); + player.play(); + player.seekTo(30); + if (listen) Thread.sleep(200); + + player.pause(); + if (listen) Thread.sleep(100); + + player.resume(); // TODO: Examine what happens if play() is called instead. + if (listen) Thread.sleep(200); + //player.stop(); + + // TODO: asserts and listen=false + } +} diff --git a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java index 755896d..8f88f38 100644 --- a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java @@ -197,12 +197,15 @@ void stopped() { } @Test - void sourceDataLine() { - final SourceDataLine sourceDataLine = player.getSourceDataLine(); + void sourceDataLine() throws StreamPlayerException { + assertNull(player.getSourceDataLine()); - assertNotNull(sourceDataLine); + player.open(audioFile); + assertNotNull(player.getSourceDataLine()); - fail("Test not done"); + player.play(); + + assertNotNull(player.getSourceDataLine()); } @Test @@ -295,11 +298,21 @@ void stop() { } @Test - void pan() { - player.getPan(); - player.setPan(1000); + void pan() throws StreamPlayerException { + double delta = 1e-6; + final float initialPan = player.getPan(); + assertEquals(0, initialPan); - fail("Test not done"); + player.open(audioFile); + player.play(); + + double pan = -0.9; + player.setPan(pan); + assertEquals(pan, player.getPan(), delta); + + double outsideRange = 1.1; + player.setPan(outsideRange); + assertEquals(pan, player.getPan(), delta); } @Test @@ -331,7 +344,7 @@ void seekBytes() throws StreamPlayerException { } - // The methods tested below aren't used otherwhere in this project, nor in XR3Player + // The methods tested below aren't used elsewhere in this project, nor in XR3Player @Test void lineBufferSize() { @@ -403,5 +416,15 @@ void equalizerKey() { fail("Test not done"); } + @Test + void resetShouldStopPlayback() throws StreamPlayerException { + player.open((audioFile)); + player.play(); + System.out.println("play()"); + player.reset(); + System.out.println("reset()"); + player.stop(); + } + }