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