forked from recloudstream/cloudstream
download not working
This commit is contained in:
parent
9da1ce6032
commit
602cd065ce
5 changed files with 168 additions and 210 deletions
|
@ -71,8 +71,11 @@ dependencies {
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
|
|
||||||
// Exoplayer
|
// Exoplayer
|
||||||
implementation 'com.google.android.exoplayer:exoplayer:2.14.0'
|
implementation 'com.google.android.exoplayer:exoplayer:2.14.1'
|
||||||
implementation 'com.google.android.exoplayer:extension-cast:2.14.0'
|
implementation 'com.google.android.exoplayer:extension-cast:2.14.1'
|
||||||
implementation "com.google.android.exoplayer:extension-mediasession:2.14.0"
|
implementation "com.google.android.exoplayer:extension-mediasession:2.14.1"
|
||||||
//implementation "com.google.android.exoplayer:extension-leanback:2.14.0"
|
//implementation "com.google.android.exoplayer:extension-leanback:2.14.0"
|
||||||
|
|
||||||
|
//download manager
|
||||||
|
implementation "com.anggrayudi:storage:0.9.0"
|
||||||
}
|
}
|
|
@ -147,11 +147,11 @@ class EpisodeAdapter(
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
|
||||||
} else {
|
} else {
|
||||||
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
|
// 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 {
|
} else {
|
||||||
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
|
// 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,10 +341,19 @@ class ResultFragment : Fragment() {
|
||||||
if (tempUrl != null) {
|
if (tempUrl != null) {
|
||||||
viewModel.loadEpisode(episodeClick.data, true) { data ->
|
viewModel.loadEpisode(episodeClick.data, true) { data ->
|
||||||
if (data is Resource.Success) {
|
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(
|
VideoDownloadManager.DownloadEpisode(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
tempUrl,
|
tempUrl,
|
||||||
episodeClick.data,
|
meta,
|
||||||
data.value.links
|
data.value.links
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,31 +6,23 @@ import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.net.toUri
|
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.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
|
||||||
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.MainActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
|
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
|
||||||
import com.lagradost.cloudstream3.USER_AGENT
|
import java.io.BufferedInputStream
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
import java.io.InputStream
|
||||||
import java.io.File
|
import java.net.URL
|
||||||
import java.util.concurrent.Executor
|
import java.net.URLConnection
|
||||||
|
|
||||||
|
|
||||||
const val CHANNEL_ID = "cloudstream3.general"
|
const val CHANNEL_ID = "cloudstream3.general"
|
||||||
|
@ -38,6 +30,11 @@ const val CHANNEL_NAME = "Downloads"
|
||||||
const val CHANNEL_DESCRIPT = "The download notification channel"
|
const val CHANNEL_DESCRIPT = "The download notification channel"
|
||||||
|
|
||||||
object VideoDownloadManager {
|
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
|
@DrawableRes
|
||||||
const val imgDone = R.drawable.rddone
|
const val imgDone = R.drawable.rddone
|
||||||
|
|
||||||
|
@ -76,6 +73,16 @@ object VideoDownloadManager {
|
||||||
Stop,
|
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 var hasCreatedNotChanel = false
|
||||||
private fun Context.createNotificationChannel() {
|
private fun Context.createNotificationChannel() {
|
||||||
hasCreatedNotChanel = true
|
hasCreatedNotChanel = true
|
||||||
|
@ -111,29 +118,22 @@ object VideoDownloadManager {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createNotification(
|
private fun createNotification(
|
||||||
context: Context,
|
context: Context,
|
||||||
text: String,
|
source: String?,
|
||||||
source: String,
|
linkName: String?,
|
||||||
ep: ResultEpisode,
|
ep: DownloadEpisodeMetadata,
|
||||||
state: DownloadType,
|
state: DownloadType,
|
||||||
progress: Long,
|
progress: Long,
|
||||||
total: 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)
|
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setColorized(true)
|
.setColorized(true)
|
||||||
.setAutoCancel(true)
|
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
||||||
.setContentText(text)
|
.setContentTitle(ep.mainName)
|
||||||
.setSmallIcon(
|
.setSmallIcon(
|
||||||
when (state) {
|
when (state) {
|
||||||
DownloadType.IsDone -> imgDone
|
DownloadType.IsDone -> imgDone
|
||||||
|
@ -143,19 +143,72 @@ object VideoDownloadManager {
|
||||||
DownloadType.IsStopped -> imgStopped
|
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) {
|
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
||||||
builder.setProgress(total.toInt(), progress.toInt(), false)
|
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (ep.poster != null) {
|
if (ep.poster != null) {
|
||||||
val poster = context.getImageBitmapFromUrl(ep.poster)
|
val poster = context.getImageBitmapFromUrl(ep.poster)
|
||||||
if (poster != null)
|
if (poster != null)
|
||||||
builder.setLargeIcon(poster)
|
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) {
|
if ((state == DownloadType.IsDownloading || state == DownloadType.IsPaused) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val actionTypes: MutableList<DownloadActionType> = ArrayList()
|
val actionTypes: MutableList<DownloadActionType> = ArrayList()
|
||||||
// INIT
|
// INIT
|
||||||
|
@ -171,9 +224,9 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
// ADD ACTIONS
|
// ADD ACTIONS
|
||||||
for ((index, i) in actionTypes.withIndex()) {
|
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) {
|
"type", when (i) {
|
||||||
DownloadActionType.Resume -> "resume"
|
DownloadActionType.Resume -> "resume"
|
||||||
DownloadActionType.Pause -> "pause"
|
DownloadActionType.Pause -> "pause"
|
||||||
|
@ -181,11 +234,11 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
_resultIntent.putExtra("id", ep.id)
|
actionResultIntent.putExtra("id", ep.id)
|
||||||
|
|
||||||
val pending: PendingIntent = PendingIntent.getService(
|
val pending: PendingIntent = PendingIntent.getService(
|
||||||
context, 4337 + index + ep.id,
|
context, 4337 + index + ep.id,
|
||||||
_resultIntent,
|
actionResultIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -215,96 +268,86 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//https://exoplayer.dev/downloading-media.html
|
fun DownloadSingleEpisode(
|
||||||
fun DownloadSingleEpisode(context: Context, source: String, ep: ResultEpisode, link: ExtractorLink) {
|
context: Context,
|
||||||
val url = link.url
|
source: String?,
|
||||||
val headers = mapOf("User-Agent" to USER_AGENT, "Referer" to link.referer)
|
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 resume = false
|
||||||
val databaseProvider = ExoDatabaseProvider(context)
|
|
||||||
|
|
||||||
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()) {
|
||||||
// A download cache should not evict media, so should use a NoopCacheEvictor.
|
return false
|
||||||
val downloadCache = SimpleCache(
|
|
||||||
downloadDirectory,
|
|
||||||
NoOpCacheEvictor(),
|
|
||||||
databaseProvider)
|
|
||||||
|
|
||||||
// Create a factory for reading the data from the network.
|
|
||||||
val dataSourceFactory = DefaultHttpDataSourceFactory()
|
|
||||||
|
|
||||||
// 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() }
|
|
||||||
|
|
||||||
|
|
||||||
// Create the download manager.
|
|
||||||
val downloadManager = DownloadManager(
|
|
||||||
context,
|
|
||||||
databaseProvider,
|
|
||||||
downloadCache,
|
|
||||||
dataSourceFactory,
|
|
||||||
downloadExecutor)
|
|
||||||
|
|
||||||
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())
|
|
||||||
|
|
||||||
val downloadRequest: DownloadRequest = builder.build()
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)*/
|
}
|
||||||
|
if (!dFile.exists()) {
|
||||||
|
dFile.createFile("video/mp4", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun DownloadEpisode(context: Context, source: String, ep: ResultEpisode, links: List<ExtractorLink>) {
|
// OPEN FILE
|
||||||
|
val fileStream = dFile.openOutputStream(context, resume) ?: return false
|
||||||
|
|
||||||
|
// CONNECT
|
||||||
|
val connection: URLConnection = URL(link.url).openConnection()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// READ DATA FROM CONNECTION
|
||||||
|
val connectionInputStream: InputStream = BufferedInputStream(connection.inputStream)
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var count: Int
|
||||||
|
var bytesDownloaded = resumeLength
|
||||||
|
|
||||||
|
fun updateNotification(type : DownloadType) {
|
||||||
|
createNotification(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
link.name,
|
||||||
|
ep,
|
||||||
|
type,
|
||||||
|
bytesDownloaded,
|
||||||
|
bytesTotal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
count = connectionInputStream.read(buffer)
|
||||||
|
if (count < 0) break
|
||||||
|
bytesDownloaded += count
|
||||||
|
|
||||||
|
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: DownloadEpisodeMetadata, links: List<ExtractorLink>) {
|
||||||
val validLinks = links.filter { !it.isM3u8 }
|
val validLinks = links.filter { !it.isM3u8 }
|
||||||
if (validLinks.isNotEmpty()) {
|
if (validLinks.isNotEmpty()) {
|
||||||
DownloadSingleEpisode(context, source, ep, validLinks.first())
|
DownloadSingleEpisode(context, source, ep, validLinks.first())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue