Horis 2 лет назад
Родитель
Сommit
0782daf0f9

+ 24 - 15
app/src/main/java/io/legado/app/help/book/BookHelp.kt

@@ -21,6 +21,7 @@ import io.legado.app.utils.NetworkUtils
 import io.legado.app.utils.StringUtils
 import io.legado.app.utils.SvgUtils
 import io.legado.app.utils.UrlUtil
+import io.legado.app.utils.createFileIfNotExist
 import io.legado.app.utils.exists
 import io.legado.app.utils.externalFiles
 import io.legado.app.utils.getFile
@@ -29,10 +30,10 @@ import io.legado.app.utils.onEachParallel
 import io.legado.app.utils.postEvent
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.ensureActive
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.withContext
 import org.apache.commons.text.similarity.JaccardSimilarity
 import splitties.init.appCtx
@@ -55,7 +56,7 @@ object BookHelp {
     private const val cacheFolderName = "book_cache"
     private const val cacheImageFolderName = "images"
     private const val cacheEpubFolderName = "epub"
-    private val downloadImages = ConcurrentHashMap.newKeySet<String>()
+    private val downloadImages = ConcurrentHashMap<String, Mutex>()
 
     val cachePath = FileUtils.getPath(downloadDir, cacheFolderName)
 
@@ -173,15 +174,18 @@ object BookHelp {
         src: String,
         chapter: BookChapter? = null
     ) {
-        while (downloadImages.contains(src)) {
-            delay(100)
-        }
-        if (getImage(book, src).exists()) {
+        if (isImageExist(book, src)) {
             return
         }
-        downloadImages.add(src)
-        val analyzeUrl = AnalyzeUrl(src, source = bookSource)
+        val mutex = synchronized(this) {
+            downloadImages.getOrPut(src) { Mutex() }
+        }
+        mutex.lock()
         try {
+            if (isImageExist(book, src)) {
+                return
+            }
+            val analyzeUrl = AnalyzeUrl(src, source = bookSource)
             val bytes = analyzeUrl.getByteArrayAwait()
             //某些图片被加密,需要进一步解密
             ImageUtils.decode(
@@ -193,13 +197,7 @@ object BookHelp {
                     // throw NoStackTraceException("数据异常")
                     AppLog.put("${book.name} ${chapter?.title} 图片 $src 下载错误 数据异常")
                 }
-                FileUtils.createFileIfNotExist(
-                    downloadDir,
-                    cacheFolderName,
-                    book.getFolderName(),
-                    cacheImageFolderName,
-                    "${MD5Utils.md5Encode16(src)}.${getImageSuffix(src)}"
-                ).writeBytes(it)
+                writeImage(book, src, it)
             }
         } catch (e: Exception) {
             coroutineContext.ensureActive()
@@ -207,6 +205,7 @@ object BookHelp {
             AppLog.put(msg, e)
         } finally {
             downloadImages.remove(src)
+            mutex.unlock()
         }
     }
 
@@ -219,6 +218,16 @@ object BookHelp {
         )
     }
 
+    @Synchronized
+    fun writeImage(book: Book, src: String, bytes: ByteArray) {
+        getImage(book, src).createFileIfNotExist().writeBytes(bytes)
+    }
+
+    @Synchronized
+    fun isImageExist(book: Book, src: String): Boolean {
+        return getImage(book, src).exists()
+    }
+
     fun getImageSuffix(src: String): String {
         return UrlUtil.getSuffix(src, "jpg")
     }

+ 1 - 1
app/src/main/java/io/legado/app/model/ImageProvider.kt

@@ -94,7 +94,7 @@ object ImageProvider {
     ): File {
         return withContext(IO) {
             val vFile = BookHelp.getImage(book, src)
-            if (!vFile.exists()) {
+            if (!BookHelp.isImageExist(book, src)) {
                 val inputStream = when {
                     book.isEpub -> EpubFile.getImage(book, src)
                     book.isPdf -> PdfFile.getImage(book, src)

+ 16 - 17
app/src/main/java/io/legado/app/service/HttpReadAloudService.kt

@@ -8,6 +8,7 @@ import androidx.media3.common.C
 import androidx.media3.common.MediaItem
 import androidx.media3.common.PlaybackException
 import androidx.media3.common.Player
+import androidx.media3.common.Timeline
 import androidx.media3.database.StandaloneDatabaseProvider
 import androidx.media3.datasource.DataSource
 import androidx.media3.datasource.cache.CacheDataSink
@@ -75,7 +76,7 @@ class HttpReadAloudService : BaseReadAloudService(),
     private val cache by lazy {
         SimpleCache(
             File(cacheDir, "httpTTS_cache"),
-            LeastRecentlyUsedCacheEvictor((50 * 1024 * 1024).toLong()),
+            LeastRecentlyUsedCacheEvictor(128 * 1024 * 1024),
             StandaloneDatabaseProvider(appCtx)
         )
     }
@@ -182,15 +183,7 @@ class HttpReadAloudService : BaseReadAloudService(),
                     val file = getSpeakFileAsMd5(fileName)
                     val mediaItem = MediaItem.fromUri(Uri.fromFile(file))
                     launch(Main) {
-                        if (exoPlayer.playbackState == Player.STATE_ENDED) {
-                            exoPlayer.stop()
-                            exoPlayer.clearMediaItems()
-                        }
                         exoPlayer.addMediaItem(mediaItem)
-                        if (!exoPlayer.isPlaying) {
-                            exoPlayer.playWhenReady = !pause
-                            exoPlayer.prepare()
-                        }
                     }
                 }
                 preDownloadAudios(httpTts)
@@ -256,15 +249,7 @@ class HttpReadAloudService : BaseReadAloudService(),
                     downloaderChannel.send(downloader)
                     val mediaSource = createMediaSource(dataSourceFactory, fileName)
                     launch(Main) {
-                        if (exoPlayer.playbackState == Player.STATE_ENDED) {
-                            exoPlayer.stop()
-                            exoPlayer.clearMediaItems()
-                        }
                         exoPlayer.addMediaSource(mediaSource)
-                        if (!exoPlayer.isPlaying) {
-                            exoPlayer.playWhenReady = !pause
-                            exoPlayer.prepare()
-                        }
                     }
                 }
                 preDownloadAudiosStream(httpTts, downloaderChannel)
@@ -540,10 +525,24 @@ class HttpReadAloudService : BaseReadAloudService(),
                 // 结束
                 playErrorNo = 0
                 updateNextPos()
+                exoPlayer.stop()
+                exoPlayer.clearMediaItems()
             }
         }
     }
 
+    override fun onTimelineChanged(timeline: Timeline, reason: Int) {
+        when (reason) {
+            Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED -> {
+                if (!timeline.isEmpty && exoPlayer.playbackState == Player.STATE_IDLE) {
+                    exoPlayer.prepare()
+                }
+            }
+
+            else -> {}
+        }
+    }
+
     override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
         if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return
         if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {

+ 1 - 1
app/src/main/java/io/legado/app/utils/FileExtensions.kt

@@ -35,7 +35,7 @@ fun File.listFileDocs(filter: FileDocFilter? = null): ArrayList<FileDoc> {
 
 fun File.createFileIfNotExist(): File {
     if (!exists()) {
-        parentFile?.createFileIfNotExist()
+        parentFile?.createFolderIfNotExist()
         createNewFile()
     }
     return this