download not working

This commit is contained in:
LagradOst 2021-07-03 22:59:46 +02:00
parent 9da1ce6032
commit 602cd065ce
5 changed files with 168 additions and 210 deletions

View File

@ -71,8 +71,11 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
// Exoplayer
implementation 'com.google.android.exoplayer:exoplayer:2.14.0'
implementation 'com.google.android.exoplayer:extension-cast:2.14.0'
implementation "com.google.android.exoplayer:extension-mediasession:2.14.0"
implementation 'com.google.android.exoplayer:exoplayer:2.14.1'
implementation 'com.google.android.exoplayer:extension-cast:2.14.1'
implementation "com.google.android.exoplayer:extension-mediasession:2.14.1"
//implementation "com.google.android.exoplayer:extension-leanback:2.14.0"
//download manager
implementation "com.anggrayudi:storage:0.9.0"
}

View File

@ -147,11 +147,11 @@ class EpisodeAdapter(
clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
} else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
}
} else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
}
}
}

View File

@ -341,10 +341,19 @@ class ResultFragment : Fragment() {
if (tempUrl != null) {
viewModel.loadEpisode(episodeClick.data, true) { data ->
if (data is Resource.Success) {
val meta = VideoDownloadManager.DownloadEpisodeMetadata(
episodeClick.data.id,
currentHeaderName ?: return@loadEpisode,
apiName ?: return@loadEpisode,
episodeClick.data.poster,
episodeClick.data.name,
episodeClick.data.season,
episodeClick.data.episode
)
VideoDownloadManager.DownloadEpisode(
requireContext(),
tempUrl,
episodeClick.data,
meta,
data.value.links
)
}

View File

@ -6,31 +6,23 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import androidx.annotation.DrawableRes
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.net.toUri
import com.anggrayudi.storage.extension.closeStream
import com.anggrayudi.storage.file.DocumentFileCompat
import com.anggrayudi.storage.file.openOutputStream
import com.bumptech.glide.Glide
import com.google.android.exoplayer2.database.ExoDatabaseProvider
import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.offline.DownloadRequest
import com.google.android.exoplayer2.offline.DownloadService
import com.google.android.exoplayer2.offline.DownloadService.sendAddDownload
import com.google.android.exoplayer2.offline.StreamKey
import com.google.android.exoplayer2.scheduler.Requirements
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import java.io.File
import java.util.concurrent.Executor
import java.io.BufferedInputStream
import java.io.InputStream
import java.net.URL
import java.net.URLConnection
const val CHANNEL_ID = "cloudstream3.general"
@ -38,6 +30,11 @@ const val CHANNEL_NAME = "Downloads"
const val CHANNEL_DESCRIPT = "The download notification channel"
object VideoDownloadManager {
var maxConcurrentDownloads = 3
private const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
@DrawableRes
const val imgDone = R.drawable.rddone
@ -76,6 +73,16 @@ object VideoDownloadManager {
Stop,
}
data class DownloadEpisodeMetadata(
val id: Int,
val mainName: String,
val sourceApiName: String?,
val poster: String?,
val name: String?,
val season: Int?,
val episode: Int?
)
private var hasCreatedNotChanel = false
private fun Context.createNotificationChannel() {
hasCreatedNotChanel = true
@ -111,29 +118,22 @@ object VideoDownloadManager {
return null
}
fun createNotification(
private fun createNotification(
context: Context,
text: String,
source: String,
ep: ResultEpisode,
source: String?,
linkName: String?,
ep: DownloadEpisodeMetadata,
state: DownloadType,
progress: Long,
total: Long,
) {
val intent = Intent(context, MainActivity::class.java).apply {
data = source.toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setAutoCancel(true)
.setColorized(true)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
.setContentText(text)
.setContentTitle(ep.mainName)
.setSmallIcon(
when (state) {
DownloadType.IsDone -> imgDone
@ -143,19 +143,72 @@ object VideoDownloadManager {
DownloadType.IsStopped -> imgStopped
}
)
.setContentIntent(pendingIntent)
if (ep.sourceApiName != null) {
builder.setSubText(ep.sourceApiName)
}
if (source != null) {
val intent = Intent(context, MainActivity::class.java).apply {
data = source.toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
builder.setContentIntent(pendingIntent)
}
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
builder.setProgress(total.toInt(), progress.toInt(), false)
}
val rowTwoExtra = if (ep.name != null) " - ${ep.name}\n" else ""
val rowTwo = if (ep.season != null && ep.episode != null) {
"S${ep.season}:E${ep.episode}" + rowTwoExtra
} else if (ep.episode != null) {
"Episode ${ep.episode}" + rowTwoExtra
} else {
(ep.name ?: "") + ""
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (ep.poster != null) {
val poster = context.getImageBitmapFromUrl(ep.poster)
if (poster != null)
builder.setLargeIcon(poster)
}
val progressPercentage = progress * 100 / total
val progressMbString = "%.1f".format(progress / 1000000f)
val totalMbString = "%.1f".format(total / 1000000f)
val bigText =
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
(if (linkName == null) "" else "$linkName\n") + "$rowTwo\n$progressPercentage % ($progressMbString MB/$totalMbString MB)"
} else if (state == DownloadType.IsFailed) {
"Download Failed - $rowTwo"
} else if (state == DownloadType.IsDone) {
"Download Done - $rowTwo"
} else {
"Download Stopped - $rowTwo"
}
val bodyStyle = NotificationCompat.BigTextStyle()
bodyStyle.bigText(bigText)
builder.setStyle(bodyStyle)
} else {
val txt = if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
rowTwo
} else if (state == DownloadType.IsFailed) {
"Download Failed - $rowTwo"
} else if (state == DownloadType.IsDone) {
"Download Done - $rowTwo"
} else {
"Download Stopped - $rowTwo"
}
builder.setContentText(txt)
}
if ((state == DownloadType.IsDownloading || state == DownloadType.IsPaused) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val actionTypes: MutableList<DownloadActionType> = ArrayList()
// INIT
@ -171,9 +224,9 @@ object VideoDownloadManager {
// ADD ACTIONS
for ((index, i) in actionTypes.withIndex()) {
val _resultIntent = Intent(context, DownloadService::class.java)
val actionResultIntent = Intent(context, DownloadService::class.java)
_resultIntent.putExtra(
actionResultIntent.putExtra(
"type", when (i) {
DownloadActionType.Resume -> "resume"
DownloadActionType.Pause -> "pause"
@ -181,11 +234,11 @@ object VideoDownloadManager {
}
)
_resultIntent.putExtra("id", ep.id)
actionResultIntent.putExtra("id", ep.id)
val pending: PendingIntent = PendingIntent.getService(
context, 4337 + index + ep.id,
_resultIntent,
actionResultIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
@ -215,96 +268,86 @@ object VideoDownloadManager {
}
}
//https://exoplayer.dev/downloading-media.html
fun DownloadSingleEpisode(context: Context, source: String, ep: ResultEpisode, link: ExtractorLink) {
val url = link.url
val headers = mapOf("User-Agent" to USER_AGENT, "Referer" to link.referer)
fun DownloadSingleEpisode(
context: Context,
source: String?,
ep: DownloadEpisodeMetadata,
link: ExtractorLink
): Boolean {
val name = (ep.name ?: "Episode ${ep.episode}")
val path = "Downloads/Anime/$name.mp4"
val dFile = DocumentFileCompat.fromSimplePath(context, basePath = path) ?: return false
// Note: This should be a singleton in your app.
val databaseProvider = ExoDatabaseProvider(context)
val resume = false
val downloadDirectory = File(Environment.getExternalStorageDirectory().path + "/Download/" + (ep.name ?: "Episode ${ep.episode}")) // File(context.cacheDir, "video_${ep.id}")
if (!resume && dFile.exists()) {
if (!dFile.delete()) {
return false
}
}
if (!dFile.exists()) {
dFile.createFile("video/mp4", name)
}
// A download cache should not evict media, so should use a NoopCacheEvictor.
val downloadCache = SimpleCache(
downloadDirectory,
NoOpCacheEvictor(),
databaseProvider)
// OPEN FILE
val fileStream = dFile.openOutputStream(context, resume) ?: return false
// Create a factory for reading the data from the network.
val dataSourceFactory = DefaultHttpDataSourceFactory()
// CONNECT
val connection: URLConnection = URL(link.url).openConnection()
// Choose an executor for downloading data. Using Runnable::run will cause each download task to
// download data on its own thread. Passing an executor that uses multiple threads will speed up
// download tasks that can be split into smaller parts for parallel execution. Applications that
// already have an executor for background downloads may wish to reuse their existing executor.
val downloadExecutor = Executor { obj: Runnable -> obj.run() }
// SET CONNECTION SETTINGS
connection.connectTimeout = 10000
connection.setRequestProperty("Accept-Encoding", "identity")
connection.setRequestProperty("User-Agent", USER_AGENT)
if (link.referer.isNotEmpty()) connection.setRequestProperty("Referer", link.referer)
if (resume) connection.setRequestProperty("Range", "bytes=${dFile.length()}-")
val resumeLength = (if (resume) dFile.length() else 0)
// ON CONNECTION
connection.connect()
val contentLength = connection.contentLength
if (contentLength < 5000000) return false // less than 5mb
val bytesTotal = contentLength + resumeLength
// Create the download manager.
val downloadManager = DownloadManager(
context,
databaseProvider,
downloadCache,
dataSourceFactory,
downloadExecutor)
// READ DATA FROM CONNECTION
val connectionInputStream: InputStream = BufferedInputStream(connection.inputStream)
val buffer = ByteArray(1024)
var count: Int
var bytesDownloaded = resumeLength
val requirements = Requirements(Requirements.NETWORK)
// Optionally, setters can be called to configure the download manager.
downloadManager.requirements = requirements
downloadManager.maxParallelDownloads = 3
val builder = DownloadRequest.Builder(ep.id.toString(), link.url.toUri())
fun updateNotification(type : DownloadType) {
createNotification(
context,
source,
link.name,
ep,
type,
bytesDownloaded,
bytesTotal
)
}
val downloadRequest: DownloadRequest = builder.build()
while (true) {
count = connectionInputStream.read(buffer)
if (count < 0) break
bytesDownloaded += count
DownloadService.sendAddDownload(
context,
VideoDownloadService::class.java,
downloadRequest,
/* foreground= */ true)
/*
val disposable = url.download(header = headers)
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = { progress ->
createNotification(
context,
"Downloading ${progress.downloadSizeStr()}/${progress.totalSizeStr()}",
source,
ep,
DownloadType.IsDownloading,
progress.downloadSize,
progress.totalSize
)
},
onComplete = {
createNotification(
context,
"Download Done",
source,
ep,
DownloadType.IsDone,
0, 0
)
},
onError = {
createNotification(
context,
"Download Failed",
source,
ep,
DownloadType.IsFailed,
0, 0
)
}
)*/
updateNotification(DownloadType.IsDownloading)
fileStream.write(buffer, 0, count)
}
// DOWNLOAD EXITED CORRECTLY
updateNotification(DownloadType.IsDone)
fileStream.closeStream()
connectionInputStream.closeStream()
return true
}
public fun DownloadEpisode(context: Context, source: String, ep: ResultEpisode, links: List<ExtractorLink>) {
public fun DownloadEpisode(context: Context, source: String, ep: DownloadEpisodeMetadata, links: List<ExtractorLink>) {
val validLinks = links.filter { !it.isM3u8 }
if (validLinks.isNotEmpty()) {
DownloadSingleEpisode(context, source, ep, validLinks.first())
}
}
}

View File

@ -1,97 +0,0 @@
package com.lagradost.cloudstream3.utils
import android.app.IntentService
import android.app.Notification
import android.app.PendingIntent
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.net.toUri
import com.google.android.exoplayer2.offline.Download
import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.offline.DownloadService
import com.google.android.exoplayer2.scheduler.PlatformScheduler
import com.google.android.exoplayer2.scheduler.Scheduler
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
import java.lang.Exception
private const val JOB_ID = 1
private const val FOREGROUND_NOTIFICATION_ID = 1
class VideoDownloadService : DownloadService(
FOREGROUND_NOTIFICATION_ID,
DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
CHANNEL_ID,
R.string.exo_download_notification_channel_name, /* channelDescriptionResourceId= */
0) {
override fun getDownloadManager(): DownloadManager {
val ctx = this
return ExoPlayerHelper.downloadManager.apply {
requirements = DownloadManager.DEFAULT_REQUIREMENTS
maxParallelDownloads = 3
addListener(
object : DownloadManager.Listener {
override fun onDownloadChanged(
downloadManager: DownloadManager,
download: Download,
finalException: Exception?,
) {
val intent = Intent(ctx, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(ctx, 0, intent, 0)
val builder = NotificationCompat.Builder(ctx, CHANNEL_ID)
.setAutoCancel(true)
.setColorized(true)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setColor(colorFromAttribute(R.attr.colorPrimary))
.setContentText("${download.bytesDownloaded} / ${download.contentLength}")
.setSmallIcon(
VideoDownloadManager.imgDownloading
)
.setProgress((download.bytesDownloaded / 1000).toInt(),
(download.contentLength / 1000).toInt(),
false) // in case the size is over 2gb / 1000
.setContentIntent(pendingIntent)
builder.build()
with(NotificationManagerCompat.from(ctx)) {
// notificationId is a unique int for each notification that you must define
notify(download.request.id.hashCode(), builder.build())
}
super.onDownloadChanged(downloadManager, download, finalException)
}
}
)
}
}
override fun getScheduler(): Scheduler =
PlatformScheduler(this, JOB_ID)
override fun getForegroundNotification(downloads: MutableList<Download>): Notification {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setAutoCancel(true)
.setColorized(true)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setColor(colorFromAttribute(R.attr.colorPrimary))
.setContentText("Downloading ${downloads.size} item${if (downloads.size == 1) "" else "s"}")
.setSmallIcon(
VideoDownloadManager.imgDownloading
)
.setContentIntent(pendingIntent)
return builder.build()
}
}