converted app, info/notification/update util, notification receiver to kotlin
This commit is contained in:
parent
604fd300c8
commit
49db13833f
13 changed files with 1047 additions and 1043 deletions
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
84
app/src/main/java/com/deniscerri/ytdlnis/App.kt
Normal file
84
app/src/main/java/com/deniscerri/ytdlnis/App.kt
Normal file
|
|
@ -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(R.layout.activity_main)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
context = baseContext
|
context = baseContext
|
||||||
|
askPermissions()
|
||||||
reconnectDownloadService()
|
reconnectDownloadService()
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
fm = supportFragmentManager
|
fm = supportFragmentManager
|
||||||
|
|
@ -126,7 +127,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
view.onApplyWindowInsets(windowInsets)
|
view.onApplyWindowInsets(windowInsets)
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
askPermissions()
|
|
||||||
val intent = intent
|
val intent = intent
|
||||||
handleIntents(intent)
|
handleIntents(intent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,12 +192,11 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
||||||
Video v = info.getVideo();
|
Video v = info.getVideo();
|
||||||
v = findVideo(v.getURL(), v.getDownloadedType());
|
v = findVideo(v.getURL(), v.getDownloadedType());
|
||||||
try {
|
try {
|
||||||
downloadsObjects.remove(v);
|
|
||||||
databaseManager = new DatabaseManager(context);
|
databaseManager = new DatabaseManager(context);
|
||||||
databaseManager.clearHistoryItem(v,false);
|
databaseManager.clearHistoryItem(v,false);
|
||||||
databaseManager.close();
|
databaseManager.close();
|
||||||
downloadsRecyclerViewAdapter.notifyItemRemoved(downloadsObjects.indexOf(v));
|
downloadsRecyclerViewAdapter.notifyItemRemoved(downloadsObjects.indexOf(v));
|
||||||
|
downloadsObjects.remove(v);
|
||||||
if (downloadsObjects.isEmpty()) initCards();
|
if (downloadsObjects.isEmpty()) initCards();
|
||||||
downloading = false;
|
downloading = false;
|
||||||
}catch (Exception ignored){}
|
}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®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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
603
app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
Normal file
603
app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
Normal file
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
195
app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt
Normal file
195
app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt
Normal file
|
|
@ -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 versionMajor = 1
|
||||||
def versionMinor = 4
|
def versionMinor = 4
|
||||||
def versionPatch = 6
|
def versionPatch = 7
|
||||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue