Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions lib/src/main/java/xyz/gianlu/librespot/core/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ private void authenticate(@NotNull Authentication.LoginCredentials credentials)
* {@code true} for {@link Session#reconnect()}.
*/
private void authenticatePartial(@NotNull Authentication.LoginCredentials credentials, boolean removeLock) throws IOException, GeneralSecurityException, SpotifyAuthenticationException {
if (cipherPair == null) throw new IllegalStateException("Connection not established!");
if (conn == null || cipherPair == null) throw new IllegalStateException("Connection not established!");

Authentication.ClientResponseEncrypted clientResponseEncrypted = Authentication.ClientResponseEncrypted.newBuilder()
.setLoginCredentials(credentials)
Expand All @@ -409,7 +409,6 @@ private void authenticatePartial(@NotNull Authentication.LoginCredentials creden

receiver = new Receiver();


byte[] bytes0x0f = new byte[20];
random().nextBytes(bytes0x0f);
sendUnchecked(Packet.Type.Unknown_0x0f, bytes0x0f);
Expand Down Expand Up @@ -452,6 +451,8 @@ private void authenticatePartial(@NotNull Authentication.LoginCredentials creden
public void close() throws IOException {
LOGGER.info("Closing session. {deviceId: {}}", inner.deviceId);

if (scheduledReconnect != null) scheduledReconnect.cancel(true);

closing = true;

scheduler.shutdownNow();
Expand Down Expand Up @@ -513,6 +514,9 @@ public void close() throws IOException {
}

private void sendUnchecked(Packet.Type cmd, byte[] payload) throws IOException {
if (conn == null)
throw new IOException("Cannot write to missing connection.");

cipherPair.sendEncoded(conn.out, cmd.val, payload);
}

Expand Down Expand Up @@ -692,6 +696,9 @@ public Configuration configuration() {
}

private void reconnect() {
if (closing)
return;

synchronized (reconnectionListeners) {
reconnectionListeners.forEach(ReconnectionListener::onConnectionDropped);
}
Expand All @@ -716,6 +723,9 @@ private void reconnect() {
reconnectionListeners.forEach(ReconnectionListener::onConnectionEstablished);
}
} catch (IOException | GeneralSecurityException | SpotifyAuthenticationException ex) {
if (closing)
return;

conn = null;
LOGGER.error("Failed reconnecting, retrying in 10 seconds...", ex);

Expand Down Expand Up @@ -1310,7 +1320,7 @@ public void run() {
continue;
}
} catch (IOException | GeneralSecurityException ex) {
if (running) {
if (running && !closing) {
LOGGER.error("Failed reading packet!", ex);
reconnect();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public abstract class Codec implements Closeable {
protected int seekZero = 0;
private OutputAudioFormat format;

Codec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) {
public Codec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) {
this.audioIn = audioFile.stream();
this.audioFile = audioFile;
this.duration = duration;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2021 devgianlu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package xyz.gianlu.librespot.player.codecs;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.gianlu.librespot.audio.GeneralAudioStream;
import xyz.gianlu.librespot.audio.NormalizationData;
import xyz.gianlu.librespot.audio.format.SuperAudioFormat;
import xyz.gianlu.librespot.player.PlayerConfiguration;

import java.util.*;

/**
* @author devgianlu
*/
public final class Codecs {
private static final Map<SuperAudioFormat, HashSet<Class<? extends Codec>>> codecs = new EnumMap<>(SuperAudioFormat.class);
private static final Logger LOGGER = LoggerFactory.getLogger(Codecs.class);

static {
registerCodec(SuperAudioFormat.VORBIS, VorbisCodec.class);
registerCodec(SuperAudioFormat.MP3, Mp3Codec.class);
}

private Codecs() {
}

@Nullable
public static Codec initCodec(@NotNull SuperAudioFormat format, @NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) {
Set<Class<? extends Codec>> set = codecs.get(format);
if (set == null) return null;

Optional<Class<? extends Codec>> opt = set.stream().findFirst();
if (!opt.isPresent()) return null;

try {
Class<? extends Codec> clazz = opt.get();
return clazz.getConstructor(GeneralAudioStream.class, NormalizationData.class, PlayerConfiguration.class, int.class).newInstance(audioFile, normalizationData, conf, duration);
} catch (ReflectiveOperationException ex) {
LOGGER.error("Failed initializing Codec instance for {}", format, ex);
return null;
}
}

public static void registerCodec(@NotNull SuperAudioFormat format, @NotNull Class<? extends Codec> clazz) {
codecs.computeIfAbsent(format, (key) -> new HashSet<>(5)).add(clazz);
}

public static void replaceCodecs(@NotNull SuperAudioFormat format, @NotNull Class<? extends Codec> clazz) {
Set<Class<? extends Codec>> set = codecs.get(format);
if (set != null) set.clear();
registerCodec(format, clazz);
}

public static void unregisterCodec(@NotNull Class<? extends Codec> clazz) {
for (Set<Class<? extends Codec>> set : codecs.values())
set.remove(clazz);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package xyz.gianlu.librespot.player.playback;

import javazoom.jl.decoder.BitstreamException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
Expand All @@ -33,8 +32,7 @@
import xyz.gianlu.librespot.player.PlayerConfiguration;
import xyz.gianlu.librespot.player.StateWrapper;
import xyz.gianlu.librespot.player.codecs.Codec;
import xyz.gianlu.librespot.player.codecs.Mp3Codec;
import xyz.gianlu.librespot.player.codecs.VorbisCodec;
import xyz.gianlu.librespot.player.codecs.Codecs;
import xyz.gianlu.librespot.player.codecs.VorbisOnlyAudioQuality;
import xyz.gianlu.librespot.player.crossfade.CrossfadeController;
import xyz.gianlu.librespot.player.metrics.PlaybackMetrics;
Expand Down Expand Up @@ -132,20 +130,9 @@ private void load(boolean preload) throws IOException, Codec.CodecException, Mer
if (crossfade.hasAnyFadeOut() || conf.preloadEnabled)
notifyInstant(INSTANT_PRELOAD, (int) (crossfade.fadeOutStartTimeMin() - TimeUnit.SECONDS.toMillis(20)));

switch (stream.in.codec()) {
case VORBIS:
codec = new VorbisCodec(stream.in, stream.normalizationData, conf, metadata.duration());
break;
case MP3:
try {
codec = new Mp3Codec(stream.in, stream.normalizationData, conf, metadata.duration());
} catch (BitstreamException ex) {
throw new IOException(ex);
}
break;
default:
throw new UnsupportedEncodingException(stream.in.codec().toString());
}
codec = Codecs.initCodec(stream.in.codec(), stream.in, stream.normalizationData, conf, metadata.duration());
if (codec == null)
throw new UnsupportedEncodingException(stream.in.codec().toString());

LOGGER.trace("Loaded {} codec. {of: {}, format: {}, playbackId: {}}", stream.in.codec(), stream.in.describe(), codec.getAudioFormat(), playbackId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.RejectedExecutionException;

/**
* @author Gianlu
Expand All @@ -65,6 +66,7 @@ public final class DeviceStateHandler implements Closeable, DealerClient.Message
private final Connect.PutStateRequest.Builder putState;
private final AsyncWorker<Connect.PutStateRequest> putStateWorker;
private volatile String connectionId = null;
private volatile boolean closing = false;

public DeviceStateHandler(@NotNull Session session, @NotNull PlayerConfiguration conf) {
this.session = session;
Expand Down Expand Up @@ -226,7 +228,11 @@ public synchronized void updateState(@NotNull Connect.PutStateReason reason, int
.setClientSideTimestamp(TimeProvider.currentTimeMillis())
.getDeviceBuilder().setDeviceInfo(deviceInfo).setPlayerState(state);

putStateWorker.submit(putState.build());
try {
putStateWorker.submit(putState.build());
} catch (RejectedExecutionException ex) {
if (!closing) LOGGER.error("Failed to submit update state task.", ex);
}
}

public synchronized int getVolume() {
Expand All @@ -244,6 +250,8 @@ public void setVolume(int val) {

@Override
public void close() {
closing = true;

session.dealer().removeMessageListener(this);
session.dealer().removeRequestListener(this);

Expand Down