Ariver
2026-06-14 d14fa75079de570cb9c4dbf63f6b5bd65f959b09
privatevoice.src/app.go
@@ -63,23 +63,25 @@
   indicator      overlay.Overlay
   // State
   mu                sync.Mutex
   isRecording       bool
   isFreetalking     bool
   hotkeyActive      bool
   hotkeyPressTime   time.Time
   isCombination     bool
   tapStopOnPress    bool
   isRecordingHotkey bool
   hideGen           atomic.Uint64
   lastStopTime      time.Time
   hotkeyPollSeen    bool
   lastHotkeyDown    bool
   lastHotkeyBlocked time.Time
   silenceSince      time.Time // when continuous silence started (free-talk only)
   freeTalkStart     time.Time // when free-talk mode started
   liveCaptionCancel context.CancelFunc
   liveCaptionSeq    uint64
   mu                      sync.Mutex
   isRecording             bool
   isStoppingRecording     bool
   isFreetalking           bool
   hotkeyActive            bool
   hotkeyPressTime         time.Time
   isCombination           bool
   tapStopOnPress          bool
   isRecordingHotkey       bool
   hideGen                 atomic.Uint64
   lastStopTime            time.Time
   hotkeyPollSeen          bool
   lastHotkeyDown          bool
   lastHotkeyBlocked       time.Time
   silenceSince            time.Time // when continuous silence started (free-talk only)
   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 + "说话"