Просмотр исходного кода

Update HttpReadAloudService.kt

Ktouls 9 месяцев назад
Родитель
Сommit
7c4b8bc17d
1 измененных файлов с 77 добавлено и 51 удалено
  1. 77 51
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt

+ 77 - 51
app/src/main/java/io/legado/app/service/HttpReadAloudService.kt

@@ -29,6 +29,8 @@ 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.appDb
+// 【关键修复】补全 Book 引用,防止构建失败
+import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 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
@@ -68,7 +70,7 @@ import kotlin.coroutines.coroutineContext
 
 
 /**
 /**
  * 在线朗读
  * 在线朗读
- * (终极修正版:使用全局 ReadBook.durChapterIndex 和 textChapter.chapter 路径)
+ * (最终稳定版:补全Import,增加容错,强制读取文件)
  */
  */
 @SuppressLint("UnsafeOptInUsageError")
 @SuppressLint("UnsafeOptInUsageError")
 class HttpReadAloudService : BaseReadAloudService(),
 class HttpReadAloudService : BaseReadAloudService(),
@@ -196,45 +198,67 @@ class HttpReadAloudService : BaseReadAloudService(),
         }
         }
     }
     }
 
 
+    /**
+     * 辅助方法:安全获取章节内容
+     * 就算内存里没有,也会尝试去本地文件读取
+     */
+    private fun getChapterContent(book: Book, chapter: BookChapter): String? {
+        // BookHelp.getContent 内部封装了从文件读取的逻辑
+        // 我们单独拿出来调用,确保不会因为 TextChapter 的缺失而跳过
+        return BookHelp.getContent(book, chapter)
+    }
+
     private suspend fun preDownloadAudios(httpTts: HttpTTS) {
     private suspend fun preDownloadAudios(httpTts: HttpTTS) {
         val book = ReadBook.book ?: return
         val book = ReadBook.book ?: return
-        
-        // 终极修复:使用 ReadBook.durChapterIndex (最安全的获取当前章节索引的方式)
         val currentIdx = ReadBook.durChapterIndex
         val currentIdx = ReadBook.durChapterIndex
         val limit = AppConfig.audioPreDownloadNum
         val limit = AppConfig.audioPreDownloadNum
         
         
-        for (i in 1..limit) {
-            currentCoroutineContext().ensureActive()
-            
-            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 ->
+        // 增加容错,防止个别章节报错导致整个预下载停止
+        try {
+            for (i in 1..limit) {
                 currentCoroutineContext().ensureActive()
                 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, "")
-                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 = getChapterContent(book, chapter)
+                
+                // 如果内容真的是空的(文件没下载),就记录日志并跳过
+                if (contentString.isNullOrEmpty()) {
+                    // 可以在日志里看到是哪一章缺了
+                    // AppLog.put("预下载跳过:第 ${chapter.index + 1} 章 (${chapter.title}) 本地无内容")
+                    continue
+                }
+
+                val contentList = contentString.split("\n").filter { it.isNotEmpty() }
+
+                contentList.forEach { content ->
+                    currentCoroutineContext().ensureActive()
+                    
+                    // 必须使用 chapter.title 生成文件名,确保与前台一致
+                    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)
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
             }
             }
+        } catch (e: Exception) {
+            AppLog.put("听书预下载异常: ${e.localizedMessage}", e)
         }
         }
     }
     }
 
 
@@ -283,31 +307,34 @@ class HttpReadAloudService : BaseReadAloudService(),
         downloaderChannel: Channel<Downloader>
         downloaderChannel: Channel<Downloader>
     ) {
     ) {
         val book = ReadBook.book ?: return
         val book = ReadBook.book ?: return
-        // 终极修复:使用 ReadBook.durChapterIndex
         val currentIdx = ReadBook.durChapterIndex
         val currentIdx = ReadBook.durChapterIndex
         val limit = AppConfig.audioPreDownloadNum
         val limit = AppConfig.audioPreDownloadNum
         
         
-        for (i in 1..limit) {
-            currentCoroutineContext().ensureActive()
-            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 ->
+        try {
+            for (i in 1..limit) {
                 currentCoroutineContext().ensureActive()
                 currentCoroutineContext().ensureActive()
-                val titleMd5 = MD5Utils.md5Encode16(chapter.title)
-                val contentMd5 = MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content")
-                val fileName = "${titleMd5}_${contentMd5}"
+                val targetIndex = currentIdx + i
+                val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, targetIndex) ?: break
                 
                 
-                val speakText = content.replace(AppPattern.notReadAloudRegex, "")
-                val dataSourceFactory = createDataSourceFactory(httpTts, speakText)
-                val downloader = createDownloader(dataSourceFactory, fileName)
-                downloaderChannel.send(downloader)
+                val contentString = getChapterContent(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)
+                }
             }
             }
+        } catch (e: Exception) {
+            AppLog.put("听书流式预下载异常: ${e.localizedMessage}", e)
         }
         }
     }
     }
 
 
@@ -433,7 +460,7 @@ class HttpReadAloudService : BaseReadAloudService(),
     }
     }
 
 
     private fun md5SpeakFileName(content: String, textChapter: TextChapter? = this.textChapter): String {
     private fun md5SpeakFileName(content: String, textChapter: TextChapter? = this.textChapter): String {
-        return MD5Utils.md5Encode16(textChapter?.title ?: "") + "_" +
+        return MD5Utils.md5Encode16(textChapter?.chapter?.title ?: "") + "_" +
                 MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content")
                 MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content")
     }
     }
 
 
@@ -463,10 +490,9 @@ class HttpReadAloudService : BaseReadAloudService(),
     }
     }
 
 
     /**
     /**
-     * 移除缓存文件 (支持自定义清理时间)
+     * 移除缓存文件
      */
      */
     private fun removeCacheFile() {
     private fun removeCacheFile() {
-        // 终极修复:TextChapter 里其实包裹了一个 chapter,这才是 BookChapter,才有 title
         val titleMd5 = MD5Utils.md5Encode16(this.textChapter?.chapter?.title ?: "")
         val titleMd5 = MD5Utils.md5Encode16(this.textChapter?.chapter?.title ?: "")
         FileUtils.listDirsAndFiles(ttsFolderPath)?.forEach {
         FileUtils.listDirsAndFiles(ttsFolderPath)?.forEach {
             val isSilentSound = it.length() == 2160L
             val isSilentSound = it.length() == 2160L