diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 00000000..d53f09fd
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 917ec63c..cef79b0f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -64,6 +64,7 @@
+
diff --git a/README.md b/README.md
index f85ed78e..4cec4e66 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,56 @@
-# ytdlnis
-Android App for downloading youtube videos
-Created as a project for the class, Mobile Programming.
+
+ ytldnis
+
+
+ Android App for downloading videos using yt-dlp
+
+
+ Created as a project for the class, Mobile Programming. Continued to update it because why not.
+
-Features:
----
-- [x] download audio
-- [x] download video
-- [x] download full playlist in audio or video format
-- [x] use custom download paths for each format
-- [x] download history
-- [x] search from the app
-- [x] works with youtube links
-- [x] share videos / playlists from the Youtube App using the share menu
-- [x] heads-up download notifications
+## Download
-|||
-|:-------------------------:|:-------------------------:|
-||
-||
+[Get Latest Release Here](https://github.com/deniscerri/ytdlnis/releases/)
+## Features:
+- download audio and video files
+- download full playlists
+- download selected videos on a list and add to a queue
+- use custom download paths for each format
+- remove non music parts from youtube audio files
+- embed subtitles on videos
+- add chapters to youtube videos by using sponsorblock chapters
+- select different download formats
+- share any yt-dlp compatible link into the app and start downloading
+- foreground download services which work even if app is cleared from recents and updates ui when u go back
+- search from the app
+- material you interface
+- really easy to use
+- run terminal commands for more flexibility
+
+## Screenshots
+
+
+
+
+## License
+
+[GNU GPL v3.0](https://github.com/deniscerri/ytdlnis/blob/main/LICENSE)
+
+## Thanks
+
+- [yt-dlp](https://github.com/yt-dlp/yt-dlp) and its contributors for making this tool possible. Without it this app wouldn't exist.
+- [youtubedl-android](https://github.com/yausername/youtubedl-android) for porting yt-dlp to android
+- [dvd](https://github.com/yausername/dvd) for showing how to implement youtubedl-android
+- [seal](https://github.com/JunkFood02/Seal) for certain design elements and features i wanted to use aswell in this app
+
+and alot of other internet forum strangers.
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/deniscerri/ytdl/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/deniscerri/ytdlnis/ExampleInstrumentedTest.java
similarity index 95%
rename from app/src/androidTest/java/com/deniscerri/ytdl/ExampleInstrumentedTest.java
rename to app/src/androidTest/java/com/deniscerri/ytdlnis/ExampleInstrumentedTest.java
index d5997f2e..bf95e48d 100644
--- a/app/src/androidTest/java/com/deniscerri/ytdl/ExampleInstrumentedTest.java
+++ b/app/src/androidTest/java/com/deniscerri/ytdlnis/ExampleInstrumentedTest.java
@@ -1,4 +1,4 @@
-package com.deniscerri.ytdl;
+package com.deniscerri.ytdlnis;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 53d5818d..3c06d882 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
+ package="com.deniscerri.ytdlnis" >
diff --git a/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java b/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java
deleted file mode 100644
index 6600590c..00000000
--- a/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.deniscerri.ytdl;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.IBinder;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
-
-public class DownloaderService extends Service {
-
- private LocalBinder binder = new LocalBinder();
- private NotificationCompat.Builder builder;
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- Intent theIntent = new Intent(this, MainActivity.class);
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
-
- String title = intent.getStringExtra("title");
- int id = intent.getIntExtra("id", 1);
-
- Notification notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,title);
- startForeground(id, notification);
- return binder;
- }
-
-
- @Override
- public boolean onUnbind(Intent intent) {
- stopForeground(true);
- stopSelf();
- return super.onUnbind(intent);
- }
-
- public class LocalBinder extends Binder {
-
- public DownloaderService getService() {
- return DownloaderService.this;
- }
- }
-}
diff --git a/app/src/main/java/com/deniscerri/ytdl/page/CustomCommandActivity.java b/app/src/main/java/com/deniscerri/ytdl/page/CustomCommandActivity.java
deleted file mode 100644
index 8c2f074e..00000000
--- a/app/src/main/java/com/deniscerri/ytdl/page/CustomCommandActivity.java
+++ /dev/null
@@ -1,206 +0,0 @@
-package com.deniscerri.ytdl.page;
-
-import static com.deniscerri.ytdl.App.notificationUtil;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.media.MediaScannerConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.text.method.ScrollingMovementMethod;
-import android.util.Log;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
-import com.deniscerri.ytdl.BuildConfig;
-import com.deniscerri.ytdl.DownloaderService;
-import com.deniscerri.ytdl.R;
-import com.deniscerri.ytdl.page.settings.SettingsFragment;
-import com.deniscerri.ytdl.util.NotificationUtil;
-import com.google.android.material.appbar.MaterialToolbar;
-import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
-import com.yausername.youtubedl_android.DownloadProgressCallback;
-import com.yausername.youtubedl_android.YoutubeDL;
-import com.yausername.youtubedl_android.YoutubeDLRequest;
-import java.io.File;
-import java.util.Collections;
-import java.util.LinkedList;
-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 CustomCommandActivity extends AppCompatActivity {
- private static final String TAG = "CustomCommandActivity";
- private MaterialToolbar topAppBar;
- private boolean running = false;
- private boolean isDownloadServiceRunning = false;
- public DownloaderService downloaderService;
- private CompositeDisposable compositeDisposable = new CompositeDisposable();
- private TextView output;
- private EditText input;
- private ExtendedFloatingActionButton fab;
- private ExtendedFloatingActionButton cancelFab;
- Context context;
-
- private final DownloadProgressCallback callback = (progress, etaInSeconds, line) -> CustomCommandActivity.this.runOnUiThread(() -> {
- output.append("\n"+line);
- notificationUtil.updateDownloadNotification(NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID,
- line, (int) progress, 0, getString(R.string.running_ytdlp_command));
- });
-
- private final ServiceConnection serviceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- downloaderService = ((DownloaderService.LocalBinder) service).getService();
- isDownloadServiceRunning = true;
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- downloaderService = null;
- isDownloadServiceRunning = false;
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_custom_command);
-
- context = getBaseContext();
- topAppBar = findViewById(R.id.custom_command_toolbar);
- topAppBar.setNavigationOnClickListener(view -> onBackPressed());
- output = findViewById(R.id.custom_command_output);
- output.setMovementMethod(new ScrollingMovementMethod());
-
- input = findViewById(R.id.command_edittext);
- input.requestFocus();
-
- fab = findViewById(R.id.command_fab);
- fab.setOnClickListener(view -> {
- if (isStoragePermissionGranted()){
- runCommand(input.getText().toString());
- }
- });
-
- cancelFab = findViewById(R.id.cancel_command_fab);
- cancelFab.setOnClickListener(view -> {
- compositeDisposable.clear();
- stopDownloadService();
- swapFabs();
- running = false;
- input.setEnabled(true);
- });
- }
-
- private void swapFabs(){
- int cancel = cancelFab.getVisibility();
- int start = fab.getVisibility();
- cancelFab.setVisibility(start);
- fab.setVisibility(cancel);
- }
-
-
- @Override
- public void onBackPressed() {
- super.onBackPressed();
- }
-
- private void runCommand(String text){
- if (running) {
- Toast.makeText(this, "Cannot start command! A command is already in progress", Toast.LENGTH_LONG).show();
- return;
- }
- if(!text.startsWith("yt-dlp ")){
- Toast.makeText(context, "Wrong input! Try Again!", Toast.LENGTH_SHORT).show();
- return;
- }
- input.setEnabled(false);
- output.setText("");
- startDownloadService(getString(R.string.running_ytdlp_command), NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID);
- 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");
-
- running = true;
- swapFabs();
-
- Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, callback))
- .subscribeOn(Schedulers.newThread())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(youtubeDLResponse -> {
- output.append(youtubeDLResponse.getOut());
- running = false;
- stopDownloadService();
- // MEDIA SCAN
- MediaScannerConnection.scanFile(context, new String[]{"/storage"}, null, null);
- input.setEnabled(true);
- swapFabs();
- }, e -> {
- if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e);
- output.append(e.getMessage());
- running = false;
- stopDownloadService();
- swapFabs();
- input.setEnabled(true);
- });
- compositeDisposable.add(disposable);
- }
-
- public void startDownloadService(String title, int id){
- if(isDownloadServiceRunning) return;
- Intent serviceIntent = new Intent(context, DownloaderService.class);
- serviceIntent.putExtra("title", title);
- serviceIntent.putExtra("id", id);
- context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
- }
-
- public void stopDownloadService(){
- if(!isDownloadServiceRunning) return;
- context.getApplicationContext().unbindService(serviceConnection);
- isDownloadServiceRunning = false;
- }
-
- public boolean isStoragePermissionGranted() {
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- }else{
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
- return false;
- }
- }
-}
diff --git a/app/src/main/java/com/deniscerri/ytdl/App.java b/app/src/main/java/com/deniscerri/ytdlnis/App.java
similarity index 90%
rename from app/src/main/java/com/deniscerri/ytdl/App.java
rename to app/src/main/java/com/deniscerri/ytdlnis/App.java
index 4ecccabb..16a862c0 100644
--- a/app/src/main/java/com/deniscerri/ytdl/App.java
+++ b/app/src/main/java/com/deniscerri/ytdlnis/App.java
@@ -1,24 +1,18 @@
-package com.deniscerri.ytdl;
+package com.deniscerri.ytdlnis;
import android.app.Application;
-import android.os.Environment;
-import android.provider.ContactsContract;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
-import com.deniscerri.ytdl.util.NotificationUtil;
-import com.deniscerri.ytdl.util.UpdateUtil;
+import com.deniscerri.ytdlnis.util.NotificationUtil;
import com.google.android.material.color.DynamicColors;
+import com.yausername.aria2c.Aria2c;
import com.yausername.ffmpeg.FFmpeg;
import com.yausername.youtubedl_android.YoutubeDL;
import com.yausername.youtubedl_android.YoutubeDLException;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.exceptions.UndeliverableException;
@@ -75,6 +69,7 @@ public class App extends Application {
private void initLibraries() throws YoutubeDLException {
YoutubeDL.getInstance().init(this);
FFmpeg.getInstance().init(this);
+ Aria2c.getInstance().init(this);
}
private void createNotificationChannels() {
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.java b/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.java
new file mode 100644
index 00000000..0d584aff
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.java
@@ -0,0 +1,374 @@
+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.media.MediaScannerConnection;
+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.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.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+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 activities = new ConcurrentHashMap<>();
+ private DownloadInfo downloadInfo = new DownloadInfo();
+ private LinkedList