listeners) {
this.source = source;
this.playerState = playerStatus;
this.encodedStreamPosition = encodedStreamPosition;
this.description = description;
this.listeners = listeners;
+ this.logger = source.getLogger();
}
@Override
@@ -79,8 +82,7 @@ public String call() {
listeners.forEach(listener -> listener
.statusUpdated(new StreamPlayerEvent(source, playerState, encodedStreamPosition, description)));
}
-
- System.out.println("Stream player Status -> " + playerState);
+ logger.log(Level.INFO, "Stream player Status -> " + playerState);
return "OK";
}
}
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 bca832e..683e9a2 100644
--- a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java
+++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java
@@ -17,7 +17,6 @@
* not as a part of test driven development.
*/
public class StreamPlayerMethodsTest {
-
StreamPlayer player;
private File audioFile;
@@ -98,15 +97,15 @@ void gain() throws StreamPlayerException, InterruptedException {
player.play();
player.setGain(gain1);
final float actualGain1First = player.getGainValue();
- if (listen) Thread.sleep(2000);
+ if (listen) Thread.sleep(2000);
final float actualGain1 = player.getGainValue();
player.setGain(gain2);
- if (listen) Thread.sleep(2000);
+ if (listen) Thread.sleep(2000);
final float actualGain2 = player.getGainValue();
player.setGain(gain1);
- if (listen) Thread.sleep(2000);
+ if (listen) Thread.sleep(2000);
player.stop();
@@ -114,7 +113,73 @@ void gain() throws StreamPlayerException, InterruptedException {
assertEquals(0, initialGain);
assertEquals(actualGain1First, actualGain1);
assertEquals(gain1, actualGain1, delta); // TODO: Investigate probable bug.
- // fail("Test not done");
+ // 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 = true;
+
+ // 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 that the maximum gain is greater than the minimum gain. That is about all we can expect.
+ * The actual values depend on the available {@link SourceDataLine}.
+ * We don't know anything about its scale beforehand.
+ *
+ * The player must be started before maximum and minimum gains can be queried.
+ *
+ * // TODO: Is it really acceptable that we cannot check gain before the player is started?
+ *
+ * @throws StreamPlayerException
+ */
+ @Test
+ void maximumGain() throws StreamPlayerException {
+
+ player.open(audioFile);
+ player.play();
+ final float maximumGain = player.getMaximumGain();
+ final float minimumGain = player.getMinimumGain();
+ player.stop();
+
+ assertTrue(minimumGain < maximumGain,
+ String.format("Maximum gain (%.2f) should be greater than minimum gain (%.2f).",
+ maximumGain, minimumGain)
+ );
}
@Test
@@ -132,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
@@ -230,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
@@ -266,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() {
@@ -282,13 +360,6 @@ void lineCurrentBufferSize() {
fail("Test not done");
}
- @Test
- void maximumGain() {
- player.getMaximumGain();
-
- fail("Test not done");
- }
-
@Test
void minimumGain() {
player.getMinimumGain();