mirror of https://github.com/deniscerri/ytdlnis
converted app, info/notification/update util, notification receiver to kotlin
parent
604fd300c8
commit
49db13833f
@ -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
|
||||
}
|
||||
}
|
||||
@ -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®ionCode="+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®ionCode="+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®ionCode="+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®ionCode="+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®ionCode="+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®ionCode="+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®ion="+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®ionCode=$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®ionCode=" + 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®ionCode=$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®ionCode=" + 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®ionCode=$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®ionCode=$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®ion=" + 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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue