|
@@ -1,7 +1,11 @@
|
|
|
package io.legado.app.service
|
|
package io.legado.app.service
|
|
|
|
|
|
|
|
import android.app.PendingIntent
|
|
import android.app.PendingIntent
|
|
|
-import android.media.MediaPlayer
|
|
|
|
|
|
|
+import android.net.Uri
|
|
|
|
|
+import com.google.android.exoplayer2.ExoPlayer
|
|
|
|
|
+import com.google.android.exoplayer2.MediaItem
|
|
|
|
|
+import com.google.android.exoplayer2.PlaybackException
|
|
|
|
|
+import com.google.android.exoplayer2.Player
|
|
|
import io.legado.app.R
|
|
import io.legado.app.R
|
|
|
import io.legado.app.constant.AppLog
|
|
import io.legado.app.constant.AppLog
|
|
|
import io.legado.app.constant.AppPattern
|
|
import io.legado.app.constant.AppPattern
|
|
@@ -19,21 +23,21 @@ import kotlinx.coroutines.sync.Mutex
|
|
|
import kotlinx.coroutines.sync.withLock
|
|
import kotlinx.coroutines.sync.withLock
|
|
|
import okhttp3.Response
|
|
import okhttp3.Response
|
|
|
import org.mozilla.javascript.WrappedException
|
|
import org.mozilla.javascript.WrappedException
|
|
|
-
|
|
|
|
|
import java.io.File
|
|
import java.io.File
|
|
|
-import java.io.FileDescriptor
|
|
|
|
|
-import java.io.FileInputStream
|
|
|
|
|
import java.net.ConnectException
|
|
import java.net.ConnectException
|
|
|
import java.net.SocketTimeoutException
|
|
import java.net.SocketTimeoutException
|
|
|
import java.util.*
|
|
import java.util.*
|
|
|
import javax.script.ScriptException
|
|
import javax.script.ScriptException
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 在线朗读
|
|
|
|
|
+ */
|
|
|
class HttpReadAloudService : BaseReadAloudService(),
|
|
class HttpReadAloudService : BaseReadAloudService(),
|
|
|
- MediaPlayer.OnPreparedListener,
|
|
|
|
|
- MediaPlayer.OnErrorListener,
|
|
|
|
|
- MediaPlayer.OnCompletionListener {
|
|
|
|
|
|
|
+ Player.Listener {
|
|
|
|
|
|
|
|
- private val mediaPlayer = MediaPlayer()
|
|
|
|
|
|
|
+ private val exoPlayer: ExoPlayer by lazy {
|
|
|
|
|
+ ExoPlayer.Builder(this).build()
|
|
|
|
|
+ }
|
|
|
private val ttsFolderPath: String by lazy {
|
|
private val ttsFolderPath: String by lazy {
|
|
|
cacheDir.absolutePath + File.separator + "httpTTS" + File.separator
|
|
cacheDir.absolutePath + File.separator + "httpTTS" + File.separator
|
|
|
}
|
|
}
|
|
@@ -45,19 +49,16 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
|
|
|
|
|
override fun onCreate() {
|
|
override fun onCreate() {
|
|
|
super.onCreate()
|
|
super.onCreate()
|
|
|
- mediaPlayer.setOnErrorListener(this)
|
|
|
|
|
- mediaPlayer.setOnPreparedListener(this)
|
|
|
|
|
- mediaPlayer.setOnCompletionListener(this)
|
|
|
|
|
|
|
+ exoPlayer.addListener(this)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
override fun onDestroy() {
|
|
|
super.onDestroy()
|
|
super.onDestroy()
|
|
|
task?.cancel()
|
|
task?.cancel()
|
|
|
- mediaPlayer.release()
|
|
|
|
|
|
|
+ exoPlayer.release()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun newReadAloud(play: Boolean) {
|
|
override fun newReadAloud(play: Boolean) {
|
|
|
- mediaPlayer.reset()
|
|
|
|
|
playingIndex = -1
|
|
playingIndex = -1
|
|
|
super.newReadAloud(play)
|
|
super.newReadAloud(play)
|
|
|
}
|
|
}
|
|
@@ -77,7 +78,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
} else {
|
|
} else {
|
|
|
val file = getSpeakFileAsMd5(fileName)
|
|
val file = getSpeakFileAsMd5(fileName)
|
|
|
if (file.exists()) {
|
|
if (file.exists()) {
|
|
|
- playAudio(FileInputStream(file).fd)
|
|
|
|
|
|
|
+ playAudio(file)
|
|
|
} else {
|
|
} else {
|
|
|
downloadAudio()
|
|
downloadAudio()
|
|
|
}
|
|
}
|
|
@@ -89,7 +90,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun playStop() {
|
|
override fun playStop() {
|
|
|
- mediaPlayer.stop()
|
|
|
|
|
|
|
+ exoPlayer.stop()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private fun playNext() {
|
|
private fun playNext() {
|
|
@@ -118,8 +119,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
if (hasSpeakFile(fileName)) { //已经下载好的语音缓存
|
|
if (hasSpeakFile(fileName)) { //已经下载好的语音缓存
|
|
|
if (index == nowSpeak) {
|
|
if (index == nowSpeak) {
|
|
|
val file = getSpeakFileAsMd5(fileName)
|
|
val file = getSpeakFileAsMd5(fileName)
|
|
|
- val fis = FileInputStream(file)
|
|
|
|
|
- playAudio(fis.fd)
|
|
|
|
|
|
|
+ playAudio(file)
|
|
|
}
|
|
}
|
|
|
} else if (hasSpeakCache(fileName)) { //缓存文件还在,可能还没下载完
|
|
} else if (hasSpeakCache(fileName)) { //缓存文件还在,可能还没下载完
|
|
|
return@forEachIndexed
|
|
return@forEachIndexed
|
|
@@ -160,9 +160,8 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
val file = createSpeakFileAsMd5IfNotExist(fileName)
|
|
val file = createSpeakFileAsMd5IfNotExist(fileName)
|
|
|
file.writeBytes(bytes)
|
|
file.writeBytes(bytes)
|
|
|
removeSpeakCache(fileName)
|
|
removeSpeakCache(fileName)
|
|
|
- val fis = FileInputStream(file)
|
|
|
|
|
if (index == nowSpeak) {
|
|
if (index == nowSpeak) {
|
|
|
- playAudio(fis.fd)
|
|
|
|
|
|
|
+ playAudio(file)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
downloadErrorNo = 0
|
|
downloadErrorNo = 0
|
|
@@ -213,16 +212,17 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Synchronized
|
|
@Synchronized
|
|
|
- private fun playAudio(fd: FileDescriptor) {
|
|
|
|
|
|
|
+ private fun playAudio(file: File) {
|
|
|
if (playingIndex != nowSpeak && requestFocus()) {
|
|
if (playingIndex != nowSpeak && requestFocus()) {
|
|
|
- try {
|
|
|
|
|
- mediaPlayer.reset()
|
|
|
|
|
- mediaPlayer.setDataSource(fd)
|
|
|
|
|
- mediaPlayer.prepareAsync()
|
|
|
|
|
- playingIndex = nowSpeak
|
|
|
|
|
- postEvent(EventBus.TTS_PROGRESS, readAloudNumber + 1)
|
|
|
|
|
- } catch (e: Exception) {
|
|
|
|
|
- e.printOnDebug()
|
|
|
|
|
|
|
+ launch {
|
|
|
|
|
+ kotlin.runCatching {
|
|
|
|
|
+ val mediaItem = MediaItem.fromUri(Uri.fromFile(file))
|
|
|
|
|
+ exoPlayer.setMediaItem(mediaItem)
|
|
|
|
|
+ exoPlayer.playWhenReady = true
|
|
|
|
|
+ exoPlayer.prepare()
|
|
|
|
|
+ }.onFailure {
|
|
|
|
|
+ it.printOnDebug()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -281,7 +281,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
super.pauseReadAloud(pause)
|
|
super.pauseReadAloud(pause)
|
|
|
kotlin.runCatching {
|
|
kotlin.runCatching {
|
|
|
playIndexJob?.cancel()
|
|
playIndexJob?.cancel()
|
|
|
- mediaPlayer.pause()
|
|
|
|
|
|
|
+ exoPlayer.pause()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -291,7 +291,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
if (playingIndex == -1) {
|
|
if (playingIndex == -1) {
|
|
|
play()
|
|
play()
|
|
|
} else {
|
|
} else {
|
|
|
- mediaPlayer.start()
|
|
|
|
|
|
|
+ exoPlayer.play()
|
|
|
upPlayPos()
|
|
upPlayPos()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -302,22 +302,22 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
val textChapter = textChapter ?: return
|
|
val textChapter = textChapter ?: return
|
|
|
playIndexJob = launch {
|
|
playIndexJob = launch {
|
|
|
postEvent(EventBus.TTS_PROGRESS, readAloudNumber + 1)
|
|
postEvent(EventBus.TTS_PROGRESS, readAloudNumber + 1)
|
|
|
- if (mediaPlayer.duration <= 0) {
|
|
|
|
|
|
|
+ if (exoPlayer.duration <= 0) {
|
|
|
return@launch
|
|
return@launch
|
|
|
}
|
|
}
|
|
|
val speakTextLength = contentList[nowSpeak].length
|
|
val speakTextLength = contentList[nowSpeak].length
|
|
|
if (speakTextLength <= 0) {
|
|
if (speakTextLength <= 0) {
|
|
|
return@launch
|
|
return@launch
|
|
|
}
|
|
}
|
|
|
- val sleep = mediaPlayer.duration / speakTextLength
|
|
|
|
|
- val start = speakTextLength * mediaPlayer.currentPosition / mediaPlayer.duration
|
|
|
|
|
|
|
+ val sleep = exoPlayer.duration / speakTextLength
|
|
|
|
|
+ val start = speakTextLength * exoPlayer.currentPosition / exoPlayer.duration
|
|
|
for (i in start..contentList[nowSpeak].length) {
|
|
for (i in start..contentList[nowSpeak].length) {
|
|
|
if (readAloudNumber + i > textChapter.getReadLength(pageIndex + 1)) {
|
|
if (readAloudNumber + i > textChapter.getReadLength(pageIndex + 1)) {
|
|
|
pageIndex++
|
|
pageIndex++
|
|
|
ReadBook.moveToNextPage()
|
|
ReadBook.moveToNextPage()
|
|
|
postEvent(EventBus.TTS_PROGRESS, readAloudNumber + i)
|
|
postEvent(EventBus.TTS_PROGRESS, readAloudNumber + i)
|
|
|
}
|
|
}
|
|
|
- delay(sleep.toLong())
|
|
|
|
|
|
|
+ delay(sleep)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -327,39 +327,46 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|
|
*/
|
|
*/
|
|
|
override fun upSpeechRate(reset: Boolean) {
|
|
override fun upSpeechRate(reset: Boolean) {
|
|
|
task?.cancel()
|
|
task?.cancel()
|
|
|
- mediaPlayer.stop()
|
|
|
|
|
|
|
+ exoPlayer.stop()
|
|
|
playingIndex = -1
|
|
playingIndex = -1
|
|
|
downloadAudio()
|
|
downloadAudio()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- override fun onPrepared(mp: MediaPlayer?) {
|
|
|
|
|
- super.play()
|
|
|
|
|
- if (pause) return
|
|
|
|
|
- mediaPlayer.start()
|
|
|
|
|
- upPlayPos()
|
|
|
|
|
|
|
+ override fun onPlaybackStateChanged(playbackState: Int) {
|
|
|
|
|
+ super.onPlaybackStateChanged(playbackState)
|
|
|
|
|
+ when (playbackState) {
|
|
|
|
|
+ Player.STATE_IDLE -> {
|
|
|
|
|
+ // 空闲
|
|
|
|
|
+ }
|
|
|
|
|
+ Player.STATE_BUFFERING -> {
|
|
|
|
|
+ // 缓冲中
|
|
|
|
|
+ }
|
|
|
|
|
+ Player.STATE_READY -> {
|
|
|
|
|
+ // 准备好
|
|
|
|
|
+ if (pause) return
|
|
|
|
|
+ exoPlayer.play()
|
|
|
|
|
+ upPlayPos()
|
|
|
|
|
+ }
|
|
|
|
|
+ Player.STATE_ENDED -> {
|
|
|
|
|
+ // 结束
|
|
|
|
|
+ playErrorNo = 0
|
|
|
|
|
+ playNext()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private var playErrorNo = 0
|
|
private var playErrorNo = 0
|
|
|
|
|
|
|
|
- override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
|
|
|
|
|
- if (what == -38 && extra == 0) {
|
|
|
|
|
- play()
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
- AppLog.put("朗读错误($what, $extra)\n${contentList[nowSpeak]}")
|
|
|
|
|
|
|
+ override fun onPlayerError(error: PlaybackException) {
|
|
|
|
|
+ super.onPlayerError(error)
|
|
|
|
|
+ AppLog.put("朗读错误\n${contentList[nowSpeak]}", error)
|
|
|
playErrorNo++
|
|
playErrorNo++
|
|
|
if (playErrorNo >= 5) {
|
|
if (playErrorNo >= 5) {
|
|
|
- toastOnUi("朗读连续5次错误, 最后一次错误代码($what, $extra)")
|
|
|
|
|
|
|
+ toastOnUi("朗读连续5次错误, 最后一次错误代码(${error.localizedMessage})")
|
|
|
ReadAloud.pause(this)
|
|
ReadAloud.pause(this)
|
|
|
} else {
|
|
} else {
|
|
|
playNext()
|
|
playNext()
|
|
|
}
|
|
}
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onCompletion(mp: MediaPlayer?) {
|
|
|
|
|
- playErrorNo = 0
|
|
|
|
|
- playNext()
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun aloudServicePendingIntent(actionStr: String): PendingIntent? {
|
|
override fun aloudServicePendingIntent(actionStr: String): PendingIntent? {
|