cleanup
This commit is contained in:
parent
51ff22abcd
commit
2a02175ade
19 changed files with 959 additions and 1173 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,7 +1,13 @@
|
||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/tasks.xml
|
||||||
|
/.idea/gradle.xml
|
||||||
|
/.idea/dictionaries
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
| Build | Latest Release | Downloads | Contribute |
|
| Build | Latest Release | Downloads | Contribute |
|
||||||
|-------|----------|------------|------------|
|
|-------|----------|------------|------------|
|
||||||
|  | [](https://github.com/deniscerri/ytdlnis/releases/latest) | [](https://github.com/deniscerri/ytdlnis/releases) | [](https://hosted.weblate.org/engage/ytdlnis/?utm_source=widget) |
|
|  | [](https://github.com/deniscerri/ytdlnis/releases) | [](https://img.shields.io/github/downloads/deniscerri/ytdlnis/total?style=flat-square) | [](https://hosted.weblate.org/engage/ytdlnis/?utm_source=widget) |
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ android {
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
archivesBaseName = "YTDLnis-$versionName"
|
archivesBaseName = "YTDLnis-$versionName"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||||
}
|
}
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary true
|
useSupportLibrary true
|
||||||
|
|
@ -75,7 +75,7 @@ android {
|
||||||
abi {
|
abi {
|
||||||
enable true
|
enable true
|
||||||
reset()
|
reset()
|
||||||
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
include 'armeabi-v7a', 'arm64-v8a'
|
||||||
universalApk true
|
universalApk true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,9 +91,9 @@ dependencies {
|
||||||
// implementation project(":library")
|
// implementation project(":library")
|
||||||
// implementation project(":ffmpeg")
|
// implementation project(":ffmpeg")
|
||||||
|
|
||||||
implementation "com.github.JunkFood02.youtubedl-android:library:$youtubedlAndroidVer"
|
implementation "com.github.yausername.youtubedl-android:library:$youtubedlAndroidVer"
|
||||||
implementation "com.github.JunkFood02.youtubedl-android:ffmpeg:$youtubedlAndroidVer"
|
implementation "com.github.yausername.youtubedl-android:ffmpeg:$youtubedlAndroidVer"
|
||||||
implementation "com.github.JunkFood02.youtubedl-android:aria2c:$youtubedlAndroidVer"
|
implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:$appCompatVer"
|
implementation "androidx.appcompat:appcompat:$appCompatVer"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||||
|
|
@ -119,7 +119,7 @@ dependencies {
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||||
implementation 'com.facebook.shimmer:shimmer:0.5.0'
|
implementation 'com.facebook.shimmer:shimmer:0.5.0'
|
||||||
|
|
||||||
implementation "androidx.work:work-runtime-ktx:$workVer"
|
implementation "androidx.work:work-runtime:$workVer"
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:2.4.3"
|
implementation "androidx.room:room-runtime:2.4.3"
|
||||||
implementation "androidx.room:room-ktx:2.4.3"
|
implementation "androidx.room:room-ktx:2.4.3"
|
||||||
|
|
@ -127,8 +127,7 @@ dependencies {
|
||||||
androidTestImplementation "androidx.room:room-testing:2.4.3"
|
androidTestImplementation "androidx.room:room-testing:2.4.3"
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
||||||
implementation "androidx.compose.runtime:runtime:$composeVer"
|
implementation "androidx.compose.runtime:runtime:$composeVer"
|
||||||
androidTestImplementation "com.google.truth:truth:1.1.3"
|
|
||||||
|
|
||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVer"
|
|
||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVer"
|
androidTestImplementation "com.google.truth:truth:1.1.3"
|
||||||
}
|
}
|
||||||
|
|
@ -2,15 +2,10 @@ package com.deniscerri.ytdlnis
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.yausername.aria2c.Aria2c
|
import com.yausername.aria2c.Aria2c
|
||||||
import com.yausername.ffmpeg.FFmpeg
|
import com.yausername.ffmpeg.FFmpeg
|
||||||
|
|
@ -22,7 +17,6 @@ import io.reactivex.exceptions.UndeliverableException
|
||||||
import io.reactivex.observers.DisposableCompletableObserver
|
import io.reactivex.observers.DisposableCompletableObserver
|
||||||
import io.reactivex.plugins.RxJavaPlugins
|
import io.reactivex.plugins.RxJavaPlugins
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class App : Application() {
|
class App : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
|
|
||||||
515
app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.java
Normal file
515
app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.java
Normal file
|
|
@ -0,0 +1,515 @@
|
||||||
|
package com.deniscerri.ytdlnis;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.deniscerri.ytdlnis.database.Video;
|
||||||
|
import com.deniscerri.ytdlnis.page.CustomCommandActivity;
|
||||||
|
import com.deniscerri.ytdlnis.service.DownloadInfo;
|
||||||
|
import com.deniscerri.ytdlnis.service.IDownloaderListener;
|
||||||
|
import com.deniscerri.ytdlnis.service.IDownloaderService;
|
||||||
|
import com.deniscerri.ytdlnis.util.NotificationUtil;
|
||||||
|
import com.yausername.youtubedl_android.DownloadProgressCallback;
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDL;
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDLRequest;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class DownloaderService extends Service {
|
||||||
|
|
||||||
|
private LocalBinder binder = new LocalBinder();
|
||||||
|
private Map<Activity, ArrayList<IDownloaderListener>> activities = new ConcurrentHashMap<>();
|
||||||
|
private DownloadInfo downloadInfo = new DownloadInfo();
|
||||||
|
private LinkedList<Video> downloadQueue = new LinkedList<>();
|
||||||
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
private final NotificationUtil notificationUtil = App.notificationUtil;
|
||||||
|
private Context context;
|
||||||
|
public String downloadProcessID = "processID";
|
||||||
|
private static final String TAG = "DownloaderService";
|
||||||
|
private int downloadNotificationID;
|
||||||
|
|
||||||
|
private final DownloadProgressCallback callback = (progress, etaInSeconds, line) -> {
|
||||||
|
downloadInfo.setProgress((int) progress);
|
||||||
|
downloadInfo.setOutputLine(line);
|
||||||
|
downloadInfo.setDownloadQueue(downloadQueue);
|
||||||
|
String title = getString(R.string.running_ytdlp_command);
|
||||||
|
if (!downloadQueue.isEmpty()){
|
||||||
|
title = downloadQueue.peek().getTitle();
|
||||||
|
}
|
||||||
|
notificationUtil.updateDownloadNotification(downloadNotificationID,
|
||||||
|
line, (int) progress, downloadQueue.size(), title);
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
if (activities.get(activity) != null){
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadProgress(downloadInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception ignored){}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
context = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
Intent theIntent;
|
||||||
|
PendingIntent pendingIntent;
|
||||||
|
|
||||||
|
if(intent.getBooleanExtra("rebind", false)){
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = intent.getIntExtra("id", 1);
|
||||||
|
switch (id){
|
||||||
|
case NotificationUtil.DOWNLOAD_NOTIFICATION_ID:
|
||||||
|
theIntent = new Intent(this, MainActivity.class);
|
||||||
|
pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
downloadNotificationID = id;
|
||||||
|
|
||||||
|
ArrayList<? extends Video> queue = intent.getParcelableArrayListExtra("queue");
|
||||||
|
downloadQueue = new LinkedList<>();
|
||||||
|
downloadQueue.addAll(queue);
|
||||||
|
downloadInfo.setDownloadQueue(downloadQueue);
|
||||||
|
|
||||||
|
String title = downloadInfo.getVideo().getTitle();
|
||||||
|
Notification notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,title);
|
||||||
|
startForeground(downloadNotificationID, notification);
|
||||||
|
startDownload(downloadQueue);
|
||||||
|
break;
|
||||||
|
case NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID:
|
||||||
|
theIntent = new Intent(this, CustomCommandActivity.class);
|
||||||
|
pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
downloadNotificationID = id;
|
||||||
|
|
||||||
|
String command = intent.getStringExtra("command");
|
||||||
|
Notification command_notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,getString(R.string.running_ytdlp_command));
|
||||||
|
startForeground(downloadNotificationID, command_notification);
|
||||||
|
startCommandDownload(command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LocalBinder extends Binder implements IDownloaderService {
|
||||||
|
public DownloaderService getService() {
|
||||||
|
return DownloaderService.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadInfo getDownloadInfo(){
|
||||||
|
return downloadInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActivity(Activity activity, ArrayList<IDownloaderListener> callbacks) {
|
||||||
|
if(!activities.containsKey(activity)){
|
||||||
|
activities.put(activity, callbacks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeActivity(Activity activity) {
|
||||||
|
activities.remove(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateQueue(ArrayList<Video> queue){
|
||||||
|
try{
|
||||||
|
for (int i = 0; i < queue.size(); i++){
|
||||||
|
downloadQueue.add((Video) queue.get(i).clone());
|
||||||
|
}
|
||||||
|
if (downloadQueue.size() == queue.size()){
|
||||||
|
downloadInfo.setDownloadQueue(downloadQueue);
|
||||||
|
startDownload(downloadQueue);
|
||||||
|
}else{
|
||||||
|
downloadInfo.setDownloadQueue(downloadQueue);
|
||||||
|
Toast.makeText(context, getString(R.string.added_to_queue), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}catch (Exception e){
|
||||||
|
Toast.makeText(context, "Couldn't update download queue! :(", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelDownload(boolean cancelAll){
|
||||||
|
try{
|
||||||
|
YoutubeDL.getInstance().destroyProcessById(downloadProcessID);
|
||||||
|
compositeDisposable.clear();
|
||||||
|
//stopForeground(true);
|
||||||
|
if (cancelAll) {
|
||||||
|
onDownloadCancelAll();
|
||||||
|
}
|
||||||
|
}catch(Exception err){
|
||||||
|
Log.e(TAG, err.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeItemFromDownloadQueue(Video video, String type){
|
||||||
|
//if its the same video with the same download type as the current downloading one
|
||||||
|
if (downloadInfo.getVideo().getURL().equals(video.getURL()) && downloadInfo.getVideo().getDownloadedType().equals(video.getDownloadedType())){
|
||||||
|
cancelDownload(false);
|
||||||
|
|
||||||
|
downloadInfo.setDownloadType(type);
|
||||||
|
onDownloadCancel(downloadInfo);
|
||||||
|
|
||||||
|
downloadQueue.pop();
|
||||||
|
downloadInfo.setDownloadQueue(downloadQueue);
|
||||||
|
startDownload(downloadQueue);
|
||||||
|
}else {
|
||||||
|
downloadQueue.remove(video);
|
||||||
|
try{
|
||||||
|
DownloadInfo info = new DownloadInfo();
|
||||||
|
info.setVideo(video);
|
||||||
|
info.setDownloadType(type);
|
||||||
|
onDownloadCancel(info);
|
||||||
|
}catch(Exception ignored){}
|
||||||
|
}
|
||||||
|
Toast.makeText(context, video.getTitle() + " " + getString(R.string.removed_from_queue), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishService(){
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadServiceEnd();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception ignored){}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadCancelAll(){
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadCancelAll(downloadInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception ignored){}
|
||||||
|
onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadEnd(){
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadEnd(downloadInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadCancel(DownloadInfo cancelInfo){
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadCancel(cancelInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception ignored){}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startDownload(LinkedList<Video> videos) {
|
||||||
|
Video video;
|
||||||
|
if(videos.size() == 0){
|
||||||
|
finishService();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
video = videos.peek();
|
||||||
|
} catch (Exception e) {
|
||||||
|
finishService();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadStart(downloadInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception err){
|
||||||
|
err.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = video.getURL();
|
||||||
|
YoutubeDLRequest request = new YoutubeDLRequest(url);
|
||||||
|
String type = video.getDownloadedType();
|
||||||
|
File youtubeDLDir = getDownloadLocation(type);
|
||||||
|
|
||||||
|
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||||
|
|
||||||
|
boolean aria2 = sharedPreferences.getBoolean("aria2", false);
|
||||||
|
if(aria2){
|
||||||
|
request.addOption("--downloader", "libaria2c.so");
|
||||||
|
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"");
|
||||||
|
}else{
|
||||||
|
int concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1);
|
||||||
|
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments);
|
||||||
|
}
|
||||||
|
|
||||||
|
String limitRate = sharedPreferences.getString("limit_rate", "");
|
||||||
|
if(!limitRate.equals("")) request.addOption("-r", limitRate);
|
||||||
|
|
||||||
|
boolean writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false);
|
||||||
|
if(writeThumbnail) {
|
||||||
|
request.addOption("--write-thumbnail");
|
||||||
|
request.addOption("--convert-thumbnails", "png");
|
||||||
|
}
|
||||||
|
|
||||||
|
request.addOption("--no-mtime");
|
||||||
|
|
||||||
|
Set<String> sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filtes", Collections.emptySet());
|
||||||
|
if (!sponsorBlockFilters.isEmpty()){
|
||||||
|
request.addOption("--sponsorblock-remove", String.join(",", sponsorBlockFilters));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.equals("audio")) {
|
||||||
|
request.addOption("-x");
|
||||||
|
String format = video.getAudioFormat();
|
||||||
|
if (format == null) format = sharedPreferences.getString("audio_format", "");
|
||||||
|
request.addOption("--audio-format", format);
|
||||||
|
request.addOption("--embed-metadata");
|
||||||
|
|
||||||
|
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
|
||||||
|
if(embedThumb){
|
||||||
|
request.addOption("--embed-thumbnail");
|
||||||
|
request.addOption("--convert-thumbnails", "png");
|
||||||
|
try{
|
||||||
|
File config = new File(getCacheDir(), "config.txt");
|
||||||
|
String config_data = "--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"";
|
||||||
|
FileOutputStream stream = new FileOutputStream(config);
|
||||||
|
stream.write(config_data.getBytes());
|
||||||
|
stream.close();
|
||||||
|
request.addOption("--config", config.getAbsolutePath());
|
||||||
|
}catch(Exception ignored){}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.addCommands(Arrays.asList("--replace-in-metadata","title",".*.",video.getTitle()));
|
||||||
|
request.addCommands(Arrays.asList("--replace-in-metadata","uploader",".*.",video.getAuthor()));
|
||||||
|
|
||||||
|
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(uploader)s - %(title)s.%(ext)s");
|
||||||
|
} else if (type.equals("video")) {
|
||||||
|
boolean addChapters = sharedPreferences.getBoolean("add_chapters", false);
|
||||||
|
if(addChapters){
|
||||||
|
request.addOption("--sponsorblock-mark", "all");
|
||||||
|
}
|
||||||
|
boolean embedSubs = sharedPreferences.getBoolean("embed_subtitles", false);
|
||||||
|
if(embedSubs){
|
||||||
|
request.addOption("--embed-subs", "");
|
||||||
|
}
|
||||||
|
String videoQuality = video.getVideoQuality();
|
||||||
|
if (videoQuality == null) videoQuality = sharedPreferences.getString("video_quality", "");
|
||||||
|
String formatArgument = "bestvideo+bestaudio/best";
|
||||||
|
if(videoQuality.equals("Worst Quality")){
|
||||||
|
formatArgument = "worst";
|
||||||
|
}else if (!videoQuality.isEmpty() && !videoQuality.equals("Best Quality")) {
|
||||||
|
formatArgument = "bestvideo[height<="+videoQuality.substring(0, videoQuality.length()-1)+"]+bestaudio/best";
|
||||||
|
}
|
||||||
|
|
||||||
|
request.addOption("-f", formatArgument);
|
||||||
|
String format = video.getVideoFormat();
|
||||||
|
if (format == null) format = sharedPreferences.getString("video_format", "");
|
||||||
|
if(!format.equals("DEFAULT")) request.addOption("--merge-output-format", format);
|
||||||
|
|
||||||
|
if(!format.equals("webm")){
|
||||||
|
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
|
||||||
|
if(embedThumb){
|
||||||
|
request.addOption("--embed-thumbnail");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(uploader)s - %(title)s.%(ext)s");
|
||||||
|
}
|
||||||
|
|
||||||
|
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, downloadProcessID, callback))
|
||||||
|
.subscribeOn(Schedulers.newThread())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(youtubeDLResponse -> {
|
||||||
|
downloadInfo.setDownloadPath(youtubeDLDir.getAbsolutePath());
|
||||||
|
downloadInfo.setDownloadType(type);
|
||||||
|
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadEnd(downloadInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCAN NEXT IN QUEUE
|
||||||
|
videos.remove();
|
||||||
|
downloadInfo.setDownloadQueue(videos);
|
||||||
|
startDownload(videos);
|
||||||
|
}, e -> {
|
||||||
|
if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e);
|
||||||
|
notificationUtil.updateDownloadNotification(NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
|
||||||
|
getString(R.string.failed_download), 0, 0, downloadQueue.peek().getTitle());
|
||||||
|
downloadInfo.setDownloadType(type);
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadError(downloadInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception err){
|
||||||
|
err.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCAN NEXT IN QUEUE
|
||||||
|
videos.remove();
|
||||||
|
downloadInfo.setDownloadQueue(videos);
|
||||||
|
startDownload(videos);
|
||||||
|
});
|
||||||
|
compositeDisposable.add(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCommandDownload(String text){
|
||||||
|
if(!text.startsWith("yt-dlp ")){
|
||||||
|
text = text.substring(6).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
YoutubeDLRequest request = new YoutubeDLRequest(Collections.emptyList());
|
||||||
|
String commandRegex = "\"([^\"]*)\"|(\\S+)";
|
||||||
|
Matcher m = Pattern.compile(commandRegex).matcher(text);
|
||||||
|
while (m.find()) {
|
||||||
|
if (m.group(1) != null) {
|
||||||
|
request.addOption(m.group(1));
|
||||||
|
} else {
|
||||||
|
request.addOption(m.group(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||||
|
String downloadsDir = sharedPreferences.getString("command_path", getString(R.string.command_path));
|
||||||
|
File youtubeDLDir = new File(downloadsDir);
|
||||||
|
if (!youtubeDLDir.exists()) {
|
||||||
|
boolean isDirCreated = youtubeDLDir.mkdir();
|
||||||
|
if (!isDirCreated) {
|
||||||
|
Toast.makeText(context, R.string.failed_making_directory, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s");
|
||||||
|
|
||||||
|
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, downloadProcessID, callback))
|
||||||
|
.subscribeOn(Schedulers.newThread())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(youtubeDLResponse -> {
|
||||||
|
downloadInfo.setOutputLine(youtubeDLResponse.getOut());
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadEnd(downloadInfo);
|
||||||
|
callback.onDownloadServiceEnd();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, e -> {
|
||||||
|
downloadInfo.setOutputLine(e.getMessage());
|
||||||
|
try{
|
||||||
|
for (Activity activity: activities.keySet()){
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||||
|
IDownloaderListener callback = activities.get(activity).get(i);
|
||||||
|
callback.onDownloadError(downloadInfo);
|
||||||
|
callback.onDownloadServiceEnd();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (Exception err){
|
||||||
|
err.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
compositeDisposable.add(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private File getDownloadLocation(String type) {
|
||||||
|
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||||
|
String downloadsDir;
|
||||||
|
if (type.equals("audio")) {
|
||||||
|
downloadsDir = sharedPreferences.getString("music_path", getString(R.string.music_path));
|
||||||
|
} else {
|
||||||
|
downloadsDir = sharedPreferences.getString("video_path", getString(R.string.video_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
File youtubeDLDir = new File(downloadsDir);
|
||||||
|
if (!youtubeDLDir.exists()) {
|
||||||
|
boolean isDirCreated = youtubeDLDir.mkdir();
|
||||||
|
if (!isDirCreated) {
|
||||||
|
notificationUtil.updateDownloadNotification(NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
|
||||||
|
getString(R.string.failed_making_directory), 0, 0, downloadQueue.peek().getTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return youtubeDLDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,592 +0,0 @@
|
||||||
package com.deniscerri.ytdlnis
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Binder
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.SystemClock
|
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import android.provider.Settings.Global
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import androidx.work.workDataOf
|
|
||||||
import com.deniscerri.ytdlnis.database.Video
|
|
||||||
import com.deniscerri.ytdlnis.page.CustomCommandActivity
|
|
||||||
import com.deniscerri.ytdlnis.service.DownloadInfo
|
|
||||||
import com.deniscerri.ytdlnis.service.IDownloaderListener
|
|
||||||
import com.deniscerri.ytdlnis.service.IDownloaderService
|
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
|
||||||
import com.deniscerri.ytdlnis.work.FileTransferWorker
|
|
||||||
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.originDir
|
|
||||||
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.title
|
|
||||||
import com.deniscerri.ytdlnis.work.FileTransferWorker.Companion.downLocation
|
|
||||||
import com.yausername.youtubedl_android.DownloadProgressCallback
|
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
|
||||||
import com.yausername.youtubedl_android.YoutubeDLResponse
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
|
||||||
import io.reactivex.schedulers.Schedulers
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
import kotlin.jvm.functions.Function3
|
|
||||||
|
|
||||||
|
|
||||||
class DownloaderService : Service() {
|
|
||||||
private val binder = LocalBinder()
|
|
||||||
private val activities: MutableMap<Activity, ArrayList<IDownloaderListener>?> =
|
|
||||||
ConcurrentHashMap()
|
|
||||||
private val fileUtil = FileUtil()
|
|
||||||
private val downloadInfo = DownloadInfo()
|
|
||||||
private var downloadQueue = LinkedList<Video>()
|
|
||||||
private val compositeDisposable = CompositeDisposable()
|
|
||||||
private val notificationUtil = App.notificationUtil
|
|
||||||
private var context: Context? = null
|
|
||||||
var downloadProcessID = "processID"
|
|
||||||
private var downloadNotificationID = 0
|
|
||||||
private lateinit var notificationChannelID : String
|
|
||||||
private lateinit var workManager : WorkManager
|
|
||||||
|
|
||||||
private val callback: (Float, Long?, String?) -> Unit =
|
|
||||||
{ progress: Float, _: Long?, line: String? ->
|
|
||||||
downloadInfo.progress = progress.toInt()
|
|
||||||
downloadInfo.outputLine = line
|
|
||||||
downloadInfo.downloadQueue = downloadQueue
|
|
||||||
var title: String? = getString(R.string.running_ytdlp_command)
|
|
||||||
if (!downloadQueue.isEmpty()) {
|
|
||||||
title = downloadQueue.peek()?.title
|
|
||||||
}
|
|
||||||
notificationUtil.updateDownloadNotification(
|
|
||||||
downloadNotificationID,
|
|
||||||
line!!, progress.toInt(), downloadQueue.size, title,
|
|
||||||
notificationChannelID
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
if (activities[activity] != null) {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadProgress(downloadInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
context = this
|
|
||||||
this.workManager = WorkManager.getInstance(context!!.applicationContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
val theIntent: Intent
|
|
||||||
val pendingIntent: PendingIntent
|
|
||||||
if (intent.getBooleanExtra("rebind", false)) {
|
|
||||||
return binder
|
|
||||||
}
|
|
||||||
when (val id = intent.getIntExtra("id", 1)) {
|
|
||||||
NotificationUtil.DOWNLOAD_NOTIFICATION_ID -> {
|
|
||||||
notificationChannelID = NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
|
||||||
theIntent = Intent(this, MainActivity::class.java)
|
|
||||||
pendingIntent =
|
|
||||||
PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE)
|
|
||||||
downloadNotificationID = id
|
|
||||||
val queue: ArrayList<out Video>? = intent.getParcelableArrayListExtra("queue")
|
|
||||||
downloadQueue = LinkedList()
|
|
||||||
downloadQueue.addAll(queue!!)
|
|
||||||
downloadInfo.downloadQueue = downloadQueue
|
|
||||||
val title = downloadInfo.video.title
|
|
||||||
val notification =
|
|
||||||
App.notificationUtil.createDownloadServiceNotification(pendingIntent, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
|
|
||||||
startForeground(downloadNotificationID, notification)
|
|
||||||
startDownload(downloadQueue)
|
|
||||||
}
|
|
||||||
NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID -> {
|
|
||||||
notificationChannelID = NotificationUtil.COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID
|
|
||||||
theIntent = Intent(this, CustomCommandActivity::class.java)
|
|
||||||
pendingIntent =
|
|
||||||
PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE)
|
|
||||||
downloadNotificationID = id
|
|
||||||
val command = intent.getStringExtra("command")
|
|
||||||
val commandNotification = App.notificationUtil.createDownloadServiceNotification(
|
|
||||||
pendingIntent,
|
|
||||||
getString(R.string.running_ytdlp_command),
|
|
||||||
NotificationUtil.COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID
|
|
||||||
)
|
|
||||||
startForeground(downloadNotificationID, commandNotification)
|
|
||||||
startCommandDownload(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binder
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
|
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
||||||
}else{
|
|
||||||
stopForeground(true)
|
|
||||||
}
|
|
||||||
stopSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class LocalBinder : Binder(), IDownloaderService {
|
|
||||||
val service: DownloaderService
|
|
||||||
get() = this@DownloaderService
|
|
||||||
|
|
||||||
override fun getInfo(): DownloadInfo {
|
|
||||||
return downloadInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addActivity(activity: Activity, callbacks: ArrayList<IDownloaderListener>) {
|
|
||||||
if (!activities.containsKey(activity)) {
|
|
||||||
activities[activity] = callbacks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeActivity(activity: Activity) {
|
|
||||||
activities.remove(activity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateQueue(queue: ArrayList<Video>) {
|
|
||||||
try {
|
|
||||||
for (i in queue.indices) {
|
|
||||||
downloadQueue.add(queue[i].clone() as Video)
|
|
||||||
}
|
|
||||||
if (downloadQueue.size == queue.size) {
|
|
||||||
downloadInfo.downloadQueue = downloadQueue
|
|
||||||
startDownload(downloadQueue)
|
|
||||||
} else {
|
|
||||||
downloadInfo.downloadQueue = downloadQueue
|
|
||||||
Toast.makeText(context, getString(R.string.added_to_queue), Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(context, "Couldn't update download queue! :(", Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancelDownload(cancelAll: Boolean) {
|
|
||||||
try {
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(downloadProcessID)
|
|
||||||
compositeDisposable.clear()
|
|
||||||
//stopForeground(true);
|
|
||||||
if (cancelAll) {
|
|
||||||
onDownloadCancelAll()
|
|
||||||
}
|
|
||||||
} catch (err: Exception) {
|
|
||||||
Log.e(TAG, err.message!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeItemFromDownloadQueue(video: Video, type: String) {
|
|
||||||
//if its the same video with the same download type as the current downloading one
|
|
||||||
if (downloadInfo.video.getURL() == video.getURL() && downloadInfo.video.downloadedType == video.downloadedType) {
|
|
||||||
cancelDownload(false)
|
|
||||||
downloadInfo.downloadType = type
|
|
||||||
onDownloadCancel(downloadInfo)
|
|
||||||
downloadQueue.pop()
|
|
||||||
downloadInfo.downloadQueue = downloadQueue
|
|
||||||
startDownload(downloadQueue)
|
|
||||||
} else {
|
|
||||||
downloadQueue.remove(video)
|
|
||||||
try {
|
|
||||||
val info = DownloadInfo()
|
|
||||||
info.video = video
|
|
||||||
info.downloadType = type
|
|
||||||
onDownloadCancel(info)
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
video.title + " " + getString(R.string.removed_from_queue),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun finishService() {
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadServiceEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onDownloadCancelAll() {
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadCancelAll(downloadInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onDownloadEnd() {
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadEnd(downloadInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onDownloadCancel(cancelInfo: DownloadInfo) {
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadCancel(cancelInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startDownload(videos: LinkedList<Video>) {
|
|
||||||
if (videos.size == 0) {
|
|
||||||
finishService()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val video: Video = try {
|
|
||||||
videos.peek() as Video
|
|
||||||
} catch (e: Exception) {
|
|
||||||
finishService()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadStart(downloadInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: Exception) {
|
|
||||||
err.printStackTrace()
|
|
||||||
}
|
|
||||||
val url = video.getURL()
|
|
||||||
val request = YoutubeDLRequest(url)
|
|
||||||
val type = video.downloadedType
|
|
||||||
val downloadLocation = getDownloadLocation(type)
|
|
||||||
|
|
||||||
var tempFileDir = File(cacheDir.absolutePath + """/${video.title}##${video.downloadedType}""")
|
|
||||||
tempFileDir.delete()
|
|
||||||
tempFileDir.mkdir()
|
|
||||||
|
|
||||||
val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE)
|
|
||||||
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
|
||||||
if (aria2) {
|
|
||||||
request.addOption("--downloader", "libaria2c.so")
|
|
||||||
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
|
|
||||||
} else {
|
|
||||||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
|
||||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
|
||||||
}
|
|
||||||
val limitRate = sharedPreferences.getString("limit_rate", "")
|
|
||||||
if (limitRate != "") request.addOption("-r", limitRate!!)
|
|
||||||
val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false)
|
|
||||||
if (writeThumbnail) {
|
|
||||||
request.addOption("--write-thumbnail")
|
|
||||||
request.addOption("--convert-thumbnails", "png")
|
|
||||||
}
|
|
||||||
request.addOption("--no-mtime")
|
|
||||||
val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
|
|
||||||
if (sponsorBlockFilters!!.isNotEmpty()) {
|
|
||||||
val filters = java.lang.String.join(",", sponsorBlockFilters)
|
|
||||||
request.addOption("--sponsorblock-remove", filters)
|
|
||||||
}
|
|
||||||
if (type == "audio") {
|
|
||||||
if (downloadLocation.equals(getString(R.string.music_path))){
|
|
||||||
tempFileDir = File(getString(R.string.music_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
request.addOption("-x")
|
|
||||||
var format = video.audioFormat
|
|
||||||
if (format == null) format = sharedPreferences.getString("audio_format", "")
|
|
||||||
request.addOption("--audio-format", format!!)
|
|
||||||
request.addOption("--embed-metadata")
|
|
||||||
|
|
||||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
|
||||||
if (embedThumb) {
|
|
||||||
request.addOption("--embed-thumbnail")
|
|
||||||
request.addOption("--convert-thumbnails", "png")
|
|
||||||
try {
|
|
||||||
val config = File(cacheDir, "config.txt")
|
|
||||||
val config_data =
|
|
||||||
"--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
|
||||||
val stream = FileOutputStream(config)
|
|
||||||
stream.write(config_data.toByteArray())
|
|
||||||
stream.close()
|
|
||||||
request.addOption("--config", config.absolutePath)
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.addCommands(Arrays.asList("--replace-in-metadata", "title", ".*.", video.title))
|
|
||||||
request.addCommands(
|
|
||||||
Arrays.asList(
|
|
||||||
"--replace-in-metadata",
|
|
||||||
"uploader",
|
|
||||||
".*.",
|
|
||||||
video.author
|
|
||||||
)
|
|
||||||
)
|
|
||||||
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
|
|
||||||
} else if (type == "video") {
|
|
||||||
if (downloadLocation.equals(getString(R.string.video_path))){
|
|
||||||
tempFileDir = File(getString(R.string.video_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
|
|
||||||
if (addChapters) {
|
|
||||||
request.addOption("--sponsorblock-mark", "all")
|
|
||||||
}
|
|
||||||
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
|
|
||||||
if (embedSubs) {
|
|
||||||
request.addOption("--embed-subs", "")
|
|
||||||
}
|
|
||||||
var videoQuality = video.videoQuality
|
|
||||||
if (videoQuality == null) videoQuality =
|
|
||||||
sharedPreferences.getString("video_quality", "")
|
|
||||||
var formatArgument = "bestvideo+bestaudio/best"
|
|
||||||
if (videoQuality == "Worst Quality") {
|
|
||||||
formatArgument = "worst"
|
|
||||||
} else if (!videoQuality!!.isEmpty() && videoQuality != "Best Quality") {
|
|
||||||
formatArgument = "bestvideo[height<=" + videoQuality.substring(
|
|
||||||
0,
|
|
||||||
videoQuality.length - 1
|
|
||||||
) + "]+bestaudio/best"
|
|
||||||
}
|
|
||||||
request.addOption("-f", formatArgument)
|
|
||||||
var format = video.videoFormat
|
|
||||||
if (format == null) format = sharedPreferences.getString("video_format", "")
|
|
||||||
if (format != "DEFAULT") request.addOption("--merge-output-format", format!!)
|
|
||||||
if (format != "webm") {
|
|
||||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
|
||||||
if (embedThumb) {
|
|
||||||
request.addOption("--embed-thumbnail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
|
|
||||||
}
|
|
||||||
|
|
||||||
val disposable = Observable.fromCallable {
|
|
||||||
YoutubeDL.getInstance().execute(request, downloadProcessID, callback)
|
|
||||||
}
|
|
||||||
.subscribeOn(Schedulers.newThread())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe({
|
|
||||||
val workTag = video.getURL()
|
|
||||||
val workData = workDataOf(
|
|
||||||
originDir to tempFileDir.absolutePath,
|
|
||||||
downLocation to downloadLocation,
|
|
||||||
title to video.title
|
|
||||||
)
|
|
||||||
val fileTransferWorkRequest = OneTimeWorkRequestBuilder<FileTransferWorker>()
|
|
||||||
.addTag(workTag)
|
|
||||||
.setInputData(workData)
|
|
||||||
.build()
|
|
||||||
workManager.enqueueUniqueWork(
|
|
||||||
workTag,
|
|
||||||
ExistingWorkPolicy.KEEP,
|
|
||||||
fileTransferWorkRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
downloadInfo.downloadPath = fileUtil.formatPath(getDownloadLocation(type)!!)
|
|
||||||
Log.e(TAG, downloadInfo.downloadPath)
|
|
||||||
downloadInfo.downloadType = type
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadEnd(downloadInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SCAN NEXT IN QUEUE
|
|
||||||
videos.remove()
|
|
||||||
downloadInfo.downloadQueue = videos
|
|
||||||
startDownload(videos)
|
|
||||||
}) { e: Throwable? ->
|
|
||||||
tempFileDir.delete()
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
Toast.makeText(context, e!!.message, Toast.LENGTH_LONG).show()
|
|
||||||
Log.e(TAG, getString(R.string.failed_download), e)
|
|
||||||
}
|
|
||||||
notificationUtil.updateDownloadNotification(
|
|
||||||
NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
|
|
||||||
getString(R.string.failed_download), 0, 0, downloadQueue.peek()?.title,
|
|
||||||
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
|
||||||
)
|
|
||||||
downloadInfo.downloadType = type
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadError(downloadInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: Exception) {
|
|
||||||
err.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SCAN NEXT IN QUEUE
|
|
||||||
videos.remove()
|
|
||||||
downloadInfo.downloadQueue = videos
|
|
||||||
startDownload(videos)
|
|
||||||
}
|
|
||||||
compositeDisposable.add(disposable)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startCommandDownload(text: String?) {
|
|
||||||
var text = text
|
|
||||||
if (text!!.startsWith("yt-dlp ")) {
|
|
||||||
text = text.substring(6).trim { it <= ' ' }
|
|
||||||
}
|
|
||||||
|
|
||||||
val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE)
|
|
||||||
val downloadLocation = sharedPreferences.getString("command_path", getString(R.string.command_path))
|
|
||||||
|
|
||||||
var tempFileDir = File(cacheDir.absolutePath + "/command")
|
|
||||||
tempFileDir.delete()
|
|
||||||
tempFileDir.mkdir()
|
|
||||||
|
|
||||||
if (downloadLocation.equals(getString(R.string.command_path))){
|
|
||||||
tempFileDir = File(getString(R.string.command_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val request = YoutubeDLRequest(emptyList())
|
|
||||||
val commandRegex = "\"([^\"]*)\"|(\\S+)"
|
|
||||||
val m = Pattern.compile(commandRegex).matcher(text)
|
|
||||||
while (m.find()) {
|
|
||||||
if (m.group(1) != null) {
|
|
||||||
request.addOption(m.group(1))
|
|
||||||
} else {
|
|
||||||
request.addOption(m.group(2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.addOption("-o", tempFileDir.absolutePath + "/%(title)s.%(ext)s")
|
|
||||||
val disposable = Observable.fromCallable {
|
|
||||||
YoutubeDL.getInstance().execute(request, downloadProcessID, callback)
|
|
||||||
}
|
|
||||||
.subscribeOn(Schedulers.newThread())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe({ youtubeDLResponse: YoutubeDLResponse ->
|
|
||||||
downloadInfo.outputLine = youtubeDLResponse.out
|
|
||||||
|
|
||||||
val workTag = SystemClock.uptimeMillis().toString()
|
|
||||||
val workData = workDataOf(
|
|
||||||
originDir to tempFileDir.absolutePath,
|
|
||||||
downLocation to downloadLocation,
|
|
||||||
title to ""
|
|
||||||
)
|
|
||||||
val fileTransferWorkRequest = OneTimeWorkRequestBuilder<FileTransferWorker>()
|
|
||||||
.addTag(workTag)
|
|
||||||
.setInputData(workData)
|
|
||||||
.build()
|
|
||||||
workManager.enqueueUniqueWork(
|
|
||||||
workTag,
|
|
||||||
ExistingWorkPolicy.KEEP,
|
|
||||||
fileTransferWorkRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadEnd(downloadInfo)
|
|
||||||
callback.onDownloadServiceEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}) { e: Throwable ->
|
|
||||||
downloadInfo.outputLine = e.message
|
|
||||||
tempFileDir.delete()
|
|
||||||
try {
|
|
||||||
for (activity in activities.keys) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
for (i in activities[activity]!!.indices) {
|
|
||||||
val callback = activities[activity]!![i]
|
|
||||||
callback.onDownloadError(downloadInfo)
|
|
||||||
callback.onDownloadServiceEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: Exception) {
|
|
||||||
err.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compositeDisposable.add(disposable)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDownloadLocation(type: String): String? {
|
|
||||||
val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE)
|
|
||||||
var downloadsDir: String? = if (type == "audio") {
|
|
||||||
sharedPreferences.getString("music_path", getString(R.string.music_path))
|
|
||||||
} else {
|
|
||||||
sharedPreferences.getString("video_path", getString(R.string.video_path))
|
|
||||||
}
|
|
||||||
Log.e(TAG, downloadsDir!!)
|
|
||||||
return downloadsDir
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "DownloaderService"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -60,7 +60,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
iDownloaderService!!.addActivity(this@MainActivity, listeners)
|
iDownloaderService!!.addActivity(this@MainActivity, listeners)
|
||||||
for (i in listeners.indices) {
|
for (i in listeners.indices) {
|
||||||
val listener = listeners[i]
|
val listener = listeners[i]
|
||||||
listener.onDownloadStart(iDownloaderService!!.info)
|
listener.onDownloadStart(iDownloaderService!!.downloadInfo)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
@ -316,17 +316,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun createDefaultFolders(){
|
|
||||||
val audio = File(getString(R.string.music_path))
|
|
||||||
val video = File(getString(R.string.video_path))
|
|
||||||
val command = File(getString(R.string.command_path))
|
|
||||||
|
|
||||||
audio.mkdirs()
|
|
||||||
video.mkdirs()
|
|
||||||
command.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(
|
override fun onRequestPermissionsResult(
|
||||||
requestCode: Int,
|
requestCode: Int,
|
||||||
permissions: Array<String>,
|
permissions: Array<String>,
|
||||||
|
|
@ -338,11 +327,22 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
|
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
|
||||||
createPermissionRequestDialog()
|
createPermissionRequestDialog()
|
||||||
}else{
|
}else{
|
||||||
createDefaultFolders()
|
createDefaultFolders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createDefaultFolders(){
|
||||||
|
val audio = File(getString(R.string.music_path))
|
||||||
|
val video = File(getString(R.string.video_path))
|
||||||
|
val command = File(getString(R.string.command_path))
|
||||||
|
|
||||||
|
audio.mkdirs()
|
||||||
|
video.mkdirs()
|
||||||
|
command.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun exit() {
|
private fun exit() {
|
||||||
finishAffinity()
|
finishAffinity()
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ public class CustomCommandActivity extends AppCompatActivity {
|
||||||
ArrayList<IDownloaderListener> listeners = new ArrayList<>();
|
ArrayList<IDownloaderListener> listeners = new ArrayList<>();
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
iDownloaderService.addActivity(CustomCommandActivity.this, listeners);
|
iDownloaderService.addActivity(CustomCommandActivity.this, listeners);
|
||||||
listener.onDownloadStart(iDownloaderService.getInfo());
|
listener.onDownloadStart(iDownloaderService.getDownloadInfo());
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
@ -76,14 +76,7 @@ public class CustomCommandActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDownloadProgress(DownloadInfo info) {
|
public void onDownloadProgress(DownloadInfo info) {
|
||||||
String newInfo = info.getOutputLine();
|
output.append("\n" + info.getOutputLine() + "\n");
|
||||||
if (newInfo.contains("[download]")){
|
|
||||||
String temp = output.getText().toString();
|
|
||||||
output.setText(
|
|
||||||
temp.substring(0, temp.lastIndexOf(System.getProperty("line.separator")) - 2)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
output.append(info.getOutputLine() + " \n");
|
|
||||||
output.scrollTo(0, output.getHeight());
|
output.scrollTo(0, output.getHeight());
|
||||||
scrollView.fullScroll(View.FOCUS_DOWN);
|
scrollView.fullScroll(View.FOCUS_DOWN);
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +141,6 @@ public class CustomCommandActivity extends AppCompatActivity {
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import com.deniscerri.ytdlnis.database.DatabaseManager;
|
||||||
import com.deniscerri.ytdlnis.database.Video;
|
import com.deniscerri.ytdlnis.database.Video;
|
||||||
import com.deniscerri.ytdlnis.service.DownloadInfo;
|
import com.deniscerri.ytdlnis.service.DownloadInfo;
|
||||||
import com.deniscerri.ytdlnis.service.IDownloaderListener;
|
import com.deniscerri.ytdlnis.service.IDownloaderListener;
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil;
|
|
||||||
import com.facebook.shimmer.ShimmerFrameLayout;
|
import com.facebook.shimmer.ShimmerFrameLayout;
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
|
|
@ -77,7 +76,6 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
||||||
public ArrayList<Video> selectedObjects;
|
public ArrayList<Video> selectedObjects;
|
||||||
private LinearProgressIndicator progressBar;
|
private LinearProgressIndicator progressBar;
|
||||||
private ExtendedFloatingActionButton deleteFab;
|
private ExtendedFloatingActionButton deleteFab;
|
||||||
private FileUtil fileUtil;
|
|
||||||
private String format = "";
|
private String format = "";
|
||||||
private String website = "";
|
private String website = "";
|
||||||
private String sort = "DESC";
|
private String sort = "DESC";
|
||||||
|
|
@ -125,7 +123,6 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
||||||
int position = downloadsObjects.indexOf(v);
|
int position = downloadsObjects.indexOf(v);
|
||||||
downloadsRecyclerViewAdapter.notifyItemRemoved(position);
|
downloadsRecyclerViewAdapter.notifyItemRemoved(position);
|
||||||
downloadsObjects.remove(v);
|
downloadsObjects.remove(v);
|
||||||
|
|
||||||
if (downloadsObjects.isEmpty()) initCards();
|
if (downloadsObjects.isEmpty()) initCards();
|
||||||
downloading = false;
|
downloading = false;
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
|
|
@ -137,12 +134,28 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
||||||
Video item = downloadInfo.getVideo();
|
Video item = downloadInfo.getVideo();
|
||||||
String url = item.getURL();
|
String url = item.getURL();
|
||||||
String type = downloadInfo.getDownloadType();
|
String type = downloadInfo.getDownloadType();
|
||||||
String path = downloadInfo.getDownloadPath();
|
|
||||||
item = findVideo(url, type);
|
item = findVideo(url, type);
|
||||||
int index = downloadsObjects.indexOf(item);
|
int index = downloadsObjects.indexOf(item);
|
||||||
try{
|
try{
|
||||||
|
// MEDIA SCAN
|
||||||
|
ArrayList<File> files = new ArrayList<>();
|
||||||
|
String title = downloadInfo.getVideo().getTitle().replaceAll("[^a-zA-Z0-9]","");
|
||||||
|
String pathTmp = "";
|
||||||
|
File path = new File(downloadInfo.getDownloadPath());
|
||||||
|
for( File file : path.listFiles() ){
|
||||||
|
if(file.isFile()){
|
||||||
|
pathTmp = file.getAbsolutePath().replaceAll("[^a-zA-Z0-9]","");
|
||||||
|
if (pathTmp.contains(title)){
|
||||||
|
files.add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] paths = new String[files.size()];
|
||||||
|
for (int i = 0; i < files.size(); i++) paths[i] = files.get(i).getAbsolutePath();
|
||||||
|
MediaScannerConnection.scanFile(context, paths, null, null);
|
||||||
item.setDownloadedType(type);
|
item.setDownloadedType(type);
|
||||||
item.setDownloadPath(path);
|
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
Date date = new Date();
|
Date date = new Date();
|
||||||
cal.setTime(date);
|
cal.setTime(date);
|
||||||
|
|
@ -156,13 +169,12 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
||||||
|
|
||||||
String downloadPath = "";
|
String downloadPath = "";
|
||||||
try{
|
try{
|
||||||
downloadPath = fileUtil.scanMedia(item, context);
|
downloadPath = paths[0];
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseManager = new DatabaseManager(context);
|
databaseManager = new DatabaseManager(context);
|
||||||
//TODO observe file transfer worker here and then update, but first you need to turn this to kotlin and implement proper mvvm
|
|
||||||
try {
|
try {
|
||||||
item.setDownloadedTime(downloadedTime);
|
item.setDownloadedTime(downloadedTime);
|
||||||
item.setDownloadPath(downloadPath);
|
item.setDownloadPath(downloadPath);
|
||||||
|
|
@ -244,7 +256,6 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
||||||
selectionChips = fragmentView.findViewById(R.id.downloads_selection_chips);
|
selectionChips = fragmentView.findViewById(R.id.downloads_selection_chips);
|
||||||
websiteGroup = fragmentView.findViewById(R.id.website_chip_group);
|
websiteGroup = fragmentView.findViewById(R.id.website_chip_group);
|
||||||
deleteFab = fragmentView.findViewById(R.id.delete_selected_fab);
|
deleteFab = fragmentView.findViewById(R.id.delete_selected_fab);
|
||||||
fileUtil = new FileUtil();
|
|
||||||
|
|
||||||
deleteFab.setTag("deleteSelected");
|
deleteFab.setTag("deleteSelected");
|
||||||
deleteFab.setOnClickListener(this);
|
deleteFab.setOnClickListener(this);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,359 @@
|
||||||
|
package com.deniscerri.ytdlnis.page.settings;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
import androidx.activity.result.ActivityResultCallback;
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.preference.EditTextPreference;
|
||||||
|
import androidx.preference.ListPreference;
|
||||||
|
import androidx.preference.MultiSelectListPreference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
import androidx.preference.SeekBarPreference;
|
||||||
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
|
||||||
|
import com.deniscerri.ytdlnis.BuildConfig;
|
||||||
|
import com.deniscerri.ytdlnis.R;
|
||||||
|
import com.deniscerri.ytdlnis.util.UpdateUtil;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
|
Preference musicPath;
|
||||||
|
Preference videoPath;
|
||||||
|
Preference commandPath;
|
||||||
|
|
||||||
|
SwitchPreferenceCompat incognito;
|
||||||
|
SwitchPreferenceCompat downloadCard;
|
||||||
|
EditTextPreference apiKey;
|
||||||
|
SeekBarPreference concurrentFragments;
|
||||||
|
EditTextPreference limitRate;
|
||||||
|
SwitchPreferenceCompat aria2;
|
||||||
|
|
||||||
|
MultiSelectListPreference sponsorblockFilters;
|
||||||
|
SwitchPreferenceCompat embedSubtitles;
|
||||||
|
SwitchPreferenceCompat embedThumbnail;
|
||||||
|
SwitchPreferenceCompat addChapters;
|
||||||
|
SwitchPreferenceCompat writeThumbnail;
|
||||||
|
ListPreference audioFormat;
|
||||||
|
ListPreference videoFormat;
|
||||||
|
SeekBarPreference audioQuality;
|
||||||
|
ListPreference videoQuality;
|
||||||
|
|
||||||
|
Preference updateYTDL;
|
||||||
|
SwitchPreferenceCompat updateApp;
|
||||||
|
Preference version;
|
||||||
|
|
||||||
|
private UpdateUtil updateUtil;
|
||||||
|
|
||||||
|
public static final int MUSIC_PATH_CODE = 33333;
|
||||||
|
public static final int VIDEO_PATH_CODE = 55555;
|
||||||
|
public static final int COMMAND_PATH_CODE = 77777;
|
||||||
|
private static final String TAG = "SettingsFragment";
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
|
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||||
|
|
||||||
|
updateUtil = new UpdateUtil(requireContext());
|
||||||
|
initPreferences();
|
||||||
|
initListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPreferences(){
|
||||||
|
SharedPreferences preferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
|
||||||
|
musicPath = findPreference("music_path");
|
||||||
|
videoPath = findPreference("video_path");
|
||||||
|
commandPath = findPreference("command_path");
|
||||||
|
|
||||||
|
incognito = findPreference("incognito");
|
||||||
|
downloadCard = findPreference("download_card");
|
||||||
|
apiKey = findPreference("api_key");
|
||||||
|
concurrentFragments = findPreference("concurrent_fragments");
|
||||||
|
limitRate = findPreference("limit_rate");
|
||||||
|
aria2 = findPreference("aria2");
|
||||||
|
|
||||||
|
sponsorblockFilters = findPreference("sponsorblock_filter");
|
||||||
|
embedSubtitles = findPreference("embed_subtitles");
|
||||||
|
embedThumbnail = findPreference("embed_thumbnail");
|
||||||
|
addChapters = findPreference("add_chapters");
|
||||||
|
writeThumbnail = findPreference("write_thumbnail");
|
||||||
|
audioFormat = findPreference("audio_format");
|
||||||
|
videoFormat = findPreference("video_format");
|
||||||
|
audioQuality = findPreference("audio_quality");
|
||||||
|
videoQuality = findPreference("video_quality");
|
||||||
|
|
||||||
|
updateYTDL = findPreference("update_ytdl");
|
||||||
|
updateApp = findPreference("update_app");
|
||||||
|
version = findPreference("version");
|
||||||
|
version.setSummary(BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
|
if (preferences.getString("music_path", "").isEmpty()){
|
||||||
|
editor.putString("music_path", getString(R.string.music_path));
|
||||||
|
}
|
||||||
|
if (preferences.getString("video_path", "").isEmpty()){
|
||||||
|
editor.putString("video_path", getString(R.string.video_path));
|
||||||
|
}
|
||||||
|
if (preferences.getString("command_path", "").isEmpty()){
|
||||||
|
editor.putString("command_path", getString(R.string.command_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.putBoolean("incognito", incognito.isChecked());
|
||||||
|
editor.putBoolean("download_card", downloadCard.isChecked());
|
||||||
|
editor.putString("api_key", apiKey.getText());
|
||||||
|
editor.putInt("concurrent_fragments", concurrentFragments.getValue());
|
||||||
|
editor.putString("limit_rate", limitRate.getText());
|
||||||
|
editor.putBoolean("aria2", aria2.isChecked());
|
||||||
|
editor.putStringSet("sponsorblock_filters", sponsorblockFilters.getValues());
|
||||||
|
editor.putBoolean("embed_subtitles", embedSubtitles.isChecked());
|
||||||
|
editor.putBoolean("embed_thumbnail", embedThumbnail.isChecked());
|
||||||
|
editor.putBoolean("add_chapters", addChapters.isChecked());
|
||||||
|
editor.putBoolean("write_thumbnail", writeThumbnail.isChecked());
|
||||||
|
editor.putString("audio_format", audioFormat.getValue());
|
||||||
|
editor.putString("video_format", videoFormat.getValue());
|
||||||
|
editor.putInt("audio_quality", audioQuality.getValue());
|
||||||
|
editor.putString("video_quality", videoQuality.getValue());
|
||||||
|
editor.putBoolean("update_app", updateApp.isChecked());
|
||||||
|
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initListeners(){
|
||||||
|
SharedPreferences preferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
|
||||||
|
musicPath.setSummary(preferences.getString("music_path", ""));
|
||||||
|
musicPath.setOnPreferenceClickListener(preference -> {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
musicPathResultLauncher.launch(intent);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
videoPath.setSummary(preferences.getString("video_path", ""));
|
||||||
|
videoPath.setOnPreferenceClickListener(preference -> {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
videoPathResultLauncher.launch(intent);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
commandPath.setSummary(preferences.getString("command_path", ""));
|
||||||
|
commandPath.setOnPreferenceClickListener(preference -> {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
commandPathResultLauncher.launch(intent);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
incognito.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean enable = (Boolean) newValue;
|
||||||
|
editor.putBoolean("incognito", enable);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
downloadCard.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean enable = (Boolean) newValue;
|
||||||
|
editor.putBoolean("download_card", enable);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
apiKey.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
editor.putString("api_key", String.valueOf(newValue));
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
concurrentFragments.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
int value = Integer.parseInt(String.valueOf(newValue));
|
||||||
|
editor.putInt("concurrent_fragments", value);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
limitRate.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
editor.putString("limit_rate", String.valueOf(newValue));
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
aria2.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean enable = (Boolean) newValue;
|
||||||
|
editor.putBoolean("aria2", enable);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
sponsorblockFilters.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
sponsorblockFilters.setValues((Set<String>) newValue);
|
||||||
|
editor.putStringSet("sponsorblock_filters", (Set<String>)newValue);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
embedSubtitles.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean embed = (Boolean) newValue;
|
||||||
|
editor.putBoolean("embed_subtitles", embed);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
embedThumbnail.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean embed = (Boolean) newValue;
|
||||||
|
editor.putBoolean("embed_thumbnail", embed);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
addChapters.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean add = (Boolean) newValue;
|
||||||
|
editor.putBoolean("add_chapters", add);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
writeThumbnail.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean write = (Boolean) newValue;
|
||||||
|
editor.putBoolean("write_thumbnail", write);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
audioFormat.setSummary(preferences.getString("audio_format", ""));
|
||||||
|
audioFormat.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
editor.putString("audio_format", String.valueOf(newValue));
|
||||||
|
audioFormat.setSummary(String.valueOf(newValue));
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
videoFormat.setSummary(preferences.getString("video_format", ""));
|
||||||
|
videoFormat.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
editor.putString("video_format", String.valueOf(newValue));
|
||||||
|
videoFormat.setSummary(String.valueOf(newValue));
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
audioQuality.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
editor.putInt("audio_format", Integer.parseInt(String.valueOf(newValue)));
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
videoQuality.setSummary(preferences.getString("video_quality", ""));
|
||||||
|
videoQuality.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
editor.putString("video_quality", String.valueOf(newValue));
|
||||||
|
videoQuality.setSummary(String.valueOf(newValue));
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateYTDL.setOnPreferenceClickListener(preference -> {
|
||||||
|
updateUtil.updateYoutubeDL();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateApp.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
boolean enable = (Boolean) newValue;
|
||||||
|
editor.putBoolean("update_app", enable);
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
version.setOnPreferenceClickListener(preference -> {
|
||||||
|
if(!updateUtil.updateApp()){
|
||||||
|
Toast.makeText(getContext(), R.string.you_are_in_latest_version, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityResultLauncher<Intent> musicPathResultLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
new ActivityResultCallback<ActivityResult>() {
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(ActivityResult result) {
|
||||||
|
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||||
|
changePath(musicPath, result.getData(), MUSIC_PATH_CODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ActivityResultLauncher<Intent> videoPathResultLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
new ActivityResultCallback<ActivityResult>() {
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(ActivityResult result) {
|
||||||
|
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||||
|
changePath(videoPath, result.getData(), VIDEO_PATH_CODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ActivityResultLauncher<Intent> commandPathResultLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
new ActivityResultCallback<ActivityResult>() {
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(ActivityResult result) {
|
||||||
|
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||||
|
changePath(commandPath, result.getData(), COMMAND_PATH_CODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public void changePath(Preference p, Intent data, int requestCode){
|
||||||
|
Log.e("TEST", data.toUri(0));
|
||||||
|
String dataValue = data.getData().toString();
|
||||||
|
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "");
|
||||||
|
dataValue = dataValue.replaceAll("%3A", "/");
|
||||||
|
|
||||||
|
try{
|
||||||
|
dataValue = java.net.URLDecoder.decode(dataValue, StandardCharsets.UTF_8.name());
|
||||||
|
}catch(Exception ignored){}
|
||||||
|
|
||||||
|
String[] pieces = dataValue.split("/");
|
||||||
|
|
||||||
|
int index = 1;
|
||||||
|
StringBuilder path = new StringBuilder("/storage/");
|
||||||
|
if(pieces[0].equals("primary")){
|
||||||
|
path.append("emulated/0/");
|
||||||
|
}else{
|
||||||
|
path.append(pieces[0]).append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = index; i < pieces.length; i++){
|
||||||
|
path.append(pieces[i]).append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, path.toString());
|
||||||
|
p.setSummary(path.toString());
|
||||||
|
SharedPreferences sharedPreferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
switch (requestCode){
|
||||||
|
case MUSIC_PATH_CODE:
|
||||||
|
editor.putString("music_path", path.toString());
|
||||||
|
break;
|
||||||
|
case VIDEO_PATH_CODE:
|
||||||
|
editor.putString("video_path", path.toString());
|
||||||
|
break;
|
||||||
|
case COMMAND_PATH_CODE:
|
||||||
|
editor.putString("command_path", path.toString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,326 +0,0 @@
|
||||||
package com.deniscerri.ytdlnis.page.settings
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.preference.*
|
|
||||||
import com.deniscerri.ytdlnis.BuildConfig
|
|
||||||
import com.deniscerri.ytdlnis.R
|
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
|
||||||
private var musicPath: Preference? = null
|
|
||||||
private var videoPath: Preference? = null
|
|
||||||
private var commandPath: Preference? = null
|
|
||||||
private var incognito: SwitchPreferenceCompat? = null
|
|
||||||
private var downloadCard: SwitchPreferenceCompat? = null
|
|
||||||
private var apiKey: EditTextPreference? = null
|
|
||||||
private var concurrentFragments: SeekBarPreference? = null
|
|
||||||
private var limitRate: EditTextPreference? = null
|
|
||||||
private var aria2: SwitchPreferenceCompat? = null
|
|
||||||
private var sponsorblockFilters: MultiSelectListPreference? = null
|
|
||||||
private var embedSubtitles: SwitchPreferenceCompat? = null
|
|
||||||
private var embedThumbnail: SwitchPreferenceCompat? = null
|
|
||||||
private var addChapters: SwitchPreferenceCompat? = null
|
|
||||||
private var writeThumbnail: SwitchPreferenceCompat? = null
|
|
||||||
private var audioFormat: ListPreference? = null
|
|
||||||
private var videoFormat: ListPreference? = null
|
|
||||||
private var audioQuality: SeekBarPreference? = null
|
|
||||||
private var videoQuality: ListPreference? = null
|
|
||||||
private var updateYTDL: Preference? = null
|
|
||||||
private var updateApp: SwitchPreferenceCompat? = null
|
|
||||||
private var version: Preference? = null
|
|
||||||
private var updateUtil: UpdateUtil? = null
|
|
||||||
private var fileUtil: FileUtil? = null
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
|
||||||
updateUtil = UpdateUtil(requireContext())
|
|
||||||
fileUtil = FileUtil()
|
|
||||||
initPreferences()
|
|
||||||
initListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initPreferences() {
|
|
||||||
val preferences =
|
|
||||||
requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
|
||||||
val editor = preferences.edit()
|
|
||||||
musicPath = findPreference("music_path")
|
|
||||||
videoPath = findPreference("video_path")
|
|
||||||
commandPath = findPreference("command_path")
|
|
||||||
incognito = findPreference("incognito")
|
|
||||||
downloadCard = findPreference("download_card")
|
|
||||||
apiKey = findPreference("api_key")
|
|
||||||
concurrentFragments = findPreference("concurrent_fragments")
|
|
||||||
limitRate = findPreference("limit_rate")
|
|
||||||
aria2 = findPreference("aria2")
|
|
||||||
sponsorblockFilters = findPreference("sponsorblock_filter")
|
|
||||||
embedSubtitles = findPreference("embed_subtitles")
|
|
||||||
embedThumbnail = findPreference("embed_thumbnail")
|
|
||||||
addChapters = findPreference("add_chapters")
|
|
||||||
writeThumbnail = findPreference("write_thumbnail")
|
|
||||||
audioFormat = findPreference("audio_format")
|
|
||||||
videoFormat = findPreference("video_format")
|
|
||||||
audioQuality = findPreference("audio_quality")
|
|
||||||
videoQuality = findPreference("video_quality")
|
|
||||||
updateYTDL = findPreference("update_ytdl")
|
|
||||||
updateApp = findPreference("update_app")
|
|
||||||
version = findPreference("version")
|
|
||||||
version!!.summary = BuildConfig.VERSION_NAME
|
|
||||||
if (preferences.getString("music_path", "")!!.isEmpty()) {
|
|
||||||
editor.putString("music_path", getString(R.string.music_path))
|
|
||||||
}
|
|
||||||
if (preferences.getString("video_path", "")!!.isEmpty()) {
|
|
||||||
editor.putString("video_path", getString(R.string.video_path))
|
|
||||||
}
|
|
||||||
if (preferences.getString("command_path", "")!!.isEmpty()) {
|
|
||||||
editor.putString("command_path", getString(R.string.command_path))
|
|
||||||
}
|
|
||||||
editor.putBoolean("incognito", incognito!!.isChecked)
|
|
||||||
editor.putBoolean("download_card", downloadCard!!.isChecked)
|
|
||||||
editor.putString("api_key", apiKey!!.text)
|
|
||||||
editor.putInt("concurrent_fragments", concurrentFragments!!.value)
|
|
||||||
editor.putString("limit_rate", limitRate!!.text)
|
|
||||||
editor.putBoolean("aria2", aria2!!.isChecked)
|
|
||||||
editor.putStringSet("sponsorblock_filters", sponsorblockFilters!!.values)
|
|
||||||
editor.putBoolean("embed_subtitles", embedSubtitles!!.isChecked)
|
|
||||||
editor.putBoolean("embed_thumbnail", embedThumbnail!!.isChecked)
|
|
||||||
editor.putBoolean("add_chapters", addChapters!!.isChecked)
|
|
||||||
editor.putBoolean("write_thumbnail", writeThumbnail!!.isChecked)
|
|
||||||
editor.putString("audio_format", audioFormat!!.value)
|
|
||||||
editor.putString("video_format", videoFormat!!.value)
|
|
||||||
editor.putInt("audio_quality", audioQuality!!.value)
|
|
||||||
editor.putString("video_quality", videoQuality!!.value)
|
|
||||||
editor.putBoolean("update_app", updateApp!!.isChecked)
|
|
||||||
editor.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initListeners() {
|
|
||||||
val preferences =
|
|
||||||
requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
|
||||||
val editor = preferences.edit()
|
|
||||||
musicPath!!.summary = fileUtil?.formatPath(preferences.getString("music_path", "")!!)
|
|
||||||
musicPath!!.onPreferenceClickListener =
|
|
||||||
Preference.OnPreferenceClickListener {
|
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
|
||||||
musicPathResultLauncher.launch(intent)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
videoPath!!.summary = fileUtil?.formatPath(preferences.getString("video_path", "")!!)
|
|
||||||
videoPath!!.onPreferenceClickListener =
|
|
||||||
Preference.OnPreferenceClickListener {
|
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
|
||||||
videoPathResultLauncher.launch(intent)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
commandPath!!.summary = fileUtil?.formatPath(preferences.getString("command_path", "")!!)
|
|
||||||
commandPath!!.onPreferenceClickListener =
|
|
||||||
Preference.OnPreferenceClickListener {
|
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
|
||||||
commandPathResultLauncher.launch(intent)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
incognito!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val enable = newValue as Boolean
|
|
||||||
editor.putBoolean("incognito", enable)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
downloadCard!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val enable = newValue as Boolean
|
|
||||||
editor.putBoolean("download_card", enable)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
apiKey!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
editor.putString("api_key", newValue.toString())
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
concurrentFragments!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val value = newValue.toString().toInt()
|
|
||||||
editor.putInt("concurrent_fragments", value)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
limitRate!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
editor.putString("limit_rate", newValue.toString())
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
aria2!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val enable = newValue as Boolean
|
|
||||||
editor.putBoolean("aria2", enable)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
sponsorblockFilters!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
|
|
||||||
sponsorblockFilters!!.values = newValue as Set<String?>?
|
|
||||||
editor.putStringSet("sponsorblock_filters", newValue)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
embedSubtitles!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val embed = newValue as Boolean
|
|
||||||
editor.putBoolean("embed_subtitles", embed)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
embedThumbnail!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val embed = newValue as Boolean
|
|
||||||
editor.putBoolean("embed_thumbnail", embed)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
addChapters!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val add = newValue as Boolean
|
|
||||||
editor.putBoolean("add_chapters", add)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
writeThumbnail!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val write = newValue as Boolean
|
|
||||||
editor.putBoolean("write_thumbnail", write)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
audioFormat!!.summary = preferences.getString("audio_format", "")
|
|
||||||
audioFormat!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
editor.putString("audio_format", newValue.toString())
|
|
||||||
audioFormat!!.summary = newValue.toString()
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
videoFormat!!.summary = preferences.getString("video_format", "")
|
|
||||||
videoFormat!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
editor.putString("video_format", newValue.toString())
|
|
||||||
videoFormat!!.summary = newValue.toString()
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
audioQuality!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
editor.putInt("audio_format", newValue.toString().toInt())
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
videoQuality!!.summary = preferences.getString("video_quality", "")
|
|
||||||
videoQuality!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
editor.putString("video_quality", newValue.toString())
|
|
||||||
videoQuality!!.summary = newValue.toString()
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
updateYTDL!!.onPreferenceClickListener =
|
|
||||||
Preference.OnPreferenceClickListener {
|
|
||||||
updateUtil!!.updateYoutubeDL()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
updateApp!!.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
|
||||||
val enable = newValue as Boolean
|
|
||||||
editor.putBoolean("update_app", enable)
|
|
||||||
editor.apply()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
version!!.onPreferenceClickListener =
|
|
||||||
Preference.OnPreferenceClickListener {
|
|
||||||
if (!updateUtil!!.updateApp()) {
|
|
||||||
Toast.makeText(context, R.string.you_are_in_latest_version, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var musicPathResultLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult()
|
|
||||||
) { result ->
|
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
|
||||||
result.data?.data?.let {
|
|
||||||
activity?.contentResolver?.takePersistableUriPermission(
|
|
||||||
it,
|
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
)
|
|
||||||
}
|
|
||||||
changePath(musicPath, result.data, MUSIC_PATH_CODE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var videoPathResultLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult()
|
|
||||||
) { result ->
|
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
|
||||||
result.data?.data?.let {
|
|
||||||
activity?.contentResolver?.takePersistableUriPermission(
|
|
||||||
it,
|
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
)
|
|
||||||
}
|
|
||||||
changePath(videoPath, result.data, VIDEO_PATH_CODE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var commandPathResultLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.StartActivityForResult()
|
|
||||||
) { result ->
|
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
|
||||||
result.data?.data?.let {
|
|
||||||
activity?.contentResolver?.takePersistableUriPermission(
|
|
||||||
it,
|
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
)
|
|
||||||
}
|
|
||||||
changePath(commandPath, result.data, COMMAND_PATH_CODE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
|
|
||||||
Log.e("TEST", data!!.toUri(0))
|
|
||||||
val path = data.data.toString()
|
|
||||||
p!!.summary = fileUtil?.formatPath(data.data.toString())
|
|
||||||
val sharedPreferences =
|
|
||||||
requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
|
||||||
val editor = sharedPreferences.edit()
|
|
||||||
when (requestCode) {
|
|
||||||
MUSIC_PATH_CODE -> editor.putString("music_path", path)
|
|
||||||
VIDEO_PATH_CODE -> editor.putString("video_path", path)
|
|
||||||
COMMAND_PATH_CODE -> editor.putString("command_path", path)
|
|
||||||
}
|
|
||||||
editor.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val MUSIC_PATH_CODE = 33333
|
|
||||||
const val VIDEO_PATH_CODE = 55555
|
|
||||||
const val COMMAND_PATH_CODE = 77777
|
|
||||||
private const val TAG = "SettingsFragment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.deniscerri.ytdlnis.database.Video;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public interface IDownloaderService {
|
public interface IDownloaderService {
|
||||||
DownloadInfo getInfo();
|
DownloadInfo getDownloadInfo();
|
||||||
void addActivity(Activity activity, ArrayList<IDownloaderListener> callback);
|
void addActivity(Activity activity, ArrayList<IDownloaderListener> callback);
|
||||||
void removeActivity(Activity activity);
|
void removeActivity(Activity activity);
|
||||||
void updateQueue(ArrayList<Video> queue);
|
void updateQueue(ArrayList<Video> queue);
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,8 @@
|
||||||
package com.deniscerri.ytdlnis.util
|
package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.MediaScannerConnection
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.FileUtils
|
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import android.util.Log
|
|
||||||
import android.webkit.MimeTypeMap
|
|
||||||
import android.widget.Toast
|
|
||||||
import com.deniscerri.ytdlnis.DownloaderService
|
|
||||||
import com.deniscerri.ytdlnis.R
|
|
||||||
import com.deniscerri.ytdlnis.database.Video
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.net.URLDecoder
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
class FileUtil() {
|
|
||||||
|
|
||||||
|
class FileUtil {
|
||||||
fun deleteFile(path: String){
|
fun deleteFile(path: String){
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
|
|
@ -31,96 +15,4 @@ class FileUtil() {
|
||||||
if (path.isEmpty()) return false
|
if (path.isEmpty()) return false
|
||||||
return file.exists()
|
return file.exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formatPath(path: String) : String {
|
|
||||||
var dataValue = path
|
|
||||||
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "")
|
|
||||||
dataValue = dataValue.replace("%3A".toRegex(), "/")
|
|
||||||
try {
|
|
||||||
dataValue = URLDecoder.decode(dataValue, StandardCharsets.UTF_8.name())
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
val pieces = dataValue.split("/").toTypedArray()
|
|
||||||
var formattedPath = StringBuilder("/storage/")
|
|
||||||
if (pieces[0].equals("primary")){
|
|
||||||
formattedPath.append("emulated/0/")
|
|
||||||
}else{
|
|
||||||
formattedPath.append(pieces[0]).append("/")
|
|
||||||
}
|
|
||||||
pieces.forEachIndexed { i, it ->
|
|
||||||
if (i > 0 && it.isNotEmpty()){
|
|
||||||
formattedPath.append(it).append("/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return formattedPath.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun scanMedia(video: Video, context: Context) : String {
|
|
||||||
val files = ArrayList<File>()
|
|
||||||
val title: String = video.title.replace("[^a-zA-Z0-9]".toRegex(), "")
|
|
||||||
var pathTmp: String
|
|
||||||
val path = File(video.downloadPath)
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (file in path.listFiles()!!) {
|
|
||||||
if (file.isFile) {
|
|
||||||
pathTmp = file.absolutePath.replace("[^a-zA-Z0-9]".toRegex(), "")
|
|
||||||
if (pathTmp.contains(title)) {
|
|
||||||
files.add(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val paths = arrayOfNulls<String>(files.size)
|
|
||||||
for (i in files.indices) paths[i] = files[i].absolutePath
|
|
||||||
MediaScannerConnection.scanFile(context, paths, null, null)
|
|
||||||
return paths[0]!!
|
|
||||||
}catch (e: Exception){
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.getString(R.string.unfound_file);
|
|
||||||
}
|
|
||||||
fun moveFile(originDir : File, context:Context, destDir : Uri, progress: (p: Int) -> Unit){
|
|
||||||
originDir.listFiles()?.forEach {
|
|
||||||
val mimeType =
|
|
||||||
MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
|
||||||
|
|
||||||
if (it.name.equals("rList")){
|
|
||||||
it.delete()
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
val destUri = DocumentsContract.createDocument(
|
|
||||||
context.contentResolver,
|
|
||||||
destDir,
|
|
||||||
mimeType,
|
|
||||||
it.name
|
|
||||||
) ?: return@forEach
|
|
||||||
|
|
||||||
val inputStream = it.inputStream()
|
|
||||||
val outputStream = context.contentResolver.openOutputStream(destUri) ?: return@forEach
|
|
||||||
val fileLength = it.length()
|
|
||||||
var bytesCopied: Long = 0
|
|
||||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
|
||||||
var bytes = inputStream.read(buffer)
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (bytes >= 0) {
|
|
||||||
outputStream.write(buffer, 0, bytes)
|
|
||||||
bytesCopied += bytes
|
|
||||||
progress((bytesCopied * 100 / fileLength).toInt())
|
|
||||||
bytes = inputStream.read(buffer)
|
|
||||||
}
|
|
||||||
}catch (e : Exception){
|
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
inputStream.close()
|
|
||||||
outputStream.close()
|
|
||||||
|
|
||||||
it.delete()
|
|
||||||
}
|
|
||||||
originDir.delete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -29,30 +29,28 @@ class InfoUtil(context: Context) {
|
||||||
context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
||||||
key = sharedPreferences.getString("api_key", "")
|
key = sharedPreferences.getString("api_key", "")
|
||||||
databaseManager = DatabaseManager(context)
|
databaseManager = DatabaseManager(context)
|
||||||
|
val thread = Thread {
|
||||||
|
|
||||||
|
//get Locale
|
||||||
|
val country = genericRequest("https://ipwho.is/")
|
||||||
|
countryCODE = try {
|
||||||
|
country.getString("country_code")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"US"
|
||||||
|
}
|
||||||
|
val res = genericRequest(invidousURL + "stats")
|
||||||
|
useInvidous = res.length() != 0
|
||||||
|
}
|
||||||
|
thread.start()
|
||||||
|
thread.join()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
videos = ArrayList()
|
videos = ArrayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun init(){
|
|
||||||
if (countryCODE == null){
|
|
||||||
val country = genericRequest("https://ipwho.is/")
|
|
||||||
countryCODE = try {
|
|
||||||
country.getString("country_code")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
"US"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (useInvidous == false){
|
|
||||||
val res = genericRequest(invidousURL + "stats")
|
|
||||||
useInvidous = res.length() != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun search(query: String): ArrayList<Video?> {
|
fun search(query: String): ArrayList<Video?> {
|
||||||
init()
|
|
||||||
videos = ArrayList()
|
videos = ArrayList()
|
||||||
return if (key!!.isEmpty()) {
|
return if (key!!.isEmpty()) {
|
||||||
if (useInvidous) searchFromInvidous(query) else getFromYTDL(query)
|
if (useInvidous) searchFromInvidous(query) else getFromYTDL(query)
|
||||||
|
|
@ -135,7 +133,6 @@ class InfoUtil(context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
||||||
init()
|
|
||||||
videos = ArrayList()
|
videos = ArrayList()
|
||||||
if (key!!.isEmpty()) {
|
if (key!!.isEmpty()) {
|
||||||
return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
|
return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
|
||||||
|
|
@ -219,7 +216,6 @@ class InfoUtil(context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getVideo(id: String): Video? {
|
fun getVideo(id: String): Video? {
|
||||||
init()
|
|
||||||
if (key!!.isEmpty()) {
|
if (key!!.isEmpty()) {
|
||||||
return if (useInvidous) {
|
return if (useInvidous) {
|
||||||
val res = genericRequest(invidousURL + "videos/" + id)
|
val res = genericRequest(invidousURL + "videos/" + id)
|
||||||
|
|
@ -396,7 +392,6 @@ class InfoUtil(context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getTrending(context: Context): ArrayList<Video?> {
|
fun getTrending(context: Context): ArrayList<Video?> {
|
||||||
init()
|
|
||||||
videos = ArrayList()
|
videos = ArrayList()
|
||||||
return if (key!!.isEmpty()) {
|
return if (key!!.isEmpty()) {
|
||||||
if (useInvidous) getTrendingFromInvidous(context) else ArrayList()
|
if (useInvidous) getTrendingFromInvidous(context) else ArrayList()
|
||||||
|
|
@ -452,7 +447,6 @@ class InfoUtil(context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFromYTDL(query: String): ArrayList<Video?> {
|
fun getFromYTDL(query: String): ArrayList<Video?> {
|
||||||
init()
|
|
||||||
videos = ArrayList()
|
videos = ArrayList()
|
||||||
try {
|
try {
|
||||||
val request = YoutubeDLRequest(query)
|
val request = YoutubeDLRequest(query)
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,8 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.receiver.NotificationReceiver
|
import com.deniscerri.ytdlnis.receiver.NotificationReceiver
|
||||||
|
|
||||||
class NotificationUtil(var context: Context) {
|
class NotificationUtil(var context: Context) {
|
||||||
private val downloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID)
|
private var notificationBuilder: NotificationCompat.Builder =
|
||||||
private val commandDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID)
|
NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||||
private val fileTransferNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, FILE_TRANSFER_CHANNEL_ID)
|
|
||||||
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
|
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
|
||||||
|
|
||||||
fun createNotificationChannel() {
|
fun createNotificationChannel() {
|
||||||
|
|
@ -39,63 +38,13 @@ class NotificationUtil(var context: Context) {
|
||||||
channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||||
channel.description = description
|
channel.description = description
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
|
||||||
//file transfers
|
|
||||||
name = context.getString(R.string.file_transfer_notification_channel_name)
|
|
||||||
description = context.getString(R.string.file_transfer_notification_channel_description)
|
|
||||||
channel = NotificationChannel(FILE_TRANSFER_CHANNEL_ID, name, importance)
|
|
||||||
channel.description = description
|
|
||||||
notificationManager.createNotificationChannel(channel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFileTransferNotification(
|
|
||||||
pendingIntent: PendingIntent?,
|
|
||||||
title: String?,
|
|
||||||
) : Notification {
|
|
||||||
val intent = Intent(context, NotificationReceiver::class.java)
|
|
||||||
|
|
||||||
return fileTransferNotificationBuilder
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setCategory(Notification.CATEGORY_PROGRESS)
|
|
||||||
.setSmallIcon(R.drawable.ic_app_icon)
|
|
||||||
.setContentText("")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
||||||
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateFileTransferNotification(
|
|
||||||
id: Int,
|
|
||||||
progress: Int
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
fileTransferNotificationBuilder.setProgress(100, progress, false)
|
|
||||||
notificationManager.notify(id, fileTransferNotificationBuilder.build())
|
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBuilder(channel: String) : NotificationCompat.Builder {
|
|
||||||
when(channel) {
|
|
||||||
DOWNLOAD_SERVICE_CHANNEL_ID -> { return downloadNotificationBuilder}
|
|
||||||
COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID -> { return commandDownloadNotificationBuilder }
|
|
||||||
FILE_TRANSFER_CHANNEL_ID -> { return fileTransferNotificationBuilder }
|
|
||||||
}
|
|
||||||
return downloadNotificationBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun createDownloadServiceNotification(
|
fun createDownloadServiceNotification(
|
||||||
pendingIntent: PendingIntent?,
|
pendingIntent: PendingIntent?,
|
||||||
title: String?,
|
title: String?
|
||||||
channel: String
|
|
||||||
): Notification {
|
): Notification {
|
||||||
val notificationBuilder = getBuilder(channel)
|
|
||||||
|
|
||||||
val intent = Intent(context, NotificationReceiver::class.java)
|
val intent = Intent(context, NotificationReceiver::class.java)
|
||||||
intent.putExtra("cancel", "")
|
intent.putExtra("cancel", "")
|
||||||
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
|
@ -131,21 +80,17 @@ class NotificationUtil(var context: Context) {
|
||||||
desc: String,
|
desc: String,
|
||||||
progress: Int,
|
progress: Int,
|
||||||
queue: Int,
|
queue: Int,
|
||||||
title: String?,
|
title: String?
|
||||||
channel : String
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val notificationBuilder = getBuilder(channel)
|
|
||||||
var contentText = ""
|
var contentText = ""
|
||||||
if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + "\n"
|
if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}"""
|
||||||
contentText += desc.replace("\\[.*?\\] ".toRegex(), "")
|
contentText += desc.replace("\\[.*?\\] ".toRegex(), "")
|
||||||
try {
|
try {
|
||||||
notificationBuilder.setProgress(100, progress, false)
|
notificationBuilder.setProgress(100, progress, false)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||||
notificationManager.notify(id, notificationBuilder.build())
|
notificationManager.notify(id, notificationBuilder.build())
|
||||||
} catch (e: Exception) {
|
} catch (ignored: Exception) {
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +103,6 @@ class NotificationUtil(var context: Context) {
|
||||||
const val DOWNLOAD_NOTIFICATION_ID = 1
|
const val DOWNLOAD_NOTIFICATION_ID = 1
|
||||||
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
|
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
|
||||||
const val COMMAND_DOWNLOAD_NOTIFICATION_ID = 2
|
const val COMMAND_DOWNLOAD_NOTIFICATION_ID = 2
|
||||||
const val FILE_TRANSFER_CHANNEL_ID = "3"
|
|
||||||
private const val PROGRESS_MAX = 100
|
private const val PROGRESS_MAX = 100
|
||||||
private const val PROGRESS_CURR = 0
|
private const val PROGRESS_CURR = 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import java.io.InputStreamReader
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class UpdateUtil(var context: Context) {
|
class UpdateUtil(var context: Context) {
|
||||||
private val tag = "UpdateUtil"
|
private val tag = "UpdateUtil"
|
||||||
|
|
@ -164,12 +163,11 @@ class UpdateUtil(var context: Context) {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ status: UpdateStatus ->
|
.subscribe({ status: UpdateStatus ->
|
||||||
when (status) {
|
when (status) {
|
||||||
UpdateStatus.DONE ->
|
UpdateStatus.DONE -> Toast.makeText(
|
||||||
Toast.makeText(
|
context,
|
||||||
context,
|
context.getString(R.string.ytld_update_success),
|
||||||
context.getString(R.string.ytld_update_success),
|
Toast.LENGTH_LONG
|
||||||
Toast.LENGTH_LONG
|
).show()
|
||||||
).show()
|
|
||||||
UpdateStatus.ALREADY_UP_TO_DATE -> Toast.makeText(
|
UpdateStatus.ALREADY_UP_TO_DATE -> Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.you_are_in_latest_version),
|
context.getString(R.string.you_are_in_latest_version),
|
||||||
|
|
@ -178,7 +176,7 @@ class UpdateUtil(var context: Context) {
|
||||||
else -> Toast.makeText(context, status.toString(), Toast.LENGTH_LONG).show()
|
else -> Toast.makeText(context, status.toString(), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
updatingYTDL = false
|
updatingYTDL = false
|
||||||
} as ((UpdateStatus?) -> Unit)?) { e: Throwable? ->
|
}) { e: Throwable? ->
|
||||||
if (BuildConfig.DEBUG) Log.e(tag, context.getString(R.string.ytdl_update_failed), e)
|
if (BuildConfig.DEBUG) Log.e(tag, context.getString(R.string.ytdl_update_failed), e)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:background="@drawable/ic_app_icon"
|
android:background="@drawable/ic_app_icon"
|
||||||
android:scaleType="center" />
|
android:scaleType="center"
|
||||||
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/result_title"
|
android:id="@+id/result_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="120dp"
|
android:layout_height="100dp"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingEnd="20dp"
|
android:paddingEnd="20dp"
|
||||||
android:paddingBottom="20dp"
|
android:paddingBottom="20dp"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ buildscript {
|
||||||
|
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 4
|
def versionMinor = 4
|
||||||
def versionPatch = 8
|
def versionPatch = 9
|
||||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
|
@ -14,18 +14,17 @@ buildscript {
|
||||||
// dependency versions
|
// dependency versions
|
||||||
appCompatVer = '1.5.1'
|
appCompatVer = '1.5.1'
|
||||||
junitVer = '4.13.2'
|
junitVer = '4.13.2'
|
||||||
androidJunitVer = '1.1.1'
|
androidJunitVer = '1.1.4'
|
||||||
espressoVer = '3.2.0'
|
espressoVer = '3.5.0'
|
||||||
jacksonVer = '2.9.8'
|
jacksonVer = '2.9.8'
|
||||||
// supports java 1.6
|
// supports java 1.6
|
||||||
commonsIoVer = '2.5'
|
commonsIoVer = '2.5'
|
||||||
// supports java 1.6
|
// supports java 1.6
|
||||||
commonsCompressVer = '1.12'
|
commonsCompressVer = '1.12'
|
||||||
youtubedlAndroidVer = "a3b971724d"
|
youtubedlAndroidVer = "master-SNAPSHOT"
|
||||||
workVer = "2.7.1"
|
workVer = "2.7.1"
|
||||||
composeVer = "1.3.2"
|
composeVer = "1.3.2"
|
||||||
kotlinVer = "1.7.20"
|
kotlinVer = "1.7.20"
|
||||||
coroutineVer = "1.6.4"
|
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue