converted app, info/notification/update util, notification receiver to kotlin

pull/15/head
Denis Çerri 4 years ago
parent 604fd300c8
commit 49db13833f
No known key found for this signature in database
GPG Key ID: 96B3554AF5B193EE

@ -1,73 +0,0 @@
package com.deniscerri.ytdlnis;
import android.app.Application;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
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 io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.observers.DisposableCompletableObserver;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
public class App extends Application {
private static final String TAG = "App";
public static NotificationUtil notificationUtil;
@Override
public void onCreate() {
super.onCreate();
DynamicColors.applyToActivitiesIfAvailable(this);
notificationUtil = new NotificationUtil(this);
createNotificationChannels();
PreferenceManager.setDefaultValues(this, "root_preferences", this.MODE_PRIVATE, R.xml.root_preferences, false);
configureRxJavaErrorHandler();
Completable.fromAction(this::initLibraries).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableCompletableObserver() {
@Override
public void onComplete() {
// it worked
}
@Override
public void onError(Throwable e) {
if(BuildConfig.DEBUG) Log.e(TAG, "failed to initialize youtubedl-android", e);
Toast.makeText(getApplicationContext(), "initialization failed: " + e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void configureRxJavaErrorHandler() {
RxJavaPlugins.setErrorHandler(e -> {
if (e instanceof UndeliverableException) {
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
e = e.getCause();
}
if (e instanceof InterruptedException) {
// fine, some blocking code was interrupted by a dispose call
return;
}
Log.e(TAG, "Undeliverable exception received, not sure what to do", e);
});
}
private void initLibraries() throws YoutubeDLException {
YoutubeDL.getInstance().init(this);
FFmpeg.getInstance().init(this);
Aria2c.getInstance().init(this);
}
private void createNotificationChannels() {
notificationUtil.createNotificationChannel();
}
}

@ -0,0 +1,84 @@
package com.deniscerri.ytdlnis
import android.annotation.SuppressLint
import android.app.Application
import android.util.Log
import android.widget.Toast
import androidx.preference.PreferenceManager
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 io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.exceptions.UndeliverableException
import io.reactivex.observers.DisposableCompletableObserver
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
class App : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
notificationUtil = NotificationUtil(this)
createNotificationChannels()
PreferenceManager.setDefaultValues(
this,
"root_preferences",
MODE_PRIVATE,
R.xml.root_preferences,
false
)
configureRxJavaErrorHandler()
Completable.fromAction { initLibraries() }.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : DisposableCompletableObserver() {
override fun onComplete() {
// it worked
}
override fun onError(e: Throwable) {
if (BuildConfig.DEBUG) Log.e(TAG, "failed to initialize youtubedl-android", e)
Toast.makeText(
applicationContext,
"initialization failed: " + e.localizedMessage,
Toast.LENGTH_SHORT
).show()
}
})
}
private fun configureRxJavaErrorHandler() {
var err = Throwable()
RxJavaPlugins.setErrorHandler { e: Throwable ->
if (e is UndeliverableException) {
// As UndeliverableException is a wrapper, get the cause of it to get the "real" exception
err = e.cause!!
}
if (e is InterruptedException) {
// fine, some blocking code was interrupted by a dispose call
return@setErrorHandler
}
Log.e(TAG, "Undeliverable exception received, not sure what to do", err)
}
}
@Throws(YoutubeDLException::class)
private fun initLibraries() {
YoutubeDL.getInstance().init(this)
FFmpeg.getInstance().init(this)
Aria2c.getInstance().init(this)
}
private fun createNotificationChannels() {
notificationUtil.createNotificationChannel()
}
companion object {
private const val TAG = "App"
@SuppressLint("StaticFieldLeak")
lateinit var notificationUtil: NotificationUtil
}
}

@ -79,6 +79,7 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
setContentView(binding.root)
context = baseContext
askPermissions()
reconnectDownloadService()
checkUpdate()
fm = supportFragmentManager
@ -126,7 +127,6 @@ class MainActivity : AppCompatActivity() {
view.onApplyWindowInsets(windowInsets)
windowInsets
}
askPermissions()
val intent = intent
handleIntents(intent)
}

@ -192,12 +192,11 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
Video v = info.getVideo();
v = findVideo(v.getURL(), v.getDownloadedType());
try {
downloadsObjects.remove(v);
databaseManager = new DatabaseManager(context);
databaseManager.clearHistoryItem(v,false);
databaseManager.close();
downloadsRecyclerViewAdapter.notifyItemRemoved(downloadsObjects.indexOf(v));
downloadsObjects.remove(v);
if (downloadsObjects.isEmpty()) initCards();
downloading = false;
}catch (Exception ignored){}

@ -1,57 +0,0 @@
package com.deniscerri.ytdlnis.receiver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import com.deniscerri.ytdlnis.DownloaderService;
import com.deniscerri.ytdlnis.service.IDownloaderListener;
import com.deniscerri.ytdlnis.service.IDownloaderService;
import java.util.ArrayList;
public class NotificationReceiver extends BroadcastReceiver {
public DownloaderService downloaderService;
private ArrayList<IDownloaderListener> listeners = null;
private IDownloaderService iDownloaderService;
private Context context;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
downloaderService = ((DownloaderService.LocalBinder) service).getService();
iDownloaderService = (IDownloaderService) service;
cancelDownload();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
downloaderService = null;
iDownloaderService = null;
}
};
@Override
public void onReceive(Context c, Intent intent) {
context = c;
String message = intent.getStringExtra("cancel");
if (message != null){
Intent serviceIntent = new Intent(context.getApplicationContext(), DownloaderService.class);
serviceIntent.putExtra("rebind", true);
context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
}
private void cancelDownload(){
try {
iDownloaderService.cancelDownload(true);
context.getApplicationContext().unbindService(serviceConnection);
context.getApplicationContext().stopService(new Intent(context.getApplicationContext(), DownloaderService.class));
}catch (Exception ignored){}
}
}

@ -0,0 +1,53 @@
package com.deniscerri.ytdlnis.receiver
import android.content.*
import android.os.IBinder
import com.deniscerri.ytdlnis.DownloaderService
import com.deniscerri.ytdlnis.DownloaderService.LocalBinder
import com.deniscerri.ytdlnis.service.IDownloaderService
class NotificationReceiver : BroadcastReceiver() {
var downloaderService: DownloaderService? = null
private var iDownloaderService: IDownloaderService? = null
private var context: Context? = null
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
downloaderService = (service as LocalBinder).service
iDownloaderService = service
cancelDownload()
}
override fun onServiceDisconnected(componentName: ComponentName) {
downloaderService = null
iDownloaderService = null
}
}
override fun onReceive(c: Context, intent: Intent) {
context = c
val message = intent.getStringExtra("cancel")
if (message != null) {
val serviceIntent = Intent(context!!.applicationContext, DownloaderService::class.java)
serviceIntent.putExtra("rebind", true)
context!!.applicationContext.bindService(
serviceIntent,
serviceConnection,
Context.BIND_AUTO_CREATE
)
}
}
private fun cancelDownload() {
try {
iDownloaderService!!.cancelDownload(true)
context!!.applicationContext.unbindService(serviceConnection)
context!!.applicationContext.stopService(
Intent(
context!!.applicationContext,
DownloaderService::class.java
)
)
} catch (ignored: Exception) {
}
}
}

@ -1,609 +0,0 @@
package com.deniscerri.ytdlnis.util;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.Html;
import android.util.Log;
import com.deniscerri.ytdlnis.R;
import com.deniscerri.ytdlnis.database.DatabaseManager;
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 org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Locale;
public class InfoUtil {
private static final String TAG = "API MANAGER";
private static final String invidousURL = "https://invidious.baczek.me/api/v1/";
private static String countryCODE;
private ArrayList<Video> videos;
private String key;
private boolean useInvidous;
private DatabaseManager databaseManager;
public InfoUtil(Context context) {
try{
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
key = sharedPreferences.getString("api_key", "");
databaseManager = new DatabaseManager(context);
Thread thread = new Thread(() -> {
//get Locale
JSONObject country = genericRequest("https://ipwho.is/");
try{
countryCODE = country.getString("country_code");
}catch(Exception e){
countryCODE = "US";
}
JSONObject res = genericRequest(invidousURL + "stats");
if(res.length() == 0) useInvidous = false;
else useInvidous = true;
});
thread.start();
thread.join();
}catch(Exception e){
e.printStackTrace();
}
videos = new ArrayList<>();
}
public ArrayList<Video> search(String query) throws JSONException{
videos = new ArrayList<>();
if (key.isEmpty()) {
if(useInvidous) return searchFromInvidous(query);
else return getFromYTDL(query);
}
return searchFromKey(query);
}
public ArrayList<Video> searchFromKey(String query) throws JSONException{
//short data
JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q="+query+"&maxResults=25&regionCode="+countryCODE+"&key="+key);
if (!res.has("items")) return getFromYTDL(query);
JSONArray dataArray = res.getJSONArray("items");
//extra data
String url2 = "https://www.googleapis.com/youtube/v3/videos?id=";
//getting all ids, for the extra data request
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
JSONObject snippet = element.getJSONObject("snippet");
if(element.getJSONObject("id").getString("kind").equals("youtube#video")){
String videoID = element.getJSONObject("id").getString("videoId");
url2 = url2 + videoID + ",";
snippet.put("videoID", videoID);
}
}
url2 = url2.substring(0, url2.length()-1) + "&part=contentDetails&regionCode="+countryCODE+"&key="+key;
JSONObject extra = genericRequest(url2);
int j = 0;
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
JSONObject snippet = element.getJSONObject("snippet");
if(element.getJSONObject("id").getString("kind").equals("youtube#video")){
String duration = extra.getJSONArray("items").getJSONObject(j++).getJSONObject("contentDetails").getString("duration");
duration = formatDuration(duration);
if(duration.equals("0:00")){
continue;
}
snippet.put("duration", duration);
fixThumbnail(snippet);
Video v = createVideofromJSON(snippet);
if(v == null || v.getThumb().isEmpty()){
continue;
}
videos.add(createVideofromJSON(snippet));
}
}
return videos;
}
public ArrayList<Video> searchFromInvidous(String query) throws JSONException{
JSONObject res = genericRequest(invidousURL + "search?q="+query);
if(res.length() == 0) return getFromYTDL(query);
JSONArray dataArray = res.getJSONArray("");
int j = 0;
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
String duration = formatIntegerDuration(element.getInt("lengthSeconds"));
if(duration.equals("0:00")){
continue;
}
element.put("duration", duration);
element.put("thumb", element.getJSONArray("videoThumbnails").getJSONObject(0).getString("url"));
Video v = createVideofromInvidiousJSON(element);
if(v == null || v.getThumb().isEmpty()){
continue;
}
videos.add(v);
}
return videos;
}
public PlaylistTuple getPlaylist(String id, String nextPageToken) throws JSONException{
videos = new ArrayList<>();
if (key.isEmpty()) {
if (useInvidous) return getPlaylistFromInvidous(id);
else return new PlaylistTuple("", getFromYTDL("https://www.youtube.com/playlist?list="+id));
}
String url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken="+nextPageToken+"&maxResults=50&regionCode="+countryCODE+"&playlistId="+id+"&key="+key;
//short data
JSONObject res = genericRequest(url);
if (!res.has("items")) return new PlaylistTuple("", getFromYTDL("https://www.youtube.com/playlist?list="+id));
JSONArray dataArray = res.getJSONArray("items");
//extra data
String url2 = "https://www.googleapis.com/youtube/v3/videos?id=";
//getting all ids, for the extra data request
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
JSONObject snippet = element.getJSONObject("snippet");
String videoID = snippet.getJSONObject("resourceId").getString("videoId");
url2 = url2 + videoID + ",";
snippet.put("videoID", videoID);
}
url2 = url2.substring(0, url2.length()-1) + "&part=contentDetails&regionCode="+countryCODE+"&key="+key;
JSONObject extra = genericRequest(url2);
JSONArray extraArray = extra.getJSONArray("items");
int j = 0;
int i;
for(i = 0; i < extraArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
JSONObject snippet = element.getJSONObject("snippet");
String duration = extra.getJSONArray("items").getJSONObject(j).getJSONObject("contentDetails").getString("duration");
duration = formatDuration(duration);
snippet.put("duration", duration);
fixThumbnail(snippet);
Video v = createVideofromJSON(snippet);
if(v == null || v.getThumb().isEmpty()){
continue;
}else{
j++;
}
v.setIsPlaylistItem(1);
videos.add(v);
}
String next = res.optString("nextPageToken");
return new PlaylistTuple(next, videos);
}
public PlaylistTuple getPlaylistFromInvidous(String id){
String url = invidousURL + "playlists/"+id;
JSONObject res = genericRequest(url);
if (res.length() == 0) return new PlaylistTuple("", getFromYTDL("https://www.youtube.com/playlist?list="+id));
try{
JSONArray vids = res.getJSONArray("videos");
for(int i = 0; i < vids.length(); i++){
JSONObject element = vids.getJSONObject(i);
Video v = createVideofromInvidiousJSON(element);
if(v == null || v.getThumb().isEmpty()) continue;
v.setPlaylistTitle(res.getString("title"));
videos.add(v);
}
}catch(Exception e){
e.printStackTrace();
}
return new PlaylistTuple("", videos);
}
public Video getVideo(String id) throws JSONException {
if (key.isEmpty()) {
if (useInvidous){
JSONObject res = genericRequest(invidousURL + "videos/"+id);
if (res.length() == 0) return getFromYTDL("https://www.youtube.com/watch?v="+id).get(0);
return createVideofromInvidiousJSON(res);
}else{
return getFromYTDL("https://www.youtube.com/watch?v="+id).get(0);
}
}
//short data
JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id="+id+"&key="+key);
if (!res.has("items")) return getFromYTDL("https://www.youtube.com/watch?v="+id).get(0);
String duration = res.getJSONArray("items").getJSONObject(0).getJSONObject("contentDetails").getString("duration");
duration = formatDuration(duration);
res = res.getJSONArray("items").getJSONObject(0).getJSONObject("snippet");
res.put("videoID", id);
res.put("duration", duration);
fixThumbnail(res);
return createVideofromJSON(res);
}
private Video createVideofromJSON(JSONObject obj){
Video video = null;
try{
String id = obj.getString("videoID");
String title = obj.getString("title");
title = Html.fromHtml(title).toString();
String author = obj.getString("channelTitle");
author = Html.fromHtml(author).toString();
String duration = obj.getString("duration");
String thumb = obj.getString("thumb");
String url = "https://www.youtube.com/watch?v=" + id;
int downloadedAudio = databaseManager.checkDownloaded(url, "audio");
int downloadedVideo = databaseManager.checkDownloaded(url, "video");
int isPlaylist = 0;
video = new Video(id, url, title, author, duration, thumb, downloadedAudio, downloadedVideo, isPlaylist, "youtube", 0, 0, "");
}catch(Exception e){
Log.e(TAG, e.toString());
}
return video;
}
private Video createVideofromInvidiousJSON(JSONObject obj){
Video video = null;
try{
String id = obj.getString("videoId");
String title = obj.getString("title");
title = Html.fromHtml(title).toString();
String author = obj.getString("author");
author = Html.fromHtml(author).toString();
String duration = formatIntegerDuration(obj.getInt("lengthSeconds"));
String thumb = "https://i.ytimg.com/vi/"+id+"/hqdefault.jpg";
String url = "https://www.youtube.com/watch?v=" + id;
int downloadedAudio = databaseManager.checkDownloaded(url, "audio");
int downloadedVideo = databaseManager.checkDownloaded(url, "video");
int isPlaylist = 0;
video = new Video(id, url, title, author, duration, thumb, downloadedAudio, downloadedVideo, isPlaylist, "youtube", 0, 0, "");
}catch(Exception e){
Log.e(TAG, e.toString());
}
return video;
}
private JSONObject genericRequest(String url){
Log.e(TAG, url);
BufferedReader reader;
String line;
StringBuilder responseContent = new StringBuilder();
HttpURLConnection conn;
JSONObject json = new JSONObject();
try{
URL req = new URL(url);
conn = (HttpURLConnection) req.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000);
conn.setReadTimeout(5000);
if(conn.getResponseCode() < 300){
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while((line = reader.readLine()) != null){
responseContent.append(line);
}
reader.close();
json = new JSONObject(responseContent.toString());
if(json.has("error")){
throw new Exception();
}
}
conn.disconnect();
}catch(Exception e){
Log.e(TAG, e.toString());
}
return json;
}
private JSONArray genericArrayRequest(String url){
Log.e(TAG, url);
BufferedReader reader;
String line;
StringBuilder responseContent = new StringBuilder();
HttpURLConnection conn;
JSONArray json = new JSONArray();
try{
URL req = new URL(url);
conn = (HttpURLConnection) req.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000);
conn.setReadTimeout(5000);
if(conn.getResponseCode() < 300){
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while((line = reader.readLine()) != null){
responseContent.append(line);
}
reader.close();
json = new JSONArray(responseContent.toString());
}
conn.disconnect();
}catch(Exception e){
Log.e(TAG, e.toString());
}
return json;
}
private JSONObject fixThumbnail(JSONObject o){
String imageURL = "";
try{
JSONObject thumbs = o.getJSONObject("thumbnails");
imageURL = thumbs.getJSONObject("maxres").getString("url");
}catch(Exception e){
try {
JSONObject thumbs = o.getJSONObject("thumbnails");
imageURL = thumbs.getJSONObject("high").getString("url");
}catch(Exception u){
try{
JSONObject thumbs = o.getJSONObject("thumbnails");
imageURL = thumbs.getJSONObject("default").getString("url");
}catch(Exception ignored){}
}
}
try{
o.put("thumb", imageURL);
}catch(Exception ignored){}
return o;
}
public ArrayList<Video> getTrending(Context context) throws JSONException{
videos = new ArrayList<>();
if (key.isEmpty()) {
if (useInvidous) return getTrendingFromInvidous(context);
else return new ArrayList<>();
}
return getTrendingFromKey(context);
}
public ArrayList<Video> getTrendingFromKey(Context context) throws JSONException{
String url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10&regionCode="+countryCODE+"&maxResults=25&key="+key;
//short data
JSONObject res = genericRequest(url);
//extra data from the same videos
JSONObject contentDetails = genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10&regionCode="+countryCODE+"&maxResults=25&key="+key);
if(!contentDetails.has("items")) return new ArrayList<>();
JSONArray dataArray = res.getJSONArray("items");
JSONArray extraDataArray = contentDetails.getJSONArray("items");
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
JSONObject snippet = element.getJSONObject("snippet");
String duration = extraDataArray.getJSONObject(i).getJSONObject("contentDetails").getString("duration");
duration = formatDuration(duration);
snippet.put("videoID", element.getString("id"));
snippet.put("duration", duration);
fixThumbnail(snippet);
Video v = createVideofromJSON(snippet);
if(v == null || v.getThumb().isEmpty()){
continue;
}
v.setPlaylistTitle(context.getString(R.string.trendingPlaylist));
videos.add(v);
}
return videos;
}
public ArrayList<Video> getTrendingFromInvidous(Context context) {
String url = invidousURL + "trending?type=music&region="+countryCODE;
JSONArray res = genericArrayRequest(url);
try{
for(int i = 0; i < res.length(); i++){
JSONObject element = res.getJSONObject(i);
if(!element.getString("type").equals("video")) continue;
Video v = createVideofromInvidiousJSON(element);
if(v == null || v.getThumb().isEmpty()) continue;
v.setPlaylistTitle(context.getString(R.string.trendingPlaylist));
videos.add(v);
}
}catch(Exception e){
e.printStackTrace();
}
return videos;
}
public ArrayList<Video> getFromYTDL(String query){
videos = new ArrayList<>();
try {
YoutubeDLRequest request = new YoutubeDLRequest(query);
request.addOption("--flat-playlist");
request.addOption("-j");
request.addOption("--skip-download");
request.addOption("-R", "1");
request.addOption("--socket-timeout", "5");
if (!query.contains("http")) request.addOption("--default-search", "ytsearch25");
YoutubeDLResponse youtubeDLResponse = YoutubeDL.getInstance().execute(request);
String[] results;
try {
results = youtubeDLResponse.getOut().split(System.getProperty("line.separator"));
}catch(Exception e){
results = new String[]{youtubeDLResponse.getOut()};
}
int isPlaylist = 0;
JSONObject pl = new JSONObject(results[0]);
if (pl.has("playlist")){
if (! pl.getString("playlist").equals(query)) isPlaylist = 1;
}
for (String result : results) {
JSONObject jsonObject = new JSONObject(result);
String title = "";
if (jsonObject.has("title")){
if (jsonObject.getString("title").equals("[Private video]")) continue;
title = jsonObject.getString("title");
}else{
title = jsonObject.getString("webpage_url_basename");
}
String author = "";
if (jsonObject.has("uploader")){
author = jsonObject.getString("uploader");
}
String duration = "";
if (jsonObject.has("duration")){
duration = formatIntegerDuration(jsonObject.getInt("duration"));
}
String url = jsonObject.getString("webpage_url");
String thumb = "";
if (jsonObject.has("thumbnail")){
thumb = jsonObject.getString("thumbnail");
}else if (jsonObject.has("thumbnails")){
JSONArray thumbs = jsonObject.getJSONArray("thumbnails");
thumb = thumbs.getJSONObject(thumbs.length()-1).getString("url");
}
String website = "";
if (jsonObject.has("ie_key")) website = jsonObject.getString("ie_key");
else website = jsonObject.getString("extractor");
String playlistTitle = "";
if (jsonObject.has("playlist")) playlistTitle = jsonObject.getString("playlist");
videos.add(new Video(
jsonObject.getString("id"),
url,
title,
author,
duration,
thumb,
databaseManager.checkDownloaded(url, "audio"),
databaseManager.checkDownloaded(url, "video"),
isPlaylist,
website,
0,
0,
playlistTitle)
);
}
}catch(Exception e){
e.printStackTrace();
}
return videos;
}
public ArrayList<String> getSearchSuggestions(String query){
if (!useInvidous) return new ArrayList<>();
String url = invidousURL + "search/suggestions?q="+query;
JSONObject res = genericRequest(url);
if(res.length() == 0) return new ArrayList<>();
ArrayList<String> suggestionList = new ArrayList<>();
try{
JSONArray suggestions = res.getJSONArray("suggestions");
for (int i = 0; i < suggestions.length(); i++){
suggestionList.add(suggestions.getString(i));
}
}catch(Exception ignored){}
return suggestionList;
}
public String formatDuration(String dur){
if(dur.equals("P0D")){
return "LIVE";
}
boolean hours = false;
String duration = "";
dur = dur.substring(2);
if (dur.contains("H")) {
hours = true;
duration += String.format(Locale.getDefault(), "%02d", Integer.parseInt(dur.substring(0, dur.indexOf("H")))) + ":";
dur = dur.substring(dur.indexOf("H")+1);
}
if (dur.contains("M")) {
duration += String.format(Locale.getDefault(), "%02d", Integer.parseInt(dur.substring(0, dur.indexOf("M")))) + ":";
dur = dur.substring(dur.indexOf("M")+1);
}else if(hours) duration += "00:";
if (dur.contains("S")){
if(duration.isEmpty()) duration = "00:";
duration += String.format(Locale.getDefault(), "%02d", Integer.parseInt(dur.substring(0, dur.indexOf("S"))));
}else{
duration += "00";
}
if(duration.equals("00:00")){
duration = "";
}
return duration;
}
public String formatIntegerDuration(int dur){
String format = String.format(Locale.getDefault(), "%02d:%02d:%02d", (dur/3600), ((dur % 3600)/60), (dur % 60));
// 00:00:00
if (dur < 600) format = format.substring(4);
else if (dur < 3600) format = format.substring(3);
else if (dur < 36000) format = format.substring(1);
return format;
}
public static class PlaylistTuple {
String nextPageToken;
ArrayList<Video> videos;
PlaylistTuple(String token, ArrayList<Video> videos){
nextPageToken = token;
this.videos = videos;
}
public String getNextPageToken() {
return nextPageToken;
}
public ArrayList<Video> getVideos() {
return videos;
}
}
}

@ -0,0 +1,603 @@
package com.deniscerri.ytdlnis.util
import android.app.Activity
import android.content.Context
import android.util.Log
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DatabaseManager
import com.deniscerri.ytdlnis.database.Video
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
class InfoUtil(context: Context) {
private var videos: ArrayList<Video?>
private var key: String? = null
private var useInvidous = false
private var databaseManager: DatabaseManager? = null
init {
try {
val sharedPreferences =
context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
key = sharedPreferences.getString("api_key", "")
databaseManager = DatabaseManager(context)
val thread = Thread {
//get Locale
val country = genericRequest("https://ipwho.is/")
countryCODE = try {
country.getString("country_code")
} catch (e: Exception) {
"US"
}
val res = genericRequest(invidousURL + "stats")
useInvidous = res.length() != 0
}
thread.start()
thread.join()
} catch (e: Exception) {
e.printStackTrace()
}
videos = ArrayList()
}
@Throws(JSONException::class)
fun search(query: String): ArrayList<Video?> {
videos = ArrayList()
return if (key!!.isEmpty()) {
if (useInvidous) searchFromInvidous(query) else getFromYTDL(query)
} else searchFromKey(query)
}
@Throws(JSONException::class)
fun searchFromKey(query: String): ArrayList<Video?> {
//short data
val res =
genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q=$query&maxResults=25&regionCode=$countryCODE&key=$key")
if (!res.has("items")) return getFromYTDL(query)
val dataArray = res.getJSONArray("items")
//extra data
var url2 = "https://www.googleapis.com/youtube/v3/videos?id="
//getting all ids, for the extra data request
for (i in 0 until dataArray.length()) {
val element = dataArray.getJSONObject(i)
val snippet = element.getJSONObject("snippet")
if (element.getJSONObject("id").getString("kind") == "youtube#video") {
val videoID = element.getJSONObject("id").getString("videoId")
url2 = "$url2$videoID,"
snippet.put("videoID", videoID)
}
}
url2 = url2.substring(
0,
url2.length - 1
) + "&part=contentDetails&regionCode=" + countryCODE + "&key=" + key
val extra = genericRequest(url2)
var j = 0
for (i in 0 until dataArray.length()) {
val element = dataArray.getJSONObject(i)
val snippet = element.getJSONObject("snippet")
if (element.getJSONObject("id").getString("kind") == "youtube#video") {
var duration =
extra.getJSONArray("items").getJSONObject(j++).getJSONObject("contentDetails")
.getString("duration")
duration = formatDuration(duration)
if (duration == "0:00") {
continue
}
snippet.put("duration", duration)
fixThumbnail(snippet)
val v = createVideofromJSON(snippet)
if (v == null || v.thumb.isEmpty()) {
continue
}
videos.add(createVideofromJSON(snippet))
}
}
return videos
}
@Throws(JSONException::class)
fun searchFromInvidous(query: String): ArrayList<Video?> {
val dataArray = genericArrayRequest(invidousURL + "search?q=" + query + "?type=video")
if (dataArray.length() == 0) return getFromYTDL(query)
for (i in 0 until dataArray.length()) {
val element = dataArray.getJSONObject(i)
if (!element.getString("type").equals("video")) continue
val duration = formatIntegerDuration(element.getInt("lengthSeconds"))
if (duration == "0:00") {
continue
}
element.put("duration", duration)
element.put(
"thumb",
element.getJSONArray("videoThumbnails").getJSONObject(0).getString("url")
)
val v = createVideofromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) {
continue
}
videos.add(v)
}
return videos
}
@Throws(JSONException::class)
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
videos = ArrayList()
if (key!!.isEmpty()) {
return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
"",
getFromYTDL("https://www.youtube.com/playlist?list=$id")
)
}
val url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken=$nextPageToken&maxResults=50&regionCode=$countryCODE&playlistId=$id&key=$key"
//short data
val res = genericRequest(url)
if (!res.has("items")) return PlaylistTuple(
"",
getFromYTDL("https://www.youtube.com/playlist?list=$id")
)
val dataArray = res.getJSONArray("items")
//extra data
var url2 = "https://www.googleapis.com/youtube/v3/videos?id="
//getting all ids, for the extra data request
for (i in 0 until dataArray.length()) {
val element = dataArray.getJSONObject(i)
val snippet = element.getJSONObject("snippet")
val videoID = snippet.getJSONObject("resourceId").getString("videoId")
url2 = "$url2$videoID,"
snippet.put("videoID", videoID)
}
url2 = url2.substring(
0,
url2.length - 1
) + "&part=contentDetails&regionCode=" + countryCODE + "&key=" + key
val extra = genericRequest(url2)
val extraArray = extra.getJSONArray("items")
var j = 0
var i = 0
while (i < extraArray.length()) {
val element = dataArray.getJSONObject(i)
val snippet = element.getJSONObject("snippet")
var duration =
extra.getJSONArray("items").getJSONObject(j).getJSONObject("contentDetails")
.getString("duration")
duration = formatDuration(duration)
snippet.put("duration", duration)
fixThumbnail(snippet)
val v = createVideofromJSON(snippet)
if (v == null || v.thumb.isEmpty()) {
i++
continue
} else {
j++
}
v.isPlaylistItem = 1
videos.add(v)
i++
}
val next = res.optString("nextPageToken")
return PlaylistTuple(next, videos)
}
private fun getPlaylistFromInvidous(id: String): PlaylistTuple {
val url = invidousURL + "playlists/" + id
val res = genericRequest(url)
if (res.length() == 0) return PlaylistTuple(
"",
getFromYTDL("https://www.youtube.com/playlist?list=$id")
)
try {
val vids = res.getJSONArray("videos")
for (i in 0 until vids.length()) {
val element = vids.getJSONObject(i)
val v = createVideofromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) continue
v.playlistTitle = res.getString("title")
v.isPlaylistItem = 1
videos.add(v)
}
} catch (e: Exception) {
e.printStackTrace()
}
return PlaylistTuple("", videos)
}
@Throws(JSONException::class)
fun getVideo(id: String): Video? {
if (key!!.isEmpty()) {
return if (useInvidous) {
val res = genericRequest(invidousURL + "videos/" + id)
if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideofromInvidiousJSON(
res
)
} else {
getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
}
}
//short data
var res =
genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=$id&key=$key")
if (!res.has("items")) return getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
var duration = res.getJSONArray("items").getJSONObject(0).getJSONObject("contentDetails")
.getString("duration")
duration = formatDuration(duration)
res = res.getJSONArray("items").getJSONObject(0).getJSONObject("snippet")
res.put("videoID", id)
res.put("duration", duration)
fixThumbnail(res)
return createVideofromJSON(res)
}
private fun createVideofromJSON(obj: JSONObject): Video? {
var video: Video? = null
try {
val id = obj.getString("videoID")
val title = obj.getString("title").toString()
val author = obj.getString("channelTitle").toString()
val duration = obj.getString("duration")
val thumb = obj.getString("thumb")
val url = "https://www.youtube.com/watch?v=$id"
val downloadedAudio = databaseManager!!.checkDownloaded(url, "audio")
val downloadedVideo = databaseManager!!.checkDownloaded(url, "video")
val isPlaylist = 0
video = Video(
id,
url,
title,
author,
duration,
thumb,
downloadedAudio,
downloadedVideo,
isPlaylist,
"youtube",
0,
0,
""
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
return video
}
private fun createVideofromInvidiousJSON(obj: JSONObject): Video? {
var video: Video? = null
try {
val id = obj.getString("videoId")
val title = obj.getString("title").toString()
val author = obj.getString("author").toString()
val duration = formatIntegerDuration(obj.getInt("lengthSeconds"))
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
val url = "https://www.youtube.com/watch?v=$id"
val downloadedAudio = databaseManager!!.checkDownloaded(url, "audio")
val downloadedVideo = databaseManager!!.checkDownloaded(url, "video")
val isPlaylist = 0
video = Video(
id,
url,
title,
author,
duration,
thumb,
downloadedAudio,
downloadedVideo,
isPlaylist,
"youtube",
0,
0,
""
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
return video
}
private fun genericRequest(url: String): JSONObject {
Log.e(TAG, url)
val reader: BufferedReader
var line: String?
val responseContent = StringBuilder()
val conn: HttpURLConnection
var json = JSONObject()
try {
val req = URL(url)
conn = req.openConnection() as HttpURLConnection
conn.requestMethod = "GET"
conn.connectTimeout = 10000
conn.readTimeout = 5000
if (conn.responseCode < 300) {
reader = BufferedReader(InputStreamReader(conn.inputStream))
while (reader.readLine().also { line = it } != null) {
responseContent.append(line)
}
reader.close()
json = JSONObject(responseContent.toString())
if (json.has("error")) {
throw Exception()
}
}
conn.disconnect()
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
return json
}
private fun genericArrayRequest(url: String): JSONArray {
Log.e(TAG, url)
val reader: BufferedReader
var line: String?
val responseContent = StringBuilder()
val conn: HttpURLConnection
var json = JSONArray()
try {
val req = URL(url)
conn = req.openConnection() as HttpURLConnection
conn.requestMethod = "GET"
conn.connectTimeout = 10000
conn.readTimeout = 5000
if (conn.responseCode < 300) {
reader = BufferedReader(InputStreamReader(conn.inputStream))
while (reader.readLine().also { line = it } != null) {
responseContent.append(line)
}
reader.close()
json = JSONArray(responseContent.toString())
}
conn.disconnect()
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
return json
}
private fun fixThumbnail(o: JSONObject): JSONObject {
var imageURL = ""
try {
val thumbs = o.getJSONObject("thumbnails")
imageURL = thumbs.getJSONObject("maxres").getString("url")
} catch (e: Exception) {
try {
val thumbs = o.getJSONObject("thumbnails")
imageURL = thumbs.getJSONObject("high").getString("url")
} catch (u: Exception) {
try {
val thumbs = o.getJSONObject("thumbnails")
imageURL = thumbs.getJSONObject("default").getString("url")
} catch (ignored: Exception) {
}
}
}
try {
o.put("thumb", imageURL)
} catch (ignored: Exception) {
}
return o
}
@Throws(JSONException::class)
fun getTrending(context: Context): ArrayList<Video?> {
videos = ArrayList()
return if (key!!.isEmpty()) {
if (useInvidous) getTrendingFromInvidous(context) else ArrayList()
} else getTrendingFromKey(context)
}
@Throws(JSONException::class)
fun getTrendingFromKey(context: Context): ArrayList<Video?> {
val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10&regionCode=$countryCODE&maxResults=25&key=$key"
//short data
val res = genericRequest(url)
//extra data from the same videos
val contentDetails =
genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10&regionCode=$countryCODE&maxResults=25&key=$key")
if (!contentDetails.has("items")) return ArrayList()
val dataArray = res.getJSONArray("items")
val extraDataArray = contentDetails.getJSONArray("items")
for (i in 0 until dataArray.length()) {
val element = dataArray.getJSONObject(i)
val snippet = element.getJSONObject("snippet")
var duration = extraDataArray.getJSONObject(i).getJSONObject("contentDetails")
.getString("duration")
duration = formatDuration(duration)
snippet.put("videoID", element.getString("id"))
snippet.put("duration", duration)
fixThumbnail(snippet)
val v = createVideofromJSON(snippet)
if (v == null || v.thumb.isEmpty()) {
continue
}
v.playlistTitle = context.getString(R.string.trendingPlaylist)
videos.add(v)
}
return videos
}
private fun getTrendingFromInvidous(context: Context): ArrayList<Video?> {
val url = invidousURL + "trending?type=music&region=" + countryCODE
val res = genericArrayRequest(url)
try {
for (i in 0 until res.length()) {
val element = res.getJSONObject(i)
if (element.getString("type") != "video") continue
val v = createVideofromInvidiousJSON(element)
if (v == null || v.thumb.isEmpty()) continue
v.playlistTitle = context.getString(R.string.trendingPlaylist)
videos.add(v)
}
} catch (e: Exception) {
e.printStackTrace()
}
return videos
}
fun getFromYTDL(query: String): ArrayList<Video?> {
videos = ArrayList()
try {
val request = YoutubeDLRequest(query)
request.addOption("--flat-playlist")
request.addOption("-j")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
if (!query.contains("http")) request.addOption("--default-search", "ytsearch25")
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
youtubeDLResponse.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(youtubeDLResponse.out)
}
var isPlaylist = 0
val pl = JSONObject(results[0]!!)
if (pl.has("playlist")) {
if (pl.getString("playlist") != query) isPlaylist = 1
}
for (result in results) {
val jsonObject = JSONObject(result!!)
val title = if (jsonObject.has("title")) {
if (jsonObject.getString("title") == "[Private video]") continue
jsonObject.getString("title")
} else {
jsonObject.getString("webpage_url_basename")
}
var author: String? = ""
if (jsonObject.has("uploader")) {
author = jsonObject.getString("uploader")
}
var duration = ""
if (jsonObject.has("duration")) {
duration = formatIntegerDuration(jsonObject.getInt("duration"))
}
val url = jsonObject.getString("webpage_url")
var thumb: String? = ""
if (jsonObject.has("thumbnail")) {
thumb = jsonObject.getString("thumbnail")
} else if (jsonObject.has("thumbnails")) {
val thumbs = jsonObject.getJSONArray("thumbnails")
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
}
val website = if (jsonObject.has("ie_key")) jsonObject.getString("ie_key") else jsonObject.getString("extractor")
var playlistTitle: String? = ""
if (jsonObject.has("playlist")) playlistTitle = jsonObject.getString("playlist")
videos.add(
Video(
jsonObject.getString("id"),
url,
title,
author,
duration,
thumb,
databaseManager!!.checkDownloaded(url, "audio"),
databaseManager!!.checkDownloaded(url, "video"),
isPlaylist,
website,
0,
0,
playlistTitle
)
)
}
} catch (e: Exception) {
e.printStackTrace()
}
return videos
}
fun getSearchSuggestions(query: String): ArrayList<String> {
if (!useInvidous) return ArrayList()
val url = invidousURL + "search/suggestions?q=" + query
val res = genericRequest(url)
if (res.length() == 0) return ArrayList()
val suggestionList = ArrayList<String>()
try {
val suggestions = res.getJSONArray("suggestions")
for (i in 0 until suggestions.length()) {
suggestionList.add(suggestions.getString(i))
}
} catch (ignored: Exception) {
}
return suggestionList
}
private fun formatDuration(dur: String): String {
var badDur = dur
if (dur == "P0D") {
return "LIVE"
}
var hours = false
var duration = ""
badDur = badDur.substring(2)
if (badDur.contains("H")) {
hours = true
duration += String.format(
Locale.getDefault(),
"%02d",
badDur.substring(0, badDur.indexOf("H")).toInt()
) + ":"
badDur = badDur.substring(badDur.indexOf("H") + 1)
}
if (badDur.contains("M")) {
duration += String.format(
Locale.getDefault(),
"%02d",
badDur.substring(0, badDur.indexOf("M")).toInt()
) + ":"
badDur = badDur.substring(badDur.indexOf("M") + 1)
} else if (hours) duration += "00:"
if (badDur.contains("S")) {
if (duration.isEmpty()) duration = "00:"
duration += String.format(
Locale.getDefault(),
"%02d",
badDur.substring(0, badDur.indexOf("S")).toInt()
)
} else {
duration += "00"
}
if (duration == "00:00") {
duration = ""
}
return duration
}
private fun formatIntegerDuration(dur: Int): String {
var format = String.format(
Locale.getDefault(),
"%02d:%02d:%02d",
dur / 3600,
dur % 3600 / 60,
dur % 60
)
// 00:00:00
if (dur < 600) format = format.substring(4) else if (dur < 3600) format =
format.substring(3) else if (dur < 36000) format = format.substring(1)
return format
}
class PlaylistTuple internal constructor(
var nextPageToken: String,
var videos: ArrayList<Video?>
)
companion object {
private const val TAG = "API MANAGER"
private const val invidousURL = "https://invidious.baczek.me/api/v1/"
private var countryCODE: String? = null
}
}

@ -1,104 +0,0 @@
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.ytdlnis.R;
import com.deniscerri.ytdlnis.receiver.NotificationReceiver;
public class NotificationUtil {
Context context;
public static final String DOWNLOAD_SERVICE_CHANNEL_ID = "1";
public static final int DOWNLOAD_NOTIFICATION_ID = 1;
public static final String COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2";
public static final int COMMAND_DOWNLOAD_NOTIFICATION_ID = 2;
public static NotificationCompat.Builder notificationBuilder;
private static int PROGRESS_MAX = 100;
private static int PROGRESS_CURR = 0;
private NotificationManager notificationManager;
public NotificationUtil(Context context){
this.context = context;
notificationBuilder = new NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID);
notificationManager = context.getSystemService(NotificationManager.class);
}
public void createNotificationChannel(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
//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_LOW;
NotificationChannel channel = new NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
//command downloads
name = context.getString(R.string.command_download_notification_channel_name);
description = context.getString(R.string.command_download_notification_channel_description);
channel = new NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
}
}
public Notification createDownloadServiceNotification(PendingIntent pendingIntent, String title){
Intent intent = new Intent(context, NotificationReceiver.class);
intent.putExtra("cancel", "");
PendingIntent cancelNotificationPendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
);
Notification notification = notificationBuilder
.setContentTitle(title)
.setOngoing(true)
.setCategory(Notification.CATEGORY_PROGRESS)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), android.R.drawable.stat_sys_download))
.setContentText("")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
.setContentIntent(pendingIntent)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.clearActions()
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
.build();
return notification;
}
public void updateDownloadNotification(int id, String desc, int progress, int queue, String title){
String contentText = "";
if (queue > 1) contentText += queue - 1 + " " + context.getString(R.string.items_left) + "\n";
contentText += desc.replaceAll("\\[.*?\\] ", "");
try {
notificationBuilder.setProgress(100, (int) progress, false)
.setContentTitle(title)
.setStyle(new NotificationCompat.BigTextStyle().bigText(contentText));
notificationManager.notify(id, notificationBuilder.build());
}catch (Exception ignored){}
}
public void cancelDownloadNotification(int id){
notificationManager.cancel(id);
}
}

@ -0,0 +1,109 @@
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.ytdlnis.R
import com.deniscerri.ytdlnis.receiver.NotificationReceiver
class NotificationUtil(var context: Context) {
private var notificationBuilder: NotificationCompat.Builder =
NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID)
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService(
NotificationManager::class.java
)
//gui downloads
var name: CharSequence = context.getString(R.string.download_notification_channel_name)
var description = context.getString(R.string.download_notification_channel_description)
val importance = NotificationManager.IMPORTANCE_LOW
var channel = NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
channel.description = description
notificationManager.createNotificationChannel(channel)
//command downloads
name = context.getString(R.string.command_download_notification_channel_name)
description =
context.getString(R.string.command_download_notification_channel_description)
channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
channel.description = description
notificationManager.createNotificationChannel(channel)
}
}
fun createDownloadServiceNotification(
pendingIntent: PendingIntent?,
title: String?
): Notification {
val intent = Intent(context, NotificationReceiver::class.java)
intent.putExtra("cancel", "")
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
return notificationBuilder
.setContentTitle(title)
.setOngoing(true)
.setCategory(Notification.CATEGORY_PROGRESS)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
android.R.drawable.stat_sys_download
)
)
.setContentText("")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
.setContentIntent(pendingIntent)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.clearActions()
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
.build()
}
fun updateDownloadNotification(
id: Int,
desc: String,
progress: Int,
queue: Int,
title: String?
) {
var contentText = ""
if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}"""
contentText += desc.replace("\\[.*?\\] ".toRegex(), "")
try {
notificationBuilder.setProgress(100, progress, false)
.setContentTitle(title)
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
notificationManager.notify(id, notificationBuilder.build())
} catch (ignored: Exception) {
}
}
fun cancelDownloadNotification(id: Int) {
notificationManager.cancel(id)
}
companion object {
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
const val DOWNLOAD_NOTIFICATION_ID = 1
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
const val COMMAND_DOWNLOAD_NOTIFICATION_ID = 2
private const val PROGRESS_MAX = 100
private const val PROGRESS_CURR = 0
}
}

@ -1,196 +0,0 @@
package com.deniscerri.ytdlnis.util;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
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;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
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;
public class UpdateUtil {
Context context;
private final String TAG = "UpdateUtil";
private DownloadManager downloadManager;
public static boolean updatingYTDL;
public static boolean updatingApp;
MaterialCardView materialCardView;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public UpdateUtil(Context context){
this.context = context;
downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
}
public boolean updateApp(){
if (updatingApp) {
Toast.makeText(context, context.getString(R.string.ytdl_already_updating), Toast.LENGTH_LONG).show();
return true;
}
AtomicReference<JSONObject> res = new AtomicReference<>(new JSONObject());
try{
Thread thread = new Thread(() -> res.set(checkForAppUpdate()));
thread.start();
thread.join();
}catch(Exception e){
Log.e(TAG, e.toString());
}
String version = "";
String body = "";
try {
version = res.get().getString("tag_name");
body = res.get().getString("body");
} catch (JSONException ignored) {
return false;
}
if(version.equals("v"+BuildConfig.VERSION_NAME)){
return false;
}
MaterialAlertDialogBuilder updateDialog = new MaterialAlertDialogBuilder(context)
.setTitle(version)
.setMessage(body)
.setIcon(R.drawable.ic_update_app)
.setNegativeButton("Cancel", (dialogInterface, i) -> {})
.setPositiveButton("Update", (dialogInterface, i) -> startAppUpdate(res.get()));
updateDialog.show();
return true;
}
public JSONObject checkForAppUpdate(){
String url = "https://api.github.com/repos/deniscerri/ytdlnis/releases/latest";
BufferedReader reader;
String line;
StringBuilder responseContent = new StringBuilder();
HttpURLConnection conn;
JSONObject json = new JSONObject();
try{
URL req = new URL(url);
conn = (HttpURLConnection) req.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000);
conn.setReadTimeout(5000);
if(conn.getResponseCode() < 300){
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while((line = reader.readLine()) != null){
responseContent.append(line);
}
reader.close();
json = new JSONObject(responseContent.toString());
if(json.has("error")){
throw new Exception();
}
}
conn.disconnect();
}catch(Exception e){
Log.e(TAG, e.toString());
}
return json;
}
private void startAppUpdate(JSONObject updateInfo){
try{
JSONArray versions = updateInfo.getJSONArray("assets");
String url = "";
String app_name = "";
for (int i = 0; i < versions.length(); i++){
JSONObject tmp = versions.getJSONObject(i);
if(tmp.getString("name").contains(Build.SUPPORTED_ABIS[0])){
url = tmp.getString("browser_download_url");
app_name = tmp.getString("name");
break;
}
}
if(url.isEmpty()){
Toast.makeText(context, R.string.couldnt_find_apk, Toast.LENGTH_SHORT).show();
return;
}
Uri uri = Uri.parse(url);
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.mkdirs();
downloadManager.enqueue(new DownloadManager.Request(uri)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |
DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, app_name));
}catch(Exception ignored){}
}
public void updateYoutubeDL() {
if (updatingYTDL) {
Toast.makeText(context, context.getString(R.string.ytdl_already_updating), Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(context, context.getString(R.string.ytdl_updating_started), Toast.LENGTH_SHORT).show();
updatingYTDL = true;
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().updateYoutubeDL(context))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(status -> {
switch (status) {
case DONE:
Toast.makeText(context, context.getString(R.string.ytld_update_success), Toast.LENGTH_LONG).show();
break;
case ALREADY_UP_TO_DATE:
Toast.makeText(context, context.getString(R.string.you_are_in_latest_version), Toast.LENGTH_LONG).show();
break;
default:
Toast.makeText(context, status.toString(), Toast.LENGTH_LONG).show();
break;
}
updatingYTDL = false;
}, e -> {
if(BuildConfig.DEBUG) Log.e(TAG, context.getString(R.string.ytdl_update_failed), e);
Toast.makeText(context, context.getString(R.string.ytdl_update_failed), Toast.LENGTH_LONG).show();
updatingYTDL = false;
});
compositeDisposable.add(disposable);
}
}

@ -0,0 +1,195 @@
package com.deniscerri.ytdlnis.util
import android.app.DownloadManager
import android.content.Context
import android.content.DialogInterface
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import android.widget.Toast
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDL.UpdateStatus
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.atomic.AtomicReference
class UpdateUtil(var context: Context) {
private val tag = "UpdateUtil"
private val downloadManager: DownloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
private val compositeDisposable = CompositeDisposable()
fun updateApp(): Boolean {
if (updatingApp) {
Toast.makeText(
context,
context.getString(R.string.ytdl_already_updating),
Toast.LENGTH_LONG
).show()
return true
}
val res = AtomicReference(JSONObject())
try {
val thread = Thread { res.set(checkForAppUpdate()) }
thread.start()
thread.join()
} catch (e: Exception) {
Log.e(tag, e.toString())
}
val version: String
val body: String?
try {
version = res.get().getString("tag_name")
body = res.get().getString("body")
} catch (ignored: JSONException) {
return false
}
if (version == "v" + BuildConfig.VERSION_NAME) {
return false
}
val updateDialog = MaterialAlertDialogBuilder(context)
.setTitle(version)
.setMessage(body)
.setIcon(R.drawable.ic_update_app)
.setNegativeButton("Cancel") { _: DialogInterface?, _: Int -> }
.setPositiveButton("Update") { _: DialogInterface?, _: Int ->
startAppUpdate(
res.get()
)
}
updateDialog.show()
return true
}
private fun checkForAppUpdate(): JSONObject {
val url = "https://api.github.com/repos/deniscerri/ytdlnis/releases/latest"
val reader: BufferedReader
var line: String?
val responseContent = StringBuilder()
val conn: HttpURLConnection
var json = JSONObject()
try {
val req = URL(url)
conn = req.openConnection() as HttpURLConnection
conn.requestMethod = "GET"
conn.connectTimeout = 10000
conn.readTimeout = 5000
if (conn.responseCode < 300) {
reader = BufferedReader(InputStreamReader(conn.inputStream))
while (reader.readLine().also { line = it } != null) {
responseContent.append(line)
}
reader.close()
json = JSONObject(responseContent.toString())
if (json.has("error")) {
throw Exception()
}
}
conn.disconnect()
} catch (e: Exception) {
Log.e(tag, e.toString())
}
return json
}
private fun startAppUpdate(updateInfo: JSONObject) {
try {
val versions = updateInfo.getJSONArray("assets")
var url = ""
var appName = ""
for (i in 0 until versions.length()) {
val tmp = versions.getJSONObject(i)
if (tmp.getString("name").contains(Build.SUPPORTED_ABIS[0])) {
url = tmp.getString("browser_download_url")
appName = tmp.getString("name")
break
}
}
if (url.isEmpty()) {
Toast.makeText(context, R.string.couldnt_find_apk, Toast.LENGTH_SHORT).show()
return
}
val uri = Uri.parse(url)
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.mkdirs()
downloadManager.enqueue(
DownloadManager.Request(uri)
.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or
DownloadManager.Request.NETWORK_MOBILE
)
.setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, appName)
)
} catch (ignored: Exception) {
}
}
fun updateYoutubeDL() {
if (updatingYTDL) {
Toast.makeText(
context,
context.getString(R.string.ytdl_already_updating),
Toast.LENGTH_LONG
).show()
return
}
Toast.makeText(
context,
context.getString(R.string.ytdl_updating_started),
Toast.LENGTH_SHORT
).show()
updatingYTDL = true
val disposable = Observable.fromCallable {
YoutubeDL.getInstance().updateYoutubeDL(
context
)
}
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ status: UpdateStatus ->
when (status) {
UpdateStatus.DONE -> Toast.makeText(
context,
context.getString(R.string.ytld_update_success),
Toast.LENGTH_LONG
).show()
UpdateStatus.ALREADY_UP_TO_DATE -> Toast.makeText(
context,
context.getString(R.string.you_are_in_latest_version),
Toast.LENGTH_LONG
).show()
else -> Toast.makeText(context, status.toString(), Toast.LENGTH_LONG).show()
}
updatingYTDL = false
}) { e: Throwable? ->
if (BuildConfig.DEBUG) Log.e(tag, context.getString(R.string.ytdl_update_failed), e)
Toast.makeText(
context,
context.getString(R.string.ytdl_update_failed),
Toast.LENGTH_LONG
).show()
updatingYTDL = false
}
compositeDisposable.add(disposable)
}
companion object {
var updatingYTDL = false
var updatingApp = false
}
}

@ -5,7 +5,7 @@ buildscript {
def versionMajor = 1
def versionMinor = 4
def versionPatch = 6
def versionPatch = 7
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
ext {

Loading…
Cancel
Save