|
|
@ -84,6 +84,7 @@ class AgentServiceImpl: NSObject { |
|
|
private var serverurl = "" |
|
|
private var serverurl = "" |
|
|
private var usertoken = "" |
|
|
private var usertoken = "" |
|
|
|
|
|
|
|
|
|
|
|
private let timerQueue = DispatchQueue(label: "com.example.idleTimer.queue") |
|
|
private var idleTimer: DispatchSourceTimer? |
|
|
private var idleTimer: DispatchSourceTimer? |
|
|
private let maxIdleSeconds: TimeInterval = 10 |
|
|
private let maxIdleSeconds: TimeInterval = 10 |
|
|
var micCapture: MicrophoneCapture! |
|
|
var micCapture: MicrophoneCapture! |
|
|
@ -360,41 +361,62 @@ class AgentServiceImpl: NSObject { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private func startIdleCheck(idleSeconds: TimeInterval = 10) { |
|
|
private func startIdleCheck(idleSeconds: TimeInterval = 10) { |
|
|
stopIdleCheck() |
|
|
// 2. 所有对idleTimer的操作都通过串行队列执行,避免竞态条件 |
|
|
|
|
|
timerQueue.async { |
|
|
guard isRecognizing else { return } |
|
|
// 先停止旧的定时器(在串行队列中执行,确保线程安全) |
|
|
|
|
|
self.stopIdleCheck() |
|
|
os_log("启动空闲检测,超时时间: %.0f秒,模式: %{public}@", log: logger, type: .info, idleSeconds, currentRecognitionMode) |
|
|
|
|
|
|
|
|
guard self.isRecognizing else { return } |
|
|
// 使用全局队列而不是主队列,避免后台挂起问题 |
|
|
|
|
|
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .utility)) |
|
|
os_log("启动空闲检测,超时时间: %.0f秒,模式: %{public}@", |
|
|
idleTimer = timer |
|
|
log: self.logger, |
|
|
|
|
|
type: .info, |
|
|
timer.schedule(deadline: .now() + idleSeconds) |
|
|
idleSeconds, |
|
|
timer.setEventHandler { [weak self] in |
|
|
self.currentRecognitionMode) |
|
|
// 使用更安全的方式检查self是否存在 |
|
|
|
|
|
DispatchQueue.main.async { [weak self] in |
|
|
// 使用全局队列作为定时器回调队列(避免后台挂起) |
|
|
|
|
|
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .utility)) |
|
|
|
|
|
|
|
|
|
|
|
timer.schedule(deadline: .now() + idleSeconds) |
|
|
|
|
|
timer.setEventHandler { [weak self] in |
|
|
guard let self = self else { return } |
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
|
if self.isRecognizing && !self.isSpeaking && !self.isAiStreaming { |
|
|
// 回调在全局队列,涉及UI或实例变量操作时切换到主线程 |
|
|
os_log("空闲检测触发自动停止,模式: %{public}@", log: self.logger, type: .info, self.currentRecognitionMode) |
|
|
DispatchQueue.main.async { [weak self] in |
|
|
// 关闭编码器 (设备侧) |
|
|
guard let self = self else { return } |
|
|
BleService.shared.closeCodec() |
|
|
|
|
|
self.stopRecognition() |
|
|
// 检查状态(确保在主线程访问这些属性,避免UI相关竞态) |
|
|
self.sendEvent(name: "auto_stop", data: [ |
|
|
if self.isRecognizing && !self.isSpeaking && !self.isAiStreaming { |
|
|
"reason": "idle_timeout", |
|
|
os_log("空闲检测触发自动停止,模式: %{public}@", |
|
|
"seconds": idleSeconds, |
|
|
log: self.logger, |
|
|
"mode": self.currentRecognitionMode |
|
|
type: .info, |
|
|
]) |
|
|
self.currentRecognitionMode) |
|
|
|
|
|
|
|
|
|
|
|
// 关闭编码器(设备侧操作,若BleService线程安全可直接调用,否则需加锁) |
|
|
|
|
|
BleService.shared.closeCodec() |
|
|
|
|
|
|
|
|
|
|
|
self.stopRecognition() |
|
|
|
|
|
self.sendEvent(name: "auto_stop", data: [ |
|
|
|
|
|
"reason": "idle_timeout", |
|
|
|
|
|
"seconds": idleSeconds, |
|
|
|
|
|
"mode": self.currentRecognitionMode |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
timer.resume() |
|
|
|
|
|
// 3. 在串行队列中安全赋值 |
|
|
|
|
|
self.idleTimer = timer |
|
|
} |
|
|
} |
|
|
timer.resume() |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private func stopIdleCheck() { |
|
|
private func stopIdleCheck() { |
|
|
idleTimer?.cancel() |
|
|
timerQueue.async { |
|
|
idleTimer = nil |
|
|
self.idleTimer?.cancel() |
|
|
|
|
|
self.idleTimer = nil // 清空引用 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private func restartIdleCheck() { |
|
|
private func restartIdleCheck() { |
|
|
@ -523,11 +545,11 @@ class AgentServiceImpl: NSObject { |
|
|
if !isRecognizing || !isInitialized { |
|
|
if !isRecognizing || !isInitialized { |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
guard let audioStream = azureAsrHelper?.audioStream else { |
|
|
guard let audioStream = azureAsrHelper?.audioStream else { |
|
|
os_log("AI音频流未初始化", log: logger,type: .error) |
|
|
os_log("AI音频流未初始化", log: logger,type: .error) |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
audioStream.saveAudioDataTo(data: audioData) |
|
|
audioStream.saveAudioDataTo(data: audioData) |
|
|
|
|
|
|
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
|