You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
67 lines
3.4 KiB
67 lines
3.4 KiB
import 'dart:io' show Platform;
|
|
|
|
import 'package:audio_session/audio_session.dart';
|
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
|
|
import '../../core/utils/logger.dart';
|
|
|
|
/// EMAI 语音链路(麦克风上行 + TTS 下行)共用的一份 AVAudioSession 配置。
|
|
///
|
|
/// 为什么要抽出来:麦克风和播放器是两个模块,各自配一次会话就会互相覆盖。
|
|
/// 连续对话下两者同时在跑,谁最后写谁生效,表现是「录着录着没声了」或
|
|
/// 「播着播着麦死了」。统一到这里,两边写的是同一份配置,重复调用无害。
|
|
///
|
|
/// ⚠️ **开麦之前必须先调一次**。真机实测:会话停在 `soloAmbient` 且未激活时,
|
|
/// `record` 的 `startStream` 会**成功返回**、AVAudioEngine 也起得来,但输入
|
|
/// tap 一个 buffer 都不回调 —— 上行一个字节都发不出去,服务端 30 秒后回
|
|
/// `ClientAudioTimeout`,而客户端这边没有任何报错,只看到「麦克风采集启动」
|
|
/// 那行日志,非常难查。
|
|
class EmaiAudioSession {
|
|
const EmaiAudioSession._();
|
|
|
|
static const String _tag = 'EmaiAudioSession';
|
|
|
|
static bool _configured = false;
|
|
|
|
static bool get _apply => !kIsWeb && (Platform.isIOS || Platform.isAndroid);
|
|
|
|
/// 配置(只做一次)并激活会话。失败只记日志,不抛——
|
|
/// 会话配不上时让调用方继续尝试,总比整条链路直接不启动好。
|
|
static Future<void> ensure() async {
|
|
if (!_apply) return;
|
|
try {
|
|
final session = await AudioSession.instance;
|
|
if (!_configured) {
|
|
_configured = true;
|
|
await session.configure(AudioSessionConfiguration(
|
|
// iOS 必须 playAndRecord:上行要录音、下行要外放,缺一不可。
|
|
avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
|
|
avAudioSessionCategoryOptions:
|
|
AVAudioSessionCategoryOptions.defaultToSpeaker |
|
|
AVAudioSessionCategoryOptions.allowBluetooth,
|
|
// ⚠️ iOS 固定 voiceChat:record 开了 echoCancel,它会打开
|
|
// voice-processing,系统随之把 mode 改成 voiceChat 并一直持有
|
|
// (真机 dump 确认)。播放这边如果写回 default,就是两边互相改会话。
|
|
// defaultToSpeaker 在 voiceChat 下仍然走扬声器,实测不会被拽到听筒。
|
|
avAudioSessionMode: Platform.isIOS
|
|
? AVAudioSessionMode.voiceChat
|
|
: AVAudioSessionMode.defaultMode,
|
|
// ⚠️ Android 的 usage 千万别用 voiceCommunication:华为
|
|
// (EMUI/HarmonyOS) 对通话流卡得很严,不在 MODE_IN_COMMUNICATION 下建的
|
|
// VOICE_COMMUNICATION 音轨会被直接静音,logcat 只留一行
|
|
// `AudioTrack: set playback slient`,音轨建得出来、数据也在写,就是没声音。
|
|
// ALN-AL80 上实测复现过,用 media 就没这问题。
|
|
androidAudioAttributes: const AndroidAudioAttributes(
|
|
contentType: AndroidAudioContentType.speech,
|
|
usage: AndroidAudioUsage.media,
|
|
),
|
|
// 助手说话时把背景音乐暂停,而不是压低——压低了在嘈杂环境还是听不清
|
|
androidAudioFocusGainType: AndroidAudioFocusGainType.gainTransient,
|
|
));
|
|
}
|
|
await session.setActive(true);
|
|
} catch (e) {
|
|
Logger.w(_tag, '音频会话配置失败(继续尝试): $e');
|
|
}
|
|
}
|
|
}
|
|
|