优化
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user