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
This commit is contained in:
parent
2e355562d3
commit
6473f452a1
44 changed files with 1127 additions and 670 deletions
17
.idea/deploymentTargetDropDown.xml
Normal file
17
.idea/deploymentTargetDropDown.xml
Normal file
|
|
@ -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" />
|
||||
|
|
|
|||
69
README.md
69
README.md
|
|
@ -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
|
||||
|
||||
|||
|
||||
|:-------------------------:|:-------------------------:|
|
||||
||
|
||||
||
|
||||
[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() {
|
||||
374
app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.java
Normal file
374
app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.java
Normal file
|
|
@ -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));
|
||||
}
|
||||
card.setChecked(false);
|
||||
card.setStrokeWidth(0);
|
||||
|
||||
if(checkedVideos.contains(position)){
|
||||
card.setChecked(true);
|
||||
card.setStrokeWidth(5);
|
||||
}else{
|
||||
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();
|
||||
@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;
|
||||
}
|
||||
|
||||
try {
|
||||
video = videos.peek();
|
||||
} catch (Exception e) {
|
||||
mainActivity.stopDownloadService();
|
||||
return;
|
||||
if (isStoragePermissionGranted()){
|
||||
mainActivity.startDownloadService(downloadQueue, listener);
|
||||
downloadQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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\"");
|
||||
public boolean isStoragePermissionGranted() {
|
||||
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}else{
|
||||
int concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1);
|
||||
request.addOption("-N", concurrentFragments);
|
||||
downloadQueue = new ArrayList<>();
|
||||
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
4
app/src/main/res/drawable/transparent_background.xml
Normal file
4
app/src/main/res/drawable/transparent_background.xml
Normal file
|
|
@ -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;
|
||||
|
||||
10
fastlane/metadata/android/en-US/full_description.txt
Normal file
10
fastlane/metadata/android/en-US/full_description.txt
Normal file
|
|
@ -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.
|
||||
BIN
fastlane/metadata/android/en-US/images/screenshots/1.png
Normal file
BIN
fastlane/metadata/android/en-US/images/screenshots/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
BIN
fastlane/metadata/android/en-US/images/screenshots/2.png
Normal file
BIN
fastlane/metadata/android/en-US/images/screenshots/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
fastlane/metadata/android/en-US/images/screenshots/3.png
Normal file
BIN
fastlane/metadata/android/en-US/images/screenshots/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
fastlane/metadata/android/en-US/images/screenshots/4.png
Normal file
BIN
fastlane/metadata/android/en-US/images/screenshots/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
fastlane/metadata/android/en-US/images/screenshots/5.png
Normal file
BIN
fastlane/metadata/android/en-US/images/screenshots/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
BIN
fastlane/metadata/android/en-US/images/screenshots/6.png
Normal file
BIN
fastlane/metadata/android/en-US/images/screenshots/6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Android YouTube Downloader app
|
||||
1
fastlane/metadata/android/en-US/title.txt
Normal file
1
fastlane/metadata/android/en-US/title.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
YTDLnis
|
||||
Loading…
Reference in a new issue