From f810cfedd495d8a1b807b96acc8141ef926288d6 Mon Sep 17 00:00:00 2001 From: Shuming Liu Date: Sun, 13 Sep 2026 11:35:46 +0800 Subject: [PATCH] fixed some bugs on ios --- .../UserInterfaceState.xcuserstate | Bin 23710 -> 23700 bytes ios/OralTrainer/Player/PlayerController.swift | 19 +++++++++++++++--- .../Services/ImitationAssessor.swift | 14 ++++++++----- ios/OralTrainer/Views/RootView.swift | 3 +++ ios/OralTrainer/Views/TestView.swift | 4 ++-- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate b/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate index 43073e8a88688b75878aecbbc4de38016339e7d0..a8b47828a4122cc3d87260213a818202589c4cd5 100644 GIT binary patch delta 1359 zcma))du$X%9LHy-dr(@BJIbR7++5!j+H$>e53aPOz1`WF19pKv5h|o`7pU56rLEYe z&|M-%NWj2Kh%G2wL9kRMuKa;w0H2jd#DfMSLgb+cF^YzVs95)k@gM$iH<_7F_WS$h z_xb{M{L7#`dQM`xf+l!YcECn`YGkqga0Gxhi3X7jL|?4wr}?AK*W4SaaMpdYaM z6O*$*2FQT7;T?F_*nbCEN|$0$%o;bzq5qQSgj&!RfP1ywGXhJI4W)o_5rmOli6}i0 za0aC*rOF;9bMZ6v;ZPtP3_TxeUJ`6v-r5ifx6BQNSG24g)$r3Oa!mOb-JLBU% z2bQ>g{6;wlkVVYw2CpfuQJOzDuOmWndXg54ls-s*zuy9ZPpZ=pc1on^pmfsoD5`wm2p?J!jx*1OF~g9ozfw&o{N3)(qVpQ->1KT_xQxX0bzs zJ0Dt{GHOm6HCJa1S)4KOD+5QCrjGy5;+*NcVPP7G&GmJ|g_fqp!E9}kkgf0L2aIc- zd1q1ob-jbMLBEF=3d&sh{=9tR^X9qTE+NnF6NEgcUo4nGd>*&oIlZcGRfES>Ti>*@ zF&t`aF7QlqFRGs(1E)K8y6&P`!*g>cH>pYKD*T|)Ito;RMi2ybpbexLYm2cujkgdK z09T3wl<6Ydm|%(ncs?%2mAD!& z#46VDIeZb{#1HVGJ>G7ykFneAQ|-m}S$a}M4hPq2B{h#H9`}6g`OMSr`NKO=UsiKE zu5&Bj&A-LJ$A8G5;Lr2l@E7^-_^bR6{0;stf1m%09~6*a65@pf!6UQ^yM&X%J>n#V zgeM}IM;4OBBuE-a6KNys$a=DwY$si$hxCzm$Pw}-iIM^GHMu}8k;~*7xlV492V_u0 zA}0vSwB0$ z&a+GGGP}mEv!B_5U)gPTkKGTTz=%LnV1hJ7%9Y#_FU^(~NK2%rrLgp@)FQ2x+NCwp sMro7uvb0s&CT*8qmAa*4()Y4O_Q`&Ej$A3%$Q|-}`9*DQtsTDf4+J#6KmY&$ delta 1404 zcmZuxZA=tL7@nQmL(~d~2uOilK8`CQ2giB0cSnPA?9T3->8=ENV5+r6?*eFnDo`tt zFq>K})!L7>O^Tl!6-10}-8RNb4XtgX5sgKyX-!NTO&hgVTg9lUrh91pL+8i)&Xawf zC-c0snJtH?9fzpT`l&=4d7iuwb!LRoK?n5L&vX zHQchmVmWu=#?#X`D+8NqzdpsBvH;RI3f6)SSO|-%m8xJcAVgpxAfvF@&^ssU>vq*0 z+__Cr2Cns1426U?(w9>f+6AKP444Mu3>61`AlVs#9_S@|$zTMQfYW3jDI>W{Le0zC z8pBPk&xM=nTI(0R($EwRG=;*ihg%X>^lF&RHJ2E?eflc4(qi_S4v<0Zc#$yGs#3Ws zWqXp(?^r|?XxklGQ~E|tCqTSu1Quw=99dMKe%0}gc{=pcK{BXPl@Y2NmXI&WK24oF zQ|sYUEYM0XrI(RCDpjLW3ypc*=jI#pa#oMh4IrLgJ~8qCOpIwZS@U_r!h8;TRcfc zuf55hsK)4xXbm;bBNqETT)ErsFZYOccd@_R?)AHi?Jlo_bGn?oU-bHdwW}IA*W!j( z>zkXx%`Hx@&|T9|Q5*6Uxb-~AHYqHsI zfa1&|P@%UglW8ibt*d-Ca3AdB_5GGMmh%u^BAOT3H*L$L6y>7U{87v+3Lpj&N^s8@MiRBiGIKbNji^ zxi7eb+!1b=JIejQ9pkQert1r9PMSJ<_`UoPf0Q5Lf98MVuke5HH~7E#G5)S#7Gi~| zLY$Bw%n(w9Q~?R=ginQEPz)+V0aS^CXbGxA%TWtji#pL;=zX*WZAX1*03ASw(Ghe4 zT|~d4%jkD>6Y3=+#~nOyXAg)ME*-jQ;?!4HOkA% aGG)E;uJYdag90FY)MM^E>i<>cgMR_^CCjk@ diff --git a/ios/OralTrainer/Player/PlayerController.swift b/ios/OralTrainer/Player/PlayerController.swift index cd3f8f1..2d16d70 100644 --- a/ios/OralTrainer/Player/PlayerController.swift +++ b/ios/OralTrainer/Player/PlayerController.swift @@ -318,21 +318,27 @@ final class PlayerController: ObservableObject { } /// 只播放当前句子,播完自动暂停并回到句首(评测“播放原句”)。 + /// 必须先打上句尾标记再 seek,这样 completion 里的 forwardPlaybackEndTime + /// 会卡在当前句 endMs,而不是等 250ms 观察器才停。 func playCurrentSentenceAndStop() { guard let sentence = currentSentence else { return } - performSeek(to: sentenceSeekPosition(sentence)) stopAtSentenceEnd = true stopAtSentenceEndIndex = sentence.index - play() + lastSentenceIndex = sentence.index + playbackIntent = true + performSeek(to: sentenceSeekPosition(sentence), resumePlayback: true) } // MARK: - 设置 func setContinuousPlayback(_ enabled: Bool) { config.continuousPlayback = enabled - refreshSentenceBoundaryGuard() + if !enabled { + lastSentenceIndex = currentSentence?.index ?? lastSentenceIndex + } + refreshSentenceBoundaryGuard(force: true) } func setSentenceMode(_ enabled: Bool) { @@ -783,6 +789,13 @@ final class PlayerController: ObservableObject { tick() return } + // 评测“播放原句”即使处于连续播放,也要用当前句硬边界,不能滑进下一句。 + if stopAtSentenceEnd, + let index = stopAtSentenceEndIndex, + let sentence = mediaItem?.sentences.first(where: { $0.index == index }) { + applySentenceEndAction(sentence) + return + } // 若最后一个句子的 endMs 就是视频末尾,forwardPlaybackEndTime 和自然结束 // 是同一时间点。此时必须执行“当前句”的句尾动作,而不能整条视频回零。 if config.sentenceMode, diff --git a/ios/OralTrainer/Services/ImitationAssessor.swift b/ios/OralTrainer/Services/ImitationAssessor.swift index 464e2d8..de13c67 100644 --- a/ios/OralTrainer/Services/ImitationAssessor.swift +++ b/ios/OralTrainer/Services/ImitationAssessor.swift @@ -88,16 +88,20 @@ enum ImitationAssessor { appendFile(&body, boundary: boundary, name: "audio", filename: filename, mimeType: mimeType, data: recordingData) body.append(Data("--\(boundary)--\r\n".utf8)) + var headers = [ + "Accept": "application/json", + "Content-Type": "multipart/form-data; boundary=\(boundary)", + "X-Client-Key": AppConfig.assessmentAPIKey, + ] + if !AppConfig.authToken.isEmpty { + headers["Authorization"] = "Bearer \(AppConfig.authToken)" + } let result = try await HTTPClient.execute( baseURL: AppConfig.serverBaseURL, path: path, method: "POST", timeout: AppConfig.assessmentTimeout, - headers: [ - "Accept": "application/json", - "Content-Type": "multipart/form-data; boundary=\(boundary)", - "X-Client-Key": AppConfig.assessmentAPIKey, - ], + headers: headers, body: body ) guard (200...299).contains(result.statusCode) else { diff --git a/ios/OralTrainer/Views/RootView.swift b/ios/OralTrainer/Views/RootView.swift index f133526..f45305a 100644 --- a/ios/OralTrainer/Views/RootView.swift +++ b/ios/OralTrainer/Views/RootView.swift @@ -90,6 +90,9 @@ struct RootView: View { .onChange(of: activeModule) { module in if module == .train { controller.setContinuousPlayback(continuousPlayback) + } else if module == .test { + // 与安卓测试页一致:按当前句窗口播放,避免滑进下一句。 + controller.setContinuousPlayback(false) } else { controller.setContinuousPlayback(true) } diff --git a/ios/OralTrainer/Views/TestView.swift b/ios/OralTrainer/Views/TestView.swift index e0ebc70..78901db 100644 --- a/ios/OralTrainer/Views/TestView.swift +++ b/ios/OralTrainer/Views/TestView.swift @@ -35,7 +35,7 @@ struct TestView: View { PlayerSectionView( controller: controller, showsContinuousToggle: false, - continuousPlayback: .constant(true), + continuousPlayback: .constant(false), showsSubtitle: $testSubtitleVisible, onFullscreen: onFullscreen ) @@ -321,7 +321,7 @@ struct TestView: View { dubSegments[sentence.index] = audioURL dubRawSegments[sentence.index] = rawPersistedURL mergedDubURL = nil - submitAssessment(recordingURL: url) + submitAssessment(recordingURL: audioURL) } private func restorePlaybackAudioSession() {