|
|
|
@ -549,7 +549,7 @@ public class AzureTtsHelper: NSObject, ITtsService, AVAudioPlayerDelegate { |
|
|
|
} |
|
|
|
speaking = true |
|
|
|
// lastSpokenText = text |
|
|
|
// os_log("调用链: performSynthesis 准备合成 session=%{public}@ pushStream=%{public}@ suppress=%{public}@ textLen=%{public}d", log: log, type: .info, sessionid, isUsingPushStreamCapture.description, suppressStreamPlayback.description, text.count) |
|
|
|
os_log("liwei----------- 调用链: performSynthesis 准备合成 session=%{public}@ pushStream=%{public}@ suppress=%{public}@ textLen=%{public}d", log: log, type: .info, sessionid, isUsingPushStreamCapture.description, suppressStreamPlayback.description, text.count) |
|
|
|
if !applyAudioSessionForTTS() { |
|
|
|
speaking = false |
|
|
|
notifyEvent(eventType: .error, params: [ |
|
|
|
@ -1088,6 +1088,10 @@ private func checkStreamPlaybackCompletedIfNeeded() { |
|
|
|
* - Throws: 无 |
|
|
|
*/ |
|
|
|
private func startPlaybackIfNeeded() { |
|
|
|
if isPlaying, audioPlayer?.isPlaying != true { |
|
|
|
isPlaying = false |
|
|
|
audioPlayer = nil |
|
|
|
} |
|
|
|
guard !isPlaying, !playbackQueue.isEmpty else { return } |
|
|
|
let next = playbackQueue.first! |
|
|
|
let playableData = ensureWavDataForPlayback(next) |
|
|
|
@ -1106,15 +1110,20 @@ private func checkStreamPlaybackCompletedIfNeeded() { |
|
|
|
} |
|
|
|
audioPlayer?.delegate = self |
|
|
|
audioPlayer?.prepareToPlay() |
|
|
|
isPlaying = true |
|
|
|
// os_log("调用链: 开始播放 队首长度=%{public}dB session=%{public}@", log: log, type: .info, next.count, sessionid) |
|
|
|
notifyEvent(eventType: .playbackStarted) |
|
|
|
audioPlayer?.play() |
|
|
|
let started = audioPlayer?.play() == true |
|
|
|
if started { |
|
|
|
isPlaying = true |
|
|
|
// os_log("调用链: 开始播放 队首长度=%{public}dB session=%{public}@", log: log, type: .info, next.count, sessionid) |
|
|
|
notifyEvent(eventType: .playbackStarted) |
|
|
|
} else { |
|
|
|
throw NSError(domain: "AzureTtsHelper", code: -1, userInfo: [NSLocalizedDescriptionKey: "AVAudioPlayer.play() 返回 false"]) |
|
|
|
} |
|
|
|
} catch { |
|
|
|
os_log("播放初始化失败: %{public}@", log: log, type: .error, error.localizedDescription) |
|
|
|
// 异常情况下移除该条并尝试播放下一条 |
|
|
|
playbackQueue.removeFirst() |
|
|
|
isPlaying = false |
|
|
|
audioPlayer = nil |
|
|
|
startPlaybackIfNeeded() |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1415,6 +1424,7 @@ private func ensureAudioSessionQueueSpecificKeySet() { |
|
|
|
*/ |
|
|
|
private func applyAudioSessionForTTS() -> Bool { |
|
|
|
var success = true |
|
|
|
os_log("liwei-------------TTS applyAudioSessionForTTS currentRecognitionMode:%{public}@", log: self.log, type: .info, currentRecognitionMode) |
|
|
|
ensureAudioSessionQueueSpecificKeySet() |
|
|
|
let applyBody = { |
|
|
|
if self.isApplyingAudioSessionConfig { return } |
|
|
|
@ -1648,55 +1658,24 @@ private func deactivateAudioSessionAfterTTSIfIdle() { |
|
|
|
* - 配置过程中(isApplyingAudioSessionConfig = true)直接忽略 |
|
|
|
*/ |
|
|
|
@objc private func handleRouteChange(_ notification: Notification) { |
|
|
|
// guard let info = notification.userInfo, |
|
|
|
// let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt, |
|
|
|
// let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } |
|
|
|
|
|
|
|
// if !isTtsBusy() { return } |
|
|
|
// os_log("音频路由变化: %{public}@", log: log, type: .info, String(describing: reason)) |
|
|
|
|
|
|
|
// // 配置中触发的路由变化,直接忽略,避免重入导致死循环 |
|
|
|
// if isApplyingAudioSessionConfig { |
|
|
|
// os_log("配置进行中,忽略本次路由变化", log: log, type: .debug) |
|
|
|
// return |
|
|
|
// } |
|
|
|
|
|
|
|
// // 忽略会造成循环的原因:categoryChange(你的日志 reason=3),以及 routeConfigurationChange 等 |
|
|
|
// switch reason { |
|
|
|
// case .categoryChange, .routeConfigurationChange, .wakeFromSleep, .noSuitableRouteForCategory: |
|
|
|
// os_log("忽略路由变化原因: %{public}@", log: log, type: .debug, String(describing: reason)) |
|
|
|
// return |
|
|
|
// default: |
|
|
|
// break |
|
|
|
// } |
|
|
|
|
|
|
|
// // 通话期间避免重配,避免与电话路由冲突 |
|
|
|
// if isTelephonyActive() { |
|
|
|
// os_log("通话期间检测到路由变化,跳过重配", log: log, type: .info) |
|
|
|
// return |
|
|
|
// } |
|
|
|
|
|
|
|
// let isActivelyPlayingNow = |
|
|
|
// speaking || |
|
|
|
// isPlaying || |
|
|
|
// (audioPlayer?.isPlaying == true) || |
|
|
|
// (playerNode?.isPlaying == true) |
|
|
|
// if isActivelyPlayingNow { |
|
|
|
// routeChangeDebounceWorkItem?.cancel() |
|
|
|
// _ = stop() |
|
|
|
// os_log("路由变化时正在播报,已停止以避免音源冲突", log: log, type: .info) |
|
|
|
// return |
|
|
|
// } |
|
|
|
|
|
|
|
// 去抖处理:300ms 内只执行最后一次重配 |
|
|
|
// 忽略 categoryChange 等 |
|
|
|
// 去抖:在 audioSessionQueue 上延迟调用 |
|
|
|
// routeChangeDebounceWorkItem?.cancel() |
|
|
|
// let work = DispatchWorkItem { [weak self] in |
|
|
|
// self?.safeConfigureAudioSessionForTTS() |
|
|
|
// } |
|
|
|
// routeChangeDebounceWorkItem = work |
|
|
|
// audioSessionQueue.asyncAfter(deadline: .now() + 0.3, execute: work) |
|
|
|
guard let info = notification.userInfo, |
|
|
|
let reasonValue = info[AVAudioSessionRouteChangeReasonKey] as? UInt, |
|
|
|
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } |
|
|
|
|
|
|
|
if reason != .categoryChange { return } |
|
|
|
|
|
|
|
let session = AVAudioSession.sharedInstance() |
|
|
|
if session.category != .record { return } |
|
|
|
|
|
|
|
let isActivelyPlayingNow = |
|
|
|
speaking || |
|
|
|
isPlaying || |
|
|
|
(audioPlayer?.isPlaying == true) || |
|
|
|
(playerNode?.isPlaying == true) |
|
|
|
if !isActivelyPlayingNow { return } |
|
|
|
|
|
|
|
_ = stop() |
|
|
|
os_log("检测到音频会话切到录音,停止TTS以避免冲突", log: log, type: .info) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
|