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.

409 lines
10 KiB

import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:realtime/realtime.dart';
import '../../../core/utils/logger.dart';
class RealtimeController extends GetxController with GetTickerProviderStateMixin {
// Realtime服务实例
final RealtimeService _realtimeService = RealtimeService();
// 连接状态
final RxBool isConnected = false.obs;
final RxBool isConnecting = false.obs;
// 语音状态
final RxBool isListening = false.obs;
final RxBool isSpeaking = false.obs;
// 状态和字幕显示
final RxString currentStatus = '正在初始化...'.obs;
final RxString userSpeechText = ''.obs;
final RxString aiResponseText = ''.obs;
// 音频可视化数据
final RxList<double> audioLevels = <double>[].obs;
final RxDouble userVoiceLevel = 0.0.obs;
final RxDouble aiVoiceLevel = 0.0.obs;
// 动画控制器
late AnimationController pulseAnimationController;
late Animation<double> pulseAnimation;
// 状态监听
StreamSubscription<RealtimeEvent>? _eventSubscription;
Timer? _audioUpdateTimer;
@override
void onInit() {
super.onInit();
_initializeAnimations();
_initializeAudioLevels();
_setupEventListener();
Logger.info('Realtime模块初始化完成');
// 自动开始连接
WidgetsBinding.instance.addPostFrameCallback((_) {
_autoConnect();
});
}
@override
void onClose() {
_cleanup();
super.onClose();
}
/// 初始化动画
void _initializeAnimations() {
pulseAnimationController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
pulseAnimation = Tween<double>(
begin: 0.8,
end: 1.2,
).animate(CurvedAnimation(
parent: pulseAnimationController,
curve: Curves.easeInOut,
));
}
/// 初始化音频级别数据
void _initializeAudioLevels() {
audioLevels.value = List.generate(20, (index) => 0.0);
}
/// 设置事件监听器
void _setupEventListener() {
_eventSubscription = _realtimeService.eventStream.listen(
(event) {
_handleRealtimeEvent(event);
},
onError: (error) {
Logger.error('Realtime事件流错误: $error');
},
);
}
/// 处理Realtime事件
void _handleRealtimeEvent(RealtimeEvent event) {
switch (event.type) {
case 'connectionStatusChanged':
_handleConnectionStatusChanged(event.data);
break;
case 'voiceStatusChanged':
_handleVoiceStatusChanged(event.data);
break;
case 'textReceived':
_handleTextReceived(event.data);
break;
case 'error':
_handleError(event.data);
break;
}
}
/// 自动连接
Future<void> _autoConnect() async {
currentStatus.value = '正在连接AI服务器...';
await connect();
}
/// 处理连接状态变化
void _handleConnectionStatusChanged(String status) {
switch (status) {
case 'connected':
isConnected.value = true;
isConnecting.value = false;
currentStatus.value = '已连接,正在开始录音...';
_startAudioUpdates();
Logger.info('已连接到Vocode服务器');
// 连接成功后自动开始录音
Timer(const Duration(milliseconds: 500), () {
startListening();
});
break;
case 'connecting':
isConnecting.value = true;
currentStatus.value = '正在连接AI服务器...';
Logger.info('正在连接Vocode服务器...');
break;
case 'disconnected':
isConnected.value = false;
isConnecting.value = false;
currentStatus.value = '已断开连接';
_resetState();
Logger.info('已断开Vocode服务器连接');
break;
case 'error':
isConnected.value = false;
isConnecting.value = false;
currentStatus.value = '连接失败,请检查网络';
_resetState();
Logger.error('Vocode服务器连接错误');
break;
}
}
/// 处理语音状态变化
void _handleVoiceStatusChanged(String status) {
switch (status) {
case 'recording':
isListening.value = true;
isSpeaking.value = false;
currentStatus.value = '正在聆听,请说话...';
pulseAnimationController.repeat(reverse: true);
Logger.info('开始录音');
break;
case 'processing':
// 用户说话结束,等待AI回应
isListening.value = false;
currentStatus.value = 'AI正在思考中...';
break;
case 'playing':
isListening.value = false;
isSpeaking.value = true;
currentStatus.value = 'AI正在回复...';
pulseAnimationController.repeat(reverse: true);
Logger.info('AI开始说话');
break;
case 'idle':
isListening.value = false;
isSpeaking.value = false;
currentStatus.value = isConnected.value ? '等待用户说话...' : '已断开连接';
pulseAnimationController.stop();
Logger.info('语音状态空闲');
break;
}
}
/// 处理收到的文本消息
void _handleTextReceived(String text) {
Logger.info('收到文本消息: $text');
// 根据消息来源更新相应的文本
if (text.startsWith('[USER]')) {
userSpeechText.value = text.replaceFirst('[USER]', '').trim();
} else if (text.startsWith('[AI]')) {
aiResponseText.value = text.replaceFirst('[AI]', '').trim();
} else {
// 默认认为是AI回复
aiResponseText.value = text;
}
}
/// 处理错误
void _handleError(String error) {
Logger.error('Realtime错误: $error');
// 可以显示错误提示
}
/// 切换连接状态
Future<void> toggleConnection() async {
if (isConnected.value) {
await disconnect();
} else {
await connect();
}
}
/// 连接到AI服务
Future<void> connect() async {
if (isConnecting.value || isConnected.value) return;
try {
HapticFeedback.lightImpact();
// 初始化Realtime服务
final initialized = await _realtimeService.initialize(
serverUrl: 'ws://192.168.1.11:8000/ws', // 本地开发服务器使用ws协议
sampleRate: 16000,
channels: 1,
bitsPerSample: 16,
);
if (!initialized) {
throw Exception('初始化Realtime服务失败');
}
// 连接服务器
final connected = await _realtimeService.connect();
if (!connected) {
throw Exception('连接Vocode服务器失败');
}
} catch (e) {
Logger.error('连接失败: $e');
isConnecting.value = false;
_resetState();
}
}
/// 断开连接
Future<void> disconnect() async {
try {
HapticFeedback.mediumImpact();
// 停止录音和播放
if (isListening.value) {
await _realtimeService.stopRecording();
}
await _realtimeService.stopPlaying();
// 断开连接
await _realtimeService.disconnect();
// 停止本地更新
_stopAudioUpdates();
pulseAnimationController.stop();
} catch (e) {
Logger.error('断开连接失败: $e');
}
}
/// 开始/停止聆听
void toggleListening() {
if (!isConnected.value) return;
if (isListening.value) {
stopListening();
} else {
startListening();
}
}
/// 开始聆听
void startListening() {
if (!isConnected.value || isListening.value) return;
HapticFeedback.lightImpact();
_realtimeService.startRecording().then((success) {
if (!success) {
Logger.error('开始录音失败');
}
});
}
/// 停止聆听
void stopListening() {
if (!isListening.value) return;
_realtimeService.stopRecording().then((success) {
if (!success) {
Logger.error('停止录音失败');
}
});
}
/// 开始音频数据更新
void _startAudioUpdates() {
_audioUpdateTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (!isConnected.value) {
timer.cancel();
return;
}
_updateAudioLevels();
});
}
/// 停止音频数据更新
void _stopAudioUpdates() {
_audioUpdateTimer?.cancel();
_audioUpdateTimer = null;
}
/// 更新音频级别
void _updateAudioLevels() {
final random = Random();
// 更新用户语音级别
if (isListening.value) {
userVoiceLevel.value = 0.3 + random.nextDouble() * 0.7;
} else {
userVoiceLevel.value = userVoiceLevel.value * 0.8; // 衰减
}
// 更新AI语音级别
if (isSpeaking.value) {
aiVoiceLevel.value = 0.2 + random.nextDouble() * 0.8;
} else {
aiVoiceLevel.value = aiVoiceLevel.value * 0.9; // 衰减
}
// 更新音频级别数组
final newLevels = List<double>.generate(20, (index) {
if (isListening.value || isSpeaking.value) {
return random.nextDouble() * (isListening.value ? userVoiceLevel.value : aiVoiceLevel.value);
}
return audioLevels[index] * 0.95; // 衰减
});
audioLevels.value = newLevels;
}
/// 重置音频级别
void _resetAudioLevels() {
userVoiceLevel.value = 0.0;
aiVoiceLevel.value = 0.0;
audioLevels.value = List.generate(20, (index) => 0.0);
}
/// 关闭并返回上一页
Future<void> closeAndGoBack() async {
// 立即返回上一页,避免阻塞UI
Get.back();
// 之后异步进行清理,不阻塞页面切换
unawaited(_performShutdown());
}
/// 实际执行关闭逻辑(后台)
Future<void> _performShutdown() async {
try {
currentStatus.value = '正在关闭连接...';
if (isListening.value) {
await _realtimeService.stopRecording();
}
await _realtimeService.stopPlaying();
await _realtimeService.disconnect();
_resetState();
Logger.info('实时语音连接已关闭');
} catch (e) {
Logger.error('关闭连接失败: $e');
}
}
/// 重置状态
void _resetState() {
isListening.value = false;
isSpeaking.value = false;
userSpeechText.value = '';
aiResponseText.value = '';
_resetAudioLevels();
pulseAnimationController.stop();
_stopAudioUpdates();
}
/// 清理资源
void _cleanup() async {
_eventSubscription?.cancel();
_stopAudioUpdates();
pulseAnimationController.dispose();
try {
await _realtimeService.dispose();
} catch (e) {
Logger.error('清理Realtime服务失败: $e');
}
}
}