From 0b85a2ed57b8f486e763b5f58d6f995ce8f752bb Mon Sep 17 00:00:00 2001 From: Shuming Liu Date: Sun, 30 Aug 2026 11:57:23 +0800 Subject: [PATCH] refresh borders of sentences --- .../oraltrainer/sdk/OralTrainerController.kt | 22 ++++++++++++++ .../oraltrainer/sdk/SentenceBoundaryApi.kt | 1 + .../oraltrainer/sample/MainActivity.kt | 30 +++++++++++++++++-- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/OralTrainerController.kt b/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/OralTrainerController.kt index 09132bd..438a52f 100644 --- a/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/OralTrainerController.kt +++ b/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/OralTrainerController.kt @@ -141,6 +141,28 @@ class OralTrainerController internal constructor( loadItems(listOf(item)) } + /** + * Replaces sentences without preparing/reloading media, preserving playback + * position and state so a background boundary refresh can apply immediately. + */ + fun updateSentences(mediaId: String, sentences: List): Boolean { + val index = mediaItems.indexOfFirst { it.id == mediaId } + if (index < 0) { + return false + } + val updatedItem = mediaItems[index].copy(sentences = sentences) + if (mediaItems[index] == updatedItem) { + return true + } + mediaItems[index] = updatedItem + if (index == player.currentMediaItemIndex) { + listeners.forEach { it.onMediaChanged(updatedItem) } + notifySentenceIfChanged(force = true) + notifySnapshot() + } + return true + } + fun play() { player.play() } diff --git a/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/SentenceBoundaryApi.kt b/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/SentenceBoundaryApi.kt index 8f8a43f..88eb1c9 100644 --- a/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/SentenceBoundaryApi.kt +++ b/android/oral-trainer-sdk/src/main/java/cn/learningpad/oraltrainer/sdk/SentenceBoundaryApi.kt @@ -111,6 +111,7 @@ class SentenceBoundaryApi internal constructor( connection.requestMethod = "GET" connection.connectTimeout = config.connectTimeoutMs connection.readTimeout = config.readTimeoutMs + connection.useCaches = false connection.setRequestProperty("Accept", "application/json") connection.setRequestProperty("User-Agent", config.userAgent) val statusCode = connection.responseCode diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt index 008864d..25a90c9 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt @@ -107,6 +107,7 @@ class MainActivity : Activity() { private var activeItemId: String = "" private var currentSentenceCount = 0 + private var sentenceBoundaryRequestGeneration = 0 private var catalogVideos: List = emptyList() private var catalogStatusTextValue = "正在同步" private var activeModule = Module.TRAIN @@ -1419,6 +1420,9 @@ class MainActivity : Activity() { catalogStatusText.text = catalogStatusTextValue } renderCatalog() + catalogVideos + .firstOrNull { it.videoHash == activeItemId } + ?.let { refreshSentenceBoundaries(it, background = true) } } override fun onError(error: Throwable) { @@ -1460,18 +1464,38 @@ class MainActivity : Activity() { val loadingItem = video.toTrainingMediaItem() controller.loadItem(loadingItem) currentSentenceCount = video.sentenceCount + refreshSentenceBoundaries(video, background = false) + } + + private fun refreshSentenceBoundaries( + video: TrainingVideoSummary, + background: Boolean, + ) { + val requestGeneration = ++sentenceBoundaryRequestGeneration + if (!background && ::statusText.isInitialized) { + statusText.text = "正在加载云端课程..." + } else if (::statusText.isInitialized) { + statusText.text = "正在刷新句子边界..." + } sdk.sentenceBoundaryApi.fetch(video.videoHash, object : SentenceBoundaryApiCallback { override fun onSuccess(result: SentenceBoundaryApiResult) { - if (activeItemId != video.videoHash) { + if ( + sentenceBoundaryRequestGeneration != requestGeneration || + activeItemId != video.videoHash + ) { return } currentSentenceCount = result.sentences.size - controller.loadItem(video.toTrainingMediaItem(result.sentences)) + controller.updateSentences(video.videoHash, result.sentences) + refreshCurrentUi() statusText.text = "课程已就绪:${result.sentences.size} 句" } override fun onError(error: Throwable) { - if (activeItemId != video.videoHash) { + if ( + sentenceBoundaryRequestGeneration != requestGeneration || + activeItemId != video.videoHash + ) { return } statusText.text = "句子边界加载失败:${error.message.orEmpty()}"