Skip to content

Commit

Permalink
Separate sink code from librespot-player to allow a platform depend…
Browse files Browse the repository at this point in the history
…ent sink (#337)

There are two new modules:
- `sink-api` that contains a small amount of code for the sink implementation to be based on
- `sink` that implements the default sink using javax.sound
  • Loading branch information
devgianlu committed Apr 12, 2021
1 parent 7f3357a commit c204e8a
Show file tree
Hide file tree
Showing 25 changed files with 480 additions and 234 deletions.
2 changes: 1 addition & 1 deletion lib/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
<version>${slf4j-api.version}</version>
</dependency>
</dependencies>
</project>
15 changes: 0 additions & 15 deletions lib/src/main/java/xyz/gianlu/librespot/common/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sound.sampled.Mixer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -294,20 +293,6 @@ public static String artistsToString(List<Metadata.Artist> artists) {
return builder.toString();
}

@NotNull
public static String mixersToString(List<Mixer> list) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (Mixer mixer : list) {
if (!first) builder.append(", ");
first = false;

builder.append('\'').append(mixer.getMixerInfo().getName()).append('\'');
}

return builder.toString();
}

@NotNull
public static String toBase64(@NotNull ByteString bytes) {
return Base64.getEncoder().encodeToString(bytes.toByteArray());
Expand Down
12 changes: 12 additions & 0 deletions player/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
<artifactId>librespot-lib</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>xyz.gianlu.librespot</groupId>
<artifactId>librespot-sink-api</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Audio -->
<dependency>
Expand Down Expand Up @@ -93,5 +98,12 @@
<artifactId>toml</artifactId>
<version>3.6.3</version>
</dependency>

<!-- Default sink -->
<dependency>
<groupId>xyz.gianlu.librespot</groupId>
<artifactId>librespot-sink</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ public PlayerConfiguration toPlayer() {
.setMixerSearchKeywords(getStringArray("player.mixerSearchKeywords", ';'))
.setNormalisationPregain(normalisationPregain())
.setOutput(config.getEnum("player.output", PlayerConfiguration.AudioOutput.class))
.setOutputClass(config.get("player.outputClass"))
.setOutputPipe(outputPipe())
.setPreferredQuality(preferredQuality())
.setPreloadEnabled(config.get("preload.enabled"))
Expand Down
8 changes: 1 addition & 7 deletions player/src/main/java/xyz/gianlu/librespot/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@
import xyz.gianlu.librespot.player.metrics.PlaybackMetrics;
import xyz.gianlu.librespot.player.metrics.PlayerMetrics;
import xyz.gianlu.librespot.player.mixing.AudioSink;
import xyz.gianlu.librespot.player.mixing.LineHelper;
import xyz.gianlu.librespot.player.playback.PlayerSession;
import xyz.gianlu.librespot.player.state.DeviceStateHandler;
import xyz.gianlu.librespot.player.state.DeviceStateHandler.PlayCommandHelper;

import javax.sound.sampled.LineUnavailableException;
import java.io.Closeable;
import java.io.IOException;
import java.util.*;
Expand Down Expand Up @@ -63,11 +61,7 @@ public Player(@NotNull PlayerConfiguration conf, @NotNull Session session) {
this.session = session;
this.events = new EventsDispatcher(conf);
this.sink = new AudioSink(conf, ex -> {
if (ex instanceof LineHelper.MixerException || ex instanceof LineUnavailableException)
LOGGER.fatal("An error with the mixer occurred. This is likely a configuration issue, please consult the project repository.", ex);
else
LOGGER.fatal("Sink error!", ex);

LOGGER.fatal("Sink error!", ex);
panicState(PlaybackMetrics.Reason.TRACK_ERROR);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public final class PlayerConfiguration {

// Output
public final AudioOutput output;
public final String outputClass;
public final File outputPipe;
public final File metadataPipe;
public final String[] mixerSearchKeywords;
Expand All @@ -32,14 +33,15 @@ public final class PlayerConfiguration {
public final boolean bypassSinkVolume;

private PlayerConfiguration(AudioQuality preferredQuality, boolean enableNormalisation, float normalisationPregain, boolean autoplayEnabled, int crossfadeDuration, boolean preloadEnabled,
AudioOutput output, File outputPipe, File metadataPipe, String[] mixerSearchKeywords, boolean logAvailableMixers, int releaseLineDelay,
AudioOutput output, String outputClass, File outputPipe, File metadataPipe, String[] mixerSearchKeywords, boolean logAvailableMixers, int releaseLineDelay,
int initialVolume, int volumeSteps, boolean bypassSinkVolume) {
this.preferredQuality = preferredQuality;
this.enableNormalisation = enableNormalisation;
this.normalisationPregain = normalisationPregain;
this.autoplayEnabled = autoplayEnabled;
this.crossfadeDuration = crossfadeDuration;
this.output = output;
this.outputClass = outputClass;
this.outputPipe = outputPipe;
this.metadataPipe = metadataPipe;
this.mixerSearchKeywords = mixerSearchKeywords;
Expand All @@ -52,7 +54,7 @@ private PlayerConfiguration(AudioQuality preferredQuality, boolean enableNormali
}

public enum AudioOutput {
MIXER, PIPE, STDOUT
MIXER, PIPE, STDOUT, CUSTOM
}

public final static class Builder {
Expand All @@ -66,6 +68,7 @@ public final static class Builder {

// Output
private AudioOutput output = AudioOutput.MIXER;
private String outputClass;
private File outputPipe;
private File metadataPipe;
private String[] mixerSearchKeywords;
Expand Down Expand Up @@ -110,6 +113,11 @@ public Builder setOutput(AudioOutput output) {
return this;
}

public Builder setOutputClass(String outputClass) {
this.outputClass = outputClass;
return this;
}

public Builder setOutputPipe(File outputPipe) {
this.outputPipe = outputPipe;
return this;
Expand Down Expand Up @@ -164,7 +172,7 @@ public Builder setBypassSinkVolume(boolean bypassSinkVolume) {
@Contract(value = " -> new", pure = true)
public @NotNull PlayerConfiguration build() {
return new PlayerConfiguration(preferredQuality, enableNormalisation, normalisationPregain, autoplayEnabled, crossfadeDuration, preloadEnabled,
output, outputPipe, metadataPipe, mixerSearchKeywords, logAvailableMixers, releaseLineDelay,
output, outputClass, outputPipe, metadataPipe, mixerSearchKeywords, logAvailableMixers, releaseLineDelay,
initialVolume, volumeSteps, bypassSinkVolume);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import xyz.gianlu.librespot.audio.GeneralAudioStream;
import xyz.gianlu.librespot.audio.NormalizationData;
import xyz.gianlu.librespot.player.PlayerConfiguration;
import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat;

import javax.sound.sampled.AudioFormat;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
Expand All @@ -26,7 +26,7 @@ public abstract class Codec implements Closeable {
private final GeneralAudioStream audioFile;
protected volatile boolean closed = false;
protected int seekZero = 0;
private AudioFormat format;
private OutputAudioFormat format;

Codec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) {
this.audioIn = audioFile.stream();
Expand Down Expand Up @@ -75,12 +75,12 @@ public void seek(int positionMs) {
}

@NotNull
public final AudioFormat getAudioFormat() {
public final OutputAudioFormat getAudioFormat() {
if (format == null) throw new IllegalStateException();
return format;
}

protected final void setAudioFormat(@NotNull AudioFormat format) {
protected final void setAudioFormat(@NotNull OutputAudioFormat format) {
this.format = format;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import xyz.gianlu.librespot.audio.GeneralAudioStream;
import xyz.gianlu.librespot.audio.NormalizationData;
import xyz.gianlu.librespot.player.PlayerConfiguration;
import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat;

import javax.sound.sampled.AudioFormat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
Expand All @@ -29,7 +29,7 @@ public Mp3Codec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationDa

audioIn.mark(-1);

setAudioFormat(new AudioFormat(in.getSampleRate(), 16, in.getChannels(), true, false));
setAudioFormat(new OutputAudioFormat(in.getSampleRate(), 16, in.getChannels(), true, false));
}

private static void skipMp3Tags(@NotNull InputStream in) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
import xyz.gianlu.librespot.audio.GeneralAudioStream;
import xyz.gianlu.librespot.audio.NormalizationData;
import xyz.gianlu.librespot.player.PlayerConfiguration;
import xyz.gianlu.librespot.player.mixing.LineHelper;
import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat;

import javax.sound.sampled.AudioFormat;
import java.io.IOException;
import java.io.OutputStream;

Expand All @@ -41,7 +40,7 @@ public final class VorbisCodec extends Codec {
private int index;
private long pcm_offset;

public VorbisCodec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) throws IOException, CodecException, LineHelper.MixerException {
public VorbisCodec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) throws IOException, CodecException {
super(audioFile, normalizationData, conf, duration);

this.joggSyncState.init();
Expand All @@ -59,7 +58,7 @@ public VorbisCodec(@NotNull GeneralAudioStream audioFile, @Nullable Normalizatio
pcmInfo = new float[1][][];
pcmIndex = new int[jorbisInfo.channels];

setAudioFormat(new AudioFormat(jorbisInfo.rate, 16, jorbisInfo.channels, true, false));
setAudioFormat(new OutputAudioFormat(jorbisInfo.rate, 16, jorbisInfo.channels, true, false));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import xyz.gianlu.librespot.metadata.PlayableId;
import xyz.gianlu.librespot.player.Player;
import xyz.gianlu.librespot.player.TrackOrEpisode;
import xyz.gianlu.librespot.player.mixing.AudioSink;
import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat;

import java.io.Closeable;
import java.io.File;
Expand Down Expand Up @@ -88,8 +88,9 @@ private void sendProgress(@NotNull Player player) {
TrackOrEpisode metadata = player.currentMetadata();
if (metadata == null) return;

String data = String.format("1/%.0f/%.0f", player.time() * AudioSink.DEFAULT_FORMAT.getSampleRate() / 1000 + 1,
metadata.duration() * AudioSink.DEFAULT_FORMAT.getSampleRate() / 1000 + 1);
String data = String.format("1/%.0f/%.0f",
player.time() * OutputAudioFormat.DEFAULT_FORMAT.getSampleRate() / 1000 + 1,
metadata.duration() * OutputAudioFormat.DEFAULT_FORMAT.getSampleRate() / 1000 + 1);
safeSend(EventsMetadataPipe.TYPE_SSNC, EventsMetadataPipe.CODE_PRGR, data);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import xyz.gianlu.librespot.player.codecs.Mp3Codec;
import xyz.gianlu.librespot.player.codecs.VorbisCodec;
import xyz.gianlu.librespot.player.crossfade.CrossfadeController;

import javax.sound.sampled.AudioFormat;
import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat;

/**
* @author devgianlu
Expand All @@ -32,7 +31,7 @@ public PlayerMetrics(@Nullable PlayableContentFeeder.Metrics contentMetrics, @Nu
decodedLength = codec.decodedLength();
decryptTime = codec.decryptTimeMs();

AudioFormat format = codec.getAudioFormat();
OutputAudioFormat format = codec.getAudioFormat();
bitrate = (int) (format.getFrameRate() * format.getFrameSize());

if (codec instanceof VorbisCodec) encoding = "vorbis";
Expand Down
Loading

0 comments on commit c204e8a

Please sign in to comment.