From 88864b198c796bf7924063ba50ba062a3af083f4 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 22 Apr 2021 06:49:52 +0000 Subject: [PATCH 01/10] Bump undertow-core from 2.2.3.Final to 2.2.7.Final Bumps [undertow-core](https://github.com/undertow-io/undertow) from 2.2.3.Final to 2.2.7.Final. - [Release notes](https://github.com/undertow-io/undertow/releases) - [Commits](https://github.com/undertow-io/undertow/compare/2.2.3.Final...2.2.7.Final) Signed-off-by: dependabot-preview[bot] --- api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/pom.xml b/api/pom.xml index d4a0c461..676d1e56 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -99,7 +99,7 @@ io.undertow undertow-core - 2.2.3.Final + 2.2.7.Final \ No newline at end of file From a8441c12ca7c59f2381b5c2f3026e000ed4b52ad Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 22 Apr 2021 06:57:17 +0000 Subject: [PATCH 02/10] Bump undertow-core from 2.2.3.Final to 2.2.7.Final in /api Bumps [undertow-core](https://github.com/undertow-io/undertow) from 2.2.3.Final to 2.2.7.Final. - [Release notes](https://github.com/undertow-io/undertow/releases) - [Commits](https://github.com/undertow-io/undertow/compare/2.2.3.Final...2.2.7.Final) Signed-off-by: dependabot-preview[bot] --- api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/pom.xml b/api/pom.xml index d4a0c461..676d1e56 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -99,7 +99,7 @@ io.undertow undertow-core - 2.2.3.Final + 2.2.7.Final \ No newline at end of file From 34ec54647397c0495f5dddeb193c75b347cbb351 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sat, 24 Apr 2021 11:10:44 +0200 Subject: [PATCH 03/10] Better CannotGetTimeException and CodecException with public constructors (#343) --- .../xyz/gianlu/librespot/player/codecs/Codec.java | 14 ++++++++++++-- .../gianlu/librespot/player/codecs/Mp3Codec.java | 2 +- .../librespot/player/codecs/VorbisCodec.java | 12 +++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java b/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java index 10c76773..77e41782 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java @@ -121,12 +121,22 @@ public int decryptTimeMs() { } public static class CannotGetTimeException extends Exception { - CannotGetTimeException() { + public CannotGetTimeException(String message) { + super(message); + } + + public CannotGetTimeException(String message, Throwable cause) { + super(message, cause); } } public static class CodecException extends Exception { - CodecException() { + public CodecException(String message) { + super(message); + } + + public CodecException(String message, Throwable cause) { + super(message, cause); } } } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java b/player/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java index 06ac7c78..c4622cb0 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java @@ -84,7 +84,7 @@ public int readInternal(@NotNull OutputStream out) throws IOException { @Override public int time() throws CannotGetTimeException { - throw new CannotGetTimeException(); + throw new CannotGetTimeException("No way to get time on MP3 stream"); } @Override diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java b/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java index 59deb832..ccab8f8a 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java @@ -117,7 +117,7 @@ private void readHeader() throws IOException, CodecException { } if (joggStreamState.pagein(joggPage) == -1) - throw new CodecException(); + throw new CodecException("Failed reading page"); if (joggStreamState.packetout(joggPacket) == -1) throw new HoleInDataException(); @@ -133,7 +133,7 @@ private void readHeader() throws IOException, CodecException { buffer = joggSyncState.data; if (count == 0 && !finished) - throw new CodecException(); + throw new CodecException("Buffer under-run"); } } @@ -153,7 +153,7 @@ public synchronized int readInternal(@NotNull OutputStream out) throws IOExcepti // Read more } else if (result == 1) { if (joggStreamState.pagein(joggPage) == -1) - throw new CodecException(); + throw new CodecException("Failed reading page"); if (joggPage.granulepos() == 0) return -1; @@ -244,8 +244,14 @@ public void close() throws IOException { } private static class NotVorbisException extends CodecException { + NotVorbisException() { + super("Data read is not vorbis data"); + } } private static class HoleInDataException extends CodecException { + HoleInDataException() { + super("Hole in vorbis data"); + } } } From 31641ce3d87e51dfa8a4cf7ecf1aace46bb441fc Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sat, 24 Apr 2021 11:30:04 +0200 Subject: [PATCH 04/10] Added Login5 code (#322) --- .../xyz/gianlu/librespot/core/Login5Api.java | 156 ++++++++++++++++++ .../gianlu/librespot/dealer/ApiClient.java | 6 +- .../librespot/mercury/MercuryRequests.java | 2 +- 3 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 lib/src/main/java/xyz/gianlu/librespot/core/Login5Api.java diff --git a/lib/src/main/java/xyz/gianlu/librespot/core/Login5Api.java b/lib/src/main/java/xyz/gianlu/librespot/core/Login5Api.java new file mode 100644 index 00000000..b0e4db86 --- /dev/null +++ b/lib/src/main/java/xyz/gianlu/librespot/core/Login5Api.java @@ -0,0 +1,156 @@ +/* + * 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.core; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import com.spotify.login5v3.ClientInfoOuterClass; +import com.spotify.login5v3.Hashcash; +import com.spotify.login5v3.Login5; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import xyz.gianlu.librespot.mercury.MercuryRequests; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import static xyz.gianlu.librespot.dealer.ApiClient.protoBody; + +/** + * @author devgianlu + */ +public final class Login5Api { + private final Session session; + + public Login5Api(@NotNull Session session) { + this.session = session; + } + + private static boolean checkTenTrailingBits(byte[] array) { + if (array[array.length - 1] != 0) return false; + else return Integer.numberOfTrailingZeros(array[array.length - 2]) >= 2; + } + + private static void incrementCtr(byte[] ctr, int index) { + ctr[index]++; + if (ctr[index] == 0 && index != 0) + incrementCtr(ctr, index - 1); + } + + @NotNull + private static ChallengeSolve solveHashCash(byte[] prefix, int length, byte[] random) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA1"); + + byte[] suffix = new byte[16]; + System.arraycopy(random, 0, suffix, 0, 8); + + assert length == 10; + + int iters = 0; + while (true) { + md.reset(); + md.update(prefix); + md.update(suffix); + byte[] digest = md.digest(); + if (checkTenTrailingBits(digest)) + return new ChallengeSolve(suffix, iters); + + incrementCtr(suffix, suffix.length - 1); + incrementCtr(suffix, 7); + iters++; + } + } + + @NotNull + private static Login5.LoginRequest.Builder solveChallenge(@NotNull Login5.LoginResponse resp) throws NoSuchAlgorithmException { + byte[] loginContext = resp.getLoginContext().toByteArray(); + + Hashcash.HashcashChallenge hashcash = resp.getChallenges().getChallenges(0).getHashcash(); + + byte[] prefix = hashcash.getPrefix().toByteArray(); + byte[] seed = new byte[8]; + byte[] loginContextDigest = MessageDigest.getInstance("SHA1").digest(loginContext); + System.arraycopy(loginContextDigest, 12, seed, 0, 8); + + long start = System.nanoTime(); + ChallengeSolve solved = solveHashCash(prefix, hashcash.getLength(), seed); + long durationNano = System.nanoTime() - start; + + return Login5.LoginRequest.newBuilder() + .setLoginContext(ByteString.copyFrom(loginContext)) + .setChallengeSolutions(Login5.ChallengeSolutions.newBuilder() + .addSolutions(Login5.ChallengeSolution.newBuilder() + .setHashcash(Hashcash.HashcashSolution.newBuilder() + .setDuration(Duration.newBuilder() + .setSeconds((int) (durationNano / 1_000_000_000)) + .setNanos((int) (durationNano % 1_000_000_000)) + .build()) + .setSuffix(ByteString.copyFrom(solved.suffix)) + .build()).build()).build()); + } + + @NotNull + private Login5.LoginResponse send(@NotNull Login5.LoginRequest msg) throws IOException { + Request.Builder req = new Request.Builder() + .method("POST", protoBody(msg)) + .url("https://login5.spotify.com/v3/login"); + + try (Response resp = session.client().newCall(req.build()).execute()) { + ResponseBody body = resp.body(); + if (body == null) throw new IOException("No body"); + return Login5.LoginResponse.parseFrom(body.bytes()); + } + } + + @NotNull + public Login5.LoginResponse login5(@NotNull Login5.LoginRequest req) throws IOException, NoSuchAlgorithmException { + req = req.toBuilder() + .setClientInfo(ClientInfoOuterClass.ClientInfo.newBuilder() + .setClientId(MercuryRequests.KEYMASTER_CLIENT_ID) + .setDeviceId(session.deviceId()) + .build()) + .build(); + + Login5.LoginResponse resp = send(req); + if (resp.hasChallenges()) { + Login5.LoginRequest.Builder reqq = solveChallenge(resp); + reqq.setClientInfo(req.getClientInfo()) + .setAppleSignInCredential(req.getAppleSignInCredential()) + .setFacebookAccessToken(req.getFacebookAccessToken()) + .setOneTimeToken(req.getOneTimeToken()) + .setPhoneNumber(req.getPhoneNumber()) + .setStoredCredential(req.getStoredCredential()) + .setPassword(req.getPassword()); + resp = send(reqq.build()); + } + + return resp; + } + + private static class ChallengeSolve { + final byte[] suffix; + final int ctr; + + ChallengeSolve(byte[] suffix, int ctr) { + this.suffix = suffix; + this.ctr = ctr; + } + } +} diff --git a/lib/src/main/java/xyz/gianlu/librespot/dealer/ApiClient.java b/lib/src/main/java/xyz/gianlu/librespot/dealer/ApiClient.java index 949282cb..88a1b40d 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/dealer/ApiClient.java +++ b/lib/src/main/java/xyz/gianlu/librespot/dealer/ApiClient.java @@ -37,9 +37,9 @@ import static com.spotify.canvaz.CanvazOuterClass.EntityCanvazResponse; /** - * @author Gianlu + * @author devgianlu */ -public class ApiClient { +public final class ApiClient { private static final Logger LOGGER = LoggerFactory.getLogger(ApiClient.class); private final Session session; private final String baseUrl; @@ -50,7 +50,7 @@ public ApiClient(@NotNull Session session) { } @NotNull - private static RequestBody protoBody(@NotNull Message msg) { + public static RequestBody protoBody(@NotNull Message msg) { return new RequestBody() { @Override public MediaType contentType() { diff --git a/lib/src/main/java/xyz/gianlu/librespot/mercury/MercuryRequests.java b/lib/src/main/java/xyz/gianlu/librespot/mercury/MercuryRequests.java index ea640817..50a69790 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/mercury/MercuryRequests.java +++ b/lib/src/main/java/xyz/gianlu/librespot/mercury/MercuryRequests.java @@ -35,7 +35,7 @@ * @author Gianlu */ public final class MercuryRequests { - private static final String KEYMASTER_CLIENT_ID = "65b708073fc0480ea92a077233ca87bd"; + public static final String KEYMASTER_CLIENT_ID = "65b708073fc0480ea92a077233ca87bd"; private MercuryRequests() { } From 332d334b8c0332827ef6d122a283dce5e7660535 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Sun, 25 Apr 2021 09:23:39 +0200 Subject: [PATCH 05/10] Accept null `outputClassParams` --- .../java/xyz/gianlu/librespot/player/mixing/AudioSink.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java b/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java index a7d4cdef..9ad5ae41 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java @@ -61,7 +61,9 @@ public AudioSink(@NotNull PlayerConfiguration conf, @NotNull Listener listener) if (conf.outputClass == null || conf.outputClass.isEmpty()) throw new IllegalArgumentException("Custom output sink class not configured!"); - output = initCustomOutputSink(conf.outputClass, conf.outputClassParams); + Object[] params = conf.outputClassParams; + if (params == null) params = new Object[0]; + output = initCustomOutputSink(conf.outputClass, params); break; default: throw new IllegalArgumentException("Unknown output: " + conf.output); From a271952fcfcc931fd7206cbf88b7cfd4973f419c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 28 Apr 2021 22:34:19 +0000 Subject: [PATCH 06/10] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..196f0c87 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,45 @@ +version: 2 +updates: +- package-ecosystem: maven + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + ignore: + - dependency-name: com.google.protobuf:protobuf-java + versions: + - 3.15.0 + - 3.15.1 + - 3.15.2 + - 3.15.4 + - 3.15.5 +- package-ecosystem: maven + directory: "/lib" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: maven + directory: "/player" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: maven + directory: "/api" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: maven + directory: "/dacp" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: maven + directory: "/sink" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: maven + directory: "/sink-api" + schedule: + interval: daily + open-pull-requests-limit: 10 From f81b2122c068fa87acc14109e11df56fc19a09f6 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Thu, 29 Apr 2021 15:25:00 +0200 Subject: [PATCH 07/10] Updated README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 001fc354..cca51bc6 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ Its main features are: - Zeroconf (Spotify Connect) - Gapless playback - Mixed playlists (cuepoints and transitions) +- DACP metadata pipe +- Execute commands for various events +- Android compatible (see [librespot-android](https://github.com/devgianlu/librespot-android)) +- Optional HTTP API (see [librespot-api](api)) +- Supports custom sinks and decoders +- Actively developed and up-to-date with the latest internal API ## The library The `lib` module provides all the necessary components and tools to interact with Spotify. More [here](lib). @@ -39,6 +45,7 @@ Snapshots for all variants are available [here](https://oss.sonatype.org/content - [librespot](https://github.com/librespot-org/librespot) - [ansible-role-librespot](https://github.com/xMordax/ansible-role-librespot/tree/master) - Ansible role that will build, install and configure librespot-java. - [spocon](https://github.com/spocon/spocon) - Install librespot-java from APT +- [librespot-android](https://github.com/devgianlu/librespot-android) - Run librespot-java on your Android device # Special thanks - All the developers of [librespot](https://github.com/librespot-org/librespot) which started this project in Rust From 2f1cda489c451c92a8e032ba89b8c65adf126b47 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Thu, 29 Apr 2021 19:06:11 +0200 Subject: [PATCH 08/10] Renamed Codec to Decoder + simplified constructor --- .../librespot/player/FileConfiguration.java | 2 +- .../xyz/gianlu/librespot/player/Player.java | 6 +- .../librespot/player/PlayerConfiguration.java | 2 +- .../{codecs => decoders}/AudioQuality.java | 2 +- .../Codec.java => decoders/Decoder.java} | 22 +++---- .../Codecs.java => decoders/Decoders.java} | 40 ++++++------- .../Mp3Decoder.java} | 11 ++-- .../VorbisDecoder.java} | 17 +++--- .../VorbisOnlyAudioQuality.java | 2 +- .../player/metrics/PlayerMetrics.java | 24 ++++---- .../librespot/player/mixing/AudioSink.java | 4 +- .../librespot/player/mixing/MixingLine.java | 6 +- .../player/playback/PlayerQueueEntry.java | 58 ++++++++++--------- .../player/playback/PlayerSession.java | 8 +-- 14 files changed, 97 insertions(+), 107 deletions(-) rename player/src/main/java/xyz/gianlu/librespot/player/{codecs => decoders}/AudioQuality.java (97%) rename player/src/main/java/xyz/gianlu/librespot/player/{codecs/Codec.java => decoders/Decoder.java} (84%) rename player/src/main/java/xyz/gianlu/librespot/player/{codecs/Codecs.java => decoders/Decoders.java} (50%) rename player/src/main/java/xyz/gianlu/librespot/player/{codecs/Mp3Codec.java => decoders/Mp3Decoder.java} (92%) rename player/src/main/java/xyz/gianlu/librespot/player/{codecs/VorbisCodec.java => decoders/VorbisDecoder.java} (92%) rename player/src/main/java/xyz/gianlu/librespot/player/{codecs => decoders}/VorbisOnlyAudioQuality.java (98%) diff --git a/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java b/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java index 0f803de8..ea127b62 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java @@ -37,7 +37,7 @@ import xyz.gianlu.librespot.common.Utils; import xyz.gianlu.librespot.core.Session; import xyz.gianlu.librespot.core.TimeProvider; -import xyz.gianlu.librespot.player.codecs.AudioQuality; +import xyz.gianlu.librespot.player.decoders.AudioQuality; import java.io.File; import java.io.FileReader; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/Player.java b/player/src/main/java/xyz/gianlu/librespot/player/Player.java index f9445dd5..da67141a 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/Player.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/Player.java @@ -40,8 +40,8 @@ import xyz.gianlu.librespot.metadata.ImageId; import xyz.gianlu.librespot.metadata.PlayableId; import xyz.gianlu.librespot.player.StateWrapper.NextPlayable; -import xyz.gianlu.librespot.player.codecs.Codec; import xyz.gianlu.librespot.player.contexts.AbsSpotifyContext; +import xyz.gianlu.librespot.player.decoders.Decoder; import xyz.gianlu.librespot.player.metrics.NewPlaybackIdEvent; import xyz.gianlu.librespot.player.metrics.NewSessionIdEvent; import xyz.gianlu.librespot.player.metrics.PlaybackMetrics; @@ -577,7 +577,7 @@ private void handlePause() { try { if (playerSession != null) state.setPosition(playerSession.currentTime()); - } catch (Codec.CannotGetTimeException ex) { + } catch (Decoder.CannotGetTimeException ex) { state.setPosition(state.getPosition()); } @@ -820,7 +820,7 @@ public byte[] currentCoverImage() throws IOException { public int time() { try { return playerSession == null ? -1 : playerSession.currentTime(); - } catch (Codec.CannotGetTimeException ex) { + } catch (Decoder.CannotGetTimeException ex) { return -1; } } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java b/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java index 3dec8cb6..68d953dc 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java @@ -18,7 +18,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import xyz.gianlu.librespot.player.codecs.AudioQuality; +import xyz.gianlu.librespot.player.decoders.AudioQuality; import java.io.File; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/AudioQuality.java b/player/src/main/java/xyz/gianlu/librespot/player/decoders/AudioQuality.java similarity index 97% rename from player/src/main/java/xyz/gianlu/librespot/player/codecs/AudioQuality.java rename to player/src/main/java/xyz/gianlu/librespot/player/decoders/AudioQuality.java index b3e46235..0139838c 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/AudioQuality.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/decoders/AudioQuality.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.codecs; +package xyz.gianlu.librespot.player.decoders; import com.spotify.metadata.Metadata.AudioFile; import org.jetbrains.annotations.NotNull; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java b/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java similarity index 84% rename from player/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java rename to player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java index 77e41782..063b1f88 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codec.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java @@ -14,16 +14,13 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.codecs; +package xyz.gianlu.librespot.player.decoders; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.gianlu.librespot.audio.AbsChunkedInputStream; 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 java.io.Closeable; @@ -33,9 +30,9 @@ /** * @author Gianlu */ -public abstract class Codec implements Closeable { +public abstract class Decoder implements Closeable { public static final int BUFFER_SIZE = 2048; - private static final Logger LOGGER = LoggerFactory.getLogger(Codec.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Decoder.class); protected final AbsChunkedInputStream audioIn; protected final float normalizationFactor; protected final int duration; @@ -44,14 +41,11 @@ public abstract class Codec implements Closeable { protected int seekZero = 0; private OutputAudioFormat format; - public Codec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) { + public Decoder(@NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) { this.audioIn = audioFile.stream(); this.audioFile = audioFile; this.duration = duration; - if (conf.enableNormalisation) - this.normalizationFactor = normalizationData != null ? normalizationData.getFactor(conf.normalisationPregain) : 1; - else - this.normalizationFactor = 1; + this.normalizationFactor = normalizationFactor; } public final int writeSomeTo(@NotNull OutputStream out) throws IOException, CodecException { @@ -108,15 +102,15 @@ public final int duration() { return duration; } - public int size() { + public final int size() { return audioIn.size(); } - public int decodedLength() { + public final int decodedLength() { return audioIn.decodedLength(); } - public int decryptTimeMs() { + public final int decryptTimeMs() { return audioFile.decryptTimeMs(); } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codecs.java b/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoders.java similarity index 50% rename from player/src/main/java/xyz/gianlu/librespot/player/codecs/Codecs.java rename to player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoders.java index af655042..ed9affa8 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Codecs.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoders.java @@ -14,63 +14,61 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.codecs; +package xyz.gianlu.librespot.player.decoders; 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>> codecs = new EnumMap<>(SuperAudioFormat.class); - private static final Logger LOGGER = LoggerFactory.getLogger(Codecs.class); +public final class Decoders { + private static final Map>> decoders = new EnumMap<>(SuperAudioFormat.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Decoders.class); static { - registerCodec(SuperAudioFormat.VORBIS, VorbisCodec.class); - registerCodec(SuperAudioFormat.MP3, Mp3Codec.class); + registerDecoder(SuperAudioFormat.VORBIS, VorbisDecoder.class); + registerDecoder(SuperAudioFormat.MP3, Mp3Decoder.class); } - private Codecs() { + private Decoders() { } @Nullable - public static Codec initCodec(@NotNull SuperAudioFormat format, @NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) { - Set> set = codecs.get(format); + public static Decoder initDecoder(@NotNull SuperAudioFormat format, @NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) { + Set> set = decoders.get(format); if (set == null) return null; - Optional> opt = set.stream().findFirst(); + Optional> opt = set.stream().findFirst(); if (!opt.isPresent()) return null; try { - Class clazz = opt.get(); - return clazz.getConstructor(GeneralAudioStream.class, NormalizationData.class, PlayerConfiguration.class, int.class).newInstance(audioFile, normalizationData, conf, duration); + Class clazz = opt.get(); + return clazz.getConstructor(GeneralAudioStream.class, float.class, int.class).newInstance(audioFile, normalizationFactor, duration); } catch (ReflectiveOperationException ex) { LOGGER.error("Failed initializing Codec instance for {}", format, ex); return null; } } - public static void registerCodec(@NotNull SuperAudioFormat format, @NotNull Class clazz) { - codecs.computeIfAbsent(format, (key) -> new HashSet<>(5)).add(clazz); + public static void registerDecoder(@NotNull SuperAudioFormat format, @NotNull Class clazz) { + decoders.computeIfAbsent(format, (key) -> new HashSet<>(5)).add(clazz); } - public static void replaceCodecs(@NotNull SuperAudioFormat format, @NotNull Class clazz) { - Set> set = codecs.get(format); + public static void replaceDecoder(@NotNull SuperAudioFormat format, @NotNull Class clazz) { + Set> set = decoders.get(format); if (set != null) set.clear(); - registerCodec(format, clazz); + registerDecoder(format, clazz); } - public static void unregisterCodec(@NotNull Class clazz) { - for (Set> set : codecs.values()) + public static void unregisterDecoder(@NotNull Class clazz) { + for (Set> set : decoders.values()) set.remove(clazz); } } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java b/player/src/main/java/xyz/gianlu/librespot/player/decoders/Mp3Decoder.java similarity index 92% rename from player/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java rename to player/src/main/java/xyz/gianlu/librespot/player/decoders/Mp3Decoder.java index c4622cb0..64f50776 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/Mp3Codec.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/decoders/Mp3Decoder.java @@ -14,14 +14,11 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.codecs; +package xyz.gianlu.librespot.player.decoders; import javazoom.jl.decoder.*; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; 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 java.io.IOException; @@ -33,12 +30,12 @@ /** * @author Gianlu */ -public final class Mp3Codec extends Codec { +public final class Mp3Decoder extends Decoder { private final byte[] buffer = new byte[2 * BUFFER_SIZE]; private final Mp3InputStream in; - public Mp3Codec(@NotNull GeneralAudioStream audioFile, @Nullable NormalizationData normalizationData, @NotNull PlayerConfiguration conf, int duration) throws IOException, BitstreamException { - super(audioFile, normalizationData, conf, duration); + public Mp3Decoder(@NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) throws IOException, BitstreamException { + super(audioFile, normalizationFactor, duration); skipMp3Tags(audioIn); this.in = new Mp3InputStream(audioIn, normalizationFactor); diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java b/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisDecoder.java similarity index 92% rename from player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java rename to player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisDecoder.java index ccab8f8a..c942ed51 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisCodec.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisDecoder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.codecs; +package xyz.gianlu.librespot.player.decoders; import com.jcraft.jogg.Packet; import com.jcraft.jogg.Page; @@ -25,10 +25,7 @@ import com.jcraft.jorbis.DspState; import com.jcraft.jorbis.Info; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; 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 java.io.IOException; @@ -37,7 +34,7 @@ /** * @author Gianlu */ -public final class VorbisCodec extends Codec { +public final class VorbisDecoder extends Decoder { private static final int CONVERTED_BUFFER_SIZE = BUFFER_SIZE * 2; private final StreamState joggStreamState = new StreamState(); private final DspState jorbisDspState = new DspState(); @@ -56,8 +53,8 @@ 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 { - super(audioFile, normalizationData, conf, duration); + public VorbisDecoder(@NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) throws IOException, CodecException { + super(audioFile, normalizationFactor, duration); this.joggSyncState.init(); this.joggSyncState.buffer(BUFFER_SIZE); @@ -91,8 +88,8 @@ public int time() { /** * Reads the body. All "holes" (-1) in data will stop the playback. * - * @throws Codec.CodecException if a decoding exception occurs - * @throws IOException if an I/O exception occurs + * @throws Decoder.CodecException if a decoding exception occurs + * @throws IOException if an I/O exception occurs */ private void readHeader() throws IOException, CodecException { boolean finished = false; @@ -140,7 +137,7 @@ private void readHeader() throws IOException, CodecException { /** * Reads the body. All "holes" (-1) are skipped, and the playback continues * - * @throws Codec.CodecException if a decoding exception occurs + * @throws Decoder.CodecException if a decoding exception occurs * @throws IOException if an I/O exception occurs */ @Override diff --git a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisOnlyAudioQuality.java b/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisOnlyAudioQuality.java similarity index 98% rename from player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisOnlyAudioQuality.java rename to player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisOnlyAudioQuality.java index 5d466b63..7b085872 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/codecs/VorbisOnlyAudioQuality.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisOnlyAudioQuality.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.codecs; +package xyz.gianlu.librespot.player.decoders; import com.spotify.metadata.Metadata; import org.jetbrains.annotations.NotNull; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java b/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java index 598ac306..322947fe 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java @@ -18,10 +18,10 @@ import org.jetbrains.annotations.Nullable; import xyz.gianlu.librespot.audio.PlayableContentFeeder; -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.crossfade.CrossfadeController; +import xyz.gianlu.librespot.player.decoders.Decoder; +import xyz.gianlu.librespot.player.decoders.Mp3Decoder; +import xyz.gianlu.librespot.player.decoders.VorbisDecoder; import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat; /** @@ -39,21 +39,21 @@ public final class PlayerMetrics { public String transition = "none"; public int decryptTime = 0; - public PlayerMetrics(@Nullable PlayableContentFeeder.Metrics contentMetrics, @Nullable CrossfadeController crossfade, @Nullable Codec codec) { + public PlayerMetrics(@Nullable PlayableContentFeeder.Metrics contentMetrics, @Nullable CrossfadeController crossfade, @Nullable Decoder decoder) { this.contentMetrics = contentMetrics; - if (codec != null) { - size = codec.size(); - duration = codec.duration(); - decodedLength = codec.decodedLength(); - decryptTime = codec.decryptTimeMs(); + if (decoder != null) { + size = decoder.size(); + duration = decoder.duration(); + decodedLength = decoder.decodedLength(); + decryptTime = decoder.decryptTimeMs(); - OutputAudioFormat format = codec.getAudioFormat(); + OutputAudioFormat format = decoder.getAudioFormat(); bitrate = (int) (format.getFrameRate() * format.getFrameSize()); sampleRate = format.getSampleRate(); - if (codec instanceof VorbisCodec) encoding = "vorbis"; - else if (codec instanceof Mp3Codec) encoding = "mp3"; + if (decoder instanceof VorbisDecoder) encoding = "vorbis"; + else if (decoder instanceof Mp3Decoder) encoding = "mp3"; } if (crossfade != null) { diff --git a/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java b/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java index 9ad5ae41..9c6266dd 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/mixing/AudioSink.java @@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable; import xyz.gianlu.librespot.player.Player; import xyz.gianlu.librespot.player.PlayerConfiguration; -import xyz.gianlu.librespot.player.codecs.Codec; +import xyz.gianlu.librespot.player.decoders.Decoder; import xyz.gianlu.librespot.player.mixing.output.*; import java.io.Closeable; @@ -152,7 +152,7 @@ public void close() { @Override public void run() { - byte[] buffer = new byte[Codec.BUFFER_SIZE * 2]; + byte[] buffer = new byte[Decoder.BUFFER_SIZE * 2]; boolean started = false; while (!closed) { diff --git a/player/src/main/java/xyz/gianlu/librespot/player/mixing/MixingLine.java b/player/src/main/java/xyz/gianlu/librespot/player/mixing/MixingLine.java index 7d3dc1c5..6a0316ea 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/mixing/MixingLine.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/mixing/MixingLine.java @@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import xyz.gianlu.librespot.player.codecs.Codec; +import xyz.gianlu.librespot.player.decoders.Decoder; import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat; import java.io.InputStream; @@ -82,7 +82,7 @@ public MixingOutput someOut() { @NotNull public MixingOutput firstOut() { if (fout == null) { - fcb = new GainAwareCircularBuffer(Codec.BUFFER_SIZE * 4); + fcb = new GainAwareCircularBuffer(Decoder.BUFFER_SIZE * 4); fout = new FirstOutputStream(); } @@ -92,7 +92,7 @@ public MixingOutput firstOut() { @NotNull public MixingOutput secondOut() { if (sout == null) { - scb = new GainAwareCircularBuffer(Codec.BUFFER_SIZE * 4); + scb = new GainAwareCircularBuffer(Decoder.BUFFER_SIZE * 4); sout = new SecondOutputStream(); } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java index 2dc2e9fe..1bb048b8 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java @@ -31,10 +31,10 @@ import xyz.gianlu.librespot.metadata.PlayableId; 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.Codecs; -import xyz.gianlu.librespot.player.codecs.VorbisOnlyAudioQuality; import xyz.gianlu.librespot.player.crossfade.CrossfadeController; +import xyz.gianlu.librespot.player.decoders.Decoder; +import xyz.gianlu.librespot.player.decoders.Decoders; +import xyz.gianlu.librespot.player.decoders.VorbisOnlyAudioQuality; import xyz.gianlu.librespot.player.metrics.PlaybackMetrics; import xyz.gianlu.librespot.player.metrics.PlayerMetrics; import xyz.gianlu.librespot.player.mixing.AudioSink; @@ -69,7 +69,7 @@ class PlayerQueueEntry extends PlayerQueue.Entry implements Closeable, Runnable, private final Session session; CrossfadeController crossfade; PlaybackMetrics.Reason endReason = PlaybackMetrics.Reason.END_PLAY; - private Codec codec; + private Decoder decoder; private MetadataWrapper metadata; private volatile boolean closed = false; private volatile MixingLine.MixingOutput output; @@ -104,7 +104,7 @@ PlayerQueueEntry retrySelf(boolean preloaded) { * * @throws PlayableContentFeeder.ContentRestrictedException If the content cannot be retrieved because of restrictions (this condition won't change with a retry). */ - private void load(boolean preload) throws IOException, Codec.CodecException, MercuryClient.MercuryException, CdnManager.CdnException, PlayableContentFeeder.ContentRestrictedException { + private void load(boolean preload) throws IOException, Decoder.CodecException, MercuryClient.MercuryException, CdnManager.CdnException, PlayableContentFeeder.ContentRestrictedException { PlayableContentFeeder.LoadedStream stream; if (playable instanceof LocalId) stream = PlayableContentFeeder.LoadedStream.forLocalFile((LocalId) playable, @@ -130,11 +130,15 @@ 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))); - codec = Codecs.initCodec(stream.in.codec(), stream.in, stream.normalizationData, conf, metadata.duration()); - if (codec == null) + float normalizationFactor; + if (stream.normalizationData == null || !conf.enableNormalisation) normalizationFactor = 1; + else normalizationFactor = stream.normalizationData.getFactor(conf.normalisationPregain); + + decoder = Decoders.initDecoder(stream.in.codec(), stream.in, normalizationFactor, metadata.duration()); + if (decoder == null) throw new UnsupportedEncodingException(stream.in.codec().toString()); - LOGGER.trace("Loaded {} codec. {of: {}, format: {}, playbackId: {}}", stream.in.codec(), stream.in.describe(), codec.getAudioFormat(), playbackId); + LOGGER.trace("Loaded {} codec. {of: {}, format: {}, playbackId: {}}", stream.in.codec(), stream.in.describe(), decoder.getAudioFormat(), playbackId); } /** @@ -154,17 +158,17 @@ public MetadataWrapper metadata() { */ @NotNull PlayerMetrics metrics() { - return new PlayerMetrics(contentMetrics, crossfade, codec); + return new PlayerMetrics(contentMetrics, crossfade, decoder); } /** * Returns the current position. * * @return The current position of the player or {@code -1} if not ready. - * @throws Codec.CannotGetTimeException If the time is unavailable for the codec being used. + * @throws Decoder.CannotGetTimeException If the time is unavailable for the codec being used. */ - int getTime() throws Codec.CannotGetTimeException { - return codec == null ? -1 : codec.time(); + int getTime() throws Decoder.CannotGetTimeException { + return decoder == null ? -1 : decoder.time(); } /** @@ -176,7 +180,7 @@ int getTime() throws Codec.CannotGetTimeException { int getTimeNoThrow() { try { return getTime(); - } catch (Codec.CannotGetTimeException e) { + } catch (Decoder.CannotGetTimeException e) { return -1; } } @@ -241,14 +245,14 @@ public boolean hasOutput() { * @param when The time in milliseconds */ void notifyInstant(int callbackId, int when) { - if (codec != null) { + if (decoder != null) { try { - int time = codec.time(); + int time = decoder.time(); if (time >= when) { listener.instantReached(this, callbackId, time); return; } - } catch (Codec.CannotGetTimeException ex) { + } catch (Decoder.CannotGetTimeException ex) { return; } } @@ -262,7 +266,7 @@ public void run() { try { load(preloaded); - } catch (IOException | PlayableContentFeeder.ContentRestrictedException | CdnManager.CdnException | MercuryClient.MercuryException | Codec.CodecException ex) { + } catch (IOException | PlayableContentFeeder.ContentRestrictedException | CdnManager.CdnException | MercuryClient.MercuryException | Decoder.CodecException ex) { close(); listener.loadingError(this, ex, retried); LOGGER.trace("{} terminated at loading.", this, ex); @@ -270,7 +274,7 @@ public void run() { } if (seekTime != -1) { - codec.seek(seekTime); + decoder.seek(seekTime); seekTime = -1; } @@ -291,38 +295,38 @@ public void run() { } if (closed) break; - output.toggle(true, codec.getAudioFormat()); + output.toggle(true, decoder.getAudioFormat()); if (seekTime != -1) { - codec.seek(seekTime); + decoder.seek(seekTime); seekTime = -1; } if (canGetTime) { try { - int time = codec.time(); + int time = decoder.time(); if (!notifyInstants.isEmpty()) checkInstants(time); if (output == null) continue; output.gain(crossfade.getGain(time)); - } catch (Codec.CannotGetTimeException ex) { + } catch (Decoder.CannotGetTimeException ex) { canGetTime = false; } } try { - if (codec.writeSomeTo(output) == -1) { + if (decoder.writeSomeTo(output) == -1) { try { - int time = codec.time(); + int time = decoder.time(); LOGGER.debug("Player time offset is {}. {id: {}}", metadata.duration() - time, playbackId); - } catch (Codec.CannotGetTimeException ignored) { + } catch (Decoder.CannotGetTimeException ignored) { } close(); break; } - } catch (IOException | Codec.CodecException ex) { + } catch (IOException | Decoder.CodecException ex) { if (!closed) { close(); listener.playbackError(this, ex); @@ -367,7 +371,7 @@ public void close() { clearOutput(); try { - if (codec != null) codec.close(); + if (decoder != null) decoder.close(); } catch (IOException ignored) { } } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerSession.java b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerSession.java index c7ccf48e..27c54db0 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerSession.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerSession.java @@ -27,8 +27,8 @@ import xyz.gianlu.librespot.core.Session; import xyz.gianlu.librespot.metadata.PlayableId; import xyz.gianlu.librespot.player.PlayerConfiguration; -import xyz.gianlu.librespot.player.codecs.Codec; import xyz.gianlu.librespot.player.crossfade.CrossfadeController; +import xyz.gianlu.librespot.player.decoders.Decoder; import xyz.gianlu.librespot.player.metrics.PlaybackMetrics.Reason; import xyz.gianlu.librespot.player.metrics.PlayerMetrics; import xyz.gianlu.librespot.player.mixing.AudioSink; @@ -258,7 +258,7 @@ public void playbackResumed(@NotNull PlayerQueueEntry entry, int chunk, int diff try { int time = head.prev.getTime(); head.prev.notifyInstant(PlayerQueueEntry.INSTANT_END, ((CrossfadeController.PartialFadeInterval) fadeOut).end(time)); - } catch (Codec.CannotGetTimeException ex) { + } catch (Decoder.CannotGetTimeException ex) { head.prev.close(); } } else { @@ -338,9 +338,9 @@ public MetadataWrapper currentMetadata() { /** * @return The time for the current head or {@code -1} if not available. - * @throws Codec.CannotGetTimeException If the head is available, but time cannot be retrieved + * @throws Decoder.CannotGetTimeException If the head is available, but time cannot be retrieved */ - public int currentTime() throws Codec.CannotGetTimeException { + public int currentTime() throws Decoder.CannotGetTimeException { if (queue.head() == null) return -1; else return queue.head().getTime(); } From 77a558475d860bd5c672bc58dac7baeb17e219c6 Mon Sep 17 00:00:00 2001 From: Gianlu Date: Thu, 29 Apr 2021 19:42:31 +0200 Subject: [PATCH 09/10] Refactored Decoder into librespot-decoder-api --- decoder-api/pom.xml | 47 +++++++++++++++++++ .../librespot/player/decoders/Decoder.java | 20 ++------ .../player/decoders/SeekableInputStream.java | 39 +++++++++++++++ lib/pom.xml | 18 +++++++ .../audio/AbsChunkedInputStream.java | 11 +++-- ...dioStream.java => DecodedAudioStream.java} | 4 +- .../audio/PlayableContentFeeder.java | 10 ++-- .../librespot/audio/cdn/CdnManager.java | 2 +- .../audio}/decoders/AudioQuality.java | 2 +- .../librespot/audio}/decoders/Decoders.java | 9 ++-- .../librespot/audio}/decoders/Mp3Decoder.java | 12 ++--- .../audio}/decoders/VorbisDecoder.java | 27 ++++++----- .../decoders/VorbisOnlyAudioQuality.java | 2 +- .../audio/storage/AudioFileStreaming.java | 6 +-- player/pom.xml | 12 ----- .../librespot/player/FileConfiguration.java | 2 +- .../librespot/player/PlayerConfiguration.java | 2 +- .../player/metrics/PlayerMetrics.java | 26 +++++++--- .../player/playback/PlayerQueueEntry.java | 11 +++-- pom.xml | 1 + 20 files changed, 182 insertions(+), 81 deletions(-) create mode 100644 decoder-api/pom.xml rename {player => decoder-api}/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java (86%) create mode 100644 decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/SeekableInputStream.java rename lib/src/main/java/xyz/gianlu/librespot/audio/{GeneralAudioStream.java => DecodedAudioStream.java} (93%) rename {player/src/main/java/xyz/gianlu/librespot/player => lib/src/main/java/xyz/gianlu/librespot/audio}/decoders/AudioQuality.java (97%) rename {player/src/main/java/xyz/gianlu/librespot/player => lib/src/main/java/xyz/gianlu/librespot/audio}/decoders/Decoders.java (86%) rename {player/src/main/java/xyz/gianlu/librespot/player => lib/src/main/java/xyz/gianlu/librespot/audio}/decoders/Mp3Decoder.java (93%) rename {player/src/main/java/xyz/gianlu/librespot/player => lib/src/main/java/xyz/gianlu/librespot/audio}/decoders/VorbisDecoder.java (88%) rename {player/src/main/java/xyz/gianlu/librespot/player => lib/src/main/java/xyz/gianlu/librespot/audio}/decoders/VorbisOnlyAudioQuality.java (98%) diff --git a/decoder-api/pom.xml b/decoder-api/pom.xml new file mode 100644 index 00000000..2faf2712 --- /dev/null +++ b/decoder-api/pom.xml @@ -0,0 +1,47 @@ + + + + 4.0.0 + + + xyz.gianlu.librespot + librespot-java + 1.5.6-SNAPSHOT + ../pom.xml + + + librespot-decoder-api + jar + + librespot-java decoder API + + + + xyz.gianlu.librespot + librespot-sink-api + ${project.version} + + + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + \ No newline at end of file diff --git a/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java b/decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java similarity index 86% rename from player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java rename to decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java index 063b1f88..6f5efcd1 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java +++ b/decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/Decoder.java @@ -19,8 +19,6 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import xyz.gianlu.librespot.audio.AbsChunkedInputStream; -import xyz.gianlu.librespot.audio.GeneralAudioStream; import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat; import java.io.Closeable; @@ -28,22 +26,20 @@ import java.io.OutputStream; /** - * @author Gianlu + * @author devgianlu */ public abstract class Decoder implements Closeable { public static final int BUFFER_SIZE = 2048; private static final Logger LOGGER = LoggerFactory.getLogger(Decoder.class); - protected final AbsChunkedInputStream audioIn; + protected final SeekableInputStream audioIn; protected final float normalizationFactor; protected final int duration; - private final GeneralAudioStream audioFile; protected volatile boolean closed = false; protected int seekZero = 0; private OutputAudioFormat format; - public Decoder(@NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) { - this.audioIn = audioFile.stream(); - this.audioFile = audioFile; + public Decoder(@NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) { + this.audioIn = audioIn; this.duration = duration; this.normalizationFactor = normalizationFactor; } @@ -106,14 +102,6 @@ public final int size() { return audioIn.size(); } - public final int decodedLength() { - return audioIn.decodedLength(); - } - - public final int decryptTimeMs() { - return audioFile.decryptTimeMs(); - } - public static class CannotGetTimeException extends Exception { public CannotGetTimeException(String message) { super(message); diff --git a/decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/SeekableInputStream.java b/decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/SeekableInputStream.java new file mode 100644 index 00000000..32996667 --- /dev/null +++ b/decoder-api/src/main/java/xyz/gianlu/librespot/player/decoders/SeekableInputStream.java @@ -0,0 +1,39 @@ +/* + * 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.decoders; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author devgianlu + */ +public abstract class SeekableInputStream extends InputStream { + public abstract int size(); + + public abstract int position(); + + public abstract void seek(int seekZero) throws IOException; + + public abstract long skip(long skip) throws IOException; + + public abstract int read(byte[] buffer, int index, int length) throws IOException; + + public abstract void close(); + + public abstract int decodedLength(); +} diff --git a/lib/pom.xml b/lib/pom.xml index 890ce277..b8dedaea 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -82,6 +82,24 @@ + + xyz.gianlu.librespot + librespot-decoder-api + ${project.version} + + + + + org.jcraft + jorbis + 0.0.17 + + + com.badlogicgames.jlayer + jlayer + 1.0.2-gdx + + com.google.protobuf diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/AbsChunkedInputStream.java b/lib/src/main/java/xyz/gianlu/librespot/audio/AbsChunkedInputStream.java index 6f2b4e7b..7c870d3e 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/AbsChunkedInputStream.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/AbsChunkedInputStream.java @@ -17,16 +17,16 @@ package xyz.gianlu.librespot.audio; import org.jetbrains.annotations.NotNull; +import xyz.gianlu.librespot.player.decoders.SeekableInputStream; import java.io.IOException; -import java.io.InputStream; import static xyz.gianlu.librespot.audio.storage.ChannelManager.CHUNK_SIZE; /** - * @author Gianlu + * @author devgianlu */ -public abstract class AbsChunkedInputStream extends InputStream implements HaltListener { +public abstract class AbsChunkedInputStream extends SeekableInputStream implements HaltListener { private static final int PRELOAD_AHEAD = 3; private static final int PRELOAD_CHUNK_RETRIES = 2; private static final int MAX_CHUNK_TRIES = 128; @@ -82,10 +82,12 @@ public final synchronized void reset() { pos = mark; } - public final synchronized int pos() { + @Override + public final synchronized int position() { return pos; } + @Override public final synchronized void seek(int where) throws IOException { if (where < 0) throw new IllegalArgumentException(); if (closed) throw new IOException("Stream is closed!"); @@ -260,6 +262,7 @@ public final void notifyChunkError(int index, @NotNull ChunkException ex) { } } + @Override public int decodedLength() { return decodedLength; } diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/GeneralAudioStream.java b/lib/src/main/java/xyz/gianlu/librespot/audio/DecodedAudioStream.java similarity index 93% rename from lib/src/main/java/xyz/gianlu/librespot/audio/GeneralAudioStream.java rename to lib/src/main/java/xyz/gianlu/librespot/audio/DecodedAudioStream.java index b0c3e10b..dd8d5e4a 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/GeneralAudioStream.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/DecodedAudioStream.java @@ -21,9 +21,9 @@ /** - * @author Gianlu + * @author devgianlu */ -public interface GeneralAudioStream { +public interface DecodedAudioStream { @NotNull AbsChunkedInputStream stream(); diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/PlayableContentFeeder.java b/lib/src/main/java/xyz/gianlu/librespot/audio/PlayableContentFeeder.java index bc092217..46406cba 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/PlayableContentFeeder.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/PlayableContentFeeder.java @@ -183,7 +183,7 @@ private LoadedStream loadEpisode(@NotNull EpisodeId id, @NotNull AudioQualityPic } } - private static class FileAudioStream implements GeneralAudioStream { + private static class FileAudioStream implements DecodedAudioStream { private static final Logger LOGGER = LoggerFactory.getLogger(FileAudioStream.class); private final File file; private final RandomAccessFile raf; @@ -276,25 +276,25 @@ public int decryptTimeMs() { public static class LoadedStream { public final MetadataWrapper metadata; - public final GeneralAudioStream in; + public final DecodedAudioStream in; public final NormalizationData normalizationData; public final Metrics metrics; - public LoadedStream(@NotNull Metadata.Track track, @NotNull GeneralAudioStream in, @Nullable NormalizationData normalizationData, @NotNull Metrics metrics) { + public LoadedStream(@NotNull Metadata.Track track, @NotNull DecodedAudioStream in, @Nullable NormalizationData normalizationData, @NotNull Metrics metrics) { this.metadata = new MetadataWrapper(track, null, null); this.in = in; this.normalizationData = normalizationData; this.metrics = metrics; } - public LoadedStream(@NotNull Metadata.Episode episode, @NotNull GeneralAudioStream in, @Nullable NormalizationData normalizationData, @NotNull Metrics metrics) { + public LoadedStream(@NotNull Metadata.Episode episode, @NotNull DecodedAudioStream in, @Nullable NormalizationData normalizationData, @NotNull Metrics metrics) { this.metadata = new MetadataWrapper(null, episode, null); this.in = in; this.normalizationData = normalizationData; this.metrics = metrics; } - private LoadedStream(@NotNull LocalId id, @NotNull GeneralAudioStream in) { + private LoadedStream(@NotNull LocalId id, @NotNull DecodedAudioStream in) { this.metadata = new MetadataWrapper(null, null, id); this.in = in; this.normalizationData = null; diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java index 476ba4f0..4904b3c6 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/cdn/CdnManager.java @@ -195,7 +195,7 @@ void setUrl(@NotNull HttpUrl url) { } } - public class Streamer implements GeneralAudioStream, GeneralWritableStream { + public class Streamer implements DecodedAudioStream, GeneralWritableStream { private final StreamId streamId; private final ExecutorService executorService = Executors.newCachedThreadPool(new NameThreadFactory((r) -> "cdn-async-" + r.hashCode())); private final SuperAudioFormat format; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/decoders/AudioQuality.java b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/AudioQuality.java similarity index 97% rename from player/src/main/java/xyz/gianlu/librespot/player/decoders/AudioQuality.java rename to lib/src/main/java/xyz/gianlu/librespot/audio/decoders/AudioQuality.java index 0139838c..aa8e020a 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/decoders/AudioQuality.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/AudioQuality.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.decoders; +package xyz.gianlu.librespot.audio.decoders; import com.spotify.metadata.Metadata.AudioFile; import org.jetbrains.annotations.NotNull; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoders.java b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Decoders.java similarity index 86% rename from player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoders.java rename to lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Decoders.java index ed9affa8..cf70e6a7 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/decoders/Decoders.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Decoders.java @@ -14,14 +14,15 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.decoders; +package xyz.gianlu.librespot.audio.decoders; 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.format.SuperAudioFormat; +import xyz.gianlu.librespot.player.decoders.Decoder; +import xyz.gianlu.librespot.player.decoders.SeekableInputStream; import java.util.*; @@ -41,7 +42,7 @@ private Decoders() { } @Nullable - public static Decoder initDecoder(@NotNull SuperAudioFormat format, @NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) { + public static Decoder initDecoder(@NotNull SuperAudioFormat format, @NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) { Set> set = decoders.get(format); if (set == null) return null; @@ -50,7 +51,7 @@ public static Decoder initDecoder(@NotNull SuperAudioFormat format, @NotNull Gen try { Class clazz = opt.get(); - return clazz.getConstructor(GeneralAudioStream.class, float.class, int.class).newInstance(audioFile, normalizationFactor, duration); + return clazz.getConstructor(SeekableInputStream.class, float.class, int.class).newInstance(audioIn, normalizationFactor, duration); } catch (ReflectiveOperationException ex) { LOGGER.error("Failed initializing Codec instance for {}", format, ex); return null; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/decoders/Mp3Decoder.java b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Mp3Decoder.java similarity index 93% rename from player/src/main/java/xyz/gianlu/librespot/player/decoders/Mp3Decoder.java rename to lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Mp3Decoder.java index 64f50776..9c552934 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/decoders/Mp3Decoder.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/Mp3Decoder.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.decoders; +package xyz.gianlu.librespot.audio.decoders; import javazoom.jl.decoder.*; import org.jetbrains.annotations.NotNull; -import xyz.gianlu.librespot.audio.GeneralAudioStream; +import xyz.gianlu.librespot.player.decoders.Decoder; +import xyz.gianlu.librespot.player.decoders.SeekableInputStream; import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat; import java.io.IOException; @@ -31,17 +32,16 @@ * @author Gianlu */ public final class Mp3Decoder extends Decoder { - private final byte[] buffer = new byte[2 * BUFFER_SIZE]; + private final byte[] buffer = new byte[2 * Decoder.BUFFER_SIZE]; private final Mp3InputStream in; - public Mp3Decoder(@NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) throws IOException, BitstreamException { - super(audioFile, normalizationFactor, duration); + public Mp3Decoder(@NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) throws IOException, BitstreamException { + super(audioIn, normalizationFactor, duration); skipMp3Tags(audioIn); this.in = new Mp3InputStream(audioIn, normalizationFactor); audioIn.mark(-1); - setAudioFormat(new OutputAudioFormat(in.getSampleRate(), 16, in.getChannels(), true, false)); } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisDecoder.java b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/VorbisDecoder.java similarity index 88% rename from player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisDecoder.java rename to lib/src/main/java/xyz/gianlu/librespot/audio/decoders/VorbisDecoder.java index c942ed51..2f574a26 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisDecoder.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/VorbisDecoder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.decoders; +package xyz.gianlu.librespot.audio.decoders; import com.jcraft.jogg.Packet; import com.jcraft.jogg.Page; @@ -25,7 +25,8 @@ import com.jcraft.jorbis.DspState; import com.jcraft.jorbis.Info; import org.jetbrains.annotations.NotNull; -import xyz.gianlu.librespot.audio.GeneralAudioStream; +import xyz.gianlu.librespot.player.decoders.Decoder; +import xyz.gianlu.librespot.player.decoders.SeekableInputStream; import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat; import java.io.IOException; @@ -35,7 +36,7 @@ * @author Gianlu */ public final class VorbisDecoder extends Decoder { - private static final int CONVERTED_BUFFER_SIZE = BUFFER_SIZE * 2; + private static final int CONVERTED_BUFFER_SIZE = Decoder.BUFFER_SIZE * 2; private final StreamState joggStreamState = new StreamState(); private final DspState jorbisDspState = new DspState(); private final Block jorbisBlock = new Block(jorbisDspState); @@ -53,15 +54,15 @@ public final class VorbisDecoder extends Decoder { private int index; private long pcm_offset; - public VorbisDecoder(@NotNull GeneralAudioStream audioFile, float normalizationFactor, int duration) throws IOException, CodecException { - super(audioFile, normalizationFactor, duration); + public VorbisDecoder(@NotNull SeekableInputStream audioIn, float normalizationFactor, int duration) throws IOException, CodecException { + super(audioIn, normalizationFactor, duration); this.joggSyncState.init(); - this.joggSyncState.buffer(BUFFER_SIZE); + this.joggSyncState.buffer(Decoder.BUFFER_SIZE); this.buffer = joggSyncState.data; readHeader(); - seekZero = audioIn.pos(); + seekZero = audioIn.position(); convertedBuffer = new byte[CONVERTED_BUFFER_SIZE]; @@ -96,7 +97,7 @@ private void readHeader() throws IOException, CodecException { int packet = 1; while (!finished) { - count = audioIn.read(buffer, index, BUFFER_SIZE); + count = audioIn.read(buffer, index, Decoder.BUFFER_SIZE); joggSyncState.wrote(count); int result = joggSyncState.pageout(joggPage); @@ -126,7 +127,7 @@ private void readHeader() throws IOException, CodecException { else packet++; } - index = joggSyncState.buffer(BUFFER_SIZE); + index = joggSyncState.buffer(Decoder.BUFFER_SIZE); buffer = joggSyncState.data; if (count == 0 && !finished) @@ -138,7 +139,7 @@ private void readHeader() throws IOException, CodecException { * Reads the body. All "holes" (-1) are skipped, and the playback continues * * @throws Decoder.CodecException if a decoding exception occurs - * @throws IOException if an I/O exception occurs + * @throws IOException if an I/O exception occurs */ @Override public synchronized int readInternal(@NotNull OutputStream out) throws IOException, CodecException { @@ -172,11 +173,11 @@ public synchronized int readInternal(@NotNull OutputStream out) throws IOExcepti return -1; } - index = joggSyncState.buffer(BUFFER_SIZE); + index = joggSyncState.buffer(Decoder.BUFFER_SIZE); buffer = joggSyncState.data; if (index == -1) return -1; - count = audioIn.read(buffer, index, BUFFER_SIZE); + count = audioIn.read(buffer, index, Decoder.BUFFER_SIZE); joggSyncState.wrote(count); if (count == 0) return -1; @@ -219,7 +220,7 @@ private int decodeCurrentPacket(@NotNull OutputStream out) throws IOException { long granulepos = joggPacket.granulepos; if (granulepos != -1 && joggPacket.e_o_s == 0) { granulepos -= samples; - granulepos -= (long) BUFFER_SIZE * 6 * sampleSizeBytes(); // Account for buffer between the decoder and the player + granulepos -= (long) Decoder.BUFFER_SIZE * 6 * sampleSizeBytes(); // Account for buffer between the decoder and the player pcm_offset = granulepos; } } diff --git a/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisOnlyAudioQuality.java b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/VorbisOnlyAudioQuality.java similarity index 98% rename from player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisOnlyAudioQuality.java rename to lib/src/main/java/xyz/gianlu/librespot/audio/decoders/VorbisOnlyAudioQuality.java index 7b085872..c740befa 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/decoders/VorbisOnlyAudioQuality.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/decoders/VorbisOnlyAudioQuality.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package xyz.gianlu.librespot.player.decoders; +package xyz.gianlu.librespot.audio.decoders; import com.spotify.metadata.Metadata; import org.jetbrains.annotations.NotNull; diff --git a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java index 5305ea6f..7f318870 100644 --- a/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java +++ b/lib/src/main/java/xyz/gianlu/librespot/audio/storage/AudioFileStreaming.java @@ -23,7 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.gianlu.librespot.audio.AbsChunkedInputStream; -import xyz.gianlu.librespot.audio.GeneralAudioStream; +import xyz.gianlu.librespot.audio.DecodedAudioStream; import xyz.gianlu.librespot.audio.HaltListener; import xyz.gianlu.librespot.audio.decrypt.AesAudioDecrypt; import xyz.gianlu.librespot.audio.decrypt.AudioDecrypt; @@ -41,9 +41,9 @@ import java.util.concurrent.Executors; /** - * @author Gianlu + * @author devgianlu */ -public class AudioFileStreaming implements AudioFile, GeneralAudioStream { +public class AudioFileStreaming implements AudioFile, DecodedAudioStream { private static final Logger LOGGER = LoggerFactory.getLogger(AudioFileStreaming.class); private final CacheManager.Handler cacheHandler; private final Metadata.AudioFile file; diff --git a/player/pom.xml b/player/pom.xml index 359d8965..09376599 100644 --- a/player/pom.xml +++ b/player/pom.xml @@ -100,18 +100,6 @@ ${project.version} - - - org.jcraft - jorbis - 0.0.17 - - - com.badlogicgames.jlayer - jlayer - 1.0.2-gdx - - org.slf4j diff --git a/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java b/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java index ea127b62..c7123ece 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/FileConfiguration.java @@ -34,10 +34,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.gianlu.librespot.ZeroconfServer; +import xyz.gianlu.librespot.audio.decoders.AudioQuality; import xyz.gianlu.librespot.common.Utils; import xyz.gianlu.librespot.core.Session; import xyz.gianlu.librespot.core.TimeProvider; -import xyz.gianlu.librespot.player.decoders.AudioQuality; import java.io.File; import java.io.FileReader; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java b/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java index 68d953dc..b8379379 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/PlayerConfiguration.java @@ -18,7 +18,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import xyz.gianlu.librespot.player.decoders.AudioQuality; +import xyz.gianlu.librespot.audio.decoders.AudioQuality; import java.io.File; diff --git a/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java b/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java index 322947fe..4e19eb11 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/metrics/PlayerMetrics.java @@ -17,11 +17,10 @@ package xyz.gianlu.librespot.player.metrics; import org.jetbrains.annotations.Nullable; +import xyz.gianlu.librespot.audio.DecodedAudioStream; import xyz.gianlu.librespot.audio.PlayableContentFeeder; import xyz.gianlu.librespot.player.crossfade.CrossfadeController; import xyz.gianlu.librespot.player.decoders.Decoder; -import xyz.gianlu.librespot.player.decoders.Mp3Decoder; -import xyz.gianlu.librespot.player.decoders.VorbisDecoder; import xyz.gianlu.librespot.player.mixing.output.OutputAudioFormat; /** @@ -39,21 +38,34 @@ public final class PlayerMetrics { public String transition = "none"; public int decryptTime = 0; - public PlayerMetrics(@Nullable PlayableContentFeeder.Metrics contentMetrics, @Nullable CrossfadeController crossfade, @Nullable Decoder decoder) { + public PlayerMetrics(@Nullable PlayableContentFeeder.Metrics contentMetrics, @Nullable CrossfadeController crossfade, + @Nullable DecodedAudioStream stream, @Nullable Decoder decoder) { this.contentMetrics = contentMetrics; if (decoder != null) { size = decoder.size(); duration = decoder.duration(); - decodedLength = decoder.decodedLength(); - decryptTime = decoder.decryptTimeMs(); OutputAudioFormat format = decoder.getAudioFormat(); bitrate = (int) (format.getFrameRate() * format.getFrameSize()); sampleRate = format.getSampleRate(); + } + + if (stream != null) { + decryptTime = stream.decryptTimeMs(); + decodedLength = stream.stream().decodedLength(); - if (decoder instanceof VorbisDecoder) encoding = "vorbis"; - else if (decoder instanceof Mp3Decoder) encoding = "mp3"; + switch (stream.codec()) { + case MP3: + encoding = "mp3"; + break; + case VORBIS: + encoding = "vorbis"; + break; + case AAC: + encoding = "aac"; + break; + } } if (crossfade != null) { diff --git a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java index 1bb048b8..0d1d2326 100644 --- a/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java +++ b/player/src/main/java/xyz/gianlu/librespot/player/playback/PlayerQueueEntry.java @@ -20,10 +20,13 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import xyz.gianlu.librespot.audio.DecodedAudioStream; import xyz.gianlu.librespot.audio.HaltListener; import xyz.gianlu.librespot.audio.MetadataWrapper; import xyz.gianlu.librespot.audio.PlayableContentFeeder; import xyz.gianlu.librespot.audio.cdn.CdnManager; +import xyz.gianlu.librespot.audio.decoders.Decoders; +import xyz.gianlu.librespot.audio.decoders.VorbisOnlyAudioQuality; import xyz.gianlu.librespot.common.Utils; import xyz.gianlu.librespot.core.Session; import xyz.gianlu.librespot.mercury.MercuryClient; @@ -33,8 +36,6 @@ import xyz.gianlu.librespot.player.StateWrapper; import xyz.gianlu.librespot.player.crossfade.CrossfadeController; import xyz.gianlu.librespot.player.decoders.Decoder; -import xyz.gianlu.librespot.player.decoders.Decoders; -import xyz.gianlu.librespot.player.decoders.VorbisOnlyAudioQuality; import xyz.gianlu.librespot.player.metrics.PlaybackMetrics; import xyz.gianlu.librespot.player.metrics.PlayerMetrics; import xyz.gianlu.librespot.player.mixing.AudioSink; @@ -70,6 +71,7 @@ class PlayerQueueEntry extends PlayerQueue.Entry implements Closeable, Runnable, CrossfadeController crossfade; PlaybackMetrics.Reason endReason = PlaybackMetrics.Reason.END_PLAY; private Decoder decoder; + private DecodedAudioStream audioStream; private MetadataWrapper metadata; private volatile boolean closed = false; private volatile MixingLine.MixingOutput output; @@ -114,6 +116,7 @@ private void load(boolean preload) throws IOException, Decoder.CodecException, M metadata = stream.metadata; contentMetrics = stream.metrics; + audioStream = stream.in; if (metadata.isEpisode() && metadata.episode != null) { LOGGER.info("Loaded episode. {name: '{}', duration: {}, uri: {}, id: {}}", metadata.episode.getName(), @@ -134,7 +137,7 @@ private void load(boolean preload) throws IOException, Decoder.CodecException, M if (stream.normalizationData == null || !conf.enableNormalisation) normalizationFactor = 1; else normalizationFactor = stream.normalizationData.getFactor(conf.normalisationPregain); - decoder = Decoders.initDecoder(stream.in.codec(), stream.in, normalizationFactor, metadata.duration()); + decoder = Decoders.initDecoder(stream.in.codec(), stream.in.stream(), normalizationFactor, metadata.duration()); if (decoder == null) throw new UnsupportedEncodingException(stream.in.codec().toString()); @@ -158,7 +161,7 @@ public MetadataWrapper metadata() { */ @NotNull PlayerMetrics metrics() { - return new PlayerMetrics(contentMetrics, crossfade, decoder); + return new PlayerMetrics(contentMetrics, crossfade, audioStream, decoder); } /** diff --git a/pom.xml b/pom.xml index d4849f13..24ea4253 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,7 @@ sink-api sink + decoder-api dacp lib player From 55337b6a55b90fb5100fc84d45657b12992df981 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Apr 2021 08:09:56 +0000 Subject: [PATCH 10/10] Bump disruptor from 3.4.3 to 3.4.4 Bumps [disruptor](https://github.com/LMAX-Exchange/disruptor) from 3.4.3 to 3.4.4. - [Release notes](https://github.com/LMAX-Exchange/disruptor/releases) - [Changelog](https://github.com/LMAX-Exchange/disruptor/blob/master/CHANGELOG.md) - [Commits](https://github.com/LMAX-Exchange/disruptor/compare/3.4.3...3.4.4) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24ea4253..2d4d83f0 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ 3.15.8 1.7.30 2.14.1 - 3.4.3 + 3.4.4