[优化] 优化听书相关代码

This commit is contained in:
HapeLee
2026-01-17 06:39:29 +08:00
committed by Kudomaga
parent 65acfd9f51
commit a5a88fb7b2
12 changed files with 123 additions and 87 deletions
@@ -69,6 +69,7 @@ object PreferKey {
const val webDavAccount = "web_dav_account"
const val webDavPassword = "web_dav_password"
const val webDavDir = "webDavDir"
const val webServiceAutoStart = "webServiceAutoStart"
const val enableCustomExport = "enableCustomExport"
const val exportToWebDav = "webDavCacheBackup"
const val exportNoChapterName = "exportNoChapterName"
@@ -243,4 +244,6 @@ object PreferKey {
const val showTip = "showTip"
const val sliderVibrator = "sliderVibrator"
const val audioCacheCleanTime = "audioCacheCleanTime"
const val audioPreDownloadNum = "audioPreDownloadNum"
}
@@ -20,9 +20,6 @@ import io.legado.app.utils.removePref
import io.legado.app.utils.sysConfiguration
import io.legado.app.utils.toastOnUi
import splitties.init.appCtx
// 【新增引用】为了支持文件清理操作
import java.io.File
import io.legado.app.utils.FileUtils
@Suppress("MemberVisibilityCanBePrivate", "ConstPropertyName")
object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
@@ -60,7 +57,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
var optimizeRender = CanvasRecorderFactory.isSupport
&& appCtx.getPrefBoolean(PreferKey.optimizeRender, false)
var recordLog = appCtx.getPrefBoolean(PreferKey.recordLog)
var webServiceAutoStart = appCtx.getPrefBoolean(PreferKey.webServiceAutoStart)
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
@@ -976,39 +973,16 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
appCtx.putPrefBoolean(PreferKey.sliderVibrator, value)
}
// ================= 自定义功能区域 Start =================
// 1. 听书预加载数量
// 逻辑:从设置里读取字符串,转成数字。如果读不到,默认返回 10。
val audioPreDownloadNum: Int
get() {
// 注意:EditTextPreference 保存的是 String
val str = appCtx.getPrefString("audioPreDownloadNum")
return str?.toIntOrNull() ?: 10
}
get() = appCtx.getPrefInt(PreferKey.audioPreDownloadNum, 10)
val audioCacheCleanTimeOrgin: Int
get() = appCtx.getPrefInt(PreferKey.audioCacheCleanTime, 10)
// 2. 音频缓存保留时间 (返回毫秒)
// 逻辑:用户输入的是“分钟”,我们在这里把它乘以 60000 变成“毫秒”。
val audioCacheCleanTime: Long
get() {
val str = appCtx.getPrefString("audioCacheCleanTime")
val minutes = str?.toLongOrNull() ?: 10L // 默认 10 分钟
return minutes * 60 * 1000L
val str = appCtx.getPrefInt(PreferKey.audioCacheCleanTime, 10)
return str * 60 * 1000L
}
/**
* 【新增】手动清理 TTS 缓存
* 这个方法会自动寻找 externalCacheDir(外部存储),如果找不到再找内部。
* 确保和 Service 里的逻辑保持一致。
*/
fun clearTtsCache() {
val baseDir = appCtx.externalCacheDir ?: appCtx.cacheDir
// 清理音频文件
FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS")
// 清理数据库索引 (可选,建议一起清,防止索引还在但文件没了)
FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS_cache")
}
// ================= 自定义功能区域 End =================
}
@@ -65,11 +65,9 @@ import java.io.File
import java.io.InputStream
import java.net.ConnectException
import java.net.SocketTimeoutException
import kotlin.coroutines.coroutineContext
/**
* 在线朗读
* (完美匹配版:修复文件名MD5不一致 + 修复缓存不可见 + 修复0分钟不彻底清理)
*/
@SuppressLint("UnsafeOptInUsageError")
class HttpReadAloudService : BaseReadAloudService(),
@@ -78,7 +76,7 @@ class HttpReadAloudService : BaseReadAloudService(),
ExoPlayer.Builder(this).build()
}
// 【修改点1】改为外部存储,方便你查看文件,确认是否生成
// 改为外部存储
private val ttsFolderPath: String by lazy {
val baseDir = externalCacheDir ?: cacheDir
baseDir.absolutePath + File.separator + "httpTTS" + File.separator
@@ -454,13 +452,10 @@ class HttpReadAloudService : BaseReadAloudService(),
}
/**
* 【核心修复点】生成音频文件名
* 1. 之前的问题:textChapter.title 可能是 "第一章 内容" (带格式),而数据库里是 "第一章 内容" (不带格式)。
* 2. 现在的做法:强制使用 textChapter.chapter.title (数据库原始标题)。
* 3. 结果:前台 (播放) 和后台 (下载) 用的标题来源完全一致,MD5 绝对匹配,不会再重复下载了!
* 生成音频文件名
*/
private fun md5SpeakFileName(content: String, textChapter: TextChapter? = this.textChapter): String {
val titleToUse = textChapter?.chapter?.title ?: "" // 强制取“核芯”标题
val titleToUse = textChapter?.chapter?.title ?: ""
return MD5Utils.md5Encode16(titleToUse) + "_" +
MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content")
}
@@ -492,7 +487,7 @@ class HttpReadAloudService : BaseReadAloudService(),
/**
* 移除缓存文件
* 修复逻辑:如果时间设置为0,则不再保护当前章节,退出即全删。
* 如果时间设置为0,则不再保护当前章节,退出即全删。
*/
private fun removeCacheFile() {
val keepTime = AppConfig.audioCacheCleanTime
@@ -1,5 +1,6 @@
package io.legado.app.service
// ——————【新增引用】——————
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
@@ -21,6 +22,7 @@ import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.postEvent
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.putPrefBoolean
import io.legado.app.utils.sendToClip
import io.legado.app.utils.servicePendingIntent
import io.legado.app.utils.startForegroundServiceCompat
@@ -33,8 +35,6 @@ import splitties.init.appCtx
import splitties.systemservices.powerManager
import splitties.systemservices.wifiManager
import java.io.IOException
// ——————【新增引用】——————
import io.legado.app.utils.putPrefBoolean
class WebService : BaseService() {
@@ -43,9 +43,6 @@ class WebService : BaseService() {
var hostAddress = ""
fun start(context: Context) {
// ——————【修改开始】记录开启状态——————
appCtx.putPrefBoolean("web_service_auto", true)
// ——————【修改结束】——————
context.startService<WebService>()
}
@@ -55,9 +52,6 @@ class WebService : BaseService() {
}
fun stop(context: Context) {
// ——————【修改开始】记录关闭状态——————
appCtx.putPrefBoolean("web_service_auto", false)
// ——————【修改结束】——————
context.stopService<WebService>()
}
@@ -1,5 +1,8 @@
package io.legado.app.ui.book.read.config
//import io.legado.app.lib.theme.backgroundColor
//import io.legado.app.lib.theme.primaryColor
// 【新增引用】为了显示清理成功的提示
import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
@@ -18,16 +21,16 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.SelectItem
import io.legado.app.lib.prefs.SwitchPreference
import io.legado.app.lib.prefs.fragment.PreferenceFragment
//import io.legado.app.lib.theme.backgroundColor
//import io.legado.app.lib.theme.primaryColor
import io.legado.app.model.ReadAloud
import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.widget.number.NumberPickerDialog
import io.legado.app.utils.GSON
import io.legado.app.utils.StringUtils
import io.legado.app.utils.TTSCacheUtils
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.postEvent
import io.legado.app.utils.putPrefInt
import io.legado.app.utils.showDialogFragment
// 【新增引用】为了显示清理成功的提示
import io.legado.app.utils.toastOnUi
class ReadAloudConfigDialog : BasePrefDialogFragment() {
@@ -73,17 +76,16 @@ class ReadAloudConfigDialog : BasePrefDialogFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_aloud)
upSpeakEngineSummary()
upPreferenceSummary(PreferKey.audioPreDownloadNum)
upPreferenceSummary(PreferKey.audioCacheCleanTime)
findPreference<SwitchPreference>(PreferKey.pauseReadAloudWhilePhoneCalls)?.let {
it.isEnabled = AppConfig.ignoreAudioFocus
}
// 【新增代码】绑定清理缓存按钮事件
findPreference<Preference>("clear_cache")?.let {
it.summary = getString(R.string.clear_cache)
it.setOnPreferenceClickListener {
// 调用 AppConfig 中的万能清理函数 (自动处理外部存储)
AppConfig.clearTtsCache()
// 弹出提示
TTSCacheUtils.clearTtsCache()
toastOnUi("音频缓存已清理")
true
}
@@ -107,6 +109,38 @@ class ReadAloudConfigDialog : BasePrefDialogFragment() {
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference.key) {
PreferKey.audioPreDownloadNum -> {
NumberPickerDialog(requireContext())
.setTitle(getString(R.string.read_aloud_preload))
.setMaxValue(50)
.setMinValue(0)
.setValue(10)
.setCustomButton((R.string.btn_default_s)) {
putPrefInt(PreferKey.audioPreDownloadNum, 10)
upPreferenceSummary(PreferKey.audioPreDownloadNum)
}
.show {
putPrefInt(PreferKey.audioPreDownloadNum, it)
upPreferenceSummary(PreferKey.audioPreDownloadNum)
}
}
PreferKey.audioCacheCleanTime -> {
NumberPickerDialog(requireContext())
.setTitle(getString(R.string.audio_cache_clean_time))
.setMaxValue(50)
.setMinValue(0)
.setValue(1)
.setCustomButton((R.string.btn_default_s)) {
putPrefInt(PreferKey.audioCacheCleanTime, 10)
upPreferenceSummary(PreferKey.audioCacheCleanTime)
}
.show {
putPrefInt(PreferKey.audioCacheCleanTime, it)
upPreferenceSummary(PreferKey.audioCacheCleanTime)
}
}
PreferKey.ttsEngine -> showDialogFragment(SpeakEngineDialog())
"sysTtsConfig" -> IntentHelp.openTTSSetting()
}
@@ -139,12 +173,34 @@ class ReadAloudConfigDialog : BasePrefDialogFragment() {
preference.summary = if (index >= 0) preference.entries[index] else null
}
else -> {
preference?.summary = value
}
}
}
private fun upPreferenceSummary(preferenceKey: String, value: String? = null) {
val preference = findPreference<Preference>(preferenceKey) ?: return
when (preferenceKey) {
PreferKey.audioPreDownloadNum -> {
preference.summary = getString(
R.string.read_aloud_preload_summary,
AppConfig.audioPreDownloadNum
)
}
PreferKey.audioCacheCleanTime -> {
preference.summary = getString(
R.string.audio_cache_clean_time_summary,
AppConfig.audioCacheCleanTimeOrgin
)
}
else -> preference.summary = value
}
}
override fun upSpeakEngineSummary() {
upPreferenceSummary(
findPreference(PreferKey.ttsEngine),
@@ -1,5 +1,6 @@
package io.legado.app.ui.book.read.config
//import io.legado.app.lib.theme.primaryColor
import android.content.Context
import android.os.Bundle
import android.view.MenuItem
@@ -24,15 +25,14 @@ import io.legado.app.help.DirectLinkUpload
import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.SelectItem
import io.legado.app.lib.dialogs.alert
//import io.legado.app.lib.theme.primaryColor
import io.legado.app.model.ReadAloud
import io.legado.app.model.ReadBook
import io.legado.app.ui.association.ImportHttpTtsDialog
import io.legado.app.ui.file.HandleFileContract
import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.utils.ACache
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.TTSCacheUtils
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.gone
import io.legado.app.utils.isAbsUrl
@@ -49,7 +49,6 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import java.io.File
/**
* tts引擎管理
@@ -236,19 +235,10 @@ class SpeakEngineDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_recycl
adapter.notifyItemRangeChanged(adapter.getHeaderCount(), adapter.itemCount)
}
/**
* 【核心修改点】清理缓存方法
* 原来:只删除 cacheDir (内部存储)
* 现在:调用 AppConfig.clearTtsCache(),自动处理 externalCacheDir (外部存储)
*/
fun clearCache() {
execute {
ReadAloud.upReadAloudClass()
// 调用万能清理函数
AppConfig.clearTtsCache()
// 显示成功提示
TTSCacheUtils.clearTtsCache()
toastOnUi(R.string.clear_cache_success)
}
}
@@ -42,6 +42,7 @@ import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.Backup
import io.legado.app.help.update.AppUpdateGitHub
import io.legado.app.lib.dialogs.alert
import io.legado.app.service.WebService
import io.legado.app.ui.about.CrashLogsDialog
import io.legado.app.ui.about.UpdateDialog
import io.legado.app.ui.book.read.ReadBookActivity
@@ -71,7 +72,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import io.legado.app.service.WebService
/**
* 主界面
@@ -135,13 +135,11 @@ open class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
binding.viewPagerMain.fitsSystemWindows = true
// 其他初始化逻辑
setupBackCallback()
// ——————【修改开始】——————
// 智能自启:如果上次是手动开启状态(web_service_auto 为 true),则自启
if (getPrefBoolean("web_service_auto", false)) {
if (AppConfig.webServiceAutoStart) {
WebService.startForeground(this)
}
// ——————【修改结束】——————
upBottomMenu()
initView()
@@ -0,0 +1,16 @@
package io.legado.app.utils
import splitties.init.appCtx
import java.io.File
object TTSCacheUtils {
fun clearTtsCache() {
val baseDir = appCtx.externalCacheDir ?: appCtx.cacheDir
// 清理音频文件
FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS")
// 清理数据库索引 (可选,建议一起清,防止索引还在但文件没了)
FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS_cache")
}
}