mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 07:50:31 +05:30
use download manager
This commit is contained in:
parent
6879c91dd2
commit
7159eb0472
@ -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'
|
||||
}
|
@ -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"
|
||||
|
@ -7,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_libretube"
|
||||
@ -17,6 +18,7 @@
|
||||
android:name=".myApp"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
>
|
||||
<activity
|
||||
android:name=".Player"
|
||||
@ -37,6 +39,10 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".DownloadService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
173
app/src/main/java/com/github/libretube/DownloadService.kt
Normal file
173
app/src/main/java/com/github/libretube/DownloadService.kt
Normal file
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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<MaterialButton>(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() {
|
||||
|
@ -23,4 +23,5 @@
|
||||
<string name="login_register">Login/Register</string>
|
||||
<string name="please_login">Please Login or Register from the settings to show your Subscriptions!</string>
|
||||
<string name="subscribeIsEmpty">Subscribe to some channels first!</string>
|
||||
<string name="cannotDownload">Can\'t Download this stream!</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user