0.6.14 - Fix FLAC for HiFi users
This commit is contained in:
parent
97cc8c92ff
commit
9d0c4c521d
|
@ -1,6 +1,7 @@
|
||||||
package f.f.freezer;
|
package f.f.freezer;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.jaudiotagger.audio.AudioFile;
|
import org.jaudiotagger.audio.AudioFile;
|
||||||
import org.jaudiotagger.audio.AudioFileIO;
|
import org.jaudiotagger.audio.AudioFileIO;
|
||||||
|
@ -36,6 +37,7 @@ public class Deezer {
|
||||||
String token;
|
String token;
|
||||||
String arl;
|
String arl;
|
||||||
String sid;
|
String sid;
|
||||||
|
String accessToken;
|
||||||
boolean authorized = false;
|
boolean authorized = false;
|
||||||
boolean authorizing = false;
|
boolean authorizing = false;
|
||||||
|
|
||||||
|
@ -63,9 +65,98 @@ public class Deezer {
|
||||||
logger.warn("Error authorizing to Deezer API! " + e.toString());
|
logger.warn("Error authorizing to Deezer API! " + e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//TV API
|
||||||
|
if (accessToken == null) {
|
||||||
|
try {
|
||||||
|
authorizeTVAPI();
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.accessToken = null;
|
||||||
|
e.printStackTrace();
|
||||||
|
logger.warn("Error authorizing TV API - FLAC might not be available! " + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
authorizing = false;
|
authorizing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Make POST request
|
||||||
|
private String POST(String _url, String data, String cookie) throws Exception {
|
||||||
|
URL url = new URL(_url);
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setConnectTimeout(20000);
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
|
||||||
|
connection.setRequestProperty("Accept-Language", "*");
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.setRequestProperty("Accept", "*/*");
|
||||||
|
connection.setRequestProperty("Content-Length", Integer.toString(data.getBytes(StandardCharsets.UTF_8).length));
|
||||||
|
if (cookie != null) {
|
||||||
|
connection.setRequestProperty("Cookie", cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write body
|
||||||
|
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
|
||||||
|
wr.writeBytes(data);
|
||||||
|
wr.close();
|
||||||
|
//Get response
|
||||||
|
String output = "";
|
||||||
|
Scanner scanner = new Scanner(connection.getInputStream());
|
||||||
|
while (scanner.hasNext()) {
|
||||||
|
output += scanner.nextLine();
|
||||||
|
}
|
||||||
|
scanner.close();
|
||||||
|
connection.disconnect();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String GET(String _url) throws Exception {
|
||||||
|
URL url = new URL(_url);
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setConnectTimeout(20000);
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
|
||||||
|
connection.setRequestProperty("Accept-Language", "*");
|
||||||
|
connection.setRequestProperty("Accept", "*/*");
|
||||||
|
//Get response
|
||||||
|
String output = "";
|
||||||
|
Scanner scanner = new Scanner(connection.getInputStream());
|
||||||
|
while (scanner.hasNext()) {
|
||||||
|
output += scanner.nextLine();
|
||||||
|
}
|
||||||
|
scanner.close();
|
||||||
|
connection.disconnect();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Authorize TV API for generating URLs
|
||||||
|
public void authorizeTVAPI() throws Exception {
|
||||||
|
JSONObject json = new JSONObject(POST(
|
||||||
|
"https://distribution-api.deezer.com/device/token",
|
||||||
|
"{\"brand_name\":\"Hisense\",\"device_id\":\"7239e4071d8992c955ad\",\"model_name\":\"HE50A6109FUWTS\",\"country_code\":\"FRA\"}",
|
||||||
|
null
|
||||||
|
));
|
||||||
|
String deviceToken = json.getString("device_token");
|
||||||
|
// Get unauthorized token
|
||||||
|
json = new JSONObject(GET("https://connect.deezer.com/oauth/access_token.php?grant_type=client_credentials&client_id=447462&client_secret=a83bf7f38ad2f137e444727cfc3775cf&output=json"));
|
||||||
|
String accessToken = json.getString("access_token");
|
||||||
|
// Get smart login code
|
||||||
|
json = new JSONObject(POST(
|
||||||
|
"https://connect.deezer.com/2.0/smartlogin/device?access_token=" + accessToken + "&device_token=" + deviceToken,
|
||||||
|
"", null
|
||||||
|
));
|
||||||
|
String smartLoginCode = json.getJSONObject("data").getString("smartLoginCode");
|
||||||
|
// Get the fuck that is
|
||||||
|
callGWAPI("deezer.associateSmartLoginCodeWithUser", "{\"SMARTLOGIN_CODE\": \"" + smartLoginCode + "\"}");
|
||||||
|
// Get authorized access tonk
|
||||||
|
json = new JSONObject(GET("https://connect.deezer.com/2.0/smartlogin/" + smartLoginCode + "?access_token=" + accessToken));
|
||||||
|
accessToken = json.getJSONObject("data").getString("accessToken");
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
Log.d("DDDD", "Authorized TV: " + this.accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
public JSONObject callGWAPI(String method, String params) throws Exception {
|
public JSONObject callGWAPI(String method, String params) throws Exception {
|
||||||
//Get token
|
//Get token
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
|
@ -73,36 +164,11 @@ public class Deezer {
|
||||||
callGWAPI("deezer.getUserData", "{}");
|
callGWAPI("deezer.getUserData", "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Call
|
String data = POST(
|
||||||
URL url = new URL("https://www.deezer.com/ajax/gw-light.php?method=" + method + "&input=3&api_version=1.0&api_token=" + token);
|
"https://www.deezer.com/ajax/gw-light.php?method=" + method + "&input=3&api_version=1.0&api_token=" + token,
|
||||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
params,
|
||||||
connection.setConnectTimeout(20000);
|
"arl=" + arl + "; sid=" + sid
|
||||||
connection.setDoOutput(true);
|
);
|
||||||
connection.setRequestMethod("POST");
|
|
||||||
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
|
|
||||||
connection.setRequestProperty("Accept-Language", "*");
|
|
||||||
connection.setRequestProperty("Content-Type", "application/json");
|
|
||||||
connection.setRequestProperty("Accept", "*/*");
|
|
||||||
connection.setRequestProperty("Content-Length", Integer.toString(params.getBytes(StandardCharsets.UTF_8).length));
|
|
||||||
String cookies = "arl=" + arl + "; sid=" + sid;
|
|
||||||
connection.setRequestProperty("Cookie", cookies);
|
|
||||||
|
|
||||||
//Write body
|
|
||||||
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
|
|
||||||
wr.writeBytes(params);
|
|
||||||
wr.close();
|
|
||||||
//Get response
|
|
||||||
String data = "";
|
|
||||||
Scanner scanner = new Scanner(connection.getInputStream());
|
|
||||||
while (scanner.hasNext()) {
|
|
||||||
data += scanner.nextLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
//End
|
|
||||||
try {
|
|
||||||
connection.disconnect();
|
|
||||||
scanner.close();
|
|
||||||
} catch (Exception e) {}
|
|
||||||
|
|
||||||
//Parse JSON
|
//Parse JSON
|
||||||
JSONObject out = new JSONObject(data);
|
JSONObject out = new JSONObject(data);
|
||||||
|
@ -110,16 +176,7 @@ public class Deezer {
|
||||||
//Save token
|
//Save token
|
||||||
if ((token == null || token.equals("null")) && method.equals("deezer.getUserData")) {
|
if ((token == null || token.equals("null")) && method.equals("deezer.getUserData")) {
|
||||||
token = out.getJSONObject("results").getString("checkForm");
|
token = out.getJSONObject("results").getString("checkForm");
|
||||||
//SID
|
sid = out.getJSONObject("results").getString("SESSION_ID");
|
||||||
try {
|
|
||||||
String newSid = null;
|
|
||||||
for (String cookie : connection.getHeaderFields().get("Set-Cookie")) {
|
|
||||||
if (cookie.startsWith("sid=")) {
|
|
||||||
newSid = cookie.split(";")[0].split("=")[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.sid = newSid;
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
@ -153,7 +210,7 @@ public class Deezer {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Generate track download URL
|
//Generate track download URL
|
||||||
public static String getTrackUrl(String trackId, String md5origin, String mediaVersion, int quality) {
|
public static String generateTrackUrl(String trackId, String md5origin, String mediaVersion, int quality) {
|
||||||
try {
|
try {
|
||||||
int magic = 164;
|
int magic = 164;
|
||||||
|
|
||||||
|
@ -201,6 +258,39 @@ public class Deezer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns URL and wether encrypted
|
||||||
|
public Pair<String, Boolean> getTrackUrl(String trackId, String md5origin, String mediaVersion, int quality) {
|
||||||
|
// TV API URL Gen
|
||||||
|
if (this.accessToken != null && quality == 9) {
|
||||||
|
try {
|
||||||
|
URL url = new URL("https://api.deezer.com/platform/gcast/track/" + trackId + "/streamUrls");
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setConnectTimeout(20000);
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
|
||||||
|
connection.setRequestProperty("Accept-Language", "*");
|
||||||
|
connection.setRequestProperty("Accept", "*/*");
|
||||||
|
connection.setRequestProperty("Authorization", "Bearer " + this.accessToken);
|
||||||
|
//Get response
|
||||||
|
String output = "";
|
||||||
|
Scanner scanner = new Scanner(connection.getInputStream());
|
||||||
|
while (scanner.hasNext()) {
|
||||||
|
output += scanner.nextLine();
|
||||||
|
}
|
||||||
|
scanner.close();
|
||||||
|
connection.disconnect();
|
||||||
|
|
||||||
|
JSONObject json = new JSONObject(output).getJSONObject("data").getJSONObject("attributes");
|
||||||
|
return new Pair<String, Boolean>(json.getString("url_flac"), false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
logger.warn("Failed generating ATV URL!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Normal url gen
|
||||||
|
return new Pair<String, Boolean>(Deezer.generateTrackUrl(trackId, md5origin, mediaVersion, quality), true);
|
||||||
|
}
|
||||||
|
|
||||||
public static String bytesToHex(byte[] bytes) {
|
public static String bytesToHex(byte[] bytes) {
|
||||||
final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||||
char[] hexChars = new char[bytes.length * 2];
|
char[] hexChars = new char[bytes.length * 2];
|
||||||
|
@ -499,6 +589,7 @@ public class Deezer {
|
||||||
String trackId;
|
String trackId;
|
||||||
int initialQuality;
|
int initialQuality;
|
||||||
DownloadLog logger;
|
DownloadLog logger;
|
||||||
|
boolean encrypted;
|
||||||
|
|
||||||
QualityInfo(int quality, String trackId, String md5origin, String mediaVersion, DownloadLog logger) {
|
QualityInfo(int quality, String trackId, String md5origin, String mediaVersion, DownloadLog logger) {
|
||||||
this.quality = quality;
|
this.quality = quality;
|
||||||
|
@ -509,16 +600,16 @@ public class Deezer {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean fallback(Deezer deezer) {
|
String fallback(Deezer deezer) {
|
||||||
//Quality fallback
|
//Quality fallback
|
||||||
try {
|
try {
|
||||||
qualityFallback();
|
String url = qualityFallback(deezer);
|
||||||
//No quality
|
//No quality
|
||||||
if (quality == -1)
|
if (quality == -1)
|
||||||
throw new Exception("No quality to fallback to!");
|
throw new Exception("No quality to fallback to!");
|
||||||
|
|
||||||
//Success
|
//Success
|
||||||
return true;
|
return url;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("Quality fallback failed! ID: " + trackId + " " + e.toString());
|
logger.warn("Quality fallback failed! ID: " + trackId + " " + e.toString());
|
||||||
quality = initialQuality;
|
quality = initialQuality;
|
||||||
|
@ -562,12 +653,15 @@ public class Deezer {
|
||||||
logger.error("ISRC Fallback failed, track unavailable! ID: " + trackId + " " + e.toString());
|
logger.error("ISRC Fallback failed, track unavailable! ID: " + trackId + " " + e.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void qualityFallback() throws Exception {
|
private String qualityFallback(Deezer deezer) throws Exception {
|
||||||
|
Pair<String,Boolean> urlGen = deezer.getTrackUrl(trackId, md5origin, mediaVersion, quality);
|
||||||
|
this.encrypted = urlGen.second;
|
||||||
|
|
||||||
//Create HEAD requests to check if exists
|
//Create HEAD requests to check if exists
|
||||||
URL url = new URL(getTrackUrl(trackId, md5origin, mediaVersion, quality));
|
URL url = new URL(urlGen.first);
|
||||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
connection.setRequestMethod("HEAD");
|
connection.setRequestMethod("HEAD");
|
||||||
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
|
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
|
||||||
|
@ -580,12 +674,13 @@ public class Deezer {
|
||||||
//-1 if no quality available
|
//-1 if no quality available
|
||||||
if (quality == 1) {
|
if (quality == 1) {
|
||||||
quality = -1;
|
quality = -1;
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
if (quality == 3) quality = 1;
|
if (quality == 3) quality = 1;
|
||||||
if (quality == 9) quality = 3;
|
if (quality == 9) quality = 3;
|
||||||
qualityFallback();
|
return qualityFallback(deezer);
|
||||||
}
|
}
|
||||||
|
return urlGen.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
@ -318,10 +319,11 @@ public class DownloadService extends Service {
|
||||||
|
|
||||||
//Fallback
|
//Fallback
|
||||||
Deezer.QualityInfo qualityInfo = new Deezer.QualityInfo(this.download.quality, this.download.trackId, this.download.md5origin, this.download.mediaVersion, logger);
|
Deezer.QualityInfo qualityInfo = new Deezer.QualityInfo(this.download.quality, this.download.trackId, this.download.md5origin, this.download.mediaVersion, logger);
|
||||||
|
String sURL = null;
|
||||||
if (!download.isUserUploaded()) {
|
if (!download.isUserUploaded()) {
|
||||||
try {
|
try {
|
||||||
boolean res = qualityInfo.fallback(deezer);
|
sURL = qualityInfo.fallback(deezer);
|
||||||
if (!res)
|
if (sURL == null)
|
||||||
throw new Exception("No more to fallback!");
|
throw new Exception("No more to fallback!");
|
||||||
|
|
||||||
download.quality = qualityInfo.quality;
|
download.quality = qualityInfo.quality;
|
||||||
|
@ -378,7 +380,6 @@ public class DownloadService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Download
|
//Download
|
||||||
String sURL = Deezer.getTrackUrl(qualityInfo.trackId, qualityInfo.md5origin, qualityInfo.mediaVersion, qualityInfo.quality);
|
|
||||||
try {
|
try {
|
||||||
URL url = new URL(sURL);
|
URL url = new URL(sURL);
|
||||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
@ -436,15 +437,17 @@ public class DownloadService extends Service {
|
||||||
//Post processing
|
//Post processing
|
||||||
|
|
||||||
//Decrypt
|
//Decrypt
|
||||||
try {
|
if (qualityInfo.encrypted) {
|
||||||
File decFile = new File(tmpFile.getPath() + ".DEC");
|
try {
|
||||||
deezer.decryptFile(download.trackId, tmpFile.getPath(), decFile.getPath());
|
File decFile = new File(tmpFile.getPath() + ".DEC");
|
||||||
tmpFile.delete();
|
deezer.decryptFile(download.trackId, tmpFile.getPath(), decFile.getPath());
|
||||||
tmpFile = decFile;
|
tmpFile.delete();
|
||||||
} catch (Exception e) {
|
tmpFile = decFile;
|
||||||
logger.error("Decryption error: " + e.toString(), download);
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
logger.error("Decryption error: " + e.toString(), download);
|
||||||
//Shouldn't ever fail
|
e.printStackTrace();
|
||||||
|
//Shouldn't ever fail
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ public class StreamServer {
|
||||||
//Shared log & API
|
//Shared log & API
|
||||||
private DownloadLog logger;
|
private DownloadLog logger;
|
||||||
private Deezer deezer;
|
private Deezer deezer;
|
||||||
|
private boolean authorized = false;
|
||||||
|
|
||||||
StreamServer(String arl, String offlinePath) {
|
StreamServer(String arl, String offlinePath) {
|
||||||
//Initialize shared variables
|
//Initialize shared variables
|
||||||
|
@ -174,6 +175,12 @@ public class StreamServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response deezerStream(IHTTPSession session, int startBytes, int end, boolean isRanged) {
|
private Response deezerStream(IHTTPSession session, int startBytes, int end, boolean isRanged) {
|
||||||
|
// Authorize
|
||||||
|
if (!authorized) {
|
||||||
|
deezer.authorize();
|
||||||
|
authorized = true;
|
||||||
|
}
|
||||||
|
|
||||||
//Get QP into Quality Info
|
//Get QP into Quality Info
|
||||||
Deezer.QualityInfo qualityInfo = new Deezer.QualityInfo(
|
Deezer.QualityInfo qualityInfo = new Deezer.QualityInfo(
|
||||||
Integer.parseInt(session.getParameters().get("q").get(0)),
|
Integer.parseInt(session.getParameters().get("q").get(0)),
|
||||||
|
@ -183,19 +190,23 @@ public class StreamServer {
|
||||||
logger
|
logger
|
||||||
);
|
);
|
||||||
//Fallback
|
//Fallback
|
||||||
|
String sURL;
|
||||||
try {
|
try {
|
||||||
boolean res = qualityInfo.fallback(deezer);
|
sURL = qualityInfo.fallback(deezer);
|
||||||
if (!res)
|
if (sURL == null)
|
||||||
throw new Exception("No more to fallback!");
|
throw new Exception("No more to fallback!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Fallback failed!");
|
return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Fallback failed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Calculate Deezer offsets
|
//Calculate Deezer offsets
|
||||||
int deezerStart = startBytes - (startBytes % 2048);
|
int _deezerStart = startBytes;
|
||||||
|
if (qualityInfo.encrypted)
|
||||||
|
_deezerStart -= startBytes % 2048;
|
||||||
|
final int deezerStart = _deezerStart;
|
||||||
int dropBytes = startBytes % 2048;
|
int dropBytes = startBytes % 2048;
|
||||||
|
|
||||||
//Start download
|
//Start download
|
||||||
String sURL = Deezer.getTrackUrl(qualityInfo.trackId, qualityInfo.md5origin, qualityInfo.mediaVersion, qualityInfo.quality);
|
|
||||||
try {
|
try {
|
||||||
URL url = new URL(sURL);
|
URL url = new URL(sURL);
|
||||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
@ -208,58 +219,70 @@ public class StreamServer {
|
||||||
connection.setRequestProperty("Range", "bytes=" + Integer.toString(deezerStart) + "-" + ((end == -1) ? "" : Integer.toString(end)));
|
connection.setRequestProperty("Range", "bytes=" + Integer.toString(deezerStart) + "-" + ((end == -1) ? "" : Integer.toString(end)));
|
||||||
connection.connect();
|
connection.connect();
|
||||||
|
|
||||||
//Get decryption key
|
Response outResponse;
|
||||||
final byte[] key = Deezer.getKey(qualityInfo.trackId);
|
// Encrypted response
|
||||||
|
if (qualityInfo.encrypted) {
|
||||||
|
//Get decryption key
|
||||||
|
final byte[] key = Deezer.getKey(qualityInfo.trackId);
|
||||||
|
|
||||||
//Write response headers
|
outResponse = newFixedLengthResponse(
|
||||||
Response outResponse = newFixedLengthResponse(
|
isRanged ? Response.Status.PARTIAL_CONTENT : Response.Status.OK,
|
||||||
|
(qualityInfo.quality == 9) ? "audio/flac" : "audio/mpeg",
|
||||||
|
new BufferedInputStream(new FilterInputStream(connection.getInputStream()) {
|
||||||
|
|
||||||
|
int counter = deezerStart / 2048;
|
||||||
|
int drop = dropBytes;
|
||||||
|
|
||||||
|
//Decryption stream
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
//Read 2048b or EOF
|
||||||
|
byte[] buffer = new byte[2048];
|
||||||
|
int read = 0;
|
||||||
|
int totalRead = 0;
|
||||||
|
while (read != -1 && totalRead != 2048) {
|
||||||
|
read = in.read(buffer, totalRead, 2048 - totalRead);
|
||||||
|
if (read != -1)
|
||||||
|
totalRead += read;
|
||||||
|
}
|
||||||
|
if (totalRead == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
//Not full chunk return unencrypted
|
||||||
|
if (totalRead != 2048) {
|
||||||
|
System.arraycopy(buffer, 0, b, off, totalRead);
|
||||||
|
return totalRead;
|
||||||
|
}
|
||||||
|
//Decrypt
|
||||||
|
if ((counter % 3) == 0) {
|
||||||
|
buffer = Deezer.decryptChunk(key, buffer);
|
||||||
|
}
|
||||||
|
//Drop bytes from rounding to 2048
|
||||||
|
if (drop > 0) {
|
||||||
|
int output = 2048 - drop;
|
||||||
|
System.arraycopy(buffer, drop, b, off, output);
|
||||||
|
drop = 0;
|
||||||
|
counter++;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
//Copy
|
||||||
|
System.arraycopy(buffer, 0, b, off, 2048);
|
||||||
|
counter++;
|
||||||
|
return 2048;
|
||||||
|
}
|
||||||
|
}, 2048),
|
||||||
|
connection.getContentLength() - dropBytes
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Decrypted
|
||||||
|
outResponse = newFixedLengthResponse(
|
||||||
isRanged ? Response.Status.PARTIAL_CONTENT : Response.Status.OK,
|
isRanged ? Response.Status.PARTIAL_CONTENT : Response.Status.OK,
|
||||||
(qualityInfo.quality == 9) ? "audio/flac" : "audio/mpeg",
|
(qualityInfo.quality == 9) ? "audio/flac" : "audio/mpeg",
|
||||||
new BufferedInputStream(new FilterInputStream(connection.getInputStream()) {
|
connection.getInputStream(),
|
||||||
|
connection.getContentLength()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int counter = deezerStart / 2048;
|
|
||||||
int drop = dropBytes;
|
|
||||||
|
|
||||||
//Decryption stream
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
//Read 2048b or EOF
|
|
||||||
byte[] buffer = new byte[2048];
|
|
||||||
int read = 0;
|
|
||||||
int totalRead = 0;
|
|
||||||
while (read != -1 && totalRead != 2048) {
|
|
||||||
read = in.read(buffer, totalRead, 2048 - totalRead);
|
|
||||||
if (read != -1)
|
|
||||||
totalRead += read;
|
|
||||||
}
|
|
||||||
if (totalRead == 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
//Not full chunk return unencrypted
|
|
||||||
if (totalRead != 2048) {
|
|
||||||
System.arraycopy(buffer, 0, b, off, totalRead);
|
|
||||||
return totalRead;
|
|
||||||
}
|
|
||||||
//Decrypt
|
|
||||||
if ((counter % 3) == 0) {
|
|
||||||
buffer = Deezer.decryptChunk(key, buffer);
|
|
||||||
}
|
|
||||||
//Drop bytes from rounding to 2048
|
|
||||||
if (drop > 0) {
|
|
||||||
int output = 2048 - drop;
|
|
||||||
System.arraycopy(buffer, drop, b, off, output);
|
|
||||||
drop = 0;
|
|
||||||
counter++;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
//Copy
|
|
||||||
System.arraycopy(buffer, 0, b, off, 2048);
|
|
||||||
counter++;
|
|
||||||
return 2048;
|
|
||||||
}
|
|
||||||
}, 2048),
|
|
||||||
connection.getContentLength() - dropBytes
|
|
||||||
);
|
|
||||||
//Ranged header
|
//Ranged header
|
||||||
if (isRanged) {
|
if (isRanged) {
|
||||||
String range = "bytes " + Integer.toString(startBytes) + "-" + Integer.toString((end == -1) ? (connection.getContentLength() + deezerStart) - 1 : end);
|
String range = "bytes " + Integer.toString(startBytes) + "-" + Integer.toString((end == -1) ? (connection.getContentLength() + deezerStart) - 1 : end);
|
||||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 0.6.13+1
|
version: 0.6.14+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.8.0 <3.0.0"
|
sdk: ">=2.8.0 <3.0.0"
|
||||||
|
|
Loading…
Reference in New Issue