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.app.PictureInPictureParams
import android.content.Context import android.content.Context
import android.content.Intent
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
@ -9,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.anggrayudi.storage.SimpleStorage
import com.google.android.gms.cast.ApplicationMetadata import com.google.android.gms.cast.ApplicationMetadata
import com.google.android.gms.cast.Cast import com.google.android.gms.cast.Cast
import com.google.android.gms.cast.LaunchOptions import com.google.android.gms.cast.LaunchOptions
@ -38,8 +40,16 @@ class MainActivity : AppCompatActivity() {
var canShowPipMode: Boolean = false var canShowPipMode: Boolean = false
var isInPIPMode: 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() { private fun enterPIPMode() {
if (!shouldShowPIPMode(isInPlayer) || !canShowPipMode) return if (!shouldShowPIPMode(isInPlayer) || !canShowPipMode) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -83,9 +93,32 @@ class MainActivity : AppCompatActivity() {
super.onBackPressed() 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mainContext = this mainContext = this
setupSimpleStorage()
storage.requestStorageAccess(REQUEST_CODE_STORAGE_ACCESS)
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)
@ -100,8 +133,11 @@ class MainActivity : AppCompatActivity() {
val navController = findNavController(R.id.nav_host_fragment) val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each // Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations. // menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(setOf( val appBarConfiguration = AppBarConfiguration(
R.id.navigation_home, R.id.navigation_search, R.id.navigation_notifications)) setOf(
R.id.navigation_home, R.id.navigation_search, R.id.navigation_notifications
)
)
//setupActionBarWithNavController(navController, appBarConfiguration) //setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController) navView.setupWithNavController(navController)

View file

@ -151,7 +151,7 @@ class EpisodeAdapter(
} }
} 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)) 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 lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap() private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
private var currentHeaderName: String? = null private var currentHeaderName: String? = null
private var currentType: TvType? = null
private var currentEpisodes: List<ResultEpisode>? = null private var currentEpisodes: List<ResultEpisode>? = null
override fun onCreateView( override fun onCreateView(
@ -341,18 +342,30 @@ 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 isMovie = currentIsMovie ?: return@loadEpisode
val titleName = currentHeaderName?: return@loadEpisode
val meta = VideoDownloadManager.DownloadEpisodeMetadata( val meta = VideoDownloadManager.DownloadEpisodeMetadata(
episodeClick.data.id, episodeClick.data.id,
currentHeaderName ?: return@loadEpisode, titleName ,
apiName ?: return@loadEpisode, apiName ?: return@loadEpisode,
episodeClick.data.poster, episodeClick.data.poster ?: currentPoster,
episodeClick.data.name, episodeClick.data.name,
episodeClick.data.season, if (isMovie) null else episodeClick.data.season,
episodeClick.data.episode 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( VideoDownloadManager.DownloadEpisode(
requireContext(), requireContext(),
tempUrl, tempUrl,
folder,
meta, meta,
data.value.links data.value.links
) )
@ -435,6 +448,7 @@ class ResultFragment : Fragment() {
result_bookmark_button.text = "Watching" result_bookmark_button.text = "Watching"
currentHeaderName = d.name currentHeaderName = d.name
currentType = d.type
currentPoster = d.posterUrl currentPoster = d.posterUrl
currentIsMovie = !d.isEpisodeBased() currentIsMovie = !d.isEpisodeBased()
@ -610,10 +624,15 @@ activity?.startActivityForResult(vlcIntent, REQUEST_CODE)
if (castContext.castState == CastState.CONNECTED) { if (castContext.castState == CastState.CONNECTED) {
handleAction(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card)) handleAction(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
} else { } else {
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card)) handleAction(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
} }
} else { } 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.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.utils.Coroutines.main
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.InputStream import java.io.InputStream
import java.net.URL import java.net.URL
import java.net.URLConnection import java.net.URLConnection
const val CHANNEL_ID = "cloudstream3.general" const val CHANNEL_ID = "cloudstream3.general"
const val CHANNEL_NAME = "Downloads" const val CHANNEL_NAME = "Downloads"
const val CHANNEL_DESCRIPT = "The download notification channel" 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( fun DownloadSingleEpisode(
context: Context, context: Context,
source: String?, source: String?,
folder: String?,
ep: DownloadEpisodeMetadata, ep: DownloadEpisodeMetadata,
link: ExtractorLink link: ExtractorLink
): Boolean { ): Boolean {
val name = (ep.name ?: "Episode ${ep.episode}") val name = (ep.name ?: "Episode ${ep.episode}")
val path = "Downloads/Anime/$name.mp4" val path = sanitizeFilename("Download/${if (folder == null) "" else "$folder/"}$name")
val dFile = DocumentFileCompat.fromSimplePath(context, basePath = path) ?: return false 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 (!fileExists) resume = false
if (!dFile.delete()) { if (fileExists && !resume) {
if (tempFile?.delete() == false) { // DELETE FAILED ON RESUME FILE
return false 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 // OPEN FILE
@ -306,8 +330,16 @@ object VideoDownloadManager {
// ON CONNECTION // ON CONNECTION
connection.connect() connection.connect()
val contentLength = connection.contentLength val contentLength = connection.contentLength
if (contentLength < 5000000) return false // less than 5mb
val bytesTotal = contentLength + resumeLength 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 // READ DATA FROM CONNECTION
val connectionInputStream: InputStream = BufferedInputStream(connection.inputStream) val connectionInputStream: InputStream = BufferedInputStream(connection.inputStream)
@ -315,6 +347,7 @@ object VideoDownloadManager {
var count: Int var count: Int
var bytesDownloaded = resumeLength var bytesDownloaded = resumeLength
// TO NOT REUSE CODE
fun updateNotification(type: DownloadType) { fun updateNotification(type: DownloadType) {
createNotification( createNotification(
context, context,
@ -327,7 +360,7 @@ object VideoDownloadManager {
) )
} }
while (true) { while (true) { // TODO PAUSE
count = connectionInputStream.read(buffer) count = connectionInputStream.read(buffer)
if (count < 0) break if (count < 0) break
bytesDownloaded += count bytesDownloaded += count
@ -344,10 +377,25 @@ object VideoDownloadManager {
return true 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 } val validLinks = links.filter { !it.isM3u8 }
if (validLinks.isNotEmpty()) { 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) {
}
}
}
}