Skip to content

Commit 972f417

Browse files
committed
Introducing StreamFeeder
1 parent 8dc6a7f commit 972f417

7 files changed

Lines changed: 239 additions & 107 deletions

File tree

core/src/main/java/xyz/gianlu/librespot/DefaultConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import org.jetbrains.annotations.NotNull;
44
import org.jetbrains.annotations.Nullable;
55
import xyz.gianlu.librespot.core.Session;
6-
import xyz.gianlu.librespot.player.TrackHandler;
6+
import xyz.gianlu.librespot.player.StreamFeeder;
77

88
import java.io.File;
99

@@ -18,8 +18,8 @@ public final class DefaultConfiguration extends AbsConfiguration {
1818

1919
@NotNull
2020
@Override
21-
public TrackHandler.AudioQuality preferredQuality() {
22-
return TrackHandler.AudioQuality.VORBIS_320;
21+
public StreamFeeder.AudioQuality preferredQuality() {
22+
return StreamFeeder.AudioQuality.VORBIS_320;
2323
}
2424

2525
@Override

core/src/main/java/xyz/gianlu/librespot/FileConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import org.jetbrains.annotations.Nullable;
77
import xyz.gianlu.librespot.common.Utils;
88
import xyz.gianlu.librespot.core.Session;
9-
import xyz.gianlu.librespot.player.TrackHandler;
9+
import xyz.gianlu.librespot.player.StreamFeeder;
1010

1111
import java.io.File;
1212
import java.io.FileReader;
@@ -97,8 +97,8 @@ public boolean doCleanUp() {
9797
}
9898

9999
@Override
100-
public @NotNull TrackHandler.AudioQuality preferredQuality() {
101-
return TrackHandler.AudioQuality.valueOf(properties.getProperty("player.preferredAudioQuality", defaults.preferredQuality().name()));
100+
public @NotNull StreamFeeder.AudioQuality preferredQuality() {
101+
return StreamFeeder.AudioQuality.valueOf(properties.getProperty("player.preferredAudioQuality", defaults.preferredQuality().name()));
102102
}
103103

104104
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class CacheManager {
2929
private final ControlTable controlTable;
3030
private final ExecutorService executorService = Executors.newCachedThreadPool();
3131

32-
CacheManager(@NotNull CacheConfiguration conf) throws IOException {
32+
public CacheManager(@NotNull CacheConfiguration conf) throws IOException {
3333
this.enabled = conf.cacheEnabled();
3434
if (enabled) {
3535
this.loadedHandlers = new HashMap<>();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ private int getQueuedTrack(boolean consume) {
407407

408408
public interface PlayerConfiguration {
409409
@NotNull
410-
TrackHandler.AudioQuality preferredQuality();
410+
StreamFeeder.AudioQuality preferredQuality();
411411

412412
boolean preloadEnabled();
413413

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package xyz.gianlu.librespot.player;
2+
3+
import org.apache.log4j.Logger;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
6+
import xyz.gianlu.librespot.common.Utils;
7+
import xyz.gianlu.librespot.common.proto.Metadata;
8+
import xyz.gianlu.librespot.common.proto.Spirc;
9+
import xyz.gianlu.librespot.core.Session;
10+
import xyz.gianlu.librespot.crypto.Packet;
11+
import xyz.gianlu.librespot.mercury.MercuryClient;
12+
import xyz.gianlu.librespot.mercury.MercuryRequests;
13+
import xyz.gianlu.librespot.mercury.model.TrackId;
14+
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
/**
21+
* @author Gianlu
22+
*/
23+
public class StreamFeeder {
24+
private static final Logger LOGGER = Logger.getLogger(StreamFeeder.class);
25+
private final Session session;
26+
private final CacheManager cacheManager;
27+
28+
public StreamFeeder(@NotNull Session session, @NotNull CacheManager cacheManager) {
29+
this.session = session;
30+
this.cacheManager = cacheManager;
31+
}
32+
33+
@Nullable
34+
private static Metadata.Track pickAlternativeIfNecessary(@NotNull Metadata.Track track) {
35+
if (track.getFileCount() > 0) return track;
36+
37+
for (Metadata.Track alt : track.getAlternativeList()) {
38+
if (alt.getFileCount() > 0) {
39+
Metadata.Track.Builder builder = track.toBuilder();
40+
builder.clearFile();
41+
builder.addAllFile(alt.getFileList());
42+
return builder.build();
43+
}
44+
}
45+
46+
return null;
47+
}
48+
49+
@NotNull
50+
public LoadedStream load(@NotNull Spirc.TrackRef ref, @NotNull AudioQualityPreference audioQualityPreference) throws IOException, MercuryClient.MercuryException {
51+
Metadata.Track track = session.mercury().sendSync(MercuryRequests.getTrack(TrackId.fromTrackRef(ref))).proto();
52+
track = pickAlternativeIfNecessary(track);
53+
if (track == null) {
54+
LOGGER.fatal("Couldn't find playable track: " + Utils.bytesToHex(ref.getGid()));
55+
throw new FeederException();
56+
}
57+
58+
Metadata.AudioFile file = audioQualityPreference.getFile(track);
59+
if (file == null) {
60+
LOGGER.fatal(String.format("Couldn't find any suitable audio file, available: %s", AudioQuality.listFormats(track)));
61+
throw new FeederException();
62+
}
63+
64+
session.send(Packet.Type.Unknown_0x4f, new byte[0]);
65+
66+
byte[] key = session.audioKey().getAudioKey(track, file);
67+
AudioFileStreaming audioStreaming = new AudioFileStreaming(session, cacheManager, file, key);
68+
audioStreaming.open();
69+
70+
InputStream in = audioStreaming.stream();
71+
NormalizationData normalizationData = NormalizationData.read(in);
72+
LOGGER.trace(String.format("Loaded normalization data, track_gain: %.2f, track_peak: %.2f, album_gain: %.2f, album_peak: %.2f",
73+
normalizationData.track_gain_db, normalizationData.track_peak, normalizationData.album_gain_db, normalizationData.album_peak));
74+
75+
if (in.skip(0xa7) != 0xa7)
76+
throw new IOException("Couldn't skip 0xa7 bytes!");
77+
78+
return new LoadedStream(track, audioStreaming, normalizationData);
79+
}
80+
81+
public enum AudioQuality {
82+
VORBIS_96(Metadata.AudioFile.Format.OGG_VORBIS_96),
83+
VORBIS_160(Metadata.AudioFile.Format.OGG_VORBIS_160),
84+
VORBIS_320(Metadata.AudioFile.Format.OGG_VORBIS_320);
85+
86+
private final Metadata.AudioFile.Format format;
87+
88+
AudioQuality(@NotNull Metadata.AudioFile.Format format) {
89+
this.format = format;
90+
}
91+
92+
@Nullable
93+
public static Metadata.AudioFile getAnyVorbisFile(@NotNull Metadata.Track track) {
94+
for (Metadata.AudioFile file : track.getFileList()) {
95+
Metadata.AudioFile.Format fmt = file.getFormat();
96+
if (fmt == Metadata.AudioFile.Format.OGG_VORBIS_96
97+
|| fmt == Metadata.AudioFile.Format.OGG_VORBIS_160
98+
|| fmt == Metadata.AudioFile.Format.OGG_VORBIS_320) {
99+
return file;
100+
}
101+
}
102+
103+
return null;
104+
}
105+
106+
@NotNull
107+
public static List<Metadata.AudioFile.Format> listFormats(Metadata.Track track) {
108+
List<Metadata.AudioFile.Format> list = new ArrayList<>(track.getFileCount());
109+
for (Metadata.AudioFile file : track.getFileList()) list.add(file.getFormat());
110+
return list;
111+
}
112+
113+
@Nullable Metadata.AudioFile getFile(@NotNull Metadata.Track track) {
114+
for (Metadata.AudioFile file : track.getFileList()) {
115+
if (file.getFormat() == this.format)
116+
return file;
117+
}
118+
119+
return null;
120+
}
121+
}
122+
123+
public interface AudioQualityPreference {
124+
125+
@Nullable
126+
Metadata.AudioFile getFile(@NotNull Metadata.Track track);
127+
}
128+
129+
public static class LoadedStream {
130+
public final Metadata.Track track;
131+
public final AudioFileStreaming in;
132+
public final NormalizationData normalizationData;
133+
134+
LoadedStream(@NotNull Metadata.Track track, @NotNull AudioFileStreaming in, @NotNull NormalizationData normalizationData) {
135+
this.track = track;
136+
this.in = in;
137+
this.normalizationData = normalizationData;
138+
}
139+
}
140+
141+
public static class SuperAudioQuality implements AudioQualityPreference {
142+
private final SuperAudioFormat format;
143+
144+
public SuperAudioQuality(@NotNull SuperAudioFormat format) {
145+
this.format = format;
146+
}
147+
148+
@Override
149+
public @Nullable Metadata.AudioFile getFile(Metadata.@NotNull Track track) {
150+
for (Metadata.AudioFile file : track.getFileList()) {
151+
if (SuperAudioFormat.get(file.getFormat()) == format)
152+
return file;
153+
}
154+
155+
LOGGER.fatal(String.format("Couldn't find any file, format: %s, available: %s", format, AudioQuality.listFormats(track)));
156+
return null;
157+
}
158+
}
159+
160+
public static class VorbisOnlyAudioQuality implements AudioQualityPreference {
161+
private final AudioQuality preferred;
162+
163+
public VorbisOnlyAudioQuality(@NotNull StreamFeeder.AudioQuality preferred) {
164+
this.preferred = preferred;
165+
}
166+
167+
@Override
168+
public @Nullable Metadata.AudioFile getFile(Metadata.@NotNull Track track) {
169+
Metadata.AudioFile file = preferred.getFile(track);
170+
if (file == null) {
171+
file = AudioQuality.getAnyVorbisFile(track);
172+
if (file == null) {
173+
LOGGER.fatal(String.format("Couldn't find any Vorbis file, available: %s", AudioQuality.listFormats(track)));
174+
return null;
175+
} else {
176+
LOGGER.warn(String.format("Using %s because preferred %s couldn't be found.", file, preferred));
177+
}
178+
}
179+
180+
return file;
181+
}
182+
}
183+
184+
public static class FeederException extends IOException {
185+
FeederException() {
186+
}
187+
}
188+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package xyz.gianlu.librespot.player;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import xyz.gianlu.librespot.common.proto.Metadata;
5+
6+
/**
7+
* @author Gianlu
8+
*/
9+
public enum SuperAudioFormat {
10+
MP3, VORBIS, AAC, OTHER;
11+
12+
@NotNull
13+
public static SuperAudioFormat get(@NotNull Metadata.AudioFile.Format format) {
14+
switch (format) {
15+
case OGG_VORBIS_96:
16+
case OGG_VORBIS_160:
17+
case OGG_VORBIS_320:
18+
return VORBIS;
19+
case MP3_256:
20+
case MP3_320:
21+
case MP3_160:
22+
case MP3_96:
23+
case MP3_160_ENC:
24+
return MP3;
25+
case AAC_160:
26+
case AAC_320:
27+
return AAC;
28+
case OTHER2:
29+
case OTHER3:
30+
case OTHER4:
31+
case OTHER5:
32+
return OTHER;
33+
default:
34+
throw new IllegalArgumentException("Unknown audio format: " + format);
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)