Explorar o código

Update HttpReadAloudService.kt

Ktouls hai 9 meses
pai
achega
0b1a8294cb
Modificáronse 1 ficheiros con 81 adicións e 32 borrados
  1. 81 32
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt

+ 81 - 32
app/src/main/java/io/legado/app/service/HttpReadAloudService.kt

@@ -28,8 +28,10 @@ import com.script.ScriptException
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppPattern
 import io.legado.app.constant.AppPattern
+import io.legado.app.data.appDb
 import io.legado.app.data.entities.HttpTTS
 import io.legado.app.data.entities.HttpTTS
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
+import io.legado.app.help.BookHelp
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.exoplayer.InputStreamDataSource
 import io.legado.app.help.exoplayer.InputStreamDataSource
@@ -65,6 +67,7 @@ import kotlin.coroutines.coroutineContext
 
 
 /**
 /**
  * 在线朗读
  * 在线朗读
+ * (已修改:支持跨章节预缓存 + 自定义清理时间)
  */
  */
 @SuppressLint("UnsafeOptInUsageError")
 @SuppressLint("UnsafeOptInUsageError")
 class HttpReadAloudService : BaseReadAloudService(),
 class HttpReadAloudService : BaseReadAloudService(),
@@ -150,6 +153,8 @@ class HttpReadAloudService : BaseReadAloudService(),
             downloadTaskActiveLock.withLock {
             downloadTaskActiveLock.withLock {
                 ensureActive()
                 ensureActive()
                 val httpTts = ReadAloud.httpTTS ?: throw NoStackTraceException("tts is null")
                 val httpTts = ReadAloud.httpTTS ?: throw NoStackTraceException("tts is null")
+                
+                // 1. 处理当前正在播放的章节
                 contentList.forEachIndexed { index, content ->
                 contentList.forEachIndexed { index, content ->
                     ensureActive()
                     ensureActive()
                     if (index < nowSpeak) return@forEachIndexed
                     if (index < nowSpeak) return@forEachIndexed
@@ -184,6 +189,7 @@ class HttpReadAloudService : BaseReadAloudService(),
                         exoPlayer.addMediaItem(mediaItem)
                         exoPlayer.addMediaItem(mediaItem)
                     }
                     }
                 }
                 }
+                // 2. 触发预下载 (新逻辑)
                 preDownloadAudios(httpTts)
                 preDownloadAudios(httpTts)
             }
             }
         }.onError {
         }.onError {
@@ -191,26 +197,55 @@ class HttpReadAloudService : BaseReadAloudService(),
         }
         }
     }
     }
 
 
+    // ================================================================
+    // 修改点:强力预缓存逻辑 (循环查库,跨章节)
+    // ================================================================
     private suspend fun preDownloadAudios(httpTts: HttpTTS) {
     private suspend fun preDownloadAudios(httpTts: HttpTTS) {
-        val textChapter = ReadBook.nextTextChapter ?: return
-        val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)
-            .splitToSequence("\n")
-            .filter { it.isNotEmpty() }
-            .take(AppConfig.audioPreDownloadNum) // <--- 修改:读取配置
-            .toList()
-        contentList.forEach { content ->
+        val book = ReadBook.book ?: return
+        // 获取当前章节索引
+        val currentIdx = ReadBook.textChapter?.chapterIndex ?: return
+        // 获取用户设置的预加载数量 (例如 50)
+        val limit = AppConfig.audioPreDownloadNum
+        
+        // 循环:从下一章开始,往后找 limit 个章节
+        for (i in 1..limit) {
             currentCoroutineContext().ensureActive()
             currentCoroutineContext().ensureActive()
-            val fileName = md5SpeakFileName(content, textChapter)
-            val speakText = content.replace(AppPattern.notReadAloudRegex, "")
-            if (speakText.isEmpty()) {
-                createSilentSound(fileName)
-            } else if (!hasSpeakFile(fileName)) {
-                runCatching {
-                    val inputStream = getSpeakStream(httpTts, speakText)
-                    if (inputStream != null) {
-                        createSpeakFile(fileName, inputStream)
-                    } else {
-                        createSilentSound(fileName)
+            
+            // 计算目标章节索引
+            val targetIndex = currentIdx + i
+            
+            // 查数据库获取章节信息 (如果超出了总章数会返回空,就停止)
+            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, targetIndex) ?: break
+            
+            // 获取章节内容 (这里假设已缓存文本,或者尝试读取)
+            val contentString = BookHelp.getContent(book, chapter)
+            
+            // 如果内容为空 (还没下载正文),就跳过,不强行下载正文以免阻塞
+            if (contentString.isNullOrEmpty()) continue
+
+            // 模拟 TextChapter 的分割逻辑,转成段落列表
+            val contentList = contentString.split("\n").filter { it.isNotEmpty() }
+
+            // 逐段检查并下载音频
+            contentList.forEach { content ->
+                currentCoroutineContext().ensureActive()
+                // 注意:跨章节时 MD5 需要基于 targetIndex 对应的 title (虽然 MD5Utils.md5Encode16(chapter.title) 可能和 TextChapter 不完全一致,但通常足够)
+                // 为了保险,我们手动拼装 MD5 文件名逻辑
+                val titleMd5 = MD5Utils.md5Encode16(chapter.title)
+                val contentMd5 = MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content")
+                val fileName = "${titleMd5}_${contentMd5}"
+                
+                val speakText = content.replace(AppPattern.notReadAloudRegex, "")
+                if (speakText.isEmpty()) {
+                    createSilentSound(fileName)
+                } else if (!hasSpeakFile(fileName)) {
+                    runCatching {
+                        val inputStream = getSpeakStream(httpTts, speakText)
+                        if (inputStream != null) {
+                            createSpeakFile(fileName, inputStream)
+                        } else {
+                            createSilentSound(fileName)
+                        }
                     }
                     }
                 }
                 }
             }
             }
@@ -257,23 +292,37 @@ class HttpReadAloudService : BaseReadAloudService(),
         }
         }
     }
     }
 
 
+    // ================================================================
+    // 修改点:流式预缓存逻辑 (循环查库,跨章节)
+    // ================================================================
     private suspend fun preDownloadAudiosStream(
     private suspend fun preDownloadAudiosStream(
         httpTts: HttpTTS,
         httpTts: HttpTTS,
         downloaderChannel: Channel<Downloader>
         downloaderChannel: Channel<Downloader>
     ) {
     ) {
-        val textChapter = ReadBook.nextTextChapter ?: return
-        val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)
-            .splitToSequence("\n")
-            .filter { it.isNotEmpty() }
-            .take(AppConfig.audioPreDownloadNum) // <--- 修改:读取配置
-            .toList()
-        contentList.forEach { content ->
+        val book = ReadBook.book ?: return
+        val currentIdx = ReadBook.textChapter?.chapterIndex ?: return
+        val limit = AppConfig.audioPreDownloadNum
+        
+        for (i in 1..limit) {
             currentCoroutineContext().ensureActive()
             currentCoroutineContext().ensureActive()
-            val fileName = md5SpeakFileName(content, textChapter)
-            val speakText = content.replace(AppPattern.notReadAloudRegex, "")
-            val dataSourceFactory = createDataSourceFactory(httpTts, speakText)
-            val downloader = createDownloader(dataSourceFactory, fileName)
-            downloaderChannel.send(downloader)
+            val targetIndex = currentIdx + i
+            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, targetIndex) ?: break
+            val contentString = BookHelp.getContent(book, chapter)
+            if (contentString.isNullOrEmpty()) continue
+
+            val contentList = contentString.split("\n").filter { it.isNotEmpty() }
+            
+            contentList.forEach { content ->
+                currentCoroutineContext().ensureActive()
+                val titleMd5 = MD5Utils.md5Encode16(chapter.title)
+                val contentMd5 = MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content")
+                val fileName = "${titleMd5}_${contentMd5}"
+                
+                val speakText = content.replace(AppPattern.notReadAloudRegex, "")
+                val dataSourceFactory = createDataSourceFactory(httpTts, speakText)
+                val downloader = createDownloader(dataSourceFactory, fileName)
+                downloaderChannel.send(downloader)
+            }
         }
         }
     }
     }
 
 
@@ -429,14 +478,14 @@ class HttpReadAloudService : BaseReadAloudService(),
     }
     }
 
 
     /**
     /**
-     * 移除缓存文件
+     * 移除缓存文件 (支持自定义清理时间)
      */
      */
     private fun removeCacheFile() {
     private fun removeCacheFile() {
         val titleMd5 = MD5Utils.md5Encode16(textChapter?.title ?: "")
         val titleMd5 = MD5Utils.md5Encode16(textChapter?.title ?: "")
         FileUtils.listDirsAndFiles(ttsFolderPath)?.forEach {
         FileUtils.listDirsAndFiles(ttsFolderPath)?.forEach {
             val isSilentSound = it.length() == 2160L
             val isSilentSound = it.length() == 2160L
             if ((!it.name.startsWith(titleMd5)
             if ((!it.name.startsWith(titleMd5)
-                        && System.currentTimeMillis() - it.lastModified() > AppConfig.audioCacheCleanTime) // <--- 修改:读取配置
+                        && System.currentTimeMillis() - it.lastModified() > AppConfig.audioCacheCleanTime)
                 || isSilentSound
                 || isSilentSound
             ) {
             ) {
                 FileUtils.delete(it.absolutePath)
                 FileUtils.delete(it.absolutePath)