Skip to content

Commit 803f146

Browse files
committed
Fixed storage not working + handle CDN header
1 parent fcd47e2 commit 803f146

6 files changed

Lines changed: 69 additions & 22 deletions

File tree

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* @author Gianlu
1212
*/
1313
public abstract class AbsChunkedInputStream extends InputStream implements HaltListener {
14-
public static final int PRELOAD_AHEAD = 3;
14+
private static final int PRELOAD_AHEAD = 3;
1515
private static final int PRELOAD_CHUNK_RETRIES = 2;
1616
private static final int MAX_CHUNK_TRIES = 128;
1717
private final Object waitLock = new Object();
@@ -234,12 +234,15 @@ public ChunkException(@NotNull Throwable cause) {
234234
super(cause);
235235
}
236236

237+
protected ChunkException() {
238+
}
239+
237240
private ChunkException(@NotNull String message) {
238241
super(message);
239242
}
240243

241244
@NotNull
242-
public static ChunkException from(short streamError) {
245+
public static ChunkException fromStreamError(short streamError) {
243246
return new ChunkException("Failed due to stream error, code: " + streamError);
244247
}
245248
}

core/src/main/java/xyz/gianlu/librespot/player/feeders/PlayableContentFeeder.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.protobuf.ByteString;
44
import com.spotify.metadata.proto.Metadata;
5+
import okhttp3.HttpUrl;
56
import okhttp3.Response;
67
import okhttp3.ResponseBody;
78
import org.apache.log4j.Logger;
@@ -22,6 +23,7 @@
2223
import xyz.gianlu.librespot.player.codecs.AudioQualityPreference;
2324
import xyz.gianlu.librespot.player.feeders.cdn.CdnFeedHelper;
2425
import xyz.gianlu.librespot.player.feeders.cdn.CdnManager;
26+
import xyz.gianlu.librespot.player.feeders.storage.AudioFileFetch;
2527
import xyz.gianlu.librespot.player.feeders.storage.StorageFeedHelper;
2628

2729
import java.io.IOException;
@@ -86,6 +88,14 @@ private StorageResolveResponse resolveStorageInteractive(@NotNull ByteString fil
8688
return loadTrack(track, audioQualityPreference, haltListener);
8789
}
8890

91+
@NotNull
92+
private LoadedStream loadCdnStream(@NotNull Metadata.AudioFile file, @Nullable Metadata.Track track, @Nullable Metadata.Episode episode, @NotNull String urlStr, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
93+
HttpUrl url = HttpUrl.get(urlStr);
94+
if (track != null) return CdnFeedHelper.loadTrack(session, track, file, url, haltListener);
95+
else if (episode != null) return CdnFeedHelper.loadEpisode(session, episode, file, url, haltListener);
96+
else throw new IllegalStateException();
97+
}
98+
8999
@NotNull
90100
private LoadedStream loadStream(@NotNull Metadata.AudioFile file, @Nullable Metadata.Track track, @Nullable Metadata.Episode episode, @Nullable HaltListener haltListener) throws IOException, MercuryClient.MercuryException, CdnManager.CdnException {
91101
StorageResolveResponse resp = resolveStorageInteractive(file.getFileId());
@@ -95,9 +105,17 @@ private LoadedStream loadStream(@NotNull Metadata.AudioFile file, @Nullable Meta
95105
else if (episode != null) return CdnFeedHelper.loadEpisode(session, episode, file, resp, haltListener);
96106
else throw new IllegalStateException();
97107
case STORAGE:
98-
if (track != null) return StorageFeedHelper.loadTrack(session, track, file, haltListener);
99-
else if (episode != null) return StorageFeedHelper.loadEpisode(session, episode, file, haltListener);
100-
else throw new IllegalStateException();
108+
try {
109+
if (track != null)
110+
return StorageFeedHelper.loadTrack(session, track, file, haltListener);
111+
else if (episode != null)
112+
return StorageFeedHelper.loadEpisode(session, episode, file, haltListener);
113+
else
114+
throw new IllegalStateException();
115+
} catch (AudioFileFetch.StorageNotAvailable ex) {
116+
LOGGER.info("Storage is not available. Going CDN: " + ex.cdnUrl);
117+
return loadCdnStream(file, track, episode, ex.cdnUrl, haltListener);
118+
}
101119
case RESTRICTED:
102120
throw new IllegalStateException("Content is restricted!");
103121
case UNRECOGNIZED:

core/src/main/java/xyz/gianlu/librespot/player/feeders/cdn/CdnFeedHelper.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ private static HttpUrl getUrl(@NotNull Session session, @NotNull StorageResolveR
3131
return HttpUrl.get(resp.getCdnurl(session.random().nextInt(resp.getCdnurlCount())));
3232
}
3333

34-
public static @NotNull LoadedStream loadTrack(@NotNull Session session, Metadata.@NotNull Track track, Metadata.@NotNull AudioFile file, @NotNull StorageResolveResponse storage, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
34+
public static @NotNull LoadedStream loadTrack(@NotNull Session session, Metadata.@NotNull Track track, Metadata.@NotNull AudioFile file, @NotNull HttpUrl url, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
3535
byte[] key = session.audioKey().getAudioKey(track.getGid(), file.getFileId());
36-
CdnManager.Streamer streamer = session.cdn().streamFile(file, key, getUrl(session, storage), haltListener);
36+
CdnManager.Streamer streamer = session.cdn().streamFile(file, key, url, haltListener);
3737
InputStream in = streamer.stream();
3838
NormalizationData normalizationData = NormalizationData.read(in);
3939
if (in.skip(0xa7) != 0xa7) throw new IOException("Couldn't skip 0xa7 bytes!");
4040
return new LoadedStream(track, streamer, normalizationData);
4141
}
4242

43+
public static @NotNull LoadedStream loadTrack(@NotNull Session session, Metadata.@NotNull Track track, Metadata.@NotNull AudioFile file, @NotNull StorageResolveResponse storage, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
44+
return loadTrack(session, track, file, getUrl(session, storage), haltListener);
45+
}
46+
4347
public static @NotNull LoadedStream loadEpisodeExternal(@NotNull Session session, Metadata.@NotNull Episode episode, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
4448
try (Response resp = session.client().newCall(new Request.Builder().head()
4549
.url(episode.getExternalUrl()).build()).execute()) {
@@ -55,12 +59,16 @@ private static HttpUrl getUrl(@NotNull Session session, @NotNull StorageResolveR
5559
}
5660
}
5761

58-
public static @NotNull LoadedStream loadEpisode(@NotNull Session session, Metadata.@NotNull Episode episode, @NotNull Metadata.AudioFile file, @NotNull StorageResolveResponse storage, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
62+
public static @NotNull LoadedStream loadEpisode(@NotNull Session session, Metadata.@NotNull Episode episode, @NotNull Metadata.AudioFile file, @NotNull HttpUrl url, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
5963
byte[] key = session.audioKey().getAudioKey(episode.getGid(), file.getFileId());
60-
CdnManager.Streamer streamer = session.cdn().streamFile(file, key, getUrl(session, storage), haltListener);
64+
CdnManager.Streamer streamer = session.cdn().streamFile(file, key, url, haltListener);
6165
InputStream in = streamer.stream();
6266
NormalizationData normalizationData = NormalizationData.read(in);
6367
if (in.skip(0xa7) != 0xa7) throw new IOException("Couldn't skip 0xa7 bytes!");
6468
return new LoadedStream(episode, streamer, normalizationData);
6569
}
70+
71+
public static @NotNull LoadedStream loadEpisode(@NotNull Session session, Metadata.@NotNull Episode episode, @NotNull Metadata.AudioFile file, @NotNull StorageResolveResponse storage, @Nullable HaltListener haltListener) throws IOException, CdnManager.CdnException {
72+
return loadEpisode(session, episode, file, getUrl(session, storage), haltListener);
73+
}
6674
}

core/src/main/java/xyz/gianlu/librespot/player/feeders/storage/AudioFileFetch.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package xyz.gianlu.librespot.player.feeders.storage;
22

33
import org.apache.log4j.Logger;
4+
import org.jetbrains.annotations.NotNull;
45
import org.jetbrains.annotations.Nullable;
56
import xyz.gianlu.librespot.cache.CacheManager;
67
import xyz.gianlu.librespot.common.Utils;
@@ -17,12 +18,13 @@
1718
*/
1819
public class AudioFileFetch implements AudioFile {
1920
public static final byte HEADER_SIZE = 0x3;
21+
public static final byte HEADER_CDN = 0x4;
2022
private static final Logger LOGGER = Logger.getLogger(AudioFileFetch.class);
2123
private final CacheManager.Handler cache;
2224
private int size = -1;
2325
private int chunks = -1;
2426
private volatile boolean closed = false;
25-
private Short streamError = null;
27+
private AbsChunkedInputStream.ChunkException exception = null;
2628

2729
AudioFileFetch(@Nullable CacheManager.Handler cache) {
2830
this.cache = cache;
@@ -52,7 +54,10 @@ public synchronized void writeHeader(byte id, byte[] bytes, boolean cached) thro
5254
size *= 4;
5355
chunks = (size + CHUNK_SIZE - 1) / CHUNK_SIZE;
5456

55-
streamError = null;
57+
exception = null;
58+
notifyAll();
59+
} else if (id == HEADER_CDN) {
60+
exception = new StorageNotAvailable(new String(bytes));
5661
notifyAll();
5762
}
5863
}
@@ -61,24 +66,32 @@ public synchronized void writeHeader(byte id, byte[] bytes, boolean cached) thro
6166
public synchronized void streamError(int chunkIndex, short code) {
6267
LOGGER.fatal(String.format("Stream error, index: %d, code: %d", chunkIndex, code));
6368

64-
streamError = code;
69+
exception = AbsChunkedInputStream.ChunkException.fromStreamError(code);
6570
notifyAll();
6671
}
6772

6873
synchronized void waitChunk() throws AbsChunkedInputStream.ChunkException {
6974
if (size != -1) return;
7075

7176
try {
72-
streamError = null;
77+
exception = null;
7378
wait();
7479

75-
if (streamError != null)
76-
throw AbsChunkedInputStream.ChunkException.from(streamError);
80+
if (exception != null)
81+
throw exception;
7782
} catch (InterruptedException ex) {
7883
throw new IllegalStateException(ex);
7984
}
8085
}
8186

87+
public static class StorageNotAvailable extends AbsChunkedInputStream.ChunkException {
88+
public final String cdnUrl;
89+
90+
StorageNotAvailable(@NotNull String cdnUrl) {
91+
this.cdnUrl = cdnUrl;
92+
}
93+
}
94+
8295
public int getSize() {
8396
if (size == -1) throw new IllegalStateException("Headers not received yet!");
8497
return size;

core/src/main/java/xyz/gianlu/librespot/player/feeders/storage/AudioFileStreaming.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class AudioFileStreaming implements AudioFile, GeneralAudioStream {
4141
private int chunks = -1;
4242
private ChunksBuffer chunksBuffer;
4343

44-
public AudioFileStreaming(@NotNull Session session, @NotNull Metadata.AudioFile file, byte[] key, @Nullable HaltListener haltListener) throws IOException {
44+
AudioFileStreaming(@NotNull Session session, @NotNull Metadata.AudioFile file, byte[] key, @Nullable HaltListener haltListener) throws IOException {
4545
this.session = session;
4646
this.haltListener = haltListener;
4747
this.cacheHandler = session.cache().forFileId(Utils.bytesToHex(file.getFileId()));
@@ -93,6 +93,10 @@ private boolean tryCacheHeaders(@NotNull AudioFileFetch fetch) throws IOExceptio
9393
if (headers.isEmpty())
9494
return false;
9595

96+
CacheManager.Header cdnHeader;
97+
if ((cdnHeader = CacheManager.Header.find(headers, AudioFileFetch.HEADER_CDN)) != null)
98+
throw new AudioFileFetch.StorageNotAvailable(new String(cdnHeader.value));
99+
96100
for (CacheManager.Header header : headers)
97101
fetch.writeHeader(header.id, header.value, true);
98102

@@ -112,12 +116,11 @@ private AudioFileFetch requestHeaders() throws IOException {
112116
return fetch;
113117
}
114118

115-
public void open() throws IOException {
119+
void open() throws IOException {
116120
AudioFileFetch fetch = requestHeaders();
117121
int size = fetch.getSize();
118122
chunks = fetch.getChunks();
119123
chunksBuffer = new ChunksBuffer(size, chunks);
120-
// FIXME: Check that we don't need to wait for the first chunk
121124
}
122125

123126
private void requestChunk(int index) {
@@ -147,7 +150,7 @@ public void writeHeader(byte id, byte[] bytes, boolean cached) {
147150
@Override
148151
public void streamError(int chunkIndex, short code) {
149152
LOGGER.fatal(String.format("Stream error, index: %d, code: %d", chunkIndex, code));
150-
chunksBuffer.internalStream.notifyChunkError(chunkIndex, AbsChunkedInputStream.ChunkException.from(code));
153+
chunksBuffer.internalStream.notifyChunkError(chunkIndex, AbsChunkedInputStream.ChunkException.fromStreamError(code));
151154
}
152155

153156
@Override
@@ -178,8 +181,10 @@ private class ChunksBuffer implements Closeable {
178181
void writeChunk(@NotNull byte[] chunk, int chunkIndex) throws IOException {
179182
if (internalStream.isClosed()) return;
180183

181-
if (chunk.length != buffer[chunkIndex].length)
184+
if (chunk.length != buffer[chunkIndex].length) {
185+
System.out.println(Utils.bytesToHex(chunk));
182186
throw new IllegalArgumentException(String.format("Buffer size mismatch, required: %d, received: %d, index: %d", buffer[chunkIndex].length, chunk.length, chunkIndex));
187+
}
183188

184189
audioDecrypt.decryptChunk(chunkIndex, chunk, buffer[chunkIndex]);
185190
internalStream.notifyChunkAvailable(chunkIndex);

core/src/main/java/xyz/gianlu/librespot/player/feeders/storage/ChannelManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ void requestChunk(@NotNull ByteString fileId, int index, @NotNull AudioFile file
5959

6060
@Override
6161
protected void handle(@NotNull Packet packet) {
62-
LOGGER.warn(String.format("Couldn't handle packet, cmd: %s, length %d", packet.type(), packet.payload.length));
62+
throw new UnsupportedOperationException();
6363
}
6464

6565
@Override
@@ -129,7 +129,7 @@ private boolean handle(@NotNull ByteBuffer payload) throws IOException {
129129

130130
if (header) {
131131
short length;
132-
while ((length = payload.getShort()) > 0) {
132+
while (payload.remaining() > 0 && (length = payload.getShort()) > 0) {
133133
byte headerId = payload.get();
134134
byte[] headerData = new byte[length - 1];
135135
payload.get(headerData);

0 commit comments

Comments
 (0)