diff --git a/app/build.gradle b/app/build.gradle index 14a56faa6..77db803b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,7 +51,7 @@ android { } dependencies { - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' + //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' @@ -74,5 +74,5 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.1' implementation 'com.squareup.retrofit2:converter-scalars:2.1.0' - implementation 'com.arthenica:ffmpeg-kit-https:4.5.1.LTS' + implementation 'com.arthenica:ffmpeg-kit-min:4.5.1.LTS' } \ No newline at end of file diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 024e64961..6946d9a63 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -8,12 +8,30 @@ "variantName": "release", "elements": [ { - "type": "UNIVERSAL", - "filters": [], + "type": "ONE_OF_MANY", + "filters": [ + { + "filterType": "ABI", + "value": "x86_64" + } + ], "attributes": [], "versionCode": 4, "versionName": "0.2.2", - "outputFile": "app-universal-release.apk" + "outputFile": "app-x86_64-release.apk" + }, + { + "type": "ONE_OF_MANY", + "filters": [ + { + "filterType": "ABI", + "value": "x86" + } + ], + "attributes": [], + "versionCode": 4, + "versionName": "0.2.2", + "outputFile": "app-x86-release.apk" }, { "type": "ONE_OF_MANY", @@ -40,32 +58,6 @@ "versionCode": 4, "versionName": "0.2.2", "outputFile": "app-armeabi-v7a-release.apk" - }, - { - "type": "ONE_OF_MANY", - "filters": [ - { - "filterType": "ABI", - "value": "x86_64" - } - ], - "attributes": [], - "versionCode": 4, - "versionName": "0.2.2", - "outputFile": "app-x86_64-release.apk" - }, - { - "type": "ONE_OF_MANY", - "filters": [ - { - "filterType": "ABI", - "value": "x86" - } - ], - "attributes": [], - "versionCode": 4, - "versionName": "0.2.2", - "outputFile": "app-x86-release.apk" } ], "elementType": "File" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e0cc0a308..a04fe4ee8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/DownloadService.kt b/app/src/main/java/com/github/libretube/DownloadService.kt new file mode 100644 index 000000000..eb194e16c --- /dev/null +++ b/app/src/main/java/com/github/libretube/DownloadService.kt @@ -0,0 +1,173 @@ +package com.github.libretube + +import android.app.* +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.graphics.Color +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.os.IBinder +import android.util.Log +import android.widget.Toast +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import com.arthenica.ffmpegkit.FFmpegKit +import java.io.File + +class DownloadService : Service(){ + val TAG = "DownloadService" + private var downloadId: Long =0 + private lateinit var videoId: String + private lateinit var videoUrl: String + private lateinit var audioUrl: String + private var duration: Int = 0 + //private lateinit var command: String + private lateinit var audioDir: File + private lateinit var videoDir: File + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + videoId = intent?.getStringExtra("videoId")!! + videoUrl = intent.getStringExtra("videoUrl")!! + audioUrl = intent.getStringExtra("audioUrl")!! + //command = intent.getStringExtra("command")!! + duration = intent.getIntExtra("duration",1) + downloadManager() + + return super.onStartCommand(intent, flags, startId) + } + override fun onBind(intent: Intent?): IBinder? { + TODO("Not yet implemented") + } + + private fun downloadManager() { + val path = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + val folder_main = ".tmp" + val f = File(path, folder_main) + if (!f.exists()) { + f.mkdirs() + Log.e(TAG, "Directory make") + } else { + Log.e(TAG, "Directory already have") + } + audioDir = File(f, "$videoId-audio") + videoDir = File(f, "$videoId-video") + try { + Log.e(TAG, "Directory make") + val request: DownloadManager.Request = + DownloadManager.Request(Uri.parse(videoUrl)) + .setTitle("Video") // Title of the Download Notification + .setDescription("Downloading") // Description of the Download Notification + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) // Visibility of the download Notification + .setDestinationUri(Uri.fromFile(videoDir)) + .setAllowedOverMetered(true) // Set if download is allowed on Mobile network + .setAllowedOverRoaming(true) // + val downloadManager: DownloadManager = + applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager + downloadId = downloadManager.enqueue(request) + + registerReceiver(onDownloadComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) + } catch (e: IllegalArgumentException) { + Log.e(TAG, "download error $e") + } + } + + private val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + //Fetching the download id received with the broadcast + val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) + //Checking if the received broadcast is for our enqueued download by matching download id + if (downloadId == id) { + Toast.makeText(this@DownloadService, "Download Completed", Toast.LENGTH_SHORT) + .show() + downloadId=0 + val request: DownloadManager.Request = + DownloadManager.Request(Uri.parse(audioUrl)) + .setTitle("Audio") // Title of the Download Notification + .setDescription("Downloading") // Description of the Download Notification + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) // Visibility of the download Notification + .setDestinationUri(Uri.fromFile(audioDir)) + .setAllowedOverMetered(true) // Set if download is allowed on Mobile network + .setAllowedOverRoaming(true) // + val downloadManager: DownloadManager = + applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager + downloadManager.enqueue(request) + + }else if (downloadId == 0L){ + val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val chan = NotificationChannel("service", + "DownloadService", NotificationManager.IMPORTANCE_NONE) + chan.lightColor = Color.BLUE + chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE + service.createNotificationChannel(chan) + "service" + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } + //Toast.makeText(this,command, Toast.LENGTH_SHORT).show() + val pendingIntent: PendingIntent = PendingIntent.getActivity( + this@DownloadService, 0, intent, 0) + //Sets the maximum progress as 100 + val progressMax = 100 + //Creating a notification and setting its various attributes + val notification = + NotificationCompat.Builder(this@DownloadService, channelId) + .setSmallIcon(R.drawable.ic_download) + .setContentTitle("LibreTube") + .setContentText("Downloading") + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .setOnlyAlertOnce(true) + .setProgress(progressMax, 0, true) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + + FFmpegKit.executeAsync("-y -i $videoDir -i $audioDir -c copy ${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/${videoId}.mkv", + { session -> + val state = session.state + val returnCode = session.returnCode + // CALLED WHEN SESSION IS EXECUTED + Log.d( + TAG, + String.format( + "FFmpeg process exited with state %s and rc %s.%s", + state, + returnCode, + session.failStackTrace + ) + ) + val path = applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + val folder_main = ".tmp" + val f = File(path, folder_main) + f.deleteRecursively() + stopForeground(true) + }, { + // CALLED WHEN SESSION PRINTS LOGS + Log.e(TAG,it.message.toString()) + }) { + // CALLED WHEN SESSION GENERATES STATISTICS + Log.e(TAG+"stat",it.time.toString()) + val progress = it.time/(10*duration!!) + if (progress<1){ + notification + .setProgress(progressMax, progress.toInt(), false) + service.notify(1,notification.build()) + } + } + startForeground(1,notification.build()) + } + } + } + + + override fun onDestroy() { + unregisterReceiver(onDownloadComplete) + super.onDestroy() + } + +} diff --git a/app/src/main/java/com/github/libretube/PlayerFragment.kt b/app/src/main/java/com/github/libretube/PlayerFragment.kt index ee5fb82a5..06c6e663c 100644 --- a/app/src/main/java/com/github/libretube/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/PlayerFragment.kt @@ -3,15 +3,16 @@ package com.github.libretube import android.Manifest import android.annotation.SuppressLint import android.app.Activity +import android.app.ActivityManager import android.content.Context import android.content.DialogInterface +import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.os.Build import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.os.Environment -import android.os.Environment.DIRECTORY_DOWNLOADS import android.text.Html import android.util.Log import android.util.TypedValue @@ -23,13 +24,13 @@ import androidx.appcompat.app.AlertDialog import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat.getSystemService import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.arthenica.ffmpegkit.FFmpegKit import com.github.libretube.adapters.TrendingAdapter import com.github.libretube.obj.PipedStream import com.github.libretube.obj.Subscribe @@ -48,7 +49,6 @@ import com.google.android.exoplayer2.upstream.DefaultHttpDataSource import com.google.android.material.button.MaterialButton import com.squareup.picasso.Picasso import retrofit2.HttpException -import java.io.File import java.io.IOException import kotlin.math.abs @@ -364,6 +364,9 @@ class PlayerFragment : Fragment() { val subButton = view.findViewById(R.id.player_subscribe) isSubscribed(subButton, channelId!!) } + //check if livestream + if (response.duration!!>0){ + //download clicked relDownloadVideo.setOnClickListener { val mainActivity = activity as MainActivity Log.e(TAG,"download button clicked!") @@ -396,11 +399,24 @@ class PlayerFragment : Fragment() { ) } } - FFmpegKit.executeAsync("-i ${response.videoStreams[0].url} -i ${response.audioStreams!![0].url} -c copy ${context?.getExternalFilesDir(DIRECTORY_DOWNLOADS)}${File.separator}output2.mkv", + val builderr: AlertDialog.Builder? = activity?.let { + AlertDialog.Builder(it) + } + var videos = videosNameArray.drop(1).toTypedArray() + builderr!!.setTitle(R.string.choose_quality_dialog) + .setItems(videos, + DialogInterface.OnClickListener { _, which -> + val intent = Intent(context,DownloadService::class.java) + intent.putExtra("videoId",videoId) + intent.putExtra("videoUrl",response.videoStreams[which].url) + intent.putExtra("audioUrl",response.audioStreams!![0].url) + intent.putExtra("duration",response.duration) + //intent.putExtra("command","-y -i ${response.videoStreams[which].url} -i ${response.audioStreams!![0].url} -c copy ${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/${videoId}.mkv") + context?.startService(intent) + /*FFmpegKit.executeAsync("-y -i ${response.videoStreams[which].url} -i ${response.audioStreams!![0].url} -c copy ${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/${videoId}.mkv", { session -> val state = session.state val returnCode = session.returnCode - // CALLED WHEN SESSION IS EXECUTED Log.d( TAG, @@ -413,11 +429,17 @@ class PlayerFragment : Fragment() { ) }, { // CALLED WHEN SESSION PRINTS LOGS - Log.e(TAG,it.toString()) + Log.e(TAG,it.message.toString()) }) { // CALLED WHEN SESSION GENERATES STATISTICS - } - + Log.e(TAG,it.time.toString()) + }*/ + }) + val dialog: AlertDialog? = builderr?.create() + dialog?.show() + } + }else{ + Toast.makeText(context,R.string.cannotDownload, Toast.LENGTH_SHORT).show() } } } @@ -427,8 +449,15 @@ class PlayerFragment : Fragment() { } - - +/* private fun isMyServiceRunning(serviceClass: Class<*>): Boolean { + val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? + for (service in manager!!.getRunningServices(Int.MAX_VALUE)) { + if (serviceClass.name == service.service.className) { + return true + } + } + return false + }*/ private fun isSubscribed(button: MaterialButton, channel_id: String){ @SuppressLint("ResourceAsColor") fun run() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a21a2ad6c..3c24751f7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,4 +23,5 @@ Login/Register Please Login or Register from the settings to show your Subscriptions! Subscribe to some channels first! + Can\'t Download this stream! \ No newline at end of file