diff --git a/README.md b/README.md index f780dc1..d89bc8c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +### [![AlexKent](https://user-images.githubusercontent.com/20374208/75432997-f5422100-5957-11ea-87a2-164eb98d83ef.png)](https://www.minepi.com/AlexKent) Support me joining PI Network app with invitation code [AlexKent](https://www.minepi.com/AlexKent) [![AlexKent](https://user-images.githubusercontent.com/20374208/75432997-f5422100-5957-11ea-87a2-164eb98d83ef.png)](https://www.minepi.com/AlexKent) + ---

Java Stream Player ( Library )

@@ -27,7 +29,7 @@ - FLAC - MONKEY's AUDIO - SPEEX -- **Not Supported Yet ❌** +- **Not Supported Yet ❌** - AAC - THEORA - ... all the others diff --git a/pom.xml b/pom.xml index fc849b0..b85c184 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.goxr3plus java-stream-player - 9.0.5 + 10.0.0 diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index f0ee979..6e3a514 100644 --- a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java @@ -35,11 +35,7 @@ import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -56,34 +52,32 @@ */ public class StreamPlayer implements StreamPlayerInterface, Callable { - /** - * Class logger - */ + /** Class logger */ private Logger logger; // -------------------AUDIO----------------,----- private volatile Status status = Status.NOT_SPECIFIED; - /** - * The data source - */ + /** The data source */ private DataSource source; - /** The audio input stream. */ - private volatile AudioInputStream audioInputStream; - - /** The encoded audio input stream. */ + /** The encoded audio input stream. mp3, ogg or similar */ private AudioInputStream encodedAudioInputStream; + /** Copy of the encoded audio input stream. */ + private AudioInputStream encodedAudioInputStreamCopy; + + /** The decoded audio input stream. Linear format */ + private AudioInputStream decodedAudioInputStream; + + /** The audio file format. */ private AudioFileFormat audioFileFormat; // -------------------LOCKS--------------------- - /** - * It is used for synchronization in place of audioInputStream - */ + /** Used for synchronization in place of audioInputStream */ private final Object audioLock = new Object(); // -------------------VARIABLES--------------------- @@ -187,9 +181,8 @@ public void reset() { outlet.flushAndFreeDataLine(); // AudioFile - audioInputStream = null; audioFileFormat = null; - encodedAudioInputStream = null; + encodedAudioInputStreamCopy = null; encodedAudioLength = -1; // Controls @@ -328,7 +321,7 @@ private void initAudioInputStream() throws StreamPlayerException { generateEvent(Status.OPENING, getEncodedStreamPosition(), source); // Audio resources from file||URL||inputStream. - audioInputStream = source.getAudioInputStream(); + encodedAudioInputStream = source.getAudioInputStream(); // Audio resources from file||URL||inputStream. audioFileFormat = source.getAudioFileFormat(); @@ -425,13 +418,13 @@ private void initLine() throws LineUnavailableException, StreamPlayerException { createLine(); if (!outlet.getSourceDataLine().isOpen()) { currentLineBufferSize = lineBufferSize >= 0 ? lineBufferSize : outlet.getSourceDataLine().getBufferSize(); - openLine(audioInputStream.getFormat(), currentLineBufferSize); + openLine(decodedAudioInputStream.getFormat(), currentLineBufferSize); } else { - AudioFormat format = audioInputStream == null ? null : audioInputStream.getFormat(); + AudioFormat format = decodedAudioInputStream == null ? null : decodedAudioInputStream.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); + openLine(decodedAudioInputStream.getFormat(), currentLineBufferSize); } } } @@ -474,7 +467,7 @@ private void createLine() throws LineUnavailableException, StreamPlayerException if (outlet.getSourceDataLine() != null) logger.warning("Warning Source DataLine is not null!\n"); else { - final AudioFormat sourceFormat = audioInputStream.getFormat(); + final AudioFormat sourceFormat = encodedAudioInputStream.getFormat(); logger.info(() -> "Create Line : Source format : " + sourceFormat + "\n"); @@ -495,17 +488,18 @@ private void createLine() throws LineUnavailableException, StreamPlayerException + "Target format: " + targetFormat + "\n"); // Keep a reference on encoded stream to progress notification. - encodedAudioInputStream = audioInputStream; + encodedAudioInputStreamCopy = encodedAudioInputStream; try { // Get total length in bytes of the encoded stream. - encodedAudioLength = encodedAudioInputStream.available(); + encodedAudioLength = encodedAudioInputStreamCopy.available(); } catch (final IOException e) { logger.warning("Cannot get m_encodedaudioInputStream.available()\n" + e); } // Create decoded Stream - audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream); - final DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, audioInputStream.getFormat(), + // audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream); // TODO: Remove re-assignment + decodedAudioInputStream = AudioSystem.getAudioInputStream(targetFormat, encodedAudioInputStream); + final DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, decodedAudioInputStream.getFormat(), AudioSystem.NOT_SPECIFIED); if (!AudioSystem.isLineSupported(lineInfo)) throw new StreamPlayerException(PlayerException.LINE_NOT_SUPPORTED); @@ -708,12 +702,12 @@ public long seekBytes(final long bytes) throws StreamPlayerException { synchronized (audioLock) { generateEvent(Status.SEEKING, AudioSystem.NOT_SPECIFIED, null); initAudioInputStream(); - if (audioInputStream != null) { + if (decodedAudioInputStream != null) { long skipped; // Loop until bytes are really skipped. while (totalSkipped < bytes) { // totalSkipped < (bytes-SKIP_INACCURACY_SIZE))) - skipped = audioInputStream.skip(bytes - totalSkipped); + skipped = decodedAudioInputStream.skip(bytes - totalSkipped); if (skipped == 0) break; totalSkipped += skipped; @@ -843,7 +837,7 @@ public Void call() { // Reads up a specified maximum number of bytes from audio stream // wtf i have written here omg //to fix! cause it is complicated - for (; toRead > 0 && (nBytesRead = audioInputStream.read(audioDataBuffer.array(), totalRead, + for (; toRead > 0 && (nBytesRead = decodedAudioInputStream.read(audioDataBuffer.array(), totalRead, toRead)) != -1; toRead -= nBytesRead, totalRead += nBytesRead) // Check for under run @@ -870,11 +864,11 @@ public Void call() { // Notify all registered Listeners listeners.forEach(listener -> { - if (audioInputStream instanceof PropertiesContainer) { + if (decodedAudioInputStream instanceof PropertiesContainer) { // Pass audio parameters such as instant // bit rate, ... listener.progress(nEncodedBytes, outlet.getSourceDataLine().getMicrosecondPosition(), - trimBuffer, ((PropertiesContainer) audioInputStream).properties()); + trimBuffer, ((PropertiesContainer) decodedAudioInputStream).properties()); } else // Pass audio parameters listener.progress(nEncodedBytes, outlet.getSourceDataLine().getMicrosecondPosition(), @@ -937,9 +931,9 @@ private void goOutOfPause() { @Override public int getEncodedStreamPosition() { int position = -1; - if (source.isFile() && encodedAudioInputStream != null) + if (source.isFile() && encodedAudioInputStreamCopy != null) try { - position = encodedAudioLength - encodedAudioInputStream.available(); + position = encodedAudioLength - encodedAudioInputStreamCopy.available(); } catch (final IOException ex) { logger.log(Level.WARNING, "Cannot get m_encodedaudioInputStream.available()", ex); stop(); @@ -952,8 +946,9 @@ public int getEncodedStreamPosition() { */ private void closeStream() { try { - if (audioInputStream != null) { - audioInputStream.close(); + AudioInputStream stream1 = this.decodedAudioInputStream; + if (decodedAudioInputStream != null) { // TODO: Find out which audioInputStream or audioInputStream1 should be closed. + decodedAudioInputStream.close(); logger.info("Stream closed"); } } catch (final IOException ex) { @@ -994,17 +989,16 @@ public List getMixers() { // audio mixers that are currently installed on the system. final Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); - if (mixerInfos != null) - Arrays.stream(mixerInfos).forEach(mInfo -> { - // line info - final Line.Info lineInfo = new Line.Info(SourceDataLine.class); - final Mixer mixer = AudioSystem.getMixer(mInfo); + Arrays.stream(mixerInfos).forEach(mInfo -> { + // line info + final Line.Info lineInfo = new Line.Info(SourceDataLine.class); + final Mixer mixer = AudioSystem.getMixer(mInfo); - // if line supported - if (mixer.isLineSupported(lineInfo)) - mixers.add(mInfo.getName()); + // if line supported + if (mixer.isLineSupported(lineInfo)) + mixers.add(mInfo.getName()); - }); + }); return mixers; } @@ -1023,7 +1017,7 @@ private Mixer getMixer(final String name) { // audio mixers that are currently installed on the system. final Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo(); - if (name != null && mixerInfos != null) + if (name != null) for (Mixer.Info mixerInfo : mixerInfos) if (mixerInfo.getName().equals(name)) { mixer = AudioSystem.getMixer(mixerInfo); @@ -1067,12 +1061,18 @@ public float getMinimumGain() { /** * Returns Pan precision. + *

+ * Obtains the resolution or granularity of the control, in the units that the control measures. + * The precision is the size of the increment between discrete valid values for this control, + * over the set of supported floating-point values. * - * @return The Precision Value + * @return The Precision Value for the pan control, if it exists, otherwise 0.0. */ @Override public float getPrecision() { - return !outlet.hasControl(FloatControl.Type.PAN, outlet.getPanControl()) ? 0.0F : outlet.getPanControl().getPrecision(); + return !outlet.hasControl(FloatControl.Type.PAN, outlet.getPanControl()) + ? 0 + : outlet.getPanControl().getPrecision(); } @@ -1248,10 +1248,10 @@ public void setBalance(final float fBalance) { */ @Override public void setEqualizer(final float[] array, final int stop) { - if (!isPausedOrPlaying() || !(audioInputStream instanceof PropertiesContainer)) + if (!isPausedOrPlaying() || !(decodedAudioInputStream instanceof PropertiesContainer)) return; // Map map = ((PropertiesContainer) audioInputStream).properties() - final float[] equalizer = (float[]) ((PropertiesContainer) audioInputStream).properties().get("mp3.equalizer"); + final float[] equalizer = (float[]) ((PropertiesContainer) decodedAudioInputStream).properties().get("mp3.equalizer"); if (stop >= 0) System.arraycopy(array, 0, equalizer, 0, stop); } @@ -1264,10 +1264,10 @@ public void setEqualizer(final float[] array, final int stop) { */ @Override public void setEqualizerKey(final float value, final int key) { - if (!isPausedOrPlaying() || !(audioInputStream instanceof PropertiesContainer)) + if (!isPausedOrPlaying() || !(decodedAudioInputStream instanceof PropertiesContainer)) return; // Map map = ((PropertiesContainer) audioInputStream).properties() - final float[] equalizer = (float[]) ((PropertiesContainer) audioInputStream).properties().get("mp3.equalizer"); + final float[] equalizer = (float[]) ((PropertiesContainer) decodedAudioInputStream).properties().get("mp3.equalizer"); equalizer[key] = value; } diff --git a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java new file mode 100644 index 0000000..4a6c452 --- /dev/null +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java @@ -0,0 +1,73 @@ +package com.goxr3plus.streamplayer.stream; + +import com.goxr3plus.streamplayer.enums.Status; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +/** + * Tests of all or most of the public methods of StreamPlayer. + * These unit tests are written primarily as documentation of the behavior and as example use case, + * not as a part of test driven development. + */ +public class StreamPlayerFutureImprovementTest { + 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"); + } + + /** + * This test fails if it's permitted to add a null to the StreamPlayer listener list. + */ + @Test + void addStreamPlayerListener_dontAcceptNull() { + // Currently, we can add a null to the list of stream player listeners. + // Should that really be allowed? + assertThrows(Exception.class, () -> player.addStreamPlayerListener(null)); + + fail("Test not done"); + } + + + @Test + @DisplayName("When play() is called without first calling open(), an exception is thrown") + void playingUnopenedSourceThrowsException() { + + assertThrows(Exception.class, () -> player.play()); + } + + @Test + void seekBytes() throws StreamPlayerException { + player.open(audioFile); + player.play(); + int positionByte1 = player.getPositionByte(); + + player.seekBytes(100); + int positionByte2 = player.getPositionByte(); + + assertTrue( positionByte2 > positionByte1); + + // TODO: It seems that getPositionByte doesn't work. + // It isn't called from within this project, except for in this test. + // It is however called by XR3Player. If XR3Player needs this method, it must be tested + // within this project. The method relies on a map, which doesn't seem to be updated by play() + } + +} diff --git a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java index 4cfbc5b..1c247e4 100644 --- a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java @@ -3,13 +3,18 @@ import com.goxr3plus.streamplayer.enums.Status; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; -import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.*; import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; import java.util.logging.Logger; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; /** * Tests of all or most of the public methods of StreamPlayer. @@ -85,7 +90,7 @@ void status() throws StreamPlayerException { @Test void gain() throws StreamPlayerException, InterruptedException { // Setup - final double gain1 = 0.99; + final double gain1_dB = 0.5; final double gain2 = 0.2; final double delta = 0.05; final boolean listen = false; @@ -95,8 +100,8 @@ void gain() throws StreamPlayerException, InterruptedException { player.open(audioFile); player.seekTo(30); player.play(); - player.setGain(gain1); - final float actualGain1First = player.getGainValue(); + player.setGain(gain1_dB); + final float actualGain0 = player.getGainValue(); if (listen) Thread.sleep(2000); final float actualGain1 = player.getGainValue(); @@ -104,16 +109,19 @@ void gain() throws StreamPlayerException, InterruptedException { if (listen) Thread.sleep(2000); final float actualGain2 = player.getGainValue(); - player.setGain(gain1); + player.setGain(gain1_dB); if (listen) Thread.sleep(2000); player.stop(); // Verify assertEquals(0, initialGain); - assertEquals(actualGain1First, actualGain1); - assertEquals(gain1, actualGain1, delta); // TODO: Investigate probable bug. - // fail("Test not done"); + assertEquals(actualGain0, actualGain1); + assertEquals(20.0 * Math.log10(gain1_dB), actualGain1, delta); + + // TODO: Consider changing the API. setGain() and getGainValue() have different scales. + // setGain(linear scale), + // whereas getGainValue() returns a logarithmic dB scale value. This is inconsistent. } /** @@ -127,7 +135,7 @@ void gain() throws StreamPlayerException, InterruptedException { @Test void logScaleGain() throws StreamPlayerException, InterruptedException { // Setup - final boolean listen = true; + final boolean listen = false; // Set to true to listen to the test. // Exercise @@ -183,17 +191,26 @@ void maximumGain() throws StreamPlayerException { } @Test - void totalBytes() { - player.getTotalBytes(); + void totalBytes() throws StreamPlayerException, InterruptedException { + int expectedLengthOfExampleAudioFile = 5877062; - fail("Test not done"); + + assertEquals(-1, player.getTotalBytes()); + + player.open(audioFile); + assertEquals(expectedLengthOfExampleAudioFile, player.getTotalBytes()); + + player.play(); + assertEquals(expectedLengthOfExampleAudioFile, player.getTotalBytes()); } @Test void stopped() { - player.isStopped(); - fail("Test not done"); + assertFalse(player.isStopped()); + + player.stop(); + assertTrue(player.isStopped()); } @Test @@ -209,92 +226,195 @@ void sourceDataLine() throws StreamPlayerException { } @Test - void playing() { - final boolean playing = player.isPlaying(); + void playing() throws StreamPlayerException { - assertFalse(playing); + assertFalse(player.isPlaying()); - fail("Test not done"); - } + player.open(audioFile); + assertFalse(player.isPlaying()); - @Test - void pausedOrPlaying() { - player.isPausedOrPlaying(); + player.play(); + assertTrue(player.isPlaying()); - fail("Test not done"); + player.pause(); + assertFalse(player.isPlaying()); } @Test - void paused() { - player.isPaused(); + void pausedOrPlaying() throws StreamPlayerException { - fail("Test not done"); + assertFalse(player.isPausedOrPlaying()); + + player.open(audioFile); + assertFalse(player.isPausedOrPlaying()); + + player.play(); + assertTrue(player.isPausedOrPlaying()); + + player.pause(); + assertTrue(player.isPausedOrPlaying()); + + player.stop(); + assertFalse(player.isPausedOrPlaying()); } @Test - void addStreamPlayerListener_dontAcceptNull() { - assertThrows(Exception.class, () -> player.addStreamPlayerListener(null)); + void paused() throws StreamPlayerException { + assertFalse(player.isPaused()); - fail("Test not done"); + player.open(audioFile); + assertFalse(player.isPaused()); + + player.play(); + assertFalse(player.isPaused()); + + player.pause(); + assertTrue(player.isPaused()); } @Test - void addStreamPlayerListener() { + void addStreamPlayerListener() throws StreamPlayerException, InterruptedException { + // Setup final StreamPlayerListener listener = mock(StreamPlayerListener.class); + + ArgumentCaptor dataSourceCaptor = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor propertiesCaptor1 = ArgumentCaptor.forClass(Map.class); + + // Execute player.addStreamPlayerListener(listener); + player.open(audioFile); + player.play(); + Thread.sleep(30); + + // Verify + verify(listener).opened(dataSourceCaptor.capture(), propertiesCaptor1.capture()); + Object value = dataSourceCaptor.getValue(); + assertTrue(value instanceof File); + + Map value11 = propertiesCaptor1.getValue(); + + assertTrue(value11.containsKey("basicplayer.sourcedataline")); + + verify(listener, times(4)).statusUpdated(any()); + + verify(listener, times(1)).opened(any(), any()); + + verify(listener, atLeast(4)).progress(anyInt(), anyLong(), any(), any()); + verify(listener, atMost(30)).progress(anyInt(), anyLong(), any(), any()); + + // TODO: Make separate tests for the different calls made to the listener + // TODO: Do we need to test the values passed to these methods? - fail("Test not done"); // TODO: CHeck that the listener is actually added } @Test - void mute() { - player.getMute(); + void mute() throws StreamPlayerException { + // TODO: How can mute be tested, without too much assumptions about the actual implementation? + // A manual test would involve listening. + + + assertFalse(player.getMute()); + player.open(audioFile); + player.play(); + player.setMute(true); + assertTrue(player.getMute()); player.setMute(false); + assertFalse(player.getMute()); - fail("Test not done"); } @Test - void speedFactor() { - player.getSpeedFactor(); - player.setSpeedFactor(1000); + void speedFactor() throws StreamPlayerException, InterruptedException { + assertEquals(player.getSpeedFactor(), 1); + + double fast = 1; + player.setSpeedFactor(fast); + assertEquals(fast, player.getSpeedFactor()); + + double slow = 0.5; + player.open(audioFile); + player.play(); + player.setSpeedFactor(slow); + Thread.sleep(50); + assertEquals(slow, player.getSpeedFactor()); + + // TODO: Find a way to verify that the speed factor actually works. That it can be read back is no proof. + // I might be possible to play a short sequence of known length, and measure the time it takes. + // But things that take time are generally not advisable in unit tests. + - fail("Test not done"); } @Test void equalizer() { player.setEqualizer(null, 0); - - fail("Test not done"); + // TODO: Find out what the intention of setEqualizer() is, and make a test for that assumption. } @Test - void play() throws StreamPlayerException { + void play() throws StreamPlayerException, InterruptedException { + // Setup + player.open(audioFile); + + // Pre-validate + assertFalse(player.isPlaying()); + + // Execute player.play(); - fail("Test not done"); + // Verify + assertTrue(player.isPlaying()); + + // TODO: Find way to verify that the player is actually playing, that doesn't need listening. + // The method might look at the playing position, but it must be fairly quick. } @Test - void resume() { - player.resume(); + void resume() throws StreamPlayerException { + assertFalse(player.isPlaying()); - fail("Test not done"); + player.open(audioFile); + assertFalse(player.isPlaying()); + + player.play(); + assertTrue(player.isPlaying()); + + player.pause(); + assertFalse(player.isPlaying()); + + + player.resume(); + assertTrue(player.isPlaying()); } @Test - void pause() { + void pause() throws StreamPlayerException { + + // Setup + player.open(audioFile); + player.play(); + // Pre-validate + assertFalse(player.isPaused()); + + // Execute player.pause(); - fail("Test not done"); + // Verify + assertTrue(player.isPaused()); + } @Test void stop() { + + assertFalse(player.isStopped()); + player.stop(); - fail("Test not done"); + assertTrue(player.isStopped()); + + // TODO: Find a way to verify that playback is stopped by running the stop method. + // The isStopped() method is not enough. } @Test @@ -310,42 +430,49 @@ void pan() throws StreamPlayerException { player.setPan(pan); assertEquals(pan, player.getPan(), delta); + // If we set the pan outside the permitted range, it will not change + // The permitted range is undefined. double outsideRange = 1.1; player.setPan(outsideRange); assertEquals(pan, player.getPan(), delta); + + float precision = player.getPrecision(); + assertNotEquals(0, precision); + double expected = 128.0; // Possibly platform dependent. Tested on a Mac with Intellij. + assertEquals(expected, 1.0/precision, 2.0); } @Test void unknown() { player.isUnknown(); - - fail("Test not done"); + // This is a useless test of a useless method. + // TODO: Remove player.isUnknown(). It's not used, and it's useless. + // There is already getStatus(). } @Test void open() throws StreamPlayerException { - File file = null; + File file = spy(audioFile); player.open(file); + verify(file, atLeast(1)).getPath(); - fail("Test not done"); + // It's unclear what the contract of open() is; what we need it to do. + // It's a pre-requisite for play(), but play() doesn't throw an + // exception if open() is missing. } @Test void mixers() { - player.getMixers(); - - fail("Test not done"); + List mixers = player.getMixers(); + // TODO: Make this method player.getMixers() private, remove it from the interface. + // There is nothing that can be done with the information outside the private scope. } - @Test - void seekBytes() throws StreamPlayerException { - player.seekBytes(0); - fail("Test not done"); - } // The methods tested below aren't used elsewhere in this project, nor in XR3Player + // TODO: Consider each of the tested methods below, to see if they can be removed from StreamPlayer. @Test void lineBufferSize() { @@ -355,10 +482,18 @@ void lineBufferSize() { } @Test - void lineCurrentBufferSize() { - player.getLineCurrentBufferSize(); + void lineCurrentBufferSize() throws StreamPlayerException { + // TODO: Document the purpose of getLineCurrentBufferSize(). What is it good for? + // Can it be removed? The method doesn't really return the current line buffer size, + // but a cached value, which might be the same thing. Hard to say. - fail("Test not done"); + assertEquals(-1, player.getLineCurrentBufferSize(), "Initially, the buffer size is undefined, coded as -1."); + + player.open(audioFile); + assertEquals(-1, player.getLineCurrentBufferSize(), "After the player is opened, the buffer size is undefined"); + + player.play(); + assertEquals(2 * 44100, player.getLineCurrentBufferSize(), "After the play starts, the buffer size 1 second at CD sampling rate"); } @Test @@ -376,17 +511,22 @@ void positionByte() { } @Test - void precision() { - player.getPrecision(); + void precision() throws StreamPlayerException { + assertEquals(0f, player.getPrecision()); - fail("Test not done"); + player.open(audioFile); + player.play(); + + assertNotEquals(0f, player.getPrecision()); + // On one computer the precision = 1/128. There are no guarantees. } @Test - void opened() { - player.isOpened(); + void opened() throws StreamPlayerException { + assertFalse(player.isOpened()); - fail("Test not done"); + player.open(audioFile); + assertTrue(player.isOpened()); } @Test @@ -404,10 +544,31 @@ void removeStreamPlayerListener() { } @Test - void seekTo() throws StreamPlayerException { - player.seekTo(1000); + void seekTo() throws StreamPlayerException, IOException, UnsupportedAudioFileException { - fail("Test not done"); + // Some tests before we do the real tests + AudioFileFormat audioFileFormat = AudioSystem.getAudioFileFormat(audioFile); + + + // Setup + player.open(audioFile); + player.play(); + player.pause(); + int encodedStreamPosition1 = player.getEncodedStreamPosition(); + + // Execute + player.seekTo(10); + + // Verify + int encodedStreamPosition2 = player.getEncodedStreamPosition(); + assertTrue(encodedStreamPosition2 > encodedStreamPosition1); + + // Execute: go backwards + player.seekTo(5); + + // Verify: position goes backwards + int encodedStreamPosition3 = player.getEncodedStreamPosition(); + assertTrue(encodedStreamPosition3 < encodedStreamPosition2); } @Test