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();
+ }
+
}