download stuff

This commit is contained in:
LagradOst 2021-07-17 17:56:26 +02:00
parent b5f913cc72
commit bd34c66592
6 changed files with 261 additions and 138 deletions

View file

@ -5,6 +5,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
@ -21,6 +22,9 @@ import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
import com.lagradost.cloudstream3.services.RESTART_ALL_DOWNLOADS_AND_QUEUE
import com.lagradost.cloudstream3.services.START_VALUE_KEY
import com.lagradost.cloudstream3.services.VideoDownloadKeepAliveService
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.getKeys
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.fragment_result.*
@ -126,7 +130,7 @@ class MainActivity : AppCompatActivity() {
mainContext = this
setupSimpleStorage()
if(!storage.isStorageAccessGranted(StorageId.PRIMARY)) {
if (!storage.isStorageAccessGranted(StorageId.PRIMARY)) {
storage.requestStorageAccess(REQUEST_CODE_STORAGE_ACCESS)
}
@ -163,7 +167,29 @@ class MainActivity : AppCompatActivity() {
// val mServiceIntent = Intent(this, mYourService::class.java).putExtra(START_VALUE_KEY, RESTART_ALL_DOWNLOADS_AND_QUEUE)
// this.startService(mServiceIntent)
//}
//settingsManager.getBoolean("disable_automatic_data_downloads", true) &&
if ( isUsingMobileData()) {
Toast.makeText(this, "Downloads not resumed on mobile data", Toast.LENGTH_LONG).show()
} else {
val keys = getKeys(VideoDownloadManager.KEY_RESUME_PACKAGES)
val resumePkg = keys.mapNotNull { k -> getKey<VideoDownloadManager.DownloadResumePackage>(k) }
// To remove a bug where this is permanent
removeKeys(VideoDownloadManager.KEY_RESUME_PACKAGES)
for (pkg in resumePkg) { // ADD ALL CURRENT DOWNLOADS
VideoDownloadManager.downloadFromResume(this, pkg, false)
}
// ADD QUEUE
// array needed because List gets cast exception to linkedList for some unknown reason
val resumeQueue =
getKey<Array<VideoDownloadManager.DownloadQueueResumePackage>>(VideoDownloadManager.KEY_RESUME_QUEUE_PACKAGES)
resumeQueue?.sortedBy { it.index }?.forEach {
VideoDownloadManager.downloadFromResume(this, it.pkg)
}
}
/*
val castContext = CastContext.getSharedInstance(applicationContext)
fun buildMediaQueueItem(video: String): MediaQueueItem {

View file

@ -76,6 +76,7 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.VIDEO_PLAYER_BRIGHTNESS
import com.lagradost.cloudstream3.utils.getId
import kotlinx.android.synthetic.main.fragment_player.*
import kotlinx.android.synthetic.main.player_custom_layout.*
@ -413,6 +414,8 @@ class PlayerFragment : Fragment() {
) // 0.05f *if (diffY > 0) 1 else -1
brightness_overlay?.alpha = alpha
context?.setKey(VIDEO_PLAYER_BRIGHTNESS, alpha)
progressBarRight?.max = 100 * 100
progressBarRight?.progress = ((1f - alpha) * 100 * 100).toInt()
}
@ -764,6 +767,8 @@ class PlayerFragment : Fragment() {
playerResizeEnabled = settingsManager.getBoolean("player_resize_enabled", true)
doubleTapEnabled = settingsManager.getBoolean("double_tap_enabled", false)
brightness_overlay?.alpha = context?.getKey(VIDEO_PLAYER_BRIGHTNESS, 0f) ?: 0f
isInPlayer = true // NEED REFERENCE TO MAIN ACTIVITY FOR PIP
navigationBarHeight = requireContext().getNavigationBarHeight()

View file

@ -21,6 +21,7 @@ import com.google.android.gms.cast.framework.CastState
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import com.lagradost.cloudstream3.UIHelper.isConnectedToChromecast
import com.lagradost.cloudstream3.utils.getId
import kotlinx.android.synthetic.main.result_episode.view.episode_holder
import kotlinx.android.synthetic.main.result_episode.view.episode_text

View file

@ -1,8 +1,9 @@
package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.*
import android.content.Context.CLIPBOARD_SERVICE
import android.content.Intent.*
import android.net.Uri
import android.os.Bundle
import android.text.SpannableStringBuilder
@ -16,6 +17,8 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.FileProvider
import androidx.core.text.color
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
@ -31,6 +34,7 @@ import com.google.android.gms.cast.framework.CastState
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.UIHelper.checkWrite
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight
@ -39,6 +43,7 @@ import com.lagradost.cloudstream3.UIHelper.isConnectedToChromecast
import com.lagradost.cloudstream3.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIconsAndNoStringres
import com.lagradost.cloudstream3.UIHelper.requestRW
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.WatchType
@ -54,6 +59,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.coroutines.Job
import java.io.File
const val MAX_SYNO_LENGH = 300
@ -268,13 +274,16 @@ class ResultFragment : Fragment() {
var currentLinks: ArrayList<ExtractorLink>? = null
var currentSubs: ArrayList<SubtitleFile>? = null
suspend fun requireLinks(isCasting: Boolean = false): Boolean {
val showTitle = episodeClick.data.name ?: "Episode ${episodeClick.data.episode}"
suspend fun requireLinks(isCasting: Boolean): Boolean {
val currentLinksTemp =
if (allEpisodes.containsKey(episodeClick.data.id)) allEpisodes[episodeClick.data.id] else null
val currentSubsTemp =
if (allEpisodes.containsKey(episodeClick.data.id)) allEpisodes[episodeClick.data.id] else null
if (allEpisodesSubs.containsKey(episodeClick.data.id)) allEpisodesSubs[episodeClick.data.id] else null
if (currentLinksTemp != null && currentLinksTemp.size > 0) {
currentLinks = currentLinksTemp
currentSubs = currentSubsTemp
return true
}
@ -318,100 +327,45 @@ class ResultFragment : Fragment() {
return false
}
val isLoaded = when (episodeClick.action) {
ACTION_PLAY_EPISODE_IN_PLAYER -> true
ACTION_CHROME_CAST_EPISODE -> requireLinks(true)
ACTION_CHROME_CAST_MIRROR -> requireLinks(true)
else -> requireLinks()
}
if (!isLoaded) return@main // CANT LOAD
when (episodeClick.action) {
ACTION_SHOW_OPTIONS -> {
fun aquireSingeExtractorLink(links: List<ExtractorLink>, title: String, callback: (ExtractorLink) -> Unit) {
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
var dialog: AlertDialog? = null
builder.setTitle(episodeClick.data.name)
val options = requireContext().resources.getStringArray(R.array.episode_long_click_options)
val optionsValues =
requireContext().resources.getIntArray(R.array.episode_long_click_options_values)
val verifiedOptions = ArrayList<String>()
val verifiedOptionsValues = ArrayList<Int>()
for (i in options.indices) {
val opv = optionsValues[i]
val op = options[i]
val isConnected = requireContext().isConnectedToChromecast()
val add = when (opv) {
ACTION_CHROME_CAST_EPISODE -> isConnected
ACTION_CHROME_CAST_MIRROR -> isConnected
else -> true
}
if (add) {
verifiedOptions.add(op)
verifiedOptionsValues.add(opv)
builder.setTitle(title)
builder.setItems(links.map { it.name }.toTypedArray()) { dia, which ->
callback.invoke(links[which])
dia?.dismiss()
}
builder.create().show()
}
builder.setItems(
verifiedOptions.toTypedArray()
) { _, which ->
handleAction(EpisodeClickEvent(verifiedOptionsValues[which], episodeClick.data))
dialog?.dismiss()
fun aquireSingeExtractorLink(title: String, callback: (ExtractorLink) -> Unit) {
aquireSingeExtractorLink(currentLinks ?: return, title, callback)
}
dialog = builder.create()
dialog.show()
}
ACTION_CHROME_CAST_EPISODE -> {
val eps = currentEpisodes ?: return@main
fun startChromecast(startIndex: Int) {
val eps = currentEpisodes ?: return
context?.startCast(
apiName ?: return@main,
currentIsMovie ?: return@main,
apiName ?: return,
currentIsMovie ?: return,
currentHeaderName,
currentPoster,
episodeClick.data.index,
eps,
sortUrls(currentLinks ?: return@main),
currentSubs ?: return@main,
sortUrls(currentLinks ?: return),
currentSubs ?: return,
startTime = episodeClick.data.getRealPosition(),
startIndex = startIndex
)
}
ACTION_PLAY_EPISODE_IN_PLAYER -> {
if (buildInPlayer) {
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit
)
.add(
R.id.homeRoot,
PlayerFragment.newInstance(
PlayerData(index, null, 0),
episodeClick.data.getRealPosition()
)
)
.commit()
}
}
ACTION_RELOAD_EPISODE -> {
viewModel.loadEpisode(episodeClick.data, false)
}
ACTION_DOWNLOAD_EPISODE -> {
val isMovie = currentIsMovie ?: return@main
val titleName = sanitizeFilename(currentHeaderName ?: return@main)
fun startDownload(links: List<ExtractorLink>) {
val isMovie = currentIsMovie ?: return
val titleName = sanitizeFilename(currentHeaderName ?: return)
val meta = VideoDownloadManager.DownloadEpisodeMetadata(
episodeClick.data.id,
titleName,
apiName ?: return@main,
apiName ?: return,
episodeClick.data.poster ?: currentPoster,
episodeClick.data.name,
if (isMovie) null else episodeClick.data.season,
@ -459,13 +413,188 @@ class ResultFragment : Fragment() {
// DOWNLOAD VIDEO
VideoDownloadManager.downloadEpisode(
ctx,
url ?: return@main,
url ?: return,
folder,
meta,
currentLinks ?: return@main
links
)
}
}
val isLoaded = when (episodeClick.action) {
ACTION_PLAY_EPISODE_IN_PLAYER -> true
ACTION_CHROME_CAST_EPISODE -> requireLinks(true)
ACTION_CHROME_CAST_MIRROR -> requireLinks(true)
else -> requireLinks(false)
}
if (!isLoaded) return@main // CANT LOAD
when (episodeClick.action) {
ACTION_SHOW_OPTIONS -> {
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
var dialog: AlertDialog? = null
builder.setTitle(showTitle)
val options = requireContext().resources.getStringArray(R.array.episode_long_click_options)
val optionsValues =
requireContext().resources.getIntArray(R.array.episode_long_click_options_values)
val verifiedOptions = ArrayList<String>()
val verifiedOptionsValues = ArrayList<Int>()
for (i in options.indices) {
val opv = optionsValues[i]
val op = options[i]
val isConnected = requireContext().isConnectedToChromecast()
val add = when (opv) {
ACTION_CHROME_CAST_EPISODE -> isConnected
ACTION_CHROME_CAST_MIRROR -> isConnected
else -> true
}
if (add) {
verifiedOptions.add(op)
verifiedOptionsValues.add(opv)
}
}
builder.setItems(
verifiedOptions.toTypedArray()
) { _, which ->
handleAction(EpisodeClickEvent(verifiedOptionsValues[which], episodeClick.data))
dialog?.dismiss()
}
dialog = builder.create()
dialog.show()
}
ACTION_COPY_LINK -> {
aquireSingeExtractorLink("Copy Link") { link ->
val serviceClipboard =
(requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager?)
?: return@aquireSingeExtractorLink
val clip = ClipData.newPlainText(link.name, link.url)
serviceClipboard.setPrimaryClip(clip)
Toast.makeText(requireContext(), "Text Copied", Toast.LENGTH_SHORT).show()
}
}
ACTION_PLAY_EPISODE_IN_BROWSER -> {
aquireSingeExtractorLink("Play in Browser") { link ->
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(link.url)
startActivity(i)
}
}
ACTION_CHROME_CAST_MIRROR -> {
aquireSingeExtractorLink("Cast Mirror") { link ->
val mirrorIndex = currentLinks?.indexOf(link) ?: -1
startChromecast(if (mirrorIndex == -1) 0 else mirrorIndex)
}
}
ACTION_CHROME_CAST_EPISODE -> {
startChromecast(0)
}
ACTION_PLAY_EPISODE_IN_EXTERNAL_PLAYER -> {
if (activity?.checkWrite() != true) {
activity?.requestRW()
if (activity?.checkWrite() == true) return@main
}
val data = currentLinks ?: return@main
val subs = currentSubs
val outputDir = requireContext().cacheDir
val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir)
var text = "#EXTM3U"
if (subs != null) {
for (sub in subs) {
text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.lang}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.lang}\",URI=\"${sub.url}\""
}
}
for (link in data.sortedBy { -it.quality }) {
text += "\n#EXTINF:, ${link.name}\n${link.url}"
}
outputFile.writeText(text)
val VLC_PACKAGE = "org.videolan.vlc"
val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
val VLC_COMPONENT: ComponentName =
ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity")
val REQUEST_CODE = 42
val FROM_START = -1
val FROM_PROGRESS = -2
val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
vlcIntent.setPackage(VLC_PACKAGE)
vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION)
vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION)
vlcIntent.setDataAndType(
FileProvider.getUriForFile(
requireActivity(),
requireActivity().applicationContext.packageName + ".provider",
outputFile
), "video/*"
)
val startId = FROM_PROGRESS
var position = startId
if (startId == FROM_START) {
position = 1
} else if (startId == FROM_PROGRESS) {
position = 0
}
vlcIntent.putExtra("position", position)
vlcIntent.component = VLC_COMPONENT
activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
}
ACTION_PLAY_EPISODE_IN_PLAYER -> {
if (buildInPlayer) {
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit
)
.add(
R.id.homeRoot,
PlayerFragment.newInstance(
PlayerData(index, null, 0),
episodeClick.data.getRealPosition()
)
)
.commit()
}
}
ACTION_RELOAD_EPISODE -> {
viewModel.loadEpisode(episodeClick.data, false)
}
ACTION_DOWNLOAD_EPISODE -> {
startDownload(currentLinks ?: return@main)
}
ACTION_DOWNLOAD_MIRROR -> {
aquireSingeExtractorLink(
(currentLinks ?: return@main).filter { !it.isM3u8 },
"Download Mirror"
) { link ->
startDownload(listOf(link))
}
}
}
}
@ -620,63 +749,6 @@ class ResultFragment : Fragment() {
}
}
fun playEpisode(data: ArrayList<ExtractorLink>?, episodeIndex: Int) {
if (data != null) {
/*
if (activity?.checkWrite() != true) {
activity?.requestRW()
if (activity?.checkWrite() == true) return
}
val outputDir = context!!.cacheDir
val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir)
var text = "#EXTM3U";
for (link in data.sortedBy { -it.quality }) {
text += "\n#EXTINF:, ${link.name}\n${link.url}"
}
outputFile.writeText(text)
val VLC_PACKAGE = "org.videolan.vlc"
val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
val VLC_COMPONENT: ComponentName =
ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity")
val REQUEST_CODE = 42
val FROM_START = -1
val FROM_PROGRESS = -2
val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
vlcIntent.setPackage(VLC_PACKAGE)
vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION)
vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION)
vlcIntent.setDataAndType(FileProvider.getUriForFile(activity!!,
activity!!.applicationContext.packageName + ".provider",
outputFile), "video/*")
val startId = FROM_PROGRESS
var position = startId
if (startId == FROM_START) {
position = 1
} else if (startId == FROM_PROGRESS) {
position = 0
}
vlcIntent.putExtra("position", position)
//vlcIntent.putExtra("title", episodeName)
vlcIntent.setComponent(VLC_COMPONENT)
activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
*/
*/
}
}
if (d.plot != null) {
var syno = d.plot!!
if (syno.length > MAX_SYNO_LENGH) {

View file

@ -8,6 +8,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha"
const val PREFERENCES_NAME: String = "rebuild_preference"

View file

@ -10,6 +10,7 @@ import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
@ -638,8 +639,6 @@ object VideoDownloadManager {
return
}
val dQueue = downloadQueue.toList().mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
currentDownloads.add(id)
main {
@ -729,9 +728,28 @@ object VideoDownloadManager {
return context.getKey(KEY_RESUME_PACKAGES, id.toString())
}
fun downloadFromResume(context: Context, pkg: DownloadResumePackage) {
fun downloadFromResume(context: Context, pkg: DownloadResumePackage, setKey: Boolean = true) {
if (!currentDownloads.any { it == pkg.item.ep.id }) {
if (currentDownloads.size == maxConcurrentDownloads) {
main {
Toast.makeText(
context,
"${pkg.item.ep.mainName}${pkg.item.ep.episode?.let { " Episode $it " } ?: " "}queued",
Toast.LENGTH_SHORT
).show()
}
}
downloadQueue.addLast(pkg)
downloadCheck(context)
if (setKey) saveQueue(context)
}
}
private fun saveQueue(context: Context) {
val dQueue =
downloadQueue.toList().mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
.toTypedArray()
context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
}
fun isMyServiceRunning(context: Context, serviceClass: Class<*>): Boolean {