This commit is contained in:
Horis
2024-08-24 15:05:53 +08:00
parent 7a7cb28813
commit 84bcd42644
7 changed files with 357 additions and 245 deletions
+6 -5
View File
@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -368,12 +369,12 @@
<!-- pdf -->
<data android:mimeType="application/pdf" />
<!-- mobi -->
<data android:mimeType="application/mobi"/>
<data android:mimeType="application/x-mobipocket-ebook"/>
<data android:mimeType="application/mobi" />
<data android:mimeType="application/x-mobipocket-ebook" />
<!-- azw/azw3 -->
<data android:mimeType="application/azw"/>
<data android:mimeType="application/azw3"/>
<data android:mimeType="application/x-mobi8-ebook"/>
<data android:mimeType="application/azw" />
<data android:mimeType="application/azw3" />
<data android:mimeType="application/x-mobi8-ebook" />
<data android:mimeType="application/octet-stream" />
</intent-filter>
<!-- Works when an app doesn't know the media type, e.g. Dropbox -->
@@ -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(book: Book) {
stop()
AudioPlay.book = book
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
simulatedChapterSize = if (book.readSimulating()) {
book.simulatedTotalChapterNum()
} else {
chapterSize
}
bookSource = book.getBookSource()
durChapterIndex = book.durChapterIndex
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
}
}
fun resetData(context: Context, book: Book) {
stop(context)
AudioPlay.book = book
titleData.postValue(book.name)
coverData.postValue(book.getDisplayCover())
bookSource = book.getBookSource()
durChapterIndex = book.durChapterIndex
upDurChapter(book)
private fun removeLoading(index: Int) {
synchronized(this) {
loadingChapters.remove(index)
}
}
fun loadOrUpPlayUrl() {
if (durPlayUrl.isEmpty()) {
loadPlayUrl()
} else {
upPlayUrl()
}
}
/**
* 加载播放URL
*/
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: Context) {
book?.let {
if (durChapter == null) {
upDurChapter(it)
}
durChapter?.let {
context.startService<AudioPlayService> {
action = IntentAction.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
stop()
durChapterIndex = index
durChapterPos = 0
durPlayUrl = ""
saveRead()
loadPlayUrl()
}
}
fun prev() {
Coroutine.async {
stop()
if (durChapterIndex > 0) {
durChapterIndex -= 1
durChapterPos = 0
durPlayUrl = ""
saveRead()
playNew(context)
loadPlayUrl()
}
}
}
fun prev(context: Context) {
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)
}
}
}
}
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)
}
}
@@ -29,16 +29,12 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.IntentAction
import io.legado.app.constant.NotificationId
import io.legado.app.constant.Status
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.MediaHelp
import io.legado.app.help.config.AppConfig
import io.legado.app.help.exoplayer.ExoPlayerHelper
import io.legado.app.help.glide.ImageLoader
import io.legado.app.model.AudioPlay
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.webBook.WebBook
import io.legado.app.receiver.MediaButtonReceiver
import io.legado.app.ui.book.audio.AudioPlayActivity
import io.legado.app.utils.activityPendingIntent
@@ -122,6 +118,7 @@ class AudioPlayService : BaseService(),
super.onCreate()
isRun = true
exoPlayer.addListener(this)
AudioPlay.registerService(this)
initMediaSession()
initBroadcastReceiver()
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
@@ -147,7 +144,8 @@ class AudioPlayService : BaseService(),
upPlayProgressJob?.cancel()
pause = false
position = AudioPlay.book?.durChapterPos ?: 0
loadContent()
url = AudioPlay.durPlayUrl
play()
}
IntentAction.playNew -> {
@@ -155,13 +153,14 @@ class AudioPlayService : BaseService(),
upPlayProgressJob?.cancel()
pause = false
position = 0
loadContent()
url = AudioPlay.durPlayUrl
play()
}
IntentAction.pause -> pause()
IntentAction.resume -> resume()
IntentAction.prev -> AudioPlay.prev(this)
IntentAction.next -> AudioPlay.next(this)
IntentAction.prev -> AudioPlay.prev()
IntentAction.next -> AudioPlay.next()
IntentAction.adjustSpeed -> upSpeed(intent.getFloatExtra("adjust", 1f))
IntentAction.addTimer -> addTimer()
IntentAction.setTimer -> setTimer(intent.getIntExtra("minute", 0))
@@ -189,6 +188,7 @@ class AudioPlayService : BaseService(),
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED)
AudioPlay.status = Status.STOP
postEvent(EventBus.AUDIO_STATE, Status.STOP)
AudioPlay.unregisterService()
}
/**
@@ -216,6 +216,7 @@ class AudioPlayService : BaseService(),
)
exoPlayer.setMediaItem(analyzeUrl.getMediaItem())
exoPlayer.playWhenReady = true
exoPlayer.seekTo(position.toLong())
exoPlayer.prepare()
}.onError {
AppLog.put("播放出错\n${it.localizedMessage}", it)
@@ -261,7 +262,7 @@ class AudioPlayService : BaseService(),
try {
pause = false
if (url.isEmpty()) {
loadContent()
AudioPlay.loadOrUpPlayUrl()
return
}
if (!exoPlayer.isPlaying) {
@@ -317,9 +318,7 @@ class AudioPlayService : BaseService(),
Player.STATE_READY -> {
// 准备好
if (exoPlayer.currentPosition != position.toLong()) {
exoPlayer.seekTo(position.toLong())
}
AudioPlay.upLoading(false)
if (exoPlayer.playWhenReady) {
AudioPlay.status = Status.PLAY
postEvent(EventBus.AUDIO_STATE, Status.PLAY)
@@ -336,7 +335,8 @@ class AudioPlayService : BaseService(),
Player.STATE_ENDED -> {
// 结束
upPlayProgressJob?.cancel()
AudioPlay.next(this)
AudioPlay.playPositionChanged(exoPlayer.duration.toInt())
AudioPlay.next()
}
}
upAudioPlayNotification()
@@ -395,7 +395,7 @@ class AudioPlayService : BaseService(),
timeMinute--
}
if (timeMinute == 0) {
AudioPlay.stop(this@AudioPlayService)
AudioPlay.stop()
}
}
postEvent(EventBus.AUDIO_DS, timeMinute)
@@ -411,81 +411,16 @@ class AudioPlayService : BaseService(),
upPlayProgressJob?.cancel()
upPlayProgressJob = lifecycleScope.launch {
while (isActive) {
AudioPlay.book?.let {
//更新buffer位置
postEvent(EventBus.AUDIO_BUFFER_PROGRESS, exoPlayer.bufferedPosition.toInt())
it.durChapterPos = exoPlayer.currentPosition.toInt()
postEvent(EventBus.AUDIO_PROGRESS, it.durChapterPos)
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
saveProgress(it)
}
//更新buffer位置
AudioPlay.playPositionChanged(exoPlayer.currentPosition.toInt())
postEvent(EventBus.AUDIO_BUFFER_PROGRESS, exoPlayer.bufferedPosition.toInt())
postEvent(EventBus.AUDIO_PROGRESS, AudioPlay.durChapterPos)
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
delay(1000)
}
}
}
/**
* 加载播放URL
*/
private fun loadContent() = with(AudioPlay) {
durChapter?.let { chapter ->
if (addLoading(chapter.index)) {
val book = AudioPlay.book
val bookSource = AudioPlay.bookSource
if (book != null && bookSource != null) {
WebBook.getContent(lifecycleScope, bookSource, book, chapter)
.onSuccess { content ->
if (content.isEmpty()) {
toastOnUi("未获取到资源链接")
} else {
contentLoadFinish(chapter, content)
}
}.onError {
contentLoadFinish(chapter, it.localizedMessage ?: toString())
}.onFinally {
removeLoading(chapter.index)
}
} else {
removeLoading(chapter.index)
toastOnUi("book or source is null")
}
}
}
}
private fun addLoading(index: Int): Boolean {
synchronized(this) {
if (AudioPlay.loadingChapters.contains(index)) return false
AudioPlay.loadingChapters.add(index)
return true
}
}
private fun removeLoading(index: Int) {
synchronized(this) {
AudioPlay.loadingChapters.remove(index)
}
}
/**
* 加载完成
*/
private fun contentLoadFinish(chapter: BookChapter, content: String) {
if (chapter.index == AudioPlay.book?.durChapterIndex) {
url = content
play()
}
}
/**
* 保存播放进度
*/
private fun saveProgress(book: Book) {
execute {
appDb.bookDao.upProgress(book.bookUrl, book.durChapterPos)
}
}
/**
* 更新媒体状态
*/
@@ -584,7 +519,6 @@ class AudioPlayService : BaseService(),
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
AppLog.put("音频焦点暂时丢失并会很快再次获得,暂停播放")
needResumeOnAudioFocusGain = true
if (!pause) {
needResumeOnAudioFocusGain = true
pause(false)
@@ -24,7 +24,6 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.databinding.ActivityAudioPlayBinding
import io.legado.app.help.book.isAudio
import io.legado.app.help.book.removeType
import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.model.AudioPlay
@@ -59,7 +58,8 @@ import java.util.Locale
@SuppressLint("ObsoleteSdkInt")
class AudioPlayActivity :
VMBaseActivity<ActivityAudioPlayBinding, AudioPlayViewModel>(toolBarTheme = Theme.Dark),
ChangeBookSourceDialog.CallBack {
ChangeBookSourceDialog.CallBack,
AudioPlay.CallBack {
override val binding by viewBinding(ActivityAudioPlayBinding::inflate)
override val viewModel by viewModels<AudioPlayViewModel>()
@@ -78,7 +78,7 @@ class AudioPlayActivity :
if (it.first != AudioPlay.book?.durChapterIndex
|| it.second == 0
) {
AudioPlay.skipTo(this, it.first)
AudioPlay.skipTo(it.first)
}
}
}
@@ -91,10 +91,11 @@ class AudioPlayActivity :
override fun onActivityCreated(savedInstanceState: Bundle?) {
binding.titleBar.setBackgroundResource(R.color.transparent)
AudioPlay.titleData.observe(this) {
AudioPlay.register(this)
viewModel.titleData.observe(this) {
binding.titleBar.title = it
}
AudioPlay.coverData.observe(this) {
viewModel.coverData.observe(this) {
upCover(it)
}
viewModel.initData(intent)
@@ -143,13 +144,13 @@ class AudioPlayActivity :
playButton()
}
binding.fabPlayStop.onLongClick {
AudioPlay.stop(this@AudioPlayActivity)
AudioPlay.stop()
}
binding.ivSkipNext.setOnClickListener {
AudioPlay.next(this@AudioPlayActivity)
AudioPlay.next()
}
binding.ivSkipPrevious.setOnClickListener {
AudioPlay.prev(this@AudioPlayActivity)
AudioPlay.prev()
}
binding.playerProgress.setOnSeekBarChangeListener(object : SeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
@@ -162,7 +163,7 @@ class AudioPlayActivity :
override fun onStopTrackingTouch(seekBar: SeekBar) {
adjustProgress = false
AudioPlay.adjustProgress(this@AudioPlayActivity, seekBar.progress)
AudioPlay.adjustProgress(seekBar.progress)
}
})
binding.ivChapter.setOnClickListener {
@@ -175,10 +176,10 @@ class AudioPlayActivity :
binding.ivFastForward.invisible()
}
binding.ivFastForward.setOnClickListener {
AudioPlay.adjustSpeed(this@AudioPlayActivity, 0.1f)
AudioPlay.adjustSpeed(0.1f)
}
binding.ivFastRewind.setOnClickListener {
AudioPlay.adjustSpeed(this@AudioPlayActivity, -0.1f)
AudioPlay.adjustSpeed(-0.1f)
}
binding.ivTimer.setOnClickListener {
timerSliderPopup.showAsDropDown(it, 0, (-100).dpToPx(), Gravity.TOP)
@@ -196,7 +197,7 @@ class AudioPlayActivity :
when (AudioPlay.status) {
Status.PLAY -> AudioPlay.pause(this)
Status.PAUSE -> AudioPlay.resume(this)
else -> AudioPlay.play(this)
else -> AudioPlay.loadOrUpPlayUrl()
}
}
@@ -207,7 +208,7 @@ class AudioPlayActivity :
if (book.isAudio) {
viewModel.changeTo(source, book, toc)
} else {
AudioPlay.stop(this)
AudioPlay.stop()
lifecycleScope.launch {
withContext(IO) {
AudioPlay.book?.migrateTo(book, toc)
@@ -247,8 +248,9 @@ class AudioPlayActivity :
override fun onDestroy() {
super.onDestroy()
if (AudioPlay.status != Status.PLAY) {
AudioPlay.stop(this)
AudioPlay.stop()
}
AudioPlay.unregister(this)
}
@SuppressLint("SetTextI18n")
@@ -268,11 +270,9 @@ class AudioPlayActivity :
}
observeEventSticky<String>(EventBus.AUDIO_SUB_TITLE) {
binding.tvSubTitle.text = it
AudioPlay.book?.let { book ->
binding.ivSkipPrevious.isEnabled = book.durChapterIndex > 0
binding.ivSkipNext.isEnabled =
book.durChapterIndex < book.simulatedTotalChapterNum() - 1
}
binding.ivSkipPrevious.isEnabled = AudioPlay.durChapterIndex > 0
binding.ivSkipNext.isEnabled =
AudioPlay.durChapterIndex < AudioPlay.simulatedChapterSize - 1
}
observeEventSticky<Int>(EventBus.AUDIO_SIZE) {
binding.playerProgress.max = it
@@ -296,4 +296,10 @@ class AudioPlayActivity :
}
}
override fun upLoading(loading: Boolean) {
runOnUiThread {
binding.progressLoading.visible(loading)
}
}
}
@@ -2,9 +2,10 @@ package io.legado.app.ui.book.audio
import android.app.Application
import android.content.Intent
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.MutableLiveData
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppLog
import io.legado.app.constant.BookType
import io.legado.app.constant.EventBus
import io.legado.app.data.appDb
@@ -13,14 +14,15 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.help.book.getBookSource
import io.legado.app.help.book.removeType
import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.model.AudioPlay
import io.legado.app.model.AudioPlay.durChapter
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO
class AudioPlayViewModel(application: Application) : BaseViewModel(application) {
val titleData = MutableLiveData<String>()
val coverData = MutableLiveData<String>()
fun initData(intent: Intent) = AudioPlay.apply {
execute {
@@ -33,37 +35,53 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
}
}
private fun initBook(book: Book) {
private suspend fun initBook(book: Book) {
val isSameBook = AudioPlay.book?.bookUrl == book.bookUrl
if (isSameBook) {
AudioPlay.upData(context, book)
AudioPlay.upData(book)
} else {
AudioPlay.resetData(context, book)
AudioPlay.resetData(book)
}
if (durChapter == null) {
if (book.tocUrl.isEmpty()) {
loadBookInfo(book)
titleData.postValue(book.name)
coverData.postValue(book.getDisplayCover())
if (book.tocUrl.isEmpty() && !loadBookInfo(book)) {
return
}
if (AudioPlay.chapterSize == 0 && !loadChapterList(book)) {
return
}
}
private suspend fun loadBookInfo(book: Book): Boolean {
val bookSource = AudioPlay.bookSource ?: return true
try {
WebBook.getBookInfoAwait(bookSource, book)
return true
} catch (e: Exception) {
AppLog.put("详情页出错: ${e.localizedMessage}", e, true)
return false
}
}
private suspend fun loadChapterList(book: Book): Boolean {
val bookSource = AudioPlay.bookSource ?: return true
try {
val oldBook = book.copy()
val cList = WebBook.getChapterListAwait(bookSource, book).getOrThrow()
if (oldBook.bookUrl == book.bookUrl) {
appDb.bookDao.update(book)
} else {
loadChapterList(book)
appDb.bookDao.insert(book)
}
}
}
private fun loadBookInfo(book: Book) {
val bookSource = AudioPlay.bookSource ?: return
WebBook.getBookInfo(viewModelScope, bookSource, book).onSuccess(IO) {
loadChapterList(book)
}
}
private fun loadChapterList(book: Book) {
val bookSource = AudioPlay.bookSource ?: return
WebBook.getChapterList(viewModelScope, bookSource, book).onSuccess(IO) { cList ->
book.save()
appDb.bookChapterDao.delByBook(book.bookUrl)
appDb.bookChapterDao.insert(*cList.toTypedArray())
AudioPlay.upDurChapter(book)
}.onError {
AudioPlay.chapterSize = cList.size
AudioPlay.simulatedChapterSize = book.simulatedTotalChapterNum()
AudioPlay.upDurChapter()
return true
} catch (e: Exception) {
context.toastOnUi(R.string.error_load_toc)
return false
}
}
@@ -83,7 +101,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
AudioPlay.book = book
AudioPlay.bookSource = source
appDb.bookChapterDao.insert(*toc.toTypedArray())
AudioPlay.upDurChapter(book)
AudioPlay.upDurChapter()
}.onFinally {
postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)
}
+40 -18
View File
@@ -130,9 +130,9 @@
android:id="@+id/iv_timer"
android:layout_width="46dp"
android:layout_height="46dp"
android:padding="5dp"
android:background="@drawable/selector_circle_btn_bg"
android:contentDescription="@string/set_timer"
android:padding="5dp"
android:src="@drawable/ic_timer_black_24dp"
app:tint="@color/md_white_1000"
tools:ignore="ImageContrastCheck" />
@@ -146,9 +146,9 @@
android:id="@+id/iv_fast_rewind"
android:layout_width="46dp"
android:layout_height="46dp"
android:padding="5dp"
android:background="@drawable/selector_circle_btn_bg"
android:contentDescription="@string/skip_previous"
android:padding="5dp"
android:src="@drawable/ic_fast_rewind"
app:tint="@color/md_white_1000"
tools:ignore="ImageContrastCheck" />
@@ -162,34 +162,56 @@
android:id="@+id/iv_skip_previous"
android:layout_width="46dp"
android:layout_height="46dp"
android:padding="5dp"
android:background="@drawable/selector_circle_btn_bg"
android:contentDescription="@string/skip_previous"
android:padding="5dp"
android:src="@drawable/ic_skip_previous"
app:tint="@color/md_white_1000"
tools:ignore="ImageContrastCheck" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_play_stop"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:contentDescription="@string/audio_play"
android:src="@drawable/ic_play_24dp"
android:tint="@color/md_black_1000"
app:backgroundTint="@color/md_white_1000"
app:elevation="2dp"
app:fabSize="normal"
app:pressedTranslationZ="2dp"
tools:ignore="ImageContrastCheck" />
android:layout_height="wrap_content">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_play_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:contentDescription="@string/audio_play"
android:src="@drawable/ic_play_24dp"
android:tint="@color/md_black_1000"
app:backgroundTint="@color/md_white_1000"
app:elevation="2dp"
app:fabSize="normal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:pressedTranslationZ="2dp"
tools:ignore="ImageContrastCheck" />
<io.legado.app.lib.theme.view.ThemeProgressBar
android:id="@+id/progress_loading"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="invisible"
app:layout_constraintWidth="parent"
app:layout_constraintHeight="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<io.legado.app.ui.widget.image.ImageButton
android:id="@+id/iv_skip_next"
android:layout_width="46dp"
android:layout_height="46dp"
android:padding="5dp"
android:background="@drawable/selector_circle_btn_bg"
android:contentDescription="@string/skip_next"
android:padding="5dp"
android:src="@drawable/ic_skip_next"
app:tint="@color/md_white_1000"
tools:ignore="ImageContrastCheck" />
@@ -203,9 +225,9 @@
android:id="@+id/iv_fast_forward"
android:layout_width="46dp"
android:layout_height="46dp"
android:padding="5dp"
android:background="@drawable/selector_circle_btn_bg"
android:contentDescription="@string/skip_next"
android:padding="5dp"
android:src="@drawable/ic_fast_forward"
app:tint="@color/md_white_1000"
tools:ignore="ImageContrastCheck" />
@@ -219,9 +241,9 @@
android:id="@+id/iv_chapter"
android:layout_width="46dp"
android:layout_height="46dp"
android:padding="5dp"
android:background="@drawable/selector_circle_btn_bg"
android:contentDescription="@string/chapter_list"
android:padding="5dp"
android:src="@drawable/ic_chapter_list"
app:tint="@color/md_white_1000"
tools:ignore="ImageContrastCheck" />
+1 -1
View File
@@ -7,7 +7,7 @@
android:padding="20dp"
android:orientation="horizontal">
<ProgressBar
<io.legado.app.lib.theme.view.ThemeProgressBar
android:id="@+id/pb"
android:layout_width="30dp"
android:layout_height="30dp"