Update HttpReadAloudService.kt
This commit is contained in:
1 parent
ac65fd620d
commit
0b1a8294cb
1 file changed
+81
-32
@@ -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 book = ReadBook.book ?: return
|
||||||
val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)
|
// 获取当前章节索引
|
||||||
.splitToSequence("\n")
|
val currentIdx = ReadBook.textChapter?.chapterIndex ?: return
|
||||||
.filter { it.isNotEmpty() }
|
// 获取用户设置的预加载数量 (例如 50)
|
||||||
.take(AppConfig.audioPreDownloadNum) // <--- 修改:读取配置
|
val limit = AppConfig.audioPreDownloadNum
|
||||||
.toList()
|
|
||||||
contentList.forEach { content ->
|
// 循环:从下一章开始,往后找 limit 个章节
|
||||||
|
for (i in 1..limit) {
|
||||||
currentCoroutineContext().ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
val fileName = md5SpeakFileName(content, textChapter)
|
|
||||||
val speakText = content.replace(AppPattern.notReadAloudRegex, "")
|
// 计算目标章节索引
|
||||||
if (speakText.isEmpty()) {
|
val targetIndex = currentIdx + i
|
||||||
createSilentSound(fileName)
|
|
||||||
} else if (!hasSpeakFile(fileName)) {
|
// 查数据库获取章节信息 (如果超出了总章数会返回空,就停止)
|
||||||
runCatching {
|
val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, targetIndex) ?: break
|
||||||
val inputStream = getSpeakStream(httpTts, speakText)
|
|
||||||
if (inputStream != null) {
|
// 获取章节内容 (这里假设已缓存文本,或者尝试读取)
|
||||||
createSpeakFile(fileName, inputStream)
|
val contentString = BookHelp.getContent(book, chapter)
|
||||||
} else {
|
|
||||||
createSilentSound(fileName)
|
// 如果内容为空 (还没下载正文),就跳过,不强行下载正文以免阻塞
|
||||||
|
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 book = ReadBook.book ?: return
|
||||||
val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)
|
val currentIdx = ReadBook.textChapter?.chapterIndex ?: return
|
||||||
.splitToSequence("\n")
|
val limit = AppConfig.audioPreDownloadNum
|
||||||
.filter { it.isNotEmpty() }
|
|
||||||
.take(AppConfig.audioPreDownloadNum) // <--- 修改:读取配置
|
for (i in 1..limit) {
|
||||||
.toList()
|
|
||||||
contentList.forEach { content ->
|
|
||||||
currentCoroutineContext().ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
val fileName = md5SpeakFileName(content, textChapter)
|
val targetIndex = currentIdx + i
|
||||||
val speakText = content.replace(AppPattern.notReadAloudRegex, "")
|
val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, targetIndex) ?: break
|
||||||
val dataSourceFactory = createDataSourceFactory(httpTts, speakText)
|
val contentString = BookHelp.getContent(book, chapter)
|
||||||
val downloader = createDownloader(dataSourceFactory, fileName)
|
if (contentString.isNullOrEmpty()) continue
|
||||||
downloaderChannel.send(downloader)
|
|
||||||
|
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)
|
||||||
|
|||||||
Reference in new issue
Block a user