added stateless downloads & more

changed folder structure to ytdlnis instead of ytdl
when downloads are finished, history fragment updates itself
changed video duration of livestreams to LIVE
Fixed update dialog showing when you dont have internet
removed download functionality from home fragment and put it all on downloadservice. This makes the download stateless and the ui can be updated when you go back to the app
did the same for custom command activity
slight changes on result card ui
added fastlane metadata
added support for other yt-dlp videos instead of just focusing on youtube. Youtube is still the main focus
updated database
slight design changes
made bottom navigation bar the same color as the background
changed download buttons background colors inside cards
fixed result items changing download status icons when scrolling through recyclerview
updated readme
pull/1/head
Denis Çerri 4 years ago
parent 2e355562d3
commit 6473f452a1
No known key found for this signature in database
GPG Key ID: 3F50F14A8E7F7A13

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="192.168.1.131:44159" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-09-23T20:00:37.414283600Z" />
</component>
</project>

@ -64,6 +64,7 @@
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_video.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_video_downloaded.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_video_stopped.xml" value="0.14" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/transparent_background.xml" value="0.1965" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/activity_custom_command.xml" value="0.15940685820203893" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/activity_main.xml" value="0.18" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/activity_settings.xml" value="0.16875" />

@ -1,23 +1,56 @@
# ytdlnis
Android App for downloading youtube videos
Created as a project for the class, Mobile Programming.
<h1 align="center">
ytldnis
</h1>
<p align="center">
Android App for downloading videos using yt-dlp
</p>
<p align="center">
Created as a project for the class, Mobile Programming. Continued to update it because why not.
</p>
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
|![image](https://user-images.githubusercontent.com/64997243/185808455-0b04f573-afe5-43d8-b6c8-6481b799e474.png)|![image](https://user-images.githubusercontent.com/64997243/185808467-7fcc9938-f124-45c1-8b29-3384269fb693.png)|
|:-------------------------:|:-------------------------:|
![image](https://user-images.githubusercontent.com/64997243/185808474-5df57756-6e27-4c03-9870-3c47f782bfb9.png)|![image](https://user-images.githubusercontent.com/64997243/185808477-073a8ea8-70ae-4702-9865-317149f2bb25.png)|
![image](https://user-images.githubusercontent.com/64997243/185808483-9b5e62c3-f28b-4bfb-9caf-adbb899ce702.png)|![image](https://user-images.githubusercontent.com/64997243/185808495-9930312c-34df-43f1-b7f3-e519d1231c09.png)|
[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
<div>
<img src="fastlane/metadata/android/en-US/images/screenshots/1.png" width="30%" />
<img src="fastlane/metadata/android/en-US/images/screenshots/2.png" width="30%" />
<img src="fastlane/metadata/android/en-US/images/screenshots/3.png" width="30%" />
<img src="fastlane/metadata/android/en-US/images/screenshots/4.png" width="30%" />
<img src="fastlane/metadata/android/en-US/images/screenshots/5.png" width="30%" />
<img src="fastlane/metadata/android/en-US/images/screenshots/6.png" width="30%" />
</div>
## 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.

@ -1,4 +1,4 @@
package com.deniscerri.ytdl;
package com.deniscerri.ytdlnis;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.deniscerri.ytdl" >
package="com.deniscerri.ytdlnis" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

@ -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;
}
}
}

@ -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;
}
}
}

@ -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() {

@ -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<Activity, 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 final DownloadProgressCallback callback = (progress, etaInSeconds, line) -> {
downloadInfo.setProgress((int) progress);
downloadInfo.setOutputLine(line);
downloadInfo.setDownloadQueue(downloadQueue);
notificationUtil.updateDownloadNotification(NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
line, (int) progress, downloadQueue.size(), downloadQueue.peek().getTitle());
try{
for (Activity activity: activities.keySet()){
activity.runOnUiThread(() -> {
IDownloaderListener callback = activities.get(activity);
callback.onDownloadProgress(downloadInfo);
});
}
}catch (Exception e){
e.printStackTrace();
}
};
@Override
public void onCreate() {
super.onCreate();
context = this;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Intent theIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
if(intent.getBooleanExtra("rebind", false)){
return binder;
}
int id = intent.getIntExtra("id", 1);
switch (id){
case NotificationUtil.DOWNLOAD_NOTIFICATION_ID:
ArrayList queue = (ArrayList<Video>) intent.getSerializableExtra("queue");
downloadQueue = new LinkedList<>();
downloadQueue.addAll(queue);
downloadInfo.setDownloadQueue(downloadQueue);
String title = downloadInfo.getVideo().getTitle();
Notification notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,title);
startForeground(NotificationUtil.DOWNLOAD_NOTIFICATION_ID, notification);
startDownload(downloadQueue);
break;
case NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID:
String command = intent.getStringExtra("command");
Notification command_notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,getString(R.string.running_ytdlp_command));
startForeground(NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID, command_notification);
startCommandDownload(command);
break;
}
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
stopForeground(true);
stopSelf();
return super.onUnbind(intent);
}
public class LocalBinder extends Binder implements IDownloaderService {
public DownloaderService getService() {
return DownloaderService.this;
}
public DownloadInfo getDownloadInfo(){
return downloadInfo;
}
public void addActivity(Activity activity, IDownloaderListener callback) {
if(!activities.containsKey(activity)){
activities.put(activity, callback);
}
}
public void removeActivity(Activity activity) {
activities.remove(activity);
}
public void updateQueue(ArrayList<Video> queue){
downloadQueue.addAll(queue);
}
public void cancelDownload(){
try{
YoutubeDL.getInstance().destroyProcessById(downloadProcessID);
}catch(Exception err){
Log.e(TAG, err.getMessage());
}
compositeDisposable.clear();
}
}
private void finishService(){
try{
for (Activity activity: activities.keySet()){
activity.runOnUiThread(() -> {
IDownloaderListener callback = activities.get(activity);
callback.onDownloadServiceEnd();
});
}
}catch (Exception e){
e.printStackTrace();
}
}
private void startDownload(LinkedList<Video> videos) {
Video video;
if(videos.size() == 0){
finishService();
return;
}
try {
video = videos.peek();
} catch (Exception e) {
finishService();
return;
}
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);
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("--no-mtime");
if (type.equals("mp3")) {
boolean removeNonMusic = sharedPreferences.getBoolean("remove_non_music", false);
if(removeNonMusic){
request.addOption("--sponsorblock-remove", "all");
}
request.addOption("--postprocessor-args", "-write_id3v1 1 -id3v2_version 3");
request.addOption("--add-metadata");
request.addOption("-x");
String format = sharedPreferences.getString("audio_format", "");
request.addOption("--audio-format", format);
if(format.equals("mp3") || format.equals("m4a") || format.equals("flac")){
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
if(embedThumb){
request.addOption("--embed-thumbnail");
}
}
} else if (type.equals("mp4")) {
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", "");
}
request.addOption("-f", "bestvideo+bestaudio/best");
String format = sharedPreferences.getString("video_format", "");
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() + "/%(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());
try{
for (Activity activity: activities.keySet()){
activity.runOnUiThread(() -> {
IDownloaderListener callback = activities.get(activity);
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());
try{
for (Activity activity: activities.keySet()){
activity.runOnUiThread(() -> {
IDownloaderListener callback = activities.get(activity);
callback.onDownloadError(downloadInfo);
});
}
}catch (Exception err){
err.printStackTrace();
}
// SCAN NEXT IN QUEUE
videos.remove();
startDownload(videos);
});
compositeDisposable.add(disposable);
}
private void startCommandDownload(String text){
if(!text.startsWith("yt-dlp ")){
Toast.makeText(context, "Wrong input! Try Again!", Toast.LENGTH_SHORT).show();
finishService();
return;
}
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(() -> {
IDownloaderListener callback = activities.get(activity);
callback.onDownloadEnd(downloadInfo);
callback.onDownloadServiceEnd();
});
}
}catch (Exception e){
e.printStackTrace();
}
}, e -> {
downloadInfo.setOutputLine(e.getMessage());
try{
for (Activity activity: activities.keySet()){
activity.runOnUiThread(() -> {
IDownloaderListener callback = activities.get(activity);
callback.onDownloadError(downloadInfo);
});
}
}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("mp3")) {
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,5 +1,6 @@
package com.deniscerri.ytdl;
package com.deniscerri.ytdlnis;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@ -7,18 +8,23 @@ import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
import com.deniscerri.ytdl.databinding.ActivityMainBinding;
import com.deniscerri.ytdl.page.HistoryFragment;
import com.deniscerri.ytdl.page.HomeFragment;
import com.deniscerri.ytdl.page.MoreFragment;
import com.deniscerri.ytdl.page.settings.SettingsActivity;
import com.deniscerri.ytdl.page.settings.SettingsFragment;
import com.deniscerri.ytdl.util.UpdateUtil;
import com.deniscerri.ytdlnis.database.Video;
import com.deniscerri.ytdlnis.databinding.ActivityMainBinding;
import com.deniscerri.ytdlnis.page.HistoryFragment;
import com.deniscerri.ytdlnis.page.HomeFragment;
import com.deniscerri.ytdlnis.page.MoreFragment;
import com.deniscerri.ytdlnis.page.settings.SettingsActivity;
import com.deniscerri.ytdlnis.service.IDownloaderListener;
import com.deniscerri.ytdlnis.service.IDownloaderService;
import com.deniscerri.ytdlnis.util.UpdateUtil;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity{
@ -37,17 +43,27 @@ public class MainActivity extends AppCompatActivity{
private boolean isDownloadServiceRunning = false;
public DownloaderService downloaderService;
private IDownloaderListener listener = null;
private IDownloaderService iDownloaderService;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
downloaderService = ((DownloaderService.LocalBinder) service).getService();
iDownloaderService = (IDownloaderService) service;
isDownloadServiceRunning = true;
try{
iDownloaderService.addActivity(MainActivity.this, listener);
listener.onDownloadStart(iDownloaderService.getDownloadInfo());
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
downloaderService = null;
iDownloaderService = null;
isDownloadServiceRunning = false;
}
};
@ -61,7 +77,7 @@ public class MainActivity extends AppCompatActivity{
setContentView(R.layout.activity_main);
setContentView(binding.getRoot());
context = getBaseContext();
reconnectDownloadService();
checkUpdate();
fm = getSupportFragmentManager();
@ -70,6 +86,8 @@ public class MainActivity extends AppCompatActivity{
historyFragment = new HistoryFragment();
moreFragment = new MoreFragment();
listener = homeFragment.listener;
initFragments();
binding.bottomNavigationView.setOnItemSelectedListener(item -> {
@ -140,22 +158,69 @@ public class MainActivity extends AppCompatActivity{
lastFragment = f;
}
public void startDownloadService(String title, int id){
if(isDownloadServiceRunning) return;
public void startDownloadService(ArrayList<Video> downloadQueue, IDownloaderListener awaitingListener){
if(isDownloadServiceRunning){
iDownloaderService.updateQueue(downloadQueue);
Toast.makeText(context, getString(R.string.added_to_queue), Toast.LENGTH_SHORT).show();
return;
}
listener = awaitingListener;
Intent serviceIntent = new Intent(context, DownloaderService.class);
serviceIntent.putExtra("title", title);
serviceIntent.putExtra("id", id);
serviceIntent.putExtra("queue", downloadQueue);
context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public void stopDownloadService(){
if(!isDownloadServiceRunning) return;
iDownloaderService.removeActivity(this);
context.getApplicationContext().unbindService(serviceConnection);
downloaderService.stopForeground(true);
downloaderService.stopSelf();
isDownloadServiceRunning = false;
}
public void cancelDownloadService(){
if(!isDownloadServiceRunning) return;
iDownloaderService.cancelDownload();
stopDownloadService();
}
public boolean isDownloadServiceRunning() {
ActivityManager.RunningServiceInfo service = getService(DownloaderService.class);
if(service != null){
isDownloadServiceRunning = true;
return true;
}
return false;
}
private void reconnectDownloadService(){
ActivityManager.RunningServiceInfo service = getService(DownloaderService.class);
if(service != null){
Intent serviceIntent = new Intent(context.getApplicationContext(), DownloaderService.class);
serviceIntent.putExtra("rebind", true);
context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
isDownloadServiceRunning = true;
}
}
private ActivityManager.RunningServiceInfo getService(Class className){
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (className.getName().equals(service.service.getClassName())) {
return service;
}
}
return null;
}
private void checkUpdate(){
UpdateUtil updateUtil = new UpdateUtil(this);
updateUtil.updateApp();
}
public void updateHistoryFragment(){
historyFragment.initCards();
}
}

@ -1,6 +1,5 @@
package com.deniscerri.ytdl.adapter;
package com.deniscerri.ytdlnis.adapter;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
@ -11,8 +10,6 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
@ -20,9 +17,8 @@ import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.database.Video;
import com.google.android.material.button.MaterialButton;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.database.Video;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.adapter;
package com.deniscerri.ytdlnis.adapter;
import android.app.Activity;
import android.graphics.Color;
@ -14,12 +14,11 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.database.Video;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.database.Video;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.squareup.picasso.Picasso;
@ -28,7 +27,7 @@ import java.util.ArrayList;
public class HomeRecyclerViewAdapter extends RecyclerView.Adapter<HomeRecyclerViewAdapter.ViewHolder> {
private ArrayList<Video> videoList;
private ArrayList<MaterialCardView> checkedVideos;
private ArrayList<Integer> checkedVideos;
private final OnItemClickListener onItemClickListener;
private Activity activity;
@ -53,13 +52,12 @@ public class HomeRecyclerViewAdapter extends RecyclerView.Adapter<HomeRecyclerVi
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View cardView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.result_card, parent, false);
checkedVideos = new ArrayList<>();
return new HomeRecyclerViewAdapter.ViewHolder(cardView, onItemClickListener);
}
@ -113,12 +111,23 @@ public class HomeRecyclerViewAdapter extends RecyclerView.Adapter<HomeRecyclerVi
if(video.isAudioDownloaded() == 1){
musicBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded));
}else{
musicBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music));
}
if(video.isVideoDownloaded() == 1){
videoBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded));
}else{
videoBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video));
}
if(checkedVideos.contains(position)){
card.setChecked(true);
card.setStrokeWidth(5);
}else{
card.setChecked(false);
card.setStrokeWidth(0);
}
card.setChecked(false);
card.setStrokeWidth(0);
card.setTag(videoID + "##card");
card.setOnLongClickListener(view -> {
checkCard(card, position);
@ -134,10 +143,10 @@ public class HomeRecyclerViewAdapter extends RecyclerView.Adapter<HomeRecyclerVi
private void checkCard(MaterialCardView card, int position){
if(card.isChecked()){
card.setStrokeWidth(0);
checkedVideos.remove(card);
checkedVideos.remove(position);
}else{
card.setStrokeWidth(5);
checkedVideos.add(card);
checkedVideos.add(position);
}
card.setChecked(!card.isChecked());
onItemClickListener.onCardClick(position, card.isChecked());

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.api;
package com.deniscerri.ytdlnis.api;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@ -7,17 +7,22 @@ import android.text.Html;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.deniscerri.ytdl.database.DBManager;
import com.deniscerri.ytdl.database.Video;
import com.deniscerri.ytdlnis.database.DBManager;
import com.deniscerri.ytdlnis.database.Video;
import com.yausername.youtubedl_android.YoutubeDL;
import com.yausername.youtubedl_android.YoutubeDLRequest;
import com.yausername.youtubedl_android.YoutubeDLResponse;
import com.yausername.youtubedl_android.mapper.VideoInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Locale;
public class YoutubeAPIManager {
private static final String TAG = "API MANAGER";
@ -25,6 +30,7 @@ public class YoutubeAPIManager {
private ArrayList<Video> videos;
private String key;
private DBManager dbManager;
private YoutubeDLRequest youtubeDLRequest;
public YoutubeAPIManager(Context context) {
@Nullable ApplicationInfo applicationInfo;
@ -175,10 +181,10 @@ public class YoutubeAPIManager {
int downloadedAudio = dbManager.checkDownloaded(id, "mp3");
int downloadedVideo = dbManager.checkDownloaded(id, "mp4");
String url = "https://www.youtube.com/watch?v=" + id;
int isPLaylist = 0;
video = new Video(id, title, author, duration, thumb, downloadedAudio, downloadedVideo, isPLaylist);
video = new Video(id, url, title, author, duration, thumb, downloadedAudio, downloadedVideo, isPLaylist, "youtube");
}catch(Exception e){
Log.e(TAG, e.toString());
}
@ -251,6 +257,32 @@ public class YoutubeAPIManager {
return o;
}
public Video getFromYTDL(String query){
Video video = null;
try {
VideoInfo streamInfo = YoutubeDL.getInstance().getInfo(query);
String id = streamInfo.getId();
video = new Video(
id,
streamInfo.getUrl(),
streamInfo.getTitle(),
streamInfo.getUploader(),
formatIntegerDuration(streamInfo.getDuration()),
streamInfo.getThumbnail(),
dbManager.checkDownloaded(id, "mp3"),
dbManager.checkDownloaded(id, "mp4"),
0,
streamInfo.getWebpageUrlBasename()
);
}catch(Exception e){
e.printStackTrace();
}
return video;
}
public ArrayList<Video> getTrending(Context context) throws JSONException{
videos = new ArrayList<>();
@ -283,6 +315,11 @@ public class YoutubeAPIManager {
}
public String formatDuration(String dur){
if(dur.equals("P0D")){
return "LIVE";
}
String duration = "";
String tmp = "";
String hours = "";
@ -325,9 +362,23 @@ public class YoutubeAPIManager {
if(Integer.parseInt(minutes) < 10) minutes = "0" + minutes;
duration = hours + ":" + minutes + ":" + seconds;
}
if(duration.equals("0:00:00")){
duration = "";
}
return duration;
}
public String formatIntegerDuration(int dur){
int hours = dur / 10000;
int minutes = (dur % 10000) / 100;
int seconds = dur % 100;
return String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, seconds);
}
// public ArrayList<String> getSearchHints(String query){
// String url = "https://google.com/complete/search?client=youtube&q="+query;
// ArrayList<String> searchHints = new ArrayList<>();

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.database;
package com.deniscerri.ytdlnis.database;
import android.content.ContentValues;
import android.content.Context;
@ -10,11 +10,12 @@ import java.util.ArrayList;
public class DBManager extends SQLiteOpenHelper {
public static final String db_name = "ytdlnis_db";
public static final int db_version = 2;
public static final int db_version = 6;
public static final String results_table_name = "results";
public static final String history_table_name = "history";
public static final String id = "id";
public static final String videoId = "videoId";
public static final String url = "url";
public static final String title = "title";
public static final String author = "author";
public static final String duration = "duration";
@ -24,6 +25,7 @@ public class DBManager extends SQLiteOpenHelper {
public static final String type = "type";
public static final String time = "time";
public static final String isPlaylistItem = "isPlaylistItem";
public static final String website = "website";
public DBManager(Context context){
@ -35,13 +37,15 @@ public class DBManager extends SQLiteOpenHelper {
String query = "CREATE TABLE " + results_table_name + " ("
+ id + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ videoId + " TEXT,"
+ url + " TEXT,"
+ title + " TEXT,"
+ author + " TEXT,"
+ duration + " TEXT,"
+ thumb + " TEXT,"
+ downloadedAudio + " INTEGER,"
+ downloadedVideo + " INTEGER,"
+ isPlaylistItem + " INTENGER)";
+ isPlaylistItem + " INTENGER,"
+ website + " TEXT)";
sqLiteDatabase.execSQL(query);
@ -107,14 +111,16 @@ public class DBManager extends SQLiteOpenHelper {
if(cursor.moveToFirst()){
do {
// on below line we are adding the data from cursor to our array list.
list.add(new Video(cursor.getString(1),
cursor.getString(2),
cursor.getString(3),
cursor.getString(4),
cursor.getString(5),
cursor.getInt(6),
cursor.getInt(7),
cursor.getInt(8)));
list.add(new Video(cursor.getString(1), //id
cursor.getString(2), //url
cursor.getString(3), //title
cursor.getString(4), //author
cursor.getString(5), //duration
cursor.getString(6), //thumb
cursor.getInt(7), //downloadedAudio
cursor.getInt(8), //downloadedVideo
cursor.getInt(9), //isPlaylistItem
cursor.getString(10))); //website
} while (cursor.moveToNext());
}
@ -155,6 +161,7 @@ public class DBManager extends SQLiteOpenHelper {
for(Video v : videot){
values.put(videoId, v.getVideoId());
values.put(url, v.getURL());
values.put(title, v.getTitle());
values.put(author, v.getAuthor());
values.put(duration, v.getDuration());
@ -162,6 +169,7 @@ public class DBManager extends SQLiteOpenHelper {
values.put(downloadedAudio, v.isAudioDownloaded());
values.put(downloadedVideo, v.isVideoDownloaded());
values.put(isPlaylistItem, v.getIsPlaylistItem());
values.put(website, v.getWebsite());
db.insert(results_table_name, null, values);
}

@ -1,23 +1,27 @@
package com.deniscerri.ytdl.database;
package com.deniscerri.ytdlnis.database;
public class Video {
import java.io.Serializable;
public class Video implements Serializable {
private int id;
private String videoId;
private String title;
private String author;
private String duration;
private String thumb;
private String url;
private String downloadedType;
private int downloadedAudio;
private int downloadedVideo;
private String downloadedTime;
private int isPlaylistItem;
private String website;
// RESULTS OBJECT
public Video(String videoId, String title, String author, String duration, String thumb,
int downloadedAudio, int downloadedVideo, int isPlaylistItem) {
public Video(String videoId, String url, String title, String author, String duration, String thumb,
int downloadedAudio, int downloadedVideo, int isPlaylistItem, String website) {
this.videoId = videoId;
this.url = url;
this.title = title;
this.author = author;
this.duration = duration;
@ -25,6 +29,7 @@ public class Video {
this.downloadedVideo = downloadedVideo;
this.downloadedAudio = downloadedAudio;
this.isPlaylistItem = isPlaylistItem;
this.website = website;
}
//HISTORY OBJECT
@ -105,4 +110,12 @@ public class Video {
public int isVideoDownloaded() {
return downloadedVideo;
}
public String getURL(){ return url; }
public void setURL(String url) { this.url = url;}
public String getWebsite() { return website; }
public void setWebsite(String site){ this.website = site; }
}

@ -0,0 +1,175 @@
package com.deniscerri.ytdlnis.page;
import android.Manifest;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
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 androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.DownloaderService;
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.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import java.util.Date;
public class CustomCommandActivity extends AppCompatActivity {
private static final String TAG = "CustomCommandActivity";
private MaterialToolbar topAppBar;
private boolean isDownloadServiceRunning = false;
public DownloaderService downloaderService;
private TextView output;
private EditText input;
private ExtendedFloatingActionButton fab;
private ExtendedFloatingActionButton cancelFab;
private IDownloaderService iDownloaderService;
Context context;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
downloaderService = ((DownloaderService.LocalBinder) service).getService();
iDownloaderService = (IDownloaderService) service;
isDownloadServiceRunning = true;
try{
iDownloaderService.addActivity(CustomCommandActivity.this, listener);
listener.onDownloadStart(iDownloaderService.getDownloadInfo());
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
downloaderService = null;
iDownloaderService = null;
isDownloadServiceRunning = false;
}
};
public IDownloaderListener listener = new IDownloaderListener() {
public void onDownloadStart(DownloadInfo info) {
input.setEnabled(false);
output.setText("");
swapFabs();
}
public void onDownloadProgress(DownloadInfo info) {
output.append("\n" + info.getOutputLine());
}
public void onDownloadError(DownloadInfo info) {
output.append("\n" + info.getOutputLine());
input.setEnabled(true);
swapFabs();
}
public void onDownloadEnd(DownloadInfo info) {
output.append(info.getOutputLine());
// MEDIA SCAN
MediaScannerConnection.scanFile(context, new String[]{"/storage"}, null, null);
input.setEnabled(true);
swapFabs();
}
public void onDownloadServiceEnd() {
stopDownloadService();
}
};
@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()){
startDownloadService(input.getText().toString(), NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID);
}
});
cancelFab = findViewById(R.id.cancel_command_fab);
cancelFab.setOnClickListener(view -> {
cancelDownloadService();
swapFabs();
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();
}
public void startDownloadService(String command, int id){
if(isDownloadServiceRunning) return;
Intent serviceIntent = new Intent(context, DownloaderService.class);
serviceIntent.putExtra("command", command);
serviceIntent.putExtra("id", id);
context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public void stopDownloadService(){
Log.e(TAG, String.valueOf(isDownloadServiceRunning));
if(!isDownloadServiceRunning) return;
iDownloaderService.removeActivity(this);
context.getApplicationContext().unbindService(serviceConnection);
downloaderService.stopForeground(true);
downloaderService.stopSelf();
isDownloadServiceRunning = false;
}
public void cancelDownloadService(){
if(!isDownloadServiceRunning) return;
iDownloaderService.cancelDownload();
stopDownloadService();
}
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;
}
}
}

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.page;
package com.deniscerri.ytdlnis.page;
import android.content.ClipData;
import android.content.ClipboardManager;
@ -7,7 +7,6 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.widget.SearchView;
import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
@ -21,16 +20,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.adapter.HistoryRecyclerViewAdapter;
import com.deniscerri.ytdl.database.DBManager;
import com.deniscerri.ytdl.database.Video;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.adapter.HistoryRecyclerViewAdapter;
import com.deniscerri.ytdlnis.database.DBManager;
import com.deniscerri.ytdlnis.database.Video;
import com.facebook.shimmer.ShimmerFrameLayout;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
@ -104,7 +101,7 @@ public class HistoryFragment extends Fragment implements HistoryRecyclerViewAdap
}
private void initCards(){
public void initCards(){
shimmerCards.startShimmer();
shimmerCards.setVisibility(View.VISIBLE);
historyRecyclerViewAdapter.clear();
@ -205,7 +202,6 @@ public class HistoryFragment extends Fragment implements HistoryRecyclerViewAdap
delete_dialog.show();
return true;
case R.id.refresh_history:
historyRecyclerViewAdapter.clear();
initCards();
return true;
}

@ -1,10 +1,9 @@
package com.deniscerri.ytdl.page;
package com.deniscerri.ytdlnis.page;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.os.Bundle;
@ -20,53 +19,37 @@ import android.view.Window;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.deniscerri.ytdl.App;
import com.deniscerri.ytdl.BuildConfig;
import com.deniscerri.ytdl.MainActivity;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.adapter.HomeRecyclerViewAdapter;
import com.deniscerri.ytdl.api.YoutubeAPIManager;
import com.deniscerri.ytdl.database.DBManager;
import com.deniscerri.ytdl.database.Video;
import com.deniscerri.ytdl.page.settings.SettingsActivity;
import com.deniscerri.ytdl.util.NotificationUtil;
import com.deniscerri.ytdlnis.MainActivity;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.adapter.HomeRecyclerViewAdapter;
import com.deniscerri.ytdlnis.api.YoutubeAPIManager;
import com.deniscerri.ytdlnis.database.DBManager;
import com.deniscerri.ytdlnis.database.Video;
import com.deniscerri.ytdlnis.service.DownloadInfo;
import com.deniscerri.ytdlnis.service.IDownloaderListener;
import com.facebook.shimmer.ShimmerFrameLayout;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.yausername.youtubedl_android.DownloadProgressCallback;
import com.yausername.youtubedl_android.YoutubeDL;
import com.yausername.youtubedl_android.YoutubeDLRequest;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Queue;
import java.util.TimeZone;
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 HomeFragment extends Fragment implements HomeRecyclerViewAdapter.OnItemClickListener, View.OnClickListener {
@ -81,7 +64,6 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
private CoordinatorLayout downloadAllFab;
private CoordinatorLayout homeFabs;
private BottomSheetDialog bottomSheet;
Context context;
Activity activity;
private MaterialToolbar topAppBar;
@ -89,25 +71,92 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
private static final String TAG = "HomeFragment";
private ArrayList<Video> resultObjects;
private ArrayList<Video> selectedObjects;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
public ArrayList<Video> selectedObjects;
private DBManager dbManager;
private YoutubeAPIManager youtubeAPIManager;
private Queue<Video> downloadQueue;
private ArrayList<Video> downloadQueue;
MainActivity mainActivity;
private final NotificationUtil notificationUtil = App.notificationUtil;
private final DownloadProgressCallback callback = new DownloadProgressCallback() {
@Override
public void onProgressUpdate(float progress, long etaInSeconds, String line) {
public IDownloaderListener listener = new IDownloaderListener() {
public void onDownloadStart(DownloadInfo downloadInfo) {
if(downloadInfo != null){
String id = downloadInfo.getVideo().getVideoId();
String type = downloadInfo.getVideo().getDownloadedType();
MaterialButton clickedButton = recyclerView.findViewWithTag(id + "##"+type);
progressBar = fragmentView.findViewWithTag(id + "##progress");
progressBar.setVisibility(View.VISIBLE);
//scroll to Card
recyclerView.scrollToPosition(resultObjects.indexOf(findVideo(id)));
progressBar.setProgress(0);
downloading = true;
topAppBar.getMenu().findItem(R.id.cancel_download).setVisible(true);
if (clickedButton != null) {
if (type.equals("mp3")) {
clickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music));
} else {
clickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video));
}
}
}
}
public void onDownloadProgress(DownloadInfo info) {
activity.runOnUiThread(() -> {
progressBar.setProgress((int) progress);
notificationUtil.updateDownloadNotification(NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
line, (int) progress, downloadQueue.size(), downloadQueue.peek().getTitle());
try{
progressBar = fragmentView.findViewWithTag(info.getVideo().getVideoId()+"##progress");
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(info.getProgress());
}catch(Exception ignored){}
});
}
public void onDownloadError(DownloadInfo info){
try{
progressBar.setProgress(0);
progressBar.setVisibility(View.GONE);
downloading = false;
topAppBar.getMenu().findItem(R.id.cancel_download).setVisible(false);
}catch(Exception ignored){}
}
public void onDownloadEnd(DownloadInfo downloadInfo) {
try{
progressBar.setProgress(0);
progressBar.setVisibility(View.GONE);
Video item = downloadInfo.getVideo();
String id = item.getVideoId();
String type = item.getDownloadedType();
MaterialButton theClickedButton = recyclerView.findViewWithTag(id + "##"+type);
if (theClickedButton != null) {
if (type.equals("mp3")) {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded));
} else {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded));
}
}
addToHistory(item, new Date());
updateDownloadStatusOnResult(item, type);
downloading = false;
topAppBar.getMenu().findItem(R.id.cancel_download).setVisible(false);
// MEDIA SCAN
MediaScannerConnection.scanFile(context, new String[]{downloadInfo.getDownloadPath()}, null, null);
mainActivity.updateHistoryFragment();
}catch(Exception ignored){}
}
public void onDownloadServiceEnd() {
mainActivity.stopDownloadService();
}
};
@ -126,8 +175,7 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
compositeDisposable = new CompositeDisposable();
downloadQueue = new LinkedList<>();
downloadQueue = new ArrayList<>();
resultObjects = new ArrayList<>();
selectedObjects = new ArrayList<>();
@ -234,6 +282,10 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
}
};
if(mainActivity.isDownloadServiceRunning()){
topAppBar.getMenu().findItem(R.id.cancel_download).setVisible(true);
}
topAppBar.getMenu().findItem(R.id.search).setOnActionExpandListener(onActionExpandListener);
SearchView searchView = (SearchView) topAppBar.getMenu().findItem(R.id.search).getActionView();
searchView.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
@ -267,10 +319,9 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
initCards();
}else if(itemId == R.id.cancel_download){
try{
compositeDisposable.clear();
mainActivity.stopDownloadService();
mainActivity.cancelDownloadService();
topAppBar.getMenu().findItem(itemId).setVisible(false);
downloadQueue = new LinkedList<>();
downloadQueue = new ArrayList<>();
downloading = false;
String id = progressBar.getTag().toString().split("##progress")[0];
@ -319,6 +370,8 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
if (inputQuery.contains("playlist?list=")) {
type = "Playlist";
}
}else if(inputQuery.contains("http")){
type = "Default";
}
Log.e(TAG, inputQuery + " " + type);
@ -439,6 +492,39 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
thread.start();
break;
}
case "Default" : {
Thread thread = new Thread(new Runnable() {
private final String query;
{
this.query = inputQuery;
}
@Override
public void run() {
try {
resultObjects.clear();
Video video = youtubeAPIManager.getFromYTDL(query);
if (video != null) {
resultObjects.add(video);
dbManager.clearResults();
dbManager.addToResults(resultObjects);
}
new Handler(Looper.getMainLooper()).post(() -> {
homeRecyclerViewAdapter.setVideoList(resultObjects, true);
shimmerCards.stopShimmer();
shimmerCards.setVisibility(View.GONE);
});
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
});
thread.start();
break;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
@ -459,154 +545,33 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
}
private void startDownload(Queue<Video> videos) {
Video video;
if(videos.size() == 0){
mainActivity.stopDownloadService();
return;
}
try {
video = videos.peek();
} catch (Exception e) {
mainActivity.stopDownloadService();
return;
}
String id = video.getVideoId();
String url = "https://www.youtube.com/watch?v=" + id;
YoutubeDLRequest request = new YoutubeDLRequest(url);
String type = video.getDownloadedType();
File youtubeDLDir = getDownloadLocation(type);
Log.e(TAG, youtubeDLDir.getAbsolutePath());
MaterialButton clickedButton = null;
@Override
public void onButtonClick(int position, String type) {
Log.e(TAG, type);
Video vid = resultObjects.get(position);
vid.setDownloadedType(type);
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
downloadQueue.add(vid);
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);
request.addOption("-N", concurrentFragments);
if (downloading) {
Toast.makeText(context, R.string.added_to_queue, Toast.LENGTH_LONG).show();
return;
}
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("--no-mtime");
if (type.equals("mp3")) {
boolean removeNonMusic = sharedPreferences.getBoolean("remove_non_music", false);
if(removeNonMusic){
request.addOption("--sponsorblock-remove", "all");
}
request.addOption("--postprocessor-args", "-write_id3v1 1 -id3v2_version 3");
request.addOption("--add-metadata");
request.addOption("-x");
String format = sharedPreferences.getString("audio_format", "");
request.addOption("--audio-format", format);
if(format.equals("mp3") || format.equals("m4a") || format.equals("flac")){
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
if(embedThumb){
request.addOption("--embed-thumbnail");
}
}
clickedButton = recyclerView.findViewWithTag(id + "##mp3");
} else if (type.equals("mp4")) {
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", "");
}
request.addOption("-f", "bestvideo+bestaudio/best");
String format = sharedPreferences.getString("video_format", "");
request.addOption("--merge-output-format", format);
if(!format.equals("webm")){
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
if(embedThumb){
request.addOption("--embed-thumbnail");
}
}
clickedButton = recyclerView.findViewWithTag(id + "##mp4");
if (isStoragePermissionGranted()){
mainActivity.startDownloadService(downloadQueue, listener);
downloadQueue.clear();
}
}
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s");
progressBar = fragmentView.findViewWithTag(id + "##progress");
progressBar.setVisibility(View.VISIBLE);
//scroll to Card
recyclerView.scrollToPosition(resultObjects.indexOf(findVideo(id)));
progressBar.setProgress(0);
downloading = true;
topAppBar.getMenu().findItem(R.id.cancel_download).setVisible(true);
Video theVideo = video;
MaterialButton theClickedButton = clickedButton;
if (theClickedButton != null) {
if (type.equals("mp3")) {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music));
} else {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video));
}
public boolean isStoragePermissionGranted() {
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
}else{
downloadQueue = new ArrayList<>();
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, callback))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(youtubeDLResponse -> {
progressBar.setProgress(0);
progressBar.setVisibility(View.GONE);
if (theClickedButton != null) {
if (type.equals("mp3")) {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded));
} else {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded));
}
}
addToHistory(theVideo, new Date());
updateDownloadStatusOnResult(theVideo, type);
downloading = false;
topAppBar.getMenu().findItem(R.id.cancel_download).setVisible(false);
// MEDIA SCAN
MediaScannerConnection.scanFile(context, new String[]{youtubeDLDir.getAbsolutePath()}, null, null);
// SCAN NEXT IN QUEUE
videos.remove();
startDownload(videos);
}, e -> {
if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e);
Toast.makeText(context, R.string.failed_download, Toast.LENGTH_LONG).show();
progressBar.setProgress(0);
progressBar.setVisibility(View.GONE);
downloading = false;
topAppBar.getMenu().findItem(R.id.cancel_download).setVisible(false);
// SCAN NEXT IN QUEUE
videos.remove();
startDownload(videos);
});
compositeDisposable.add(disposable);
}
@ -643,56 +608,6 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
}
@NonNull
private File getDownloadLocation(String type) {
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
String downloadsDir;
if (type.equals("mp3")) {
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) {
Toast.makeText(context, R.string.failed_making_directory, Toast.LENGTH_LONG).show();
}
}
return youtubeDLDir;
}
@Override
public void onButtonClick(int position, String type) {
Log.e(TAG, type);
Video vid = resultObjects.get(position);
vid.setDownloadedType(type);
downloadQueue.add(vid);
if (downloading) {
Toast.makeText(context, R.string.added_to_queue, Toast.LENGTH_LONG).show();
return;
}
if (isStoragePermissionGranted()){
mainActivity.startDownloadService(vid.getTitle(), NotificationUtil.DOWNLOAD_NOTIFICATION_ID);
startDownload(downloadQueue);
}
}
public boolean isStoragePermissionGranted() {
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
}else{
downloadQueue = new LinkedList<>();
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
@Override
public void onCardClick(int position, boolean add) {
@ -709,8 +624,6 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
}
}
@Override
public void onClick(View v) {
String viewIdName;
@ -739,8 +652,8 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
return;
}
if(isStoragePermissionGranted()){
mainActivity.startDownloadService(downloadQueue.peek().getTitle(), NotificationUtil.DOWNLOAD_NOTIFICATION_ID);
startDownload(downloadQueue);
mainActivity.startDownloadService(downloadQueue, listener);
downloadQueue.clear();
}
}
}
@ -808,8 +721,9 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
return;
}
if(isStoragePermissionGranted()){
mainActivity.startDownloadService(downloadQueue.peek().getTitle(), NotificationUtil.DOWNLOAD_NOTIFICATION_ID);
startDownload(downloadQueue);
mainActivity.startDownloadService(downloadQueue, listener);
downloadQueue.clear();
}
}
}

@ -1,30 +1,13 @@
package com.deniscerri.ytdl.page;
package com.deniscerri.ytdlnis.page;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.deniscerri.ytdl.BuildConfig;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.util.UpdateUtil;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.yausername.youtubedl_android.YoutubeDL;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.atomic.AtomicReference;
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;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.util.UpdateUtil;
public class MoreFragment extends PreferenceFragmentCompat {
Preference updateYTDL;

@ -1,19 +1,13 @@
package com.deniscerri.ytdl.page.settings;
package com.deniscerri.ytdlnis.page.settings;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputType;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.FragmentManager;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdlnis.R;
import com.google.android.material.appbar.MaterialToolbar;

@ -1,11 +1,8 @@
package com.deniscerri.ytdl.page.settings;
package com.deniscerri.ytdlnis.page.settings;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
@ -14,25 +11,17 @@ import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreferenceCompat;
import com.deniscerri.ytdl.BuildConfig;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.util.UpdateUtil;
import com.yausername.youtubedl_android.YoutubeDL;
import java.nio.charset.StandardCharsets;
import com.deniscerri.ytdlnis.BuildConfig;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.util.UpdateUtil;
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;
import java.nio.charset.StandardCharsets;
public class SettingsFragment extends PreferenceFragmentCompat {

@ -0,0 +1,66 @@
package com.deniscerri.ytdlnis.service;
import com.deniscerri.ytdlnis.database.Video;
import java.util.LinkedList;
public class DownloadInfo {
private Video video;
private int progress;
private LinkedList<Video> downloadQueue;
private String outputLine;
private String downloadStatus;
private String downloadPath;
public DownloadInfo(){}
public Video getVideo() {
return video;
}
public void setVideo(Video video) {
this.video = video;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
public LinkedList<Video> getDownloadQueue() {
return downloadQueue;
}
public void setDownloadQueue(LinkedList<Video> downloadQueue) {
this.downloadQueue = downloadQueue;
this.video = downloadQueue.peek();
}
public String getOutputLine() {
return outputLine;
}
public void setOutputLine(String outputLine) {
this.outputLine = outputLine;
}
public String getDownloadStatus(){
return downloadStatus;
}
public void setDownloadStatus(String status){
downloadStatus = status;
}
public String getDownloadPath(){
return downloadPath;
}
public void setDownloadPath(String path){
this.downloadPath = path;
}
}

@ -0,0 +1,9 @@
package com.deniscerri.ytdlnis.service;
public interface IDownloaderListener {
void onDownloadStart(DownloadInfo downloadInfo);
void onDownloadProgress(DownloadInfo downloadInfo);
void onDownloadError(DownloadInfo downloadInfo);
void onDownloadEnd(DownloadInfo downloadInfo);
void onDownloadServiceEnd();
}

@ -0,0 +1,13 @@
package com.deniscerri.ytdlnis.service;
import android.app.Activity;
import com.deniscerri.ytdlnis.database.Video;
import java.util.ArrayList;
public interface IDownloaderService {
DownloadInfo getDownloadInfo();
void addActivity(Activity activity, IDownloaderListener callback);
void removeActivity(Activity activity);
void updateQueue(ArrayList<Video> queue);
void cancelDownload();
}

@ -1,17 +1,16 @@
package com.deniscerri.ytdl.util;
package com.deniscerri.ytdlnis.util;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdlnis.R;
public class NotificationUtil {
Context context;
@ -39,7 +38,7 @@ public class NotificationUtil {
//gui downloads
CharSequence name = context.getString(R.string.download_notification_channel_name);
String description = context.getString(R.string.download_notification_channel_description);
int importance = NotificationManager.IMPORTANCE_HIGH;
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
@ -61,8 +60,7 @@ public class NotificationUtil {
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), android.R.drawable.stat_sys_download))
.setContentText("")
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOnlyAlertOnce(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
@ -84,7 +82,4 @@ public class NotificationUtil {
notificationManager.notify(id, notificationBuilder.build());
}
public void cancelNotification(int id){
notificationManager.cancel(id);
}
}

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.util;
package com.deniscerri.ytdlnis.util;
import android.app.DownloadManager;
@ -9,9 +9,8 @@ import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import com.deniscerri.ytdl.App;
import com.deniscerri.ytdl.BuildConfig;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdlnis.BuildConfig;
import com.deniscerri.ytdlnis.R;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.yausername.youtubedl_android.YoutubeDL;
@ -59,16 +58,15 @@ public class UpdateUtil {
Log.e(TAG, e.toString());
}
if(res.get() == null){
Toast.makeText(context, R.string.error_checking_latest_version, Toast.LENGTH_SHORT).show();
}
String version = "";
String body = "";
try {
version = res.get().getString("tag_name");
body = res.get().getString("body");
} catch (JSONException ignored) {}
} catch (JSONException ignored) {
return false;
}
if(version.equals("v"+BuildConfig.VERSION_NAME)){
return false;

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:alpha="1.0"/>
</shape>

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdl.page.CustomCommandActivity">
tools:context="com.deniscerri.ytdlnis.page.CustomCommandActivity">
<FrameLayout
android:id="@+id/custom_command_frame_layout"

@ -19,7 +19,7 @@
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:labelVisibilityMode="labeled"
android:background="@drawable/transparent_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdl.page.settings.SettingsActivity">
tools:context="com.deniscerri.ytdlnis.page.settings.SettingsActivity">
<FrameLayout
android:id="@+id/settings_frame_layout"

@ -41,9 +41,9 @@
android:layout_width="match_parent"
android:layout_height="100dp"
android:paddingTop="10dp"
android:paddingRight="20dp"
android:paddingEnd="20dp"
android:paddingBottom="20dp"
android:paddingLeft="10dp"
android:paddingStart="10dp"
android:textSize="18sp"
android:textColor="@color/white"
android:textStyle="bold"
@ -58,9 +58,9 @@
android:layout_height="match_parent"
android:gravity="bottom"
android:paddingTop="10dp"
android:paddingRight="20dp"
android:paddingEnd="20dp"
android:paddingBottom="20dp"
android:paddingLeft="10dp"
android:paddingStart="10dp"
android:textSize="12sp"
android:textColor="@color/white"
android:textStyle="bold"
@ -73,30 +73,30 @@
android:id="@+id/download_button_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom|right"
android:gravity="bottom|end"
android:orientation="horizontal"
android:paddingBottom="16dp"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<com.google.android.material.button.MaterialButton
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:id="@+id/download_music"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="55dp"
android:layout_height="55dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_music"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
/>
<com.google.android.material.button.MaterialButton
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:id="@+id/download_video"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_width="55dp"
android:layout_height="55dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_video"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
/>
</LinearLayout>
@ -104,7 +104,7 @@
<ProgressBar
android:id="@+id/download_progress"
android:layout_width="match_parent"
android:layout_height="7dp"
android:layout_height="4dp"
android:layout_gravity="bottom"
android:backgroundTintMode="src_in"
android:progress="0"

@ -74,7 +74,7 @@
<string name="remove_non_music_summary">Hiq segmentet e SponsorBlock që përmbajnë pjesë pa muzikë në skedarët audio</string>
<string name="processing">Proçesimi</string>
<string name="embed_subs">Vendos Titrat në Video</string>
<string name="embed_subs_summary">Vendos titra brenda videos por vetëm për formatet mp4, webm dhe mkv</string>
<string name="embed_subs_summary">Vendos titra brenda videos</string>
<string name="embed_thumb">Vendos Thumbnail</string>
<string name="embed_thumb_summary">Vendos thumbnail si pamje e pare e videos</string>
<string name="add_chapters">Vendos Kapituj në Video</string>

@ -77,7 +77,7 @@
<string name="remove_non_music_summary">Remove SponsorBlock segments that contain non music parts for audio files</string>
<string name="processing">Processing</string>
<string name="embed_subs">Embed Subtitles on Videos</string>
<string name="embed_subs_summary">Embed subtitles in the video (only for mp4,webm and mkv videos)</string>
<string name="embed_subs_summary">Embed subtitles in the video</string>
<string name="embed_thumb">Embed Thumbnail</string>
<string name="embed_thumb_summary">Embed thumbnail as cover art</string>
<string name="add_chapters">Add Chapters on Videos</string>

@ -108,7 +108,7 @@
<SeekBarPreference
android:defaultValue="5"
android:defaultValue="0"
android:icon="@drawable/ic_music"
android:max="10"
app:key="audio_quality"

@ -1,4 +1,4 @@
package com.deniscerri.ytdl;
package com.deniscerri.ytdlnis;
import org.junit.Test;

@ -0,0 +1,10 @@
Download audio and video files on different formats through yt-dlp.
Download videos, playlists, multi-select items.
Really user friendly.
Download History.
Command Line Functionality.
Material You Compatible.
Foreground Download Service.
Share links directly to the app.
Built in search functionality.
Automatically checks for app updates.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Loading…
Cancel
Save