forked from recloudstream/cloudstream
download not working
This commit is contained in:
parent
2c312cd9b4
commit
672dc8c8d1
15 changed files with 559 additions and 6 deletions
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<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.WRITE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.lagradost.cloudstream3
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
import android.app.PictureInPictureParams
|
import android.app.PictureInPictureParams
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -36,6 +37,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
var isInPlayer: Boolean = false
|
var isInPlayer: Boolean = false
|
||||||
var canShowPipMode: Boolean = false
|
var canShowPipMode: Boolean = false
|
||||||
var isInPIPMode: Boolean = false
|
var isInPIPMode: Boolean = false
|
||||||
|
lateinit var mainContext : MainActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enterPIPMode() {
|
private fun enterPIPMode() {
|
||||||
|
@ -83,6 +85,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
mainContext = this
|
||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,10 @@ import kotlinx.android.synthetic.main.result_episode.view.episode_holder
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.episode_text
|
import kotlinx.android.synthetic.main.result_episode.view.episode_text
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
||||||
|
|
||||||
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
|
|
||||||
const val ACTION_RELOAD_EPISODE = 4
|
const val ACTION_RELOAD_EPISODE = 4
|
||||||
const val ACTION_CHROME_CAST_EPISODE = 2
|
const val ACTION_CHROME_CAST_EPISODE = 2
|
||||||
|
const val ACTION_DOWNLOAD_EPISODE = 3
|
||||||
|
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
|
||||||
|
|
||||||
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
||||||
|
|
||||||
|
@ -145,10 +146,12 @@ class EpisodeAdapter(
|
||||||
if (castContext.castState == CastState.CONNECTED) {
|
if (castContext.castState == CastState.CONNECTED) {
|
||||||
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_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_DOWNLOAD_EPISODE, card))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,9 @@ import com.lagradost.cloudstream3.ui.player.PlayerData
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
import kotlinx.android.synthetic.main.fragment_result.*
|
import kotlinx.android.synthetic.main.fragment_result.*
|
||||||
|
|
||||||
|
@ -258,7 +260,6 @@ class ResultFragment : Fragment() {
|
||||||
currentLoadingCount++
|
currentLoadingCount++
|
||||||
when (episodeClick.action) {
|
when (episodeClick.action) {
|
||||||
ACTION_CHROME_CAST_EPISODE -> {
|
ACTION_CHROME_CAST_EPISODE -> {
|
||||||
|
|
||||||
val skipLoading = if (apiName != null) {
|
val skipLoading = if (apiName != null) {
|
||||||
getApiFromName(apiName).instantLinkLoading
|
getApiFromName(apiName).instantLinkLoading
|
||||||
} else false
|
} else false
|
||||||
|
@ -327,7 +328,19 @@ class ResultFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
ACTION_DOWNLOAD_EPISODE -> {
|
||||||
|
val tempUrl = url
|
||||||
|
if (tempUrl != null) {
|
||||||
|
viewModel.loadEpisode(episodeClick.data, true) { data ->
|
||||||
|
if (data is Resource.Success) {
|
||||||
|
VideoDownloadManager.DownloadEpisode(requireContext(),
|
||||||
|
tempUrl,
|
||||||
|
episodeClick.data,
|
||||||
|
data.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ class SearchFragment : Fragment() {
|
||||||
allApi.providersActive = requireActivity().getApiSettings()
|
allApi.providersActive = requireActivity().getApiSettings()
|
||||||
|
|
||||||
//searchViewModel.search("iron man")
|
//searchViewModel.search("iron man")
|
||||||
//(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro")
|
(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro")
|
||||||
/*
|
/*
|
||||||
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
|
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
|
||||||
.setCustomAnimations(R.anim.enter_anim,
|
.setCustomAnimations(R.anim.enter_anim,
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
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.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
|
||||||
|
|
||||||
|
|
||||||
|
const val CHANNEL_ID = "cloudstream3.general"
|
||||||
|
const val CHANNEL_NAME = "Downloads"
|
||||||
|
const val CHANNEL_DESCRIPT = "The download notification channel"
|
||||||
|
|
||||||
|
object VideoDownloadManager {
|
||||||
|
@DrawableRes
|
||||||
|
const val imgDone = R.drawable.rddone
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
const val imgDownloading = R.drawable.rdload
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
const val imgPaused = R.drawable.rdpause
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
const val imgStopped = R.drawable.rderror
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
const val imgError = R.drawable.rderror
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
const val pressToPauseIcon = R.drawable.ic_baseline_pause_24
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
const val pressToResumeIcon = R.drawable.ic_baseline_play_arrow_24
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
const val pressToStopIcon = R.drawable.exo_icon_stop
|
||||||
|
|
||||||
|
enum class DownloadType {
|
||||||
|
IsPaused,
|
||||||
|
IsDownloading,
|
||||||
|
IsDone,
|
||||||
|
IsFailed,
|
||||||
|
IsStopped,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DownloadActionType {
|
||||||
|
Pause,
|
||||||
|
Resume,
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
private var hasCreatedNotChanel = false
|
||||||
|
private fun Context.createNotificationChannel() {
|
||||||
|
hasCreatedNotChanel = true
|
||||||
|
// Create the NotificationChannel, but only on API 26+ because
|
||||||
|
// the NotificationChannel class is new and not in the support library
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val name = CHANNEL_NAME //getString(R.string.channel_name)
|
||||||
|
val descriptionText = CHANNEL_DESCRIPT//getString(R.string.channel_description)
|
||||||
|
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
|
||||||
|
description = descriptionText
|
||||||
|
}
|
||||||
|
// Register the channel with the system
|
||||||
|
val notificationManager: NotificationManager =
|
||||||
|
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cachedBitmaps = hashMapOf<String, Bitmap>()
|
||||||
|
private fun Context.getImageBitmapFromUrl(url: String): Bitmap? {
|
||||||
|
if (cachedBitmaps.containsKey(url)) {
|
||||||
|
return cachedBitmaps[url]
|
||||||
|
}
|
||||||
|
|
||||||
|
val bitmap = Glide.with(this)
|
||||||
|
.asBitmap()
|
||||||
|
.load(url).into(720, 720)
|
||||||
|
.get()
|
||||||
|
if (bitmap != null) {
|
||||||
|
cachedBitmaps[url] = bitmap
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNotification(
|
||||||
|
context: Context,
|
||||||
|
text: String,
|
||||||
|
source: String,
|
||||||
|
ep: ResultEpisode,
|
||||||
|
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)
|
||||||
|
.setSmallIcon(
|
||||||
|
when (state) {
|
||||||
|
DownloadType.IsDone -> imgDone
|
||||||
|
DownloadType.IsDownloading -> imgDownloading
|
||||||
|
DownloadType.IsPaused -> imgPaused
|
||||||
|
DownloadType.IsFailed -> imgError
|
||||||
|
DownloadType.IsStopped -> imgStopped
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
|
||||||
|
if (state == DownloadType.IsDownloading || state == DownloadType.IsPaused) {
|
||||||
|
builder.setProgress(total.toInt(), progress.toInt(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((state == DownloadType.IsDownloading || state == DownloadType.IsPaused) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val actionTypes: MutableList<DownloadActionType> = ArrayList()
|
||||||
|
// INIT
|
||||||
|
if (state == DownloadType.IsDownloading) {
|
||||||
|
actionTypes.add(DownloadActionType.Pause)
|
||||||
|
actionTypes.add(DownloadActionType.Stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == DownloadType.IsPaused) {
|
||||||
|
actionTypes.add(DownloadActionType.Resume)
|
||||||
|
actionTypes.add(DownloadActionType.Stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD ACTIONS
|
||||||
|
for ((index, i) in actionTypes.withIndex()) {
|
||||||
|
val _resultIntent = Intent(context, DownloadService::class.java)
|
||||||
|
|
||||||
|
_resultIntent.putExtra(
|
||||||
|
"type", when (i) {
|
||||||
|
DownloadActionType.Resume -> "resume"
|
||||||
|
DownloadActionType.Pause -> "pause"
|
||||||
|
DownloadActionType.Stop -> "stop"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
_resultIntent.putExtra("id", ep.id)
|
||||||
|
|
||||||
|
val pending: PendingIntent = PendingIntent.getService(
|
||||||
|
context, 4337 + index + ep.id,
|
||||||
|
_resultIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.addAction(
|
||||||
|
NotificationCompat.Action(
|
||||||
|
when (i) {
|
||||||
|
DownloadActionType.Resume -> pressToResumeIcon
|
||||||
|
DownloadActionType.Pause -> pressToPauseIcon
|
||||||
|
DownloadActionType.Stop -> pressToStopIcon
|
||||||
|
}, when (i) {
|
||||||
|
DownloadActionType.Resume -> "Resume"
|
||||||
|
DownloadActionType.Pause -> "Pause"
|
||||||
|
DownloadActionType.Stop -> "Stop"
|
||||||
|
}, pending
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCreatedNotChanel) {
|
||||||
|
context.createNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
with(NotificationManagerCompat.from(context)) {
|
||||||
|
// notificationId is a unique int for each notification that you must define
|
||||||
|
notify(ep.id, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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)
|
||||||
|
|
||||||
|
// Note: This should be a singleton in your app.
|
||||||
|
val databaseProvider = ExoDatabaseProvider(context)
|
||||||
|
|
||||||
|
val downloadDirectory = File(Environment.getExternalStorageDirectory().path + "/Download/" + (ep.name ?: "Episode ${ep.episode}")) // File(context.cacheDir, "video_${ep.id}")
|
||||||
|
|
||||||
|
// A download cache should not evict media, so should use a NoopCacheEvictor.
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun DownloadEpisode(context: Context, source: String, ep: ResultEpisode, links: List<ExtractorLink>) {
|
||||||
|
val validLinks = links.filter { !it.isM3u8 }
|
||||||
|
if (validLinks.isNotEmpty()) {
|
||||||
|
DownloadSingleEpisode(context, source, ep, validLinks.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.database.ExoDatabaseProvider
|
||||||
|
import com.google.android.exoplayer2.offline.DownloadManager
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
|
||||||
|
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
|
||||||
|
import com.google.android.exoplayer2.upstream.cache.SimpleCache
|
||||||
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
object ExoPlayerHelper {
|
||||||
|
private val context = MainActivity.mainContext
|
||||||
|
val databaseProvider = ExoDatabaseProvider(context)
|
||||||
|
val downloadExecutor = Executor { obj: Runnable -> obj.run() }
|
||||||
|
val dataSourceFactory = DefaultHttpDataSourceFactory()
|
||||||
|
|
||||||
|
val downloadCache: SimpleCache by lazy {
|
||||||
|
SimpleCache(
|
||||||
|
context.cacheDir,
|
||||||
|
LeastRecentlyUsedCacheEvictor(20 * 1024 * 1024),
|
||||||
|
databaseProvider
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val downloadManager: DownloadManager by lazy {
|
||||||
|
DownloadManager(context,
|
||||||
|
databaseProvider,
|
||||||
|
downloadCache,
|
||||||
|
dataSourceFactory,
|
||||||
|
downloadExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
app/src/main/res/drawable/.idea/misc.xml
Normal file
6
app/src/main/res/drawable/.idea/misc.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
app/src/main/res/drawable/.idea/modules.xml
Normal file
8
app/src/main/res/drawable/.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/drawable.iml" filepath="$PROJECT_DIR$/.idea/drawable.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
app/src/main/res/drawable/.idea/vcs.xml
Normal file
6
app/src/main/res/drawable/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/../../../../.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
24
app/src/main/res/drawable/rddone.xml
Normal file
24
app/src/main/res/drawable/rddone.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:name="vector"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorPrimary"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:name="path"
|
||||||
|
android:pathData="M 17 1.01 L 7 1 C 5.9 1 5 1.9 5 3 L 5 21 C 5 22.1 5.9 23 7 23 L 17 23 C 18.1 23 19 22.1 19 21 L 19 3 C 19 1.9 18.1 1.01 17 1.01 Z M 17 19 L 7 19 L 7 5 L 17 5 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
<path
|
||||||
|
android:name="path_1"
|
||||||
|
android:pathData="M 7.488 13.004 L 9.132 11.36 L 12.492 14.72 L 10.848 16.364 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
<path
|
||||||
|
android:name="path_2"
|
||||||
|
android:pathData="M 10.848 16.364 L 9.203 14.72 L 14.894 9.029 L 16.539 10.673 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
</vector>
|
24
app/src/main/res/drawable/rderror.xml
Normal file
24
app/src/main/res/drawable/rderror.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:name="vector"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorPrimary"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:name="path"
|
||||||
|
android:pathData="M 17 1.01 L 7 1 C 5.9 1 5 1.9 5 3 L 5 21 C 5 22.1 5.9 23 7 23 L 17 23 C 18.1 23 19 22.1 19 21 L 19 3 C 19 1.9 18.1 1.01 17 1.01 Z M 17 19 L 7 19 L 7 5 L 17 5 Z M 10.101 13.289 L 10.513 10.311 L 9.848 9.33 L 9.496 8.867 L 11 13 L 9.706 13 L 9.745 15.93 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
<path
|
||||||
|
android:name="path_1"
|
||||||
|
android:pathData="M 15.801 14.763 L 14.157 16.407 L 7.873 10.124 L 9.518 8.479 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
<path
|
||||||
|
android:name="path_2"
|
||||||
|
android:pathData="M 14.157 8.479 L 15.801 10.124 L 9.518 16.407 L 7.873 14.763 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/rdload.xml
Normal file
5
app/src/main/res/drawable/rdload.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="?attr/colorPrimary"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM16,13h-3L13,8h-2v5L8,13l4,4 4,-4z"/>
|
||||||
|
</vector>
|
19
app/src/main/res/drawable/rdpause.xml
Normal file
19
app/src/main/res/drawable/rdpause.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:name="vector"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorPrimary"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:name="path"
|
||||||
|
android:pathData="M 17 1.01 L 7 1 C 5.9 1 5 1.9 5 3 L 5 21 C 5 22.1 5.9 23 7 23 L 17 23 C 18.1 23 19 22.1 19 21 L 19 3 C 19 1.9 18.1 1.01 17 1.01 Z M 17 19 L 7 19 L 7 5 L 17 5 Z M 10.101 13.289 L 10.513 10.311 L 9.848 9.33 L 9.496 8.867 L 11 13 L 9.706 13 L 9.745 15.93 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
<path
|
||||||
|
android:name="path_1"
|
||||||
|
android:pathData="M 9.104 8.931 L 11.129 8.931 L 11.129 15.956 L 9.104 15.956 Z M 12.871 8.931 L 14.896 8.931 L 14.896 15.956 L 12.871 15.956 Z"
|
||||||
|
android:fillColor="@color/white"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
</vector>
|
Loading…
Reference in a new issue