Epub tts fix (#103)
* build: bump version to 1.0.38 and improve CFI page lookup logic * fix: resolve duplicate TTS audio and broken highlights in Vertical Mode Updated the text extraction logic in epub_reader.js to pick only the most granular matching elements, preventing duplication when elements like <p> are nested inside <li>.
This commit is contained in:
parent
fbe8e7aa56
commit
32e29dfc07
5 changed files with 44 additions and 24 deletions
|
|
@ -26,8 +26,8 @@ android {
|
||||||
applicationId = "com.aryan.reader"
|
applicationId = "com.aryan.reader"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 38
|
versionCode = 39
|
||||||
versionName = "1.0.37"
|
versionName = "1.0.38"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
|
|
|
||||||
|
|
@ -889,6 +889,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
, Text='${textToHighlight.substring(0, 50)}...' `);
|
, Text='${textToHighlight.substring(0, 50)}...' `);
|
||||||
|
console.log("TTS_LIST_DIAG: Attempting highlight. CFI: " + cfi + " Text: '" + textToHighlight.substring(0, 20) + "' Offset: " + startOffset);
|
||||||
|
|
||||||
window.removeHighlight();
|
window.removeHighlight();
|
||||||
|
|
||||||
if (!cfi || !textToHighlight) {
|
if (!cfi || !textToHighlight) {
|
||||||
|
|
@ -1098,12 +1100,15 @@
|
||||||
window.extractTextWithCfiFromTop = function () {
|
window.extractTextWithCfiFromTop = function () {
|
||||||
try {
|
try {
|
||||||
const ttsNodeSelector = "p, h1, h2, h3, h4, h5, h6, li, blockquote";
|
const ttsNodeSelector = "p, h1, h2, h3, h4, h5, h6, li, blockquote";
|
||||||
const allContentNodes = Array.from(document.body.querySelectorAll(ttsNodeSelector));
|
const allContentNodesRaw = Array.from(document.body.querySelectorAll(ttsNodeSelector));
|
||||||
|
|
||||||
|
// FIX: Filter out parents that contain matching children to prevent duplicates
|
||||||
|
const allContentNodes = allContentNodesRaw.filter(node => node.querySelector(ttsNodeSelector) === null);
|
||||||
|
|
||||||
let startBlock = null;
|
let startBlock = null;
|
||||||
let startIndex = -1;
|
let startIndex = -1;
|
||||||
|
|
||||||
for (let i = 0; i < allContentNodes.size || i < allContentNodes.length; i++) {
|
for (let i = 0; i < allContentNodes.length; i++) {
|
||||||
const node = allContentNodes[i];
|
const node = allContentNodes[i];
|
||||||
const rect = node.getBoundingClientRect();
|
const rect = node.getBoundingClientRect();
|
||||||
|
|
||||||
|
|
@ -1141,7 +1146,10 @@
|
||||||
|
|
||||||
window.extractTextWithCfi = function () {
|
window.extractTextWithCfi = function () {
|
||||||
const results =[];
|
const results =[];
|
||||||
const contentNodes = document.body.querySelectorAll("p, h1, h2, h3, h4, h5, h6, li, blockquote");
|
const ttsNodeSelector = "p, h1, h2, h3, h4, h5, h6, li, blockquote";
|
||||||
|
const contentNodesRaw = Array.from(document.body.querySelectorAll(ttsNodeSelector));
|
||||||
|
|
||||||
|
const contentNodes = contentNodesRaw.filter(node => node.querySelector(ttsNodeSelector) === null);
|
||||||
|
|
||||||
contentNodes.forEach((node) => {
|
contentNodes.forEach((node) => {
|
||||||
const text = node.innerText ? node.innerText.trim() : "";
|
const text = node.innerText ? node.innerText.trim() : "";
|
||||||
|
|
@ -1156,8 +1164,7 @@
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const jsonResult = JSON.stringify(results);
|
return JSON.stringify(results);
|
||||||
return jsonResult;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.extractTextWithCfiFromSelection = function() {
|
window.extractTextWithCfiFromSelection = function() {
|
||||||
|
|
@ -1186,8 +1193,14 @@
|
||||||
absoluteStartOffset += startOffset;
|
absoluteStartOffset += startOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allContentNodes = Array.from(document.body.querySelectorAll(ttsNodeSelector));
|
const allContentNodesRaw = Array.from(document.body.querySelectorAll(ttsNodeSelector));
|
||||||
const startIndex = allContentNodes.findIndex(node => node === startBlock);
|
const allContentNodes = allContentNodesRaw.filter(node => node.querySelector(ttsNodeSelector) === null);
|
||||||
|
|
||||||
|
let startIndex = allContentNodes.findIndex(node => node === startBlock);
|
||||||
|
|
||||||
|
if (startIndex === -1) {
|
||||||
|
startIndex = allContentNodes.findIndex(node => startBlock.contains(node));
|
||||||
|
}
|
||||||
|
|
||||||
if (startIndex === -1) return window.extractTextWithCfiFromTop();
|
if (startIndex === -1) return window.extractTextWithCfiFromTop();
|
||||||
|
|
||||||
|
|
@ -1197,11 +1210,13 @@
|
||||||
nodesToProcess.forEach((node, index) => {
|
nodesToProcess.forEach((node, index) => {
|
||||||
let fullText = node.textContent || "";
|
let fullText = node.textContent || "";
|
||||||
if (fullText.trim().length > 0 && node.offsetParent !== null) {
|
if (fullText.trim().length > 0 && node.offsetParent !== null) {
|
||||||
|
if (node.tagName === 'LI' || node.closest('li')) {
|
||||||
|
console.log("TTS_LIST_DIAG: Selection extracting node <" + node.tagName + "> inside LI. Text: '" + fullText.substring(0, 30) + "'");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const cfiObj = getCfiPathForElement(node, 0);
|
const cfiObj = getCfiPathForElement(node, 0);
|
||||||
if (cfiObj && cfiObj.cfi) {
|
if (cfiObj && cfiObj.cfi) {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
// Slice the very first block strictly from the selected character
|
|
||||||
let sliced = fullText.substring(absoluteStartOffset);
|
let sliced = fullText.substring(absoluteStartOffset);
|
||||||
if (sliced.trim().length > 0) {
|
if (sliced.trim().length > 0) {
|
||||||
results.push({
|
results.push({
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ class TtsJsBridge(
|
||||||
) {
|
) {
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun onStructuredTextExtracted(json: String) {
|
fun onStructuredTextExtracted(json: String) {
|
||||||
|
Timber.tag("TTS_LIST_DIAG").d("Bridge received JSON: $json")
|
||||||
if (json.isNotBlank() && json != "[]") {
|
if (json.isNotBlank() && json != "[]") {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
ttsStructuredTextHandler(json)
|
ttsStructuredTextHandler(json)
|
||||||
|
|
@ -545,6 +546,10 @@ fun ChapterWebView(
|
||||||
.d("JS -> ${message.substringAfter("BookmarkDiagnosis: ")}")
|
.d("JS -> ${message.substringAfter("BookmarkDiagnosis: ")}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message.startsWith("TTS_LIST_DIAG:") -> {
|
||||||
|
Timber.tag("TTS_LIST_DIAG").d("JS -> ${message.substringAfter("TTS_LIST_DIAG: ")}")
|
||||||
|
}
|
||||||
|
|
||||||
message.startsWith("CFI_DIAGNOSIS:") -> {
|
message.startsWith("CFI_DIAGNOSIS:") -> {
|
||||||
Timber.d(
|
Timber.d(
|
||||||
"JS -> ${message.substringAfter("CFI_DIAGNOSIS: ")}"
|
"JS -> ${message.substringAfter("CFI_DIAGNOSIS: ")}"
|
||||||
|
|
|
||||||
|
|
@ -2447,21 +2447,17 @@ fun EpubReaderHost(
|
||||||
ttsScope = scope,
|
ttsScope = scope,
|
||||||
onTtsTextReady = { jsonString ->
|
onTtsTextReady = { jsonString ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Timber.d("Vertical: onTtsTextReady received JSON. Length: ${jsonString.length}")
|
Timber.tag("TTS_LIST_DIAG").d("Vertical: Processing received JSON. Length: ${jsonString.length}") // Add this
|
||||||
val ttsChunks =
|
val ttsChunks = mutableListOf<TtsChunk>()
|
||||||
mutableListOf<TtsChunk>()
|
|
||||||
try {
|
try {
|
||||||
val jsonArray = JSONArray(jsonString)
|
val jsonArray = JSONArray(jsonString)
|
||||||
Timber.d("Vertical: Parsed JSON Array. Items: ${jsonArray.length()}")
|
|
||||||
for (i in 0 until jsonArray.length()) {
|
for (i in 0 until jsonArray.length()) {
|
||||||
val jsonObject =
|
val jsonObject = jsonArray.getJSONObject(i)
|
||||||
jsonArray.getJSONObject(i)
|
|
||||||
val text = jsonObject.getString("text")
|
val text = jsonObject.getString("text")
|
||||||
val cfiJsonString =
|
val cfiJsonObject = JSONObject(jsonObject.getString("cfi"))
|
||||||
jsonObject.getString("cfi")
|
|
||||||
val cfiJsonObject =
|
|
||||||
JSONObject(cfiJsonString)
|
|
||||||
val cfi = cfiJsonObject.getString("cfi")
|
val cfi = cfiJsonObject.getString("cfi")
|
||||||
|
|
||||||
|
Timber.tag("TTS_LIST_DIAG").d("Processing Chunk[$i]: text='${text.take(40)}...' cfi='$cfi'")
|
||||||
val baseOffset = jsonObject.optInt("startOffset", 0)
|
val baseOffset = jsonObject.optInt("startOffset", 0)
|
||||||
|
|
||||||
val subChunks =
|
val subChunks =
|
||||||
|
|
|
||||||
|
|
@ -1041,17 +1041,21 @@ class BookPaginator(
|
||||||
override fun findPageForCfiAndOffset(chapterIndex: Int, cfi: String, charOffset: Int): Int? {
|
override fun findPageForCfiAndOffset(chapterIndex: Int, cfi: String, charOffset: Int): Int? {
|
||||||
val index = chapterCharacterIndex[chapterIndex]
|
val index = chapterCharacterIndex[chapterIndex]
|
||||||
if (index.isNullOrEmpty()) {
|
if (index.isNullOrEmpty()) {
|
||||||
|
Timber.tag("TTS_PAGE_JUMP_DIAG").w("Lookup failed: No character index for chapter $chapterIndex")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetPath = CfiUtils.getPath(cfi)
|
val targetPath = CfiUtils.getPath(cfi)
|
||||||
val foundRange = index.find { range ->
|
|
||||||
|
val matches = index.filter { range ->
|
||||||
val rangePath = CfiUtils.getPath(range.cfi)
|
val rangePath = CfiUtils.getPath(range.cfi)
|
||||||
val cfiMatches = targetPath == rangePath || targetPath.startsWith(rangePath) || rangePath.startsWith(targetPath)
|
val pathMatches = targetPath == rangePath || targetPath.startsWith(rangePath)
|
||||||
val offsetMatches = charOffset >= range.startOffset && charOffset < range.endOffset
|
val offsetMatches = charOffset >= range.startOffset && charOffset < range.endOffset
|
||||||
cfiMatches && offsetMatches
|
pathMatches && offsetMatches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val foundRange = matches.maxByOrNull { CfiUtils.getPath(it.cfi).length }
|
||||||
|
|
||||||
return if (foundRange != null) {
|
return if (foundRange != null) {
|
||||||
val chapterStartPage = chapterStartPageIndices[chapterIndex]
|
val chapterStartPage = chapterStartPageIndices[chapterIndex]
|
||||||
if (chapterStartPage == null) {
|
if (chapterStartPage == null) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue