Browse Source

修复停止播报导致音乐暂停。修复面对面按住防抖,加入按住说话停止音乐

weicu
fdp 1 year ago
parent
commit
87ed5d7a6a
  1. 36
      lib/data/services/speech_impl/azure_asr_service.dart
  2. 29
      lib/modules/agent/controllers/agent_controller.dart
  3. 25
      lib/modules/translation/controllers/translation_controller.dart
  4. 16
      lib/modules/translation/views/translation_view.dart
  5. 10
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  6. 5
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift
  7. 12
      local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift
  8. 4
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift

36
lib/data/services/speech_impl/azure_asr_service.dart

@ -55,6 +55,11 @@ class AzureAsrService extends GetxService implements AsrService {
// 当前音频源类型
AudioSourceType _audioSourceType = AudioSourceType.microphone;
// 防抖时间戳与间隔(实例级)
DateTime? _lastStartAt;
DateTime? _lastStopAt;
static const Duration _debounce = Duration(milliseconds: 600);
AzureAsrService() {
_loadConfig();
}
@ -172,7 +177,21 @@ class AzureAsrService extends GetxService implements AsrService {
}
@override
/// 开始连续语音识别(带防抖)
///
/// 行为说明:
/// - 在 600ms 的冷却时间内再次调用会被防抖拦截并返回 false;
/// - 首次或冷却期外调用会继续启动连续识别;
/// - 若当前已处于连续识别中,会先调用停止后再启动。
Future<bool> startContinuousRecognition(bool audioSourceType) async {
// 防抖判断:短时间内重复调用直接拦截
final now = DateTime.now();
if (_lastStartAt != null && now.difference(_lastStartAt!) < _debounce) {
return false;
}
_lastStartAt = now;
if (!_isInitialized) {
await initialize();
}
@ -201,7 +220,21 @@ class AzureAsrService extends GetxService implements AsrService {
}
@override
/// 停止连续语音识别(带防抖)
///
/// 行为说明:
/// - 在 600ms 的冷却时间内再次调用会被防抖拦截并返回 false;
/// - 若当前不在识别中,直接返回 true;
/// - 调用成功后立即将内部状态置为 false(同时事件通道回调仍会再次校正)。
Future<bool> stopContinuousRecognition() async {
// 防抖判断:短时间内重复调用直接拦截
final now = DateTime.now();
if (_lastStopAt != null && now.difference(_lastStopAt!) < _debounce) {
return false;
}
_lastStopAt = now;
if (!_isInitialized || !_isContinuousRecognitionActive) {
return true;
}
@ -209,6 +242,9 @@ class AzureAsrService extends GetxService implements AsrService {
try {
final bool result =
await _channel.invokeMethod('stopContinuousRecognition');
if (result) {
_isContinuousRecognitionActive = false;
}
return result;
} catch (e) {
Logger.error('停止连续语音识别失败: ${e.toString()}');

29
lib/modules/agent/controllers/agent_controller.dart

@ -37,7 +37,7 @@ enum VoiceInputMode {
phoneCall, // 打电话模式
}
class AgentController extends GetxController {
class AgentController extends GetxController with WidgetsBindingObserver {
static AgentController get to => Get.find(); //单例访问模式
// 聊天消息列表
@ -171,6 +171,9 @@ class AgentController extends GetxController {
startVoiceInput(); //开启语音输入
// 监听应用生命周期:当应用切到后台/失焦时兜底结束按住说话,防止 onPointerUp 未触发
WidgetsBinding.instance.addObserver(this);
// _qqmusicManager.syncCurrentPlayInfo();
}
@ -217,6 +220,10 @@ class AgentController extends GetxController {
_amapeventSubscription?.cancel();
_bleEventSubscription?.cancel();
Intro.of(Get.context!).dispose();
// 取消应用生命周期监听
WidgetsBinding.instance.removeObserver(this);
super.onClose();
}
@ -2042,6 +2049,26 @@ class AgentController extends GetxController {
}
}
/// 应用生命周期变化回调
/// 目的:
/// - 当应用进入后台、失去焦点或被系统分离(inactive/paused/detached)时,
/// 兜底结束按住说话(调用 endPushToTalk),避免因 onPointerUp 未触发而卡在“按下”状态。
/// 参数:
/// - state: 当前应用生命周期状态
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) {
if (isPushToTalkActive.value) {
// 使用 microtask 避免阻塞当前生命周期回调
Future.microtask(() {
endPushToTalk();
});
}
}
}
// ========== 媒体会话回调处理方法 ==========
/// 处理来自系统通知栏的播放操作

25
lib/modules/translation/controllers/translation_controller.dart

@ -31,7 +31,7 @@ enum AudioOutputDevice {
speaker, // 强制扬声器
}
class TranslationController extends GetxController {
class TranslationController extends GetxController with WidgetsBindingObserver {
static const String TAG = 'TranslationController';
// 服务
@ -268,6 +268,9 @@ class TranslationController extends GetxController {
// 初始化历史管理器
_historyManager.initialize();
// 监听应用生命周期:当应用切到后台/失焦时兜底重置 activeSpeaker
WidgetsBinding.instance.addObserver(this);
}
/// 预先检查权限
@ -460,9 +463,29 @@ class TranslationController extends GetxController {
_timerManager.dispose();
_historyManager.dispose();
// 取消应用生命周期监听
WidgetsBinding.instance.removeObserver(this);
super.onClose();
}
/// 应用生命周期变化回调
/// 当应用进入后台、失去焦点或分离(inactive/paused/detached)时,
/// 兜底将 activeSpeaker 重置为 0,等价于“松手”,避免 onPointerUp 未触发导致的卡住问题。
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) {
if (activeSpeaker.value != 0) {
// 使用 microtask 避免阻塞当前生命周期回调
Future.microtask(() {
setActiveSpeaker(0);
});
}
}
}
// 切换TTS功能
void toggleTts() => isTtsEnabled.toggle();

16
lib/modules/translation/views/translation_view.dart

@ -608,23 +608,13 @@ class TranslationView extends GetView<TranslationController> {
return;
}
_holdTimer = Timer(const Duration(milliseconds: 200), () {
if (activeSpeaker == 0) {
controller.setActiveSpeaker(speakerId);
}
});
controller.setActiveSpeaker(speakerId);
},
onPointerUp: (_) {
_holdTimer?.cancel();
if (isActive) {
controller.setActiveSpeaker(0);
}
controller.setActiveSpeaker(0);
},
onPointerCancel: (_) {
_holdTimer?.cancel();
if (isActive) {
controller.setActiveSpeaker(0);
}
controller.setActiveSpeaker(0);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),

10
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -393,13 +393,12 @@ class AgentServiceImpl: NSObject {
stopRecognition()
}
// 检查当前音乐播放状态并记录
let currentPlayState = QPlayAutoManager.shared.currentPlayState
wasMusicPlayingBeforeRecognition = (currentPlayState == .playing)
wasMusicPlayingBeforeRecognition = MusicService.shared.isPlaying
// 如果音乐正在播放,则暂停
if wasMusicPlayingBeforeRecognition {
// QPlayAutoManager.shared.pause()
if MusicService.shared.isPlaying {
MusicService.shared.pause()
}
// 设置当前识别模式
@ -469,6 +468,7 @@ class AgentServiceImpl: NSObject {
if wasMusicPlayingBeforeRecognition {
// QPlayAutoManager.shared.play()
wasMusicPlayingBeforeRecognition = false
MusicService.shared.resume()
}
BleService.shared.closeCodec()

5
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift

@ -271,7 +271,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
if !micCapture.isCapturing{
// 停止时设置音频会话为非活跃状态
try AVAudioSession.sharedInstance().setActive(false)
// try AVAudioSession.sharedInstance().setActive(false)
}
print("音频会话已设置为非活跃状态")
@ -526,7 +526,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
// 检查是否为最后一个文本播放完毕
if self.pendingTextCount == 0 && self.pendingTasks.isEmpty && !micCapture.isCapturing{
do {
try AVAudioSession.sharedInstance().setActive(false)
// try AVAudioSession.sharedInstance().setActive(false)
print("最后一个文本播放完毕,音频会话已设置为非活跃状态")
} catch {
print("设置音频会话为非活跃状态失败: \(error.localizedDescription)")
@ -683,4 +683,3 @@ public func resetTextCount() {
}

12
local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift

@ -256,9 +256,15 @@ public class MicrophoneCapture: NSObject {
// 获取实际硬件采样率
sampleRate = audioSession.sampleRate
// 安全地停止音频引擎
if let engine = audioEngine {
engine.stop()
}
// 移除之前的音频处理块,避免重复添加
audioInputNode.removeTap(onBus: 0)
// 安全地移除音频处理块
if let inputNode = audioInputNode {
inputNode.removeTap(onBus: 0)
}
// 设置音频格式
let inputFormat = audioInputNode.inputFormat(forBus: 0)
@ -447,7 +453,7 @@ public class MicrophoneCapture: NSObject {
}
try? audioSession.overrideOutputAudioPort(.none)
try? audioSession.setActive(true)
}
}
/**
* 重置音频捕获状态

4
local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift

@ -206,7 +206,7 @@ public class MusicService: NSObject {
}
}
func pause() {
public func pause() {
player?.pause()
isPlaying = false
updatePlaybackState()
@ -215,7 +215,7 @@ public class MusicService: NSObject {
print("已暂停")
}
func resume() {
public func resume() {
player?.play()
isPlaying = true
updatePlaybackState()

Loading…
Cancel
Save