Merge pull request #1 from gedoor/master

merge
pull/42/head
Celeter 6 years ago committed by GitHub
commit 0cd13d7c0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/src/main/java/io/legado/app/data/api/IHttpPostApi.kt
  2. 5
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  3. 5
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  4. 3
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  5. 125
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  6. 20
      app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt
  7. 4
      app/src/main/java/io/legado/app/utils/EncoderUtils.kt
  8. 37
      app/src/main/java/io/legado/app/utils/FileUtils.kt

@ -45,9 +45,9 @@ interface IHttpPostApi {
@FormUrlEncoded @FormUrlEncoded
@POST @POST
fun postMapByte( fun postMapByteAsync(
@Url url: String, @Url url: String,
@FieldMap(encoded = true) fieldMap: Map<String, String>, @FieldMap(encoded = true) fieldMap: Map<String, String>,
@HeaderMap headers: Map<String, String> @HeaderMap headers: Map<String, String>
): Call<ByteArray> ): Deferred<Response<ByteArray>>
} }

@ -1,5 +1,6 @@
package io.legado.app.help package io.legado.app.help
import android.util.Base64
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.EncoderUtils import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
@ -33,6 +34,10 @@ object JsExtensions {
return EncoderUtils.base64Encode(str) return EncoderUtils.base64Encode(str)
} }
fun base64Encode(str: String, flags: Int = Base64.NO_WRAP): String? {
return EncoderUtils.base64Encode(str, flags)
}
fun strToMd5By32(str: String): String? { fun strToMd5By32(str: String): String? {
return MD5Utils.strToMd5By32(str) return MD5Utils.strToMd5By32(str)
} }

@ -1,6 +1,7 @@
package io.legado.app.model.analyzeRule package io.legado.app.model.analyzeRule
import android.text.TextUtils import android.text.TextUtils
import android.util.Base64
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.constant.AppConst.SCRIPT_ENGINE import io.legado.app.constant.AppConst.SCRIPT_ENGINE
import io.legado.app.constant.Pattern.JS_PATTERN import io.legado.app.constant.Pattern.JS_PATTERN
@ -580,6 +581,10 @@ class AnalyzeRule(private var book: BaseBook? = null) {
return EncoderUtils.base64Encode(str) return EncoderUtils.base64Encode(str)
} }
fun base64Encode(str: String, flags: Int = Base64.NO_WRAP): String? {
return EncoderUtils.base64Encode(str, flags)
}
fun strToMd5By32(str: String): String? { fun strToMd5By32(str: String): String? {
return MD5Utils.strToMd5By32(str) return MD5Utils.strToMd5By32(str)
} }

@ -101,7 +101,8 @@ abstract class BaseReadAloudService : BaseService(),
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
private fun newReadAloud(dataKey: String?, play: Boolean) { @CallSuper
open fun newReadAloud(dataKey: String?, play: Boolean) {
dataKey?.let { dataKey?.let {
textChapter = IntentDataHelp.getData(dataKey) as? TextChapter textChapter = IntentDataHelp.getData(dataKey) as? TextChapter
textChapter?.let { textChapter -> textChapter?.let { textChapter ->

@ -7,15 +7,16 @@ import io.legado.app.data.api.IHttpPostApi
import io.legado.app.help.FileHelp import io.legado.app.help.FileHelp
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.utils.LogUtils
import io.legado.app.utils.getPrefInt import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
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.Dispatchers.Main import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.toast
import java.io.File import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream import java.io.FileInputStream
import java.net.URLEncoder import java.net.URLEncoder
@ -24,10 +25,14 @@ class HttpReadAloudService : BaseReadAloudService(),
MediaPlayer.OnErrorListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener { MediaPlayer.OnCompletionListener {
private var mediaPlayer = MediaPlayer() private val mediaPlayer = MediaPlayer()
private lateinit var ttsFolder: String
private var job: Job? = null
private var playingIndex = -1
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ttsFolder = cacheDir.absolutePath + File.separator + "bdTts"
mediaPlayer.setOnErrorListener(this) mediaPlayer.setOnErrorListener(this)
mediaPlayer.setOnPreparedListener(this) mediaPlayer.setOnPreparedListener(this)
mediaPlayer.setOnCompletionListener(this) mediaPlayer.setOnCompletionListener(this)
@ -38,18 +43,72 @@ class HttpReadAloudService : BaseReadAloudService(),
mediaPlayer.release() mediaPlayer.release()
} }
private fun getAudioBody(): Map<String, String> { override fun newReadAloud(dataKey: String?, play: Boolean) {
mediaPlayer.reset()
job?.cancel()
playingIndex = -1
super.newReadAloud(dataKey, play)
}
override fun play() {
if (contentList.isEmpty()) return
if (nowSpeak == 0) {
downloadAudio()
} else {
val file = getSpeakFile(nowSpeak)
if (file.exists()) {
playAudio(FileInputStream(file).fd)
}
}
}
private fun downloadAudio() {
job = launch(IO) {
FileHelp.deleteFile(ttsFolder)
for (index in 0 until contentList.size) {
if (isActive) {
val bytes = HttpHelper.getByteRetrofit("http://tts.baidu.com")
.create(IHttpPostApi::class.java)
.postMapByteAsync(
"http://tts.baidu.com/text2audio",
getAudioBody(contentList[index]), mapOf()
).await()
.body()
if (bytes != null && isActive) {
val file = getSpeakFile(index)
file.writeBytes(bytes)
if (index == nowSpeak) {
playAudio(FileInputStream(file).fd)
}
}
} else {
break
}
}
}
}
@Synchronized
private fun playAudio(fd: FileDescriptor) {
if (playingIndex != nowSpeak) {
playingIndex = nowSpeak
try {
mediaPlayer.reset()
mediaPlayer.setDataSource(fd)
mediaPlayer.prepareAsync()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun getSpeakFile(index: Int = nowSpeak): File {
return FileHelp.getFile("${ttsFolder}${File.separator}${index}.mp3")
}
private fun getAudioBody(content: String): Map<String, String> {
return mapOf( return mapOf(
Pair( Pair("tex", encodeTwo(content)),
"tex",
URLEncoder.encode(
URLEncoder.encode(
contentList[nowSpeak],
"UTF-8"
),
"UTF-8"
)
),
Pair("spd", ((getPrefInt("ttsSpeechRate", 25) + 5) / 5).toString()), Pair("spd", ((getPrefInt("ttsSpeechRate", 25) + 5) / 5).toString()),
Pair("per", getPrefString("ttsSpeechPer") ?: "0"), Pair("per", getPrefString("ttsSpeechPer") ?: "0"),
Pair("cuid", "baidu_speech_demo"), Pair("cuid", "baidu_speech_demo"),
@ -64,27 +123,11 @@ class HttpReadAloudService : BaseReadAloudService(),
) )
} }
override fun play() { private fun encodeTwo(content: String): String {
if (contentList.isEmpty()) return return try {
launch(IO) { URLEncoder.encode(URLEncoder.encode(content, "UTF-8"), "UTF-8")
if (requestFocus()) { } catch (e: Exception) {
val bytes = HttpHelper.getByteRetrofit("http://tts.baidu.com") " "
.create(IHttpPostApi::class.java)
.postMapByte("http://tts.baidu.com/text2audio", getAudioBody(), mapOf())
.execute().body()
if (bytes == null) {
withContext(Main) {
toast("访问失败")
}
} else {
val file =
FileHelp.getFile(cacheDir.absolutePath + File.separator + "bdTts.mp3")
file.writeBytes(bytes)
mediaPlayer.reset()
mediaPlayer.setDataSource(FileInputStream(file).fd)
mediaPlayer.prepareAsync()
}
}
} }
} }
@ -99,6 +142,13 @@ class HttpReadAloudService : BaseReadAloudService(),
} }
override fun upSpeechRate(reset: Boolean) { override fun upSpeechRate(reset: Boolean) {
job?.cancel()
mediaPlayer.reset()
for (i in 0 until nowSpeak) {
contentList.removeAt(0)
}
nowSpeak = 0
playingIndex = -1
play() play()
} }
@ -138,8 +188,9 @@ class HttpReadAloudService : BaseReadAloudService(),
} }
override fun onCompletion(mp: MediaPlayer?) { override fun onCompletion(mp: MediaPlayer?) {
LogUtils.d("播放完成", contentList[nowSpeak])
readAloudNumber += contentList[nowSpeak].length + 1 readAloudNumber += contentList[nowSpeak].length + 1
if (nowSpeak < contentList.size) { if (nowSpeak < contentList.lastIndex) {
nowSpeak++ nowSpeak++
play() play()
} else { } else {

@ -0,0 +1,20 @@
package io.legado.app.ui.filechooser
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
class FileChooserDialog : DialogFragment() {
companion object {
const val tag = "FileChooserDialog"
fun show(manager: FragmentManager) {
val fragment =
(manager.findFragmentByTag(tag) as? FileChooserDialog) ?: FileChooserDialog()
fragment.show(manager, tag)
}
}
}

@ -33,7 +33,7 @@ object EncoderUtils {
} }
} }
fun base64Encode(str: String): String? { fun base64Encode(str: String, flags: Int = Base64.NO_WRAP): String? {
return Base64.encodeToString(str.toByteArray(), Base64.DEFAULT) return Base64.encodeToString(str.toByteArray(), flags)
} }
} }

@ -48,8 +48,8 @@ object FileUtils {
val list = ArrayList<String>() val list = ArrayList<String>()
for (i in 0 until length) { for (i in 0 until length) {
val storageValume = Array.get(invokeVolumeList, i)//得到StorageVolume对象 val storageVolume = Array.get(invokeVolumeList, i)//得到StorageVolume对象
val path = getPath.invoke(storageValume) as String val path = getPath.invoke(storageVolume) as String
list.add(path) list.add(path)
} }
@ -103,11 +103,8 @@ object FileUtils {
val id = DocumentsContract.getDocumentId(uri) val id = DocumentsContract.getDocumentId(uri)
val split = id.split(":").dropLastWhile { it.isEmpty() }.toTypedArray() val split = id.split(":").dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0] val type = split[0]
if ("raw".equals( if ("raw".equals(type, ignoreCase = true)) {
type, //处理某些机型(比如Google Pixel )ID是raw:/storage/emulated/0/Download/c20f8664da05ab6b4644913048ea8c83.mp4
ignoreCase = true
)
) { //处理某些机型(比如Goole Pixel )ID是raw:/storage/emulated/0/Download/c20f8664da05ab6b4644913048ea8c83.mp4
return split[1] return split[1]
} }
@ -122,12 +119,10 @@ object FileUtils {
val type = split[0] val type = split[0]
var contentUri: Uri? = null var contentUri: Uri? = null
if ("image" == type) { when (type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI "image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) { "video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI "audio" -> contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
} }
val selection = "_id=?" val selection = "_id=?"
@ -136,21 +131,17 @@ object FileUtils {
return getDataColumn(context, contentUri, selection, selectionArgs) return getDataColumn(context, contentUri, selection, selectionArgs)
}// MediaProvider }// MediaProvider
// DownloadsProvider // DownloadsProvider
} else if ("content".equals(uri.scheme!!, ignoreCase = true)) { } else if ("content".equals(uri.scheme, ignoreCase = true)) {
// Return the remote address // Return the remote address
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn( return if (isGooglePhotosUri(uri))
context, uri.lastPathSegment
uri, else
null, getDataColumn(context, uri, null, null)
null
)
} else if ("file".equals(uri.scheme!!, ignoreCase = true)) { } else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path return uri.path
}// File }// File
// MediaStore (and general) // MediaStore (and general)
return null return null
} }

Loading…
Cancel
Save