diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index f58944f7..50d681c5 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -29,6 +29,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.offline.Download import androidx.viewpager2.adapter.FragmentStateAdapter import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.AnilistHomeViewModel @@ -249,15 +250,19 @@ class MainActivity : AppCompatActivity() { GlobalScope.launch(Dispatchers.IO) { val index = Helper.downloadManager(this@MainActivity).downloadIndex - if (index != null) { - val downloadCursor = index.getDownloads() - if (downloadCursor != null) { - while (downloadCursor.moveToNext()) { - val download = downloadCursor.download - Log.e("Downloader", download.request.uri.toString()) - Log.e("Downloader", download.request.id.toString()) - Log.e("Downloader", download.request.mimeType.toString()) - } + val downloadCursor = index.getDownloads() + while (downloadCursor.moveToNext()) { + val download = downloadCursor.download + Log.e("Downloader", download.request.uri.toString()) + Log.e("Downloader", download.request.id.toString()) + Log.e("Downloader", download.request.mimeType.toString()) + Log.e("Downloader", download.request.data.size.toString()) + Log.e("Downloader", download.bytesDownloaded.toString()) + Log.e("Downloader", download.state.toString()) + Log.e("Downloader", download.failureReason.toString()) + + if (download.state == Download.STATE_FAILED) { //simple cleanup + Helper.downloadManager(this@MainActivity).removeDownload(download.request.id) } } } diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt index 1519f8a8..84a960cc 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -20,6 +20,7 @@ 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.FileUrl import ani.dantotsu.R import ani.dantotsu.currActivity import ani.dantotsu.download.DownloadedType @@ -84,7 +85,6 @@ class AnimeDownloaderService : Service() { setSmallIcon(R.drawable.ic_round_download_24) priority = NotificationCompat.PRIORITY_DEFAULT setOnlyAlertOnce(true) - setProgress(0, 0, false) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground( @@ -216,43 +216,39 @@ class AnimeDownloaderService : Service() { } saveMediaInfo(task) - var continueDownload = false - downloadManager.addListener( - object : androidx.media3.exoplayer.offline.DownloadManager.Listener { - override fun onDownloadChanged( - downloadManager: DownloadManager, - download: Download, - finalException: Exception? - ) { - continueDownload = true - } - } - ) + val downloadStarted = hasDownloadStarted(downloadManager, task, 30000) // 30 seconds timeout - //set an async timeout of 30 seconds before setting continueDownload to true - launch { - kotlinx.coroutines.delay(30000) - continueDownload = true + if (!downloadStarted) { + logger("Download failed to start") + builder.setContentText("${task.title} - ${task.episode} Download failed to start") + notificationManager.notify(NOTIFICATION_ID, builder.build()) + snackString("${task.title} - ${task.episode} Download failed to start") + broadcastDownloadFailed(task.getTaskName()) + return@withContext } // periodically check if the download is complete - while (downloadManager.downloadIndex.getDownload(task.video.file.url) != null || continueDownload == false) { + while (downloadManager.downloadIndex.getDownload(task.video.file.url) != null) { val download = downloadManager.downloadIndex.getDownload(task.video.file.url) if (download != null) { if (download.state == androidx.media3.exoplayer.offline.Download.STATE_FAILED) { logger("Download failed") builder.setContentText("${task.title} - ${task.episode} Download failed") - .setProgress(0, 0, false) notificationManager.notify(NOTIFICATION_ID, builder.build()) snackString("${task.title} - ${task.episode} Download failed") + logger("Download failed: ${download.failureReason}") + FirebaseCrashlytics.getInstance().recordException(Exception("Anime Download failed:" + + " ${download.failureReason}" + + " url: ${task.video.file.url}" + + " title: ${task.title}" + + " episode: ${task.episode}")) broadcastDownloadFailed(task.getTaskName()) break } if (download.state == androidx.media3.exoplayer.offline.Download.STATE_COMPLETED) { logger("Download completed") builder.setContentText("${task.title} - ${task.episode} Download completed") - .setProgress(0, 0, false) notificationManager.notify(NOTIFICATION_ID, builder.build()) snackString("${task.title} - ${task.episode} Download completed") getSharedPreferences(getString(R.string.anime_downloads), Context.MODE_PRIVATE).edit().putString( @@ -272,13 +268,11 @@ class AnimeDownloaderService : Service() { if (download.state == androidx.media3.exoplayer.offline.Download.STATE_STOPPED) { logger("Download stopped") builder.setContentText("${task.title} - ${task.episode} Download stopped") - .setProgress(0, 0, false) notificationManager.notify(NOTIFICATION_ID, builder.build()) snackString("${task.title} - ${task.episode} Download stopped") break } broadcastDownloadProgress(task.getTaskName(), download.percentDownloaded.toInt()) - builder.setProgress(100, download.percentDownloaded.toInt(), false) if (notifi) { notificationManager.notify(NOTIFICATION_ID, builder.build()) } @@ -294,6 +288,19 @@ class AnimeDownloaderService : Service() { } } + @androidx.annotation.OptIn(UnstableApi::class) suspend fun hasDownloadStarted(downloadManager: DownloadManager, task: DownloadTask, timeout: Long): Boolean { + val startTime = System.currentTimeMillis() + while (System.currentTimeMillis() - startTime < timeout) { + val download = downloadManager.downloadIndex.getDownload(task.video.file.url) + if (download != null) { + return true + } + // Delay between each poll + kotlinx.coroutines.delay(500) + } + return false + } + @OptIn(DelicateCoroutinesApi::class) private fun saveMediaInfo(task: DownloadTask) { GlobalScope.launch(Dispatchers.IO) { @@ -323,6 +330,13 @@ class AnimeDownloaderService : Service() { media.cover = media.cover?.let { downloadImage(it, directory, "cover.jpg") } media.banner = media.banner?.let { downloadImage(it, directory, "banner.jpg") } if (task.episodeImage != null) { + media.anime?.episodes?.get(task.episode)?.let { episode -> + episode.thumb = downloadImage(task.episodeImage, episodeDirectory, "episodeImage.jpg")?.let { + FileUrl( + it + ) + } + } downloadImage(task.episodeImage, episodeDirectory, "episodeImage.jpg") } diff --git a/app/src/main/java/ani/dantotsu/download/video/Helper.kt b/app/src/main/java/ani/dantotsu/download/video/Helper.kt index 4c10752d..8d8b52c6 100644 --- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt +++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt @@ -219,7 +219,8 @@ object Helper { episode: String, video: Video, subtitle: Subtitle? = null, - sourceMedia: Media? = null + sourceMedia: Media? = null, + episodeImage: String? = null ) { if (!isNotificationPermissionGranted(context)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -236,7 +237,8 @@ object Helper { episode, video, subtitle, - sourceMedia + sourceMedia, + episodeImage ) val downloadsManger = Injekt.get() diff --git a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt index c9d734f4..e90abecb 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt @@ -283,7 +283,8 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { episode.number, video, null, - media + media, + episode.thumb?.url?: media!!.banner?: media!!.cover ) } dismiss()