Merge remote-tracking branch 'sunmisc/main' #1554
This commit is contained in:
@@ -18,15 +18,12 @@ import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
public class PlaylistInjector {
|
public class PlaylistInjector {
|
||||||
public final static String CHANNEL_NAME = "VTCH";
|
public final static String CHANNEL_NAME = "VTCH";
|
||||||
private static final ExecutorService executor = Executors.newCachedThreadPool();
|
|
||||||
|
|
||||||
public static void injectDownloadPlaylist(Playlist playlist) {
|
public static void injectDownloadPlaylist(Playlist playlist) {
|
||||||
executor.submit(() -> AudioDownloader.cachePlaylist(playlist));
|
AudioDownloader.cachePlaylist(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean eligibleForOfflineCaching() {
|
public static boolean eligibleForOfflineCaching() {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package ru.vtosters.lite.concurrent;
|
package ru.vtosters.lite.concurrent;
|
||||||
|
|
||||||
import com.vk.core.concurrent.VkExecutors;
|
import com.vk.core.concurrent.VkExecutors;
|
||||||
import io.reactivex.Scheduler;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
|
||||||
public class VTExecutors {
|
public class VTExecutors {
|
||||||
public static ExecutorService getMusicDownloadExecutor() {
|
public static ExecutorService getMusicDownloadExecutor() {
|
||||||
@@ -32,7 +33,21 @@ public class VTExecutors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class LazyHolder {
|
private static class LazyHolder {
|
||||||
public static final ExecutorService INSTANCE = Executors.newSingleThreadExecutor();
|
private static final int MIN_PARALLELISM = 1;
|
||||||
|
private static final int MAX_PARALLELISM = 7;
|
||||||
|
private static final int PARALLELISM = clamp(
|
||||||
|
(Runtime.getRuntime().availableProcessors() >> 1) - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ExecutorService INSTANCE =
|
||||||
|
new ForkJoinPool(PARALLELISM,
|
||||||
|
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
|
||||||
|
(t, e) -> {},
|
||||||
|
/* FIFO */ true);
|
||||||
|
|
||||||
|
private static int clamp(int value) {
|
||||||
|
return Math.min(LazyHolder.MAX_PARALLELISM, Math.max(value, LazyHolder.MIN_PARALLELISM));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.vk.core.util.ToastUtils;
|
|||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.dto.music.Playlist;
|
import com.vk.dto.music.Playlist;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
|
||||||
|
import java8.util.concurrent.CompletableFuture;
|
||||||
import ru.vtosters.hooks.music.MusicCacheFilesHook;
|
import ru.vtosters.hooks.music.MusicCacheFilesHook;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
@@ -20,8 +22,7 @@ import ru.vtosters.lite.utils.AndroidUtils;
|
|||||||
import ru.vtosters.lite.utils.IOUtils;
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
||||||
|
|
||||||
@@ -32,10 +33,9 @@ import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class AudioDownloader {
|
public class AudioDownloader {
|
||||||
public static final ExecutorService executor = Executors.newCachedThreadPool();
|
|
||||||
public static final String dlpath = Preferences.getBoolValue("dldir", false) ? Environment.DIRECTORY_DOWNLOADS : Environment.DIRECTORY_MUSIC;
|
public static final String dlpath = Preferences.getBoolValue("dldir", false) ? Environment.DIRECTORY_DOWNLOADS : Environment.DIRECTORY_MUSIC;
|
||||||
|
|
||||||
public static void downloadPlaylist(Playlist playlist) {
|
public static CompletableFuture<Void> downloadPlaylist(Playlist playlist) {
|
||||||
var tracks = PlaylistConverter.getPlaylist(playlist);
|
var tracks = PlaylistConverter.getPlaylist(playlist);
|
||||||
|
|
||||||
var playlistName = IOUtils.getValidFileName(playlist.g);
|
var playlistName = IOUtils.getValidFileName(playlist.g);
|
||||||
@@ -46,7 +46,7 @@ public class AudioDownloader {
|
|||||||
var notificationId = playlistName.hashCode();
|
var notificationId = playlistName.hashCode();
|
||||||
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
|
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlistName, notificationId);
|
||||||
|
|
||||||
PlaylistDownloader.downloadPlaylist(
|
return PlaylistDownloader.downloadPlaylist(
|
||||||
tracks,
|
tracks,
|
||||||
IOUtils.getValidFileName(playlist.g),
|
IOUtils.getValidFileName(playlist.g),
|
||||||
downloadPath,
|
downloadPath,
|
||||||
@@ -65,22 +65,21 @@ public class AudioDownloader {
|
|||||||
if (MusicCacheImpl.isCachedTrack(trackId)) {
|
if (MusicCacheImpl.isCachedTrack(trackId)) {
|
||||||
MusicCacheImpl.removeTrack(trackId);
|
MusicCacheImpl.removeTrack(trackId);
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
var trackFile = MusicCacheFilesHook.getTrackFile(trackId);
|
var trackFile = MusicCacheFilesHook.getTrackFile(trackId);
|
||||||
if (!trackFile.exists())
|
if (!trackFile.exists())
|
||||||
trackFile.getParentFile().mkdirs();
|
trackFile.getParentFile().mkdirs();
|
||||||
executor.submit(() -> downloadM3U8(track, true));
|
downloadM3U8(track, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void cachePlaylist(Playlist playlist) {
|
public static CompletableFuture<Void> cachePlaylist(Playlist playlist) {
|
||||||
var tracks = PlaylistConverter.getPlaylist(playlist);
|
var tracks = PlaylistConverter.getPlaylist(playlist);
|
||||||
|
|
||||||
var notificationId = playlist.g.hashCode();
|
var notificationId = playlist.g.hashCode();
|
||||||
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
|
var notification = MusicNotificationBuilder.buildPlaylistDownloadNotification(playlist.g, notificationId);
|
||||||
|
|
||||||
PlaylistDownloader.cachePlaylist(
|
return PlaylistDownloader.cachePlaylist(
|
||||||
tracks,
|
tracks,
|
||||||
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
|
MusicCallbackBuilder.buildPlaylistCallback(tracks.size(), notification, notificationId)
|
||||||
);
|
);
|
||||||
@@ -117,21 +116,21 @@ public class AudioDownloader {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadM3U8(MusicTrack track, boolean cache) {
|
private static void
|
||||||
if (track.D == null) {
|
downloadM3U8(MusicTrack track, boolean cache) {
|
||||||
|
Objects.requireNonNull(track.D, () -> {
|
||||||
ToastUtils.a(getString(R.string.link_audio_error));
|
ToastUtils.a(getString(R.string.link_audio_error));
|
||||||
return;
|
return "link must not be null";
|
||||||
}
|
});
|
||||||
|
|
||||||
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
var musicPath = Environment.getExternalStoragePublicDirectory(dlpath).getAbsolutePath();
|
||||||
var tempId = track.d;
|
var tempId = track.d;
|
||||||
var downloadPath = musicPath + File.separator;
|
var downloadPath = musicPath + File.separator;
|
||||||
var notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
|
var notification = MusicNotificationBuilder.buildDownloadNotification(track, tempId);
|
||||||
|
|
||||||
if (cache) {
|
var cb = MusicCallbackBuilder.buildOneTrackCallback(tempId, notification);
|
||||||
TrackDownloader.cacheTrack(track, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
|
if (cache)
|
||||||
} else {
|
TrackDownloader.cacheTrack(track, cb);
|
||||||
TrackDownloader.downloadTrack(track, downloadPath, MusicCallbackBuilder.buildOneTrackCallback(tempId, notification));
|
else
|
||||||
}
|
TrackDownloader.downloadTrack(track, downloadPath, cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,25 @@ package ru.vtosters.lite.music;
|
|||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
|
||||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||||
|
import ru.vtosters.sponsorpost.utils.GzipDecompressor;
|
||||||
|
|
||||||
public class Genius {
|
public class Genius {
|
||||||
private static final String KEY = "Bearer ZTejoT_ojOEasIkT9WrMBhBQOz6eYKK5QULCMECmOhvwqjRZ6WbpamFe3geHnvp3";
|
private static final String KEY = "Bearer ZTejoT_ojOEasIkT9WrMBhBQOz6eYKK5QULCMECmOhvwqjRZ6WbpamFe3geHnvp3";
|
||||||
private static final String URL = "https://api.genius.com";
|
private static final String URL = "https://api.genius.com";
|
||||||
@@ -25,7 +28,7 @@ public class Genius {
|
|||||||
|
|
||||||
public static String getTextMusic(MusicTrack musictrack) {
|
public static String getTextMusic(MusicTrack musictrack) {
|
||||||
String uid = musictrack.y1();
|
String uid = musictrack.y1();
|
||||||
String artist = removeExtras(M3UDownloader.getArtists(musictrack));
|
String artist = removeExtras(MusicTrackUtils.getArtists(musictrack));
|
||||||
String title = removeExtras(musictrack.f);
|
String title = removeExtras(musictrack.f);
|
||||||
int duration = musictrack.h;
|
int duration = musictrack.h;
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,14 @@ package ru.vtosters.lite.music;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import okhttp3.*;
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
|
||||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
|
||||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -24,6 +19,20 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.FormBody;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||||
|
|
||||||
public class LastFMScrobbler {
|
public class LastFMScrobbler {
|
||||||
private static final String KEY = "5965d63402414776c54c266db0211746";
|
private static final String KEY = "5965d63402414776c54c266db0211746";
|
||||||
private static final String KEY_SECRET = "d982180eed13275bb948e41cf225f88f";
|
private static final String KEY_SECRET = "d982180eed13275bb948e41cf225f88f";
|
||||||
@@ -33,7 +42,7 @@ public class LastFMScrobbler {
|
|||||||
|
|
||||||
public static void grabMusicTrack(MusicTrack musictrack) {
|
public static void grabMusicTrack(MusicTrack musictrack) {
|
||||||
var uid = musictrack.y1();
|
var uid = musictrack.y1();
|
||||||
var artist = M3UDownloader.getArtists(musictrack);
|
var artist = MusicTrackUtils.getArtists(musictrack);
|
||||||
var title = musictrack.f;
|
var title = musictrack.f;
|
||||||
var duration = musictrack.h;
|
var duration = musictrack.h;
|
||||||
var track_id = musictrack.d;
|
var track_id = musictrack.d;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package ru.vtosters.lite.music.cache;
|
package ru.vtosters.lite.music.cache;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
|
||||||
|
|
||||||
public class MusicCacheDbDelegate {
|
public class MusicCacheDbDelegate {
|
||||||
static synchronized private MusicCacheDb connectToDb(Context context) {
|
private static MusicCacheDb connectToDb(Context context) {
|
||||||
synchronized (MusicCacheDb.class) {
|
|
||||||
return new MusicCacheDb(context);
|
return new MusicCacheDb(context);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void addTrack(Context context,
|
public static void addTrack(Context context,
|
||||||
String trackId,
|
String trackId,
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ package ru.vtosters.lite.music.cache;
|
|||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class MusicCacheImpl {
|
public class MusicCacheImpl {
|
||||||
public static void addTrack(MusicTrack track) {
|
public static void addTrack(MusicTrack track) {
|
||||||
@@ -20,7 +22,7 @@ public class MusicCacheImpl {
|
|||||||
track.I != null ? track.I.getId() + "" : "-1",
|
track.I != null ? track.I.getId() + "" : "-1",
|
||||||
track.f,
|
track.f,
|
||||||
!TextUtils.isEmpty(track.g) ? track.g : "",
|
!TextUtils.isEmpty(track.g) ? track.g : "",
|
||||||
M3UDownloader.getArtists(track),
|
MusicTrackUtils.getArtists(track),
|
||||||
track.I != null ? track.I.getTitle() : "",
|
track.I != null ? track.I.getTitle() : "",
|
||||||
track.K,
|
track.K,
|
||||||
track.h,
|
track.h,
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import android.database.sqlite.SQLiteDatabase;
|
|||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.vk.dto.music.AlbumLink;
|
import com.vk.dto.music.AlbumLink;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.dto.music.Thumb;
|
import com.vk.dto.music.Thumb;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@@ -24,6 +24,10 @@ import java.net.URLDecoder;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p class="note"><strong>Note:</strong> the {@link AutoCloseable} interface was
|
* <p class="note"><strong>Note:</strong> the {@link AutoCloseable} interface was
|
||||||
* first added in the {@link android.os.Build.VERSION_CODES#Q} release for the {@link SQLiteOpenHelper} </p>
|
* first added in the {@link android.os.Build.VERSION_CODES#Q} release for the {@link SQLiteOpenHelper} </p>
|
||||||
@@ -117,7 +121,7 @@ public class MusicCacheDb extends SQLiteOpenHelper implements AutoCloseable { //
|
|||||||
vals.put(Constants.COLUMN_ALBUM_ID, track.I != null ? track.I.getId() + "" : "-1");
|
vals.put(Constants.COLUMN_ALBUM_ID, track.I != null ? track.I.getId() + "" : "-1");
|
||||||
vals.put(Constants.COLUMN_TITLE, track.f);
|
vals.put(Constants.COLUMN_TITLE, track.f);
|
||||||
vals.put(Constants.COLUMN_SUBTITLE, track.g);
|
vals.put(Constants.COLUMN_SUBTITLE, track.g);
|
||||||
vals.put(Constants.COLUMN_ARTIST, M3UDownloader.getArtists(track));
|
vals.put(Constants.COLUMN_ARTIST, MusicTrackUtils.getArtists(track));
|
||||||
vals.put(Constants.COLUMN_ALBUM_TITLE, track.I != null ? track.I.getTitle() : "");
|
vals.put(Constants.COLUMN_ALBUM_TITLE, track.I != null ? track.I.getTitle() : "");
|
||||||
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(track.K, true));
|
vals.put(Constants.COLUMN_EXPLICIT, Boolean.compare(track.K, true));
|
||||||
vals.put(Constants.COLUMN_DURATION, track.h);
|
vals.put(Constants.COLUMN_DURATION, track.h);
|
||||||
|
|||||||
+10
-7
@@ -1,20 +1,23 @@
|
|||||||
package ru.vtosters.lite.music.cache.helpers;
|
package ru.vtosters.lite.music.cache.helpers;
|
||||||
|
|
||||||
|
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import com.vk.dto.music.Playlist;
|
import com.vk.dto.music.Playlist;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
|
||||||
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||||
|
|
||||||
public class PlaylistHelper {
|
public class PlaylistHelper {
|
||||||
|
|
||||||
@@ -34,7 +37,7 @@ public class PlaylistHelper {
|
|||||||
return createAlbum(
|
return createAlbum(
|
||||||
thumb != null ? thumb.h(600) : "",
|
thumb != null ? thumb.h(600) : "",
|
||||||
albumLink.getTitle(),
|
albumLink.getTitle(),
|
||||||
M3UDownloader.getArtists(track),
|
MusicTrackUtils.getArtists(track),
|
||||||
track.y1(),
|
track.y1(),
|
||||||
albumLink.getId()
|
albumLink.getId()
|
||||||
);
|
);
|
||||||
@@ -76,7 +79,7 @@ public class PlaylistHelper {
|
|||||||
list.add(createAlbum(
|
list.add(createAlbum(
|
||||||
thumb != null ? thumb.h(600) : "",
|
thumb != null ? thumb.h(600) : "",
|
||||||
albumLink.getTitle(),
|
albumLink.getTitle(),
|
||||||
M3UDownloader.getArtists(track),
|
MusicTrackUtils.getArtists(track),
|
||||||
track.y1(),
|
track.y1(),
|
||||||
albumLink.getId()
|
albumLink.getId()
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package ru.vtosters.lite.music.callback;
|
package ru.vtosters.lite.music.callback;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
|
||||||
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
import ru.vtosters.lite.downloaders.notifications.NotificationChannels;
|
||||||
import ru.vtosters.lite.music.interfaces.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
@@ -40,12 +43,12 @@ public class MusicCallbackBuilder {
|
|||||||
} catch (UnsatisfiedLinkError e) {
|
} catch (UnsatisfiedLinkError e) {
|
||||||
Log.e("AudioDownloader", "native libs");
|
Log.e("AudioDownloader", "native libs");
|
||||||
Log.e("AudioDownloader", e.getMessage());
|
Log.e("AudioDownloader", e.getMessage());
|
||||||
onFailure();
|
onFailure(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure() {
|
public void onFailure(Throwable e) {
|
||||||
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
|
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
|
||||||
notificationManager.notify(notificationId, notification.build());
|
notificationManager.notify(notificationId, notification.build());
|
||||||
}
|
}
|
||||||
@@ -78,7 +81,7 @@ public class MusicCallbackBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure() {
|
public void onFailure(Throwable e) {
|
||||||
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
|
notification.setContentText(AndroidUtils.getString(R.string.load_audio_error)).setProgress(0, 0, false);
|
||||||
notificationManager.notify(notificationId, notification.build());
|
notificationManager.notify(notificationId, notification.build());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,30 @@ package ru.vtosters.lite.music.converter.ts;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
public class MpegDemuxer {
|
public class MpegDemuxer {
|
||||||
public static void convert(File in, String out, MusicTrack track) throws IOException {
|
public static void convert(byte[] inBytes, String out) throws IOException {
|
||||||
|
try (InputStream inStream = new ByteArrayInputStream(inBytes)) {
|
||||||
|
convert(inStream, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void convert(InputStream in, String out) throws IOException {
|
||||||
int packetSize = 188;
|
int packetSize = 188;
|
||||||
int desiredPid = 256;
|
int desiredPid = 256;
|
||||||
|
|
||||||
var buffer = new byte[packetSize];
|
var buffer = new byte[packetSize];
|
||||||
var packet = ByteBuffer.wrap(buffer).order(ByteOrder.BIG_ENDIAN);
|
var packet = ByteBuffer.wrap(buffer).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
try (var fis = new BufferedInputStream(new FileInputStream(in)); var fos = new BufferedOutputStream(new FileOutputStream(out))) {
|
try (var fis = new BufferedInputStream(in); var fos = new BufferedOutputStream(new FileOutputStream(out))) {
|
||||||
while (fis.read(buffer) != -1) {
|
while (fis.read(buffer) != -1) {
|
||||||
packet.clear();
|
packet.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package ru.vtosters.lite.music.downloader;
|
||||||
|
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||||
|
|
||||||
|
public final class CachedDownloader implements ITrackDownloader {
|
||||||
|
|
||||||
|
private final ITrackDownloader origin;
|
||||||
|
|
||||||
|
public CachedDownloader(ITrackDownloader origin) {
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void download(MusicTrack track, Callback callback) {
|
||||||
|
origin.download(track, new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onProgress(int progress) {
|
||||||
|
callback.onProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
new ThumbnailDownloader().download(track, new Callback() {
|
||||||
|
@Override public void onProgress(int progress) {}
|
||||||
|
|
||||||
|
@Override public void onSuccess() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onSizeReceived(long size, long header) {}
|
||||||
|
});
|
||||||
|
MusicCacheImpl.addTrack(track);
|
||||||
|
callback.onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSizeReceived(long size, long header) {
|
||||||
|
callback.onSizeReceived(size, header);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public class ID3Tagger {
|
|||||||
public static void tag(File file, MusicTrack track) {
|
public static void tag(File file, MusicTrack track) {
|
||||||
try {
|
try {
|
||||||
// https://github.com/mpatric/mp3agic/issues/69#issuecomment-57897874
|
// https://github.com/mpatric/mp3agic/issues/69#issuecomment-57897874
|
||||||
|
// TODO: get rid of file duplication
|
||||||
var oldFile = new File(file.getAbsolutePath().replace(".mp3", "_old.mp3"));
|
var oldFile = new File(file.getAbsolutePath().replace(".mp3", "_old.mp3"));
|
||||||
if (!file.renameTo(oldFile)) {
|
if (!file.renameTo(oldFile)) {
|
||||||
Log.e("ID3Tagger", "Failed to rename file");
|
Log.e("ID3Tagger", "Failed to rename file");
|
||||||
@@ -38,7 +39,7 @@ public class ID3Tagger {
|
|||||||
id3v2Tag.setAlbum(track.I.getTitle());
|
id3v2Tag.setAlbum(track.I.getTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
id3v2Tag.setTitle(M3UDownloader.getTitle(track));
|
id3v2Tag.setTitle(Mp3Downloader.getTitle(track));
|
||||||
|
|
||||||
mp3File.setId3v2Tag(id3v2Tag);
|
mp3File.setId3v2Tag(id3v2Tag);
|
||||||
mp3File.save(file.getAbsolutePath());
|
mp3File.save(file.getAbsolutePath());
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
package ru.vtosters.lite.music.downloader;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
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.Artist;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import java8.util.concurrent.CompletableFuture;
|
|
||||||
import java8.util.concurrent.CompletionException;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
|
||||||
import ru.vtosters.lite.music.converter.ts.MpegDemuxer;
|
|
||||||
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.Cipher;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class M3UDownloader
|
|
||||||
implements ITrackDownloader {
|
|
||||||
public static M3UDownloader getInstance() {
|
|
||||||
return Holder.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void parse(
|
|
||||||
File outDir,
|
|
||||||
Callback callback,
|
|
||||||
MusicTrack track,
|
|
||||||
boolean cache)
|
|
||||||
throws IOException {
|
|
||||||
var parser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
|
|
||||||
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
|
|
||||||
var segments = ((f) parser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
|
|
||||||
var progress = new AtomicInteger();
|
|
||||||
callback.onProgress(5);
|
|
||||||
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 {
|
|
||||||
MpegDemuxer.convert(resultTs, resultMp3.getAbsolutePath(), track);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new RuntimeException("MpegDemuxer error", e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.thenRun(() -> callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size())))
|
|
||||||
.thenRun(() -> {
|
|
||||||
if (Preferences.getBoolValue("setMetaData", true)) {
|
|
||||||
ID3Tagger.tag(resultMp3, track);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.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) {
|
|
||||||
Log.e("M3UDownloader", "Failed to download track", e.getCause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
static CompletableFuture[] createFutures(
|
|
||||||
String baseUri,
|
|
||||||
List<a> 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 void processSegment(
|
|
||||||
String baseUri,
|
|
||||||
a segment,
|
|
||||||
String filename,
|
|
||||||
AtomicInteger progress,
|
|
||||||
int size,
|
|
||||||
Callback callback,
|
|
||||||
MusicTrack track) {
|
|
||||||
try {
|
|
||||||
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);
|
|
||||||
var cipherIv = new IvParameterSpec(cipherBytes);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTitle(MusicTrack track) {
|
|
||||||
return track.f + (!TextUtils.isEmpty(track.g) ? +'(' + track.g + ')' : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
static File getTsesDir(MusicTrack track) {
|
|
||||||
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
|
|
||||||
var tsesDir = new File(filesDir, String.valueOf(track.d));
|
|
||||||
tsesDir.mkdirs();
|
|
||||||
return tsesDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
static File getResultTsFile(MusicTrack track) {
|
|
||||||
return new File(getTsesDir(track), "result.ts");
|
|
||||||
}
|
|
||||||
|
|
||||||
static File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
|
|
||||||
return new File(outDir, IOUtils.getValidFileName((cache ? "track" : getArtists(track) + " - " + getTitle(track)) + ".mp3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void downloadTrack(
|
|
||||||
MusicTrack track,
|
|
||||||
File outDir,
|
|
||||||
Callback callback,
|
|
||||||
boolean cache) {
|
|
||||||
if (track.D.isEmpty()) {
|
|
||||||
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
|
|
||||||
callback.onFailure();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
parse(outDir, callback, track, cache);
|
|
||||||
} catch (IOException e) {
|
|
||||||
callback.onFailure();
|
|
||||||
Log.e("M3UDownloader", e + "");
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialization-on-demand
|
|
||||||
private static class Holder {
|
|
||||||
private static final M3UDownloader INSTANCE = new M3UDownloader();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getArtists(MusicTrack track) {
|
|
||||||
return (track.L == null || track.I == null
|
|
||||||
? normalizeMetadata(track.C)
|
|
||||||
: normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String normalizeMetadata(String in) {
|
|
||||||
return in.replaceAll("[\\\\/:*?\"<>|]", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
//package ru.vtosters.lite.music;
|
|
||||||
//
|
|
||||||
//import android.text.TextUtils;
|
|
||||||
//import android.util.Log;
|
|
||||||
//import android.widget.Toast;
|
|
||||||
//
|
|
||||||
//import com.vk.dto.music.MusicTrack;
|
|
||||||
//
|
|
||||||
//import org.json.JSONException;
|
|
||||||
//import org.json.JSONObject;
|
|
||||||
//
|
|
||||||
//import java.io.File;
|
|
||||||
//import java.io.IOException;
|
|
||||||
//
|
|
||||||
//import okhttp3.Call;
|
|
||||||
//import okhttp3.Callback;
|
|
||||||
//import okhttp3.OkHttpClient;
|
|
||||||
//import okhttp3.Request;
|
|
||||||
//import okhttp3.Response;
|
|
||||||
//import ru.vtosters.lite.downloaders.AudioDownloader;
|
|
||||||
//import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
|
||||||
//import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
//
|
|
||||||
//public class MP3Downloader implements ITrackDownloader {
|
|
||||||
// private static final String URL_FORMAT = "https://api.vk.com/method/audio.getById?v=5.131&access_token=%s&audios=%s©right=";
|
|
||||||
//
|
|
||||||
// private static final OkHttpClient client = new OkHttpClient();
|
|
||||||
//
|
|
||||||
// public static void downloadMP3(MusicTrack track) {
|
|
||||||
// var token = AndroidUtils.getPreferences().getString("vk_admin_token", "");
|
|
||||||
// if (token.isEmpty()) {
|
|
||||||
// Toast.makeText(AndroidUtils.getGlobalContext(), "Обновите токен VK Admin в настройках", Toast.LENGTH_SHORT).show();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var request = new Request.a()
|
|
||||||
// .b(String.format(URL_FORMAT, token, track.y1()))
|
|
||||||
// .a();
|
|
||||||
// client.a(request).a(new Callback() {
|
|
||||||
// @Override
|
|
||||||
// public void a(Call call, IOException e) {
|
|
||||||
// Log.d("MP3Downloader", e + "");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void a(Call call, Response response) throws IOException {
|
|
||||||
// var payload = response.a().g();
|
|
||||||
// Log.d("MP3Downloader", payload);
|
|
||||||
// try {
|
|
||||||
// JSONObject jsonObj = new JSONObject(payload);
|
|
||||||
// if (!jsonObj.has("response"))
|
|
||||||
// return;
|
|
||||||
// JSONObject responseNode = new JSONObject(payload).getJSONArray("response")
|
|
||||||
// .getJSONObject(0);
|
|
||||||
// if (!responseNode.has("url"))
|
|
||||||
// return;
|
|
||||||
// var url = responseNode.optString("url");
|
|
||||||
// Log.d("MP3Downloader", url);
|
|
||||||
// if (TextUtils.isEmpty(url))
|
|
||||||
// return;
|
|
||||||
// if (VKM3UParser.isM3U8(url)) {
|
|
||||||
// AudioDownloader.downloadM3U8(track);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// } catch (JSONException e) {
|
|
||||||
// Log.d("MP3Downloader", e + "");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package ru.vtosters.lite.music.downloader;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
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 java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
|
import ru.vtosters.lite.music.converter.ts.MpegDemuxer;
|
||||||
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||||
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
|
||||||
|
public final class Mp3Downloader implements ITrackDownloader {
|
||||||
|
private final File outputFile;
|
||||||
|
|
||||||
|
public Mp3Downloader(File outputFile) {
|
||||||
|
this.outputFile = outputFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void download(MusicTrack track, Callback callback) {
|
||||||
|
String uri = track.D;
|
||||||
|
if (Objects.requireNonNull(uri).isEmpty()) {
|
||||||
|
String msg = "link error: " + track.y1() + ", title: " + Mp3Downloader.getTitle(track);
|
||||||
|
Log.d("Mp3Downloader", msg);
|
||||||
|
throw new RuntimeException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tsParser = new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
|
||||||
|
var baseUri = track.D.substring(0, track.D.lastIndexOf("/") + 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var segments = ((f) tsParser.a(Uri.parse(baseUri), IOUtils.openStream(track.D))).o;
|
||||||
|
|
||||||
|
byte[] buff = getMergedTs(baseUri, segments, callback);
|
||||||
|
MpegDemuxer.convert(buff, outputFile.getAbsolutePath());
|
||||||
|
|
||||||
|
if (Preferences.getBoolValue("setMetaData", false)) {
|
||||||
|
ID3Tagger.tag(outputFile, track);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.onSuccess();
|
||||||
|
} catch (IOException | GeneralSecurityException e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getMergedTs(String baseUri, List<a> segments, Callback callback) throws IOException, GeneralSecurityException {
|
||||||
|
byte[] total = new byte[0];
|
||||||
|
|
||||||
|
int size = segments.size();
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
|
||||||
|
|
||||||
|
int progress = 0;
|
||||||
|
for (var segment : segments) {
|
||||||
|
var buff = IOUtils.readFully(IOUtils.openStream(baseUri + segment.a/*url*/));
|
||||||
|
if (!TextUtils.isEmpty(segment.g)) {
|
||||||
|
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
|
||||||
|
if (cipherBytes.length != 16) cipherBytes = new byte[16];
|
||||||
|
var cipherIv = new IvParameterSpec(cipherBytes);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIv);
|
||||||
|
buff = cipher.doFinal(buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) / size));
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getTitle(MusicTrack track) {
|
||||||
|
return track.f + (!TextUtils.isEmpty(track.g) ? '(' + track.g + ')' : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +1,68 @@
|
|||||||
package ru.vtosters.lite.music.downloader;
|
package ru.vtosters.lite.music.downloader;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.interfaces.Callback;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import java8.util.concurrent.CompletableFuture;
|
||||||
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
|
||||||
public class PlaylistDownloader {
|
public class PlaylistDownloader {
|
||||||
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
public static CompletableFuture<Void>
|
||||||
|
downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
||||||
var outDir = new File(path);
|
var outDir = new File(path);
|
||||||
if (!outDir.exists())
|
if (!outDir.exists())
|
||||||
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
|
if (outDir.mkdirs()) Log.v("PlaylistDownloader", "Directory created");
|
||||||
else Log.e("PlaylistDownloader", "Directory creation failed");
|
else Log.e("PlaylistDownloader", "Directory creation failed");
|
||||||
var downloadedTracks = new AtomicInteger();
|
Callback delegate = new ProgressCallback(callback);
|
||||||
for (var musicTrack : playlist)
|
return CompletableFuture.allOf(playlist
|
||||||
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
|
.stream()
|
||||||
@Override
|
.map(x -> {
|
||||||
public void onProgress(int progress) {
|
Callback.CompletableFutureCallback d = new
|
||||||
|
Callback.CompletableFutureCallback(delegate);
|
||||||
|
TrackDownloader.downloadTrack(x, path, d);
|
||||||
|
return d;
|
||||||
|
})
|
||||||
|
.toArray(CompletableFuture[]::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CompletableFuture<Void> cachePlaylist(List<MusicTrack> playlist, Callback callback) {
|
||||||
|
Callback delegate = new ProgressCallback(callback);
|
||||||
|
return CompletableFuture.allOf(playlist
|
||||||
|
.stream()
|
||||||
|
.map(x -> {
|
||||||
|
Callback.CompletableFutureCallback d = new
|
||||||
|
Callback.CompletableFutureCallback(delegate);
|
||||||
|
TrackDownloader.cacheTrack(x, d);
|
||||||
|
return d;
|
||||||
|
})
|
||||||
|
.toArray(CompletableFuture[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ProgressCallback implements Callback {
|
||||||
|
private final AtomicInteger progress = new AtomicInteger();
|
||||||
|
private final Callback origin;
|
||||||
|
|
||||||
|
public ProgressCallback(Callback origin) {
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onProgress(int progress) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
callback.onProgress(downloadedTracks.incrementAndGet());
|
origin.onProgress(progress.incrementAndGet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure() {
|
public void onFailure(Throwable e) {
|
||||||
callback.onFailure();
|
origin.onFailure(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void onSizeReceived(long size, long header) {}
|
||||||
public void onSizeReceived(long size, long header) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cachePlaylist(List<MusicTrack> playlist, Callback callback) {
|
|
||||||
var downloadedTracks = new AtomicInteger();
|
|
||||||
for (var musicTrack : playlist)
|
|
||||||
TrackDownloader.cacheTrack(musicTrack, new Callback() {
|
|
||||||
@Override
|
|
||||||
public void onProgress(int progress) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
callback.onProgress(downloadedTracks.incrementAndGet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure() {
|
|
||||||
callback.onFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSizeReceived(long size, long header) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
package ru.vtosters.lite.music.downloader;
|
package ru.vtosters.lite.music.downloader;
|
||||||
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
public class ThumbnailDownloader {
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||||
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
public static void downloadThumbnails(MusicTrack track) throws IOException {
|
public final class ThumbnailDownloader implements ITrackDownloader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void download(MusicTrack track, Callback callback) {
|
||||||
JSONObject json = track.J();
|
JSONObject json = track.J();
|
||||||
JSONObject album = json.optJSONObject("album");
|
JSONObject album = json.optJSONObject("album");
|
||||||
if (album == null) return;
|
if (album == null) return;
|
||||||
@@ -20,6 +25,7 @@ public class ThumbnailDownloader {
|
|||||||
if (thumb == null) return;
|
if (thumb == null) return;
|
||||||
JSONArray sizes = thumb.optJSONArray("sizes");
|
JSONArray sizes = thumb.optJSONArray("sizes");
|
||||||
String trackId = LibVKXClient.asId(track);
|
String trackId = LibVKXClient.asId(track);
|
||||||
|
try {
|
||||||
if (sizes == null) {
|
if (sizes == null) {
|
||||||
JSONArray names = thumb.names();
|
JSONArray names = thumb.names();
|
||||||
if (names == null) return;
|
if (names == null) return;
|
||||||
@@ -35,9 +41,14 @@ public class ThumbnailDownloader {
|
|||||||
downloadThumbnail(src, width, trackId);
|
downloadThumbnail(src, width, trackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
callback.onSuccess();
|
||||||
|
} catch (IOException e) {
|
||||||
|
callback.onFailure(e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
|
private static void downloadThumbnail(String url, int res, String trackId) throws IOException {
|
||||||
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
|
IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId, res), IOUtils.readFully(new URL(url).openStream()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,45 @@
|
|||||||
package ru.vtosters.lite.music.downloader;
|
package ru.vtosters.lite.music.downloader;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.concurrent.VTExecutors;
|
|
||||||
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
|
||||||
import ru.vtosters.lite.music.interfaces.Callback;
|
|
||||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
|
||||||
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class TrackDownloader {
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import ru.vtosters.lite.concurrent.VTExecutors;
|
||||||
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicTrackUtils;
|
||||||
|
|
||||||
|
public final class TrackDownloader {
|
||||||
|
|
||||||
|
private TrackDownloader() { }
|
||||||
|
|
||||||
public static void downloadTrack(MusicTrack track, String path, Callback callback) {
|
public static void downloadTrack(MusicTrack track, String path, Callback callback) {
|
||||||
if (track.D.isEmpty()) {
|
|
||||||
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
|
|
||||||
callback.onFailure();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
download(track, path, callback, M3UDownloader.getInstance(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void cacheTrack(MusicTrack track, Callback callback) {
|
|
||||||
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
|
|
||||||
callback.onSuccess();
|
|
||||||
return;
|
|
||||||
} else if (track.D.isEmpty()) {
|
|
||||||
Log.d("TrackDownloader", "link error: " + track.y1() + ", title: " + M3UDownloader.getTitle(track));
|
|
||||||
callback.onFailure();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
|
|
||||||
download(track, path, callback, M3UDownloader.getInstance(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void download(MusicTrack track, String path, Callback callback, ITrackDownloader downloader, boolean cache) {
|
|
||||||
File outDir = new File(path);
|
File outDir = new File(path);
|
||||||
if (!outDir.exists())
|
if (!outDir.exists()) {
|
||||||
if (outDir.mkdir())
|
if (outDir.mkdir())
|
||||||
Log.v("TrackDownloader", "Directory created");
|
Log.v("TrackDownloader", "Directory created");
|
||||||
else
|
else
|
||||||
Log.e("TrackDownloader", "Directory creation failed");
|
Log.e("TrackDownloader", "Directory creation failed");
|
||||||
|
}
|
||||||
|
var outputFile = new File(outDir, IOUtils.getValidFileName(
|
||||||
|
MusicTrackUtils.getArtists(track) + " - " + Mp3Downloader.getTitle(track)) + ".mp3");
|
||||||
|
|
||||||
VTExecutors.getMusicDownloadExecutor().execute(
|
VTExecutors.getMusicDownloadExecutor().submit(() -> new Mp3Downloader(outputFile).download(track, callback));
|
||||||
() -> downloader.downloadTrack(track, outDir, callback, cache)
|
}
|
||||||
);
|
|
||||||
|
public static void cacheTrack(MusicTrack track, Callback callback) {
|
||||||
|
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track)))
|
||||||
|
return;
|
||||||
|
var outputFile = MusicCacheStorageUtils
|
||||||
|
.getTrackFile(LibVKXClient.asId(track));
|
||||||
|
|
||||||
|
VTExecutors.getMusicDownloadExecutor().submit(() -> new CachedDownloader(
|
||||||
|
new Mp3Downloader(outputFile)
|
||||||
|
).download(track, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,46 @@
|
|||||||
package ru.vtosters.lite.music.interfaces;
|
package ru.vtosters.lite.music.interfaces;
|
||||||
|
|
||||||
|
import java8.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onProgress(int progress);
|
void onProgress(int progress);
|
||||||
|
|
||||||
void onSuccess();
|
void onSuccess();
|
||||||
|
|
||||||
void onFailure();
|
void onFailure(Throwable e);
|
||||||
|
|
||||||
void onSizeReceived(long size, long header);
|
void onSizeReceived(long size, long header);
|
||||||
|
|
||||||
|
|
||||||
|
class CompletableFutureCallback
|
||||||
|
extends CompletableFuture<Void>
|
||||||
|
implements Callback {
|
||||||
|
private final Callback origin;
|
||||||
|
|
||||||
|
public CompletableFutureCallback(Callback origin) {
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(int progress) {
|
||||||
|
origin.onProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
complete(null);
|
||||||
|
origin.onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
completeExceptionally(e);
|
||||||
|
origin.onFailure(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSizeReceived(long size, long header) {
|
||||||
|
origin.onSizeReceived(size, header);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package ru.vtosters.lite.music.interfaces;
|
|||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for track downloaders
|
* Interface for track downloaders
|
||||||
*/
|
*/
|
||||||
@@ -12,8 +10,7 @@ public interface ITrackDownloader {
|
|||||||
* Download track
|
* Download track
|
||||||
*
|
*
|
||||||
* @param track Music track
|
* @param track Music track
|
||||||
* @param outDir Directory where to download the track
|
|
||||||
* @param callback Callback to call when download status changes
|
* @param callback Callback to call when download status changes
|
||||||
*/
|
*/
|
||||||
void downloadTrack(MusicTrack track, File outDir, Callback callback, boolean cache);
|
void download(MusicTrack track, Callback callback);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.vtosters.lite.utils.music;
|
||||||
|
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import com.vk.dto.music.Artist;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class MusicTrackUtils {
|
||||||
|
public static String getArtists(MusicTrack musicTrack) {
|
||||||
|
return (musicTrack.L == null || musicTrack.I == null
|
||||||
|
? normalizeMetadata(musicTrack.C)
|
||||||
|
: normalizeMetadata(musicTrack.L.stream().map(Artist::w1).collect(Collectors.joining(", "))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeMetadata(String in) {
|
||||||
|
return in.replaceAll("[\\\\/:*?\"<>|]", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user