Skip to content

Commit 7b6ddd5

Browse files
committed
Fixed #101
1 parent 3dba254 commit 7b6ddd5

7 files changed

Lines changed: 87 additions & 110 deletions

File tree

core/src/main/java/xyz/gianlu/librespot/mercury/MercuryRequests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public List<Remote3Page> pages() {
152152
return list;
153153
}
154154

155-
@NotNull
155+
@Nullable
156156
public JsonObject metadata() {
157157
return obj.getAsJsonObject("metadata");
158158
}

core/src/main/java/xyz/gianlu/librespot/player/LinesHolder.java

Lines changed: 59 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
*/
1414
public class LinesHolder {
1515
private static final Logger LOGGER = Logger.getLogger(LinesHolder.class);
16-
private final Map<Mixer, List<Line>> openLines = new HashMap<>();
17-
private final Object waitLineLock = new Object();
16+
private final Map<Mixer, Line> openLines = new HashMap<>();
1817

1918
LinesHolder() {
2019
}
@@ -29,7 +28,7 @@ private static List<Mixer> findSupportingMixersFor(@NotNull Line.Info info) thro
2928
}
3029

3130
if (mixers.isEmpty())
32-
throw new MixerException(String.format("Couldn't find a suitable mixer, line: %s, available: %s", info, Arrays.toString(AudioSystem.getMixerInfo())));
31+
throw new MixerException(String.format("Couldn't find a suitable mixer, openLine: %s, available: %s", info, Arrays.toString(AudioSystem.getMixerInfo())));
3332
else
3433
return mixers;
3534
}
@@ -53,74 +52,29 @@ private static Mixer findMixer(@NotNull List<Mixer> mixers, @Nullable String[] k
5352
return list.get(0);
5453
}
5554

56-
@NotNull
57-
public LineWrapper getLine(@NotNull Mixer mixer, @NotNull DataLine.Info info) throws LineUnavailableException {
58-
Line line = getLineOrNull(mixer, info);
59-
if (line == null) return new LineWrapper(mixer, info);
60-
else return new LineWrapper((SourceDataLine) line);
61-
}
62-
63-
@Nullable
64-
private Line getLineOrNull(@NotNull Mixer mixer, @NotNull DataLine.Info info) throws LineUnavailableException {
65-
List<Line> lines;
66-
synchronized (openLines) {
67-
lines = openLines.computeIfAbsent(mixer, k -> new ArrayList<>());
68-
if (lines.isEmpty()) {
69-
Line line = mixer.getLine(info);
70-
lines.add(line);
71-
LOGGER.debug(String.format("Got first line from mixer '%s'", mixer.getMixerInfo().getName()));
72-
return line;
73-
}
55+
private static boolean isCompatible(@NotNull DataLine.Info line, @NotNull DataLine.Info other) {
56+
for (AudioFormat format : other.getFormats()) {
57+
if (!line.isFormatSupported(format))
58+
return false;
7459
}
7560

76-
int max = mixer.getMaxLines(info);
77-
LOGGER.debug(String.format("Mixer '%s' has %d lines in use, max: %d", mixer.getMixerInfo().getName(), lines.size(), max));
78-
79-
if (max == AudioSystem.NOT_SPECIFIED) {
80-
Line line = mixer.getLine(info);
81-
lines.add(line);
82-
return line;
83-
} else {
84-
int count = 0;
85-
for (Line line : lines) {
86-
if (line.getLineInfo().matches(info))
87-
count++;
88-
}
61+
return true;
62+
}
8963

90-
if (max < count) {
91-
Line line = mixer.getLine(info);
92-
lines.add(line);
93-
return line;
94-
} else {
95-
return null;
96-
}
97-
}
64+
@NotNull
65+
public LineWrapper getLine(@NotNull Mixer mixer, @NotNull DataLine.Info info) {
66+
return new LineWrapper(mixer, info);
9867
}
9968

10069
@NotNull
101-
public LineWrapper getLineFor(@NotNull Player.Configuration conf, @NotNull AudioFormat format) throws LineUnavailableException, MixerException {
70+
public LineWrapper getLineFor(@NotNull Player.Configuration conf, @NotNull AudioFormat format) throws MixerException {
10271
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format, AudioSystem.NOT_SPECIFIED);
10372
List<Mixer> mixers = findSupportingMixersFor(info);
10473
if (conf.logAvailableMixers()) LOGGER.info("Available mixers: " + Utils.mixersToString(mixers));
10574
Mixer mixer = findMixer(mixers, conf.mixerSearchKeywords());
106-
LOGGER.info(String.format("Mixer for playback '%s', maxLines: %d", mixer.getMixerInfo().getName(), mixer.getMaxLines(info)));
10775
return getLine(mixer, info);
10876
}
10977

110-
private void lineClosed(@NotNull Line line) {
111-
synchronized (openLines) {
112-
for (List<Line> lines : openLines.values()) {
113-
if (lines.remove(line)) {
114-
LOGGER.debug(String.format("Removed closed line, remaining: %d", lines.size()));
115-
synchronized (waitLineLock) {
116-
waitLineLock.notifyAll();
117-
return;
118-
}
119-
}
120-
}
121-
}
122-
}
123-
12478
public static class MixerException extends Exception {
12579
MixerException(String message) {
12680
super(message);
@@ -130,47 +84,69 @@ public static class MixerException extends Exception {
13084
public class LineWrapper {
13185
private Mixer mixer;
13286
private DataLine.Info info;
133-
private SourceDataLine line;
87+
private SourceDataLine openLine;
13488

13589
private LineWrapper(@NotNull Mixer mixer, @NotNull DataLine.Info info) {
13690
this.mixer = mixer;
13791
this.info = info;
13892
}
13993

140-
private LineWrapper(@NotNull SourceDataLine line) {
141-
this.line = line;
94+
public void write(byte[] buffer, int from, int to) {
95+
if (openLine == null) throw new IllegalStateException();
96+
openLine.write(buffer, from, to);
14297
}
14398

144-
@NotNull
145-
public SourceDataLine waitAndOpen(@NotNull AudioFormat format) throws LineUnavailableException {
146-
if (line != null) {
147-
line.open(format);
148-
return line;
149-
}
150-
151-
if (info == null || mixer == null) throw new IllegalStateException("Line and info are both null!");
99+
public void open(@NotNull AudioFormat format) throws LineUnavailableException, InterruptedException {
100+
Line line = openLines.get(mixer);
101+
if (line != null && isCompatible((DataLine.Info) line.getLineInfo(), info)) {
102+
openLine = (SourceDataLine) line;
103+
synchronized (openLine) {
104+
openLine.wait();
105+
}
152106

153-
while (line == null) {
154-
synchronized (waitLineLock) {
155-
try {
156-
waitLineLock.wait();
157-
line = (SourceDataLine) getLineOrNull(mixer, info);
158-
} catch (InterruptedException ex) {
159-
LOGGER.fatal("Interrupted while waiting for line! Retrying.", ex);
107+
LOGGER.trace(String.format("Reused line for mixer '%s'.", mixer.getMixerInfo().getName()));
108+
} else {
109+
if (line != null) {
110+
synchronized (line) {
111+
line.wait();
112+
line.close();
160113
}
161114
}
162-
}
163115

164-
line.open(format);
165-
LOGGER.trace(String.format("Line opened for mixer '%s'.", mixer.getMixerInfo().getName()));
166-
return line;
116+
openLine = (SourceDataLine) mixer.getLine(info);
117+
openLines.put(mixer, openLine);
118+
openLine.open(format);
119+
LOGGER.trace(String.format("New line opened for mixer '%s'.", mixer.getMixerInfo().getName()));
120+
}
167121
}
168122

169123
public void close() {
170-
if (line != null) {
171-
line.close();
172-
lineClosed(line);
124+
if (openLine != null) {
125+
synchronized (openLine) {
126+
openLine.notifyAll();
127+
}
173128
}
174129
}
130+
131+
public void stop() {
132+
if (openLine == null) throw new IllegalStateException();
133+
openLine.stop();
134+
}
135+
136+
public void start() {
137+
if (openLine == null) throw new IllegalStateException();
138+
openLine.start();
139+
}
140+
141+
public boolean isControlSupported(@NotNull Control.Type type) {
142+
if (openLine == null) throw new IllegalStateException();
143+
return openLine.isControlSupported(type);
144+
}
145+
146+
@NotNull
147+
public Control getControl(@NotNull Control.Type type) {
148+
if (openLine == null) throw new IllegalStateException();
149+
return openLine.getControl(type);
150+
}
175151
}
176152
}

core/src/main/java/xyz/gianlu/librespot/player/PlayerRunner.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import xyz.gianlu.librespot.player.codecs.VorbisCodec;
99

1010
import javax.sound.sampled.FloatControl;
11-
import javax.sound.sampled.Line;
1211
import java.io.IOException;
1312

1413
/**
@@ -77,7 +76,7 @@ public static class Controller {
7776
private final FloatControl masterGain;
7877
private int volume = 0;
7978

80-
public Controller(@NotNull Line line, int initialVolume) {
79+
public Controller(@NotNull LinesHolder.LineWrapper line, int initialVolume) {
8180
if (line.isControlSupported(FloatControl.Type.MASTER_GAIN))
8281
masterGain = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
8382
else

core/src/main/java/xyz/gianlu/librespot/player/StateWrapper.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,13 @@ void load(@NotNull Remote3Frame frame) throws IOException, MercuryClient.Mercury
241241
metadata = resolved.metadata();
242242
}
243243

244-
JsonElement elm = metadata.get("context_description");
245-
if (elm != null) state.setContextDescription(elm.getAsString());
246-
else state.setContextDescription("");
244+
if (metadata == null) {
245+
state.setContextDescription("");
246+
} else {
247+
JsonElement elm = metadata.get("context_description");
248+
if (elm != null) state.setContextDescription(elm.getAsString());
249+
else state.setContextDescription("");
250+
}
247251
}
248252

249253
state.setContextUri(frame.context.uri);

core/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ public final void run() {
6767
try {
6868
readBody();
6969
if (!stopped) listener.endOfTrack();
70-
} catch (IOException | LineUnavailableException | CodecException ex) {
70+
} catch (IOException | LineUnavailableException | CodecException | InterruptedException ex) {
7171
if (!stopped) listener.playbackError(ex);
7272
} finally {
7373
cleanup();
7474
}
7575
}
7676

77-
protected abstract void readBody() throws IOException, LineUnavailableException, CodecException;
77+
protected abstract void readBody() throws IOException, LineUnavailableException, CodecException, InterruptedException;
7878

7979
public abstract int time() throws CannotGetTimeException;
8080

core/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import xyz.gianlu.librespot.player.codecs.mp3.Mp3Sound;
77

88
import javax.sound.sampled.LineUnavailableException;
9-
import javax.sound.sampled.SourceDataLine;
109
import java.io.IOException;
1110
import java.io.InputStream;
1211

@@ -27,7 +26,7 @@ public Mp3Codec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationDa
2726

2827
try {
2928
outputLine = lines.getLineFor(conf, sound.getAudioFormat());
30-
} catch (LineUnavailableException | IllegalStateException | SecurityException ex) {
29+
} catch (IllegalStateException | SecurityException ex) {
3130
throw new CodecException(ex);
3231
}
3332

@@ -58,20 +57,20 @@ private static void skipMp3Tags(@NotNull InputStream in) throws IOException {
5857
}
5958

6059
@Override
61-
protected void readBody() throws LineUnavailableException, IOException {
62-
SourceDataLine line = outputLine.waitAndOpen(sound.getAudioFormat());
63-
this.controller = new PlayerRunner.Controller(line, listener.getVolume());
60+
protected void readBody() throws LineUnavailableException, IOException, InterruptedException {
61+
outputLine.open(sound.getAudioFormat());
62+
this.controller = new PlayerRunner.Controller(outputLine, listener.getVolume());
6463

6564
while (!stopped) {
6665
if (playing) {
67-
line.start();
66+
outputLine.start();
6867

6968
int count = sound.read(buffer);
7069
if (count == -1) break;
7170

72-
line.write(buffer, 0, count);
71+
outputLine.write(buffer, 0, count);
7372
} else {
74-
line.stop();
73+
outputLine.stop();
7574

7675
try {
7776
synchronized (pauseLock) {
@@ -86,7 +85,7 @@ protected void readBody() throws LineUnavailableException, IOException {
8685

8786
@Override
8887
public int time() throws CannotGetTimeException {
89-
throw new CannotGetTimeException();
88+
throw new CannotGetTimeException();
9089
}
9190

9291
@Override

core/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import javax.sound.sampled.AudioFormat;
1616
import javax.sound.sampled.LineUnavailableException;
17-
import javax.sound.sampled.SourceDataLine;
1817
import java.io.IOException;
1918

2019
/**
@@ -122,7 +121,7 @@ private AudioFormat initializeSound(Player.Configuration conf) throws CodecExcep
122121

123122
try {
124123
outputLine = lines.getLineFor(conf, format);
125-
} catch (LineUnavailableException | IllegalStateException | SecurityException ex) {
124+
} catch (IllegalStateException | SecurityException ex) {
126125
throw new CodecException(ex);
127126
}
128127

@@ -139,13 +138,13 @@ private AudioFormat initializeSound(Player.Configuration conf) throws CodecExcep
139138
* @throws IOException if an I/O exception occurs
140139
*/
141140
@Override
142-
protected void readBody() throws IOException, LineUnavailableException, CodecException {
143-
SourceDataLine line = outputLine.waitAndOpen(audioFormat);
144-
this.controller = new PlayerRunner.Controller(line, listener.getVolume());
141+
protected void readBody() throws IOException, LineUnavailableException, CodecException, InterruptedException {
142+
outputLine.open(audioFormat);
143+
this.controller = new PlayerRunner.Controller(outputLine, listener.getVolume());
145144

146145
while (!stopped) {
147146
if (playing) {
148-
line.start();
147+
outputLine.start();
149148

150149
int result = joggSyncState.pageout(joggPage);
151150
if (result == -1 || result == 0) {
@@ -162,7 +161,7 @@ protected void readBody() throws IOException, LineUnavailableException, CodecExc
162161
if (result == -1 || result == 0) {
163162
break;
164163
} else if (result == 1) {
165-
decodeCurrentPacket(line);
164+
decodeCurrentPacket();
166165
}
167166
}
168167

@@ -180,7 +179,7 @@ protected void readBody() throws IOException, LineUnavailableException, CodecExc
180179
if (count == 0)
181180
break;
182181
} else {
183-
line.stop();
182+
outputLine.stop();
184183

185184
try {
186185
synchronized (pauseLock) {
@@ -193,7 +192,7 @@ protected void readBody() throws IOException, LineUnavailableException, CodecExc
193192
}
194193
}
195194

196-
private void decodeCurrentPacket(@NotNull SourceDataLine line) {
195+
private void decodeCurrentPacket() {
197196
if (jorbisBlock.synthesis(joggPacket) == 0)
198197
jorbisDspState.synthesis_blockin(jorbisBlock);
199198

@@ -220,7 +219,7 @@ private void decodeCurrentPacket(@NotNull SourceDataLine line) {
220219
}
221220
}
222221

223-
line.write(convertedBuffer, 0, 2 * jorbisInfo.channels * range);
222+
outputLine.write(convertedBuffer, 0, 2 * jorbisInfo.channels * range);
224223
jorbisDspState.synthesis_read(range);
225224

226225
long granulepos = joggPacket.granulepos;

0 commit comments

Comments
 (0)