continue fixing
This commit is contained in:
@@ -357,6 +357,20 @@ class MainActivity : Activity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyTranslatedSentences(itemId: String, translations: Map<Int, String>) {
|
||||
if (translations.isEmpty()) return
|
||||
val current = controller.currentTrainingItem() ?: return
|
||||
if (current.id != itemId) return
|
||||
val updated = current.sentences.map { sentence ->
|
||||
sentence.copy(zhText = translations[sentence.index] ?: sentence.zhText)
|
||||
}
|
||||
controller.updateSentences(current.id, updated)
|
||||
screenState.courseBoundaries[current.id]?.let { cached ->
|
||||
screenState.courseBoundaries[current.id] = cached.copy(sentences = updated)
|
||||
}
|
||||
applyChineseSubtitle(controller.currentSentence())
|
||||
}
|
||||
|
||||
private fun ensureChineseTranslations() {
|
||||
if (screenState.activeModule != Module.TRAIN || !screenState.chineseSubtitlesEnabled) {
|
||||
return
|
||||
@@ -382,18 +396,16 @@ class MainActivity : Activity() {
|
||||
item.id,
|
||||
expectedIndexes,
|
||||
object : SubtitleTranslationCallback {
|
||||
override fun onProgress(result: SubtitleTranslationResult) {
|
||||
if (!screenState.isCurrentTranslationRequest(generation, item.id)) return
|
||||
applyTranslatedSentences(item.id, result.translations)
|
||||
}
|
||||
|
||||
override fun onSuccess(result: SubtitleTranslationResult) {
|
||||
if (!screenState.isCurrentTranslationRequest(generation, item.id)) return
|
||||
applyTranslatedSentences(item.id, result.translations)
|
||||
screenState.chineseTranslationLoading = false
|
||||
val current = controller.currentTrainingItem() ?: return
|
||||
if (current.id != item.id) return
|
||||
val updated = current.sentences.map { sentence ->
|
||||
sentence.copy(zhText = result.translations[sentence.index] ?: sentence.zhText)
|
||||
}
|
||||
controller.updateSentences(current.id, updated)
|
||||
screenState.courseBoundaries[current.id]?.let { cached ->
|
||||
screenState.courseBoundaries[current.id] = cached.copy(sentences = updated)
|
||||
}
|
||||
screenState.chineseTranslationError = ""
|
||||
applyChineseSubtitle(controller.currentSentence())
|
||||
if (bindings.hasPlayerChrome) {
|
||||
bindings.statusText.text = "已加载中文字幕"
|
||||
|
||||
@@ -3,9 +3,11 @@ package cn.learningpad.oraltrainer.sample
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.URL
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
@@ -14,9 +16,13 @@ import java.util.concurrent.Future
|
||||
data class SubtitleTranslationResult(
|
||||
val videoHash: String,
|
||||
val translations: Map<Int, String>,
|
||||
val complete: Boolean = false,
|
||||
val status: String = "",
|
||||
val error: String = "",
|
||||
)
|
||||
|
||||
interface SubtitleTranslationCallback {
|
||||
fun onProgress(result: SubtitleTranslationResult) {}
|
||||
fun onSuccess(result: SubtitleTranslationResult)
|
||||
fun onError(error: Throwable)
|
||||
}
|
||||
@@ -46,7 +52,11 @@ internal class SubtitleTranslationApi(
|
||||
): CancellableTranslationRequest {
|
||||
val future = executor.submit {
|
||||
try {
|
||||
val result = loadBlocking(videoHash, expectedIndexes)
|
||||
val result = loadBlocking(videoHash, expectedIndexes) { partial ->
|
||||
if (!Thread.currentThread().isInterrupted) {
|
||||
mainHandler.post { callback.onProgress(partial) }
|
||||
}
|
||||
}
|
||||
if (!Thread.currentThread().isInterrupted) {
|
||||
mainHandler.post { callback.onSuccess(result) }
|
||||
}
|
||||
@@ -66,15 +76,71 @@ internal class SubtitleTranslationApi(
|
||||
private fun loadBlocking(
|
||||
videoHash: String,
|
||||
expectedIndexes: Collection<Int>,
|
||||
onProgress: (SubtitleTranslationResult) -> Unit,
|
||||
): SubtitleTranslationResult {
|
||||
val stored = runCatching {
|
||||
requestBlocking(videoHash, method = "GET", readTimeoutMs = 15_000)
|
||||
}.getOrNull()
|
||||
val expected = expectedIndexes.toSet()
|
||||
if (stored != null && (expected.isEmpty() || expected.all { stored.translations.containsKey(it) })) {
|
||||
return stored
|
||||
val deadline = SystemClock.elapsedRealtime() + TRANSLATION_DEADLINE_MS
|
||||
var kickJob = true
|
||||
var lastError: Throwable? = null
|
||||
var lastCount = -1
|
||||
var stalledRounds = 0
|
||||
while (SystemClock.elapsedRealtime() < deadline) {
|
||||
if (Thread.currentThread().isInterrupted) {
|
||||
throw InterruptedException("Subtitle translation cancelled")
|
||||
}
|
||||
val stored = runCatching {
|
||||
requestBlocking(videoHash, method = "GET", readTimeoutMs = 15_000)
|
||||
}.onFailure { lastError = it }.getOrNull()
|
||||
if (stored == null) {
|
||||
kickJob = true
|
||||
} else {
|
||||
onProgress(stored)
|
||||
if (isComplete(stored, expected)) {
|
||||
return stored
|
||||
}
|
||||
if (stored.translations.size > lastCount) {
|
||||
lastCount = stored.translations.size
|
||||
stalledRounds = 0
|
||||
} else {
|
||||
stalledRounds += 1
|
||||
}
|
||||
if (stored.status != "running" && stalledRounds >= 2) {
|
||||
kickJob = true
|
||||
}
|
||||
}
|
||||
if (kickJob) {
|
||||
val posted = runCatching {
|
||||
requestBlocking(videoHash, method = "POST", readTimeoutMs = 30_000)
|
||||
}.onFailure { error ->
|
||||
lastError = error
|
||||
if (error !is SocketTimeoutException) {
|
||||
stalledRounds += 1
|
||||
}
|
||||
}.getOrNull()
|
||||
kickJob = false
|
||||
if (posted != null) {
|
||||
onProgress(posted)
|
||||
if (isComplete(posted, expected)) {
|
||||
return posted
|
||||
}
|
||||
if (posted.status == "error") {
|
||||
lastError = IOException(posted.error.ifBlank { "Subtitle translation failed" })
|
||||
}
|
||||
}
|
||||
}
|
||||
Thread.sleep(POLL_INTERVAL_MS)
|
||||
}
|
||||
return requestBlocking(videoHash, method = "POST", readTimeoutMs = 180_000)
|
||||
throw lastError ?: IOException("Subtitle translation timed out")
|
||||
}
|
||||
|
||||
private fun isComplete(
|
||||
result: SubtitleTranslationResult,
|
||||
expected: Set<Int>,
|
||||
): Boolean {
|
||||
if (expected.isNotEmpty()) {
|
||||
return expected.all { result.translations.containsKey(it) }
|
||||
}
|
||||
return result.complete
|
||||
}
|
||||
|
||||
private fun requestBlocking(
|
||||
@@ -115,9 +181,17 @@ internal class SubtitleTranslationApi(
|
||||
return SubtitleTranslationResult(
|
||||
videoHash = root.getString("video_hash"),
|
||||
translations = translations,
|
||||
complete = root.optBoolean("complete", false),
|
||||
status = root.optString("status"),
|
||||
error = root.optString("error"),
|
||||
)
|
||||
} finally {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val POLL_INTERVAL_MS = 1_500L
|
||||
const val TRANSLATION_DEADLINE_MS = 15 * 60 * 1000L
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user