optimize music cache logic
This commit is contained in:
@@ -10,9 +10,9 @@ import com.vk.music.common.MusicPlaybackLaunchContext;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|
||||||
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
import static ru.vtosters.hooks.other.Preferences.getBoolValue;
|
||||||
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
|
import static ru.vtosters.hooks.other.Preferences.isValidSignature;
|
||||||
|
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
||||||
|
|
||||||
public class LibVKXClient {
|
public class LibVKXClient {
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@@ -80,11 +80,6 @@ public class LibVKXClient {
|
|||||||
lambdaplay(list, musicTrack, iLibVkxService, null);
|
lambdaplay(list, musicTrack, iLibVkxService, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String asId(MusicTrack musicTrack) {
|
public static String asId(MusicTrack musicTrack)
|
||||||
String id = musicTrack.e + "_" + musicTrack.d;
|
{ return musicTrack.y1(); }
|
||||||
if (musicTrack.O != null && !musicTrack.O.equals("")) {
|
|
||||||
id += "_" + musicTrack.O;
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,9 +6,9 @@ import com.vk.api.base.ApiRequest;
|
|||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.log.L;
|
import com.vk.log.L;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.music.Genius;
|
import ru.vtosters.lite.music.Genius;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import com.vtosters.lite.R;
|
|||||||
import com.vtosters.lite.ViewUtils;
|
import com.vtosters.lite.ViewUtils;
|
||||||
import com.vtosters.lite.ui.MusicErrorViewHelper;
|
import com.vtosters.lite.ui.MusicErrorViewHelper;
|
||||||
import com.vtosters.lite.ui.holder.RecyclerHolder;
|
import com.vtosters.lite.ui.holder.RecyclerHolder;
|
||||||
import ru.vtosters.lite.music.Genius;
|
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
|
import ru.vtosters.lite.music.Genius;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package ru.vtosters.hooks;
|
|||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.music.catalog.inject.CatalogJsonInjector;
|
import ru.vtosters.hooks.music.injectors.CatalogJsonInjector;
|
||||||
|
|
||||||
public class CatalogsHook {
|
public class CatalogsHook {
|
||||||
public static JSONObject injectCatalogGetAudio(JSONObject json) {
|
public static JSONObject injectCatalogGetAudio(JSONObject json) {
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package ru.vtosters.hooks;
|
|
||||||
|
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class MusicCacheFilesHook {
|
|
||||||
public static File getTrackFile(String trackId) {
|
|
||||||
return new File(FileCacheImplementation.getTrackFolder(trackId), "track.mp3");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTrackExist(String trackId) {
|
|
||||||
return CacheDatabaseDelegate.isCached(trackId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import com.vk.music.fragment.MusicFragment;
|
import com.vk.music.fragment.MusicFragment;
|
||||||
+19
-33
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
@@ -8,45 +8,33 @@ import com.vk.music.bottomsheets.a.MusicAction;
|
|||||||
import com.vk.music.common.MusicPlaybackLaunchContext;
|
import com.vk.music.common.MusicPlaybackLaunchContext;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.utils.music.VKXUtils;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
|
import static bruhcollective.itaysonlab.libvkx.client.LibVKXClient.*;
|
||||||
import static ru.vtosters.hooks.other.Preferences.milkshake;
|
import static ru.vtosters.hooks.other.Preferences.milkshake;
|
||||||
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isCached;
|
|
||||||
import static ru.vtosters.lite.music.cache.CacheDatabaseDelegate.isVkxCached;
|
|
||||||
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
|
import static ru.vtosters.lite.utils.NetworkUtils.isNetworkConnected;
|
||||||
|
|
||||||
public class MusicBottomSheetHook {
|
public class MusicBottomSheetHook {
|
||||||
public static ArrayList<MusicAction> hook(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
public static ArrayList<MusicAction> hook(final ArrayList<MusicAction> actions,final MusicTrack musicTrack)
|
||||||
|
{
|
||||||
if (musicTrack.F1()) return actions;
|
if (musicTrack.F1()) return actions;
|
||||||
|
final var trackId=asId(musicTrack);
|
||||||
var trackid = asId(musicTrack);
|
if (isVkxInstalled())actions.add(getPlayInVKXAction());
|
||||||
|
if (isIntegrationEnabled())
|
||||||
if (isVkxInstalled()) {
|
{
|
||||||
actions.add(getPlayInVKXAction());
|
if(VKXUtils.isVkxCached(trackId))actions.add(getRemoveCacheTrackVkxAction());
|
||||||
|
else if(isNetworkConnected())actions.add(addToCacheTrackVkxAction());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (isIntegrationEnabled()) {
|
{
|
||||||
if (isVkxCached(trackid)) {
|
if(MusicCacheImpl.isCachedTrack(trackId))actions.add(getRemoveCacheTrackAction());
|
||||||
actions.add(getRemoveCacheTrackVkxAction());
|
else if(isNetworkConnected())actions.add(addToCacheTrackAction());
|
||||||
} else if (isNetworkConnected()) {
|
|
||||||
actions.add(addToCacheTrackVkxAction());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isCached(trackid)) {
|
|
||||||
actions.add(getRemoveCacheTrackAction());
|
|
||||||
} else if (isNetworkConnected()) {
|
|
||||||
actions.add(addToCacheTrackAction());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (isNetworkConnected())actions.add(downloadAsMp3Action());
|
||||||
if (isNetworkConnected()) {
|
|
||||||
actions.add(downloadAsMp3Action());
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +42,7 @@ public class MusicBottomSheetHook {
|
|||||||
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
public static ArrayList<MusicAction> hookDownloadBTN(ArrayList<MusicAction> actions, MusicTrack musicTrack) {
|
||||||
if (musicTrack.F1()) return actions;
|
if (musicTrack.F1()) return actions;
|
||||||
|
|
||||||
var trackid = asId(musicTrack);
|
if (MusicCacheImpl.isCachedTrack(musicTrack.y1())) {
|
||||||
|
|
||||||
if (isCached(trackid)) {
|
|
||||||
actions.add(new MusicAction(
|
actions.add(new MusicAction(
|
||||||
R.id.music_action_toggle_download,
|
R.id.music_action_toggle_download,
|
||||||
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
|
milkshake() ? R.drawable.ic_delete_outline_28 : R.drawable.ic_delete_24,
|
||||||
@@ -85,7 +71,7 @@ public class MusicBottomSheetHook {
|
|||||||
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
if (isVkxInstalled()) actions.add(getPlayInVKXAction());
|
||||||
|
|
||||||
if (isIntegrationEnabled()) {
|
if (isIntegrationEnabled()) {
|
||||||
if (isVkxCached(playlist.a, playlist.b)) {
|
if (VKXUtils.isVkxCached(playlist.a, playlist.b)) {
|
||||||
actions.add(getRemoveCacheTrackVkxAction());
|
actions.add(getRemoveCacheTrackVkxAction());
|
||||||
} else if (isNetworkConnected()) {
|
} else if (isNetworkConnected()) {
|
||||||
actions.add(addToCacheTrackVkxAction());
|
actions.add(addToCacheTrackVkxAction());
|
||||||
@@ -124,7 +110,7 @@ public class MusicBottomSheetHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (actionId == R.id.remove_from_cache) {
|
if (actionId == R.id.remove_from_cache) {
|
||||||
CacheDatabaseDelegate.removeTrackFromCache(asId(track));
|
MusicCacheImpl.removeTrack(asId(track));
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||||
return true;
|
return true;
|
||||||
} else if (actionId == R.id.add_to_cache) {
|
} else if (actionId == R.id.add_to_cache) {
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MusicCacheFilesHook {
|
||||||
|
public static File getTrackFile(String trackId)
|
||||||
|
{ return MusicCacheStorageUtils.getTrackFile(trackId); }
|
||||||
|
|
||||||
|
public static boolean isTrackExist(String trackId)
|
||||||
|
{ return MusicCacheImpl.isCachedTrack(trackId); }
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.hook;
|
package ru.vtosters.hooks.music;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.AttrRes;
|
import androidx.annotation.AttrRes;
|
||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.catalog.inject;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
@@ -12,7 +12,7 @@ import org.json.JSONException;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class CatalogJsonInjector {
|
|||||||
setDefaultAudioPage(oldItems, catalog);
|
setDefaultAudioPage(oldItems, catalog);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
||||||
var noPlaylists = !json.has("playlists");
|
var noPlaylists = !json.has("playlists");
|
||||||
|
|
||||||
if (noPlaylists) {
|
if (noPlaylists) {
|
||||||
@@ -91,7 +91,7 @@ public class CatalogJsonInjector {
|
|||||||
|
|
||||||
fixDailyMix(blocks);
|
fixDailyMix(blocks);
|
||||||
|
|
||||||
if (!isUsersCatalog || !CacheDatabaseDelegate.hasTracks() || LibVKXClient.isIntegrationEnabled()) {
|
if (!isUsersCatalog || MusicCacheImpl.isEmpty() || LibVKXClient.isIntegrationEnabled()) {
|
||||||
return; // early return if not users catalog or no tracks or integration enabled
|
return; // early return if not users catalog or no tracks or integration enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
+6
-8
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -12,7 +12,7 @@ import io.reactivex.ObservableOnSubscribe;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||||
|
|
||||||
@@ -25,13 +25,11 @@ public class PlaylistInjector {
|
|||||||
public final static String CHANNEL_NAME = "VTCH";
|
public final static String CHANNEL_NAME = "VTCH";
|
||||||
private static final ExecutorService executor = Executors.newCachedThreadPool();
|
private static final ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
public static void injectDownloadPlaylist(Playlist playlist) {
|
public static void injectDownloadPlaylist(Playlist playlist)
|
||||||
executor.submit(() -> AudioDownloader.cachePlaylist(playlist));
|
{ executor.submit(() -> AudioDownloader.cachePlaylist(playlist)); }
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean eligibleForOfflineCaching() {
|
public static boolean eligibleForOfflineCaching()
|
||||||
return CacheDatabaseDelegate.hasTracks();
|
{ return !MusicCacheImpl.isEmpty(); }
|
||||||
}
|
|
||||||
|
|
||||||
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
|
public static Observable<AudioGetPlaylist.c> injectGetPlaylist(AudioGetPlaylist audioGetPlaylist) {
|
||||||
try {
|
try {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vk.music.model.IpcPlayerModelImpl;
|
import com.vk.music.model.IpcPlayerModelImpl;
|
||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music.cache.injectors;
|
package ru.vtosters.hooks.music.injectors;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
@@ -11,7 +11,7 @@ import io.reactivex.ObservableOnSubscribe;
|
|||||||
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.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
import ru.vtosters.lite.music.cache.helpers.PlaylistHelper;
|
||||||
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
import ru.vtosters.lite.music.cache.helpers.TracklistHelper;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
@@ -56,7 +56,7 @@ public class TracklistInjector {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!CacheDatabaseDelegate.hasTracks()) {
|
if (MusicCacheImpl.isEmpty()) {
|
||||||
observableEmitter.b(parser.c(getEmptyCatalog()));
|
observableEmitter.b(parser.c(getEmptyCatalog()));
|
||||||
} else {
|
} else {
|
||||||
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
|
observableEmitter.b(parser.c(createVirtualCatalog(TracklistHelper.getTracks())));
|
||||||
@@ -135,7 +135,7 @@ public class TracklistInjector {
|
|||||||
|
|
||||||
button.put("action", action);
|
button.put("action", action);
|
||||||
button.put("block_id", "shuffle");
|
button.put("block_id", "shuffle");
|
||||||
button.put("ref_items_count", CacheDatabaseDelegate.getTrackCount());
|
button.put("ref_items_count", MusicCacheImpl.getTracksCount());
|
||||||
button.put("ref_layout_name", "list");
|
button.put("ref_layout_name", "list");
|
||||||
button.put("ref_data_type", "music_audios");
|
button.put("ref_data_type", "music_audios");
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ public class TracklistInjector {
|
|||||||
|
|
||||||
JSONArray blocks = firstSection.getJSONArray("blocks");
|
JSONArray blocks = firstSection.getJSONArray("blocks");
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.hasTracks() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
if (!MusicCacheImpl.isEmpty() && !LibVKXClient.isIntegrationEnabled()) { // inj in playlist list
|
||||||
boolean noPlaylists = hasNoPlaylists(blocks);
|
boolean noPlaylists = hasNoPlaylists(blocks);
|
||||||
|
|
||||||
if (noPlaylists) {
|
if (noPlaylists) {
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
package ru.vtosters.lite.downloaders;
|
package ru.vtosters.lite.downloaders;
|
||||||
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.util.Log;
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.core.util.ToastUtils;
|
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 ru.vtosters.hooks.MusicCacheFilesHook;
|
import ru.vtosters.hooks.music.MusicCacheFilesHook;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
|
import ru.vtosters.lite.music.callback.MusicCallbackBuilder;
|
||||||
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
|
import ru.vtosters.lite.music.converter.playlist.PlaylistConverter;
|
||||||
import ru.vtosters.lite.music.downloader.AudioGet;
|
import ru.vtosters.lite.music.downloader.*;
|
||||||
import ru.vtosters.lite.music.downloader.PlaylistDownloader;
|
|
||||||
import ru.vtosters.lite.music.downloader.TrackDownloader;
|
|
||||||
import ru.vtosters.lite.music.notification.MusicNotificationBuilder;
|
import ru.vtosters.lite.music.notification.MusicNotificationBuilder;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
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.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@@ -33,7 +33,8 @@ import static ru.vtosters.lite.utils.AndroidUtils.getString;
|
|||||||
public class AudioDownloader {
|
public class AudioDownloader {
|
||||||
public static final ExecutorService executor = Executors.newCachedThreadPool();
|
public static final ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
public static void downloadPlaylist(Playlist playlist) {
|
public static 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);
|
||||||
@@ -53,15 +54,14 @@ public class AudioDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void downloadAudio(MusicTrack track) {
|
public static void downloadAudio(MusicTrack track)
|
||||||
downloadM3U8(track, false);
|
{ downloadM3U8(track,false); }
|
||||||
}
|
|
||||||
|
|
||||||
public static void cacheTrack(MusicTrack track) {
|
public static void cacheTrack(MusicTrack track) {
|
||||||
var trackId = track.y1();
|
var trackId = LibVKXClient.asId(track);
|
||||||
|
|
||||||
if (CacheDatabaseDelegate.isCached(trackId)) {
|
if (MusicCacheImpl.isCachedTrack(trackId)) {
|
||||||
CacheDatabaseDelegate.removeTrackFromCache(LibVKXClient.asId(track));
|
MusicCacheImpl.removeTrack(trackId);
|
||||||
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
AndroidUtils.sendToast(AndroidUtils.getString("audio_deleted_from_cache"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import org.json.JSONObject;
|
|||||||
import ru.vtosters.hooks.other.Preferences;
|
import ru.vtosters.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -47,7 +47,7 @@ public class LastFMScrobbler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.autocache() && !CacheDatabaseDelegate.isCached(uid)) {
|
if (Preferences.autocache() && !MusicCacheImpl.isCachedTrack(uid)) {
|
||||||
if (LibVKXClient.isIntegrationEnabled()) {
|
if (LibVKXClient.isIntegrationEnabled()) {
|
||||||
LibVKXClient.getInstance().runOnService(service -> {
|
LibVKXClient.getInstance().runOnService(service -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import java8.util.concurrent.CompletableFuture;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import ru.vtosters.lite.di.singleton.VtOkHttpClient;
|
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
|
||||||
import ru.vtosters.lite.music.converter.ts.FFMpeg;
|
|
||||||
import ru.vtosters.lite.music.converter.ts.TSMerger;
|
|
||||||
import ru.vtosters.lite.music.downloader.ThumbnailDownloader;
|
|
||||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
public class M3UDownloader implements ITrackDownloader {
|
|
||||||
private static final OkHttpClient client = VtOkHttpClient.getInstance();
|
|
||||||
|
|
||||||
public static M3UDownloader getInstance() {
|
|
||||||
return Holder.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTitle(MusicTrack track) {
|
|
||||||
String title = null;
|
|
||||||
|
|
||||||
if (track.f != null) {
|
|
||||||
title = track.f;
|
|
||||||
if (!track.g.isEmpty()) {
|
|
||||||
title += " (" + track.g + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
Request request = new Request.a().b(track.D).a();
|
|
||||||
String response = client.a(request).execute().a().g();
|
|
||||||
parse(response, outDir, callback, track, cache);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
callback.onFailure();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parse(String payload, File outDir, Callback callback, MusicTrack track, boolean cache) throws UnsupportedEncodingException {
|
|
||||||
if (cache) downloadThumbnails(track);
|
|
||||||
|
|
||||||
VKM3UParser parser = new VKM3UParser(track.D.substring(0, track.D.lastIndexOf("/") + 1), payload);
|
|
||||||
AtomicInteger progress = new AtomicInteger(0);
|
|
||||||
List<CompletableFuture<Void>> futures = createFutures(parser, progress, callback, track);
|
|
||||||
|
|
||||||
var resultTs = getResultTsFile(track);
|
|
||||||
var resultMp3 = getResultMp3File(outDir, cache, track);
|
|
||||||
|
|
||||||
callback.onProgress(5);
|
|
||||||
var future = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
|
||||||
future.join();
|
|
||||||
future.exceptionally(throwable -> {
|
|
||||||
callback.onFailure();
|
|
||||||
return null;
|
|
||||||
}).thenApply(v -> TSMerger.merge(getTsesDir(track), resultTs))
|
|
||||||
.thenApply(mergeResult -> mergeResult && FFMpeg.convert(resultTs, resultMp3.getAbsolutePath(), track))
|
|
||||||
.thenApply(convertResult -> {
|
|
||||||
if (convertResult)
|
|
||||||
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / parser.mTransportStreams.size()));
|
|
||||||
else callback.onFailure();
|
|
||||||
return convertResult;
|
|
||||||
}).thenRun(() -> IOUtils.deleteRecursive(getTsesDir(track))).thenRun(() -> {
|
|
||||||
callback.onSuccess();
|
|
||||||
if (cache) CacheDatabaseDelegate.insertTrack(track);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void downloadThumbnails(MusicTrack track) {
|
|
||||||
try {
|
|
||||||
ThumbnailDownloader.downloadThumbnails(track);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CompletableFuture<Void>> createFutures(VKM3UParser parser, AtomicInteger progress, Callback callback, MusicTrack track) {
|
|
||||||
List<CompletableFuture<Void>> futures = new ArrayList<>(parser.mTransportStreams.size());
|
|
||||||
|
|
||||||
var tses = parser.mTransportStreams;
|
|
||||||
for (var i = 0; i < tses.size(); ++i) {
|
|
||||||
final var ts = tses.get(i);
|
|
||||||
final var filename = i + ".ts";
|
|
||||||
futures.add(CompletableFuture.runAsync(() -> processTransportStream(parser, ts, filename, progress, tses.size(), callback, track)));
|
|
||||||
}
|
|
||||||
return futures;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTransportStream(VKM3UParser parser, TransportStream ts, String filename, AtomicInteger progress, int size, Callback callback, MusicTrack track) {
|
|
||||||
try {
|
|
||||||
InputStream mediaIs = IOUtils.openStream(ts.getMediaSegmentUri());
|
|
||||||
byte[] content;
|
|
||||||
if (ts.needDecoding()) {
|
|
||||||
InputStream keyPubIs = IOUtils.openStream(ts.getKeyURL());
|
|
||||||
content = IOUtils.decodeStream(mediaIs, IOUtils.readFully(keyPubIs));
|
|
||||||
keyPubIs.close();
|
|
||||||
} else {
|
|
||||||
content = IOUtils.readFully(mediaIs);
|
|
||||||
}
|
|
||||||
File tsDump = new File(getTsesDir(track), filename);
|
|
||||||
IOUtils.writeToFile(tsDump, content);
|
|
||||||
callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / size));
|
|
||||||
callback.onSizeReceived((long) content.length * size, parser.mHeapSize);
|
|
||||||
mediaIs.close();
|
|
||||||
} catch (IOException | NoSuchPaddingException | NoSuchAlgorithmException |
|
|
||||||
InvalidAlgorithmParameterException | InvalidKeyException e) {
|
|
||||||
callback.onFailure();
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getTsesDir(MusicTrack track) {
|
|
||||||
var filesDir = AndroidUtils.getGlobalContext().getFilesDir();
|
|
||||||
var tsesDir = new File(filesDir, String.valueOf(track.d));
|
|
||||||
tsesDir.mkdirs();
|
|
||||||
return tsesDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getResultTsFile(MusicTrack track) {
|
|
||||||
return new File(getTsesDir(track), "result.ts");
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getResultMp3File(File outDir, boolean cache, MusicTrack track) {
|
|
||||||
var fileName = IOUtils.getValidFileName((cache ? "track" : track.C + " - " + getTitle(track)) + ".mp3");
|
|
||||||
return new File(outDir, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialization-on-demand
|
|
||||||
private static class Holder {
|
|
||||||
private static final M3UDownloader INSTANCE = new M3UDownloader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
public class TransportStream {
|
|
||||||
|
|
||||||
private final String mMediaSegmentUri;
|
|
||||||
private String mKeyUri;
|
|
||||||
private String mMediaSegmentName;
|
|
||||||
|
|
||||||
public TransportStream(String baseURI, String mediaSegmentName) {
|
|
||||||
this.mMediaSegmentUri = baseURI + mediaSegmentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransportStream(String keyURI, String baseURI, String mediaSegmentName) {
|
|
||||||
this.mKeyUri = keyURI;
|
|
||||||
this.mMediaSegmentUri = baseURI + mediaSegmentName;
|
|
||||||
this.mMediaSegmentName = mediaSegmentName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean needDecoding() {
|
|
||||||
return !TextUtils.isEmpty(mKeyUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyURL() {
|
|
||||||
return mKeyUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaSegmentUri() {
|
|
||||||
return this.mMediaSegmentUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaSegmentName() {
|
|
||||||
return this.mMediaSegmentName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package ru.vtosters.lite.music;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parser for extended M3U playlist format used by VK.
|
|
||||||
*
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#ref-M3U">M3U</a>
|
|
||||||
**/
|
|
||||||
public class VKM3UParser {
|
|
||||||
//Playlist types
|
|
||||||
static public int PLAYLIST_TYPE_VOD = 0;
|
|
||||||
static public int PLAYLIST_TYPE_EVENT = 1;
|
|
||||||
private final String mData;
|
|
||||||
//region Extended M3U global extensions
|
|
||||||
public List<TransportStream> mTransportStreams = new ArrayList<>();
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.1.2">EXT-X-VERSION</a>
|
|
||||||
**/
|
|
||||||
public int mVersion = -1;
|
|
||||||
/**
|
|
||||||
* The EXT-X-ALLOW-CACHE tag is used in protocol version 6 or lower.
|
|
||||||
* If the protocol version is upgraded by VK, we will not support that.
|
|
||||||
**/
|
|
||||||
public boolean mAllowCache = false;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.1">EXT-X-TARGETDURATION</a>
|
|
||||||
**/
|
|
||||||
public int mTargetDuration = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.2">EXT-X-MEDIA-SEQUENCE</a>
|
|
||||||
**/
|
|
||||||
public int mMediaSequence = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.3">EXT-X-DISCONTINUITY-SEQUENCE</a>
|
|
||||||
**/
|
|
||||||
public int mDiscontinuitySequence = -1;
|
|
||||||
//endregion
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.5">EXT-X-PLAYLIST-TYPE</a>
|
|
||||||
**/
|
|
||||||
public int mPlaylistType = -1;
|
|
||||||
/**
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.6">EXT-X-I-FRAMES-ONLY</a>
|
|
||||||
**/
|
|
||||||
public boolean mIFramesOnly = false;
|
|
||||||
public long mHeapSize = 0;
|
|
||||||
|
|
||||||
public VKM3UParser(String baseUri, String data) {
|
|
||||||
mData = data;
|
|
||||||
init(baseUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isTS(String str) {
|
|
||||||
return str.matches(".+\\.ts.*");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(String baseUri) {
|
|
||||||
if (TextUtils.isEmpty(mData)) throw new NullPointerException("mData==null");
|
|
||||||
Scanner scanner = new Scanner(mData);
|
|
||||||
String line = scanner.nextLine();
|
|
||||||
if (!"#EXTM3U".equals(line))
|
|
||||||
throw new IllegalStateException(String.format("Unknown initial M3U tag: %s", line));
|
|
||||||
boolean aes128 = false;
|
|
||||||
String keyUri = "";
|
|
||||||
while (scanner.hasNextLine()) {
|
|
||||||
line = scanner.nextLine();
|
|
||||||
if ('#' == line.charAt(0)) {
|
|
||||||
parseTag(line);
|
|
||||||
if (line.startsWith("#EXT-X-KEY:") && line.contains("METHOD=AES-128")) {
|
|
||||||
aes128 = true;
|
|
||||||
keyUri = getKeyUri(line, baseUri);
|
|
||||||
}
|
|
||||||
} else if (isTS(line)) {
|
|
||||||
addTransportStream(aes128, keyUri, baseUri, line);
|
|
||||||
aes128 = false;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(String.format("Failed to parse: %s", line));
|
|
||||||
}
|
|
||||||
mHeapSize += line.getBytes().length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseTag(String line) {
|
|
||||||
if (line.startsWith("#EXT-X-VERSION:"))
|
|
||||||
mVersion = Integer.parseInt(line.substring(15));
|
|
||||||
else if (line.startsWith("#EXT-X-ALLOW-CACHE:"))
|
|
||||||
mAllowCache = "YES".equalsIgnoreCase(line.substring(19));
|
|
||||||
else if (line.startsWith("#EXT-X-TARGETDURATION:"))
|
|
||||||
mTargetDuration = Integer.parseInt(line.substring(22));
|
|
||||||
else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:"))
|
|
||||||
mMediaSequence = Integer.parseInt(line.substring(22));
|
|
||||||
else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE:"))
|
|
||||||
mDiscontinuitySequence = Integer.parseInt(line.substring(30));
|
|
||||||
else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:"))
|
|
||||||
parsePlaylistType(line.substring(21));
|
|
||||||
else if (line.startsWith("#EXT-X-I-FRAMES-ONLY"))
|
|
||||||
mIFramesOnly = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parsePlaylistType(String type) {
|
|
||||||
switch (type) {
|
|
||||||
case "VOD" -> mPlaylistType = PLAYLIST_TYPE_VOD;
|
|
||||||
case "EVENT" -> mPlaylistType = PLAYLIST_TYPE_EVENT;
|
|
||||||
default -> throw new IllegalStateException(String.format("Unknown playlist type: %s", type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getKeyUri(String line, String baseUri) {
|
|
||||||
String substr = line.substring(11);
|
|
||||||
substr = substr.substring(substr.indexOf('"') + 1, substr.lastIndexOf('"'));
|
|
||||||
//TODO: supposed endpoint
|
|
||||||
return substr.startsWith("http") ? substr : baseUri + substr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTransportStream(boolean aes128, String keyUri, String baseUri, String line) {
|
|
||||||
TransportStream ts = aes128
|
|
||||||
? new TransportStream(keyUri, baseUri, line)
|
|
||||||
: new TransportStream(baseUri, line);
|
|
||||||
mTransportStreams.add(ts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
package ru.vtosters.lite.music.cache;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.DatabaseUtils;
|
|
||||||
import android.database.SQLException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
|
||||||
import com.vk.core.util.ToastUtils;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import com.vtosters.lite.R;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ru.vtosters.lite.music.cache.FileCacheImplementation.getCacheDir;
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
|
|
||||||
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
|
|
||||||
import static ru.vtosters.lite.utils.IOUtils.deleteRecursive;
|
|
||||||
|
|
||||||
public class CacheDatabaseDelegate {
|
|
||||||
private static final int DB_VERSION = 0x1;
|
|
||||||
private static final String DB_NAME = "vt_lite_cache.db";
|
|
||||||
private static final String TABLE_NAME = "tracks";
|
|
||||||
private static final String COLUMN_ID = "id";
|
|
||||||
private static final String COLUMN_TRACK_ID = "trackId";
|
|
||||||
private static final String COLUMN_ALBUM_ID = "albumId";
|
|
||||||
private static final String COLUMN_TITLE = "title";
|
|
||||||
private static final String COLUMN_SUBTITLE = "subtitle";
|
|
||||||
private static final String COLUMN_ARTIST = "artist";
|
|
||||||
private static final String COLUMN_ALBUM_TITLE = "albumTitle";
|
|
||||||
private static final String COLUMN_EXPLICIT = "explicit";
|
|
||||||
private static final String COLUMN_HAS_ARTWORK = "hasArtwork";
|
|
||||||
private static final String COLUMN_DURATION = "duration";
|
|
||||||
private static DbHelper helperInstance;
|
|
||||||
|
|
||||||
protected static DbHelper getHelper() {
|
|
||||||
if (helperInstance == null) helperInstance = new DbHelper(getGlobalContext());
|
|
||||||
return helperInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// You MUST call this method ONLY AFTER TRACK IS DOWNLOADED!
|
|
||||||
public static void insertTrack(MusicTrack track) {
|
|
||||||
try {
|
|
||||||
var json = track.J();
|
|
||||||
var albumNode = json.optJSONObject("album");
|
|
||||||
|
|
||||||
var hasArtwork = false;
|
|
||||||
var albumId = -1;
|
|
||||||
var albumName = "";
|
|
||||||
|
|
||||||
if (albumNode != null) {
|
|
||||||
hasArtwork = albumNode.has("thumb");
|
|
||||||
albumId = albumNode.getInt("id");
|
|
||||||
albumName = albumNode.getString("title");
|
|
||||||
}
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES ('%s', %s, '%s', '%s', '%s', '%s', %s, %s, %s)",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK,
|
|
||||||
track.y1(),
|
|
||||||
albumId,
|
|
||||||
URLEncoder.encode(json.getString("title"), "UTF-8"),
|
|
||||||
URLEncoder.encode(json.optString("subtitle"), "UTF-8"),
|
|
||||||
URLEncoder.encode(json.getString("artist"), "UTF-8"),
|
|
||||||
URLEncoder.encode(albumName, "UTF-8"),
|
|
||||||
json.optInt("is_explicit"),
|
|
||||||
json.getInt("duration"),
|
|
||||||
hasArtwork ? 1 : 0
|
|
||||||
);
|
|
||||||
getHelper().getReadableDatabase().execSQL(formula);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
ToastUtils.a(AndroidUtils.getString(R.string.track_bd_insert_err) + " [" + e.getMessage() + "]", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void insertTracks(List<MusicTrack> tracks) {
|
|
||||||
for (var track : tracks) insertTrack(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
getHelper().getReadableDatabase().execSQL(String.format("DELETE FROM %s", TABLE_NAME));
|
|
||||||
deleteRecursive(getCacheDir());
|
|
||||||
sendToast(AndroidUtils.getString(R.string.cached_tracks_deleted));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static JSONObject reconstructFromDb(Cursor cursor) {
|
|
||||||
try {
|
|
||||||
var trackId = cursor.getString(getColumnIndex(cursor, COLUMN_TRACK_ID));
|
|
||||||
var splits = trackId.split("_");
|
|
||||||
var id = Integer.parseInt(splits[1]);
|
|
||||||
var ownerId = Integer.parseInt(splits[0]);
|
|
||||||
|
|
||||||
return new JSONObject()
|
|
||||||
.put("url", FileCacheImplementation.getServerPath())
|
|
||||||
.put("id", id)
|
|
||||||
.put("owner_id", ownerId)
|
|
||||||
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_TITLE)), "UTF-8"))
|
|
||||||
.put("subtitle", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_SUBTITLE)), "UTF-8"))
|
|
||||||
.put("artist", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ARTIST)), "UTF-8"))
|
|
||||||
.put("duration", cursor.getInt(getColumnIndex(cursor, COLUMN_DURATION)))
|
|
||||||
.put("is_explicit", cursor.getInt(getColumnIndex(cursor, COLUMN_EXPLICIT)))
|
|
||||||
.put("access_key", 2281488)
|
|
||||||
.putOpt("album", reconstructAlbumFromDb(cursor, trackId));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return new JSONObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static JSONObject reconstructAlbumFromDb(Cursor cursor, String trackId) {
|
|
||||||
try {
|
|
||||||
if (!FileCacheImplementation.getTrackThumbnail(trackId, 300).exists()
|
|
||||||
&& !FileCacheImplementation.getTrackThumbnail(trackId, 600).exists()
|
|
||||||
&& !FileCacheImplementation.getTrackThumbnail(trackId, 1200).exists()
|
|
||||||
) {
|
|
||||||
var ownerId = Integer.parseInt(trackId.split("_")[1]);
|
|
||||||
return new JSONObject()
|
|
||||||
.put("id", cursor.getInt(getColumnIndex(cursor, COLUMN_ALBUM_ID)))
|
|
||||||
.put("owner_id", ownerId)
|
|
||||||
.put("title", URLDecoder.decode(cursor.getString(getColumnIndex(cursor, COLUMN_ALBUM_TITLE)), "UTF-8"))
|
|
||||||
.putOpt("thumb", reconstructAlbumThumbFromDb(cursor, trackId));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static JSONObject reconstructAlbumThumbFromDb(Cursor cursor, String trackId) {
|
|
||||||
try {
|
|
||||||
if (cursor.getInt(getColumnIndex(cursor, COLUMN_HAS_ARTWORK)) != 0)
|
|
||||||
new JSONObject()
|
|
||||||
.put("height", 600)
|
|
||||||
.put("width", 600)
|
|
||||||
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
|
|
||||||
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)))
|
|
||||||
.putOpt("photo_1200", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 1200)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getColumnIndex(Cursor cursor, String name) {
|
|
||||||
return cursor.getColumnIndexOrThrow(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasTracks() {
|
|
||||||
if (LibVKXClient.isIntegrationEnabled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME
|
|
||||||
);
|
|
||||||
var cursor = getHelper().getReadableDatabase().rawQuery(formula, null);
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
int count = cursor.getInt(0);
|
|
||||||
cursor.close();
|
|
||||||
return count > 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks() {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s",
|
|
||||||
TABLE_NAME
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksAsPlaylist() {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s WHERE %s > -1 GROUP BY %s HAVING COUNT(*) > 1 ORDER by id DESC",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
COLUMN_ALBUM_ID
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksByAlbum(String id) {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s WHERE %s = %s",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksByAlbumOne(String id) {
|
|
||||||
return getTracks(String.format(
|
|
||||||
"SELECT * FROM %s WHERE %s = %s LIMIT 1",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ALBUM_ID,
|
|
||||||
id
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks(String query) {
|
|
||||||
List<MusicTrack> list = new ArrayList<>();
|
|
||||||
try (Cursor cursor = getHelper().getReadableDatabase().rawQuery(query, new String[0])) {
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
list.add(new MusicTrack(reconstructFromDb(cursor)));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isCached(String trackId) {
|
|
||||||
var formula = String.format(
|
|
||||||
"SELECT COUNT(%s) FROM %s WHERE %s = '%s'",
|
|
||||||
COLUMN_ID,
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID,
|
|
||||||
trackId
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
Cursor size = getHelper().getReadableDatabase().rawQuery(formula, null);
|
|
||||||
if (size != null) {
|
|
||||||
size.moveToFirst();
|
|
||||||
int count = size.getInt(0);
|
|
||||||
size.close();
|
|
||||||
return count != 0;
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVkxCached(int playlist_id, int owner_id) {
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
|
||||||
@Override
|
|
||||||
public Boolean run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
return service.isPlaylistCached(playlist_id, owner_id);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean defaultValue() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVkxCached(String trackId) {
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
|
||||||
@Override
|
|
||||||
public Boolean run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
var splits = trackId.split("_");
|
|
||||||
var id = Integer.parseInt(splits[1]);
|
|
||||||
var owner = Integer.parseInt(splits[0]);
|
|
||||||
return service.isTrackCached(id, owner);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean defaultValue() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeTrackFromCache(String trackId) {
|
|
||||||
deleteRecursive(FileCacheImplementation.getTrackFolder(trackId));
|
|
||||||
|
|
||||||
var formula = String.format(
|
|
||||||
"DELETE FROM %s WHERE %s = '%s'",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_TRACK_ID,
|
|
||||||
trackId
|
|
||||||
);
|
|
||||||
getHelper().getWritableDatabase().execSQL(formula);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getTrackCount() {
|
|
||||||
if (!LibVKXClient.isIntegrationEnabled())
|
|
||||||
return DatabaseUtils.queryNumEntries(getHelper().getReadableDatabase(), TABLE_NAME);
|
|
||||||
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long run(ILibVkxService service) {
|
|
||||||
try {
|
|
||||||
return (long) service.getCache().size();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return defaultValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long defaultValue() {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class DbHelper extends SQLiteOpenHelper {
|
|
||||||
public DbHelper(Context context) {
|
|
||||||
super(context, DB_NAME, null, DB_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
var formula = String.format(
|
|
||||||
"CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT, %s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s INTEGER, %s INTEGER, %s INTEGER)",
|
|
||||||
TABLE_NAME,
|
|
||||||
COLUMN_ID, COLUMN_TRACK_ID, COLUMN_ALBUM_ID, COLUMN_TITLE, COLUMN_SUBTITLE, COLUMN_ARTIST, COLUMN_ALBUM_TITLE, COLUMN_EXPLICIT, COLUMN_DURATION, COLUMN_HAS_ARTWORK
|
|
||||||
);
|
|
||||||
db.execSQL(formula);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package ru.vtosters.lite.music.cache;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
|
||||||
import ru.vtosters.hooks.MusicCacheFilesHook;
|
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class FileCacheImplementation {
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
CacheDatabaseDelegate.clear();
|
|
||||||
IOUtils.deleteRecursive(getCacheDir(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getCacheDir() {
|
|
||||||
return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackFolder(String trackId) {
|
|
||||||
var dir = new File(getCacheDir(), trackId);
|
|
||||||
dir.mkdirs();
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getThumbnailsFolder(String trackId) {
|
|
||||||
var dir = new File(getTrackFolder(trackId), "thumbs");
|
|
||||||
dir.mkdirs();
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackFile(MusicTrack track) {
|
|
||||||
return MusicCacheFilesHook.getTrackFile(track.y1());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File getTrackThumbnail(String trackId, int factor) {
|
|
||||||
return new File(getThumbnailsFolder(trackId), "thumb_" + factor + ".png");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFileUri(File file) {
|
|
||||||
if (!file.exists()) return null;
|
|
||||||
return Uri.fromFile(file).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getServerPath() {
|
|
||||||
return "https://vtosters.app";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package ru.vtosters.lite.music.cache;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import ru.vtosters.lite.music.cache.db.MusicCacheDb;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MusicCacheDbDelegate
|
||||||
|
{
|
||||||
|
static synchronized private MusicCacheDb connectToDb(final Context context)
|
||||||
|
{
|
||||||
|
synchronized(MusicCacheDb.class)
|
||||||
|
{ return new MusicCacheDb(context); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void addTrack(final Context context,
|
||||||
|
final String trackId,
|
||||||
|
final String albumId,
|
||||||
|
final String title,
|
||||||
|
final String subtitle,
|
||||||
|
final String artist,
|
||||||
|
final String albumTitle,
|
||||||
|
final boolean explicit,
|
||||||
|
final int duration,
|
||||||
|
final boolean hasArtwork)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{
|
||||||
|
db.addTrack(
|
||||||
|
trackId,
|
||||||
|
albumId,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
artist,
|
||||||
|
albumTitle,
|
||||||
|
explicit,
|
||||||
|
duration,
|
||||||
|
hasArtwork);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void removeTrack(final Context context,final String trackId)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{ db.deleteTrack(trackId); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List< MusicTrack > getAllTracks(final Context context)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{ return db.getAllTracks(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List< MusicTrack > getAlbumById(final Context context,final String albumId)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{ return db.getAlbum(albumId); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List< MusicTrack > getFirstAlbumTrack(final Context context,final String albumId)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{ return db.getFirstAlbumTrack(albumId); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List< MusicTrack > getPlaylist(final Context context)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{ return db.getPlaylist(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public long getTracksCount(final Context context)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{ return db.getTracksCount(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public boolean isCachedTrack(final Context context,final String trackId)
|
||||||
|
{
|
||||||
|
try(final var db=connectToDb(context))
|
||||||
|
{ return db.isCachedTrack(trackId); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void drop(final Context context)
|
||||||
|
{ context.deleteDatabase(MusicCacheDb.Constants.DB_NAME); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package ru.vtosters.lite.music.cache;
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MusicCacheImpl
|
||||||
|
{
|
||||||
|
static public void addTrack(final MusicTrack track)
|
||||||
|
{
|
||||||
|
MusicCacheDbDelegate.addTrack(
|
||||||
|
AndroidUtils.getGlobalContext(),
|
||||||
|
track.y1(),
|
||||||
|
track.I!=null?track.I.getId()+"":"-1",
|
||||||
|
track.f,
|
||||||
|
track.g,
|
||||||
|
track.C,
|
||||||
|
track.I!=null?track.I.getTitle():"",
|
||||||
|
track.K,
|
||||||
|
track.h,
|
||||||
|
track.I!=null&&track.I.u1()!=null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public void removeTrack(final String trackId)
|
||||||
|
{
|
||||||
|
MusicCacheDbDelegate.removeTrack(AndroidUtils.getGlobalContext(),trackId);
|
||||||
|
MusicCacheStorageUtils.removeTrackDirById(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public List< MusicTrack > getAllTracks()
|
||||||
|
{ return MusicCacheDbDelegate.getAllTracks(AndroidUtils.getGlobalContext()); }
|
||||||
|
|
||||||
|
static public List< MusicTrack > getAlbumById(final String albumId)
|
||||||
|
{ return MusicCacheDbDelegate.getAlbumById(AndroidUtils.getGlobalContext(),albumId); }
|
||||||
|
|
||||||
|
static public List< MusicTrack > getFirstAlbumTrack(final String albumId)
|
||||||
|
{ return MusicCacheDbDelegate.getFirstAlbumTrack(AndroidUtils.getGlobalContext(),albumId); }
|
||||||
|
|
||||||
|
static public List< MusicTrack > getPlaylist()
|
||||||
|
{ return MusicCacheDbDelegate.getPlaylist(AndroidUtils.getGlobalContext()); }
|
||||||
|
|
||||||
|
static public long getTracksCount()
|
||||||
|
{
|
||||||
|
return !LibVKXClient.isIntegrationEnabled()
|
||||||
|
?MusicCacheDbDelegate.getTracksCount(AndroidUtils.getGlobalContext())
|
||||||
|
:LibVKXClient.getInstance().runOnServiceSync(
|
||||||
|
new LibVKXClientImpl.LibVKXActionGeneric< Long >() {
|
||||||
|
@Override
|
||||||
|
public Long run(final ILibVkxService service)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{ return (long)service.getCache().size(); }
|
||||||
|
catch(RemoteException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
return defaultValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long defaultValue()
|
||||||
|
{ return 0L; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static public boolean isCachedTrack(final String trackId)
|
||||||
|
{ return MusicCacheDbDelegate.isCachedTrack(AndroidUtils.getGlobalContext(),trackId); }
|
||||||
|
|
||||||
|
static public boolean isEmpty()
|
||||||
|
{ return !LibVKXClient.isIntegrationEnabled()&&getTracksCount()==0L; }
|
||||||
|
|
||||||
|
static public void clear()
|
||||||
|
{
|
||||||
|
MusicCacheDbDelegate.drop(AndroidUtils.getGlobalContext());
|
||||||
|
MusicCacheStorageUtils.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package ru.vtosters.lite.music.cache.db;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.DatabaseUtils;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.vk.dto.music.AlbumLink;
|
||||||
|
import com.vk.dto.music.MusicTrack;
|
||||||
|
import com.vk.dto.music.Thumb;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** <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> */
|
||||||
|
public class MusicCacheDb
|
||||||
|
extends SQLiteOpenHelper
|
||||||
|
implements AutoCloseable//for pre-Q
|
||||||
|
{
|
||||||
|
public MusicCacheDb(@Nullable Context context)
|
||||||
|
{ super(context,Constants.DB_NAME,null,Constants.DV_VERSION); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(final SQLiteDatabase db)
|
||||||
|
{ db.execSQL(Constants.CREATE_QUERY); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(final SQLiteDatabase db,final int oldVersion,final int newVersion)
|
||||||
|
{
|
||||||
|
if(oldVersion<newVersion)
|
||||||
|
{
|
||||||
|
final var tracks=getTracksWithCursor(db.query(Constants.TABLE_NAME,null,null,null,null,null,null));;
|
||||||
|
db.execSQL(Constants.DROP_QUERY);
|
||||||
|
onCreate(db);
|
||||||
|
for(var track:tracks)
|
||||||
|
{
|
||||||
|
final var vals=new ContentValues();
|
||||||
|
vals.put(Constants.COLUMN_TRACK_ID,track.y1());
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_ID,track.I!=null?track.I.getId()+"":"-1");
|
||||||
|
vals.put(Constants.COLUMN_TITLE,track.f);
|
||||||
|
vals.put(Constants.COLUMN_SUBTITLE,track.g);
|
||||||
|
vals.put(Constants.COLUMN_ARTIST,track.C);
|
||||||
|
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_DURATION,track.h);
|
||||||
|
vals.put(Constants.COLUMN_HAS_ARTWORK,track.I!=null&&track.I.u1()!=null);
|
||||||
|
db.insert(Constants.TABLE_NAME,null,vals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTrack(final String trackId,
|
||||||
|
final String albumId,
|
||||||
|
final String title,
|
||||||
|
final String subtitle,
|
||||||
|
final String artist,
|
||||||
|
final String albumTitle,
|
||||||
|
final boolean explicit,
|
||||||
|
final int duration,
|
||||||
|
final boolean hasArtwork)
|
||||||
|
{
|
||||||
|
final var vals=new ContentValues();
|
||||||
|
vals.put(Constants.COLUMN_TRACK_ID,trackId);
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_ID,albumId);
|
||||||
|
vals.put(Constants.COLUMN_TITLE,title);
|
||||||
|
vals.put(Constants.COLUMN_SUBTITLE,subtitle);
|
||||||
|
vals.put(Constants.COLUMN_ARTIST,artist);
|
||||||
|
vals.put(Constants.COLUMN_ALBUM_TITLE,albumTitle);
|
||||||
|
vals.put(Constants.COLUMN_EXPLICIT,Boolean.compare(explicit,true));
|
||||||
|
vals.put(Constants.COLUMN_DURATION,duration);
|
||||||
|
vals.put(Constants.COLUMN_HAS_ARTWORK,Boolean.compare(hasArtwork,true));
|
||||||
|
final long row=getWritableDatabase().insert(Constants.TABLE_NAME,null,vals);
|
||||||
|
if(AndroidUtils.isDebuggable())Log.d("MusicCacheDb","addTrack(): "+row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTrack(final String trackId)
|
||||||
|
{ getWritableDatabase().delete(Constants.TABLE_NAME,Constants.COLUMN_TRACK_ID+" = ?",new String[]{ trackId }); }
|
||||||
|
|
||||||
|
public boolean isCachedTrack(final String trackId)
|
||||||
|
{
|
||||||
|
try(final var cur=getReadableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
new String[]{ Constants.COLUMN_TRACK_ID },
|
||||||
|
Constants.COLUMN_TRACK_ID+" = ?",
|
||||||
|
new String[]{ trackId },
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"0,1"))
|
||||||
|
{ return cur.moveToFirst(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTracksCount()
|
||||||
|
{ return DatabaseUtils.queryNumEntries(getReadableDatabase(),Constants.TABLE_NAME); }
|
||||||
|
|
||||||
|
public List< MusicTrack > getAllTracks()
|
||||||
|
{ return getTracksWithCursor(getReadableDatabase().query(Constants.TABLE_NAME,null,null,null,null,null,null)); }
|
||||||
|
|
||||||
|
public List< MusicTrack > getAlbum(final String albumId)
|
||||||
|
{ return getAlbumTracks(albumId,null); }
|
||||||
|
|
||||||
|
public List< MusicTrack > getFirstAlbumTrack(final String albumId)
|
||||||
|
{ return getAlbumTracks(albumId,"0,1"); }
|
||||||
|
|
||||||
|
private List< MusicTrack > getAlbumTracks(final String albumId,@Nullable final String limit)
|
||||||
|
{
|
||||||
|
return getTracksWithCursor(getReadableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID+" = ?",
|
||||||
|
new String[]{ albumId },
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List< MusicTrack > getPlaylist()
|
||||||
|
{
|
||||||
|
return getTracksWithCursor(getWritableDatabase().query(
|
||||||
|
Constants.TABLE_NAME,
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID+" > -1",
|
||||||
|
null,
|
||||||
|
Constants.COLUMN_ALBUM_ID,
|
||||||
|
"count(*) > 1",
|
||||||
|
Constants.COLUMN_ID+" desc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List< MusicTrack > getTracksWithCursor(final Cursor cur)
|
||||||
|
{
|
||||||
|
final var res=new ArrayList< MusicTrack >();
|
||||||
|
if(cur!=null&&cur.moveToFirst())
|
||||||
|
try(cur)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
final var track=fromCursor(cur);
|
||||||
|
if(track!=null)res.add(track);
|
||||||
|
}while(cur.moveToNext());
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private MusicTrack fromCursor(final Cursor cur)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
final var res=new MusicTrack();
|
||||||
|
res.J=Integer.MAX_VALUE+"";//access_key
|
||||||
|
res.D="https://vtosters.app";//url
|
||||||
|
var i=cur.getColumnIndex(Constants.COLUMN_TRACK_ID);
|
||||||
|
if(i==-1)break;
|
||||||
|
final var trackId=cur.getString(i);
|
||||||
|
final var splits=trackId.split("_");
|
||||||
|
if(splits.length!=2)break;
|
||||||
|
res.d=Integer.parseInt(splits[1]);//id
|
||||||
|
res.e=Integer.parseInt(splits[0]);//owner_id
|
||||||
|
i=cur.getColumnIndex(Constants.COLUMN_TITLE);
|
||||||
|
if(i==-1)break;
|
||||||
|
res.f=URLDecoder.decode(cur.getString(i),"UTF-8");//title
|
||||||
|
i=cur.getColumnIndex(Constants.COLUMN_SUBTITLE);
|
||||||
|
if(i==-1)break;
|
||||||
|
res.g=URLDecoder.decode(cur.getString(i),"UTF-8");//subtitle
|
||||||
|
i=cur.getColumnIndex(Constants.COLUMN_ARTIST);
|
||||||
|
if(i==-1)break;
|
||||||
|
res.C=URLDecoder.decode(cur.getString(i),"UTF-8");//artist
|
||||||
|
i=cur.getColumnIndex(Constants.COLUMN_DURATION);
|
||||||
|
if(i==-1)break;
|
||||||
|
res.h=cur.getInt(i);//duration
|
||||||
|
i=cur.getColumnIndex(Constants.COLUMN_EXPLICIT);
|
||||||
|
if(i==-1)break;
|
||||||
|
res.K=cur.getInt(i)>0;//is_explicit
|
||||||
|
i=cur.getColumnIndex(Constants.COLUMN_ALBUM_ID);
|
||||||
|
if(i==-1)break;
|
||||||
|
final var albumId=Integer.parseInt(cur.getString(i));
|
||||||
|
i=cur.getColumnIndex(Constants.COLUMN_ALBUM_TITLE);
|
||||||
|
final String albumTitle=i!=-1?cur.getString(i):null;
|
||||||
|
final var node=new JSONObject();
|
||||||
|
var f=MusicCacheStorageUtils.getTrackThumb(trackId,300);
|
||||||
|
if(f.exists())node.putOpt("photo_600",Uri.fromFile(f).toString());
|
||||||
|
f=MusicCacheStorageUtils.getTrackThumb(trackId,600);
|
||||||
|
if(f.exists())node.putOpt("photo_600",Uri.fromFile(f).toString());
|
||||||
|
f=MusicCacheStorageUtils.getTrackThumb(trackId,1200);
|
||||||
|
if(f.exists())node.putOpt("photo_1200",Uri.fromFile(f).toString());
|
||||||
|
res.I=new AlbumLink(
|
||||||
|
albumId,
|
||||||
|
res.e,
|
||||||
|
res.J,
|
||||||
|
albumTitle,
|
||||||
|
node.length()>0
|
||||||
|
?new Thumb(node.put("width",600)
|
||||||
|
.put("height",600))
|
||||||
|
:null
|
||||||
|
);//album
|
||||||
|
return res;
|
||||||
|
}while(false);
|
||||||
|
}
|
||||||
|
catch(Throwable ignored)
|
||||||
|
{ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Constants
|
||||||
|
{
|
||||||
|
int DV_VERSION=0x2;
|
||||||
|
String DB_NAME="vt_lite_cache.db";
|
||||||
|
String TABLE_NAME="tracks";
|
||||||
|
//columns
|
||||||
|
String COLUMN_ID="id";
|
||||||
|
String COLUMN_TRACK_ID="trackId";
|
||||||
|
String COLUMN_ALBUM_ID="albumId";
|
||||||
|
String COLUMN_TITLE="title";
|
||||||
|
String COLUMN_SUBTITLE="subtitle";
|
||||||
|
String COLUMN_ARTIST="artist";
|
||||||
|
String COLUMN_ALBUM_TITLE="albumTitle";
|
||||||
|
String COLUMN_EXPLICIT="explicit";
|
||||||
|
String COLUMN_DURATION="duration";
|
||||||
|
String COLUMN_HAS_ARTWORK="hasArtwork";
|
||||||
|
//queries
|
||||||
|
String CREATE_QUERY="create table "+TABLE_NAME+"("
|
||||||
|
+COLUMN_ID+" INTEGER PRIMARY KEY autoincrement,"
|
||||||
|
+COLUMN_TRACK_ID+" text not null,"
|
||||||
|
+COLUMN_ALBUM_ID+" text not null,"
|
||||||
|
+COLUMN_TITLE+" text not null,"
|
||||||
|
+COLUMN_SUBTITLE+" text not null,"
|
||||||
|
+COLUMN_ARTIST+" text not null,"
|
||||||
|
+COLUMN_ALBUM_TITLE+" text not null,"
|
||||||
|
+COLUMN_EXPLICIT+" int not null,"
|
||||||
|
+COLUMN_DURATION+" int not null,"
|
||||||
|
+COLUMN_HAS_ARTWORK+" int not null"
|
||||||
|
+")";
|
||||||
|
String DROP_QUERY="drop table if exists "+TABLE_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package ru.vtosters.lite.music.cache.helpers;
|
package ru.vtosters.lite.music.cache.helpers;
|
||||||
|
|
||||||
|
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.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -44,8 +45,8 @@ public class PlaylistHelper {
|
|||||||
if (!photo.isEmpty())
|
if (!photo.isEmpty())
|
||||||
jPhoto.put("height", 600)
|
jPhoto.put("height", 600)
|
||||||
.put("width", 600)
|
.put("width", 600)
|
||||||
.putOpt("photo_300", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 300)))
|
.putOpt("photo_300",Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId,300)).toString())
|
||||||
.putOpt("photo_600", FileCacheImplementation.getFileUri(FileCacheImplementation.getTrackThumbnail(trackId, 600)));
|
.putOpt("photo_600",Uri.fromFile(MusicCacheStorageUtils.getTrackThumb(trackId,600)).toString());
|
||||||
var obj = new JSONObject()
|
var obj = new JSONObject()
|
||||||
.put("id", -2)
|
.put("id", -2)
|
||||||
.put("owner_id", albumId)
|
.put("owner_id", albumId)
|
||||||
@@ -68,7 +69,7 @@ public class PlaylistHelper {
|
|||||||
|
|
||||||
public static List<Playlist> getAlbumPlaylists() {
|
public static List<Playlist> getAlbumPlaylists() {
|
||||||
List<Playlist> list = new ArrayList<>();
|
List<Playlist> list = new ArrayList<>();
|
||||||
for (var track : CacheDatabaseDelegate.getTracksAsPlaylist()) {
|
for (var track : MusicCacheImpl.getPlaylist()) {
|
||||||
var albumLink = track.I;
|
var albumLink = track.I;
|
||||||
var thumb = albumLink.u1();
|
var thumb = albumLink.u1();
|
||||||
list.add(createAlbum(
|
list.add(createAlbum(
|
||||||
|
|||||||
+7
-7
@@ -7,8 +7,8 @@ import org.json.JSONArray;
|
|||||||
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.hooks.other.Preferences;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
@@ -22,7 +22,7 @@ public class TracklistHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks() {
|
public static List<MusicTrack> getTracks() {
|
||||||
List<MusicTrack> tracks = CacheDatabaseDelegate.getTracks();
|
List<MusicTrack> tracks = MusicCacheImpl.getAllTracks();
|
||||||
|
|
||||||
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
|
boolean doNotinvertOrder = Preferences.getBoolValue("invertCachedTracks", false);
|
||||||
|
|
||||||
@@ -35,11 +35,11 @@ public class TracklistHelper {
|
|||||||
|
|
||||||
|
|
||||||
public static List<MusicTrack> getTracks(String id) {
|
public static List<MusicTrack> getTracks(String id) {
|
||||||
return CacheDatabaseDelegate.getTracksByAlbum(id);
|
return MusicCacheImpl.getAlbumById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MusicTrack> getTracksOne(String id) {
|
public static List<MusicTrack> getTracksOne(String id) {
|
||||||
return CacheDatabaseDelegate.getTracksByAlbumOne(id);
|
return MusicCacheImpl.getFirstAlbumTrack(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
|
public static JSONArray tracksToIds(List<MusicTrack> tracks) {
|
||||||
@@ -56,7 +56,7 @@ public class TracklistHelper {
|
|||||||
var arr = new JSONArray();
|
var arr = new JSONArray();
|
||||||
for (MusicTrack track : tracks) {
|
for (MusicTrack track : tracks) {
|
||||||
var json = track.J();
|
var json = track.J();
|
||||||
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
|
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
|
||||||
try {
|
try {
|
||||||
addCachedThumbnails(json, folder);
|
addCachedThumbnails(json, folder);
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
@@ -71,7 +71,7 @@ public class TracklistHelper {
|
|||||||
List<MusicTrack> tracks = new ArrayList<>();
|
List<MusicTrack> tracks = new ArrayList<>();
|
||||||
for (MusicTrack track : list) {
|
for (MusicTrack track : list) {
|
||||||
var json = track.J();
|
var json = track.J();
|
||||||
var folder = FileCacheImplementation.getThumbnailsFolder(LibVKXClient.asId(track));
|
var folder = MusicCacheStorageUtils.getThumbDirById(LibVKXClient.asId(track));
|
||||||
try {
|
try {
|
||||||
addCachedThumbnails(json, folder);
|
addCachedThumbnails(json, folder);
|
||||||
} catch (JSONException | MalformedURLException e) {
|
} catch (JSONException | MalformedURLException e) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
|
||||||
public class MusicCallbackBuilder {
|
public class MusicCallbackBuilder {
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import static ru.vtosters.lite.proxy.ProxyUtils.getApi;
|
|||||||
* stuff for converting playlists DTO
|
* stuff for converting playlists DTO
|
||||||
*/
|
*/
|
||||||
public class PlaylistConverter {
|
public class PlaylistConverter {
|
||||||
public static List<MusicTrack> getPlaylist(Playlist oldPlaylist) {
|
public static List<MusicTrack> getPlaylist(Playlist oldPlaylist)
|
||||||
|
{
|
||||||
var tracks = requestPlaylist(0, oldPlaylist.b, oldPlaylist.Q, oldPlaylist.a);
|
var tracks = requestPlaylist(0, oldPlaylist.b, oldPlaylist.Q, oldPlaylist.a);
|
||||||
return tracks.stream().filter(track -> !track.D.isEmpty()).collect(Collectors.toList());
|
return tracks.stream().filter(track -> !track.D.isEmpty()).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
package ru.vtosters.lite.music.converter.ts;
|
package ru.vtosters.lite.music.converter.ts;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import com.arthenica.ffmpegkit.FFmpegKit;
|
import com.arthenica.ffmpegkit.FFmpegKit;
|
||||||
import com.arthenica.ffmpegkit.ReturnCode;
|
import com.arthenica.ffmpegkit.ReturnCode;
|
||||||
import com.vk.core.util.ToastUtils;
|
import com.vk.core.util.ToastUtils;
|
||||||
import com.vk.dto.music.Artist;
|
import com.vk.dto.music.Artist;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import com.vtosters.lite.R;
|
import com.vtosters.lite.R;
|
||||||
|
import ru.vtosters.lite.music.downloader.M3UDownloader;
|
||||||
import ru.vtosters.lite.utils.AndroidUtils;
|
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.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class FFMpeg {
|
public class FFMpeg
|
||||||
private static final int BUFFER_SIZE = 2048;
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
|
* @param in Merged .ts file via {@link ru.vtosters.lite.music.converter.ts.TSMerger}
|
||||||
@@ -25,79 +23,48 @@ public class FFMpeg {
|
|||||||
* @param track Track to be converted
|
* @param track Track to be converted
|
||||||
* @return True if success, else false
|
* @return True if success, else false
|
||||||
*/
|
*/
|
||||||
public static boolean convert(File in, String out, MusicTrack track) {
|
public static void convert(File in, String out, MusicTrack track)
|
||||||
|
throws Throwable
|
||||||
|
{
|
||||||
// convert .ts file to .mp3 file via ffmpeg
|
// convert .ts file to .mp3 file via ffmpeg
|
||||||
var sb = new StringBuilder();
|
final var cmd=" -y -i \""+in.getAbsolutePath()+'"'
|
||||||
sb.append("-y -i \"").append(in.getAbsolutePath()).append("\"");
|
+" -map 0 -dn"
|
||||||
sb.append(" -map 0 -dn ");
|
+" -loglevel error"
|
||||||
//sb.append(" -loglevel error");
|
+" -hide_banner"
|
||||||
//sb.append(" -hide_banner");
|
+" -write_id3v2 1"
|
||||||
sb.append(" -write_id3v2 1");
|
// +" -metadata title=\""+IOUtils.getValidFileName(M3UDownloader.getTitle(track))+'"'
|
||||||
|
// +" -metadata artist=\""+normalizeMetadata(track.L.stream().map(Artist::w1).collect(Collectors.joining(", ")))+'"'
|
||||||
String title = null;
|
// +" -metadata album=\""+normalizeMetadata(track.I.getTitle())+'"'
|
||||||
|
+" -c copy \""+out+'"';
|
||||||
if (track.f != null) {
|
final var ses=FFmpegKit.execute(cmd);
|
||||||
title = track.f;
|
if(ses.getReturnCode().isValueCancel())throw new RuntimeException(ses.getLogsAsString());
|
||||||
if (!track.g.isEmpty()) {
|
|
||||||
title += " (" + track.g + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title != null) {
|
|
||||||
sb.append(" -metadata title=\"").append(IOUtils.getValidFileName(title)).append("\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track.L != null) {
|
|
||||||
sb.append(" -metadata artist=\"").append(track.L.stream().map(Artist::w1).collect(Collectors.joining(", "))).append("\"");
|
|
||||||
} else if (track.C != null) {
|
|
||||||
sb.append(" -metadata artist=\"").append(track.C).append("\"");
|
|
||||||
}
|
|
||||||
if (track.I != null) {
|
|
||||||
sb.append(" -metadata album=\"").append(track.I.getTitle()).append("\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(" -c copy \"").append(out).append("\"");
|
|
||||||
|
|
||||||
var command = sb.toString();
|
|
||||||
|
|
||||||
var session = FFmpegKit.execute(command);
|
|
||||||
var rc = session.getReturnCode();
|
|
||||||
|
|
||||||
if (ReturnCode.isSuccess(rc)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e("FFMpeg", "FFmpegKit failed with return code " + rc);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void copyAssets() {
|
static String normalizeMetadata(String in)
|
||||||
try {
|
{ return in.replace('"','‟'); }
|
||||||
String[] files = AndroidUtils.getGlobalContext().getAssets().list("ffmpeg");
|
|
||||||
for (String file : files) {
|
|
||||||
if (file.endsWith(".so")) {
|
|
||||||
InputStream in = AndroidUtils.getGlobalContext().getAssets().open("ffmpeg" + File.separator + file);
|
|
||||||
FileOutputStream out = new FileOutputStream(AndroidUtils.getGlobalContext().getFilesDir() + File.separator + file);
|
|
||||||
byte[] buffer = new byte[BUFFER_SIZE];
|
|
||||||
int read;
|
|
||||||
while ((read = in.read(buffer)) != -1) {
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
in.close();
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkFFMpegLibs() {
|
// private static void copyAssets()
|
||||||
var lib = new File(AndroidUtils.getGlobalContext().getFilesDir(), "libffmpegkit_abidetect.so");
|
// throws IOException
|
||||||
if (!lib.exists()) {
|
// {
|
||||||
ToastUtils.a(R.string.ffmpegkit_copy);
|
// try
|
||||||
copyAssets();
|
// {
|
||||||
}
|
// for (var filename:AndroidUtils.getGlobalContext().getAssets().list("ffmpeg"))
|
||||||
}
|
// if(filename.endsWith(".so"))
|
||||||
|
// IOUtils.writeToFile(
|
||||||
|
// new File(AndroidUtils.getGlobalContext().getFilesDir(),filename),
|
||||||
|
// IOUtils.readFully(AndroidUtils.getGlobalContext().getAssets().open("ffmpeg"+File.separator+filename)));
|
||||||
|
// }
|
||||||
|
// catch(IOException e)
|
||||||
|
// { throw e; }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private static void checkFFMpegLibs()
|
||||||
|
// {
|
||||||
|
// var lib=new File(AndroidUtils.getGlobalContext().getFilesDir(),"libffmpegkit_abidetect.so");
|
||||||
|
// if(!lib.exists())
|
||||||
|
// {
|
||||||
|
// ToastUtils.a(R.string.ffmpegkit_copy);
|
||||||
|
// copyAssets();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
package ru.vtosters.lite.music.converter.ts;
|
package ru.vtosters.lite.music.converter.ts;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class TSMerger {
|
public class TSMerger {
|
||||||
/**
|
/**
|
||||||
@@ -17,58 +16,23 @@ public class TSMerger {
|
|||||||
* @param in Directory with .ts files
|
* @param in Directory with .ts files
|
||||||
* @return True if success, else false
|
* @return True if success, else false
|
||||||
*/
|
*/
|
||||||
public static boolean merge(File in, File out) {
|
public static void merge(final File in,final File out)
|
||||||
var tsesList = in.listFiles();
|
throws Throwable
|
||||||
|
{
|
||||||
if (tsesList == null) {
|
var tsFiles=in.listFiles((dir,name) -> name.matches(".*\\d+\\.ts"));
|
||||||
return false;
|
if(tsFiles==null||tsFiles.length==0)throw new IOException("Dir "+in.getAbsolutePath()+" doesn't contain indexed \"*.ts\" files");
|
||||||
}
|
Arrays.sort(tsFiles,Comparator.comparingInt(TSMerger::getFileIndex));
|
||||||
|
final var bis=new ByteArrayOutputStream();
|
||||||
var mergedByteArrayStream = new ByteArrayOutputStream();
|
for(var f:tsFiles)bis.write(IOUtils.readFully(f));
|
||||||
|
bis.close();
|
||||||
sortTsesList(tsesList);
|
IOUtils.writeToFile(out,bis.toByteArray());
|
||||||
|
|
||||||
for (var file : tsesList) {
|
|
||||||
if (file.getName().endsWith(".ts")) {
|
|
||||||
try {
|
|
||||||
mergedByteArrayStream.write(IOUtils.readFully(file));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeOutputFile(out, mergedByteArrayStream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sortTsesList(File[] tsesList) {
|
static int getFileIndex(final File f)
|
||||||
Arrays.sort(tsesList, (ts1, ts2) -> {
|
{
|
||||||
String ts1Name = ts1.getName();
|
final var p=Pattern.compile(".*(\\d+)\\.ts");
|
||||||
String ts2Name = ts2.getName();
|
final var m=p.matcher(f.getName());
|
||||||
int i1 = Integer.parseInt(ts1Name.substring(0, ts1Name.indexOf('.', 1)));
|
if(m.find())return Integer.parseInt(m.group(1));
|
||||||
int i2 = Integer.parseInt(ts2Name.substring(0, ts2Name.indexOf('.', 1)));
|
throw new NullPointerException("Filename has no index"+f.getName());
|
||||||
return Integer.compare(i1, i2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean writeOutputFile(File out, ByteArrayOutputStream mergedByteArrayStream) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
try {
|
|
||||||
Files.write(out.toPath(), mergedByteArrayStream.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try (FileOutputStream fos = new FileOutputStream(out)) {
|
|
||||||
fos.write(mergedByteArrayStream.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
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 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.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;
|
||||||
|
|
||||||
|
public class M3UDownloader
|
||||||
|
implements ITrackDownloader
|
||||||
|
{
|
||||||
|
public static M3UDownloader getInstance()
|
||||||
|
{ return Holder.INSTANCE; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadTrack(
|
||||||
|
final MusicTrack track,
|
||||||
|
final File outDir,
|
||||||
|
final Callback callback,
|
||||||
|
final 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse(
|
||||||
|
final File outDir,
|
||||||
|
final Callback callback,
|
||||||
|
final MusicTrack track,
|
||||||
|
final boolean cache)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
final var parser=new com.google.android.exoplayer2.source.hls.playlist.h(e.a(track.D));
|
||||||
|
final var baseUri=track.D.substring(0,track.D.lastIndexOf("/")+1);
|
||||||
|
final var segments=((f)parser.a(Uri.parse(baseUri),IOUtils.openStream(track.D))).o;
|
||||||
|
final var progress=new AtomicInteger();
|
||||||
|
callback.onProgress(5);
|
||||||
|
final var resultTs=getResultTsFile(track);
|
||||||
|
final 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(() -> 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(
|
||||||
|
final String baseUri,
|
||||||
|
final List< a > segments,
|
||||||
|
final AtomicInteger progress,
|
||||||
|
final Callback callback,
|
||||||
|
final MusicTrack track)
|
||||||
|
{
|
||||||
|
final var futures=new CompletableFuture[segments.size()];
|
||||||
|
for(var i=0;i<segments.size();++i)
|
||||||
|
{
|
||||||
|
final var segment=segments.get(i);
|
||||||
|
final var filename=i+".ts";
|
||||||
|
futures[i]=CompletableFuture
|
||||||
|
.runAsync(() -> processSegment(baseUri,segment,filename,progress,segments.size(),callback,track));
|
||||||
|
}
|
||||||
|
return futures;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void processSegment(
|
||||||
|
final String baseUri,
|
||||||
|
final a segment,
|
||||||
|
final String filename,
|
||||||
|
final AtomicInteger progress,
|
||||||
|
final int size,
|
||||||
|
final Callback callback,
|
||||||
|
final MusicTrack track)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var buff=IOUtils.readFully(IOUtils.openStream(baseUri+segment.a/*url*/));
|
||||||
|
if(!TextUtils.isEmpty(segment.g))
|
||||||
|
{
|
||||||
|
final var cipher=Cipher.getInstance("AES/CBC/PKCS7Padding");
|
||||||
|
final 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); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static public String getTitle(final MusicTrack track)
|
||||||
|
{ return track.f+(!TextUtils.isEmpty(track.g)?+'('+track.g+')':null); }
|
||||||
|
|
||||||
|
static File getTsesDir(final MusicTrack track)
|
||||||
|
{
|
||||||
|
var filesDir=AndroidUtils.getGlobalContext().getFilesDir();
|
||||||
|
var tsesDir=new File(filesDir,String.valueOf(track.d));
|
||||||
|
tsesDir.mkdirs();
|
||||||
|
return tsesDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getResultTsFile(final MusicTrack track)
|
||||||
|
{ return new File(getTsesDir(track),"result.ts"); }
|
||||||
|
|
||||||
|
static File getResultMp3File(final File outDir,final boolean cache,final MusicTrack track)
|
||||||
|
{ return new File(outDir,IOUtils.getValidFileName((cache?"track":track.C+" - "+getTitle(track))+".mp3")); }
|
||||||
|
|
||||||
|
// Initialization-on-demand
|
||||||
|
private static class Holder
|
||||||
|
{
|
||||||
|
private static final M3UDownloader INSTANCE = new M3UDownloader();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -70,7 +70,7 @@
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.Callback callback) {
|
// public void downloadTrack(String url, File outDir, ru.vtosters.lite.music.interfaces.Callback callback) {
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
@@ -2,34 +2,36 @@ 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.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
public class PlaylistDownloader {
|
public class PlaylistDownloader
|
||||||
public static void downloadPlaylist(List<MusicTrack> playlist, String playlistName, String path, Callback callback) {
|
{
|
||||||
File outDir = new File(path);
|
static public void downloadPlaylist(final List< MusicTrack > playlist,
|
||||||
|
final String playlistName,
|
||||||
|
final String path,
|
||||||
|
final Callback callback)
|
||||||
|
{
|
||||||
|
final var outDir=new File(path);
|
||||||
if (!outDir.exists())
|
if (!outDir.exists())
|
||||||
if (outDir.mkdirs())
|
if(outDir.mkdirs())Log.v("PlaylistDownloader","Directory created");
|
||||||
Log.v("PlaylistDownloader", "Directory created");
|
else Log.e("PlaylistDownloader","Directory creation failed");
|
||||||
else
|
final var downloadedTracks=new AtomicInteger();
|
||||||
Log.e("PlaylistDownloader", "Directory creation failed");
|
for (var musicTrack:playlist)
|
||||||
|
TrackDownloader.downloadTrack(musicTrack, path, new Callback()
|
||||||
var downloadedTracks = new AtomicInteger(0);
|
{
|
||||||
|
|
||||||
for (MusicTrack musicTrack : playlist) {
|
|
||||||
TrackDownloader.downloadTrack(musicTrack, path, new Callback() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(int progress) {
|
public void onProgress(int progress)
|
||||||
}
|
{ }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess()
|
||||||
var currentProgress = downloadedTracks.incrementAndGet();
|
{ callback.onProgress(downloadedTracks.incrementAndGet()); }
|
||||||
callback.onProgress(currentProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure() {
|
public void onFailure() {
|
||||||
@@ -37,36 +39,32 @@ public class PlaylistDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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) {
|
static public void cachePlaylist(final List< MusicTrack > playlist,final Callback callback)
|
||||||
var downloadedTracks = new AtomicInteger(0);
|
{
|
||||||
|
final var downloadedTracks=new AtomicInteger();
|
||||||
for (MusicTrack musicTrack : playlist) {
|
for (var musicTrack:playlist)
|
||||||
TrackDownloader.cacheTrack(musicTrack, new Callback() {
|
TrackDownloader.cacheTrack(musicTrack,new Callback()
|
||||||
|
{
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(int progress) {
|
public void onProgress(int progress)
|
||||||
}
|
{ }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess()
|
||||||
var currentProgress = downloadedTracks.incrementAndGet();
|
{ callback.onProgress(downloadedTracks.incrementAndGet()); }
|
||||||
callback.onProgress(currentProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure() {
|
public void onFailure()
|
||||||
callback.onFailure();
|
{ callback.onFailure(); }
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSizeReceived(long size, long header) {
|
public void onSizeReceived(long size, long header)
|
||||||
}
|
{ }
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ package ru.vtosters.lite.music.downloader;
|
|||||||
|
|
||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
import ru.vtosters.lite.utils.IOUtils;
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -11,36 +11,43 @@ import java.net.URL;
|
|||||||
public class ThumbnailDownloader {
|
public class ThumbnailDownloader {
|
||||||
|
|
||||||
public static void downloadThumbnails(MusicTrack track)
|
public static void downloadThumbnails(MusicTrack track)
|
||||||
throws IOException {
|
throws IOException
|
||||||
var obj = track.J();
|
{
|
||||||
var trackId = LibVKXClient.asId(track);
|
do
|
||||||
|
{
|
||||||
if (!obj.has("album")) return;
|
final var json=track.J();
|
||||||
var album = obj.optJSONObject("album");
|
final var album=json.optJSONObject("album");
|
||||||
|
if(album==null)break;
|
||||||
if (!album.has("thumb")) return;
|
final var thumb=album.optJSONObject("thumb");
|
||||||
var thumb = album.optJSONObject("thumb");
|
if(thumb==null)break;
|
||||||
|
final var sizes=thumb.optJSONArray("sizes");
|
||||||
if (thumb.has("sizes")) {
|
if(sizes==null)
|
||||||
var sizes = thumb.optJSONArray("sizes");
|
{
|
||||||
for (int i = 0; i < sizes.length(); i++) {
|
final var names=thumb.names();
|
||||||
var size = sizes.optJSONObject(i);
|
if(names==null)break;
|
||||||
var width = size.optInt("width");
|
final var trackId=LibVKXClient.asId(track);
|
||||||
var src = size.optString("src");
|
for(var i=0;i<names.length();++i)
|
||||||
downloadThumbnail(src, width, trackId);
|
{
|
||||||
|
var name=names.optString(i);
|
||||||
|
downloadThumbnail(thumb.optString(name),Integer.parseInt(name.substring(6)),trackId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
else
|
||||||
var names = thumb.names();
|
{
|
||||||
for (int i = 0; i < names.length(); i++) {
|
final var trackId=LibVKXClient.asId(track);
|
||||||
var name = names.optString(i);
|
for(var i=0;i<sizes.length();++i)
|
||||||
downloadThumbnail(thumb.optString(name), Integer.parseInt(name.substring(6)), trackId);
|
{
|
||||||
|
var size=sizes.optJSONObject(i);
|
||||||
|
var width=size.optInt("width");
|
||||||
|
var src=size.optString("src");
|
||||||
|
downloadThumbnail(src,width,trackId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}while(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadThumbnail(String url, int res, String trackId)
|
private static void downloadThumbnail(final String url,final int res,final String trackId)
|
||||||
throws IOException {
|
throws IOException
|
||||||
var data = IOUtils.readFully(new URL(url).openStream());
|
{ IOUtils.writeToFile(MusicCacheStorageUtils.getTrackThumb(trackId,res),IOUtils.readFully(new URL(url).openStream())); }
|
||||||
IOUtils.writeToFile(FileCacheImplementation.getTrackThumbnail(trackId, res), data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import android.util.Log;
|
|||||||
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
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.concurrent.VTExecutors;
|
||||||
import ru.vtosters.lite.music.Callback;
|
import ru.vtosters.lite.music.interfaces.Callback;
|
||||||
import ru.vtosters.lite.music.M3UDownloader;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.utils.music.MusicCacheStorageUtils;
|
||||||
import ru.vtosters.lite.music.cache.FileCacheImplementation;
|
|
||||||
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
import ru.vtosters.lite.music.interfaces.ITrackDownloader;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -24,7 +23,7 @@ public class TrackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void cacheTrack(MusicTrack track, Callback callback) {
|
public static void cacheTrack(MusicTrack track, Callback callback) {
|
||||||
if (CacheDatabaseDelegate.isCached(track.y1())) {
|
if (MusicCacheImpl.isCachedTrack(LibVKXClient.asId(track))) {
|
||||||
callback.onSuccess();
|
callback.onSuccess();
|
||||||
return;
|
return;
|
||||||
} else if (track.D.isEmpty()) {
|
} else if (track.D.isEmpty()) {
|
||||||
@@ -33,7 +32,7 @@ public class TrackDownloader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = FileCacheImplementation.getTrackFolder(LibVKXClient.asId(track)).getAbsolutePath();
|
var path = MusicCacheStorageUtils.getTrackDirById(LibVKXClient.asId(track)).getAbsolutePath();
|
||||||
download(track, path, callback, M3UDownloader.getInstance(), true);
|
download(track, path, callback, M3UDownloader.getInstance(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.vtosters.lite.music;
|
package ru.vtosters.lite.music.interfaces;
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void onProgress(int progress);
|
void onProgress(int progress);
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package ru.vtosters.lite.music.interfaces;
|
package ru.vtosters.lite.music.interfaces;
|
||||||
|
|
||||||
import com.vk.dto.music.MusicTrack;
|
import com.vk.dto.music.MusicTrack;
|
||||||
import ru.vtosters.lite.music.Callback;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ru.vtosters.lite.concurrent.VTExecutors;
|
|||||||
import ru.vtosters.lite.downloaders.AudioDownloader;
|
import ru.vtosters.lite.downloaders.AudioDownloader;
|
||||||
import ru.vtosters.lite.downloaders.VideoDownloader;
|
import ru.vtosters.lite.downloaders.VideoDownloader;
|
||||||
import ru.vtosters.lite.music.LastFMScrobbler;
|
import ru.vtosters.lite.music.LastFMScrobbler;
|
||||||
import ru.vtosters.lite.music.cache.CacheDatabaseDelegate;
|
import ru.vtosters.lite.music.cache.MusicCacheImpl;
|
||||||
import ru.vtosters.lite.proxy.ProxyUtils;
|
import ru.vtosters.lite.proxy.ProxyUtils;
|
||||||
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
|
import ru.vtosters.lite.ui.adapters.ImagineArrayAdapter;
|
||||||
import ru.vtosters.lite.utils.AccountManagerUtils;
|
import ru.vtosters.lite.utils.AccountManagerUtils;
|
||||||
@@ -108,9 +108,9 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
findPreference("lastfm_enabled").setEnabled(false);
|
findPreference("lastfm_enabled").setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter), CacheDatabaseDelegate.getTrackCount()));
|
findPreference("cached_tracks").setSummary(String.format(requireContext().getString(R.string.cached_tracks_counter),MusicCacheImpl.getTracksCount()));
|
||||||
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
|
findPreference("cached_tracks").setOnPreferenceClickListener(preference -> {
|
||||||
if (CacheDatabaseDelegate.getTrackCount() == 0) {
|
if (MusicCacheImpl.isEmpty()) {
|
||||||
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
|
AndroidUtils.sendToast(requireContext().getString(R.string.no_cache_error));
|
||||||
} else {
|
} else {
|
||||||
delcache(requireContext());
|
delcache(requireContext());
|
||||||
@@ -123,7 +123,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
findPreference("invertCachedTracks").setVisible(CacheDatabaseDelegate.hasTracks());
|
findPreference("invertCachedTracks").setVisible(!MusicCacheImpl.isEmpty());
|
||||||
|
|
||||||
if (!LibVKXClient.isVkxInstalled()) {
|
if (!LibVKXClient.isVkxInstalled()) {
|
||||||
findPreference("vkx_sett").setVisible(false);
|
findPreference("vkx_sett").setVisible(false);
|
||||||
@@ -217,7 +217,7 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
|
|||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(R.string.cached_tracks_remove_confirm)
|
.setMessage(R.string.cached_tracks_remove_confirm)
|
||||||
.setPositiveButton(R.string.yes,
|
.setPositiveButton(R.string.yes,
|
||||||
(dialog, which) -> executor.submit(CacheDatabaseDelegate::clear))
|
(dialog, which) -> executor.submit(MusicCacheImpl::clear))
|
||||||
.setNeutralButton(R.string.no,
|
.setNeutralButton(R.string.no,
|
||||||
(dialog, which) -> dialog.cancel())
|
(dialog, which) -> dialog.cancel())
|
||||||
.show();
|
.show();
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package ru.vtosters.lite.ui.fragments;
|
package ru.vtosters.lite.ui.fragments;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.*;
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|||||||
@@ -1,40 +1,13 @@
|
|||||||
package ru.vtosters.lite.utils;
|
package ru.vtosters.lite.utils;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.CipherInputStream;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class IOUtils {
|
public class IOUtils {
|
||||||
public static int BUFFER_SIZE = 8192;
|
public static int BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
public static byte[] decodeStream(InputStream encIs, byte[] aesKey)
|
|
||||||
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IOException {
|
|
||||||
CipherInputStream cip = new CipherInputStream(encIs, getCipher(aesKey));
|
|
||||||
return readFully(cip);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Cipher getCipher(byte[] aesKey)
|
|
||||||
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
Key keySpec = new SecretKeySpec(aesKey, "AES");
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
Arrays.fill(iv, (byte) 0x0);
|
|
||||||
IvParameterSpec ivps = new IvParameterSpec(iv);
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
|
|
||||||
return cipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InputStream openStream(String url)
|
public static InputStream openStream(String url)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
URLConnection connection = new URL(url).openConnection();
|
URLConnection connection = new URL(url).openConnection();
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package ru.vtosters.lite.utils.music;
|
||||||
|
|
||||||
|
import ru.vtosters.lite.utils.AndroidUtils;
|
||||||
|
import ru.vtosters.lite.utils.IOUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class MusicCacheStorageUtils
|
||||||
|
{
|
||||||
|
static public File getCacheStorageDir()
|
||||||
|
{ return AndroidUtils.getGlobalContext().getExternalFilesDir("vt_tracks"); }
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
static public File getTrackDirById(final String trackId)
|
||||||
|
{
|
||||||
|
final var res=new File(getCacheStorageDir(),trackId);
|
||||||
|
res.mkdirs();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public File getTrackFile(final String trackId)
|
||||||
|
{ return new File(getTrackDirById(trackId),"track.mp3"); }
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
static public File getThumbDirById(final String trackId)
|
||||||
|
{
|
||||||
|
final var res=new File(getTrackDirById(trackId),"thumbs");
|
||||||
|
res.mkdirs();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public File getTrackThumb(final String trackId,final int factor)
|
||||||
|
{ return new File(getThumbDirById(trackId),"thumb_"+factor+".png"); }
|
||||||
|
|
||||||
|
static public void removeTrackDirById(final String trackId)
|
||||||
|
{ IOUtils.deleteRecursive(getTrackDirById(trackId)); }
|
||||||
|
|
||||||
|
static public void clear()
|
||||||
|
{ IOUtils.deleteRecursive(getCacheStorageDir()); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package ru.vtosters.lite.utils.music;
|
||||||
|
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.ILibVkxService;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClient;
|
||||||
|
import bruhcollective.itaysonlab.libvkx.client.LibVKXClientImpl;
|
||||||
|
|
||||||
|
public class VKXUtils
|
||||||
|
{
|
||||||
|
public static boolean isVkxCached(int playlist_id, int owner_id) {
|
||||||
|
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
||||||
|
@Override
|
||||||
|
public Boolean run(ILibVkxService service) {
|
||||||
|
try {
|
||||||
|
return service.isPlaylistCached(playlist_id, owner_id);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean defaultValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isVkxCached(String trackId) {
|
||||||
|
return LibVKXClient.getInstance().runOnServiceSync(new LibVKXClientImpl.LibVKXActionGeneric<>() {
|
||||||
|
@Override
|
||||||
|
public Boolean run(ILibVkxService service) {
|
||||||
|
try {
|
||||||
|
var splits = trackId.split("_");
|
||||||
|
var id = Integer.parseInt(splits[1]);
|
||||||
|
var owner = Integer.parseInt(splits[0]);
|
||||||
|
return service.isTrackCached(id, owner);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean defaultValue() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
invoke-static {p1, v1}, Lkotlin/jvm/internal/Intrinsics;->a(Ljava/lang/Object;Ljava/lang/String;)V
|
invoke-static {p1, v1}, Lkotlin/jvm/internal/Intrinsics;->a(Ljava/lang/Object;Ljava/lang/String;)V
|
||||||
|
|
||||||
invoke-static/range {p1 .. p1}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->injectIntoExistingCatalog(Lorg/json/JSONObject;)V
|
invoke-static/range {p1 .. p1}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->injectIntoExistingCatalog(Lorg/json/JSONObject;)V
|
||||||
|
|
||||||
invoke-virtual {v0, p1}, Lcom/vk/catalog2/core/CatalogParser;->c(Lorg/json/JSONObject;)Lcom/vk/catalog2/core/api/dto/CatalogResponse;
|
invoke-virtual {v0, p1}, Lcom/vk/catalog2/core/CatalogParser;->c(Lorg/json/JSONObject;)Lcom/vk/catalog2/core/api/dto/CatalogResponse;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -83,8 +83,8 @@
|
|||||||
.locals 1
|
.locals 1
|
||||||
|
|
||||||
.line 21
|
.line 21
|
||||||
# ru.vtosters.lite.music.hook.MusicArtistSelectorHook.navigateToSearch(Context context, String artist)
|
# ru.vtosters.hooks.music.MusicArtistSelectorHook.navigateToSearch(Context context, String artist)
|
||||||
invoke-static {p1, p2}, Lru/vtosters/lite/music/hook/MusicArtistSelectorHook;->navigateToSearch(Landroid/content/Context;Ljava/lang/String;)V
|
invoke-static {p1, p2}, Lru/vtosters/hooks/music/MusicArtistSelectorHook;->navigateToSearch(Landroid/content/Context;Ljava/lang/String;)V
|
||||||
|
|
||||||
# new-instance v0, Lcom/vk/music/search/MusicSearchFragment$a;
|
# new-instance v0, Lcom/vk/music/search/MusicSearchFragment$a;
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -466,7 +466,7 @@
|
|||||||
:cond_8
|
:cond_8
|
||||||
iget-object v2, v0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheet;->b:Lcom/vk/dto/music/Playlist;
|
iget-object v2, v0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheet;->b:Lcom/vk/dto/music/Playlist;
|
||||||
|
|
||||||
invoke-static {v1, v2}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/Playlist;)Ljava/util/ArrayList;
|
invoke-static {v1, v2}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/Playlist;)Ljava/util/ArrayList;
|
||||||
|
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -139,7 +139,7 @@
|
|||||||
|
|
||||||
move-result p1
|
move-result p1
|
||||||
|
|
||||||
invoke-static {p1, p2}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/Playlist;)Z
|
invoke-static {p1, p2}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/Playlist;)Z
|
||||||
|
|
||||||
move-result v3
|
move-result v3
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
|
|
||||||
iget-object v0, p0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener;->e:Lcom/vk/dto/music/Playlist;
|
iget-object v0, p0, Lcom/vk/music/bottomsheets/playlist/PlaylistBottomSheetClickListener;->e:Lcom/vk/dto/music/Playlist;
|
||||||
|
|
||||||
invoke-static/range {v0 .. v0}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V
|
invoke-static/range {v0 .. v0}, Lru/vtosters/hooks/music/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V
|
||||||
|
|
||||||
goto/16 :goto_0
|
goto/16 :goto_0
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -716,7 +716,7 @@
|
|||||||
|
|
||||||
move-result-object v3
|
move-result-object v3
|
||||||
|
|
||||||
invoke-static {p1, v0, v2, v3}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/MusicTrack;Lcom/vk/music/common/MusicPlaybackLaunchContext;Lcom/vk/dto/music/Playlist;)Z
|
invoke-static {p1, v0, v2, v3}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->injectOnClick(ILcom/vk/dto/music/MusicTrack;Lcom/vk/music/common/MusicPlaybackLaunchContext;Lcom/vk/dto/music/Playlist;)Z
|
||||||
|
|
||||||
move-result v3
|
move-result v3
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
move-object/from16 v10, p1
|
move-object/from16 v10, p1
|
||||||
|
|
||||||
invoke-static {v1, v10}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->hookDownloadBTN(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList;
|
invoke-static {v1, v10}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->hookDownloadBTN(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList;
|
||||||
|
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
|
|
||||||
@@ -705,7 +705,7 @@
|
|||||||
|
|
||||||
.line 31
|
.line 31
|
||||||
:cond_9
|
:cond_9
|
||||||
invoke-static {v3, v1}, Lru/vtosters/lite/music/hook/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList;
|
invoke-static {v3, v1}, Lru/vtosters/hooks/music/MusicBottomSheetHook;->hook(Ljava/util/ArrayList;Lcom/vk/dto/music/MusicTrack;)Ljava/util/ArrayList;
|
||||||
|
|
||||||
move-result-object v3
|
move-result-object v3
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
.end annotation
|
.end annotation
|
||||||
|
|
||||||
invoke-static {}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->eligibleForOfflineCaching()Z
|
invoke-static {}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->eligibleForOfflineCaching()Z
|
||||||
|
|
||||||
move-result v0
|
move-result v0
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
move-result-object v0
|
move-result-object v0
|
||||||
|
|
||||||
invoke-static {v0}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->createOfflineRx(Lcom/vk/catalog2/core/CatalogParser;)Lio/reactivex/Observable;
|
invoke-static {v0}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->createOfflineRx(Lcom/vk/catalog2/core/CatalogParser;)Lio/reactivex/Observable;
|
||||||
|
|
||||||
move-result-object v0
|
move-result-object v0
|
||||||
|
|
||||||
|
|||||||
@@ -1780,13 +1780,13 @@
|
|||||||
|
|
||||||
.line 90
|
.line 90
|
||||||
:cond_3
|
:cond_3
|
||||||
invoke-static {}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->eligibleForOfflineCaching()Z
|
invoke-static {}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->eligibleForOfflineCaching()Z
|
||||||
|
|
||||||
move-result v0
|
move-result v0
|
||||||
|
|
||||||
if-eqz v0, :cond_4
|
if-eqz v0, :cond_4
|
||||||
|
|
||||||
invoke-static {p0}, Lru/vtosters/lite/music/cache/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/IpcPlayerModelImpl;)V
|
invoke-static {p0}, Lru/vtosters/hooks/music/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/IpcPlayerModelImpl;)V
|
||||||
|
|
||||||
return-void
|
return-void
|
||||||
|
|
||||||
|
|||||||
@@ -1362,13 +1362,13 @@
|
|||||||
|
|
||||||
.line 44
|
.line 44
|
||||||
:cond_0
|
:cond_0
|
||||||
invoke-static {}, Lru/vtosters/lite/music/cache/injectors/TracklistInjector;->eligibleForOfflineCaching()Z
|
invoke-static {}, Lru/vtosters/hooks/music/injectors/TracklistInjector;->eligibleForOfflineCaching()Z
|
||||||
|
|
||||||
move-result v0
|
move-result v0
|
||||||
|
|
||||||
if-eqz v0, :cond_1
|
if-eqz v0, :cond_1
|
||||||
|
|
||||||
invoke-static {p0}, Lru/vtosters/lite/music/cache/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/PlayerModelImpl;)V
|
invoke-static {p0}, Lru/vtosters/hooks/music/injectors/ShuffleInjector;->shuffleTracks(Lcom/vk/music/model/PlayerModelImpl;)V
|
||||||
|
|
||||||
return-void
|
return-void
|
||||||
|
|
||||||
|
|||||||
@@ -239,7 +239,7 @@
|
|||||||
|
|
||||||
const/4 v1, 0x1
|
const/4 v1, 0x1
|
||||||
|
|
||||||
invoke-static {p1}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectGetPlaylist(Lcom/vk/api/audio/AudioGetPlaylist;)Lio/reactivex/Observable;
|
invoke-static {p1}, Lru/vtosters/hooks/music/injectors/PlaylistInjector;->injectGetPlaylist(Lcom/vk/api/audio/AudioGetPlaylist;)Lio/reactivex/Observable;
|
||||||
|
|
||||||
move-result-object v2
|
move-result-object v2
|
||||||
|
|
||||||
|
|||||||
@@ -593,7 +593,7 @@
|
|||||||
|
|
||||||
move-result-object v0
|
move-result-object v0
|
||||||
|
|
||||||
invoke-static/range {v0 .. v0}, Lru/vtosters/lite/music/cache/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V
|
invoke-static/range {v0 .. v0}, Lru/vtosters/hooks/music/injectors/PlaylistInjector;->injectDownloadPlaylist(Lcom/vk/dto/music/Playlist;)V
|
||||||
|
|
||||||
return-void
|
return-void
|
||||||
|
|
||||||
|
|||||||
@@ -423,9 +423,9 @@
|
|||||||
.end annotation
|
.end annotation
|
||||||
.end param
|
.end param
|
||||||
|
|
||||||
# ru.vtosters.lite.music.hook.PlaylistFormatterHook -> public static CharSequence titleAndSubtitleMergeFix(Context context, Playlist playlist, @AttrRes int i)
|
# ru.vtosters.hooks.music.PlaylistFormatterHook -> public static CharSequence titleAndSubtitleMergeFix(Context context, Playlist playlist, @AttrRes int i)
|
||||||
.line 15
|
.line 15
|
||||||
invoke-static {p1, p2, p3}, Lru/vtosters/lite/music/hook/PlaylistFormatterHook;->titleAndSubtitleMergeFix(Landroid/content/Context;Lcom/vk/dto/music/Playlist;I)Ljava/lang/CharSequence;
|
invoke-static {p1, p2, p3}, Lru/vtosters/hooks/music/PlaylistFormatterHook;->titleAndSubtitleMergeFix(Landroid/content/Context;Lcom/vk/dto/music/Playlist;I)Ljava/lang/CharSequence;
|
||||||
|
|
||||||
move-result-object p1
|
move-result-object p1
|
||||||
|
|
||||||
@@ -888,7 +888,7 @@
|
|||||||
|
|
||||||
if-eqz v0, :cond_1
|
if-eqz v0, :cond_1
|
||||||
|
|
||||||
invoke-static {p1, p2}, Lru/vtosters/lite/music/hook/PlaylistFormatterHook;->getOwnerText(Landroid/content/Context;Lcom/vk/dto/music/Playlist;)Ljava/lang/String;
|
invoke-static {p1, p2}, Lru/vtosters/hooks/music/PlaylistFormatterHook;->getOwnerText(Landroid/content/Context;Lcom/vk/dto/music/Playlist;)Ljava/lang/String;
|
||||||
|
|
||||||
move-result-object p1
|
move-result-object p1
|
||||||
|
|
||||||
|
|||||||
@@ -112,14 +112,14 @@
|
|||||||
.method static a(Ljava/lang/String;)Ljava/io/File;
|
.method static a(Ljava/lang/String;)Ljava/io/File;
|
||||||
.locals 6
|
.locals 6
|
||||||
|
|
||||||
invoke-static {p0}, Lru/vtosters/hooks/MusicCacheFilesHook;->isTrackExist(Ljava/lang/String;)Z
|
invoke-static {p0}, Lru/vtosters/hooks/music/MusicCacheFilesHook;->isTrackExist(Ljava/lang/String;)Z
|
||||||
|
|
||||||
move-result v5
|
move-result v5
|
||||||
|
|
||||||
if-eqz v5, :cond_228
|
if-eqz v5, :cond_228
|
||||||
|
|
||||||
.line 5
|
.line 5
|
||||||
invoke-static {p0}, Lru/vtosters/hooks/MusicCacheFilesHook;->getTrackFile(Ljava/lang/String;)Ljava/io/File;
|
invoke-static {p0}, Lru/vtosters/hooks/music/MusicCacheFilesHook;->getTrackFile(Ljava/lang/String;)Ljava/io/File;
|
||||||
|
|
||||||
move-result-object v0
|
move-result-object v0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user