优化
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user