Merge pull request #1 from gedoor/master

update
pull/784/head^2
litcc 4 years ago committed by GitHub
commit b4c0f1f018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 127
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  2. 64
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  3. 10
      app/src/main/res/layout/item_bookshelf_list.xml
  4. 6
      app/src/main/res/menu/book_source.xml
  5. 1
      app/src/main/res/values-zh-rHK/strings.xml
  6. 1
      app/src/main/res/values-zh-rTW/strings.xml
  7. 1
      app/src/main/res/values-zh/strings.xml
  8. 1
      app/src/main/res/values/strings.xml

@ -12,11 +12,18 @@ import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.FileUtils
import io.legado.app.utils.LogUtils
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.postEvent
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.jetbrains.anko.collections.forEachWithIndex
import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.util.*
class HttpReadAloudService : BaseReadAloudService(),
MediaPlayer.OnPreparedListener,
@ -55,12 +62,17 @@ class HttpReadAloudService : BaseReadAloudService(),
override fun play() {
if (contentList.isEmpty()) return
if (nowSpeak == 0) {
downloadAudio()
} else {
val file = getSpeakFile(nowSpeak)
if (file.exists()) {
playAudio(FileInputStream(file).fd)
ReadAloud.httpTTS?.let {
val fileName = md5SpeakFileName(it.url, AppConfig.ttsSpeechRate.toString(), contentList[nowSpeak])
if (nowSpeak == 0) {
downloadAudio()
} else {
val file = getSpeakFileAsMd5(fileName)
if (file.exists()) {
playAudio(FileInputStream(file).fd)
} else {
downloadAudio()
}
}
}
}
@ -68,28 +80,54 @@ class HttpReadAloudService : BaseReadAloudService(),
private fun downloadAudio() {
task?.cancel()
task = execute {
FileUtils.deleteFile(ttsFolder)
for (index in 0 until contentList.size) {
if (isActive) {
ReadAloud.httpTTS?.let {
AnalyzeUrl(
it.url,
speakText = contentList[index],
speakSpeed = AppConfig.ttsSpeechRate
).getByteArray().let { bytes ->
if (isActive) {
val file = getSpeakFile(index)
file.writeBytes(bytes)
if (index == nowSpeak) {
@Suppress("BlockingMethodInNonBlockingContext")
val fis = FileInputStream(file)
playAudio(fis.fd)
removeCacheFile()
ReadAloud.httpTTS?.let {
contentList.forEachWithIndex { index, item ->
if (isActive) {
val fileName =
md5SpeakFileName(it.url, AppConfig.ttsSpeechRate.toString(), item)
if (hasSpeakFile(fileName)) { //已经下载好的语音缓存
if (index == nowSpeak) {
val file = getSpeakFileAsMd5(fileName)
@Suppress("BlockingMethodInNonBlockingContext")
val fis = FileInputStream(file)
playAudio(fis.fd)
}
} else if (hasSpeakCacheFile(fileName)) { //缓存文件还在,可能还没下载完
return@let
} else { //没有下载并且没有缓存文件
try {
createSpeakCacheFile(fileName)
AnalyzeUrl(
it.url,
speakText = item,
speakSpeed = AppConfig.ttsSpeechRate
).getByteArray().let { bytes ->
if (isActive) {
val file = getSpeakFileAsMd5(fileName)
//val file = getSpeakFile(index)
file.writeBytes(bytes)
if (index == nowSpeak) {
@Suppress("BlockingMethodInNonBlockingContext")
val fis = FileInputStream(file)
playAudio(fis.fd)
}
}
}
} catch (e: SocketTimeoutException) {
removeSpeakCacheFile(fileName)
// delay(2000)
// downloadAudio()
} catch (e: ConnectException) {
removeSpeakCacheFile(fileName)
} catch (e: Exception) {
removeSpeakCacheFile(fileName)
}
}
}
} else {
break
}
}
}
@ -110,10 +148,47 @@ class HttpReadAloudService : BaseReadAloudService(),
}
}
private fun getSpeakFile(index: Int = nowSpeak): File {
return FileUtils.createFileIfNotExist("${ttsFolder}${File.separator}${index}.mp3")
private fun speakFilePath() = ttsFolder + File.separator
private fun md5SpeakFileName(url: String, ttsConfig: String, content: String): String {
return MD5Utils.md5Encode16(textChapter!!.title) + "_" + MD5Utils.md5Encode16("$url-|-$ttsConfig-|-$content")
}
private fun hasSpeakFile(name: String) =
FileUtils.exist("${speakFilePath()}$name.mp3")
private fun hasSpeakCacheFile(name: String) =
FileUtils.exist("${speakFilePath()}$name.mp3.cache")
private fun createSpeakCacheFile(name: String): File =
FileUtils.createFileWithReplace("${speakFilePath()}$name.mp3.cache")
private fun removeSpeakCacheFile(name: String) {
FileUtils.delete("${speakFilePath()}$name.mp3.cache")
}
private fun getSpeakFileAsMd5(name: String): File =
FileUtils.createFileIfNotExist("${speakFilePath()}$name.mp3")
private fun removeCacheFile() {
FileUtils.listDirsAndFiles(speakFilePath())?.forEach {
if (it == null) {
return@forEach
}
if (Regex(""".+\.mp3$""").matches(it.name)) { //mp3缓存文件
val reg =
"""^${MD5Utils.md5Encode16(textChapter!!.title)}_[a-z0-9]{16}\.mp3$""".toRegex()
if (!reg.matches(it.name)) {
FileUtils.deleteFile(it.absolutePath)
}
} else {
if (Date().time - it.lastModified() > 30000) {
FileUtils.deleteFile(it.absolutePath)
}
}
}
}
override fun pauseReadAloud(pause: Boolean) {
super.pauseReadAloud(pause)
mediaPlayer.pause()

@ -130,6 +130,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
sortCheck(Sort.Update)
initLiveDataBookSource(searchView.query?.toString())
}
R.id.menu_sort_enable -> {
item.isChecked = true
sortCheck(Sort.Enable)
initLiveDataBookSource(searchView.query?.toString())
}
R.id.menu_enabled_group -> {
searchView.setQuery(getString(R.string.enabled), true)
}
@ -187,29 +192,44 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
else -> {
App.db.bookSourceDao.liveDataSearch("%$searchKey%")
}
}
bookSourceLiveDate?.observe(this, { data ->
val sourceList =
if (sortAscending) when (sort) {
Sort.Weight -> data.sortedBy { it.weight }
Sort.Name -> data.sortedWith { o1, o2 ->
o1.bookSourceName.cnCompare(o2.bookSourceName)
}.apply {
observe(this@BookSourceActivity, { data ->
val sourceList =
if (sortAscending) when (sort) {
Sort.Weight -> data.sortedBy { it.weight }
Sort.Name -> data.sortedWith { o1, o2 ->
o1.bookSourceName.cnCompare(o2.bookSourceName)
}
Sort.Url -> data.sortedBy { it.bookSourceUrl }
Sort.Update -> data.sortedByDescending { it.lastUpdateTime }
Sort.Enable -> data.sortedWith { o1, o2 ->
var sort = -o1.enabled.compareTo(o2.enabled)
if (sort == 0) {
sort = o1.bookSourceName.cnCompare(o2.bookSourceName)
}
sort
}
else -> data
}
Sort.Url -> data.sortedBy { it.bookSourceUrl }
Sort.Update -> data.sortedByDescending { it.lastUpdateTime }
else -> data
}
else when (sort) {
Sort.Weight -> data.sortedByDescending { it.weight }
Sort.Name -> data.sortedWith { o1, o2 ->
o2.bookSourceName.cnCompare(o1.bookSourceName)
else when (sort) {
Sort.Weight -> data.sortedByDescending { it.weight }
Sort.Name -> data.sortedWith { o1, o2 ->
o2.bookSourceName.cnCompare(o1.bookSourceName)
}
Sort.Url -> data.sortedByDescending { it.bookSourceUrl }
Sort.Update -> data.sortedBy { it.lastUpdateTime }
Sort.Enable -> data.sortedWith { o1, o2 ->
var sort = o1.enabled.compareTo(o2.enabled)
if (sort == 0) {
sort = o1.bookSourceName.cnCompare(o2.bookSourceName)
}
sort
}
else -> data.reversed()
}
Sort.Url -> data.sortedByDescending { it.bookSourceUrl }
Sort.Update -> data.sortedBy { it.lastUpdateTime }
else -> data.reversed()
}
adapter.setItems(sourceList, adapter.diffItemCallback)
})
adapter.setItems(sourceList, adapter.diffItemCallback)
})
}
}
private fun showHelp() {
@ -483,6 +503,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
}
enum class Sort {
Default, Name, Url, Weight, Update
Default, Name, Url, Weight, Update, Enable
}
}

@ -11,8 +11,8 @@
<io.legado.app.ui.widget.image.CoverImageView
android:id="@+id/iv_cover"
android:layout_width="60dp"
android:layout_height="80dp"
android:layout_width="66dp"
android:layout_height="90dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="12dp"
@ -97,7 +97,7 @@
android:textSize="13sp"
app:layout_constraintBottom_toTopOf="@+id/tv_read"
app:layout_constraintLeft_toRightOf="@+id/iv_author"
app:layout_constraintRight_toLeftOf="@id/fl_has_new"
app:layout_constraintRight_toRightOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_name"
tools:ignore="RtlSymmetry" />
@ -126,7 +126,7 @@
android:textSize="13sp"
app:layout_constraintBottom_toTopOf="@id/tv_last"
app:layout_constraintLeft_toRightOf="@+id/iv_read"
app:layout_constraintRight_toLeftOf="@id/fl_has_new"
app:layout_constraintRight_toRightOf="@+id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_author" />
<androidx.appcompat.widget.AppCompatImageView
@ -154,7 +154,7 @@
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/iv_last"
app:layout_constraintRight_toLeftOf="@id/fl_has_new"
app:layout_constraintRight_toRightOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_read" />
<View

@ -50,6 +50,12 @@
android:id="@+id/menu_sort_time"
android:checkable="true"
android:title="@string/sort_by_lastUpdateTime" />
<item
android:id="@+id/menu_sort_enable"
android:checkable="true"
android:title="@string/sort_enable" />
</group>
</menu>

@ -799,5 +799,6 @@
<string name="import_bookshelf">导入书单</string>
<string name="pre_download">预下载</string>
<string name="pre_download_s">预先下载10章正文</string>
<string name="sort_enable">启用排序</string>
</resources>

@ -800,5 +800,6 @@
<string name="import_bookshelf">导入书单</string>
<string name="pre_download">预下载</string>
<string name="pre_download_s">预先下载10章正文</string>
<string name="sort_enable">启用排序</string>
</resources>

@ -803,5 +803,6 @@
<string name="import_bookshelf">导入书单</string>
<string name="pre_download">预下载</string>
<string name="pre_download_s">预先下载10章正文</string>
<string name="sort_enable">启用排序</string>
</resources>

@ -806,5 +806,6 @@
<string name="import_bookshelf">导入书单</string>
<string name="pre_download">预下载</string>
<string name="pre_download_s">预先下载10章正文</string>
<string name="sort_enable">启用排序</string>
</resources>

Loading…
Cancel
Save