Ariver
2026-06-14 d14fa75079de570cb9c4dbf63f6b5bd65f959b09
privatevoice.src/app.go
@@ -65,6 +65,7 @@
   // State
   mu                sync.Mutex
   isRecording       bool
   isStoppingRecording     bool
   isFreetalking     bool
   hotkeyActive      bool
   hotkeyPressTime   time.Time
@@ -80,6 +81,7 @@
   freeTalkStart     time.Time // when free-talk mode started
   liveCaptionCancel context.CancelFunc
   liveCaptionSeq    uint64
   releaseTailCaptureNanos atomic.Int64
}
func RunApp() error {
@@ -279,10 +281,10 @@
      )
   }
   if a.eng == nil || a.isRecordingHotkey {
   if a.eng == nil || a.isRecordingHotkey || a.isStoppingRecording {
      if isDown && time.Since(a.lastHotkeyBlocked) > time.Second {
         a.lastHotkeyBlocked = time.Now()
         logger.Info("Hotkey ignored: key=%s engineReady=%t recordingHotkey=%t", hotkey.GetKeyName(a.cfg.HotkeyVK), a.eng != nil, a.isRecordingHotkey)
         logger.Info("Hotkey ignored: key=%s engineReady=%t recordingHotkey=%t stoppingRecording=%t", hotkey.GetKeyName(a.cfg.HotkeyVK), a.eng != nil, a.isRecordingHotkey, a.isStoppingRecording)
      }
      return
   }
@@ -407,7 +409,7 @@
}
func (a *App) startRecordingLocked() {
   if a.isRecording {
   if a.isRecording || a.isStoppingRecording {
      return
   }
   a.isRecording = true
@@ -450,16 +452,16 @@
   }
   a.stopLiveCaptionLocked()
   hasVoice := a.recorder.HasVoiceActivity()
   a.isStoppingRecording = true
   a.indicator.SetStatus(overlay.StatusProcessing, "识别中")
   go func() {
      samples := a.recorder.StopAndGetSamples()
      samples, hasVoice := a.stopRecorderAfterReleaseTailCapture()
      a.recognizeAndPaste(hasVoice, samples)
   }()
}
func (a *App) startTapRecordingLocked() {
   if a.isRecording || a.isFreetalking {
   if a.isRecording || a.isStoppingRecording || a.isFreetalking {
      return
   }
   a.isFreetalking = true
@@ -625,11 +627,11 @@
   a.lastStopTime = time.Now()
   a.stopLiveCaptionLocked()
   hasVoice := a.recorder.HasVoiceActivity()
   a.isStoppingRecording = true
   logger.Info("Tap recording stopped")
   a.indicator.SetStatus(overlay.StatusProcessing, "识别中")
   go func() {
      samples := a.recorder.StopAndGetSamples()
      samples, hasVoice := a.stopRecorderAfterReleaseTailCapture()
      a.recognizeAndPaste(hasVoice, samples)
   }()
}
@@ -688,6 +690,35 @@
   logger.Info("Free talk stopped (silence auto-stop)")
   a.indicator.SetStatus(overlay.StatusProcessing, "识别中")
   a.recognizeAndPaste(hasVoice, samples)
}
func (a *App) waitForReleaseTailCapture() {
   delay := a.releaseTailCaptureDelay()
   if delay <= 0 {
      return
   }
   logger.Info("Release tail capture: waiting %dms before stopping audio", delay.Milliseconds())
   time.Sleep(delay)
}
func (a *App) releaseTailCaptureDelay() time.Duration {
   nanos := a.releaseTailCaptureNanos.Load()
   if nanos <= 0 {
      return 0
   }
   return time.Duration(nanos)
}
func (a *App) stopRecorderAfterReleaseTailCapture() ([]float32, bool) {
   a.waitForReleaseTailCapture()
   samples := a.recorder.StopAndGetSamples()
   hasVoice := a.recorder.HasVoiceActivity()
   a.mu.Lock()
   a.isStoppingRecording = false
   a.mu.Unlock()
   return samples, hasVoice
}
// recognizeAndPaste runs ASR on the recorded samples and pastes the result.
@@ -857,6 +888,8 @@
}
func (a *App) replaceEngine(eng engine.Engine) {
   a.releaseTailCaptureNanos.Store(int64(releaseTailCaptureDelayForEngine(eng)))
   a.engineMu.Lock()
   old := a.eng
   a.eng = eng
@@ -867,6 +900,18 @@
   }
}
func releaseTailCaptureDelayForEngine(eng engine.Engine) time.Duration {
   tailCaptureEng, ok := eng.(engine.ReleaseTailCaptureEngine)
   if !ok {
      return 0
   }
   delay := tailCaptureEng.ReleaseTailCaptureDelay()
   if delay < 0 {
      return 0
   }
   return delay
}
func hotkeyReadyText(keyName, mode string) string {
   if mode == config.HotkeyModeTap {
      return "点按" + keyName + "说话"