|
|
@@ -1,8 +1,9 @@
|
|
|
package io.legado.app.model
|
|
|
|
|
|
+import android.annotation.SuppressLint
|
|
|
import android.content.Context
|
|
|
import android.content.Intent
|
|
|
-import androidx.lifecycle.MutableLiveData
|
|
|
+import io.legado.app.constant.AppLog
|
|
|
import io.legado.app.constant.EventBus
|
|
|
import io.legado.app.constant.IntentAction
|
|
|
import io.legado.app.constant.Status
|
|
|
@@ -12,84 +13,176 @@ import io.legado.app.data.entities.BookChapter
|
|
|
import io.legado.app.data.entities.BookSource
|
|
|
import io.legado.app.help.book.ContentProcessor
|
|
|
import io.legado.app.help.book.getBookSource
|
|
|
+import io.legado.app.help.book.readSimulating
|
|
|
import io.legado.app.help.book.simulatedTotalChapterNum
|
|
|
import io.legado.app.help.coroutine.Coroutine
|
|
|
+import io.legado.app.model.webBook.WebBook
|
|
|
import io.legado.app.service.AudioPlayService
|
|
|
import io.legado.app.utils.postEvent
|
|
|
import io.legado.app.utils.startService
|
|
|
+import io.legado.app.utils.toastOnUi
|
|
|
+import kotlinx.coroutines.CoroutineScope
|
|
|
+import kotlinx.coroutines.MainScope
|
|
|
+import kotlinx.coroutines.cancelChildren
|
|
|
import splitties.init.appCtx
|
|
|
|
|
|
+@SuppressLint("StaticFieldLeak")
|
|
|
@Suppress("unused")
|
|
|
-object AudioPlay {
|
|
|
- var titleData = MutableLiveData<String>()
|
|
|
- var coverData = MutableLiveData<String>()
|
|
|
+object AudioPlay : CoroutineScope by MainScope() {
|
|
|
var status = Status.STOP
|
|
|
+ private var activityContext: Context? = null
|
|
|
+ private var serviceContext: Context? = null
|
|
|
+ private val context: Context get() = activityContext ?: serviceContext ?: appCtx
|
|
|
+ var callback: CallBack? = null
|
|
|
var book: Book? = null
|
|
|
+ var chapterSize = 0
|
|
|
+ var simulatedChapterSize = 0
|
|
|
+ var durChapterIndex = 0
|
|
|
+ var durChapterPos = 0
|
|
|
var durChapter: BookChapter? = null
|
|
|
+ var durPlayUrl = ""
|
|
|
+ var durAudioSize = 0
|
|
|
var inBookshelf = false
|
|
|
var bookSource: BookSource? = null
|
|
|
val loadingChapters = arrayListOf<Int>()
|
|
|
- var durChapterIndex = 0
|
|
|
|
|
|
- fun upData(context: Context, book: Book) {
|
|
|
+ fun upData(book: Book) {
|
|
|
AudioPlay.book = book
|
|
|
- upDurChapter(book)
|
|
|
+ chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
|
|
|
+ simulatedChapterSize = if (book.readSimulating()) {
|
|
|
+ book.simulatedTotalChapterNum()
|
|
|
+ } else {
|
|
|
+ chapterSize
|
|
|
+ }
|
|
|
if (durChapterIndex != book.durChapterIndex) {
|
|
|
durChapterIndex = book.durChapterIndex
|
|
|
- playNew(context)
|
|
|
+ durChapterPos = book.durChapterPos
|
|
|
+ durPlayUrl = ""
|
|
|
+ durAudioSize = 0
|
|
|
}
|
|
|
+ upDurChapter()
|
|
|
}
|
|
|
|
|
|
- fun resetData(context: Context, book: Book) {
|
|
|
- stop(context)
|
|
|
+ fun resetData(book: Book) {
|
|
|
+ stop()
|
|
|
AudioPlay.book = book
|
|
|
- titleData.postValue(book.name)
|
|
|
- coverData.postValue(book.getDisplayCover())
|
|
|
+ chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
|
|
|
+ simulatedChapterSize = if (book.readSimulating()) {
|
|
|
+ book.simulatedTotalChapterNum()
|
|
|
+ } else {
|
|
|
+ chapterSize
|
|
|
+ }
|
|
|
bookSource = book.getBookSource()
|
|
|
durChapterIndex = book.durChapterIndex
|
|
|
- upDurChapter(book)
|
|
|
+ durChapterPos = book.durChapterPos
|
|
|
+ durPlayUrl = ""
|
|
|
+ durAudioSize = 0
|
|
|
+ upDurChapter()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun addLoading(index: Int): Boolean {
|
|
|
+ synchronized(this) {
|
|
|
+ if (loadingChapters.contains(index)) return false
|
|
|
+ loadingChapters.add(index)
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun removeLoading(index: Int) {
|
|
|
+ synchronized(this) {
|
|
|
+ loadingChapters.remove(index)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fun loadOrUpPlayUrl() {
|
|
|
+ if (durPlayUrl.isEmpty()) {
|
|
|
+ loadPlayUrl()
|
|
|
+ } else {
|
|
|
+ upPlayUrl()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 播放当前章节
|
|
|
+ * 加载播放URL
|
|
|
*/
|
|
|
- fun play(context: Context) {
|
|
|
- book?.let {
|
|
|
- if (durChapter == null) {
|
|
|
- upDurChapter(it)
|
|
|
- }
|
|
|
- durChapter?.let {
|
|
|
- context.startService<AudioPlayService> {
|
|
|
- action = IntentAction.play
|
|
|
+ private fun loadPlayUrl() {
|
|
|
+ val index = durChapterIndex
|
|
|
+ if (addLoading(index)) {
|
|
|
+ val book = book
|
|
|
+ val bookSource = bookSource
|
|
|
+ if (book != null && bookSource != null) {
|
|
|
+ upDurChapter()
|
|
|
+ val chapter = durChapter
|
|
|
+ if (chapter == null) {
|
|
|
+ removeLoading(index)
|
|
|
+ return
|
|
|
}
|
|
|
+ upLoading(true)
|
|
|
+ WebBook.getContent(this, bookSource, book, chapter)
|
|
|
+ .onSuccess { content ->
|
|
|
+ if (content.isEmpty()) {
|
|
|
+ appCtx.toastOnUi("未获取到资源链接")
|
|
|
+ } else {
|
|
|
+ contentLoadFinish(chapter, content)
|
|
|
+ }
|
|
|
+ }.onError {
|
|
|
+ AppLog.put("获取资源链接出错\n$it", it, true)
|
|
|
+ }.onFinally {
|
|
|
+ removeLoading(index)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ removeLoading(index)
|
|
|
+ appCtx.toastOnUi("book or source is null")
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 加载完成
|
|
|
+ */
|
|
|
+ private fun contentLoadFinish(chapter: BookChapter, content: String) {
|
|
|
+ if (chapter.index == book?.durChapterIndex) {
|
|
|
+ durPlayUrl = content
|
|
|
+ upPlayUrl()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun upPlayUrl() {
|
|
|
+ if (isPlayToEnd()) {
|
|
|
+ playNew()
|
|
|
+ } else {
|
|
|
+ play()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 播放当前章节
|
|
|
+ */
|
|
|
+ fun play() {
|
|
|
+ context.startService<AudioPlayService> {
|
|
|
+ action = IntentAction.play
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 从头播放新章节
|
|
|
*/
|
|
|
- fun playNew(context: Context) {
|
|
|
- book?.let {
|
|
|
- if (durChapter == null) {
|
|
|
- upDurChapter(it)
|
|
|
- }
|
|
|
- durChapter?.let {
|
|
|
- context.startService<AudioPlayService> {
|
|
|
- action = IntentAction.playNew
|
|
|
- }
|
|
|
- }
|
|
|
+ private fun playNew() {
|
|
|
+ context.startService<AudioPlayService> {
|
|
|
+ action = IntentAction.playNew
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 更新当前章节
|
|
|
*/
|
|
|
- fun upDurChapter(book: Book) {
|
|
|
- durChapter = appDb.bookChapterDao.getChapter(book.bookUrl, book.durChapterIndex)
|
|
|
+ fun upDurChapter() {
|
|
|
+ val book = book ?: return
|
|
|
+ durChapter = appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)
|
|
|
+ durAudioSize = durChapter?.end?.toInt() ?: 0
|
|
|
postEvent(EventBus.AUDIO_SUB_TITLE, durChapter?.title ?: "")
|
|
|
- postEvent(EventBus.AUDIO_SIZE, durChapter?.end?.toInt() ?: 0)
|
|
|
- postEvent(EventBus.AUDIO_PROGRESS, book.durChapterPos)
|
|
|
+ postEvent(EventBus.AUDIO_SIZE, durAudioSize)
|
|
|
+ postEvent(EventBus.AUDIO_PROGRESS, durChapterPos)
|
|
|
}
|
|
|
|
|
|
fun pause(context: Context) {
|
|
|
@@ -108,7 +201,7 @@ object AudioPlay {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun stop(context: Context) {
|
|
|
+ fun stop() {
|
|
|
if (AudioPlayService.isRun) {
|
|
|
context.startService<AudioPlayService> {
|
|
|
action = IntentAction.stop
|
|
|
@@ -116,7 +209,7 @@ object AudioPlay {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun adjustSpeed(context: Context, adjust: Float) {
|
|
|
+ fun adjustSpeed(adjust: Float) {
|
|
|
if (AudioPlayService.isRun) {
|
|
|
context.startService<AudioPlayService> {
|
|
|
action = IntentAction.adjustSpeed
|
|
|
@@ -125,7 +218,9 @@ object AudioPlay {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun adjustProgress(context: Context, position: Int) {
|
|
|
+ fun adjustProgress(position: Int) {
|
|
|
+ durChapterPos = position
|
|
|
+ saveRead()
|
|
|
if (AudioPlayService.isRun) {
|
|
|
context.startService<AudioPlayService> {
|
|
|
action = IntentAction.adjustProgress
|
|
|
@@ -134,57 +229,47 @@ object AudioPlay {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun skipTo(context: Context, index: Int) {
|
|
|
+ fun skipTo(index: Int) {
|
|
|
Coroutine.async {
|
|
|
- book?.let { book ->
|
|
|
- book.durChapterIndex = index
|
|
|
- book.durChapterPos = 0
|
|
|
- durChapterIndex = book.durChapterIndex
|
|
|
- durChapter = null
|
|
|
- saveRead()
|
|
|
- playNew(context)
|
|
|
- }
|
|
|
+ stop()
|
|
|
+ durChapterIndex = index
|
|
|
+ durChapterPos = 0
|
|
|
+ durPlayUrl = ""
|
|
|
+ saveRead()
|
|
|
+ loadPlayUrl()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun prev(context: Context) {
|
|
|
+ fun prev() {
|
|
|
Coroutine.async {
|
|
|
- book?.let { book ->
|
|
|
- if (book.durChapterIndex > 0) {
|
|
|
- book.durChapterIndex -= 1
|
|
|
- book.durChapterPos = 0
|
|
|
- durChapterIndex = book.durChapterIndex
|
|
|
- durChapter = null
|
|
|
- saveRead()
|
|
|
- play(context)
|
|
|
- } else {
|
|
|
- stop(context)
|
|
|
- }
|
|
|
+ stop()
|
|
|
+ if (durChapterIndex > 0) {
|
|
|
+ durChapterIndex -= 1
|
|
|
+ durChapterPos = 0
|
|
|
+ durPlayUrl = ""
|
|
|
+ saveRead()
|
|
|
+ loadPlayUrl()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun next(context: Context) {
|
|
|
- book?.let { book ->
|
|
|
- if (book.durChapterIndex + 1 < book.simulatedTotalChapterNum()) {
|
|
|
- book.durChapterIndex += 1
|
|
|
- book.durChapterPos = 0
|
|
|
- durChapterIndex = book.durChapterIndex
|
|
|
- durChapter = null
|
|
|
- saveRead()
|
|
|
- play(context)
|
|
|
- } else {
|
|
|
- stop(context)
|
|
|
- }
|
|
|
+ fun next() {
|
|
|
+ stop()
|
|
|
+ if (durChapterIndex + 1 < simulatedChapterSize) {
|
|
|
+ durChapterIndex += 1
|
|
|
+ durChapterPos = 0
|
|
|
+ durPlayUrl = ""
|
|
|
+ saveRead()
|
|
|
+ loadPlayUrl()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fun setTimer(minute: Int) {
|
|
|
if (AudioPlayService.isRun) {
|
|
|
- val intent = Intent(appCtx, AudioPlayService::class.java)
|
|
|
+ val intent = Intent(context, AudioPlayService::class.java)
|
|
|
intent.action = IntentAction.setTimer
|
|
|
intent.putExtra("minute", minute)
|
|
|
- appCtx.startService(intent)
|
|
|
+ context.startService(intent)
|
|
|
} else {
|
|
|
AudioPlayService.timeMinute = minute
|
|
|
postEvent(EventBus.AUDIO_DS, minute)
|
|
|
@@ -192,24 +277,28 @@ object AudioPlay {
|
|
|
}
|
|
|
|
|
|
fun addTimer() {
|
|
|
- val intent = Intent(appCtx, AudioPlayService::class.java)
|
|
|
+ val intent = Intent(context, AudioPlayService::class.java)
|
|
|
intent.action = IntentAction.addTimer
|
|
|
- appCtx.startService(intent)
|
|
|
+ context.startService(intent)
|
|
|
}
|
|
|
|
|
|
fun saveRead() {
|
|
|
- book?.let { book ->
|
|
|
+ val book = book ?: return
|
|
|
+ Coroutine.async {
|
|
|
book.lastCheckCount = 0
|
|
|
book.durChapterTime = System.currentTimeMillis()
|
|
|
- Coroutine.async {
|
|
|
+ val chapterChanged = book.durChapterIndex != durChapterIndex
|
|
|
+ book.durChapterIndex = durChapterIndex
|
|
|
+ book.durChapterPos = durChapterPos
|
|
|
+ if (chapterChanged) {
|
|
|
appDb.bookChapterDao.getChapter(book.bookUrl, book.durChapterIndex)?.let {
|
|
|
book.durChapterTitle = it.getDisplayTitle(
|
|
|
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),
|
|
|
book.getUseReplaceRule()
|
|
|
)
|
|
|
}
|
|
|
- book.update()
|
|
|
}
|
|
|
+ book.update()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -217,11 +306,53 @@ object AudioPlay {
|
|
|
* 保存章节长度
|
|
|
*/
|
|
|
fun saveDurChapter(audioSize: Long) {
|
|
|
+ val chapter = durChapter ?: return
|
|
|
Coroutine.async {
|
|
|
- durChapter?.let {
|
|
|
- it.end = audioSize
|
|
|
- appDb.bookChapterDao.update(it)
|
|
|
- }
|
|
|
+ durAudioSize = audioSize.toInt()
|
|
|
+ chapter.end = audioSize
|
|
|
+ appDb.bookChapterDao.update(chapter)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fun playPositionChanged(position: Int) {
|
|
|
+ durChapterPos = position
|
|
|
+ saveRead()
|
|
|
+ }
|
|
|
+
|
|
|
+ fun upLoading(loading: Boolean) {
|
|
|
+ callback?.upLoading(loading)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun isPlayToEnd(): Boolean {
|
|
|
+ return durChapterIndex + 1 == simulatedChapterSize
|
|
|
+ && durChapterPos == durAudioSize
|
|
|
+ }
|
|
|
+
|
|
|
+ fun register(context: Context) {
|
|
|
+ activityContext = context
|
|
|
+ callback = context as CallBack
|
|
|
+ }
|
|
|
+
|
|
|
+ fun unregister(context: Context) {
|
|
|
+ if (activityContext === context) {
|
|
|
+ activityContext = null
|
|
|
+ callback = null
|
|
|
+ }
|
|
|
+ coroutineContext.cancelChildren()
|
|
|
+ }
|
|
|
+
|
|
|
+ fun registerService(context: Context) {
|
|
|
+ serviceContext = context
|
|
|
+ }
|
|
|
+
|
|
|
+ fun unregisterService() {
|
|
|
+ serviceContext = null
|
|
|
+ }
|
|
|
+
|
|
|
+ interface CallBack {
|
|
|
+
|
|
|
+ fun upLoading(loading: Boolean)
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
}
|