This commit is contained in:
Horis
2024-08-24 15:05:53 +08:00
parent a226deca92
commit 419fd25024
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