切后命名方案三(用户拍板):名字不诞生于开火,只诞生于切后纯净窗:
- 开火(FASTROLL/ONSET)不再发号发注册——只切行,grayLinePending 灰行先开(未识别);switchSerial/switchPrevActive 记录本切次 - deferredNaming(T+1.5~3.5s,取切后尾1.5s纯净素材,补喂机制保证全为新说法人声音):强匹配开火前主≥0.5→撤销误切;匹配已知人→上其名;确认无人(<0.42)→发新号双空间注册(素材干净永不产幽灵);灰色带→留灰由判定链收编 - adoptSwitchLabel(灰行就地冠名/未建行走预置);undoSwitch(重开抹迹/改签并回);serial 每次落地后自增防双发 - 根治病史:16:21 S6潮(单人在2.5min被8连发开火发出S7~S21七个幽灵,源自开火窗混合素材);新日志锚 NAMING-UNDO/ADOPT/NEW/WEAK
This commit is contained in:
parent
31762d4627
commit
2725c62b14
|
|
@ -221,10 +221,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
private val onsetMinSamples = 19200 // 1.2s 开口窗
|
private val onsetMinSamples = 19200 // 1.2s 开口窗
|
||||||
// 喂料恒定滞后(2026-09-16):识别流滞后 1.2s 喂入,切点前的音频可改道;
|
// 喂料恒定滞后(2026-09-16):识别流滞后 1.2s 喂入,切点前的音频可改道;
|
||||||
// 全局字幕延迟 ~1.2s 的代价换取换人零串行
|
// 全局字幕延迟 ~1.2s 的代价换取换人零串行
|
||||||
// 主动撤销的强度门槛(2026-09-16 实锤:短插嘴混合窗复核出 0.5~0.6 弱匹配,
|
|
||||||
// 二元"认得出"误撤了 0.04 深低的真实异常;同人确认分实测 0.70~1.00,
|
|
||||||
// 0.68 取安全带) —— 复核低于此分只留痕不撤销,让切换立住
|
|
||||||
private val revertCancelMin = 0.68f
|
|
||||||
// 快速滚动通道(2026-09-16 用户三次拍板后终定 C:维持 2s 窗——最快档,
|
// 快速滚动通道(2026-09-16 用户三次拍板后终定 C:维持 2s 窗——最快档,
|
||||||
// 接受误切闪烁,由并回自愈兜底)。窗长演化留档:2s(风暴,单窗20发/5min)
|
// 接受误切闪烁,由并回自愈兜底)。窗长演化留档:2s(风暴,单窗20发/5min)
|
||||||
// →3s(折中)→ 终选回 2s;护持:连续两窗/注册表复用/销案/冷却/并回自愈。
|
// →3s(折中)→ 终选回 2s;护持:连续两窗/注册表复用/销案/冷却/并回自愈。
|
||||||
|
|
@ -234,6 +230,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
private val fastStepSamples = 16000 // 1s
|
private val fastStepSamples = 16000 // 1s
|
||||||
private val fastWinSamples = 32000 // 2s 尾窗(用户终选)
|
private val fastWinSamples = 32000 // 2s 尾窗(用户终选)
|
||||||
private val fastMinSamples = 24000 // ≥1.5s 才判
|
private val fastMinSamples = 24000 // ≥1.5s 才判
|
||||||
|
private val namingWinSamples = 24000 // 切后命名窗 1.5s(纯净素材,方案三)
|
||||||
|
private val namingMinSamples = 16000 // ≥1s 才判
|
||||||
private val feedLagSamples = 24000 // 1.5s:覆盖判定链(1.2s采集+计算)的
|
private val feedLagSamples = 24000 // 1.5s:覆盖判定链(1.2s采集+计算)的
|
||||||
// 余量,开口渗漏≈0;全局字幕延迟即此值,调参范围 1.2~1.5s
|
// 余量,开口渗漏≈0;全局字幕延迟即此值,调参范围 1.2~1.5s
|
||||||
@Volatile private var onsetSeq = 0L // 行切换/会话开始作废在途开口判定
|
@Volatile private var onsetSeq = 0L // 行切换/会话开始作废在途开口判定
|
||||||
|
|
@ -249,6 +247,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
@Volatile private var pendingForceEndpoint = false
|
@Volatile private var pendingForceEndpoint = false
|
||||||
// 滞后喂料的切点冻结请求(判定线程置位,录制线程消费,2026-09-16)
|
// 滞后喂料的切点冻结请求(判定线程置位,录制线程消费,2026-09-16)
|
||||||
@Volatile private var cutRequested = false
|
@Volatile private var cutRequested = false
|
||||||
|
// 切后命名(方案三,2026-09-16):开火只切行(灰行);名字由"切后纯净窗"
|
||||||
|
// 在 T+2.5s 诞生——强匹配旧人→上其名或撤销误切;确认无人→发新号(素材
|
||||||
|
// 干净,永不产幽灵);灰色地带留灰由判定链收编。serial 作废在途命名
|
||||||
|
@Volatile private var switchSerial = 0L
|
||||||
|
@Volatile private var switchPrevActive: String? = null
|
||||||
|
private var grayLinePending = false // 主线程:下一新行先以灰行开(等命名)
|
||||||
private val forceSwitchStreaks = 2
|
private val forceSwitchStreaks = 2
|
||||||
// 强切冷却(仅 speakerCheckHandler 线程访问,无需 volatile)
|
// 强切冷却(仅 speakerCheckHandler 线程访问,无需 volatile)
|
||||||
private var lastForceSwitchAtMs = 0L
|
private var lastForceSwitchAtMs = 0L
|
||||||
|
|
@ -653,6 +657,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
var silenceRun = 0
|
var silenceRun = 0
|
||||||
var onsetRun = 0 // 开口即查:静音后已累积的开口语音采样数
|
var onsetRun = 0 // 开口即查:静音后已累积的开口语音采样数
|
||||||
var fastSince = 0 // 快速滚动通道:1s 周期的采样计数
|
var fastSince = 0 // 快速滚动通道:1s 周期的采样计数
|
||||||
|
var lastCutConsumeMs = 0L // 方案三:最近一次切点冻结的时刻(命名窗锚)
|
||||||
// 恒定滞后喂料(2026-09-16):音频先进 pendingBuf,滞后 feedLagSamples
|
// 恒定滞后喂料(2026-09-16):音频先进 pendingBuf,滞后 feedLagSamples
|
||||||
// 再喂识别流/样本缓冲(窗口分析保持实时)。换人决定时冻结缓冲,
|
// 再喂识别流/样本缓冲(窗口分析保持实时)。换人决定时冻结缓冲,
|
||||||
// 断句后补喂给新流——新人开头音频从不进入旧句子。
|
// 断句后补喂给新流——新人开头音频从不进入旧句子。
|
||||||
|
|
@ -676,6 +681,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (!frozen && cutRequested) {
|
if (!frozen && cutRequested) {
|
||||||
cutRequested = false
|
cutRequested = false
|
||||||
frozen = true
|
frozen = true
|
||||||
|
lastCutConsumeMs = SystemClock.elapsedRealtime()
|
||||||
if (sentenceId != -1L) pendingForceEndpoint = true
|
if (sentenceId != -1L) pendingForceEndpoint = true
|
||||||
Log.i(TAG, "ONSET-HOLD frozen pending=" +
|
Log.i(TAG, "ONSET-HOLD frozen pending=" +
|
||||||
"${pendingSamples * 1000 / sampleRateInHz}ms")
|
"${pendingSamples * 1000 / sampleRateInHz}ms")
|
||||||
|
|
@ -711,19 +717,16 @@ class MainActivity : AppCompatActivity() {
|
||||||
val simFast = SpeakerRecognition.similarity(
|
val simFast = SpeakerRecognition.similarity(
|
||||||
fastEmb, anchor)
|
fastEmb, anchor)
|
||||||
if (simFast >= emaAbsorbMin) return@post
|
if (simFast >= emaAbsorbMin) return@post
|
||||||
// 乐观快切(2026-09-16 用户拍板:第一次识别
|
// 方案三(2026-09-16):开火不发号——只切行(灰行),
|
||||||
// 先切,出错由判定链并回自愈;准确复核不再
|
// 名字由切后纯净窗在 T+2.5s 诞生
|
||||||
// 挡在切换前,只做善后)
|
val serial = ++switchSerial
|
||||||
val active = activeLabelForWindow
|
switchPrevActive = activeLabelForWindow
|
||||||
val label = SpeakerRecognition.nextAutoLabel().also {
|
|
||||||
SpeakerRecognition.fastRegistry.addAuto(it, fastEmb)
|
|
||||||
}
|
|
||||||
Log.i(TAG, "ONSET-SWITCH fastSim=" +
|
Log.i(TAG, "ONSET-SWITCH fastSim=" +
|
||||||
"%.2f".format(simFast) +
|
"%.2f".format(simFast) +
|
||||||
" label=$label active=${active ?: "-"}")
|
" serial=$serial active=" +
|
||||||
|
(switchPrevActive ?: "-"))
|
||||||
cutRequested = true
|
cutRequested = true
|
||||||
uiHandler.post { onOnsetSwitch(label) }
|
uiHandler.post { onOnsetSwitch(null) }
|
||||||
accurateRecheckAfterSwitch(slice, label, active, "ONSET")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -742,6 +745,22 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (fastSince >= fastStepSamples) {
|
if (fastSince >= fastStepSamples) {
|
||||||
fastSince = 0
|
fastSince = 0
|
||||||
val winF = window.snapshot()
|
val winF = window.snapshot()
|
||||||
|
// 方案三:开火后 1.5~3.5s 的 tick 走"切后命名"(纯净素材)
|
||||||
|
val namingSerial = if (lastCutConsumeMs != 0L) {
|
||||||
|
val dt = SystemClock.elapsedRealtime() - lastCutConsumeMs
|
||||||
|
if (dt in 1_500L..3_500L) switchSerial else 0L
|
||||||
|
} else 0L
|
||||||
|
if (namingSerial != 0L) {
|
||||||
|
val nN = namingWinSamples.coerceAtMost(winF.size)
|
||||||
|
if (nN >= namingMinSamples &&
|
||||||
|
rmsOf(winF) >= minWindowRms
|
||||||
|
) {
|
||||||
|
val tailN = winF.copyOfRange(winF.size - nN, winF.size)
|
||||||
|
speakerCheckHandler.post {
|
||||||
|
deferredNaming(namingSerial, tailN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
val nF = fastWinSamples.coerceAtMost(winF.size)
|
val nF = fastWinSamples.coerceAtMost(winF.size)
|
||||||
if (nF >= fastMinSamples && rmsOf(winF) >= minWindowRms) {
|
if (nF >= fastMinSamples && rmsOf(winF) >= minWindowRms) {
|
||||||
val tailF = winF.copyOfRange(winF.size - nF, winF.size)
|
val tailF = winF.copyOfRange(winF.size - nF, winF.size)
|
||||||
|
|
@ -761,8 +780,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (fastRollLowStreak < 2) return@post
|
if (fastRollLowStreak < 2) return@post
|
||||||
fastRollLowStreak = 0
|
fastRollLowStreak = 0
|
||||||
val active = activeLabelForWindow
|
val active = activeLabelForWindow
|
||||||
// 先查注册表:认出当前人 → 销案(白捡的防护);
|
// 先查注册表:认出当前人 → 销案(白捡的防护)
|
||||||
// 认出别人 → 复用其编号;彻底陌生才发新号
|
|
||||||
val known = rollingSpeakerSearch(emb)
|
val known = rollingSpeakerSearch(emb)
|
||||||
if (known != null && known == active) {
|
if (known != null && known == active) {
|
||||||
fastRollLowStreak = 0
|
fastRollLowStreak = 0
|
||||||
|
|
@ -774,18 +792,16 @@ class MainActivity : AppCompatActivity() {
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
lastForceSwitchAtMs = nowMs
|
lastForceSwitchAtMs = nowMs
|
||||||
val label = known
|
// 方案三(2026-09-16):开火不发号——只切行(灰行),
|
||||||
?: SpeakerRecognition.nextAutoLabel().also {
|
// 名字由切后纯净窗在 T+2.5s 诞生
|
||||||
SpeakerRecognition.fastRegistry.addAuto(it, emb)
|
val serial = ++switchSerial
|
||||||
}
|
switchPrevActive = active
|
||||||
Log.i(TAG, "FASTROLL-SWITCH sim=" +
|
Log.i(TAG, "FASTROLL-SWITCH sim=" +
|
||||||
"%.2f".format(sim) +
|
"%.2f".format(sim) +
|
||||||
" label=$label kind=" +
|
" serial=$serial active=${active ?: "-"}")
|
||||||
(if (known != null) "known" else "new") +
|
|
||||||
" active=${active ?: "-"}")
|
|
||||||
cutRequested = true
|
cutRequested = true
|
||||||
uiHandler.post { onOnsetSwitch(label) }
|
uiHandler.post { onOnsetSwitch(null) }
|
||||||
accurateRecheckAfterSwitch(tailF, label, active, "FASTROLL")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1787,15 +1803,15 @@ class MainActivity : AppCompatActivity() {
|
||||||
// 底行还有在途句(旧人话尾仍在流里)→ 借强 endpoint 定格它,定格完成
|
// 底行还有在途句(旧人话尾仍在流里)→ 借强 endpoint 定格它,定格完成
|
||||||
// 再封行(onsetClosePending,见 finalizeSentence);已在途句为空则直接
|
// 再封行(onsetClosePending,见 finalizeSentence);已在途句为空则直接
|
||||||
// 封行。新标签经 pendingOnsetLabel 交给 appendNewLine。
|
// 封行。新标签经 pendingOnsetLabel 交给 appendNewLine。
|
||||||
private fun onOnsetSwitch(label: String) {
|
private fun onOnsetSwitch(label: String?) {
|
||||||
val list = if (historyMode) stashedLiveLines ?: subtitleLines else subtitleLines
|
val list = if (historyMode) stashedLiveLines ?: subtitleLines else subtitleLines
|
||||||
val bottom = list.lastOrNull() ?: return
|
val bottom = list.lastOrNull() ?: return
|
||||||
pendingOnsetLabel = label
|
pendingOnsetLabel = label
|
||||||
pendingOnsetLabelAt = SystemClock.elapsedRealtime()
|
pendingOnsetLabelAt = SystemClock.elapsedRealtime()
|
||||||
// 换行标签不依赖"partial 及时到达"(2026-09-16:预置 5s 过期曾致新行
|
// 方案三(2026-09-16):开火传 null → 下一新行先以灰行开("未识别"),
|
||||||
// 沿用旧标签,观感=没换人——实锤 S2 案预置 16s 后丢弃):落锤即写
|
// 名字等切后纯净窗;deferredNaming 结果经 adoptSwitchLabel 补上
|
||||||
// 最近确认说话人,此后任何时刻开的新行都挂新标
|
if (label == null) grayLinePending = true
|
||||||
lastConfirmedLabel = label
|
if (label != null) lastConfirmedLabel = label
|
||||||
if (!bottom.open) return // 底行已封:下一行自然消费预置标签
|
if (!bottom.open) return // 底行已封:下一行自然消费预置标签
|
||||||
val busy = bottom.sentences.any { !it.finalized }
|
val busy = bottom.sentences.any { !it.finalized }
|
||||||
if (busy) {
|
if (busy) {
|
||||||
|
|
@ -1814,73 +1830,91 @@ class MainActivity : AppCompatActivity() {
|
||||||
Log.i(TAG, "ONSET-CLOSE no-sentence forced endpoint")
|
Log.i(TAG, "ONSET-CLOSE no-sentence forced endpoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 乐观切换后的准确空间善后(2026-09-16 用户拍板:先切后改):切换已
|
/** 切后命名(方案三,2026-09-16 用户拍板):开火后 1.5~3.5s,用切后
|
||||||
* 发出,本函数只做善后——认得出是当前人 → 留痕(切错由判定链"并回"
|
* 纯净窗(补喂保证该段已完全是新说法人的声音)做准确判定——
|
||||||
* 自愈);真新人 → 补写准确空间(防后续重发号);认出别人 → 留痕(判定
|
* 强匹配开火前主 → 误切撤销;强匹配某已知人 → 上其名;确认无人
|
||||||
* 链迁回)。在 speakerCheckHandler 线程执行。 */
|
* (最好分 < 0.42)→ 发新号注册(素材干净,永不产幽灵);灰色地带留灰,
|
||||||
private fun accurateRecheckAfterSwitch(
|
* 由判定链收编。在 speakerCheckHandler 线程执行。 */
|
||||||
samples: FloatArray, label: String, active: String?, tag: String,
|
private fun deferredNaming(serial: Long, samples: FloatArray) {
|
||||||
) {
|
if (serial != switchSerial) return
|
||||||
|
val prevActive = switchPrevActive
|
||||||
val acc = SpeakerRecognition.computeEmbedding(samples, sampleRateInHz)
|
val acc = SpeakerRecognition.computeEmbedding(samples, sampleRateInHz)
|
||||||
?: return
|
?: return
|
||||||
val r = SpeakerRecognition.registry.nearestRank(acc)
|
val r = SpeakerRecognition.registry.nearestRank(acc)
|
||||||
val pre = r.name
|
val name = r.name
|
||||||
val score = r.score
|
val score = r.score
|
||||||
when {
|
when {
|
||||||
pre != null && pre == active && score >= revertCancelMin -> {
|
name != null && name == prevActive && score >= speakerThreshold -> {
|
||||||
Log.i(TAG, tag + "-CANCEL-LATE known=$pre(active)" +
|
Log.i(TAG, "NAMING-UNDO serial=$serial known=$name" +
|
||||||
" score=" + "%.2f".format(score) + " active-heal")
|
" score=" + "%.2f".format(score))
|
||||||
// 主动善后:不再被动等句子结束(长句让错标挂几十秒),立即撤销
|
uiHandler.post { undoSwitch(serial) }
|
||||||
uiHandler.post { revertOnsetSwitch(label, pre) }
|
|
||||||
}
|
}
|
||||||
pre != null && pre == active -> {
|
name != null && score >= speakerThreshold -> {
|
||||||
// 弱匹配(短插嘴混合窗常见 0.5~0.6):不撤销,让切换立住
|
Log.i(TAG, "NAMING-ADOPT serial=$serial label=$name" +
|
||||||
Log.i(TAG, tag + "-WEAK-SAME known=$pre" +
|
" score=" + "%.2f".format(score))
|
||||||
" score=" + "%.2f".format(score) +
|
uiHandler.post { adoptSwitchLabel(serial, name) }
|
||||||
" < $revertCancelMin keep-switch")
|
|
||||||
}
|
}
|
||||||
pre != null ->
|
name == null || score < verdictFallbackMin -> {
|
||||||
Log.i(TAG, tag + "-RECHECK known=$pre" +
|
// 确认无人:发新号 + 双空间注册(纯净素材)
|
||||||
" score=" + "%.2f".format(score) +
|
val fastEmb = SpeakerRecognition.computeEmbeddingFast(
|
||||||
" label=$label (verdict migrates)")
|
samples, sampleRateInHz)
|
||||||
else -> SpeakerRecognition.registry.addAuto(label, acc)
|
val label = SpeakerRecognition.nextAutoLabel().also {
|
||||||
|
SpeakerRecognition.registry.addAuto(it, acc)
|
||||||
|
if (fastEmb != null) {
|
||||||
|
SpeakerRecognition.fastRegistry.addAuto(it, fastEmb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(TAG, "NAMING-NEW serial=$serial label=$label")
|
||||||
|
uiHandler.post { adoptSwitchLabel(serial, label) }
|
||||||
|
}
|
||||||
|
else -> Log.i(TAG, "NAMING-WEAK serial=$serial known=$name" +
|
||||||
|
" score=" + "%.2f".format(score) + " keep-gray")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 乐观切换的主动善后(2026-09-16"善后不理想"整改):准确复核认出同一
|
/** 命名落地(主线程):灰行就地冠名;灰行未建立 → 走预置标签给下一行 */
|
||||||
* 人时立即撤销,不等句子结束——覆盖预置标签、还原钉住的标签、占位行
|
private fun adoptSwitchLabel(serial: Long, label: String) {
|
||||||
* 就地改签并与上方同人并回、清理快镜像占位编号。主线程执行。 */
|
if (serial != switchSerial) return
|
||||||
private fun revertOnsetSwitch(firedLabel: String, prevActive: String?) {
|
switchSerial++ // 关闭本切次命名窗口
|
||||||
if (prevActive == null) return // 无旧主可还原(会话极早期)→ 被动链
|
grayLinePending = false
|
||||||
val bottom = subtitleLines.lastOrNull()
|
val bottom = subtitleLines.lastOrNull()
|
||||||
val pending = pendingOnsetLabel
|
if (bottom != null && bottom.open && bottom.label == null) {
|
||||||
val inFlight = pending == firedLabel ||
|
bottom.label = label
|
||||||
(bottom != null && bottom.label == firedLabel && bottom.open)
|
bottom.colorIdx = colorIdxFor(label)
|
||||||
if (!inFlight) return // 状态已走远(连环切换等)→ 交给被动链
|
notifyLine(bottom)
|
||||||
if (pending == firedLabel && bottom != null &&
|
Log.i(TAG, "SWITCH-LABEL serial=$serial label=$label (line)")
|
||||||
bottom.label != firedLabel
|
} else {
|
||||||
) {
|
pendingOnsetLabel = label
|
||||||
// 占位行尚未建立(补喂机制下多数如此):抹掉切换痕迹——预置标签
|
pendingOnsetLabelAt = SystemClock.elapsedRealtime()
|
||||||
// 作废、重开旧底行,同人续说零断行,观感上从未切过
|
Log.i(TAG, "SWITCH-LABEL serial=$serial label=$label (preset)")
|
||||||
pendingOnsetLabel = null
|
|
||||||
onsetClosePending = false // 防在途定格回来补封行
|
|
||||||
if (!bottom.open) bottom.open = true
|
|
||||||
lastConfirmedLabel = prevActive
|
|
||||||
SpeakerRecognition.fastRegistry.removeAuto(firedLabel)
|
|
||||||
Log.i(TAG, "SWITCH-REVERT(pre-line) $firedLabel -> $prevActive")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// 占位行已建立:就地改签 + 与上方同人并回
|
lastConfirmedLabel = label
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 误切撤销(方案三版,主线程):灰行未建立→重开旧行抹迹;已建立→改签并回 */
|
||||||
|
private fun undoSwitch(serial: Long) {
|
||||||
|
if (serial != switchSerial) return
|
||||||
|
switchSerial++
|
||||||
|
val prevActive = switchPrevActive ?: return
|
||||||
|
grayLinePending = false
|
||||||
pendingOnsetLabel = null
|
pendingOnsetLabel = null
|
||||||
|
onsetClosePending = false
|
||||||
lastConfirmedLabel = prevActive
|
lastConfirmedLabel = prevActive
|
||||||
if (bottom != null && bottom.label == firedLabel) {
|
val bottom = subtitleLines.lastOrNull() ?: return
|
||||||
|
when {
|
||||||
|
bottom.open && bottom.label == null -> {
|
||||||
bottom.label = prevActive
|
bottom.label = prevActive
|
||||||
bottom.colorIdx = colorIdxFor(prevActive)
|
bottom.colorIdx = colorIdxFor(prevActive)
|
||||||
val merged = mergeLineIntoPrevIfSame(bottom, prevActive)
|
val merged = mergeLineIntoPrevIfSame(bottom, prevActive)
|
||||||
if (!merged) notifyLine(bottom)
|
if (!merged) notifyLine(bottom)
|
||||||
|
Log.i(TAG, "SWITCH-UNDO(line) serial=$serial -> $prevActive")
|
||||||
|
}
|
||||||
|
!bottom.open -> {
|
||||||
|
bottom.open = true
|
||||||
|
Log.i(TAG, "SWITCH-UNDO(pre-line) serial=$serial -> $prevActive")
|
||||||
|
}
|
||||||
|
else -> Log.i(TAG, "SWITCH-UNDO skipped serial=$serial (state moved)")
|
||||||
}
|
}
|
||||||
SpeakerRecognition.fastRegistry.removeAuto(firedLabel)
|
|
||||||
Log.i(TAG, "SWITCH-REVERT(line) $firedLabel -> $prevActive (active-heal)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun appendNewLine(label: String?): SubtitleLine {
|
private fun appendNewLine(label: String?): SubtitleLine {
|
||||||
|
|
@ -1905,9 +1939,15 @@ class MainActivity : AppCompatActivity() {
|
||||||
SystemClock.elapsedRealtime() - pendingOnsetLabelAt < 5_000L
|
SystemClock.elapsedRealtime() - pendingOnsetLabelAt < 5_000L
|
||||||
) {
|
) {
|
||||||
pendingOnsetLabel = null
|
pendingOnsetLabel = null
|
||||||
|
grayLinePending = false
|
||||||
lastConfirmedLabel = preset
|
lastConfirmedLabel = preset
|
||||||
eff = preset
|
eff = preset
|
||||||
Log.i(TAG, "ONSET-LINE new line label=$preset")
|
Log.i(TAG, "ONSET-LINE new line label=$preset")
|
||||||
|
} else if (grayLinePending) {
|
||||||
|
// 方案三:切后命名未落定 → 灰行先开(未识别),名字稍后补
|
||||||
|
grayLinePending = false
|
||||||
|
eff = null
|
||||||
|
Log.i(TAG, "ONSET-LINE new line gray (naming pending)")
|
||||||
} else {
|
} else {
|
||||||
if (preset != null) {
|
if (preset != null) {
|
||||||
pendingOnsetLabel = null
|
pendingOnsetLabel = null
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue