V1.0.46 oss (#253)
* add a UI trigger for demo annotations * Implement internal link navigation * Simplify bottom padding logic in `EpubReaderScreen` for vertical scroll mode * Add support for Calibre metadata * Implement advanced library management with Room-backed collections, tags, and smart rules. * Display book series information in book details and remove tags from home screen * Implement zoom reset functionality in PDF viewer * Add a "Pages" thumbnail view to the PDF navigation drawer * Implement jump-back navigation in PDF viewer * MainViewModel: disable FolderSyncWorker, add log export, and enhance PDF position logging * Replace Gson with Kotlin Serialization in SmartCollectionEngine * Implement ONNX-based speech bubble detection * Implement speech bubble detection in PDF viewer * Implement "Smart Comic Zoom" feature for PDF manga/comic reading * Implement reader session persistence and restoration in `MainViewModel` * Add tap-to-turn page feature to PDF viewer * Improve book cache management and recovery * Refactor top overlay padding logic in `PdfViewerScreen` * Implement stylus eraser support in PDF reader * Refactor ReaderTextFormatPanel to use ModalBottomSheet and add new formatting controls * Introduce a customizable horizontal margin setting for the EPUB reader * Refine folder sync and metadata handling for better conflict resolution and stability. * Introduce user-adjustable image scaling for the EPUB reader * Use maxWidthPx as default width fallback for blocks * Improve cross-page text selection and header styling in the paginated reader * Refactor speech bubble detection and UI to support segmentation masks and interactive scaling * Improve speech bubble detection masking and rendering quality * Update ONNX speech bubble detector to use `.ort` model and optimize inference * Improve pagination accuracy * Implement hierarchical folder navigation for the library. * Refactor library item layout for improved space efficiency * Add support for browsing and downloading Google Fonts * Persist library landing state and add shelf search functionality * Fix base tts sample. * Move SpeechBubbleDetector to main source set and update ONNX dependency * Implement a "locate" feature and improve synchronization for TTS (Text-to-Speech) playback across EPUB and PDF readers. * Refine TTS synchronization and voice management in the EPUB reader * Adjust OCR checks for OSS flavor and optimize external dictionary intent flags * Add "Locate" button to PDF drawer's pages tab and move the page number to bottom right of thumbnail * fix various crashes * Refactor SpeechBubbleDetector into product flavors * Implement speech bubble detection caching and background prefetching * Implement on-demand download for Bubble Zoom ML model * Add smooth animation for PDF speech bubble expansion * fixes #252 * hide Google Fonts option, in FontsScreen, for offline variant * Bump version to 1.0.46(46)
This commit is contained in:
parent
579b6f25bf
commit
d16bdd70d4
67 changed files with 9724 additions and 1890 deletions
|
|
@ -147,7 +147,35 @@ class BaseTtsSynthesizer(private val context: Context) {
|
|||
if (tts == null) return
|
||||
|
||||
try {
|
||||
val preferredVoiceName = loadNativeVoice(context) ?: return
|
||||
val preferredVoiceName = loadNativeVoice(context)
|
||||
|
||||
if (preferredVoiceName.isNullOrBlank()) {
|
||||
val defaultLocale = Locale.getDefault()
|
||||
try {
|
||||
tts?.language = defaultLocale
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "BaseTts: Failed to restore default language")
|
||||
}
|
||||
|
||||
val defaultVoice = try {
|
||||
tts?.defaultVoice ?: tts?.voices?.firstOrNull { voice ->
|
||||
voice.locale == defaultLocale && !voice.isNetworkConnectionRequired
|
||||
} ?: tts?.voices?.firstOrNull { voice ->
|
||||
voice.locale == defaultLocale
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "BaseTts: Failed to query default voice")
|
||||
null
|
||||
}
|
||||
|
||||
if (defaultVoice != null && tts?.voice?.name != defaultVoice.name) {
|
||||
Timber.d("BaseTts: Restoring system default voice to ${defaultVoice.name} (${defaultVoice.locale})")
|
||||
tts?.voice = defaultVoice
|
||||
} else {
|
||||
Timber.d("BaseTts: Using engine default voice for locale $defaultLocale")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (tts?.voice?.name == preferredVoiceName) return
|
||||
|
||||
|
|
@ -269,4 +297,4 @@ class BaseTtsSynthesizer(private val context: Context) {
|
|||
}
|
||||
|
||||
private class ZombieEngineException : Exception("Engine failed to start")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ class TtsController(context: Context) : Player.Listener {
|
|||
bookTitle: String,
|
||||
chapterTitle: String?,
|
||||
coverImageUri: String?,
|
||||
chapterIndex: Int? = null,
|
||||
ttsMode: TtsPlaybackManager.TtsMode,
|
||||
playbackSource: String = "READER",
|
||||
authToken: String? = null
|
||||
|
|
@ -179,6 +180,7 @@ class TtsController(context: Context) : Player.Listener {
|
|||
putString(KEY_BOOK_TITLE, bookTitle)
|
||||
putString(KEY_CHAPTER_TITLE, chapterTitle)
|
||||
putString(KEY_COVER_IMAGE_URI, coverImageUri)
|
||||
chapterIndex?.let { putInt(KEY_CHAPTER_INDEX, it) }
|
||||
putString(KEY_TTS_MODE, ttsMode.name)
|
||||
putString(KEY_PLAYBACK_SOURCE, playbackSource)
|
||||
putString(KEY_AUTH_TOKEN, authToken)
|
||||
|
|
@ -252,6 +254,8 @@ class TtsController(context: Context) : Player.Listener {
|
|||
val isLoading = customState.getBoolean("isLoading", false)
|
||||
val sessionFinished = customState.getBoolean("sessionFinished", false)
|
||||
val playbackSource = customState.getString("playbackSource")
|
||||
val serviceBookTitle = customState.getString("bookTitle")
|
||||
val serviceChapterIndex = customState.getInt("chapterIndex", -1).takeIf { it >= 0 }
|
||||
|
||||
val mediaItemExtras = currentMediaItem?.mediaMetadata?.extras
|
||||
val sourceCfi = mediaItemExtras?.getString("sourceCfi")
|
||||
|
|
@ -270,6 +274,16 @@ class TtsController(context: Context) : Player.Listener {
|
|||
if (isLoading) currentState.currentText else null
|
||||
},
|
||||
errorMessage = customState.getString("errorMessage"),
|
||||
bookTitle = if (isPlaybackActive) {
|
||||
currentMediaItem?.mediaMetadata?.artist?.toString() ?: serviceBookTitle
|
||||
} else {
|
||||
if (isLoading) currentState.bookTitle else serviceBookTitle
|
||||
},
|
||||
chapterIndex = if (isPlaybackActive || isLoading) {
|
||||
serviceChapterIndex ?: currentState.chapterIndex
|
||||
} else {
|
||||
serviceChapterIndex
|
||||
},
|
||||
speakerId = serviceSpeaker,
|
||||
sourceCfi = if (isPlaybackActive) {
|
||||
sourceCfi
|
||||
|
|
@ -338,4 +352,4 @@ fun rememberTtsController(): TtsController {
|
|||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ const val KEY_WORD_TIMESTAMPS = "KEY_WORD_TIMESTAMPS"
|
|||
const val KEY_WORD_OFFSETS = "KEY_WORD_OFFSETS"
|
||||
const val KEY_PLAYBACK_SOURCE = "KEY_PLAYBACK_SOURCE"
|
||||
const val KEY_AUTH_TOKEN = "KEY_AUTH_TOKEN"
|
||||
const val KEY_CHAPTER_INDEX = "KEY_CHAPTER_INDEX"
|
||||
|
||||
private const val PREFETCH_LOOKAHEAD = 3
|
||||
|
||||
|
|
@ -100,6 +101,8 @@ class TtsPlaybackManager(
|
|||
val isLoading: Boolean = false,
|
||||
val currentText: String? = null,
|
||||
val errorMessage: String? = null,
|
||||
val bookTitle: String? = null,
|
||||
val chapterIndex: Int? = null,
|
||||
val speakerId: String = DEFAULT_SPEAKER_ID,
|
||||
val sourceCfi: String? = null,
|
||||
val startOffsetInSource: Int = -1,
|
||||
|
|
@ -189,6 +192,7 @@ class TtsPlaybackManager(
|
|||
val bookTitle = args.getString(KEY_BOOK_TITLE)
|
||||
val chapterTitle = args.getString(KEY_CHAPTER_TITLE)
|
||||
val coverImageUri = args.getString(KEY_COVER_IMAGE_URI)
|
||||
val chapterIndex = args.getInt(KEY_CHAPTER_INDEX, -1).takeIf { it >= 0 }
|
||||
val ttsModeName = args.getString(KEY_TTS_MODE, TtsMode.CLOUD.name)
|
||||
val playbackSource = args.getString(KEY_PLAYBACK_SOURCE)
|
||||
val ttsMode = try { TtsMode.valueOf(ttsModeName ?: TtsMode.CLOUD.name) } catch (_: Exception) { TtsMode.CLOUD }
|
||||
|
|
@ -204,7 +208,7 @@ class TtsPlaybackManager(
|
|||
|
||||
val authToken = args.getString(KEY_AUTH_TOKEN)
|
||||
Timber.tag("TTS_CLOUD_DIAG").d("TtsPlaybackManager received START. Token present: ${!authToken.isNullOrBlank()}")
|
||||
handleStartTts(richChunks, speakerId, bookTitle, chapterTitle, coverImageUri, ttsMode, playbackSource, args)
|
||||
handleStartTts(richChunks, speakerId, bookTitle, chapterTitle, coverImageUri, chapterIndex, ttsMode, playbackSource, args)
|
||||
}
|
||||
STOP_TTS_COMMAND -> {
|
||||
Timber.d("Received STOP command.")
|
||||
|
|
@ -337,6 +341,7 @@ class TtsPlaybackManager(
|
|||
bookTitle: String?,
|
||||
chapterTitle: String?,
|
||||
coverImageUri: String?,
|
||||
chapterIndex: Int?,
|
||||
ttsMode: TtsMode,
|
||||
playbackSource: String?,
|
||||
args: Bundle // Added this parameter
|
||||
|
|
@ -375,6 +380,8 @@ class TtsPlaybackManager(
|
|||
|
||||
_ttsState.value = TtsState(
|
||||
isLoading = true,
|
||||
bookTitle = bookTitle,
|
||||
chapterIndex = chapterIndex,
|
||||
speakerId = speakerId,
|
||||
playbackSource = playbackSource,
|
||||
ttsMode = ttsMode.name
|
||||
|
|
@ -857,6 +864,8 @@ class TtsPlaybackManager(
|
|||
val bundle = Bundle().apply {
|
||||
putBoolean("isLoading", state.isLoading)
|
||||
putString("errorMessage", state.errorMessage)
|
||||
putString("bookTitle", state.bookTitle)
|
||||
putInt("chapterIndex", state.chapterIndex ?: -1)
|
||||
putString("speakerId", state.speakerId)
|
||||
putBoolean("sessionEndedByStop", state.sessionEndedByStop)
|
||||
putString("currentWordSourceCfi", state.currentWordSourceCfi)
|
||||
|
|
@ -897,4 +906,4 @@ class TtsPlaybackManager(
|
|||
}
|
||||
Timber.tag("TTS_CLOUD_DIAG").d("ExoPlayer playback state changed: $stateName")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue