diff --git a/CHANGELOG.md b/CHANGELOG.md
index faeaa959..cab64ff8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
# Change Log
+## [2.0.3] -- 2023-05-11
+
+- Updated `IDENTITY_REGEX` in https://github.com/lavalink-devs/lavaplayer/pull/44
+
+## [2.0.2] -- 2023-27-09
+
+- Fix a bug with MPEG parsing that would lead to range exceptions in https://github.com/lavalink-devs/lavaplayer/pull/31
+- Specify all request timeouts in https://github.com/lavalink-devs/lavaplayer/pull/33
+
+## [2.0.1] -- 2023-14-08
+
+### Added
+
+- Support MPEG 2.5 by [@markozajc](https://github.com/markozajc) in https://github.com/lavalink-devs/lavaplayer/pull/30
+
## [2.0.0] -- 2023-03-08
### Fixed
diff --git a/README.md b/README.md
index 6a53d7fb..181811e9 100644
--- a/README.md
+++ b/README.md
@@ -18,13 +18,14 @@ number: [![Maven Central](https://img.shields.io/maven-central/v/dev.arbjerg/lav
* Artifact: **dev.arbjerg:lavaplayer:x.y.z**
Snapshots are published
-to https://maven.arbjerg.dev/snapshots & https://s01.oss.sonatype.org/content/repositories/snapshots
+to https://maven.lavalink.dev/snapshots & https://s01.oss.sonatype.org/content/repositories/snapshots
Using in Gradle:
```gradle
repositories {
mavenCentral()
+ maven { url "https://jitpack.io" } // For com.github.walkyst.JAADec-fork:jaadec-ext-aac & ibxm-fork:com.github.walkyst:ibxm-fork
}
dependencies {
@@ -35,6 +36,13 @@ dependencies {
Using in Maven:
```xml
+
+
+ jitpack
+ https://jitpack.io
+
+
+
dev.arbjerg
diff --git a/build.gradle.kts b/build.gradle.kts
index e5d364c9..62a10470 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -39,8 +39,8 @@ subprojects {
configure {
if (findProperty("MAVEN_PASSWORD") != null && findProperty("MAVEN_USERNAME") != null) {
repositories {
- val snapshots = "https://maven.arbjerg.dev/snapshots"
- val releases = "https://maven.arbjerg.dev/releases"
+ val snapshots = "https://maven.lavalink.dev/snapshots"
+ val releases = "https://maven.lavalink.dev/releases"
maven(if (release) releases else snapshots) {
credentials {
@@ -50,7 +50,7 @@ subprojects {
}
}
} else {
- logger.lifecycle("Not publishing to maven.arbjerg.dev because credentials are not set")
+ logger.lifecycle("Not publishing to maven.lavalink.dev because credentials are not set")
}
}
diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3FrameReader.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3FrameReader.java
index 31e2930e..0698c626 100644
--- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3FrameReader.java
+++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3FrameReader.java
@@ -88,16 +88,22 @@ private int copyScanBufferEndToBeginning() {
}
private boolean parseFrameAt(int scanOffset) {
- if (scanOffset >= HEADER_SIZE && (frameSize = Mp3Decoder.getFrameSize(scanBuffer, scanOffset - HEADER_SIZE)) > 0) {
- for (int i = 0; i < HEADER_SIZE; i++) {
- frameBuffer[i] = scanBuffer[scanOffset - HEADER_SIZE + i];
- }
+ int offset = scanOffset - HEADER_SIZE;
+ boolean invalid = offset < 0
+ || !Mp3Decoder.hasFrameSync(scanBuffer, offset)
+ || Mp3Decoder.isUnsupportedVersion(scanBuffer, offset)
+ || !Mp3Decoder.isValidFrame(scanBuffer, offset);
- frameBufferPosition = HEADER_SIZE;
- return true;
+ if (invalid)
+ return false;
+
+ frameSize = Mp3Decoder.getFrameSize(scanBuffer, offset);
+ for (int i = 0; i < HEADER_SIZE; i++) {
+ frameBuffer[i] = scanBuffer[offset + i];
}
- return false;
+ frameBufferPosition = HEADER_SIZE;
+ return true;
}
/**
diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/natives/mp3/Mp3Decoder.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/natives/mp3/Mp3Decoder.java
index 6df56ac1..3dfc5ad7 100644
--- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/natives/mp3/Mp3Decoder.java
+++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/natives/mp3/Mp3Decoder.java
@@ -2,6 +2,8 @@
import com.sedmelluq.lava.common.natives.NativeResourceHolder;
+import java.util.Arrays;
+
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
@@ -69,81 +71,7 @@ protected void freeResources() {
}
private static int getFrameBitRate(byte[] buffer, int offset) {
- return isMpegVersionOne(buffer, offset) ? getFrameBitRateV1(buffer, offset) : getFrameBitRateV2(buffer, offset);
- }
-
- private static int getFrameBitRateV1(byte[] buffer, int offset) {
- switch ((buffer[offset + 2] & 0xF0) >>> 4) {
- case 1:
- return 32000;
- case 2:
- return 40000;
- case 3:
- return 48000;
- case 4:
- return 56000;
- case 5:
- return 64000;
- case 6:
- return 80000;
- case 7:
- return 96000;
- case 8:
- return 112000;
- case 9:
- return 128000;
- case 10:
- return 160000;
- case 11:
- return 192000;
- case 12:
- return 224000;
- case 13:
- return 256000;
- case 14:
- return 320000;
- default:
- throw new IllegalArgumentException("Not valid bitrate");
- }
- }
-
- private static int getFrameBitRateV2(byte[] buffer, int offset) {
- switch ((buffer[offset + 2] & 0xF0) >>> 4) {
- case 1:
- return 8000;
- case 2:
- return 16000;
- case 3:
- return 24000;
- case 4:
- return 32000;
- case 5:
- return 40000;
- case 6:
- return 48000;
- case 7:
- return 56000;
- case 8:
- return 64000;
- case 9:
- return 80000;
- case 10:
- return 96000;
- case 11:
- return 112000;
- case 12:
- return 128000;
- case 13:
- return 144000;
- case 14:
- return 160000;
- default:
- throw new IllegalArgumentException("Not valid bitrate");
- }
- }
-
- private static int calculateFrameSize(boolean isVersionOne, int bitRate, int sampleRate, boolean hasPadding) {
- return (isVersionOne ? 144 : 72) * bitRate / sampleRate + (hasPadding ? 1 : 0);
+ return MpegVersion.getVersion(buffer, offset).getBitRate(buffer, offset);
}
/**
@@ -154,7 +82,7 @@ private static int calculateFrameSize(boolean isVersionOne, int bitRate, int sam
* @return Sample rate
*/
public static int getFrameSampleRate(byte[] buffer, int offset) {
- return isMpegVersionOne(buffer, offset) ? getFrameSampleRateV1(buffer, offset) : getFrameSampleRateV2(buffer, offset);
+ return MpegVersion.getVersion(buffer, offset).getSampleRate(buffer, offset);
}
/**
@@ -168,30 +96,22 @@ public static int getFrameChannelCount(byte[] buffer, int offset) {
return (buffer[offset + 3] & 0xC0) == 0xC0 ? 1 : 2;
}
- private static int getFrameSampleRateV1(byte[] buffer, int offset) {
- switch ((buffer[offset + 2] & 0x0C) >>> 2) {
- case 0:
- return 44100;
- case 1:
- return 48000;
- case 2:
- return 32000;
- default:
- throw new IllegalArgumentException("Not valid sample rate");
- }
+ public static boolean hasFrameSync(byte[] buffer, int offset) {
+ // must start with 11 high bits
+ return (buffer[offset] & 0xFF) == 0xFF && (buffer[offset + 1] & 0xE0) == 0xE0;
}
- private static int getFrameSampleRateV2(byte[] buffer, int offset) {
- switch ((buffer[offset + 2] & 0x0C) >>> 2) {
- case 0:
- return 22050;
- case 1:
- return 24000;
- case 2:
- return 16000;
- default:
- throw new IllegalArgumentException("Not valid sample rate");
- }
+ public static boolean isUnsupportedVersion(byte[] buffer, int offset) {
+ return (buffer[offset + 1] & 0x18) >> 3 == 0x01;
+ }
+
+ public static boolean isValidFrame(byte[] buffer, int offset) {
+ int second = buffer[offset + 1] & 0xFF;
+ int third = buffer[offset + 2] & 0xFF;
+ return (second & 0x06) == 0x02 // Is Layer III
+ && (third & 0xF0) != 0x00 // Has defined bitrate
+ && (third & 0xF0) != 0xF0 // Valid bitrate
+ && (third & 0x0C) != 0x0C; // Valid sampling rate
}
/**
@@ -202,26 +122,7 @@ private static int getFrameSampleRateV2(byte[] buffer, int offset) {
* @return Frame size, or zero if not a valid frame header
*/
public static int getFrameSize(byte[] buffer, int offset) {
- int first = buffer[offset] & 0xFF;
- int second = buffer[offset + 1] & 0xFF;
- int third = buffer[offset + 2] & 0xFF;
-
- boolean invalid = (first != 0xFF || (second & 0xE0) != 0xE0) // Frame sync does not match
- || (second & 0x10) != 0x10 // Not MPEG-1 nor MPEG-2, not dealing with this stuff
- || (second & 0x06) != 0x02 // Not Layer III, not dealing with this stuff
- || (third & 0xF0) == 0x00 // No defined bitrate
- || (third & 0xF0) == 0xF0 // Invalid bitrate
- || (third & 0x0C) == 0x0C; // Invalid sampling rate
-
- if (invalid) {
- return 0;
- }
-
- int bitRate = getFrameBitRate(buffer, offset);
- int sampleRate = getFrameSampleRate(buffer, offset);
- boolean hasPadding = (third & 0x02) != 0;
-
- return calculateFrameSize(isMpegVersionOne(buffer, offset), bitRate, sampleRate, hasPadding);
+ return MpegVersion.getVersion(buffer, offset).getFrameSize(buffer, offset);
}
/**
@@ -232,10 +133,7 @@ public static int getFrameSize(byte[] buffer, int offset) {
* @return Average frame size, assuming CBR
*/
public static double getAverageFrameSize(byte[] buffer, int offset) {
- int bitRate = getFrameBitRate(buffer, offset);
- int sampleRate = getFrameSampleRate(buffer, offset);
-
- return (isMpegVersionOne(buffer, offset) ? 144.0 : 72.0) * bitRate / sampleRate;
+ return MpegVersion.getVersion(buffer, offset).getAverageFrameSize(buffer, offset);
}
/**
@@ -244,14 +142,99 @@ public static double getAverageFrameSize(byte[] buffer, int offset) {
* @return Number of samples per frame.
*/
public static long getSamplesPerFrame(byte[] buffer, int offset) {
- return isMpegVersionOne(buffer, offset) ? MPEG1_SAMPLES_PER_FRAME : MPEG2_SAMPLES_PER_FRAME;
- }
-
- private static boolean isMpegVersionOne(byte[] buffer, int offset) {
- return (buffer[offset + 1] & 0x08) == 0x08;
+ return MpegVersion.getVersion(buffer, offset).getSamplesPerFrame();
}
public static int getMaximumFrameSize() {
- return calculateFrameSize(true, 320000, 32000, true);
+ return MpegVersion.MAX_FRAME_SIZE;
+ }
+
+ private static final int[] SAMPLE_RATE_BASE = { 11025, 12000, 8000 };
+
+ public static enum MpegVersion {
+
+ MPEG_1(4, 1152, new int[] { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }),
+ MPEG_2(2, 576, new int[] { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }),
+ MPEG_2_5(1, MPEG_2.samplesPerFrame, MPEG_2.bitrateIndex);
+
+ public static final int MAX_FRAME_SIZE = getMaxFrameSize();
+
+ public static MpegVersion getVersion(byte[] buffer, int offset) {
+ // 0 - MPEG 2.5
+ // 1 - reserved (unsupported)
+ // 2 - MPEG 2
+ // 3 - MPEG 1
+ int index = (buffer[offset + 1] & 0x18) >> 3;
+ switch (index) {
+ case 0:
+ return MPEG_2_5;
+ case 2:
+ return MPEG_2;
+ case 3:
+ return MPEG_1;
+ default:
+ throw new IllegalArgumentException("Invalid version");
+ }
+ }
+
+ private static int getMaxFrameSize() {
+ int bitRate = MPEG_1.bitrateIndex[MPEG_1.bitrateIndex.length - 1] * 1000;
+ int sampleRate = MPEG_1.samplerateIndex[2];
+ return MPEG_1.calculateFrameSize(bitRate, sampleRate, true);
+ }
+
+ private final int samplesPerFrame;
+ private final int frameLengthMultiplier;
+ private final int[] bitrateIndex;
+ private final int[] samplerateIndex;
+
+ MpegVersion(int samplerateMultiplier, int samplesPerFrame, int[] bitrateIndex) {
+ this.samplesPerFrame = samplesPerFrame;
+ this.frameLengthMultiplier = samplesPerFrame / 8;
+ this.bitrateIndex = bitrateIndex;
+ this.samplerateIndex = Arrays.stream(SAMPLE_RATE_BASE).map(r -> r * samplerateMultiplier).toArray();
+ }
+
+ public int getSamplesPerFrame() {
+ return this.samplesPerFrame;
+ }
+
+ public int getFrameLengthMultiplier() {
+ return this.frameLengthMultiplier;
+ }
+
+ public int getBitRate(byte[] buffer, int offset) {
+ int index = (buffer[offset + 2] & 0xF0) >> 4;
+ if (index == 0 || index == 15)
+ throw new IllegalArgumentException("Invalid bitrate");
+
+ return this.bitrateIndex[index - 1] * 1000;
+ }
+
+ public int getSampleRate(byte[] buffer, int offset) {
+ int index = (buffer[offset + 2] & 0x0C) >> 2;
+ if (index == 3)
+ throw new IllegalArgumentException("Invalid samplerate");
+
+ return this.samplerateIndex[index];
+ }
+
+ public int getFrameSize(byte[] buffer, int offset) {
+ return calculateFrameSize(getBitRate(buffer, offset), getSampleRate(buffer, offset),
+ hasPadding(buffer, offset));
+ }
+
+ public double getAverageFrameSize(byte[] buffer, int offset) {
+ return (double) getFrameLengthMultiplier() * getBitRate(buffer, offset) / getSampleRate(buffer, offset);
+ }
+
+ private int calculateFrameSize(int bitRate, int sampleRate, boolean hasPadding) {
+ return getFrameLengthMultiplier() * bitRate / sampleRate + (hasPadding ? 1 : 0);
+ }
+
+ private static boolean hasPadding(byte[] buffer, int offset) {
+ return (buffer[offset + 2] & 0x02) != 0;
+ }
+
}
}
diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/soundcloud/DefaultSoundCloudDataReader.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/soundcloud/DefaultSoundCloudDataReader.java
index c7098517..aec76ae2 100644
--- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/soundcloud/DefaultSoundCloudDataReader.java
+++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/soundcloud/DefaultSoundCloudDataReader.java
@@ -37,7 +37,7 @@ public AudioTrackInfo readTrackInfo(JsonBrowser trackData, String identifier) {
false,
trackData.get("permalink_url").text(),
ThumbnailTools.getSoundCloudThumbnail(trackData),
- null
+ trackData.get("publisher_metadata").get("isrc").text()
);
}
diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/twitch/TwitchStreamAudioSourceManager.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/twitch/TwitchStreamAudioSourceManager.java
index b8880064..f860e646 100644
--- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/twitch/TwitchStreamAudioSourceManager.java
+++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/twitch/TwitchStreamAudioSourceManager.java
@@ -25,6 +25,7 @@
import java.io.DataOutput;
import java.io.IOException;
import java.net.URI;
+import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
@@ -81,7 +82,11 @@ public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference)
} else {
String title = channelInfo.get("lastBroadcast").get("title").text();
- final String thumbnail = channelInfo.get("profileImageURL").text().replaceFirst("-70x70", "-300x300");
+ final String thumbnail = String.format(
+ "https://static-cdn.jtvnw.net/previews-ttv/live_user_%s-440x248.jpg",
+ // Using root because the turkish lowercase "i" does not have the little dot above the letter when defaulted
+ streamName.toLowerCase(Locale.ROOT)
+ );
return new TwitchStreamAudioTrack(new AudioTrackInfo(
title,
diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/youtube/DefaultYoutubePlaylistLoader.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/youtube/DefaultYoutubePlaylistLoader.java
index ceb473c4..cde3bfa4 100644
--- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/youtube/DefaultYoutubePlaylistLoader.java
+++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/source/youtube/DefaultYoutubePlaylistLoader.java
@@ -17,7 +17,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -167,9 +166,8 @@ private String extractPlaylistTracks(JsonBrowser playlistVideoList, List";
- private static final String IDENTITY_REGEX = "\\{clientId:\"(.+?)\",.+?:\"(.+?)\"";
+ private static final String IDENTITY_REGEX = "\\{clientId:\"(.+?)\",\\n?.+?:\"(.+?)\"";
private static final Pattern authScriptPattern = Pattern.compile(AUTH_SCRIPT_REGEX);
private static final Pattern identityPattern = Pattern.compile(IDENTITY_REGEX);
diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/JsonBrowser.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/JsonBrowser.java
index 07241603..a9b302e2 100644
--- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/JsonBrowser.java
+++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/JsonBrowser.java
@@ -209,6 +209,11 @@ public String text() {
return null;
}
+ public String textOrDefault(String defaultValue) {
+ String value = text();
+ return value != null ? value : defaultValue;
+ }
+
public boolean asBoolean(boolean defaultValue) {
if (node != null) {
if (node.isBoolean()) {
diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/io/HttpClientTools.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/io/HttpClientTools.java
index c9a3099a..e4024200 100644
--- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/io/HttpClientTools.java
+++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/io/HttpClientTools.java
@@ -39,11 +39,15 @@ public class HttpClientTools {
public static final RequestConfig DEFAULT_REQUEST_CONFIG = RequestConfig.custom()
.setConnectTimeout(3000)
+ .setConnectionRequestTimeout(3000)
+ .setSocketTimeout(3000)
.setCookieSpec(CookieSpecs.STANDARD)
.build();
private static final RequestConfig NO_COOKIES_REQUEST_CONFIG = RequestConfig.custom()
.setConnectTimeout(3000)
+ .setConnectionRequestTimeout(3000)
+ .setSocketTimeout(3000)
.setCookieSpec(CookieSpecs.IGNORE_COOKIES)
.build();