Browse Source

修改名称、包名

newdev_shunjiawei
tanlongsheng 2 years ago
parent
commit
7eb6ff01f6
  1. 4
      ios/Runner/BluetoothMediaButtonHelper.swift
  2. 2
      ios/Runner/Info.plist
  3. 285
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  4. 2
      lib/main.dart
  5. 2
      lib/modules/chat/views/chat_view.dart
  6. 6
      macos/Runner.xcodeproj/project.pbxproj
  7. 2
      macos/Runner/Configs/AppInfo.xcconfig

4
ios/Runner/BluetoothMediaButtonHelper.swift

@ -230,8 +230,8 @@ class BluetoothMediaButtonHelper: NSObject, AVAudioPlayerDelegate {
// 创建音频专辑图片 // 创建音频专辑图片
let albumArt = UIImage(named: "AppIcon") // 使用应用图标作为专辑图片 let albumArt = UIImage(named: "AppIcon") // 使用应用图标作为专辑图片
var nowPlayingInfo: [String: Any] = [ var nowPlayingInfo: [String: Any] = [
MPMediaItemPropertyTitle: "DeepVoice 语音助手", MPMediaItemPropertyTitle: "DeepSound 语音助手",
MPMediaItemPropertyArtist: "DeepVoice", MPMediaItemPropertyArtist: "DeepSound",
MPNowPlayingInfoPropertyPlaybackRate: isPlaying ? 1.0 : 0.0, // 1.0表示正在播放 MPNowPlayingInfoPropertyPlaybackRate: isPlaying ? 1.0 : 0.0, // 1.0表示正在播放
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentPlaybackTime, MPNowPlayingInfoPropertyElapsedPlaybackTime: currentPlaybackTime,
MPMediaItemPropertyPlaybackDuration: totalDuration MPMediaItemPropertyPlaybackDuration: totalDuration

2
ios/Runner/Info.plist

@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Deep Voice</string> <string>DeepSound</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

285
lib/data/services/speech_impl/volcano_asr_api_service.dart

@ -15,61 +15,61 @@ import 'package:record/record.dart';
import '../asr_service.dart'; import '../asr_service.dart';
/// Volcano语音识别服务 /// Volcano语音识别服务
/// ///
/// 负责与火山引擎实时语音识别服务的通信 /// 负责与火山引擎实时语音识别服务的通信
class VolcanoAsrApiService implements AsrService { class VolcanoAsrApiService implements AsrService {
static final VolcanoAsrApiService to = Get.find<VolcanoAsrApiService>(); static final VolcanoAsrApiService to = Get.find<VolcanoAsrApiService>();
// API配置 // API配置
late final String _appKey; late final String _appKey;
late final String _secretKey; late final String _secretKey;
late final String _serviceId; late final String _serviceId;
late final String _hostUrl; late final String _hostUrl;
final String _path = '/ws/v1/asr'; final String _path = '/ws/v1/asr';
// 请求相关参数 // 请求相关参数
final String _algorithm = 'hmac-sha1'; final String _algorithm = 'hmac-sha1';
final Uuid _uuid = const Uuid(); final Uuid _uuid = const Uuid();
// 录音配置 // 录音配置
final int _sampleRate = 16000; final int _sampleRate = 16000;
final int _bitsPerSample = 16; final int _bitsPerSample = 16;
final int _channels = 1; final int _channels = 1;
final String _encoding = 'pcm'; final String _encoding = 'pcm';
// 连接相关 // 连接相关
WebSocketChannel? _channel; WebSocketChannel? _channel;
Completer<void>? _connectionCompleter; Completer<void>? _connectionCompleter;
StreamSubscription? _connectionSubscription; StreamSubscription? _connectionSubscription;
Timer? _heartbeatTimer; Timer? _heartbeatTimer;
// 语音录音 // 语音录音
final Record? _recorder = null; // TODO: 实现正确的Record实例化 final Record? _recorder = null; // TODO: 实现正确的Record实例化
Timer? _audioTimer; Timer? _audioTimer;
bool _isRecording = false; bool _isRecording = false;
// 状态控制 // 状态控制
bool _isInitialized = false; bool _isInitialized = false;
bool _isConnecting = false; bool _isConnecting = false;
bool _isContinuousRecognitionActive = false; bool _isContinuousRecognitionActive = false;
// 识别结果控制 // 识别结果控制
bool _isRecognitionEndRequested = false; bool _isRecognitionEndRequested = false;
String _currentSessionId = ''; String _currentSessionId = '';
final List<String> _supportedLanguages = ['zh-CN', 'en-US']; final List<String> _supportedLanguages = ['zh-CN', 'en-US'];
String _selectedLanguage = 'zh-CN'; String _selectedLanguage = 'zh-CN';
// 结果流 // 结果流
StreamController<RecognitionEvent>? _recognitionController; StreamController<RecognitionEvent>? _recognitionController;
// 事件计数器(用于调试) // 事件计数器(用于调试)
int _sendPacketCount = 0; int _sendPacketCount = 0;
int _receivePacketCount = 0; int _receivePacketCount = 0;
// AsrService接口实现 // AsrService接口实现
@override @override
List<String> get supportedLanguages => _supportedLanguages; List<String> get supportedLanguages => _supportedLanguages;
@override @override
Future<bool> initialize({ Future<bool> initialize({
List<String>? supportedLanguages, List<String>? supportedLanguages,
@ -79,21 +79,21 @@ class VolcanoAsrApiService implements AsrService {
if (_isInitialized) { if (_isInitialized) {
await dispose(); await dispose();
} }
_isInitialized = false; _isInitialized = false;
// 从环境变量加载配置 // 从环境变量加载配置
_loadConfig(); _loadConfig();
// 设置支持的语言 // 设置支持的语言
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
_supportedLanguages.clear(); _supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages); _supportedLanguages.addAll(supportedLanguages);
} }
// 初始化录音机 // 初始化录音机
await _initRecorder(); await _initRecorder();
_isInitialized = true; _isInitialized = true;
Logger.info('火山语音API已初始化'); Logger.info('火山语音API已初始化');
return true; return true;
@ -102,19 +102,19 @@ class VolcanoAsrApiService implements AsrService {
return false; return false;
} }
} }
@override @override
Future<RecognitionEvent> recognizeOnce() async { Future<RecognitionEvent> recognizeOnce() async {
try { try {
if (!_isInitialized) { if (!_isInitialized) {
await initialize(); await initialize();
} }
final resultCompleter = Completer<RecognitionEvent>(); final resultCompleter = Completer<RecognitionEvent>();
// 开始连续识别流程 // 开始连续识别流程
final stream = await startContinuousRecognition(); final stream = await startContinuousRecognition();
// 监听结果 // 监听结果
StreamSubscription? subscription; StreamSubscription? subscription;
subscription = stream.listen((event) { subscription = stream.listen((event) {
@ -138,7 +138,7 @@ class VolcanoAsrApiService implements AsrService {
resultCompleter.complete(RecognitionEvent.error('未收到识别结果')); resultCompleter.complete(RecognitionEvent.error('未收到识别结果'));
} }
}); });
// 设置超时 // 设置超时
Timer(const Duration(seconds: 30), () { Timer(const Duration(seconds: 30), () {
if (!resultCompleter.isCompleted) { if (!resultCompleter.isCompleted) {
@ -147,104 +147,104 @@ class VolcanoAsrApiService implements AsrService {
stopContinuousRecognition(); stopContinuousRecognition();
} }
}); });
return resultCompleter.future; return resultCompleter.future;
} catch (e) { } catch (e) {
Logger.error('单次语音识别失败: $e'); Logger.error('单次语音识别失败: $e');
return RecognitionEvent.error(e.toString()); return RecognitionEvent.error(e.toString());
} }
} }
@override @override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async { Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
if (!_isInitialized) { if (!_isInitialized) {
await initialize(); await initialize();
} }
if (_isContinuousRecognitionActive) { if (_isContinuousRecognitionActive) {
await stopContinuousRecognition(); await stopContinuousRecognition();
} }
try { try {
// 创建结果控制器 // 创建结果控制器
_recognitionController = StreamController<RecognitionEvent>.broadcast(); _recognitionController = StreamController<RecognitionEvent>.broadcast();
// 连接WebSocket // 连接WebSocket
await _connectWebSocket(); await _connectWebSocket();
// 开始录音 // 开始录音
await _startRecording(); await _startRecording();
_isContinuousRecognitionActive = true; _isContinuousRecognitionActive = true;
_isRecognitionEndRequested = false; _isRecognitionEndRequested = false;
Logger.info('连续语音识别已开始'); Logger.info('连续语音识别已开始');
return _recognitionController!.stream; return _recognitionController!.stream;
} catch (e) { } catch (e) {
Logger.error('开始连续语音识别失败: $e'); Logger.error('开始连续语音识别失败: $e');
await _cleanupResources(); await _cleanupResources();
// 返回一个包含错误的流 // 返回一个包含错误的流
final errorController = StreamController<RecognitionEvent>.broadcast(); final errorController = StreamController<RecognitionEvent>.broadcast();
errorController.add(RecognitionEvent.error(e.toString())); errorController.add(RecognitionEvent.error(e.toString()));
errorController.close(); errorController.close();
return errorController.stream; return errorController.stream;
} }
} }
@override @override
Future<bool> stopContinuousRecognition() async { Future<bool> stopContinuousRecognition() async {
if (!_isContinuousRecognitionActive) { if (!_isContinuousRecognitionActive) {
return true; return true;
} }
try { try {
_isRecognitionEndRequested = true; _isRecognitionEndRequested = true;
// 先停止录音,以确保不会再发送新的音频数据 // 先停止录音,以确保不会再发送新的音频数据
await _stopRecording(); await _stopRecording();
// 发送结束指令 // 发送结束指令
await _sendEndSignal(); await _sendEndSignal();
// 等待服务器处理完所有已发送的数据 // 等待服务器处理完所有已发送的数据
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
// 清理资源 // 清理资源
await _cleanupResources(); await _cleanupResources();
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
Logger.info('连续语音识别已停止'); Logger.info('连续语音识别已停止');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: $e'); Logger.error('停止连续语音识别失败: $e');
// 确保资源被清理 // 确保资源被清理
await _cleanupResources(); await _cleanupResources();
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
return false; return false;
} }
} }
@override @override
bool isContinuousRecognitionActive() { bool isContinuousRecognitionActive() {
return _isContinuousRecognitionActive; return _isContinuousRecognitionActive;
} }
@override @override
Future<void> dispose() async { Future<void> dispose() async {
try { try {
if (_isContinuousRecognitionActive) { if (_isContinuousRecognitionActive) {
await stopContinuousRecognition(); await stopContinuousRecognition();
} }
// 确保资源被清理 // 确保资源被清理
await _cleanupResources(); await _cleanupResources();
_isInitialized = false; _isInitialized = false;
Logger.info('火山语音API资源已释放'); Logger.info('火山语音API资源已释放');
} catch (e) { } catch (e) {
@ -253,31 +253,32 @@ class VolcanoAsrApiService implements AsrService {
_isInitialized = false; _isInitialized = false;
} }
} }
/// 从环境变量加载配置 /// 从环境变量加载配置
void _loadConfig() { void _loadConfig() {
_appKey = dotenv.env['VOLCANO_APP_KEY'] ?? ''; _appKey = dotenv.env['VOLCANO_APP_KEY'] ?? '';
_secretKey = dotenv.env['VOLCANO_SECRET_KEY'] ?? ''; _secretKey = dotenv.env['VOLCANO_SECRET_KEY'] ?? '';
_serviceId = dotenv.env['VOLCANO_SERVICE_ID'] ?? ''; _serviceId = dotenv.env['VOLCANO_SERVICE_ID'] ?? '';
_hostUrl = dotenv.env['VOLCANO_HOST_URL'] ?? 'ws-api-asr.volces.com'; _hostUrl = dotenv.env['VOLCANO_HOST_URL'] ?? 'ws-api-asr.volces.com';
if (_appKey.isEmpty || _secretKey.isEmpty || _serviceId.isEmpty) { if (_appKey.isEmpty || _secretKey.isEmpty || _serviceId.isEmpty) {
throw Exception('未找到火山语音API配置,请在.env文件中设置VOLCANO_APP_KEY, VOLCANO_SECRET_KEY和VOLCANO_SERVICE_ID'); throw Exception(
'未找到火山语音API配置,请在.env文件中设置VOLCANO_APP_KEY, VOLCANO_SECRET_KEY和VOLCANO_SERVICE_ID');
} }
} }
/// 初始化录音机 /// 初始化录音机
Future<void> _initRecorder() async { Future<void> _initRecorder() async {
try { try {
// TODO: 正确实现录音机初始化 // TODO: 正确实现录音机初始化
// _recorder = Record(); // _recorder = Record();
// 检查和请求权限 // 检查和请求权限
// final hasPermission = await _recorder!.hasPermission(); // final hasPermission = await _recorder!.hasPermission();
// if (!hasPermission) { // if (!hasPermission) {
// throw Exception('没有录音权限'); // throw Exception('没有录音权限');
// } // }
Logger.info('录音机初始化成功'); Logger.info('录音机初始化成功');
} catch (e) { } catch (e) {
// _recorder = null; // _recorder = null;
@ -285,7 +286,7 @@ class VolcanoAsrApiService implements AsrService {
rethrow; rethrow;
} }
} }
/// 连接WebSocket /// 连接WebSocket
Future<void> _connectWebSocket() async { Future<void> _connectWebSocket() async {
if (_isConnecting) { if (_isConnecting) {
@ -294,20 +295,20 @@ class VolcanoAsrApiService implements AsrService {
return _connectionCompleter!.future; return _connectionCompleter!.future;
} }
} }
_connectionCompleter = Completer<void>(); _connectionCompleter = Completer<void>();
_isConnecting = true; _isConnecting = true;
try { try {
// 关闭现有连接 // 关闭现有连接
await _closeWebSocket(); await _closeWebSocket();
// 生成新的会话ID // 生成新的会话ID
_currentSessionId = _uuid.v4(); _currentSessionId = _uuid.v4();
// 构建URL和请求头 // 构建URL和请求头
final (url, headers) = _buildConnectionParams(); final (url, headers) = _buildConnectionParams();
// 创建WebSocket连接 // 创建WebSocket连接
if (kIsWeb) { if (kIsWeb) {
_channel = WebSocketChannel.connect(Uri.parse(url)); _channel = WebSocketChannel.connect(Uri.parse(url));
@ -317,42 +318,42 @@ class VolcanoAsrApiService implements AsrService {
headers: headers, headers: headers,
); );
} }
// 处理WebSocket消息 // 处理WebSocket消息
_connectionSubscription = _channel!.stream.listen( _connectionSubscription = _channel!.stream.listen(
_handleWebSocketMessage, _handleWebSocketMessage,
onError: _handleWebSocketError, onError: _handleWebSocketError,
onDone: _handleWebSocketDone, onDone: _handleWebSocketDone,
); );
// 开始心跳 // 开始心跳
_startHeartbeat(); _startHeartbeat();
// 发送开始识别指令 // 发送开始识别指令
await _sendStartCommand(); await _sendStartCommand();
_isConnecting = false; _isConnecting = false;
_connectionCompleter!.complete(); _connectionCompleter!.complete();
Logger.info('WebSocket连接成功'); Logger.info('WebSocket连接成功');
} catch (e) { } catch (e) {
_isConnecting = false; _isConnecting = false;
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError(e); _connectionCompleter!.completeError(e);
} }
Logger.error('连接WebSocket失败: $e'); Logger.error('连接WebSocket失败: $e');
rethrow; rethrow;
} }
return _connectionCompleter!.future; return _connectionCompleter!.future;
} }
/// 构建连接参数 /// 构建连接参数
(String, Map<String, String>?) _buildConnectionParams() { (String, Map<String, String>?) _buildConnectionParams() {
final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor();
final nonce = _generateNonce(); final nonce = _generateNonce();
// 构建表单(用于签名和查询参数) // 构建表单(用于签名和查询参数)
final form = { final form = {
'algorithm': _algorithm, 'algorithm': _algorithm,
@ -362,54 +363,54 @@ class VolcanoAsrApiService implements AsrService {
'session_id': _currentSessionId, 'session_id': _currentSessionId,
'timestamp': timestamp.toString(), 'timestamp': timestamp.toString(),
}; };
// 计算签名 // 计算签名
final signature = _calculateSignature(form); final signature = _calculateSignature(form);
// 添加签名到表单 // 添加签名到表单
form['signature'] = signature; form['signature'] = signature;
// 构建URL查询参数 // 构建URL查询参数
final queryString = form.entries final queryString = form.entries
.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}') .map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
.join('&'); .join('&');
// 构建完整URL // 构建完整URL
final url = 'wss://$_hostUrl$_path?$queryString'; final url = 'wss://$_hostUrl$_path?$queryString';
// 构建请求头 // 构建请求头
final headers = <String, String>{ final headers = <String, String>{
'User-Agent': 'Deep Voice App/1.0.0', 'User-Agent': 'DeepSound App/1.0.0',
}; };
return (url, kIsWeb ? null : headers); return (url, kIsWeb ? null : headers);
} }
/// 生成随机数 /// 生成随机数
String _generateNonce() { String _generateNonce() {
final random = Random(); final random = Random();
final randoms = List.generate(16, (_) => random.nextInt(256)); final randoms = List.generate(16, (_) => random.nextInt(256));
return base64Url.encode(randoms).substring(0, 16); return base64Url.encode(randoms).substring(0, 16);
} }
/// 计算签名 /// 计算签名
String _calculateSignature(Map<String, String> form) { String _calculateSignature(Map<String, String> form) {
// 按键排序 // 按键排序
final sortedKeys = form.keys.toList()..sort(); final sortedKeys = form.keys.toList()..sort();
// 构建待签名字符串 // 构建待签名字符串
final signString = sortedKeys.map((key) => '$key=${form[key]}').join(''); final signString = sortedKeys.map((key) => '$key=${form[key]}').join('');
// 使用HMAC-SHA1进行签名 // 使用HMAC-SHA1进行签名
final key = utf8.encode(_secretKey); final key = utf8.encode(_secretKey);
final bytes = utf8.encode(signString); final bytes = utf8.encode(signString);
final hmacSha1 = Hmac(sha1, key); final hmacSha1 = Hmac(sha1, key);
final digest = hmacSha1.convert(bytes); final digest = hmacSha1.convert(bytes);
// 返回Base64编码的签名 // 返回Base64编码的签名
return base64.encode(digest.bytes); return base64.encode(digest.bytes);
} }
/// 开始心跳定时 /// 开始心跳定时
void _startHeartbeat() { void _startHeartbeat() {
_heartbeatTimer?.cancel(); _heartbeatTimer?.cancel();
@ -417,7 +418,7 @@ class VolcanoAsrApiService implements AsrService {
_sendHeartbeat(); _sendHeartbeat();
}); });
} }
/// 发送心跳包 /// 发送心跳包
void _sendHeartbeat() { void _sendHeartbeat() {
if (_channel != null && _isContinuousRecognitionActive) { if (_channel != null && _isContinuousRecognitionActive) {
@ -426,18 +427,18 @@ class VolcanoAsrApiService implements AsrService {
'type': 'heartbeat', 'type': 'heartbeat',
'sequence': _sendPacketCount++, 'sequence': _sendPacketCount++,
}; };
_channel!.sink.add(jsonEncode(heartbeatMsg)); _channel!.sink.add(jsonEncode(heartbeatMsg));
} catch (e) { } catch (e) {
Logger.error('发送心跳包失败: $e'); Logger.error('发送心跳包失败: $e');
} }
} }
} }
/// 发送开始识别指令 /// 发送开始识别指令
Future<void> _sendStartCommand() async { Future<void> _sendStartCommand() async {
if (_channel == null) return; if (_channel == null) return;
try { try {
final startCommand = { final startCommand = {
'type': 'start', 'type': 'start',
@ -454,7 +455,7 @@ class VolcanoAsrApiService implements AsrService {
'max_seconds': 60, 'max_seconds': 60,
}, },
}; };
_channel!.sink.add(jsonEncode(startCommand)); _channel!.sink.add(jsonEncode(startCommand));
Logger.info('已发送开始识别指令'); Logger.info('已发送开始识别指令');
} catch (e) { } catch (e) {
@ -462,7 +463,7 @@ class VolcanoAsrApiService implements AsrService {
rethrow; rethrow;
} }
} }
/// 开始录音 /// 开始录音
Future<void> _startRecording() async { Future<void> _startRecording() async {
// TODO: 实现正确的录音开始逻辑 // TODO: 实现正确的录音开始逻辑
@ -497,12 +498,12 @@ class VolcanoAsrApiService implements AsrService {
rethrow; rethrow;
} }
*/ */
// 临时实现,防止错误 // 临时实现,防止错误
_isRecording = true; _isRecording = true;
Logger.info('录音已开始(模拟)'); Logger.info('录音已开始(模拟)');
} }
/// 捕获并发送音频数据 /// 捕获并发送音频数据
Future<void> _captureAndSendAudio() async { Future<void> _captureAndSendAudio() async {
// TODO: 实现正确的音频捕获逻辑 // TODO: 实现正确的音频捕获逻辑
@ -545,46 +546,46 @@ class VolcanoAsrApiService implements AsrService {
} }
*/ */
} }
/// 发送音频数据 /// 发送音频数据
Future<void> _sendAudioData(Uint8List data) async { Future<void> _sendAudioData(Uint8List data) async {
if (_channel == null || data.isEmpty) return; if (_channel == null || data.isEmpty) return;
try { try {
final audioMessage = { final audioMessage = {
'type': 'audio', 'type': 'audio',
'sequence': _sendPacketCount++, 'sequence': _sendPacketCount++,
'audio': base64.encode(data), 'audio': base64.encode(data),
}; };
_channel!.sink.add(jsonEncode(audioMessage)); _channel!.sink.add(jsonEncode(audioMessage));
} catch (e) { } catch (e) {
Logger.error('发送音频数据失败: $e'); Logger.error('发送音频数据失败: $e');
} }
} }
/// 发送结束信号 /// 发送结束信号
Future<void> _sendEndSignal() async { Future<void> _sendEndSignal() async {
if (_channel == null) return; if (_channel == null) return;
try { try {
final endCommand = { final endCommand = {
'type': 'end', 'type': 'end',
'sequence': _sendPacketCount++, 'sequence': _sendPacketCount++,
}; };
_channel!.sink.add(jsonEncode(endCommand)); _channel!.sink.add(jsonEncode(endCommand));
Logger.info('已发送结束指令'); Logger.info('已发送结束指令');
} catch (e) { } catch (e) {
Logger.error('发送结束指令失败: $e'); Logger.error('发送结束指令失败: $e');
} }
} }
/// 停止录音 /// 停止录音
Future<void> _stopRecording() async { Future<void> _stopRecording() async {
_audioTimer?.cancel(); _audioTimer?.cancel();
_audioTimer = null; _audioTimer = null;
// TODO: 实现正确的录音停止逻辑 // TODO: 实现正确的录音停止逻辑
/* /*
if (_isRecording && _recorder != null) { if (_isRecording && _recorder != null) {
@ -597,64 +598,65 @@ class VolcanoAsrApiService implements AsrService {
} }
} }
*/ */
// 临时实现,防止错误 // 临时实现,防止错误
_isRecording = false; _isRecording = false;
Logger.info('录音已停止(模拟)'); Logger.info('录音已停止(模拟)');
} }
/// 处理WebSocket消息 /// 处理WebSocket消息
void _handleWebSocketMessage(dynamic message) { void _handleWebSocketMessage(dynamic message) {
try { try {
_receivePacketCount++; _receivePacketCount++;
if (message is String) { if (message is String) {
final Map<String, dynamic> data = jsonDecode(message); final Map<String, dynamic> data = jsonDecode(message);
final String type = data['type'] as String? ?? ''; final String type = data['type'] as String? ?? '';
switch (type) { switch (type) {
case 'result': case 'result':
_handleResultMessage(data); _handleResultMessage(data);
break; break;
case 'start_result': case 'start_result':
_handleStartResultMessage(data); _handleStartResultMessage(data);
break; break;
case 'end_result': case 'end_result':
_handleEndResultMessage(data); _handleEndResultMessage(data);
break; break;
case 'error': case 'error':
_handleErrorMessage(data); _handleErrorMessage(data);
break; break;
case 'heartbeat_result': case 'heartbeat_result':
// 心跳响应,不需要特殊处理 // 心跳响应,不需要特殊处理
break; break;
default: default:
Logger.info('收到未知类型的消息: $type'); Logger.info('收到未知类型的消息: $type');
} }
} }
} catch (e) { } catch (e) {
Logger.error('处理WebSocket消息失败: $e'); Logger.error('处理WebSocket消息失败: $e');
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.addError('处理消息失败: $e'); _recognitionController!.addError('处理消息失败: $e');
} }
} }
} }
/// 处理结果消息 /// 处理结果消息
void _handleResultMessage(Map<String, dynamic> data) { void _handleResultMessage(Map<String, dynamic> data) {
try { try {
final Map<String, dynamic>? result = data['result'] as Map<String, dynamic>?; final Map<String, dynamic>? result =
data['result'] as Map<String, dynamic>?;
if (result == null) return; if (result == null) return;
final bool isEnd = result['is_end'] as bool? ?? false; final bool isEnd = result['is_end'] as bool? ?? false;
final String text = result['text'] as String? ?? ''; final String text = result['text'] as String? ?? '';
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
if (isEnd) { if (isEnd) {
// 最终结果 // 最终结果
@ -674,11 +676,11 @@ class VolcanoAsrApiService implements AsrService {
Logger.error('处理结果消息失败: $e'); Logger.error('处理结果消息失败: $e');
} }
} }
/// 处理开始结果消息 /// 处理开始结果消息
void _handleStartResultMessage(Map<String, dynamic> data) { void _handleStartResultMessage(Map<String, dynamic> data) {
final int code = data['code'] as int? ?? -1; final int code = data['code'] as int? ?? -1;
if (code == 0) { if (code == 0) {
// 开始成功 // 开始成功
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
@ -689,19 +691,20 @@ class VolcanoAsrApiService implements AsrService {
} else { } else {
// 开始失败 // 开始失败
final String message = data['message'] as String? ?? '未知错误'; final String message = data['message'] as String? ?? '未知错误';
Logger.error('语音识别开始失败: [$code] $message'); Logger.error('语音识别开始失败: [$code] $message');
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.add(RecognitionEvent.error('开始失败: [$code] $message')); _recognitionController!
.add(RecognitionEvent.error('开始失败: [$code] $message'));
} }
} }
} }
/// 处理结束结果消息 /// 处理结束结果消息
void _handleEndResultMessage(Map<String, dynamic> data) { void _handleEndResultMessage(Map<String, dynamic> data) {
final int code = data['code'] as int? ?? -1; final int code = data['code'] as int? ?? -1;
if (code == 0) { if (code == 0) {
// 结束成功 // 结束成功
Logger.info('语音识别成功结束'); Logger.info('语音识别成功结束');
@ -710,73 +713,75 @@ class VolcanoAsrApiService implements AsrService {
final String message = data['message'] as String? ?? '未知错误'; final String message = data['message'] as String? ?? '未知错误';
Logger.error('语音识别结束失败: [$code] $message'); Logger.error('语音识别结束失败: [$code] $message');
} }
// 无论成功还是失败,通知会话已结束 // 无论成功还是失败,通知会话已结束
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.add(RecognitionEvent( _recognitionController!.add(RecognitionEvent(
type: RecognitionEventType.sessionStopped, type: RecognitionEventType.sessionStopped,
)); ));
} }
// 清理资源 // 清理资源
_cleanupResources(); _cleanupResources();
} }
/// 处理错误消息 /// 处理错误消息
void _handleErrorMessage(Map<String, dynamic> data) { void _handleErrorMessage(Map<String, dynamic> data) {
final int code = data['code'] as int? ?? -1; final int code = data['code'] as int? ?? -1;
final String message = data['message'] as String? ?? '未知错误'; final String message = data['message'] as String? ?? '未知错误';
Logger.error('收到错误消息: [$code] $message'); Logger.error('收到错误消息: [$code] $message');
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.add(RecognitionEvent.error('[$code] $message')); _recognitionController!.add(RecognitionEvent.error('[$code] $message'));
} }
} }
/// 处理WebSocket错误 /// 处理WebSocket错误
void _handleWebSocketError(dynamic error) { void _handleWebSocketError(dynamic error) {
Logger.error('WebSocket错误: $error'); Logger.error('WebSocket错误: $error');
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError(error); _connectionCompleter!.completeError(error);
} }
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.addError(error); _recognitionController!.addError(error);
} }
// 清理资源 // 清理资源
_cleanupResources(); _cleanupResources();
} }
/// 处理WebSocket连接关闭 /// 处理WebSocket连接关闭
void _handleWebSocketDone() { void _handleWebSocketDone() {
Logger.info('WebSocket连接已关闭'); Logger.info('WebSocket连接已关闭');
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError('WebSocket连接已关闭'); _connectionCompleter!.completeError('WebSocket连接已关闭');
} }
// 通知会话已结束 // 通知会话已结束
if (_recognitionController != null && !_recognitionController!.isClosed && !_isRecognitionEndRequested) { if (_recognitionController != null &&
!_recognitionController!.isClosed &&
!_isRecognitionEndRequested) {
_recognitionController!.add(RecognitionEvent( _recognitionController!.add(RecognitionEvent(
type: RecognitionEventType.sessionStopped, type: RecognitionEventType.sessionStopped,
)); ));
} }
// 清理资源 // 清理资源
_cleanupResources(); _cleanupResources();
} }
/// 关闭WebSocket连接 /// 关闭WebSocket连接
Future<void> _closeWebSocket() async { Future<void> _closeWebSocket() async {
_connectionSubscription?.cancel(); _connectionSubscription?.cancel();
_connectionSubscription = null; _connectionSubscription = null;
_heartbeatTimer?.cancel(); _heartbeatTimer?.cancel();
_heartbeatTimer = null; _heartbeatTimer = null;
if (_channel != null) { if (_channel != null) {
try { try {
_channel!.sink.close(); _channel!.sink.close();
@ -786,22 +791,22 @@ class VolcanoAsrApiService implements AsrService {
} }
} }
} }
/// 清理所有资源 /// 清理所有资源
Future<void> _cleanupResources() async { Future<void> _cleanupResources() async {
// 停止录音 // 停止录音
await _stopRecording(); await _stopRecording();
// 关闭WebSocket // 关闭WebSocket
await _closeWebSocket(); await _closeWebSocket();
// 关闭结果控制器 // 关闭结果控制器
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
await _recognitionController!.close(); await _recognitionController!.close();
_recognitionController = null; _recognitionController = null;
} }
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
_isRecognitionEndRequested = false; _isRecognitionEndRequested = false;
} }
} }

2
lib/main.dart

@ -30,7 +30,7 @@ void main() async {
const double width = 375.0; const double width = 375.0;
const double height = 812.0; const double height = 812.0;
setWindowTitle('Deep Voice'); setWindowTitle('DeepSound');
setWindowMinSize(const Size(width, height)); setWindowMinSize(const Size(width, height));
setWindowMaxSize(const Size(width, height)); setWindowMaxSize(const Size(width, height));

2
lib/modules/chat/views/chat_view.dart

@ -2,11 +2,9 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:math'; import 'dart:math';
import 'package:audio_waveforms/audio_waveforms.dart';
import '../controllers/chat_controller.dart'; import '../controllers/chat_controller.dart';
import '../models/message_model.dart'; import '../models/message_model.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
import '../../../core/widgets/message_bubble.dart';
class ChatView extends GetView<ChatController> { class ChatView extends GetView<ChatController> {
const ChatView({Key? key}) : super(key: key); const ChatView({Key? key}) : super(key: key);

6
macos/Runner.xcodeproj/project.pbxproj

@ -479,7 +479,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice";
@ -494,7 +494,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice";
@ -509,7 +509,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/deep_voice.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/deep_voice";

2
macos/Runner/Configs/AppInfo.xcconfig

@ -8,7 +8,7 @@
PRODUCT_NAME = deep_voice PRODUCT_NAME = deep_voice
// The application's bundle identifier // The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.deepVoice PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound
// The copyright displayed in application information // The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved.

Loading…
Cancel
Save