first working version of anime downloads
This commit is contained in:
parent
41830dba4d
commit
d16fbd9a43
19 changed files with 402 additions and 156 deletions
|
@ -273,7 +273,7 @@
|
|||
android:permission="android.permission.BIND_REMOTEVIEWS"
|
||||
android:exported="true" />
|
||||
<service
|
||||
android:name=".download.video.MyDownloadService"
|
||||
android:name=".download.video.ExoplayerDownloadService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync">
|
||||
<intent-filter>
|
||||
|
|
|
@ -15,43 +15,43 @@ class DownloadsManager(private val context: Context) {
|
|||
private val gson = Gson()
|
||||
private val downloadsList = loadDownloads().toMutableList()
|
||||
|
||||
val mangaDownloads: List<Download>
|
||||
get() = downloadsList.filter { it.type == Download.Type.MANGA }
|
||||
val animeDownloads: List<Download>
|
||||
get() = downloadsList.filter { it.type == Download.Type.ANIME }
|
||||
val novelDownloads: List<Download>
|
||||
get() = downloadsList.filter { it.type == Download.Type.NOVEL }
|
||||
val mangaDownloadedTypes: List<DownloadedType>
|
||||
get() = downloadsList.filter { it.type == DownloadedType.Type.MANGA }
|
||||
val animeDownloadedTypes: List<DownloadedType>
|
||||
get() = downloadsList.filter { it.type == DownloadedType.Type.ANIME }
|
||||
val novelDownloadedTypes: List<DownloadedType>
|
||||
get() = downloadsList.filter { it.type == DownloadedType.Type.NOVEL }
|
||||
|
||||
private fun saveDownloads() {
|
||||
val jsonString = gson.toJson(downloadsList)
|
||||
prefs.edit().putString("downloads_key", jsonString).apply()
|
||||
}
|
||||
|
||||
private fun loadDownloads(): List<Download> {
|
||||
private fun loadDownloads(): List<DownloadedType> {
|
||||
val jsonString = prefs.getString("downloads_key", null)
|
||||
return if (jsonString != null) {
|
||||
val type = object : TypeToken<List<Download>>() {}.type
|
||||
val type = object : TypeToken<List<DownloadedType>>() {}.type
|
||||
gson.fromJson(jsonString, type)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun addDownload(download: Download) {
|
||||
downloadsList.add(download)
|
||||
fun addDownload(downloadedType: DownloadedType) {
|
||||
downloadsList.add(downloadedType)
|
||||
saveDownloads()
|
||||
}
|
||||
|
||||
fun removeDownload(download: Download) {
|
||||
downloadsList.remove(download)
|
||||
removeDirectory(download)
|
||||
fun removeDownload(downloadedType: DownloadedType) {
|
||||
downloadsList.remove(downloadedType)
|
||||
removeDirectory(downloadedType)
|
||||
saveDownloads()
|
||||
}
|
||||
|
||||
fun removeMedia(title: String, type: Download.Type) {
|
||||
val subDirectory = if (type == Download.Type.MANGA) {
|
||||
fun removeMedia(title: String, type: DownloadedType.Type) {
|
||||
val subDirectory = if (type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (type == Download.Type.ANIME) {
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
|
@ -76,16 +76,16 @@ class DownloadsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
private fun cleanDownloads() {
|
||||
cleanDownload(Download.Type.MANGA)
|
||||
cleanDownload(Download.Type.ANIME)
|
||||
cleanDownload(Download.Type.NOVEL)
|
||||
cleanDownload(DownloadedType.Type.MANGA)
|
||||
cleanDownload(DownloadedType.Type.ANIME)
|
||||
cleanDownload(DownloadedType.Type.NOVEL)
|
||||
}
|
||||
|
||||
private fun cleanDownload(type: Download.Type) {
|
||||
private fun cleanDownload(type: DownloadedType.Type) {
|
||||
// remove all folders that are not in the downloads list
|
||||
val subDirectory = if (type == Download.Type.MANGA) {
|
||||
val subDirectory = if (type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (type == Download.Type.ANIME) {
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
|
@ -94,18 +94,18 @@ class DownloadsManager(private val context: Context) {
|
|||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$subDirectory"
|
||||
)
|
||||
val downloadsSubList = if (type == Download.Type.MANGA) {
|
||||
mangaDownloads
|
||||
} else if (type == Download.Type.ANIME) {
|
||||
animeDownloads
|
||||
val downloadsSubLists = if (type == DownloadedType.Type.MANGA) {
|
||||
mangaDownloadedTypes
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
animeDownloadedTypes
|
||||
} else {
|
||||
novelDownloads
|
||||
novelDownloadedTypes
|
||||
}
|
||||
if (directory.exists()) {
|
||||
val files = directory.listFiles()
|
||||
if (files != null) {
|
||||
for (file in files) {
|
||||
if (!downloadsSubList.any { it.title == file.name }) {
|
||||
if (!downloadsSubLists.any { it.title == file.name }) {
|
||||
val deleted = file.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ class DownloadsManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun saveDownloadsListToJSONFileInDownloadsFolder(downloadsList: List<Download>) //for debugging
|
||||
fun saveDownloadsListToJSONFileInDownloadsFolder(downloadsList: List<DownloadedType>) //for debugging
|
||||
{
|
||||
val jsonString = gson.toJson(downloadsList)
|
||||
val file = File(
|
||||
|
@ -138,25 +138,33 @@ class DownloadsManager(private val context: Context) {
|
|||
file.writeText(jsonString)
|
||||
}
|
||||
|
||||
fun queryDownload(download: Download): Boolean {
|
||||
return downloadsList.contains(download)
|
||||
fun queryDownload(downloadedType: DownloadedType): Boolean {
|
||||
return downloadsList.contains(downloadedType)
|
||||
}
|
||||
|
||||
private fun removeDirectory(download: Download) {
|
||||
val directory = if (download.type == Download.Type.MANGA) {
|
||||
fun queryDownload(title: String, chapter: String, type: DownloadedType.Type? = null): Boolean {
|
||||
return if (type == null) {
|
||||
downloadsList.any { it.title == title && it.chapter == chapter }
|
||||
} else {
|
||||
downloadsList.any { it.title == title && it.chapter == chapter && it.type == type }
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeDirectory(downloadedType: DownloadedType) {
|
||||
val directory = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${download.title}/${download.chapter}"
|
||||
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else if (download.type == Download.Type.ANIME) {
|
||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Anime/${download.title}/${download.chapter}"
|
||||
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${download.title}/${download.chapter}"
|
||||
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -173,26 +181,26 @@ class DownloadsManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun exportDownloads(download: Download) { //copies to the downloads folder available to the user
|
||||
val directory = if (download.type == Download.Type.MANGA) {
|
||||
fun exportDownloads(downloadedType: DownloadedType) { //copies to the downloads folder available to the user
|
||||
val directory = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${download.title}/${download.chapter}"
|
||||
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else if (download.type == Download.Type.ANIME) {
|
||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Anime/${download.title}/${download.chapter}"
|
||||
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${download.title}/${download.chapter}"
|
||||
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
val destination = File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/${download.title}/${download.chapter}"
|
||||
"Dantotsu/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
if (directory.exists()) {
|
||||
val copied = directory.copyRecursively(destination, true)
|
||||
|
@ -206,10 +214,10 @@ class DownloadsManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun purgeDownloads(type: Download.Type) {
|
||||
val directory = if (type == Download.Type.MANGA) {
|
||||
fun purgeDownloads(type: DownloadedType.Type) {
|
||||
val directory = if (type == DownloadedType.Type.MANGA) {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
|
||||
} else if (type == Download.Type.ANIME) {
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
|
||||
} else {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel")
|
||||
|
@ -237,7 +245,7 @@ class DownloadsManager(private val context: Context) {
|
|||
|
||||
}
|
||||
|
||||
data class Download(val title: String, val chapter: String, val type: Type) : Serializable {
|
||||
data class DownloadedType(val title: String, val chapter: String, val type: Type) : Serializable {
|
||||
enum class Type {
|
||||
MANGA,
|
||||
ANIME,
|
||||
|
|
|
@ -8,7 +8,6 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.IBinder
|
||||
|
@ -18,15 +17,15 @@ import androidx.core.app.NotificationCompat
|
|||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.offline.Download
|
||||
import androidx.media3.exoplayer.offline.DownloadManager
|
||||
import androidx.media3.exoplayer.offline.DownloadService
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.currActivity
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||
import ani.dantotsu.download.anime.AnimeServiceDataSingleton
|
||||
import ani.dantotsu.download.video.Helper
|
||||
import ani.dantotsu.download.video.MyDownloadService
|
||||
import ani.dantotsu.download.video.ExoplayerDownloadService
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.anime.AnimeWatchFragment
|
||||
|
@ -44,14 +43,11 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
|||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
@ -161,7 +157,7 @@ class AnimeDownloaderService : Service() {
|
|||
val url = AnimeServiceDataSingleton.downloadQueue.find { it.getTaskName() == taskName }?.video?.file?.url ?: ""
|
||||
DownloadService.sendRemoveDownload(
|
||||
this@AnimeDownloaderService,
|
||||
MyDownloadService::class.java,
|
||||
ExoplayerDownloadService::class.java,
|
||||
url,
|
||||
false
|
||||
)
|
||||
|
@ -220,16 +216,28 @@ class AnimeDownloaderService : Service() {
|
|||
}
|
||||
|
||||
saveMediaInfo(task)
|
||||
downloadsManager.addDownload(
|
||||
Download(
|
||||
task.title,
|
||||
task.episode,
|
||||
Download.Type.ANIME,
|
||||
)
|
||||
var continueDownload = false
|
||||
downloadManager.addListener(
|
||||
object : androidx.media3.exoplayer.offline.DownloadManager.Listener {
|
||||
override fun onDownloadChanged(
|
||||
downloadManager: DownloadManager,
|
||||
download: Download,
|
||||
finalException: Exception?
|
||||
) {
|
||||
continueDownload = true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
//set an async timeout of 30 seconds before setting continueDownload to true
|
||||
launch {
|
||||
kotlinx.coroutines.delay(30000)
|
||||
continueDownload = true
|
||||
}
|
||||
|
||||
|
||||
// periodically check if the download is complete
|
||||
while (downloadManager.downloadIndex.getDownload(task.video.file.url) != null) {
|
||||
while (downloadManager.downloadIndex.getDownload(task.video.file.url) != null || continueDownload == false) {
|
||||
val download = downloadManager.downloadIndex.getDownload(task.video.file.url)
|
||||
if (download != null) {
|
||||
if (download.state == androidx.media3.exoplayer.offline.Download.STATE_FAILED) {
|
||||
|
@ -251,6 +259,13 @@ class AnimeDownloaderService : Service() {
|
|||
task.getTaskName(),
|
||||
task.video.file.url
|
||||
).apply()
|
||||
downloadsManager.addDownload(
|
||||
DownloadedType(
|
||||
task.title,
|
||||
task.episode,
|
||||
DownloadedType.Type.ANIME,
|
||||
)
|
||||
)
|
||||
broadcastDownloadFinished(task.getTaskName())
|
||||
break
|
||||
}
|
||||
|
@ -284,9 +299,11 @@ class AnimeDownloaderService : Service() {
|
|||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val directory = File(
|
||||
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Anime/${task.title}"
|
||||
"${DownloadsManager.animeLocation}/${task.title}"
|
||||
)
|
||||
val episodeDirectory = File(directory, task.episode)
|
||||
if (!directory.exists()) directory.mkdirs()
|
||||
if (!episodeDirectory.exists()) episodeDirectory.mkdirs()
|
||||
|
||||
val file = File(directory, "media.json")
|
||||
val gson = GsonBuilder()
|
||||
|
@ -305,6 +322,9 @@ class AnimeDownloaderService : Service() {
|
|||
if (media != null) {
|
||||
media.cover = media.cover?.let { downloadImage(it, directory, "cover.jpg") }
|
||||
media.banner = media.banner?.let { downloadImage(it, directory, "banner.jpg") }
|
||||
if (task.episodeImage != null) {
|
||||
downloadImage(task.episodeImage, episodeDirectory, "episodeImage.jpg")
|
||||
}
|
||||
|
||||
val jsonString = gson.toJson(media)
|
||||
withContext(Dispatchers.Main) {
|
||||
|
@ -395,6 +415,7 @@ class AnimeDownloaderService : Service() {
|
|||
val video: Video,
|
||||
val subtitle: Subtitle? = null,
|
||||
val sourceMedia: Media? = null,
|
||||
val episodeImage: String? = null,
|
||||
val retries: Int = 2,
|
||||
val simultaneousDownloads: Int = 2,
|
||||
) {
|
||||
|
|
|
@ -18,7 +18,7 @@ import androidx.core.app.NotificationCompat
|
|||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
|
@ -246,10 +246,10 @@ class MangaDownloaderService : Service() {
|
|||
|
||||
saveMediaInfo(task)
|
||||
downloadsManager.addDownload(
|
||||
Download(
|
||||
DownloadedType(
|
||||
task.title,
|
||||
task.chapter,
|
||||
Download.Type.MANGA
|
||||
DownloadedType.Type.MANGA
|
||||
)
|
||||
)
|
||||
broadcastDownloadFinished(task.chapter)
|
||||
|
|
|
@ -2,7 +2,6 @@ package ani.dantotsu.download.manga
|
|||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
@ -23,20 +22,16 @@ import android.widget.GridView
|
|||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.app.ActivityCompat.recreate
|
||||
import androidx.fragment.app.Fragment
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.Refresh
|
||||
import ani.dantotsu.currActivity
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.initActivity
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.setSafeOnClickListener
|
||||
import ani.dantotsu.settings.SettingsActivity
|
||||
import ani.dantotsu.settings.SettingsDialogFragment
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.statusBarHeight
|
||||
|
@ -168,8 +163,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
// Get the OfflineMangaModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineMangaModel
|
||||
val media =
|
||||
downloadManager.mangaDownloads.firstOrNull { it.title == item.title }
|
||||
?: downloadManager.novelDownloads.firstOrNull { it.title == item.title }
|
||||
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
|
||||
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
|
||||
media?.let {
|
||||
startActivity(
|
||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||
|
@ -184,10 +179,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
gridView.setOnItemLongClickListener { parent, view, position, id ->
|
||||
// Get the OfflineMangaModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineMangaModel
|
||||
val type: Download.Type = if (downloadManager.mangaDownloads.any { it.title == item.title }) {
|
||||
Download.Type.MANGA
|
||||
val type: DownloadedType.Type = if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) {
|
||||
DownloadedType.Type.MANGA
|
||||
} else {
|
||||
Download.Type.NOVEL
|
||||
DownloadedType.Type.NOVEL
|
||||
}
|
||||
// Alert dialog to confirm deletion
|
||||
val builder = androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
||||
|
@ -292,19 +287,19 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
|
||||
private fun getDownloads() {
|
||||
downloads = listOf()
|
||||
val mangaTitles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
|
||||
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
||||
for (title in mangaTitles) {
|
||||
val _downloads = downloadManager.mangaDownloads.filter { it.title == title }
|
||||
val _downloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
|
||||
val download = _downloads.first()
|
||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||
newMangaDownloads += offlineMangaModel
|
||||
}
|
||||
downloads = newMangaDownloads
|
||||
val novelTitles = downloadManager.novelDownloads.map { it.title }.distinct()
|
||||
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
|
||||
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
||||
for (title in novelTitles) {
|
||||
val _downloads = downloadManager.novelDownloads.filter { it.title == title }
|
||||
val _downloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
|
||||
val download = _downloads.first()
|
||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||
newNovelDownloads += offlineMangaModel
|
||||
|
@ -313,17 +308,17 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
|
||||
}
|
||||
|
||||
private fun getMedia(download: Download): Media? {
|
||||
val type = if (download.type == Download.Type.MANGA) {
|
||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
||||
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (download.type == Download.Type.ANIME) {
|
||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
}
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$type/${download.title}"
|
||||
"Dantotsu/$type/${downloadedType.title}"
|
||||
)
|
||||
//load media.json and convert to media class with gson
|
||||
return try {
|
||||
|
@ -343,23 +338,23 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadOfflineMangaModel(download: Download): OfflineMangaModel {
|
||||
val type = if (download.type == Download.Type.MANGA) {
|
||||
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
||||
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (download.type == Download.Type.ANIME) {
|
||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
}
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$type/${download.title}"
|
||||
"Dantotsu/$type/${downloadedType.title}"
|
||||
)
|
||||
//load media.json and convert to media class with gson
|
||||
try {
|
||||
val media = File(directory, "media.json")
|
||||
val mediaJson = media.readText()
|
||||
val mediaModel = getMedia(download)!!
|
||||
val mediaModel = getMedia(downloadedType)!!
|
||||
val cover = File(directory, "cover.jpg")
|
||||
val coverUri: Uri? = if (cover.exists()) {
|
||||
Uri.fromFile(cover)
|
||||
|
|
|
@ -17,7 +17,7 @@ import androidx.core.app.NotificationCompat
|
|||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
|
@ -330,10 +330,10 @@ class NovelDownloaderService : Service() {
|
|||
|
||||
saveMediaInfo(task)
|
||||
downloadsManager.addDownload(
|
||||
Download(
|
||||
DownloadedType(
|
||||
task.title,
|
||||
task.chapter,
|
||||
Download.Type.NOVEL
|
||||
DownloadedType.Type.NOVEL
|
||||
)
|
||||
)
|
||||
broadcastDownloadFinished(task.originalLink)
|
||||
|
|
|
@ -11,7 +11,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler
|
|||
import ani.dantotsu.R
|
||||
|
||||
@UnstableApi
|
||||
class MyDownloadService : DownloadService(1, 2000, "download_service", R.string.downloads, 0) {
|
||||
class ExoplayerDownloadService : DownloadService(1, 2000, "download_service", R.string.downloads, 0) {
|
||||
companion object {
|
||||
private const val JOB_ID = 1
|
||||
private const val FOREGROUND_NOTIFICATION_ID = 1
|
|
@ -3,6 +3,7 @@ package ani.dantotsu.download.video
|
|||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
|
@ -12,6 +13,7 @@ import android.util.Log
|
|||
import androidx.annotation.OptIn
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.getString
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
|
@ -32,6 +34,8 @@ import androidx.media3.exoplayer.scheduler.Requirements
|
|||
import androidx.media3.ui.TrackSelectionDialogBuilder
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.defaultHeaders
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||
import ani.dantotsu.download.anime.AnimeServiceDataSingleton
|
||||
import ani.dantotsu.logError
|
||||
|
@ -50,7 +54,8 @@ import java.util.concurrent.*
|
|||
|
||||
object Helper {
|
||||
|
||||
var simpleCache: SimpleCache? = null
|
||||
private var simpleCache: SimpleCache? = null
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) {
|
||||
val dataSourceFactory = DataSource.Factory {
|
||||
|
@ -96,18 +101,18 @@ object Helper {
|
|||
)
|
||||
downloadHelper.prepare(object : DownloadHelper.Callback {
|
||||
override fun onPrepared(helper: DownloadHelper) {
|
||||
TrackSelectionDialogBuilder(
|
||||
context, "Select thingy", helper.getTracks(0).groups
|
||||
/*TrackSelectionDialogBuilder( TODO: use this for subtitles
|
||||
context, "Select Source", helper.getTracks(0).groups
|
||||
) { _, overrides ->
|
||||
val params = TrackSelectionParameters.Builder(context)
|
||||
overrides.forEach {
|
||||
params.addOverride(it.value)
|
||||
}
|
||||
helper.addTrackSelection(0, params.build())
|
||||
MyDownloadService
|
||||
ExoplayerDownloadService
|
||||
DownloadService.sendAddDownload(
|
||||
context,
|
||||
MyDownloadService::class.java,
|
||||
ExoplayerDownloadService::class.java,
|
||||
helper.getDownloadRequest(null),
|
||||
false
|
||||
)
|
||||
|
@ -117,6 +122,14 @@ object Helper {
|
|||
if (it.frameRate > 0f) it.height.toString() + "p" else it.height.toString() + "p (fps : N/A)"
|
||||
}
|
||||
build().show()
|
||||
}*/
|
||||
helper.getDownloadRequest(null).let {
|
||||
DownloadService.sendAddDownload(
|
||||
context,
|
||||
ExoplayerDownloadService::class.java,
|
||||
it,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,8 +173,8 @@ object Helper {
|
|||
Requirements(Requirements.NETWORK or Requirements.DEVICE_STORAGE_NOT_LOW)
|
||||
maxParallelDownloads = 3
|
||||
}
|
||||
downloadManager.addListener(
|
||||
object : DownloadManager.Listener { // Override methods of interest here.
|
||||
downloadManager.addListener( //for testing
|
||||
object : DownloadManager.Listener {
|
||||
override fun onDownloadChanged(
|
||||
downloadManager: DownloadManager,
|
||||
download: Download,
|
||||
|
@ -199,6 +212,7 @@ object Helper {
|
|||
return downloadDirectory!!
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun startAnimeDownloadService(
|
||||
context: Context,
|
||||
title: String,
|
||||
|
@ -224,16 +238,62 @@ object Helper {
|
|||
subtitle,
|
||||
sourceMedia
|
||||
)
|
||||
AnimeServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||
|
||||
val downloadsManger = Injekt.get<DownloadsManager>()
|
||||
val downloadCheck = downloadsManger
|
||||
.queryDownload(title, episode, DownloadedType.Type.ANIME)
|
||||
|
||||
if (downloadCheck) {
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle("Download Exists")
|
||||
.setMessage("A download for this episode already exists. Do you want to overwrite it?")
|
||||
.setPositiveButton("Yes") { _, _ ->
|
||||
DownloadService.sendRemoveDownload(
|
||||
context,
|
||||
ExoplayerDownloadService::class.java,
|
||||
context.getSharedPreferences(
|
||||
getString(context, R.string.anime_downloads),
|
||||
Context.MODE_PRIVATE
|
||||
).getString(
|
||||
downloadTask.getTaskName(),
|
||||
""
|
||||
) ?: "",
|
||||
false
|
||||
)
|
||||
context.getSharedPreferences(
|
||||
getString(context, R.string.anime_downloads),
|
||||
Context.MODE_PRIVATE
|
||||
).edit()
|
||||
.remove(downloadTask.getTaskName())
|
||||
.apply()
|
||||
downloadsManger.removeDownload(
|
||||
DownloadedType(
|
||||
title,
|
||||
episode,
|
||||
DownloadedType.Type.ANIME
|
||||
)
|
||||
)
|
||||
AnimeServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||
if (!AnimeServiceDataSingleton.isServiceRunning) {
|
||||
val intent = Intent(context, AnimeDownloaderService::class.java)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
AnimeServiceDataSingleton.isServiceRunning = true
|
||||
}
|
||||
}
|
||||
.setNegativeButton("No") { _, _ -> }
|
||||
.show()
|
||||
} else {
|
||||
AnimeServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||
if (!AnimeServiceDataSingleton.isServiceRunning) {
|
||||
val intent = Intent(context, AnimeDownloaderService::class.java)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
AnimeServiceDataSingleton.isServiceRunning = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) private fun getSimpleCache(context: Context): SimpleCache {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun getSimpleCache(context: Context): SimpleCache {
|
||||
return if (simpleCache == null) {
|
||||
val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY)
|
||||
val database = Injekt.get<StandaloneDatabaseProvider>()
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.animation.ObjectAnimator
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.app.DownloadManager
|
||||
import android.app.PictureInPictureParams
|
||||
import android.app.PictureInPictureUiState
|
||||
import android.content.ActivityNotFoundException
|
||||
|
@ -97,7 +98,9 @@ import kotlin.math.min
|
|||
import kotlin.math.roundToInt
|
||||
import androidx.media3.cast.SessionAvailabilityListener
|
||||
import androidx.media3.cast.CastPlayer
|
||||
import androidx.media3.exoplayer.offline.Download
|
||||
import androidx.mediarouter.app.MediaRouteButton
|
||||
import ani.dantotsu.download.video.Helper
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
|
||||
|
@ -150,6 +153,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
|
||||
private var orientationListener: OrientationEventListener? = null
|
||||
|
||||
private var downloadId: String? = null
|
||||
|
||||
companion object {
|
||||
var initialized = false
|
||||
lateinit var media: Media
|
||||
|
@ -475,7 +480,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
if (isInitialized) {
|
||||
isPlayerPlaying = exoPlayer.isPlaying
|
||||
(exoPlay.drawable as Animatable?)?.start()
|
||||
if (isPlayerPlaying || castPlayer.isPlaying ) {
|
||||
if (isPlayerPlaying || castPlayer.isPlaying) {
|
||||
Glide.with(this).load(R.drawable.anim_play_to_pause).into(exoPlay)
|
||||
exoPlayer.pause()
|
||||
castPlayer.pause()
|
||||
|
@ -1115,7 +1120,21 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
if (settings.cursedSpeeds)
|
||||
arrayOf(1f, 1.25f, 1.5f, 1.75f, 2f, 2.5f, 3f, 4f, 5f, 10f, 25f, 50f)
|
||||
else
|
||||
arrayOf(0.25f, 0.33f, 0.5f, 0.66f, 0.75f, 1f, 1.15f, 1.25f, 1.33f, 1.5f, 1.66f, 1.75f, 2f)
|
||||
arrayOf(
|
||||
0.25f,
|
||||
0.33f,
|
||||
0.5f,
|
||||
0.66f,
|
||||
0.75f,
|
||||
1f,
|
||||
1.15f,
|
||||
1.25f,
|
||||
1.33f,
|
||||
1.5f,
|
||||
1.66f,
|
||||
1.75f,
|
||||
2f
|
||||
)
|
||||
|
||||
val speedsName = speeds.map { "${it}x" }.toTypedArray()
|
||||
var curSpeed = loadData("${media.id}_speed", this) ?: settings.defaultSpeed
|
||||
|
@ -1292,7 +1311,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
if (video?.format == VideoType.CONTAINER || (loadData<Int>("settings_download_manager")
|
||||
?: 0) != 0
|
||||
) {
|
||||
but.visibility = View.VISIBLE
|
||||
//but.visibility = View.VISIBLE TODO: not sure if this is needed
|
||||
but.setOnClickListener {
|
||||
download(this, episode, animeTitle.text.toString())
|
||||
}
|
||||
|
@ -1317,8 +1336,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
dataSource
|
||||
}
|
||||
cacheFactory = CacheDataSource.Factory().apply {
|
||||
setCache(simpleCache)
|
||||
setCache(Helper.getSimpleCache(this@ExoplayerView))
|
||||
setUpstreamDataSourceFactory(dataSourceFactory)
|
||||
setCacheWriteDataSinkFactory(null)
|
||||
}
|
||||
|
||||
val mimeType = when (video?.format) {
|
||||
|
@ -1327,6 +1347,20 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
else -> MimeTypes.APPLICATION_MP4
|
||||
}
|
||||
|
||||
val downloadedMediaItem = if (ext.server.offline) {
|
||||
val key = ext.server.name
|
||||
downloadId = getSharedPreferences(getString(R.string.anime_downloads), MODE_PRIVATE)
|
||||
.getString(key, null)
|
||||
if (downloadId != null) {
|
||||
Helper.downloadManager(this)
|
||||
.downloadIndex.getDownload(downloadId!!)?.request?.toMediaItem()
|
||||
} else {
|
||||
snackString("Download not found")
|
||||
null
|
||||
}
|
||||
} else null
|
||||
|
||||
mediaItem = if (downloadedMediaItem == null) {
|
||||
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
|
||||
logger("url: ${video!!.file.url}")
|
||||
logger("mimeType: $mimeType")
|
||||
|
@ -1335,7 +1369,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
val listofnotnullsubs = immutableListOf(sub).filterNotNull()
|
||||
builder.setSubtitleConfigurations(listofnotnullsubs)
|
||||
}
|
||||
mediaItem = builder.build()
|
||||
builder.build()
|
||||
} else {
|
||||
downloadedMediaItem
|
||||
}
|
||||
|
||||
|
||||
//Source
|
||||
exoSource.setOnClickListener {
|
||||
|
@ -1457,7 +1495,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
exoPlayer.release()
|
||||
VideoCache.release()
|
||||
mediaSession?.release()
|
||||
if(DiscordServiceRunningSingleton.running) {
|
||||
if (DiscordServiceRunningSingleton.running) {
|
||||
val stopIntent = Intent(this, DiscordService::class.java)
|
||||
DiscordServiceRunningSingleton.running = false
|
||||
stopService(stopIntent)
|
||||
|
@ -1594,7 +1632,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
if (isInitialized) {
|
||||
if (exoPlayer.currentPosition.toFloat() / exoPlayer.duration > settings.watchPercentage) {
|
||||
preloading = true
|
||||
nextEpisode(false) { i ->
|
||||
nextEpisode(false) { i -> //TODO: make sure this works for offline episodes
|
||||
val ep = episodes[episodeArr[currentEpisodeIndex + i]] ?: return@nextEpisode
|
||||
val selected = media.selected ?: return@nextEpisode
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
|
|
|
@ -29,7 +29,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import androidx.viewpager2.widget.ViewPager2
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.manga.MangaDownloaderService
|
||||
import ani.dantotsu.download.manga.MangaServiceDataSingleton
|
||||
|
@ -166,7 +166,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
chapterAdapter =
|
||||
MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
|
||||
|
||||
for (download in downloadManager.mangaDownloads) {
|
||||
for (download in downloadManager.mangaDownloadedTypes) {
|
||||
chapterAdapter.stopDownload(download.chapter)
|
||||
}
|
||||
|
||||
|
@ -482,10 +482,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
|
||||
fun onMangaChapterRemoveDownloadClick(i: String) {
|
||||
downloadManager.removeDownload(
|
||||
Download(
|
||||
DownloadedType(
|
||||
media.nameMAL ?: media.nameRomaji,
|
||||
i,
|
||||
Download.Type.MANGA
|
||||
DownloadedType.Type.MANGA
|
||||
)
|
||||
)
|
||||
chapterAdapter.deleteDownload(i)
|
||||
|
@ -500,10 +500,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
|
||||
// Remove the download from the manager and update the UI
|
||||
downloadManager.removeDownload(
|
||||
Download(
|
||||
DownloadedType(
|
||||
media.nameMAL ?: media.nameRomaji,
|
||||
i,
|
||||
Download.Type.MANGA
|
||||
DownloadedType.Type.MANGA
|
||||
)
|
||||
)
|
||||
chapterAdapter.purgeDownload(i)
|
||||
|
|
|
@ -22,7 +22,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.novel.NovelDownloaderService
|
||||
import ani.dantotsu.download.novel.NovelServiceDataSingleton
|
||||
|
@ -92,10 +92,10 @@ class NovelReadFragment : Fragment(),
|
|||
override fun downloadedCheckWithStart(novel: ShowResponse): Boolean {
|
||||
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||
if (downloadsManager.queryDownload(
|
||||
Download(
|
||||
DownloadedType(
|
||||
media.nameMAL ?: media.nameRomaji,
|
||||
novel.name,
|
||||
Download.Type.NOVEL
|
||||
DownloadedType.Type.NOVEL
|
||||
)
|
||||
)
|
||||
) {
|
||||
|
@ -124,10 +124,10 @@ class NovelReadFragment : Fragment(),
|
|||
override fun downloadedCheck(novel: ShowResponse): Boolean {
|
||||
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||
return downloadsManager.queryDownload(
|
||||
Download(
|
||||
DownloadedType(
|
||||
media.nameMAL ?: media.nameRomaji,
|
||||
novel.name,
|
||||
Download.Type.NOVEL
|
||||
DownloadedType.Type.NOVEL
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -135,10 +135,10 @@ class NovelReadFragment : Fragment(),
|
|||
override fun deleteDownload(novel: ShowResponse) {
|
||||
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||
downloadsManager.removeDownload(
|
||||
Download(
|
||||
DownloadedType(
|
||||
media.nameMAL ?: media.nameRomaji,
|
||||
novel.name,
|
||||
Download.Type.NOVEL
|
||||
DownloadedType.Type.NOVEL
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,11 +12,17 @@ object AnimeSources : WatchSources() {
|
|||
suspend fun init(fromExtensions: StateFlow<List<AnimeExtension.Installed>>) {
|
||||
// Initialize with the first value from StateFlow
|
||||
val initialExtensions = fromExtensions.first()
|
||||
list = createParsersFromExtensions(initialExtensions)
|
||||
list = createParsersFromExtensions(initialExtensions) + Lazier(
|
||||
{ OfflineAnimeParser() },
|
||||
"Downloaded"
|
||||
)
|
||||
|
||||
// Update as StateFlow emits new values
|
||||
fromExtensions.collect { extensions ->
|
||||
list = createParsersFromExtensions(extensions)
|
||||
list = createParsersFromExtensions(extensions) + Lazier(
|
||||
{ OfflineAnimeParser() },
|
||||
"Downloaded"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -616,17 +616,18 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||
val fileName = queryPairs.find { it.first == "file" }?.second ?: ""
|
||||
|
||||
format = getVideoType(fileName)
|
||||
if (format == null) {
|
||||
val networkHelper = Injekt.get<NetworkHelper>()
|
||||
format = headRequest(videoUrl, networkHelper)
|
||||
}
|
||||
// this solves a problem no one has, so I'm commenting it out for now
|
||||
//if (format == null) {
|
||||
// val networkHelper = Injekt.get<NetworkHelper>()
|
||||
// format = headRequest(videoUrl, networkHelper)
|
||||
//}
|
||||
}
|
||||
|
||||
// If the format is still undetermined, log an error or handle it appropriately
|
||||
// If the format is still undetermined, log an error
|
||||
if (format == null) {
|
||||
logger("Unknown video format: $videoUrl")
|
||||
FirebaseCrashlytics.getInstance()
|
||||
.recordException(Exception("Unknown video format: $videoUrl"))
|
||||
//FirebaseCrashlytics.getInstance()
|
||||
// .recordException(Exception("Unknown video format: $videoUrl"))
|
||||
format = VideoType.CONTAINER
|
||||
}
|
||||
val headersMap: Map<String, String> =
|
||||
|
|
|
@ -46,6 +46,19 @@ abstract class WatchSources : BaseSources() {
|
|||
sEpisode = it.sEpisode
|
||||
)
|
||||
}
|
||||
} else if (parser is OfflineAnimeParser) {
|
||||
parser.loadEpisodes(showLink, extra, SAnime.create()).forEach {
|
||||
map[it.number] = Episode(
|
||||
it.number,
|
||||
it.link,
|
||||
it.title,
|
||||
it.description,
|
||||
it.thumbnail,
|
||||
it.isFiller,
|
||||
extra = it.extra,
|
||||
sEpisode = it.sEpisode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return map
|
||||
|
|
|
@ -7,9 +7,6 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.first
|
||||
|
||||
object MangaSources : MangaReadSources() {
|
||||
// Instantiate the static parser
|
||||
private val offlineMangaParser by lazy { OfflineMangaParser() }
|
||||
|
||||
override var list: List<Lazier<BaseParser>> = emptyList()
|
||||
|
||||
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
|
||||
|
|
106
app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt
Normal file
106
app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt
Normal file
|
@ -0,0 +1,106 @@
|
|||
package ani.dantotsu.parsers
|
||||
|
||||
import android.os.Environment
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.anime.AnimeNameAdapter
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
class OfflineAnimeParser : AnimeParser() {
|
||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||
|
||||
override val name = "Offline"
|
||||
override val saveName = "Offline"
|
||||
override val hostUrl = "Offline"
|
||||
override val isDubAvailableSeparately = false
|
||||
override val isNSFW = false
|
||||
|
||||
override suspend fun loadEpisodes(
|
||||
animeLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sAnime: SAnime
|
||||
): List<Episode> {
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"${DownloadsManager.animeLocation}/$animeLink"
|
||||
)
|
||||
//get all of the folder names and add them to the list
|
||||
val episodes = mutableListOf<Episode>()
|
||||
if (directory.exists()) {
|
||||
directory.listFiles()?.forEach {
|
||||
if (it.isDirectory) {
|
||||
val episode = Episode(
|
||||
it.name,
|
||||
"$animeLink - ${it.name}",
|
||||
it.name,
|
||||
null,
|
||||
null,
|
||||
sEpisode = SEpisodeImpl()
|
||||
)
|
||||
episodes.add(episode)
|
||||
}
|
||||
}
|
||||
episodes.sortBy { AnimeNameAdapter.findEpisodeNumber(it.number) }
|
||||
return episodes
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override suspend fun loadVideoServers(
|
||||
episodeLink: String,
|
||||
extra: Map<String, String>?,
|
||||
sEpisode: SEpisode
|
||||
): List<VideoServer> {
|
||||
return listOf(
|
||||
VideoServer(
|
||||
episodeLink,
|
||||
offline = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun search(query: String): List<ShowResponse> {
|
||||
val titles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
|
||||
val returnTitles: MutableList<String> = mutableListOf()
|
||||
for (title in titles) {
|
||||
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
|
||||
returnTitles.add(title)
|
||||
}
|
||||
}
|
||||
val returnList: MutableList<ShowResponse> = mutableListOf()
|
||||
for (title in returnTitles) {
|
||||
returnList.add(ShowResponse(title, title, title))
|
||||
}
|
||||
return returnList
|
||||
}
|
||||
|
||||
override suspend fun getVideoExtractor(server: VideoServer): VideoExtractor {
|
||||
return OfflineVideoExtractor(server)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
|
||||
override val server: VideoServer
|
||||
get() = videoServer
|
||||
|
||||
override suspend fun extract(): VideoContainer {
|
||||
val sublist = emptyList<Subtitle>()
|
||||
//we need to return a "fake" video so that the app doesn't crash
|
||||
val video = Video(
|
||||
null,
|
||||
VideoType.CONTAINER,
|
||||
"",
|
||||
)
|
||||
return VideoContainer(listOf(video), sublist)
|
||||
}
|
||||
|
||||
}
|
|
@ -76,7 +76,7 @@ class OfflineMangaParser : MangaParser() {
|
|||
}
|
||||
|
||||
override suspend fun search(query: String): List<ShowResponse> {
|
||||
val titles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||
val titles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
|
||||
val returnTitles: MutableList<String> = mutableListOf()
|
||||
for (title in titles) {
|
||||
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
|
||||
|
|
|
@ -3,10 +3,7 @@ package ani.dantotsu.parsers
|
|||
import android.os.Environment
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
@ -53,7 +50,7 @@ class OfflineNovelParser: NovelParser() {
|
|||
}
|
||||
|
||||
override suspend fun search(query: String): List<ShowResponse> {
|
||||
val titles = downloadManager.novelDownloads.map { it.title }.distinct()
|
||||
val titles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
|
||||
val returnTitles: MutableList<String> = mutableListOf()
|
||||
for (title in titles) {
|
||||
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
|
||||
|
|
|
@ -57,11 +57,15 @@ data class VideoServer(
|
|||
val name: String,
|
||||
val embed: FileUrl,
|
||||
val extraData: Map<String, String>? = null,
|
||||
val video: eu.kanade.tachiyomi.animesource.model.Video? = null
|
||||
val video: eu.kanade.tachiyomi.animesource.model.Video? = null,
|
||||
val offline: Boolean = false
|
||||
) : Serializable {
|
||||
constructor(name: String, embedUrl: String, extraData: Map<String, String>? = null)
|
||||
: this(name, FileUrl(embedUrl), extraData)
|
||||
|
||||
constructor(name: String, offline: Boolean)
|
||||
: this(name, FileUrl(""), null, null, offline)
|
||||
|
||||
constructor(
|
||||
name: String,
|
||||
embedUrl: String,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue