From 719d370aac524836099cf093c7f96e1f135eee71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Wed, 18 Dec 2019 22:43:22 +0100 Subject: [PATCH 01/12] minor JavaDoc and readability improvement --- .../goxr3plus/streamplayer/stream/StreamPlayer.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index f0ee979..3cbf3e3 100644 --- a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java @@ -1067,12 +1067,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(); } From 0ef3d2ff7dea0712edeba317de0ad6d73a85a261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Wed, 18 Dec 2019 22:43:48 +0100 Subject: [PATCH 02/12] Update StreamPlayerMethodsTest.java Some unit tests are improved, but there are more to be done. --- .../stream/StreamPlayerMethodsTest.java | 96 +++++++++++++++---- 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java index 4cfbc5b..ba43454 100644 --- a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java @@ -4,8 +4,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.*; import java.io.File; +import java.io.IOException; +import java.util.List; import java.util.logging.Logger; import static org.junit.jupiter.api.Assertions.*; @@ -191,9 +193,11 @@ void totalBytes() { @Test void stopped() { - player.isStopped(); - fail("Test not done"); + assertFalse(player.isStopped()); + + player.stop(); + assertTrue(player.isStopped()); } @Test @@ -225,10 +229,17 @@ void pausedOrPlaying() { } @Test - void paused() { - player.isPaused(); + 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 @@ -277,10 +288,21 @@ void play() throws StreamPlayerException { } @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 @@ -310,9 +332,15 @@ 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); + assertEquals(3f, 1.0/precision); } @Test @@ -332,14 +360,18 @@ void open() throws StreamPlayerException { @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); + player.open(audioFile); + int positionByte1 = player.getPositionByte(); + + player.seekBytes(100); + int positionByte2 = player.getPositionByte(); fail("Test not done"); } @@ -376,17 +408,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,8 +441,25 @@ void removeStreamPlayerListener() { } @Test - void seekTo() throws StreamPlayerException { - player.seekTo(1000); + void seekTo() throws StreamPlayerException, IOException, UnsupportedAudioFileException { + + // Some tests before we do the real tests + AudioFileFormat audioFileFormat = AudioSystem.getAudioFileFormat(audioFile); + + + // Setup + player.open(audioFile); + player.play(); + player.pause(); + int positionByte1 = player.getPositionByte(); + assertNotEquals(AudioSystem.NOT_SPECIFIED, positionByte1, "If we cannot check the position, how can we verify seek?"); + + // Execute + player.seekTo(10); + + // Verify + int positionByte2 = player.getPositionByte(); + assertNotEquals(positionByte2, positionByte1); fail("Test not done"); } From 611b279998c29ce3cd013a196675e2f81eb2a3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Thu, 19 Dec 2019 07:59:03 +0100 Subject: [PATCH 03/12] Update StreamPlayer.java Re-assignment of audioInputStream removed by changing audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream); to assignment of a new variable. Variable name changes: Initial audioInputStream changed to encodedAudioInputStream audioInputStream after the re-assignment now assigned to a field called decodedAudioInputStream. Old encodedAudioInputStream renamed to encodedAudioInputStreamCopy. --- .../streamplayer/stream/StreamPlayer.java | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index f0ee979..48c2e35 100644 --- a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java @@ -56,34 +56,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. mp3, ogg or similar */ + private volatile AudioInputStream encodedAudioInputStream; + + /** Copy of the encoded audio input stream. */ + private AudioInputStream encodedAudioInputStreamCopy; + + /** The decoded audio input stream. Linear format */ + private AudioInputStream decodedAudioInputStream; - /** The encoded audio input stream. */ - private AudioInputStream encodedAudioInputStream; /** 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 +185,8 @@ public void reset() { outlet.flushAndFreeDataLine(); // AudioFile - audioInputStream = null; audioFileFormat = null; - encodedAudioInputStream = null; + encodedAudioInputStreamCopy = null; encodedAudioLength = -1; // Controls @@ -328,7 +325,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 +422,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 +471,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 +492,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 +706,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 +841,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 +868,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 +935,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 +950,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) { @@ -1248,10 +1247,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 +1263,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; } From afb30bc1a818ec3f74f0746983ac259858433434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Thu, 19 Dec 2019 08:08:50 +0100 Subject: [PATCH 04/12] AudioSystem.getMixerInfo() will not return null, so we don't have to check for it. --- .../streamplayer/stream/StreamPlayer.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index f0ee979..e6b26f4 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; @@ -994,17 +990,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 +1018,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); From 357eacfed49923078a12381243d645a97bb56a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Thu, 19 Dec 2019 08:20:25 +0100 Subject: [PATCH 05/12] encodedAudioInputStream doesn't change, so it doesn't have to be volatile. According to Sonar, Non-primitive fields should not be "volatile" For primitive fields volatile inhibits caching. This is only of interest of the field changes value, but this one doesn't. Since the field is an object reference, value change in this context means that it gets replaced with a different object, but it will not happen. See also https://wiki.sei.cmu.edu/confluence/display/java/CON50-J.+Do+not+assume+that+declaring+a+reference+volatile+guarantees+safe+publication+of+the+members+of+the+referenced+object --- .../java/com/goxr3plus/streamplayer/stream/StreamPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index 48c2e35..eee07c3 100644 --- a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java @@ -67,7 +67,7 @@ public class StreamPlayer implements StreamPlayerInterface, Callable { private DataSource source; /** The encoded audio input stream. mp3, ogg or similar */ - private volatile AudioInputStream encodedAudioInputStream; + private AudioInputStream encodedAudioInputStream; /** Copy of the encoded audio input stream. */ private AudioInputStream encodedAudioInputStreamCopy; From 68c67ca20e6d025a89d4dbc02931331e126c6c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Fri, 20 Dec 2019 23:17:22 +0100 Subject: [PATCH 06/12] Improved and re-structured unit tests --- .../StreamPlayerFutureImprovementTest.java | 47 +++++ .../stream/StreamPlayerMethodsTest.java | 166 +++++++++++++----- 2 files changed, 172 insertions(+), 41 deletions(-) create mode 100644 src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java 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..807c4d9 --- /dev/null +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java @@ -0,0 +1,47 @@ +package com.goxr3plus.streamplayer.stream; + +import com.goxr3plus.streamplayer.enums.Status; +import org.junit.jupiter.api.BeforeEach; +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"); + } + +} diff --git a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java index ba43454..98dcab0 100644 --- a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java @@ -3,15 +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.*; 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. @@ -87,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; @@ -97,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(); @@ -106,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. } /** @@ -185,10 +191,17 @@ 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 @@ -213,19 +226,36 @@ void sourceDataLine() throws StreamPlayerException { } @Test - void playing() { - final boolean playing = player.isPlaying(); + void playing() throws StreamPlayerException { + + assertFalse(player.isPlaying()); - assertFalse(playing); + player.open(audioFile); + assertFalse(player.isPlaying()); - fail("Test not done"); + player.play(); + assertTrue(player.isPlaying()); + + player.pause(); + assertFalse(player.isPlaying()); } @Test - void pausedOrPlaying() { - player.isPausedOrPlaying(); + 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 @@ -243,34 +273,76 @@ void paused() throws StreamPlayerException { } @Test - void addStreamPlayerListener_dontAcceptNull() { - assertThrows(Exception.class, () -> player.addStreamPlayerListener(null)); + void addStreamPlayerListener() throws StreamPlayerException, InterruptedException { + // Setup + final StreamPlayerListener listener = mock(StreamPlayerListener.class); - fail("Test not done"); - } + ArgumentCaptor dataSourceCaptor = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor propertiesCaptor1 = ArgumentCaptor.forClass(Map.class); - @Test - void addStreamPlayerListener() { - final StreamPlayerListener listener = mock(StreamPlayerListener.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(5)).progress(anyInt(), anyLong(), any(), any()); + verify(listener, atMost(10)).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 @@ -387,10 +459,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 @@ -451,17 +531,21 @@ void seekTo() throws StreamPlayerException, IOException, UnsupportedAudioFileExc player.open(audioFile); player.play(); player.pause(); - int positionByte1 = player.getPositionByte(); - assertNotEquals(AudioSystem.NOT_SPECIFIED, positionByte1, "If we cannot check the position, how can we verify seek?"); + int encodedStreamPosition1 = player.getEncodedStreamPosition(); // Execute player.seekTo(10); // Verify - int positionByte2 = player.getPositionByte(); - assertNotEquals(positionByte2, positionByte1); + int encodedStreamPosition2 = player.getEncodedStreamPosition(); + assertTrue(encodedStreamPosition2 > encodedStreamPosition1); - fail("Test not done"); + // Execute: go backwards + player.seekTo(5); + + // Verify: position goes backwards + int encodedStreamPosition3 = player.getEncodedStreamPosition(); + assertTrue(encodedStreamPosition3 < encodedStreamPosition2); } @Test From dca8c7a8e3ae6c1e4136358b810df3377136c526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Sun, 22 Dec 2019 11:09:10 +0100 Subject: [PATCH 07/12] Improved and more complete unit tests. StreamPlayerMethodsTest still contains tests that don't pass. The tested methods are candidates for removal from StreamPlayer. All tests in StreamPlayerMethodsTest must be reviewed: Do they actually verify anything that need to be verified? Or are they too coupled with the current implementation? StreamPlayerFutureImprovementTest contains tests that currently fail. Failures are caused by behavior in StreamPlayer which I think is wrong or bad. But I may have misinterpreted the intended behavior. --- .../StreamPlayerFutureImprovementTest.java | 26 +++++++ .../stream/StreamPlayerMethodsTest.java | 71 ++++++++++++------- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java index 807c4d9..4a6c452 100644 --- a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerFutureImprovementTest.java @@ -2,6 +2,7 @@ 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; @@ -44,4 +45,29 @@ void addStreamPlayerListener_dontAcceptNull() { 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 98dcab0..1c247e4 100644 --- a/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java +++ b/src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java @@ -135,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 @@ -299,8 +299,8 @@ void addStreamPlayerListener() throws StreamPlayerException, InterruptedExceptio verify(listener, times(1)).opened(any(), any()); - verify(listener, atLeast(5)).progress(anyInt(), anyLong(), any(), any()); - verify(listener, atMost(10)).progress(anyInt(), anyLong(), 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? @@ -348,15 +348,25 @@ void speedFactor() throws StreamPlayerException, InterruptedException { @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 @@ -378,17 +388,33 @@ void resume() throws StreamPlayerException { } @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 @@ -412,22 +438,27 @@ void pan() throws StreamPlayerException { float precision = player.getPrecision(); assertNotEquals(0, precision); - assertEquals(3f, 1.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 @@ -437,19 +468,11 @@ void mixers() { // There is nothing that can be done with the information outside the private scope. } - @Test - void seekBytes() throws StreamPlayerException { - player.open(audioFile); - int positionByte1 = player.getPositionByte(); - - player.seekBytes(100); - int positionByte2 = player.getPositionByte(); - 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() { From 8463821490568671590402fecdf2c346b78c6c97 Mon Sep 17 00:00:00 2001 From: GOXR3PLUS STUDIO Date: Wed, 1 Jan 2020 20:29:53 +0200 Subject: [PATCH 08/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f780dc1..9075944 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ - FLAC - MONKEY's AUDIO - SPEEX -- **Not Supported Yet ❌** +- **Not Supported Yet ❌** - AAC - THEORA - ... all the others From 074a81bd7a7678e5583559e4501c77a484fcc9db Mon Sep 17 00:00:00 2001 From: Alexander Kentros Date: Thu, 27 Feb 2020 12:02:46 +0200 Subject: [PATCH 09/12] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9075944..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 )

From 1ec869bcd8fb4bc47c61ab4f3d8f369ad1e97703 Mon Sep 17 00:00:00 2001 From: Alexander Kentros Date: Tue, 31 Mar 2020 14:49:29 +0300 Subject: [PATCH 10/12] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2cb847593608703a25c92f617d0826b1db232c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Thu, 19 Dec 2019 07:59:03 +0100 Subject: [PATCH 11/12] Update StreamPlayer.java Re-assignment of audioInputStream removed by changing audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream); to assignment of a new variable. Variable name changes: Initial audioInputStream changed to encodedAudioInputStream audioInputStream after the re-assignment now assigned to a field called decodedAudioInputStream. Old encodedAudioInputStream renamed to encodedAudioInputStreamCopy. --- .../streamplayer/stream/StreamPlayer.java | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index 8756aca..6f9ed77 100644 --- a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java @@ -52,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. mp3, ogg or similar */ + private volatile AudioInputStream encodedAudioInputStream; + + /** Copy of the encoded audio input stream. */ + private AudioInputStream encodedAudioInputStreamCopy; + + /** The decoded audio input stream. Linear format */ + private AudioInputStream decodedAudioInputStream; - /** The encoded audio input stream. */ - private AudioInputStream encodedAudioInputStream; /** 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--------------------- @@ -183,9 +181,8 @@ public void reset() { outlet.flushAndFreeDataLine(); // AudioFile - audioInputStream = null; audioFileFormat = null; - encodedAudioInputStream = null; + encodedAudioInputStreamCopy = null; encodedAudioLength = -1; // Controls @@ -324,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(); @@ -421,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); } } } @@ -470,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"); @@ -491,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); @@ -704,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; @@ -839,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 @@ -866,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(), @@ -933,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(); @@ -948,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) { @@ -1249,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); } @@ -1265,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; } From d91395b33097030b8a0164f0fe6628728ceedf25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Helge=20Stenstr=C3=B6m?= Date: Thu, 19 Dec 2019 08:20:25 +0100 Subject: [PATCH 12/12] encodedAudioInputStream doesn't change, so it doesn't have to be volatile. According to Sonar, Non-primitive fields should not be "volatile" For primitive fields volatile inhibits caching. This is only of interest of the field changes value, but this one doesn't. Since the field is an object reference, value change in this context means that it gets replaced with a different object, but it will not happen. See also https://wiki.sei.cmu.edu/confluence/display/java/CON50-J.+Do+not+assume+that+declaring+a+reference+volatile+guarantees+safe+publication+of+the+members+of+the+referenced+object --- .../java/com/goxr3plus/streamplayer/stream/StreamPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java index 6f9ed77..6e3a514 100644 --- a/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java +++ b/src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java @@ -63,7 +63,7 @@ public class StreamPlayer implements StreamPlayerInterface, Callable { private DataSource source; /** The encoded audio input stream. mp3, ogg or similar */ - private volatile AudioInputStream encodedAudioInputStream; + private AudioInputStream encodedAudioInputStream; /** Copy of the encoded audio input stream. */ private AudioInputStream encodedAudioInputStreamCopy;