This commit is contained in:
Horis
2024-07-30 12:39:04 +08:00
parent 2e43b17fc8
commit 0782daf0f9
4 changed files with 42 additions and 34 deletions
@@ -21,6 +21,7 @@ import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils import io.legado.app.utils.StringUtils
import io.legado.app.utils.SvgUtils import io.legado.app.utils.SvgUtils
import io.legado.app.utils.UrlUtil import io.legado.app.utils.UrlUtil
import io.legado.app.utils.createFileIfNotExist
import io.legado.app.utils.exists import io.legado.app.utils.exists
import io.legado.app.utils.externalFiles import io.legado.app.utils.externalFiles
import io.legado.app.utils.getFile import io.legado.app.utils.getFile
@@ -29,10 +30,10 @@ import io.legado.app.utils.onEachParallel
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.JaccardSimilarity import org.apache.commons.text.similarity.JaccardSimilarity
import splitties.init.appCtx import splitties.init.appCtx
@@ -55,7 +56,7 @@ object BookHelp {
private const val cacheFolderName = "book_cache" private const val cacheFolderName = "book_cache"
private const val cacheImageFolderName = "images" private const val cacheImageFolderName = "images"
private const val cacheEpubFolderName = "epub" private const val cacheEpubFolderName = "epub"
private val downloadImages = ConcurrentHashMap.newKeySet<String>() private val downloadImages = ConcurrentHashMap<String, Mutex>()
val cachePath = FileUtils.getPath(downloadDir, cacheFolderName) val cachePath = FileUtils.getPath(downloadDir, cacheFolderName)
@@ -173,15 +174,18 @@ object BookHelp {
src: String, src: String,
chapter: BookChapter? = null chapter: BookChapter? = null
) { ) {
while (downloadImages.contains(src)) { if (isImageExist(book, src)) {
delay(100)
}
if (getImage(book, src).exists()) {
return return
} }
downloadImages.add(src) val mutex = synchronized(this) {
val analyzeUrl = AnalyzeUrl(src, source = bookSource) downloadImages.getOrPut(src) { Mutex() }
}
mutex.lock()
try { try {
if (isImageExist(book, src)) {
return
}
val analyzeUrl = AnalyzeUrl(src, source = bookSource)
val bytes = analyzeUrl.getByteArrayAwait() val bytes = analyzeUrl.getByteArrayAwait()
//某些图片被加密,需要进一步解密 //某些图片被加密,需要进一步解密
ImageUtils.decode( ImageUtils.decode(
@@ -193,13 +197,7 @@ object BookHelp {
// throw NoStackTraceException("数据异常") // throw NoStackTraceException("数据异常")
AppLog.put("${book.name} ${chapter?.title} 图片 $src 下载错误 数据异常") AppLog.put("${book.name} ${chapter?.title} 图片 $src 下载错误 数据异常")
} }
FileUtils.createFileIfNotExist( writeImage(book, src, it)
downloadDir,
cacheFolderName,
book.getFolderName(),
cacheImageFolderName,
"${MD5Utils.md5Encode16(src)}.${getImageSuffix(src)}"
).writeBytes(it)
} }
} catch (e: Exception) { } catch (e: Exception) {
coroutineContext.ensureActive() coroutineContext.ensureActive()
@@ -207,6 +205,7 @@ object BookHelp {
AppLog.put(msg, e) AppLog.put(msg, e)
} finally { } finally {
downloadImages.remove(src) 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 { fun getImageSuffix(src: String): String {
return UrlUtil.getSuffix(src, "jpg") return UrlUtil.getSuffix(src, "jpg")
} }
@@ -94,7 +94,7 @@ object ImageProvider {
): File { ): File {
return withContext(IO) { return withContext(IO) {
val vFile = BookHelp.getImage(book, src) val vFile = BookHelp.getImage(book, src)
if (!vFile.exists()) { if (!BookHelp.isImageExist(book, src)) {
val inputStream = when { val inputStream = when {
book.isEpub -> EpubFile.getImage(book, src) book.isEpub -> EpubFile.getImage(book, src)
book.isPdf -> PdfFile.getImage(book, src) book.isPdf -> PdfFile.getImage(book, src)
@@ -8,6 +8,7 @@ import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.Timeline
import androidx.media3.database.StandaloneDatabaseProvider import androidx.media3.database.StandaloneDatabaseProvider
import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSource
import androidx.media3.datasource.cache.CacheDataSink import androidx.media3.datasource.cache.CacheDataSink
@@ -75,7 +76,7 @@ class HttpReadAloudService : BaseReadAloudService(),
private val cache by lazy { private val cache by lazy {
SimpleCache( SimpleCache(
File(cacheDir, "httpTTS_cache"), File(cacheDir, "httpTTS_cache"),
LeastRecentlyUsedCacheEvictor((50 * 1024 * 1024).toLong()), LeastRecentlyUsedCacheEvictor(128 * 1024 * 1024),
StandaloneDatabaseProvider(appCtx) StandaloneDatabaseProvider(appCtx)
) )
} }
@@ -182,15 +183,7 @@ class HttpReadAloudService : BaseReadAloudService(),
val file = getSpeakFileAsMd5(fileName) val file = getSpeakFileAsMd5(fileName)
val mediaItem = MediaItem.fromUri(Uri.fromFile(file)) val mediaItem = MediaItem.fromUri(Uri.fromFile(file))
launch(Main) { launch(Main) {
if (exoPlayer.playbackState == Player.STATE_ENDED) {
exoPlayer.stop()
exoPlayer.clearMediaItems()
}
exoPlayer.addMediaItem(mediaItem) exoPlayer.addMediaItem(mediaItem)
if (!exoPlayer.isPlaying) {
exoPlayer.playWhenReady = !pause
exoPlayer.prepare()
}
} }
} }
preDownloadAudios(httpTts) preDownloadAudios(httpTts)
@@ -256,15 +249,7 @@ class HttpReadAloudService : BaseReadAloudService(),
downloaderChannel.send(downloader) downloaderChannel.send(downloader)
val mediaSource = createMediaSource(dataSourceFactory, fileName) val mediaSource = createMediaSource(dataSourceFactory, fileName)
launch(Main) { launch(Main) {
if (exoPlayer.playbackState == Player.STATE_ENDED) {
exoPlayer.stop()
exoPlayer.clearMediaItems()
}
exoPlayer.addMediaSource(mediaSource) exoPlayer.addMediaSource(mediaSource)
if (!exoPlayer.isPlaying) {
exoPlayer.playWhenReady = !pause
exoPlayer.prepare()
}
} }
} }
preDownloadAudiosStream(httpTts, downloaderChannel) preDownloadAudiosStream(httpTts, downloaderChannel)
@@ -540,10 +525,24 @@ class HttpReadAloudService : BaseReadAloudService(),
// 结束 // 结束
playErrorNo = 0 playErrorNo = 0
updateNextPos() 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) { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) { if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
@@ -35,7 +35,7 @@ fun File.listFileDocs(filter: FileDocFilter? = null): ArrayList<FileDoc> {
fun File.createFileIfNotExist(): File { fun File.createFileIfNotExist(): File {
if (!exists()) { if (!exists()) {
parentFile?.createFileIfNotExist() parentFile?.createFolderIfNotExist()
createNewFile() createNewFile()
} }
return this return this