Skip to content

Commit 9ff4cb1

Browse files
authored
Merge pull request #62 from AObuchow/add_streamplayer_getDurationInMilliseconds
Add new StreamPlayer.getDuration() API
2 parents 1ec869b + 3dcd16e commit 9ff4cb1

File tree

10 files changed

+170
-30
lines changed

10 files changed

+170
-30
lines changed

kick.mp3

2.62 KB
Binary file not shown.

kick.wav

14.4 KB
Binary file not shown.

src/main/java/com/goxr3plus/streamplayer/stream/DataSource.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.IOException;
99
import java.io.InputStream;
1010
import java.net.URL;
11+
import java.time.Duration;
1112

1213
public interface DataSource {
1314
static DataSource newDataSource(Object source) throws OperationNotSupportedException {
@@ -67,6 +68,16 @@ static DataSource newDataSource(Object source) throws OperationNotSupportedExcep
6768
* @return The duration of the source data in seconds, or -1 if duration is unavailable.
6869
*/
6970
int getDurationInSeconds();
71+
72+
/**
73+
* @return The duration of the source data in milliseconds, or -1 if duration is unavailable.
74+
*/
75+
long getDurationInMilliseconds();
76+
77+
/**
78+
* @return The duration of the source data in a {@code java.time.Duration} instance, or null if unavailable
79+
*/
80+
Duration getDuration();
7081

7182
/**
7283
* @return true if the DataSource is a FileDataSource,

src/main/java/com/goxr3plus/streamplayer/stream/FileDataSource.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import javax.sound.sampled.UnsupportedAudioFileException;
1010
import java.io.File;
1111
import java.io.IOException;
12+
import java.time.Duration;
1213

1314
public class FileDataSource implements DataSource {
1415

@@ -32,6 +33,16 @@ public AudioInputStream getAudioInputStream() throws UnsupportedAudioFileExcepti
3233
public int getDurationInSeconds() {
3334
return TimeTool.durationInSeconds(source.getAbsolutePath(), AudioType.FILE);
3435
}
36+
37+
@Override
38+
public long getDurationInMilliseconds() {
39+
return TimeTool.durationInMilliseconds(source.getAbsolutePath(), AudioType.FILE);
40+
}
41+
42+
@Override
43+
public Duration getDuration() {
44+
return Duration.ofMillis(getDurationInMilliseconds());
45+
}
3546

3647
@Override
3748
public Object getSource() {

src/main/java/com/goxr3plus/streamplayer/stream/StreamDataSource.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.goxr3plus.streamplayer.stream;
22

3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.time.Duration;
6+
37
import javax.sound.sampled.AudioFileFormat;
48
import javax.sound.sampled.AudioInputStream;
59
import javax.sound.sampled.AudioSystem;
610
import javax.sound.sampled.UnsupportedAudioFileException;
7-
import java.io.IOException;
8-
import java.io.InputStream;
911

1012
public class StreamDataSource implements DataSource {
1113

@@ -29,6 +31,16 @@ public AudioInputStream getAudioInputStream() throws UnsupportedAudioFileExcepti
2931
public int getDurationInSeconds() {
3032
return -1;
3133
}
34+
35+
@Override
36+
public long getDurationInMilliseconds() {
37+
return -1;
38+
}
39+
40+
@Override
41+
public Duration getDuration() {
42+
return null;
43+
}
3244

3345
@Override
3446
public Object getSource() {

src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayer.java

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,25 @@
1010

1111
package com.goxr3plus.streamplayer.stream;
1212

13-
import com.goxr3plus.streamplayer.enums.Status;
14-
import com.goxr3plus.streamplayer.stream.StreamPlayerException.PlayerException;
15-
import javazoom.spi.PropertiesContainer;
16-
import org.tritonus.share.sampled.TAudioFormat;
17-
import org.tritonus.share.sampled.file.TAudioFileFormat;
13+
import java.io.File;
14+
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.net.URL;
17+
import java.nio.ByteBuffer;
18+
import java.nio.ByteOrder;
19+
import java.time.Duration;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.concurrent.Callable;
26+
import java.util.concurrent.ExecutionException;
27+
import java.util.concurrent.ExecutorService;
28+
import java.util.concurrent.Executors;
29+
import java.util.concurrent.Future;
30+
import java.util.logging.Level;
31+
import java.util.logging.Logger;
1832

1933
import javax.naming.OperationNotSupportedException;
2034
import javax.sound.sampled.AudioFileFormat;
@@ -29,20 +43,14 @@
2943
import javax.sound.sampled.Mixer;
3044
import javax.sound.sampled.SourceDataLine;
3145
import javax.sound.sampled.UnsupportedAudioFileException;
32-
import java.io.File;
33-
import java.io.IOException;
34-
import java.io.InputStream;
35-
import java.net.URL;
36-
import java.nio.ByteBuffer;
37-
import java.nio.ByteOrder;
38-
import java.util.*;
39-
import java.util.concurrent.Callable;
40-
import java.util.concurrent.ExecutionException;
41-
import java.util.concurrent.ExecutorService;
42-
import java.util.concurrent.Executors;
43-
import java.util.concurrent.Future;
44-
import java.util.logging.Level;
45-
import java.util.logging.Logger;
46+
47+
import org.tritonus.share.sampled.TAudioFormat;
48+
import org.tritonus.share.sampled.file.TAudioFileFormat;
49+
50+
import com.goxr3plus.streamplayer.enums.Status;
51+
import com.goxr3plus.streamplayer.stream.StreamPlayerException.PlayerException;
52+
53+
import javazoom.spi.PropertiesContainer;
4654

4755
/**
4856
* StreamPlayer is a class based on JavaSound API. It has been successfully tested under Java 10
@@ -803,10 +811,30 @@ private void validateSeconds(int seconds, int durationInSeconds) {
803811
}
804812

805813

814+
815+
/**
816+
* @return The duration of the source data in seconds, or -1 if duration is unavailable.
817+
*/
806818
@Override
807819
public int getDurationInSeconds() {
808820
return source.getDurationInSeconds();
809821
}
822+
823+
/**
824+
* @return The duration of the source data in milliseconds, or -1 if duration is unavailable.
825+
*/
826+
@Override
827+
public long getDurationInMilliseconds() {
828+
return source.getDurationInMilliseconds();
829+
}
830+
831+
/**
832+
* @return The duration of the source data in a {@code java.time.Duration} instance, or null if unavailable
833+
*/
834+
@Override
835+
public Duration getDuration() {
836+
return source.getDuration();
837+
}
810838

811839
/**
812840
* Main loop.

src/main/java/com/goxr3plus/streamplayer/stream/StreamPlayerInterface.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.io.File;
77
import java.io.InputStream;
88
import java.net.URL;
9+
import java.time.Duration;
910
import java.util.List;
1011

1112
public interface StreamPlayerInterface {
@@ -138,6 +139,10 @@ public interface StreamPlayerInterface {
138139
long seekTo(int seconds) throws StreamPlayerException;
139140

140141
int getDurationInSeconds();
142+
143+
long getDurationInMilliseconds();
144+
145+
Duration getDuration();
141146

142147
/**
143148
* Calculates the current position of the encoded audio based on <br>

src/main/java/com/goxr3plus/streamplayer/stream/UrlDataSource.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import javax.sound.sampled.UnsupportedAudioFileException;
77
import java.io.IOException;
88
import java.net.URL;
9+
import java.time.Duration;
910

1011
public class UrlDataSource implements DataSource {
1112

@@ -29,6 +30,16 @@ public AudioInputStream getAudioInputStream() throws UnsupportedAudioFileExcepti
2930
public int getDurationInSeconds() {
3031
return -1;
3132
}
33+
34+
@Override
35+
public long getDurationInMilliseconds() {
36+
return -1;
37+
}
38+
39+
@Override
40+
public Duration getDuration() {
41+
return null;
42+
}
3243

3344
@Override
3445
public Object getSource() {

src/main/java/com/goxr3plus/streamplayer/tools/TimeTool.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import javax.sound.sampled.AudioSystem;
99
import javax.sound.sampled.UnsupportedAudioFileException;
1010

11+
import org.jaudiotagger.audio.mp3.MP3AudioHeader;
1112
import org.jaudiotagger.audio.mp3.MP3File;
1213

1314
import com.goxr3plus.streamplayer.enums.AudioType;
@@ -127,6 +128,27 @@ private static long durationInMilliseconds_Part2(final File file) {
127128
if ("mp3".equals(extension)) {
128129
try {
129130
milliseconds = new MP3File(file).getMP3AudioHeader().getTrackLength() * 1000;
131+
if (milliseconds == 0) {
132+
MP3AudioHeader header = new MP3File(file).getMP3AudioHeader();
133+
int samplesPerFrame;
134+
switch(header.getMpegLayer()) {
135+
case("Layer 1"):
136+
samplesPerFrame = 384;
137+
break;
138+
case("Layer 2"):
139+
samplesPerFrame = 576;
140+
break;
141+
case("Layer 3"):
142+
samplesPerFrame = 1152;
143+
break;
144+
default:
145+
samplesPerFrame = 1152;
146+
break;
147+
}
148+
149+
double frameLengthInMilliseconds = (((double) samplesPerFrame / header.getSampleRateAsNumber()) * 1000);
150+
milliseconds = (long) (header.getNumberOfFrames() * frameLengthInMilliseconds);
151+
}
130152

131153
// milliseconds = (int) ( (Long)
132154
// AudioSystem.getAudioFileFormat(file).properties().get("duration") / 1000 );
@@ -146,7 +168,7 @@ private static long durationInMilliseconds_Part2(final File file) {
146168
else if ("ogg".equals(extension) || "wav".equals(extension)) {
147169
try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file)) {
148170
final AudioFormat format = audioInputStream.getFormat();
149-
milliseconds = (int) (file.length() / (format.getFrameSize() * (int) format.getFrameRate())) * 1000;
171+
milliseconds = (long) (((double) file.length() / ( format.getFrameSize() * (double) format.getFrameRate())) * 1000);
150172
} catch (IOException | UnsupportedAudioFileException ex) {
151173
System.err.println("Problem getting the time of-> " + file.getAbsolutePath());
152174
}

src/test/java/com/goxr3plus/streamplayer/stream/StreamPlayerMethodsTest.java

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
11
package com.goxr3plus.streamplayer.stream;
22

3-
import com.goxr3plus.streamplayer.enums.Status;
4-
import org.junit.jupiter.api.BeforeEach;
5-
import org.junit.jupiter.api.Test;
6-
import org.mockito.ArgumentCaptor;
7-
import org.mockito.Captor;
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
6+
import static org.junit.jupiter.api.Assertions.assertNotNull;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
9+
import static org.junit.jupiter.api.Assertions.fail;
10+
import static org.mockito.ArgumentMatchers.any;
11+
import static org.mockito.ArgumentMatchers.anyInt;
12+
import static org.mockito.ArgumentMatchers.anyLong;
13+
import static org.mockito.Mockito.atLeast;
14+
import static org.mockito.Mockito.atMost;
15+
import static org.mockito.Mockito.mock;
16+
import static org.mockito.Mockito.spy;
17+
import static org.mockito.Mockito.times;
18+
import static org.mockito.Mockito.verify;
819

9-
import javax.sound.sampled.*;
1020
import java.io.File;
1121
import java.io.IOException;
1222
import java.util.List;
1323
import java.util.Map;
1424
import java.util.logging.Logger;
1525

16-
import static org.junit.jupiter.api.Assertions.*;
17-
import static org.mockito.Mockito.*;
26+
import javax.sound.sampled.AudioFileFormat;
27+
import javax.sound.sampled.AudioSystem;
28+
import javax.sound.sampled.SourceDataLine;
29+
import javax.sound.sampled.UnsupportedAudioFileException;
30+
31+
import org.junit.jupiter.api.BeforeEach;
32+
import org.junit.jupiter.api.Test;
33+
import org.mockito.ArgumentCaptor;
34+
35+
import com.goxr3plus.streamplayer.enums.Status;
1836

1937
/**
2038
* Tests of all or most of the public methods of StreamPlayer.
@@ -31,6 +49,28 @@ void setup() {
3149
player = new StreamPlayer(logger);
3250
audioFile = new File("Logic - Ballin [Bass Boosted].mp3");
3351
}
52+
53+
@Test
54+
void duration() throws StreamPlayerException {
55+
audioFile = new File("Logic - Ballin [Bass Boosted].mp3");
56+
player.open(audioFile);
57+
assertEquals(245, player.getDurationInSeconds());
58+
assertEquals(245000, player.getDurationInMilliseconds());
59+
assertNotNull(player.getDuration());
60+
assertEquals(245, player.getDuration().getSeconds());
61+
assertEquals(player.getDuration().toMillis(), player.getDurationInMilliseconds());
62+
63+
audioFile = new File("kick.wav");
64+
player.open(audioFile);
65+
assertEquals(0, player.getDurationInSeconds());
66+
assertEquals(111, player.getDurationInMilliseconds());
67+
68+
audioFile = new File("kick.mp3");
69+
player.open(audioFile);
70+
assertEquals(0, player.getDurationInSeconds());
71+
// Note: the result of calculating a .mp3's duration is different than that of a .wav file
72+
assertEquals(156, player.getDurationInMilliseconds());
73+
}
3474

3575
@Test
3676
void balance() throws StreamPlayerException {

0 commit comments

Comments
 (0)