download step 1

This commit is contained in:
LagradOst 2021-07-04 02:59:51 +02:00
parent 602cd065ce
commit 3210e71eca
5 changed files with 142 additions and 23 deletions

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3
import android.app.PictureInPictureParams
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
@ -9,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import com.anggrayudi.storage.SimpleStorage
import com.google.android.gms.cast.ApplicationMetadata
import com.google.android.gms.cast.Cast
import com.google.android.gms.cast.LaunchOptions
@ -37,9 +39,17 @@ class MainActivity : AppCompatActivity() {
var isInPlayer: Boolean = false
var canShowPipMode: Boolean = false
var isInPIPMode: Boolean = false
lateinit var mainContext : MainActivity
lateinit var mainContext: MainActivity
//https://github.com/anggrayudi/SimpleStorage/blob/4eb6306efb6cdfae4e34f170c8b9d4e135b04d51/sample/src/main/java/com/anggrayudi/storage/sample/activity/MainActivity.kt#L624
const val REQUEST_CODE_STORAGE_ACCESS = 1
const val REQUEST_CODE_PICK_FOLDER = 2
const val REQUEST_CODE_PICK_FILE = 3
const val REQUEST_CODE_ASK_PERMISSIONS = 4
}
private lateinit var storage: SimpleStorage
private fun enterPIPMode() {
if (!shouldShowPIPMode(isInPlayer) || !canShowPipMode) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -83,9 +93,32 @@ class MainActivity : AppCompatActivity() {
super.onBackPressed()
}
private fun setupSimpleStorage() {
storage = SimpleStorage(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Mandatory for Activity, but not for Fragment
storage.onActivityResult(requestCode, resultCode, data)
}
override fun onSaveInstanceState(outState: Bundle) {
storage.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
storage.onRestoreInstanceState(savedInstanceState)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainContext = this
setupSimpleStorage()
storage.requestStorageAccess(REQUEST_CODE_STORAGE_ACCESS)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
@ -100,8 +133,11 @@ class MainActivity : AppCompatActivity() {
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.navigation_home, R.id.navigation_search, R.id.navigation_notifications))
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_search, R.id.navigation_notifications
)
)
//setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)

View file

@ -151,7 +151,7 @@ class EpisodeAdapter(
}
} else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card)) //TODO REDO TO MAIN
}
}
}

View file

@ -134,6 +134,7 @@ class ResultFragment : Fragment() {
private lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
private var currentHeaderName: String? = null
private var currentType: TvType? = null
private var currentEpisodes: List<ResultEpisode>? = null
override fun onCreateView(
@ -341,18 +342,30 @@ class ResultFragment : Fragment() {
if (tempUrl != null) {
viewModel.loadEpisode(episodeClick.data, true) { data ->
if (data is Resource.Success) {
val isMovie = currentIsMovie ?: return@loadEpisode
val titleName = currentHeaderName?: return@loadEpisode
val meta = VideoDownloadManager.DownloadEpisodeMetadata(
episodeClick.data.id,
currentHeaderName ?: return@loadEpisode,
titleName ,
apiName ?: return@loadEpisode,
episodeClick.data.poster,
episodeClick.data.poster ?: currentPoster,
episodeClick.data.name,
episodeClick.data.season,
episodeClick.data.episode
if (isMovie) null else episodeClick.data.season,
if (isMovie) null else episodeClick.data.episode
)
val folder = when (currentType) {
TvType.Anime -> "Anime/$titleName"
TvType.Movie -> "Movies"
TvType.TvSeries -> "TVSeries/$titleName"
TvType.ONA -> "ONA"
else -> null
}
VideoDownloadManager.DownloadEpisode(
requireContext(),
tempUrl,
folder,
meta,
data.value.links
)
@ -435,6 +448,7 @@ class ResultFragment : Fragment() {
result_bookmark_button.text = "Watching"
currentHeaderName = d.name
currentType = d.type
currentPoster = d.posterUrl
currentIsMovie = !d.isEpisodeBased()
@ -610,10 +624,15 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
if (castContext.castState == CastState.CONNECTED) {
handleAction(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
} else {
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
handleAction(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
}
} else {
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
handleAction(
EpisodeClickEvent(
ACTION_DOWNLOAD_EPISODE,
card
)
) //TODO REDO TO MAIN
}
}
}

View file

@ -19,12 +19,14 @@ import com.google.android.exoplayer2.offline.DownloadService
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.Coroutines.main
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedInputStream
import java.io.InputStream
import java.net.URL
import java.net.URLConnection
const val CHANNEL_ID = "cloudstream3.general"
const val CHANNEL_NAME = "Downloads"
const val CHANNEL_DESCRIPT = "The download notification channel"
@ -268,25 +270,47 @@ object VideoDownloadManager {
}
}
private const val reservedChars = "|\\?*<\":>+[]/'"
private fun sanitizeFilename(name: String): String {
var tempName = name
for (c in reservedChars) {
tempName = tempName.replace(c, ' ')
}
return tempName.replace(" ", " ")
}
fun DownloadSingleEpisode(
context: Context,
source: String?,
folder: 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
val path = sanitizeFilename("Download/${if (folder == null) "" else "$folder/"}$name")
var resume = false
val resume = false
// IF RESUME, DELETE FILE IF FOUND AND RECREATE
// IF NOT RESUME CREATE FILE
val tempFile = DocumentFileCompat.fromSimplePath(context, basePath = path)
val fileExists = tempFile?.exists() ?: false
if (!resume && dFile.exists()) {
if (!dFile.delete()) {
if (!fileExists) resume = false
if (fileExists && !resume) {
if (tempFile?.delete() == false) { // DELETE FAILED ON RESUME FILE
return false
}
}
if (!dFile.exists()) {
dFile.createFile("video/mp4", name)
val dFile =
if (resume) tempFile
else DocumentFileCompat.createFile(context, basePath = path, mimeType = "video/mp4")
// END OF FILE CREATION
if (dFile == null) {
println("FUCK YOU")
return false
}
// OPEN FILE
@ -306,8 +330,16 @@ object VideoDownloadManager {
// ON CONNECTION
connection.connect()
val contentLength = connection.contentLength
if (contentLength < 5000000) return false // less than 5mb
val bytesTotal = contentLength + resumeLength
if (bytesTotal < 5000000) return false // DATA IS LESS THAN 5MB, SOMETHING IS WRONG
// Could use connection.contentType for mime types when creating the file,
// however file is already created and players don't go of file type
// https://stackoverflow.com/questions/23714383/what-are-all-the-possible-values-for-http-content-type-header
if(!connection.contentType.isNullOrEmpty() && !connection.contentType.startsWith("video")) {
return false // CONTENT IS NOT VIDEO, SHOULD NEVER HAPPENED, BUT JUST IN CASE
}
// READ DATA FROM CONNECTION
val connectionInputStream: InputStream = BufferedInputStream(connection.inputStream)
@ -315,7 +347,8 @@ object VideoDownloadManager {
var count: Int
var bytesDownloaded = resumeLength
fun updateNotification(type : DownloadType) {
// TO NOT REUSE CODE
fun updateNotification(type: DownloadType) {
createNotification(
context,
source,
@ -327,7 +360,7 @@ object VideoDownloadManager {
)
}
while (true) {
while (true) { // TODO PAUSE
count = connectionInputStream.read(buffer)
if (count < 0) break
bytesDownloaded += count
@ -344,10 +377,25 @@ object VideoDownloadManager {
return true
}
public fun DownloadEpisode(context: Context, source: String, ep: DownloadEpisodeMetadata, links: List<ExtractorLink>) {
public fun DownloadEpisode(
context: Context,
source: String,
folder: String?,
ep: DownloadEpisodeMetadata,
links: List<ExtractorLink>
) {
val validLinks = links.filter { !it.isM3u8 }
if (validLinks.isNotEmpty()) {
DownloadSingleEpisode(context, source, ep, validLinks.first())
try {
main {
withContext(Dispatchers.IO) {
DownloadSingleEpisode(context, source, folder, ep, validLinks.first())
}
}
} catch (e: Exception) {
println(e)
e.printStackTrace()
}
}
}
}

View file

@ -0,0 +1,16 @@
package com.lagradost.cloudstream3.utils
import android.app.IntentService
import android.content.Intent
class VideoDownloadService : IntentService("DownloadService") {
override fun onHandleIntent(intent: Intent?) {
if (intent != null) {
val id = intent.getIntExtra("id", -1)
val type = intent.getStringExtra("type")
if (id != -1 && type != null) {
}
}
}
}