diff --git a/app/src/main/java/ru/vtosters/lite/concurrent/VTExecutors.java b/app/src/main/java/ru/vtosters/lite/concurrent/VTExecutors.java
index 2c34f81e3..29bb60e06 100644
--- a/app/src/main/java/ru/vtosters/lite/concurrent/VTExecutors.java
+++ b/app/src/main/java/ru/vtosters/lite/concurrent/VTExecutors.java
@@ -4,7 +4,7 @@ import com.vk.core.concurrent.VkExecutors;
import io.reactivex.Scheduler;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinPool;
public class VTExecutors {
public static ExecutorService getMusicDownloadExecutor() {
@@ -32,7 +32,20 @@ public class VTExecutors {
}
private static class LazyHolder {
- public static final ExecutorService INSTANCE = Executors.newSingleThreadExecutor();
+
+ private static final int PARALLELISM = clamp(
+ (Runtime.getRuntime().availableProcessors() >> 1) - 1,
+ 1, 7);
+
+ private static final ExecutorService INSTANCE =
+ new ForkJoinPool(PARALLELISM,
+ ForkJoinPool.defaultForkJoinWorkerThreadFactory,
+ (t, e) -> {},
+ /* FIFO */ true);
+
+ private static int clamp(int value, int min, int max) {
+ return Math.min(max, Math.max(value, min));
+ }
}
}
}
diff --git a/app/src/main/java/ru/vtosters/lite/music/converter/ts/TSMerger.java b/app/src/main/java/ru/vtosters/lite/music/converter/ts/TSMerger.java
deleted file mode 100644
index bc196d5b3..000000000
--- a/app/src/main/java/ru/vtosters/lite/music/converter/ts/TSMerger.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package ru.vtosters.lite.music.converter.ts;
-
-import android.os.Build;
-import ru.vtosters.lite.utils.IOUtils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.Comparator;
-
-public class TSMerger {
- /**
- * Merge multiple .ts files into one .ts file
- *
- * @param in Directory with .ts files
- */
- public static void merge(File in, File out) {
- File[] tsesList = in.listFiles();
-
- if (tsesList == null) {
- return;
- }
-
- ByteArrayOutputStream mergedByteArrayStream = new ByteArrayOutputStream();
-
- sortTsesList(tsesList);
-
- for (File file : tsesList) {
- if (file.getName().endsWith(".ts")) {
- try {
- mergedByteArrayStream.write(IOUtils.readFully(file));
- } catch (IOException e) {
- return;
- }
- }
- }
-
- writeOutputFile(out, mergedByteArrayStream);
- }
-
- private static void sortTsesList(File[] tsesList) {
- Arrays.sort(tsesList, Comparator.comparingInt(TSMerger::getFileIndex));
- }
-
- private static int getFileIndex(File file) {
- String fileName = file.getName();
- int dotIndex = fileName.indexOf('.', 1);
- return Integer.parseInt(fileName.substring(0, dotIndex));
- }
-
- private static void writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
- byte[] bytes = mergedByteArrayStream.toByteArray();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- try {
- Files.write(out.toPath(), bytes);
- } catch (IOException ignored) {
- // ignored
- }
- } else {
- try (FileOutputStream fos = new FileOutputStream(out)) {
- fos.write(bytes);
- } catch (IOException ignored) {
- // ignored
- }
- }
- }
-}
diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
index d5a15abab..ac981ea36 100644
--- a/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
+++ b/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
@@ -7,28 +7,43 @@ import com.google.android.exoplayer2.source.hls.playlist.e;
import com.google.android.exoplayer2.source.hls.playlist.f;
import com.google.android.exoplayer2.source.hls.playlist.f.a;
import com.vk.dto.music.MusicTrack;
-import java8.util.concurrent.CompletableFuture;
-import java8.util.concurrent.CompletionException;
+
+
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.converter.ts.FFMpeg;
-import ru.vtosters.lite.music.converter.ts.TSMerger;
import ru.vtosters.lite.music.interfaces.Callback;
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.IOUtils;
-import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
+import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class M3UDownloader
implements ITrackDownloader {
+
+ private static final Cipher CIPHER;
+
+ static {
+ try {
+ CIPHER = Cipher.getInstance("AES/CBC/PKCS7Padding");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
public static M3UDownloader getInstance() {
return Holder.INSTANCE;
}
@@ -47,94 +62,77 @@ public class M3UDownloader
var resultTs = getResultTsFile(track);
var resultMp3 = getResultMp3File(outDir, cache, track);
try {
- CompletableFuture.allOf(createFutures(baseUri, segments, progress, callback, track))
- .thenRun(() -> {
- if (cache) {
- try {
- ThumbnailDownloader.downloadThumbnails(track);
- } catch (IOException e) {
- throw new RuntimeException("Failed to download thumbs", e);
- }
- }
- })
- .thenRun(() -> {
- try {
- TSMerger.merge(getTsesDir(track), resultTs);
- } catch (Throwable e) {
- throw new RuntimeException("Failed to merge ts files", e);
- }
- })
- .thenRun(() -> {
- try {
- FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track);
- } catch (Throwable e) {
- throw new RuntimeException("FFmpeg error", e);
- }
- })
- .thenRun(() -> callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size())))
- .thenRun(() -> {
- if (cache) {
- MusicCacheImpl.addTrack(track);
- }
- })
- .whenComplete((unused, tr) -> {
- if (tr != null) {
- IOUtils.deleteRecursive(MusicCacheStorageUtils.getTrackDirById(track.y1()));
- callback.onFailure();
- } else {
- callback.onSuccess();
- }
- IOUtils.deleteRecursive(getTsesDir(track));
- })
- .join();
- } catch (CompletionException e) {
+
+ byte[] buff = downloadTs(baseUri, segments, progress, callback);
+
+ IOUtils.writeToFile(resultTs, buff);
+
+ if (cache) {
+ try {
+ ThumbnailDownloader.downloadThumbnails(track);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to download thumbs", e);
+ }
+ }
+ try {
+ FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track);
+ } catch (Throwable e) {
+ throw new RuntimeException("FFmpeg error", e);
+ }
+ callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size()));
+ if (cache) {
+ try {
+ MusicCacheImpl.addTrack(track);
+ callback.onSuccess();
+ } catch (Exception e) {
+ callback.onFailure();
+ }
+ }
+ } catch (InvalidAlgorithmParameterException |
+ InvalidKeyException |
+ IllegalBlockSizeException |
+ BadPaddingException e) {
Log.e("M3UDownloader", "Failed to download track", e.getCause());
+ } finally {
+ resultTs.delete();
}
}
- @SuppressWarnings("rawtypes")
- static CompletableFuture[] createFutures(
- String baseUri,
- List segments,
- AtomicInteger progress,
- Callback callback,
- MusicTrack track) {
- var futures = new CompletableFuture[segments.size()];
- for (var i = 0; i < segments.size(); ++i) {
- var segment = segments.get(i);
- var filename = i + ".ts";
- futures[i] = CompletableFuture.runAsync(() -> processSegment(baseUri, segment, filename, progress, segments.size(), callback, track));
- }
- return futures;
- }
+ private static byte[]
+ downloadTs(String baseUri, List segments,
+ AtomicInteger progress, Callback callback
+ ) throws IOException, InvalidAlgorithmParameterException,
+ InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ byte[] total = new byte[0];
- private static void processSegment(
- String baseUri,
- a segment,
- String filename,
- AtomicInteger progress,
- int size,
- Callback callback,
- MusicTrack track) {
- try {
+ int size = segments.size();
+ for (var segment : segments) {
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
if (!TextUtils.isEmpty(segment.g)) {
- var cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
var cipherKey = new SecretKeySpec(IOUtils.readFully(IOUtils.openStream(segment.g)), "AES");
var cipherBytes = segment.h.getBytes();
- //if IV doesn't pass, it must be created manually and filled with zeros
- if (cipherBytes.length != 16) Arrays.fill((cipherBytes = new byte[16]), (byte) 0x0);
+ //if IV doesn't pass, it must be created manually
+ if (cipherBytes.length != 16)
+ cipherBytes = new byte[16];
+
var cipherIv = new IvParameterSpec(cipherBytes);
- cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
- buff = cipher.doFinal(buff);
+ CIPHER.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
+ buff = CIPHER.doFinal(buff);
}
- IOUtils.writeToFile(new File(getTsesDir(track), filename), buff);
- callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
- } catch (Exception e) {
- throw new RuntimeException(e);
+
+ // copy on write
+ final int len = total.length, n = buff.length;
+ byte[] newBytes = Arrays.copyOf(total, len + n);
+ System.arraycopy(buff, 0, newBytes, len, n);
+ total = newBytes;
+
+ callback.onProgress(10 +
+ Math.round(80.0f * progress.addAndGet(1) / size));
}
+ return total;
}
+
public static String getTitle(MusicTrack track) {
return track.f + (!TextUtils.isEmpty(track.g) ? + '(' + track.g + ')' : "");
}