Skip to content

Commit e5f3df3

Browse files
committed
Try another decoder when previous one fails
This is a workaround to devgianlu/librespot-android#9 (comment)
1 parent cb0067c commit e5f3df3

File tree

5 files changed

+68
-43
lines changed

5 files changed

+68
-43
lines changed

decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ public Decoder(@NotNull SeekableInputStream audioIn, float normalizationFactor,
4444
this.normalizationFactor = normalizationFactor;
4545
}
4646

47-
public final int writeSomeTo(@NotNull OutputStream out) throws IOException, CodecException {
47+
public final int writeSomeTo(@NotNull OutputStream out) throws IOException, DecoderException {
4848
return readInternal(out);
4949
}
5050

51-
protected abstract int readInternal(@NotNull OutputStream out) throws IOException, CodecException;
51+
protected abstract int readInternal(@NotNull OutputStream out) throws IOException, DecoderException;
5252

5353
/**
5454
* @return Time in millis
@@ -112,12 +112,12 @@ public CannotGetTimeException(String message, Throwable cause) {
112112
}
113113
}
114114

115-
public static class CodecException extends Exception {
116-
public CodecException(String message) {
115+
public static class DecoderException extends Exception {
116+
public DecoderException(String message) {
117117
super(message);
118118
}
119119

120-
public CodecException(String message, Throwable cause) {
120+
public DecoderException(String message, Throwable cause) {
121121
super(message, cause);
122122
}
123123
}

lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Decoders.java

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@
2424
import xyz.gianlu.librespot.player.decoders.Decoder;
2525
import xyz.gianlu.librespot.player.decoders.SeekableInputStream;
2626

27+
import java.io.IOException;
2728
import java.util.*;
2829

2930
/**
3031
* @author devgianlu
3132
*/
3233
public final class Decoders {
33-
private static final Map<SuperAudioFormat, HashSet<Class<? extends Decoder>>> decoders = new EnumMap<>(SuperAudioFormat.class);
34+
private static final Map<SuperAudioFormat, List<Class<? extends Decoder>>> decoders = new EnumMap<>(SuperAudioFormat.class);
3435
private static final Logger LOGGER = LoggerFactory.getLogger(Decoders.class);
3536

3637
static {
@@ -41,35 +42,54 @@ public final class Decoders {
4142
private Decoders() {
4243
}
4344

44-
@Nullable
45-
public static Decoder initDecoder(@NotNull SuperAudioFormat format, @NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) {
46-
Set<Class<? extends Decoder>> set = decoders.get(format);
47-
if (set == null) return null;
45+
@NotNull
46+
public static Iterator<Decoder> initDecoder(@NotNull SuperAudioFormat format, @NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) {
47+
List<Class<? extends Decoder>> list = decoders.get(format);
48+
if (list == null) list = Collections.emptyList();
4849

49-
Optional<Class<? extends Decoder>> opt = set.stream().findFirst();
50-
if (!opt.isPresent()) return null;
50+
int seekZero = audioIn.position();
51+
Iterator<Class<? extends Decoder>> iter = list.listIterator();
52+
return new Iterator<Decoder>() {
53+
@Override
54+
public boolean hasNext() {
55+
return iter.hasNext();
56+
}
5157

52-
try {
53-
Class<? extends Decoder> clazz = opt.get();
54-
return clazz.getConstructor(SeekableInputStream.class, float.class, int.class).newInstance(audioIn, normalizationFactor, duration);
55-
} catch (ReflectiveOperationException ex) {
56-
LOGGER.error("Failed initializing Codec instance for {}", format, ex);
57-
return null;
58-
}
58+
@Override
59+
public @Nullable Decoder next() {
60+
try {
61+
audioIn.seek(seekZero);
62+
} catch (IOException ex) {
63+
LOGGER.error("Failed rewinding SeekableInputStream!", ex);
64+
return null;
65+
}
66+
67+
try {
68+
Class<? extends Decoder> clazz = iter.next();
69+
return clazz.getConstructor(SeekableInputStream.class, float.class, int.class).newInstance(audioIn, normalizationFactor, duration);
70+
} catch (ReflectiveOperationException ex) {
71+
LOGGER.error("Failed initializing Codec instance for {}", format, ex.getCause());
72+
return null;
73+
}
74+
}
75+
};
5976
}
6077

61-
public static void registerDecoder(@NotNull SuperAudioFormat format, @NotNull Class<? extends Decoder> clazz) {
62-
decoders.computeIfAbsent(format, (key) -> new HashSet<>(5)).add(clazz);
78+
public static void registerDecoder(@NotNull SuperAudioFormat format, int index, @NotNull Class<? extends Decoder> clazz) {
79+
decoders.computeIfAbsent(format, (key) -> new ArrayList<>(5)).add(index, clazz);
6380
}
6481

65-
public static void replaceDecoder(@NotNull SuperAudioFormat format, @NotNull Class<? extends Decoder> clazz) {
66-
Set<Class<? extends Decoder>> set = decoders.get(format);
67-
if (set != null) set.clear();
68-
registerDecoder(format, clazz);
82+
public static void registerDecoder(@NotNull SuperAudioFormat format, @NotNull Class<? extends Decoder> clazz) {
83+
decoders.computeIfAbsent(format, (key) -> new ArrayList<>(5)).add(clazz);
6984
}
7085

7186
public static void unregisterDecoder(@NotNull Class<? extends Decoder> clazz) {
72-
for (Set<Class<? extends Decoder>> set : decoders.values())
73-
set.remove(clazz);
87+
for (List<Class<? extends Decoder>> list : decoders.values())
88+
list.remove(clazz);
89+
}
90+
91+
public static void removeDecoders(@NotNull SuperAudioFormat format) {
92+
List<Class<? extends Decoder>> list = decoders.get(format);
93+
if (list != null) list.clear();
7494
}
7595
}

lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Mp3Decoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ private void readMP3() throws IOException {
148148

149149
try {
150150
decoder.decodeFrame(header, bitstream);
151-
} catch (DecoderException ex) {
151+
} catch (javazoom.jl.decoder.DecoderException ex) {
152152
throw new IOException(ex);
153153
}
154154

lib/src/main/java/xyz/gianlu/librespot/audio/decoders/VorbisDecoder.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public final class VorbisDecoder extends Decoder {
5454
private int index;
5555
private long pcm_offset;
5656

57-
public VorbisDecoder(@NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) throws IOException, CodecException {
57+
public VorbisDecoder(@NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) throws IOException, DecoderException {
5858
super(audioIn, normalizationFactor, duration);
5959

6060
this.joggSyncState.init();
@@ -89,10 +89,10 @@ public int time() {
8989
/**
9090
* Reads the body. All "holes" (-1) in data will stop the playback.
9191
*
92-
* @throws Decoder.CodecException if a decoding exception occurs
93-
* @throws IOException if an I/O exception occurs
92+
* @throws DecoderException if a decoding exception occurs
93+
* @throws IOException if an I/O exception occurs
9494
*/
95-
private void readHeader() throws IOException, CodecException {
95+
private void readHeader() throws IOException, DecoderException {
9696
boolean finished = false;
9797
int packet = 1;
9898

@@ -115,7 +115,7 @@ private void readHeader() throws IOException, CodecException {
115115
}
116116

117117
if (joggStreamState.pagein(joggPage) == -1)
118-
throw new CodecException("Failed reading page");
118+
throw new DecoderException("Failed reading page");
119119

120120
if (joggStreamState.packetout(joggPacket) == -1)
121121
throw new HoleInDataException();
@@ -131,18 +131,18 @@ private void readHeader() throws IOException, CodecException {
131131
buffer = joggSyncState.data;
132132

133133
if (count == 0 && !finished)
134-
throw new CodecException("Buffer under-run");
134+
throw new DecoderException("Buffer under-run");
135135
}
136136
}
137137

138138
/**
139139
* Reads the body. All "holes" (-1) are skipped, and the playback continues
140140
*
141-
* @throws Decoder.CodecException if a decoding exception occurs
141+
* @throws DecoderException if a decoding exception occurs
142142
* @throws IOException if an I/O exception occurs
143143
*/
144144
@Override
145-
public synchronized int readInternal(@NotNull OutputStream out) throws IOException, CodecException {
145+
public synchronized int readInternal(@NotNull OutputStream out) throws IOException, DecoderException {
146146
if (closed) return -1;
147147

148148
int written = 0;
@@ -151,7 +151,7 @@ public synchronized int readInternal(@NotNull OutputStream out) throws IOExcepti
151151
// Read more
152152
} else if (result == 1) {
153153
if (joggStreamState.pagein(joggPage) == -1)
154-
throw new CodecException("Failed reading page");
154+
throw new DecoderException("Failed reading page");
155155

156156
if (joggPage.granulepos() == 0)
157157
return -1;
@@ -241,13 +241,13 @@ public void close() throws IOException {
241241
}
242242
}
243243

244-
private static class NotVorbisException extends CodecException {
244+
private static class NotVorbisException extends DecoderException {
245245
NotVorbisException() {
246246
super("Data read is not vorbis data");
247247
}
248248
}
249249

250-
private static class HoleInDataException extends CodecException {
250+
private static class HoleInDataException extends DecoderException {
251251
HoleInDataException() {
252252
super("Hole in vorbis data");
253253
}

player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ PlayerQueueEntry retrySelf(boolean preloaded) {
106106
*
107107
* @throws PlayableContentFeeder.ContentRestrictedException If the content cannot be retrieved because of restrictions (this condition won't change with a retry).
108108
*/
109-
private void load(boolean preload) throws IOException, Decoder.CodecException, MercuryClient.MercuryException, CdnManager.CdnException, PlayableContentFeeder.ContentRestrictedException {
109+
private void load(boolean preload) throws IOException, Decoder.DecoderException, MercuryClient.MercuryException, CdnManager.CdnException, PlayableContentFeeder.ContentRestrictedException {
110110
PlayableContentFeeder.LoadedStream stream;
111111
if (playable instanceof LocalId)
112112
stream = PlayableContentFeeder.LoadedStream.forLocalFile((LocalId) playable,
@@ -137,7 +137,12 @@ private void load(boolean preload) throws IOException, Decoder.CodecException, M
137137
if (stream.normalizationData == null || !conf.enableNormalisation) normalizationFactor = 1;
138138
else normalizationFactor = stream.normalizationData.getFactor(conf.normalisationPregain);
139139

140-
decoder = Decoders.initDecoder(stream.in.codec(), stream.in.stream(), normalizationFactor, metadata.duration());
140+
Iterator<Decoder> iter = Decoders.initDecoder(stream.in.codec(), stream.in.stream(), normalizationFactor, metadata.duration());
141+
while (iter.hasNext()) {
142+
decoder = iter.next();
143+
if (decoder != null) break;
144+
}
145+
141146
if (decoder == null)
142147
throw new UnsupportedEncodingException(stream.in.codec().toString());
143148

@@ -269,7 +274,7 @@ public void run() {
269274

270275
try {
271276
load(preloaded);
272-
} catch (IOException | PlayableContentFeeder.ContentRestrictedException | CdnManager.CdnException | MercuryClient.MercuryException | Decoder.CodecException ex) {
277+
} catch (IOException | PlayableContentFeeder.ContentRestrictedException | CdnManager.CdnException | MercuryClient.MercuryException | Decoder.DecoderException ex) {
273278
close();
274279
listener.loadingError(this, ex, retried);
275280
LOGGER.trace("{} terminated at loading.", this, ex);
@@ -329,7 +334,7 @@ public void run() {
329334
close();
330335
break;
331336
}
332-
} catch (IOException | Decoder.CodecException ex) {
337+
} catch (IOException | Decoder.DecoderException ex) {
333338
if (!closed) {
334339
close();
335340
listener.playbackError(this, ex);

0 commit comments

Comments
 (0)