@ -0,0 +1,2 @@ |
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" |
|||
#include "Generated.xcconfig" |
|||
@ -1,2 +1,3 @@ |
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" |
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" |
|||
#include "Generated.xcconfig" |
|||
|
|||
@ -1,478 +0,0 @@ |
|||
import 'dart:async'; |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:speech_to_text/speech_to_text.dart' as stt; |
|||
import 'package:speech_to_text/speech_recognition_result.dart'; |
|||
import 'package:speech_to_text/speech_recognition_error.dart'; |
|||
import '../../../core/utils/logger.dart'; |
|||
import '../asr_service.dart'; |
|||
|
|||
/// ASR状态枚举 |
|||
enum AsrState { |
|||
/// 未初始化 |
|||
notInitialized, |
|||
|
|||
/// 已初始化但未开始监听 |
|||
initialized, |
|||
|
|||
/// 正在监听 |
|||
listening, |
|||
|
|||
/// 正在处理识别结果 |
|||
processing, |
|||
|
|||
/// 识别完成 |
|||
done, |
|||
|
|||
/// 发生错误 |
|||
error, |
|||
} |
|||
|
|||
/// Flutter ASR服务,负责本地语音转文本功能 |
|||
/// 使用speech_to_text插件实现语音识别功能 |
|||
class FlutterAsrService extends GetxService implements AsrService { |
|||
static final FlutterAsrService to = Get.put(FlutterAsrService()); |
|||
|
|||
// speech_to_text 实例 |
|||
final stt.SpeechToText _speech = stt.SpeechToText(); |
|||
|
|||
// 可用的语音识别语言 |
|||
final List<String> _supportedLanguages = []; |
|||
@override |
|||
List<String> get supportedLanguages => _supportedLanguages; |
|||
|
|||
// 当前选择的语言 |
|||
final RxString _currentLocale = ''.obs; |
|||
String get currentLocale => _currentLocale.value; |
|||
|
|||
// 是否可用 |
|||
final RxBool _isAvailable = false.obs; |
|||
bool get isAvailable => _isAvailable.value; |
|||
|
|||
// 是否正在监听 |
|||
bool _isListening = false; |
|||
|
|||
// 当前ASR状态 |
|||
final Rx<AsrState> _asrState = AsrState.notInitialized.obs; |
|||
AsrState get asrState => _asrState.value; |
|||
|
|||
// 连续识别模式的结果流控制器 |
|||
StreamController<RecognitionEvent>? _continuousRecognitionController; |
|||
|
|||
FlutterAsrService() { |
|||
_initSpeech(); |
|||
} |
|||
|
|||
/// 初始化语音识别 |
|||
Future<void> _initSpeech() async { |
|||
try { |
|||
_asrState.value = AsrState.notInitialized; |
|||
|
|||
// 初始化语音识别,使用较短的最终超时时间,提高响应速度 |
|||
_isAvailable.value = await _speech.initialize( |
|||
onStatus: _onSpeechStatus, |
|||
onError: _onSpeechError, |
|||
debugLogging: false, |
|||
finalTimeout: const Duration(milliseconds: 800), // 更短的最终超时时间,加速结果返回 |
|||
); |
|||
|
|||
if (_isAvailable.value) { |
|||
// 获取支持的语言列表 |
|||
final systemLocales = await _speech.locales(); |
|||
|
|||
// 清空并重新填充支持的语言列表 |
|||
_supportedLanguages.clear(); |
|||
for (var locale in systemLocales) { |
|||
if (!_supportedLanguages.contains(locale.localeId)) { |
|||
_supportedLanguages.add(locale.localeId); |
|||
} |
|||
} |
|||
|
|||
// 设置默认语言(优先使用系统语言或中文) |
|||
var systemLocale = await _speech.systemLocale(); |
|||
if (systemLocale != null) { |
|||
_currentLocale.value = systemLocale.localeId; |
|||
} else if (_supportedLanguages.contains('zh-CN')) { |
|||
_currentLocale.value = 'zh-CN'; |
|||
} else if (_supportedLanguages.isNotEmpty) { |
|||
_currentLocale.value = _supportedLanguages.first; |
|||
} |
|||
|
|||
_asrState.value = AsrState.initialized; |
|||
Logger.info('语音识别初始化成功,支持 ${_supportedLanguages.length} 种语言'); |
|||
} else { |
|||
_asrState.value = AsrState.error; |
|||
Logger.error('语音识别不可用'); |
|||
} |
|||
} catch (e) { |
|||
_isAvailable.value = false; |
|||
_asrState.value = AsrState.error; |
|||
Logger.error('语音识别初始化失败: ${e.toString()}'); |
|||
} |
|||
} |
|||
|
|||
/// 处理语音识别状态变化 |
|||
void _onSpeechStatus(String status) { |
|||
Logger.info('语音识别状态: $status'); |
|||
|
|||
switch (status) { |
|||
case 'listening': |
|||
_asrState.value = AsrState.listening; |
|||
_isListening = true; |
|||
break; |
|||
case 'notListening': |
|||
if (_asrState.value == AsrState.listening) { |
|||
_asrState.value = AsrState.processing; |
|||
} |
|||
_isListening = false; |
|||
break; |
|||
case 'done': |
|||
_asrState.value = AsrState.done; |
|||
_isListening = false; |
|||
|
|||
// 在连续识别模式下,当一次识别完成后立即开始下一次识别 |
|||
if (_continuousRecognitionController != null && |
|||
!_continuousRecognitionController!.isClosed) { |
|||
_restartContinuousRecognition(); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// 处理语音识别错误 |
|||
void _onSpeechError(SpeechRecognitionError error) { |
|||
Logger.error('语音识别错误: ${error.errorMsg} (永久性错误: ${error.permanent})'); |
|||
_asrState.value = AsrState.error; |
|||
|
|||
// 向连续识别控制器发送错误事件(如果存在) |
|||
_continuousRecognitionController |
|||
?.add(RecognitionEvent.error(error.errorMsg)); |
|||
|
|||
// 在连续识别模式下,尝试从错误中恢复(非永久性错误) |
|||
if (!error.permanent && |
|||
_continuousRecognitionController != null && |
|||
!_continuousRecognitionController!.isClosed) { |
|||
_restartContinuousRecognition(); |
|||
} |
|||
} |
|||
|
|||
/// 启动语音识别的统一方法 |
|||
Future<bool> _startListening({ |
|||
required Function(SpeechRecognitionResult) onResult, |
|||
Duration listenFor = const Duration(seconds: 30), |
|||
Duration pauseFor = const Duration(seconds: 3), |
|||
bool autoPunctuation = true, |
|||
}) async { |
|||
try { |
|||
// 如果当前正在监听,先停止 |
|||
if (_speech.isListening) { |
|||
await _speech.stop(); |
|||
// 延长等待时间,确保资源完全释放 |
|||
await Future.delayed(const Duration(milliseconds: 800)); |
|||
} |
|||
|
|||
// 在启动前再次检查状态 |
|||
if (_speech.isListening) { |
|||
Logger.warning('停止识别后,仍处于监听状态,可能有系统冲突'); |
|||
await _speech.stop(); |
|||
await Future.delayed(const Duration(milliseconds: 500)); |
|||
} |
|||
|
|||
// 启动语音识别前确保状态正确 |
|||
_asrState.value = AsrState.listening; |
|||
|
|||
// 启动语音识别 |
|||
final result = await _speech.listen( |
|||
onResult: onResult, |
|||
localeId: _currentLocale.value, |
|||
listenFor: listenFor, |
|||
pauseFor: pauseFor, |
|||
listenOptions: stt.SpeechListenOptions( |
|||
partialResults: true, |
|||
autoPunctuation: autoPunctuation, |
|||
// 使用独白模式,减少与系统交互冲突 |
|||
listenMode: stt.ListenMode.dictation, |
|||
), |
|||
) ?? |
|||
false; |
|||
|
|||
if (!result) { |
|||
Logger.error('语音识别启动失败'); |
|||
_asrState.value = AsrState.error; |
|||
} |
|||
|
|||
return result; |
|||
} catch (e) { |
|||
Logger.error('启动语音识别失败: ${e.toString()}'); |
|||
_asrState.value = AsrState.error; |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// 重新启动连续识别 |
|||
Future<void> _restartContinuousRecognition() async { |
|||
try { |
|||
// 确保控制器有效 |
|||
if (_continuousRecognitionController == null || |
|||
_continuousRecognitionController!.isClosed) { |
|||
Logger.info('连续识别控制器已关闭,跳过重启'); |
|||
return; |
|||
} |
|||
|
|||
// 检查系统状态,避免频繁重启 |
|||
if (_speech.isListening) { |
|||
Logger.info('当前已在监听中,无需重启'); |
|||
return; |
|||
} |
|||
|
|||
// 延长等待时间,避免与系统资源冲突 |
|||
await Future.delayed(const Duration(milliseconds: 1000)); |
|||
|
|||
// 再次检查状态 |
|||
if (_continuousRecognitionController == null || |
|||
_continuousRecognitionController!.isClosed) { |
|||
Logger.info('延迟期间控制器已关闭,跳过重启'); |
|||
return; |
|||
} |
|||
|
|||
Logger.info('尝试重新启动连续识别'); |
|||
|
|||
// 使用统一方法启动识别 |
|||
final success = await _startListening( |
|||
onResult: _onSpeechResult, |
|||
// 增加超时时间和暂停时间 |
|||
listenFor: const Duration(seconds: 30), |
|||
pauseFor: const Duration(seconds: 4), |
|||
); |
|||
|
|||
if (success) { |
|||
Logger.info('连续识别重启成功'); |
|||
} else { |
|||
Logger.error('连续识别重启失败'); |
|||
_continuousRecognitionController |
|||
?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突')); |
|||
} |
|||
} catch (e) { |
|||
Logger.error('重启连续识别失败: ${e.toString()}'); |
|||
|
|||
// 发送错误给控制器 |
|||
_continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e')); |
|||
} |
|||
} |
|||
|
|||
/// 处理语音识别结果 |
|||
void _onSpeechResult(SpeechRecognitionResult result) { |
|||
// 如果是连续识别,发送到连续识别控制器 |
|||
if (_continuousRecognitionController != null) { |
|||
if (result.finalResult) { |
|||
Logger.info('收到最终识别结果: "${result.recognizedWords}"'); |
|||
_continuousRecognitionController!.add(RecognitionEvent.finalResult( |
|||
text: result.recognizedWords, |
|||
detectedLanguage: currentLocale, |
|||
)); |
|||
} else { |
|||
_continuousRecognitionController!.add(RecognitionEvent( |
|||
type: RecognitionEventType.intermediateResult, |
|||
text: result.recognizedWords, |
|||
detectedLanguage: currentLocale, |
|||
)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// 设置当前语言 |
|||
void setLocale(String localeId) { |
|||
if (_supportedLanguages.contains(localeId)) { |
|||
_currentLocale.value = localeId; |
|||
Logger.info('设置语音识别语言: $localeId'); |
|||
} else { |
|||
Logger.error('不支持的语言: $localeId'); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> initialize({ |
|||
List<String>? supportedLanguages, |
|||
}) async { |
|||
try { |
|||
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { |
|||
_supportedLanguages.clear(); |
|||
_supportedLanguages.addAll(supportedLanguages); |
|||
} |
|||
|
|||
// 初始化语音识别 |
|||
await _initSpeech(); |
|||
return _isAvailable.value; |
|||
} catch (e) { |
|||
Logger.error('语音识别初始化失败: ${e.toString()}'); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<RecognitionEvent> recognizeOnce() async { |
|||
try { |
|||
if (!_isAvailable.value) { |
|||
return RecognitionEvent.error('语音识别不可用'); |
|||
} |
|||
|
|||
if (_isListening) { |
|||
await stopContinuousRecognition(); |
|||
} |
|||
|
|||
// 创建Completer来等待最终结果 |
|||
final completer = Completer<RecognitionEvent>(); |
|||
|
|||
// 开始监听 |
|||
_asrState.value = AsrState.listening; |
|||
|
|||
// 定义结果处理函数 |
|||
void resultListener(SpeechRecognitionResult result) { |
|||
if (result.finalResult) { |
|||
// 只有在最终结果时才完成completer |
|||
completer.complete(RecognitionEvent.finalResult( |
|||
text: result.recognizedWords, |
|||
detectedLanguage: currentLocale, |
|||
)); |
|||
} |
|||
} |
|||
|
|||
// 使用统一方法启动识别,单次识别使用更短的时间参数 |
|||
final success = await _startListening( |
|||
onResult: resultListener, |
|||
listenFor: const Duration(seconds: 15), |
|||
pauseFor: const Duration(seconds: 2), |
|||
); |
|||
|
|||
if (!success) { |
|||
return RecognitionEvent.error('启动语音识别失败'); |
|||
} |
|||
|
|||
// 设置较短的超时时间 |
|||
return await completer.future.timeout( |
|||
const Duration(seconds: 20), // 略长于listenFor以确保获取到结果 |
|||
onTimeout: () { |
|||
_speech.stop(); |
|||
return RecognitionEvent.error('语音识别超时'); |
|||
}, |
|||
); |
|||
} catch (e) { |
|||
Logger.error('一次性语音识别失败: ${e.toString()}'); |
|||
return RecognitionEvent.error('$e'); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<Stream<RecognitionEvent>> startContinuousRecognition( |
|||
bool audioSourceType) async { |
|||
try { |
|||
if (!_isAvailable.value) { |
|||
final controller = StreamController<RecognitionEvent>.broadcast(); |
|||
controller.add(RecognitionEvent.error('语音识别不可用')); |
|||
controller.close(); |
|||
return controller.stream; |
|||
} |
|||
|
|||
// 如果已经在监听,先停止 |
|||
if (_isListening) { |
|||
await stopContinuousRecognition(); |
|||
} |
|||
|
|||
// 创建新的流控制器 |
|||
_continuousRecognitionController = |
|||
StreamController<RecognitionEvent>.broadcast(); |
|||
|
|||
// 开始会话 |
|||
_continuousRecognitionController! |
|||
.add(RecognitionEvent(type: RecognitionEventType.sessionStarted)); |
|||
|
|||
// 开始连续监听 |
|||
_asrState.value = AsrState.listening; |
|||
|
|||
// 使用统一方法启动识别,连续识别使用较长的参数 |
|||
final success = await _startListening( |
|||
onResult: _onSpeechResult, |
|||
); |
|||
|
|||
if (!success) { |
|||
// 添加错误事件,但保留流 |
|||
_continuousRecognitionController! |
|||
.add(RecognitionEvent.error('启动连续语音识别失败')); |
|||
|
|||
// 返回已有的流控制器 |
|||
return _continuousRecognitionController!.stream; |
|||
} |
|||
|
|||
_isListening = true; |
|||
return _continuousRecognitionController!.stream; |
|||
} catch (e) { |
|||
Logger.error('启动连续语音识别失败: ${e.toString()}'); |
|||
|
|||
// 如果已经创建了控制器,使用它 |
|||
if (_continuousRecognitionController != null) { |
|||
_continuousRecognitionController!.add(RecognitionEvent.error('$e')); |
|||
return _continuousRecognitionController!.stream; |
|||
} else { |
|||
// 否则创建新的控制器 |
|||
final controller = StreamController<RecognitionEvent>.broadcast(); |
|||
controller.add(RecognitionEvent.error('$e')); |
|||
return controller.stream; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> stopContinuousRecognition() async { |
|||
try { |
|||
// 无论是否正在监听,都尝试停止 |
|||
if (_speech.isListening) { |
|||
await _speech.stop(); |
|||
} |
|||
|
|||
_isListening = false; |
|||
|
|||
// 发送会话结束事件 |
|||
_continuousRecognitionController |
|||
?.add(RecognitionEvent(type: RecognitionEventType.sessionStopped)); |
|||
|
|||
// 关闭流控制器 |
|||
await _continuousRecognitionController?.close(); |
|||
_continuousRecognitionController = null; |
|||
|
|||
_asrState.value = AsrState.done; |
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('停止连续语音识别失败: ${e.toString()}'); |
|||
|
|||
// 强制重置状态 |
|||
_isListening = false; |
|||
_asrState.value = AsrState.error; |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
bool isContinuousRecognitionActive() { |
|||
return _isListening; |
|||
} |
|||
|
|||
@override |
|||
Future<void> dispose() async { |
|||
try { |
|||
// 确保停止所有识别活动 |
|||
if (_speech.isListening) { |
|||
await _speech.stop(); |
|||
} |
|||
|
|||
// 关闭控制器 |
|||
await _continuousRecognitionController?.close(); |
|||
_continuousRecognitionController = null; |
|||
|
|||
_isListening = false; |
|||
_asrState.value = AsrState.notInitialized; |
|||
} catch (e) { |
|||
Logger.error('释放语音识别资源失败: ${e.toString()}'); |
|||
} |
|||
} |
|||
} |
|||
@ -1,484 +0,0 @@ |
|||
import 'dart:async'; |
|||
import 'dart:collection'; |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:flutter_tts/flutter_tts.dart'; |
|||
import '../../../core/utils/logger.dart'; |
|||
import '../tts_service.dart'; |
|||
|
|||
/// Flutter TTS服务,负责基本的文本转语音功能 |
|||
/// |
|||
/// 使用flutter_tts插件实现TtsService接口 |
|||
class FlutterTtsService extends GetxService implements TtsService { |
|||
static final FlutterTtsService to = Get.put(FlutterTtsService()); |
|||
|
|||
// flutter_tts实例 |
|||
late final FlutterTts _flutterTts; |
|||
|
|||
// 支持的语音列表 |
|||
final List<String> _supportedVoices = []; |
|||
List<String> get supportedVoices => _supportedVoices; |
|||
|
|||
// 当前语音 |
|||
final RxString _currentVoice = ''.obs; |
|||
@override |
|||
String get currentVoice => _currentVoice.value; |
|||
|
|||
// 播放状态 |
|||
final RxBool _isSpeaking = false.obs; |
|||
@override |
|||
bool get isSpeaking => _isSpeaking.value; |
|||
|
|||
// 文本队列 |
|||
final Queue<_SpeechItem> _textQueue = Queue<_SpeechItem>(); |
|||
bool _isProcessingQueue = false; |
|||
|
|||
// 事件流控制器 |
|||
final StreamController<TtsEvent> _eventController = StreamController<TtsEvent>.broadcast(); |
|||
@override |
|||
Stream<TtsEvent> get onEvent => _eventController.stream; |
|||
|
|||
// 语言 |
|||
String _language = 'zh-CN'; |
|||
|
|||
// 流式文本缓冲区 |
|||
String _streamBuffer = ''; |
|||
|
|||
// 当前参数设置 |
|||
double _volume = 1.0; |
|||
double _rate = 0.5; |
|||
double _pitch = 1.0; |
|||
|
|||
FlutterTtsService() { |
|||
_flutterTts = FlutterTts(); |
|||
_initTts(); |
|||
} |
|||
|
|||
/// 初始化TTS |
|||
Future<void> _initTts() async { |
|||
try { |
|||
// 设置完成回调 |
|||
_flutterTts.setCompletionHandler(() { |
|||
_isSpeaking.value = false; |
|||
_eventController.add(TtsEvent(type: TtsEventType.completed)); |
|||
}); |
|||
|
|||
// 设置开始回调 |
|||
_flutterTts.setStartHandler(() { |
|||
_isSpeaking.value = true; |
|||
_eventController.add(TtsEvent(type: TtsEventType.started)); |
|||
}); |
|||
|
|||
// 设置错误回调 |
|||
_flutterTts.setErrorHandler((msg) { |
|||
_isSpeaking.value = false; |
|||
_eventController.add(TtsEvent.error(msg)); |
|||
Logger.error('TTS错误: $msg'); |
|||
}); |
|||
|
|||
// 设置取消回调 |
|||
_flutterTts.setCancelHandler(() { |
|||
_isSpeaking.value = false; |
|||
_eventController.add(TtsEvent(type: TtsEventType.canceled)); |
|||
}); |
|||
|
|||
// 设置默认语言 |
|||
await _flutterTts.setLanguage(_language); |
|||
|
|||
// 设置音量 |
|||
await _flutterTts.setVolume(_volume); |
|||
|
|||
// 设置语速 |
|||
await _flutterTts.setSpeechRate(_rate); |
|||
|
|||
// 设置音调 |
|||
await _flutterTts.setPitch(_pitch); |
|||
|
|||
Logger.info('FlutterTtsService 已初始化'); |
|||
} catch (e) { |
|||
Logger.error('TTS初始化失败: ${e.toString()}'); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> initialize({ |
|||
List<String>? supportedLanguages, |
|||
}) async { |
|||
try { |
|||
// 获取可用的语音 |
|||
await _loadVoices(); |
|||
|
|||
// 更新支持的语言列表(如果提供) |
|||
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { |
|||
_language = supportedLanguages.first; |
|||
await _flutterTts.setLanguage(_language); |
|||
} |
|||
|
|||
Logger.info('FlutterTtsService 初始化成功'); |
|||
Logger.info('支持的语音: ${_supportedVoices.join(", ")}'); |
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('TTS初始化失败: ${e.toString()}'); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// 加载可用的语音 |
|||
Future<void> _loadVoices() async { |
|||
try { |
|||
_supportedVoices.clear(); |
|||
|
|||
// 获取可用的语音 |
|||
final voices = await _flutterTts.getVoices; |
|||
|
|||
if (voices != null && voices is List) { |
|||
for (final voice in voices) { |
|||
if (voice is Map && voice.containsKey('name')) { |
|||
_supportedVoices.add(voice['name'].toString()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 如果没有找到语音,添加默认语音 |
|||
if (_supportedVoices.isEmpty) { |
|||
_supportedVoices.add('default'); |
|||
} |
|||
|
|||
// 设置默认语音 |
|||
if (_currentVoice.value.isEmpty && _supportedVoices.isNotEmpty) { |
|||
_currentVoice.value = _supportedVoices.first; |
|||
} |
|||
} catch (e) { |
|||
Logger.error('加载TTS语音失败: ${e.toString()}'); |
|||
|
|||
// 添加默认语音 |
|||
if (_supportedVoices.isEmpty) { |
|||
_supportedVoices.add('default'); |
|||
_currentVoice.value = 'default'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> setVoice(String voiceName) async { |
|||
try { |
|||
// 检查语音是否存在 |
|||
if (!_supportedVoices.contains(voiceName) && voiceName != 'default') { |
|||
Logger.warning('语音不存在: $voiceName,使用默认语音'); |
|||
voiceName = _supportedVoices.isNotEmpty ? _supportedVoices.first : 'default'; |
|||
} |
|||
|
|||
// 设置语音 |
|||
await _flutterTts.setVoice({"name": voiceName}); |
|||
_currentVoice.value = voiceName; |
|||
|
|||
Logger.info('设置TTS语音: $voiceName'); |
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('设置TTS语音失败: ${e.toString()}'); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// 设置语音参数 |
|||
Future<bool> setSpeechParams({double? rate, double? pitch, double? volume}) async { |
|||
try { |
|||
if (volume != null) { |
|||
await _flutterTts.setVolume(volume); |
|||
_volume = volume; |
|||
} |
|||
|
|||
if (rate != null) { |
|||
await _flutterTts.setSpeechRate(rate); |
|||
_rate = rate; |
|||
} |
|||
|
|||
if (pitch != null) { |
|||
await _flutterTts.setPitch(pitch); |
|||
_pitch = pitch; |
|||
} |
|||
|
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('设置语音参数失败: ${e.toString()}'); |
|||
|
|||
// 发送错误事件 |
|||
_eventController.add(TtsEvent.error('设置语音参数失败: $e')); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> speakOnce(String text) async { |
|||
if (text.isEmpty) return false; |
|||
|
|||
try { |
|||
// 如果正在播放,先停止 |
|||
if (_isSpeaking.value) { |
|||
await stop(); |
|||
} |
|||
|
|||
// 播放文本 |
|||
await _flutterTts.speak(text); |
|||
|
|||
return true; |
|||
} catch (e) { |
|||
// 发送错误事件 |
|||
_eventController.add(TtsEvent.error(e.toString())); |
|||
|
|||
Logger.error('TTS播放失败: ${e.toString()}'); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> speakStream(String text) async { |
|||
if (text.isEmpty) return false; |
|||
|
|||
try { |
|||
// 添加文本到缓冲区 |
|||
_streamBuffer += text; |
|||
|
|||
// 如果缓冲区为空,直接返回 |
|||
if (_streamBuffer.isEmpty) return true; |
|||
|
|||
// 使用正则表达式匹配句子,包括结束符号 |
|||
// 匹配任意字符,直到遇到句子结束符号 |
|||
final sentenceRegex = RegExp( |
|||
r'([^。.!!??;;:\n\r]+[。.!!??;;::\n\r]|[^。.!!??;;:\n\r]+(?:\.{3,}|…)|[^。.!!??;;:\n\r]+["」』])' |
|||
); |
|||
|
|||
bool hasProcessed = false; |
|||
|
|||
// 查找所有完整句子 |
|||
final matches = sentenceRegex.allMatches(_streamBuffer); |
|||
final List<String> sentences = []; |
|||
int lastMatchEnd = 0; |
|||
|
|||
for (final match in matches) { |
|||
// 提取完整句子(包含结束符号) |
|||
final sentence = match.group(1)?.trim(); |
|||
if (sentence != null && sentence.isNotEmpty) { |
|||
sentences.add(sentence); |
|||
lastMatchEnd = match.end; |
|||
} |
|||
} |
|||
|
|||
// 处理找到的句子 |
|||
for (final sentence in sentences) { |
|||
_textQueue.add(_SpeechItem( |
|||
text: sentence, |
|||
rate: _rate, |
|||
pitch: _pitch, |
|||
volume: _volume, |
|||
)); |
|||
hasProcessed = true; |
|||
} |
|||
|
|||
// 更新缓冲区,只保留未完成的部分 |
|||
if (lastMatchEnd > 0) { |
|||
_streamBuffer = _streamBuffer.substring(lastMatchEnd); |
|||
} |
|||
|
|||
// 如果处理了文本并且队列未在处理中,开始处理队列 |
|||
if (hasProcessed && !_isProcessingQueue) { |
|||
_processQueue(); |
|||
} |
|||
|
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('处理流式文本失败: ${e.toString()}'); |
|||
|
|||
// 发送错误事件 |
|||
_eventController.add(TtsEvent.error(e.toString())); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> flushStream() async { |
|||
if (_textQueue.isEmpty && _streamBuffer.isEmpty) return true; |
|||
|
|||
try { |
|||
// 处理缓冲区中可能的完整句子 |
|||
await speakStream(''); |
|||
|
|||
// 如果缓冲区仍有剩余文本,将其添加到播放队列 |
|||
if (_streamBuffer.isNotEmpty && _streamBuffer.trim().isNotEmpty) { |
|||
_textQueue.add(_SpeechItem( |
|||
text: _streamBuffer, |
|||
rate: _rate, |
|||
pitch: _pitch, |
|||
volume: _volume, |
|||
)); |
|||
|
|||
// 清空缓冲区 |
|||
_streamBuffer = ''; |
|||
|
|||
// 如果队列未在处理中,开始处理队列 |
|||
if (!_isProcessingQueue) { |
|||
_processQueue(); |
|||
} |
|||
} |
|||
|
|||
Logger.info('等待TTS队列播放完成,剩余${_textQueue.length}条'); |
|||
|
|||
// 等待队列处理完成 |
|||
while (_isProcessingQueue || _isSpeaking.value) { |
|||
await Future.delayed(const Duration(milliseconds: 100)); |
|||
} |
|||
|
|||
Logger.info('TTS队列播放完成'); |
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('等待TTS队列播放完成失败: ${e.toString()}'); |
|||
|
|||
// 发送错误事件 |
|||
_eventController.add(TtsEvent.error(e.toString())); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/// 处理队列中的文本 |
|||
Future<void> _processQueue() async { |
|||
if (_isProcessingQueue) return; |
|||
|
|||
_isProcessingQueue = true; |
|||
|
|||
try { |
|||
while (_textQueue.isNotEmpty) { |
|||
if (!_isSpeaking.value) { |
|||
final item = _textQueue.removeFirst(); |
|||
|
|||
// 如果参数有变化,更新参数 |
|||
if (item.rate != _rate || item.pitch != _pitch || item.volume != _volume) { |
|||
await setSpeechParams( |
|||
rate: item.rate, |
|||
pitch: item.pitch, |
|||
volume: item.volume, |
|||
); |
|||
} |
|||
|
|||
// 播放文本 |
|||
await _flutterTts.speak(item.text); |
|||
|
|||
// 等待开始播放 |
|||
int attempts = 0; |
|||
while (!_isSpeaking.value && attempts < 10) { |
|||
await Future.delayed(const Duration(milliseconds: 50)); |
|||
attempts++; |
|||
} |
|||
|
|||
// 等待播放完成 |
|||
while (_isSpeaking.value) { |
|||
await Future.delayed(const Duration(milliseconds: 100)); |
|||
} |
|||
} else { |
|||
// 等待一小段时间再检查队列 |
|||
await Future.delayed(const Duration(milliseconds: 100)); |
|||
} |
|||
} |
|||
} catch (e) { |
|||
Logger.error('TTS处理队列失败: ${e.toString()}'); |
|||
|
|||
// 发送错误事件 |
|||
_eventController.add(TtsEvent.error(e.toString())); |
|||
} finally { |
|||
_isProcessingQueue = false; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> stop() async { |
|||
try { |
|||
Logger.info('尝试停止TTS播放...'); |
|||
|
|||
// 清空队列 |
|||
_textQueue.clear(); |
|||
_streamBuffer = ''; |
|||
|
|||
// 如果正在播放,停止播放 |
|||
if (_isSpeaking.value) { |
|||
// 先重置状态标志,避免处理队列循环继续等待 |
|||
_isProcessingQueue = false; |
|||
|
|||
// 执行停止 |
|||
final result = await _flutterTts.stop(); |
|||
Logger.info('TTS停止结果: $result'); |
|||
|
|||
// 短暂延迟等待状态更新 |
|||
await Future.delayed(const Duration(milliseconds: 100)); |
|||
|
|||
// 强制更新状态 |
|||
_isSpeaking.value = false; |
|||
|
|||
// 发送取消事件 |
|||
_eventController.add(TtsEvent(type: TtsEventType.canceled)); |
|||
} |
|||
|
|||
Logger.info('TTS停止播放完成'); |
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('TTS停止播放失败: ${e.toString()}'); |
|||
|
|||
// 即使出错也尝试强制重置状态 |
|||
_isSpeaking.value = false; |
|||
_isProcessingQueue = false; |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<void> dispose() async { |
|||
try { |
|||
await stop(); |
|||
_textQueue.clear(); |
|||
|
|||
// 关闭事件流控制器 |
|||
await _eventController.close(); |
|||
|
|||
Logger.info('TTS资源已释放'); |
|||
} catch (e) { |
|||
Logger.error('释放TTS资源失败: ${e.toString()}'); |
|||
} |
|||
} |
|||
|
|||
/// 处理AI返回的文本流,将其转换为语音 |
|||
/// |
|||
/// [textStream] 文本流 |
|||
/// 返回一个Future,当流处理完成时完成 |
|||
Future<bool> processAiTextStream(Stream<String> textStream) async { |
|||
try { |
|||
await for (final text in textStream) { |
|||
if (text.isEmpty) continue; |
|||
await speakStream(text); |
|||
} |
|||
|
|||
// 处理完流后,刷新缓冲区中的剩余文本 |
|||
await flushStream(); |
|||
return true; |
|||
} catch (e) { |
|||
Logger.error('处理文本流时出错: ${e.toString()}'); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// 语音合成项目 |
|||
class _SpeechItem { |
|||
final String text; |
|||
final double rate; |
|||
final double pitch; |
|||
final double volume; |
|||
|
|||
_SpeechItem({ |
|||
required this.text, |
|||
this.rate = 0.5, |
|||
this.pitch = 1.0, |
|||
this.volume = 1.0, |
|||
}); |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
**/dgph |
|||
*.mode1v3 |
|||
*.mode2v3 |
|||
*.moved-aside |
|||
*.pbxuser |
|||
*.perspectivev3 |
|||
**/*sync/ |
|||
.sconsign.dblite |
|||
.tags* |
|||
**/.vagrant/ |
|||
**/DerivedData/ |
|||
Icon? |
|||
**/Pods/ |
|||
**/.symlinks/ |
|||
profile |
|||
xcuserdata |
|||
**/.generated/ |
|||
Flutter/App.framework |
|||
Flutter/Flutter.framework |
|||
Flutter/Flutter.podspec |
|||
Flutter/Generated.xcconfig |
|||
Flutter/ephemeral/ |
|||
Flutter/app.flx |
|||
Flutter/app.zip |
|||
Flutter/flutter_assets/ |
|||
Flutter/flutter_export_environment.sh |
|||
ServiceDefinitions.json |
|||
Runner/GeneratedPluginRegistrant.* |
|||
|
|||
# Exceptions to above rules. |
|||
!default.mode1v3 |
|||
!default.mode2v3 |
|||
!default.pbxuser |
|||
!default.perspectivev3 |
|||
@ -0,0 +1,4 @@ |
|||
#import <Flutter/Flutter.h> |
|||
|
|||
@interface AzureSpeechPlugin : NSObject<FlutterPlugin> |
|||
@end |
|||
@ -0,0 +1,12 @@ |
|||
#import "AzureSpeechPlugin.h" |
|||
#if __has_include(<azure_speech/azure_speech-Swift.h>) |
|||
#import <azure_speech/azure_speech-Swift.h> |
|||
#else |
|||
#import "azure_speech-Swift.h" |
|||
#endif |
|||
|
|||
@implementation AzureSpeechPlugin |
|||
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { |
|||
[SwiftAzureSpeechPlugin registerWithRegistrar:registrar]; |
|||
} |
|||
@end |
|||
@ -1,182 +1,388 @@ |
|||
import Flutter |
|||
import UIKit |
|||
import AVFoundation |
|||
import MicrosoftCognitiveServicesSpeech |
|||
// 自定义语音处理组件,提供音频流处理等功能 |
|||
import speech |
|||
|
|||
public class AzureSpeechPlugin: NSObject, FlutterPlugin { |
|||
private var ttsHelper: AzureTtsHelper? |
|||
private var asrHelper: AzureAsrHelper? |
|||
private var eventSink: FlutterEventSink? |
|||
|
|||
public static func register(with registrar: FlutterPluginRegistrar) { |
|||
let channel = FlutterMethodChannel(name: "azure_speech", binaryMessenger: registrar.messenger()) |
|||
let instance = AzureSpeechPlugin() |
|||
registrar.addMethodCallDelegate(instance, channel: channel) |
|||
/** |
|||
* Azure Speech Plugin |
|||
* |
|||
* 基于微软Azure语音服务的Flutter插件 |
|||
* 提供语音识别(ASR)和语音合成(TTS)功能 |
|||
*/ |
|||
public class SwiftAzureSpeechPlugin: NSObject, FlutterPlugin { |
|||
// 日志标签 |
|||
private let tag = "AzureSpeechPlugin" |
|||
|
|||
// 初始化事件通道 |
|||
let eventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger()) |
|||
eventChannel.setStreamHandler(AsrStreamHandler(instance: instance)) |
|||
} |
|||
|
|||
override init() { |
|||
super.init() |
|||
ttsHelper = AzureTtsHelper() |
|||
asrHelper = AzureAsrHelper() |
|||
// ASR相关 |
|||
private var asrChannel: FlutterMethodChannel? |
|||
private var asrEventChannel: FlutterEventChannel? |
|||
private var asrEventSink: FlutterEventSink? |
|||
private let azureAsrHelper = AzureAsrHelper() |
|||
|
|||
// 设置ASR事件处理 |
|||
asrHelper?.setEventHandler { [weak self] event in |
|||
self?.handleAsrEvent(event) |
|||
// TTS相关 |
|||
private var ttsChannel: FlutterMethodChannel? |
|||
private var ttsEventChannel: FlutterEventChannel? |
|||
private var ttsEventSink: FlutterEventSink? |
|||
private let azureTtsHelper = AzureTtsHelper() |
|||
|
|||
// 是否已添加TTS事件监听器 |
|||
private var isTtsListenerAdded = false |
|||
|
|||
// 插件注册 |
|||
public static func register(with registrar: FlutterPluginRegistrar) { |
|||
let instance = SwiftAzureSpeechPlugin() |
|||
|
|||
// 初始化ASR通道 |
|||
let asrChannel = FlutterMethodChannel(name: "azure_speech/asr", binaryMessenger: registrar.messenger()) |
|||
registrar.addMethodCallDelegate(instance, channel: asrChannel) |
|||
instance.asrChannel = asrChannel |
|||
|
|||
// 初始化TTS通道 |
|||
let ttsChannel = FlutterMethodChannel(name: "azure_speech/tts", binaryMessenger: registrar.messenger()) |
|||
registrar.addMethodCallDelegate(instance, channel: ttsChannel) |
|||
instance.ttsChannel = ttsChannel |
|||
|
|||
// 初始化ASR事件通道 |
|||
let asrEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger()) |
|||
asrEventChannel.setStreamHandler(instance) |
|||
instance.asrEventChannel = asrEventChannel |
|||
|
|||
// 初始化TTS事件通道 |
|||
let ttsEventChannel = FlutterEventChannel(name: "azure_speech/tts_events", binaryMessenger: registrar.messenger()) |
|||
ttsEventChannel.setStreamHandler(instance) |
|||
instance.ttsEventChannel = ttsEventChannel |
|||
} |
|||
|
|||
// 发送ASR事件方法 |
|||
private func sendAsrEvent(_ event: [String: Any]) { |
|||
if asrEventSink == nil { |
|||
print("\(tag): 无法发送ASR事件:事件通道未准备好") |
|||
return |
|||
} |
|||
|
|||
DispatchQueue.main.async { [weak self] in |
|||
guard let self = self else { return } |
|||
self.asrEventSink?(event) |
|||
} |
|||
} |
|||
|
|||
// 发送TTS事件方法 |
|||
private func sendTtsEvent(_ event: [String: Any]) { |
|||
if ttsEventSink == nil { |
|||
print("\(tag): 无法发送TTS事件:事件通道未准备好") |
|||
return |
|||
} |
|||
|
|||
DispatchQueue.main.async { [weak self] in |
|||
guard let self = self else { return } |
|||
self.ttsEventSink?(event) |
|||
} |
|||
} |
|||
|
|||
// 设置TTS事件监听器 |
|||
private func setupTtsEventListener() { |
|||
if !isTtsListenerAdded { |
|||
azureTtsHelper.addListener(self) |
|||
isTtsListenerAdded = true |
|||
} |
|||
} |
|||
|
|||
// 设置ASR事件监听器 |
|||
private func setupAsrEventListener() { |
|||
azureAsrHelper.addListener(self) |
|||
} |
|||
|
|||
// 处理Flutter方法调用 |
|||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
|||
if call.method.hasPrefix("tts_") { |
|||
handleTtsMethodCall(call, result) |
|||
} else { |
|||
handleAsrMethodCall(call, result) |
|||
} |
|||
} |
|||
|
|||
// MARK: - ASR 方法处理 |
|||
|
|||
private func handleAsrMethodCall(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { |
|||
switch call.method { |
|||
case "initialize": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let subscriptionKey = args["subscriptionKey"] as? String, |
|||
let region = args["region"] as? String else { |
|||
result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil)) |
|||
return |
|||
} |
|||
|
|||
let supportedLanguages = args["supportedLanguages"] as? [String] |
|||
|
|||
// 初始化ASR引擎并设置事件监听 |
|||
let success = azureAsrHelper.initialize(subscriptionKey: subscriptionKey, region: region, supportedLanguages: supportedLanguages) |
|||
setupAsrEventListener() |
|||
|
|||
result(success) |
|||
|
|||
case "recognizeOnce": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let audioBytes = args["data"] as? FlutterStandardTypedData else { |
|||
result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil)) |
|||
return |
|||
} |
|||
|
|||
// 执行一次性识别 |
|||
let success = azureAsrHelper.recognizeOnce(audioData: audioBytes.data) { recognizedText in |
|||
result(["text": recognizedText, "detectedLanguage": ""]) |
|||
} |
|||
|
|||
if !success { |
|||
result(FlutterError(code: "RECOGNITION_ERROR", message: "启动识别失败", details: nil)) |
|||
} |
|||
|
|||
case "startContinuousRecognition": |
|||
// 确保事件通道已准备好 |
|||
if asrEventSink == nil { |
|||
result(FlutterError(code: "EVENT_CHANNEL_NOT_READY", message: "事件通道未准备好,无法开始连续识别", details: nil)) |
|||
return |
|||
} |
|||
|
|||
let success = azureAsrHelper.startContinuousRecognition() |
|||
result(success) |
|||
|
|||
case "stopContinuousRecognition": |
|||
let success = azureAsrHelper.stopContinuousRecognition() |
|||
result(success) |
|||
|
|||
case "isContinuousRecognitionActive": |
|||
result(azureAsrHelper.isRecognitionActive()) |
|||
|
|||
case "dispose": |
|||
azureAsrHelper.dispose() |
|||
result(true) |
|||
|
|||
case "pushAudioData": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let audioBytes = args["data"] as? FlutterStandardTypedData else { |
|||
result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil)) |
|||
return |
|||
} |
|||
|
|||
let success = azureAsrHelper.pushAudioData(audioData: audioBytes.data) |
|||
result(success) |
|||
|
|||
default: |
|||
result(FlutterMethodNotImplemented) |
|||
} |
|||
} |
|||
|
|||
// MARK: - TTS 方法处理 |
|||
|
|||
private func handleTtsMethodCall(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { |
|||
switch call.method { |
|||
case "tts_initialize": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let subscriptionKey = args["subscriptionKey"] as? String, |
|||
let region = args["region"] as? String else { |
|||
result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil)) |
|||
return |
|||
} |
|||
|
|||
// 初始化TTS引擎 |
|||
let language = args["language"] as? String ?? "zh-CN" |
|||
let success = azureTtsHelper.initialize(ttsAppId: "", ttsAppToken: subscriptionKey, ttsResource: region, language: language) |
|||
|
|||
// 设置TTS事件监听器 |
|||
setupTtsEventListener() |
|||
|
|||
result(success) |
|||
|
|||
case "tts_set_voice": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let voiceName = args["voiceName"] as? String else { |
|||
result(FlutterError(code: "INVALID_ARGUMENTS", message: "语音名称不能为空", details: nil)) |
|||
return |
|||
} |
|||
|
|||
let success = azureTtsHelper.setVoice(voiceName) |
|||
result(success) |
|||
|
|||
case "tts_speak_once": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let text = args["text"] as? String else { |
|||
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil)) |
|||
return |
|||
} |
|||
|
|||
let success = azureTtsHelper.speakOnce(text) |
|||
result(success) |
|||
|
|||
case "tts_speak_stream": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let text = args["text"] as? String else { |
|||
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil)) |
|||
return |
|||
} |
|||
|
|||
let success = azureTtsHelper.speakStream(text) |
|||
result(success) |
|||
|
|||
case "tts_flush_stream": |
|||
let success = azureTtsHelper.flushStream() |
|||
result(success) |
|||
|
|||
case "tts_stop": |
|||
let success = azureTtsHelper.stop() |
|||
result(success) |
|||
|
|||
case "tts_isSpeaking": |
|||
result(azureTtsHelper.isSpeaking()) |
|||
|
|||
case "tts_release": |
|||
azureTtsHelper.dispose() |
|||
result(true) |
|||
|
|||
default: |
|||
result(FlutterMethodNotImplemented) |
|||
} |
|||
} |
|||
} |
|||
|
|||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
|||
switch call.method { |
|||
// TTS相关方法 |
|||
case "initializeTts": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let subscriptionKey = args["subscriptionKey"] as? String, |
|||
let serviceRegion = args["serviceRegion"] as? String else { |
|||
result(false) |
|||
return |
|||
} |
|||
|
|||
let language = args["language"] as? String ?? "zh-CN" |
|||
let success = ttsHelper?.initialize(speechSubscriptionKey: subscriptionKey, serviceRegion: serviceRegion, language: language) ?? false |
|||
result(success) |
|||
|
|||
case "setTtsVoice": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let voiceName = args["voiceName"] as? String else { |
|||
result(false) |
|||
return |
|||
} |
|||
|
|||
let success = ttsHelper?.setVoice(voiceName: voiceName) ?? false |
|||
result(success) |
|||
|
|||
case "setTtsSpeechParams": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let rate = args["rate"] as? Int, |
|||
let pitch = args["pitch"] as? Int, |
|||
let volume = args["volume"] as? Int else { |
|||
result(false) |
|||
return |
|||
} |
|||
|
|||
let success = ttsHelper?.setSpeechParams(rate: rate, pitch: pitch, volume: volume) ?? false |
|||
result(success) |
|||
|
|||
case "speakText": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let text = args["text"] as? String else { |
|||
result(false) |
|||
return |
|||
} |
|||
|
|||
ttsHelper?.speakText(text: text) { success, _ in |
|||
result(success) |
|||
} |
|||
|
|||
case "stopSpeaking": |
|||
let success = ttsHelper?.stopSpeaking() ?? false |
|||
result(success) |
|||
|
|||
case "isSpeaking": |
|||
let speaking = ttsHelper?.isSpeaking() ?? false |
|||
result(speaking) |
|||
|
|||
case "setTtsAudioOutputType": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let outputType = args["outputType"] as? String else { |
|||
result(false) |
|||
return |
|||
} |
|||
|
|||
var type: AzureTtsHelper.AudioOutputType = .auto |
|||
switch outputType.uppercased() { |
|||
case "SPEAKER": |
|||
type = .speaker |
|||
case "EARPIECE": |
|||
type = .earpiece |
|||
default: |
|||
type = .auto |
|||
} |
|||
|
|||
let success = ttsHelper?.setAudioOutputType(outputType: type) ?? false |
|||
result(success) |
|||
|
|||
// ASR相关方法 |
|||
case "initializeAsr": |
|||
guard let args = call.arguments as? [String: Any], |
|||
let subscriptionKey = args["subscriptionKey"] as? String, |
|||
let serviceRegion = args["serviceRegion"] as? String, |
|||
let supportedLanguages = args["supportedLanguages"] as? [String] else { |
|||
result(false) |
|||
return |
|||
} |
|||
|
|||
let success = asrHelper?.initialize(speechSubscriptionKey: subscriptionKey, serviceRegion: serviceRegion, supportedLanguages: supportedLanguages) ?? false |
|||
result(success) |
|||
|
|||
case "recognizeOnce": |
|||
asrHelper?.recognizeOnce { success, text, language, error in |
|||
var resultMap: [String: Any] = ["success": success] |
|||
if success { |
|||
resultMap["text"] = text |
|||
resultMap["language"] = language |
|||
} |
|||
|
|||
// MARK: - 事件处理 |
|||
extension SwiftAzureSpeechPlugin: FlutterStreamHandler { |
|||
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { |
|||
// 根据通道类型设置事件接收器 |
|||
if let args = arguments as? [String: Any], |
|||
let channel = args["channel"] as? String { |
|||
|
|||
if channel == "asr" { |
|||
asrEventSink = events |
|||
} else if channel == "tts" { |
|||
ttsEventSink = events |
|||
setupTtsEventListener() |
|||
} |
|||
} else if arguments == nil { |
|||
// 如果没有指定通道,尝试弄清楚是哪个通道在监听 |
|||
if asrEventSink == nil && ttsEventSink != nil { |
|||
asrEventSink = events |
|||
} else if asrEventSink != nil && ttsEventSink == nil { |
|||
ttsEventSink = events |
|||
setupTtsEventListener() |
|||
} else { |
|||
// 无法确定哪个通道,默认设置为ASR |
|||
asrEventSink = events |
|||
} |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
public func onCancel(withArguments arguments: Any?) -> FlutterError? { |
|||
// 根据通道类型清除事件接收器 |
|||
if let args = arguments as? [String: Any], |
|||
let channel = args["channel"] as? String { |
|||
|
|||
if channel == "asr" { |
|||
asrEventSink = nil |
|||
} else if channel == "tts" { |
|||
ttsEventSink = nil |
|||
} |
|||
} else { |
|||
resultMap["error"] = error |
|||
// 如果没有指定通道,清除所有通道 |
|||
asrEventSink = nil |
|||
ttsEventSink = nil |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
} |
|||
|
|||
// MARK: - ASR 事件监听实现 |
|||
extension SwiftAzureSpeechPlugin: AsrEventListener { |
|||
// 为ASR事件监听提供明确的选择器名称 |
|||
@objc(onAsrEvent:) |
|||
public func onEvent(_ event: AsrEvent) { |
|||
var eventMap: [String: Any] = [ |
|||
"type": getAsrEventTypeName(event.type) |
|||
] |
|||
|
|||
// 添加参数 |
|||
for (key, value) in event.params { |
|||
eventMap[key] = value |
|||
} |
|||
|
|||
sendAsrEvent(eventMap) |
|||
} |
|||
|
|||
private func getAsrEventTypeName(_ type: AsrEventType) -> String { |
|||
switch type { |
|||
case .sessionStarted: |
|||
return "sessionStarted" |
|||
case .sessionStopped: |
|||
return "sessionStopped" |
|||
case .recognizing: |
|||
return "recognizing" |
|||
case .recognized: |
|||
return "result" |
|||
case .canceled: |
|||
return "canceled" |
|||
case .error: |
|||
return "error" |
|||
@unknown default: |
|||
return "unknown" |
|||
} |
|||
result(resultMap) |
|||
} |
|||
|
|||
case "startContinuousRecognition": |
|||
let success = asrHelper?.startContinuousRecognition() ?? false |
|||
result(success) |
|||
|
|||
case "stopContinuousRecognition": |
|||
let success = asrHelper?.stopContinuousRecognition() ?? false |
|||
result(success) |
|||
|
|||
case "isContinuousRecognitionActive": |
|||
let isActive = asrHelper?.isContinuousRecognitionActive() ?? false |
|||
result(isActive) |
|||
|
|||
case "dispose": |
|||
ttsHelper?.dispose() |
|||
asrHelper?.dispose() |
|||
result(nil) |
|||
|
|||
default: |
|||
result(FlutterMethodNotImplemented) |
|||
} |
|||
} |
|||
|
|||
// 设置事件接收器 |
|||
func setEventSink(_ sink: FlutterEventSink?) { |
|||
self.eventSink = sink |
|||
} |
|||
|
|||
// 处理ASR事件 |
|||
private func handleAsrEvent(_ event: [String: Any]) { |
|||
self.eventSink?(event) |
|||
} |
|||
} |
|||
|
|||
// ASR事件流处理器 |
|||
class AsrStreamHandler: NSObject, FlutterStreamHandler { |
|||
private weak var plugin: AzureSpeechPlugin? |
|||
|
|||
init(instance: AzureSpeechPlugin) { |
|||
self.plugin = instance |
|||
super.init() |
|||
} |
|||
|
|||
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { |
|||
plugin?.setEventSink(events) |
|||
return nil |
|||
} |
|||
|
|||
func onCancel(withArguments arguments: Any?) -> FlutterError? { |
|||
plugin?.setEventSink(nil) |
|||
return nil |
|||
} |
|||
// MARK: - TTS 事件监听实现 |
|||
extension SwiftAzureSpeechPlugin: TtsEventListener { |
|||
// 使用@objc特性为方法提供一个不同的Objective-C选择器名称 |
|||
@objc(onTtsEvent:) |
|||
public func onEvent(_ event: TtsEvent) { |
|||
var eventMap: [String: Any] = [:] |
|||
|
|||
// 根据事件类型转换 |
|||
switch event.type { |
|||
case .synthesisStarted: |
|||
eventMap["type"] = "synthesis_started" |
|||
case .synthesisCompleted: |
|||
eventMap["type"] = "synthesis_completed" |
|||
case .synthesisCanceled: |
|||
eventMap["type"] = "synthesis_canceled" |
|||
if let reason = event.params["reason"] { |
|||
eventMap["reason"] = reason |
|||
} |
|||
if let errorDetails = event.params["errorDetails"] { |
|||
eventMap["errorDetails"] = errorDetails |
|||
} |
|||
case .error: |
|||
eventMap["type"] = "error" |
|||
if let errorCode = event.params["errorCode"] { |
|||
eventMap["errorCode"] = errorCode |
|||
} |
|||
if let errorMessage = event.params["errorMessage"] { |
|||
eventMap["errorMessage"] = errorMessage |
|||
} |
|||
@unknown default: |
|||
eventMap["type"] = "unknown" |
|||
for (key, value) in event.params { |
|||
eventMap[key] = value |
|||
} |
|||
} |
|||
|
|||
sendTtsEvent(eventMap) |
|||
} |
|||
} |
|||
|
|||
// MARK: - 音频数据监听实现 |
|||
extension SwiftAzureSpeechPlugin: AudioDataListener { |
|||
public func onAudioData(_ data: Data) { |
|||
if ttsEventSink == nil { return } |
|||
|
|||
let eventMap: [String: Any] = [ |
|||
"type": "audio_data", |
|||
"data": FlutterStandardTypedData(bytes: data) |
|||
] |
|||
|
|||
sendTtsEvent(eventMap) |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
#ifndef azure_speech_Bridging_Header_h |
|||
#define azure_speech_Bridging_Header_h |
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
#endif /* azure_speech_Bridging_Header_h */ |
|||
@ -0,0 +1,5 @@ |
|||
#import <Foundation/Foundation.h> |
|||
|
|||
#import "AzureSpeechPlugin.h" |
|||
|
|||
// 添加其他公共头文件导出
|
|||
@ -1,26 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>CFBundleDevelopmentRegion</key> |
|||
<string>en</string> |
|||
<key>CFBundleExecutable</key> |
|||
<string>App</string> |
|||
<key>CFBundleIdentifier</key> |
|||
<string>io.flutter.flutter.app</string> |
|||
<key>CFBundleInfoDictionaryVersion</key> |
|||
<string>6.0</string> |
|||
<key>CFBundleName</key> |
|||
<string>App</string> |
|||
<key>CFBundlePackageType</key> |
|||
<string>FMWK</string> |
|||
<key>CFBundleShortVersionString</key> |
|||
<string>1.0</string> |
|||
<key>CFBundleSignature</key> |
|||
<string>????</string> |
|||
<key>CFBundleVersion</key> |
|||
<string>1.0</string> |
|||
<key>MinimumOSVersion</key> |
|||
<string>12.0</string> |
|||
</dict> |
|||
</plist> |
|||
@ -1 +0,0 @@ |
|||
#include "Generated.xcconfig" |
|||
@ -1 +0,0 @@ |
|||
#include "Generated.xcconfig" |
|||
@ -1,619 +0,0 @@ |
|||
// !$*UTF8*$! |
|||
{ |
|||
archiveVersion = 1; |
|||
classes = { |
|||
}; |
|||
objectVersion = 54; |
|||
objects = { |
|||
|
|||
/* Begin PBXBuildFile section */ |
|||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; |
|||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; |
|||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; |
|||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; |
|||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; |
|||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; |
|||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; |
|||
/* End PBXBuildFile section */ |
|||
|
|||
/* Begin PBXContainerItemProxy section */ |
|||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { |
|||
isa = PBXContainerItemProxy; |
|||
containerPortal = 97C146E61CF9000F007C117D /* Project object */; |
|||
proxyType = 1; |
|||
remoteGlobalIDString = 97C146ED1CF9000F007C117D; |
|||
remoteInfo = Runner; |
|||
}; |
|||
/* End PBXContainerItemProxy section */ |
|||
|
|||
/* Begin PBXCopyFilesBuildPhase section */ |
|||
9705A1C41CF9048500538489 /* Embed Frameworks */ = { |
|||
isa = PBXCopyFilesBuildPhase; |
|||
buildActionMask = 2147483647; |
|||
dstPath = ""; |
|||
dstSubfolderSpec = 10; |
|||
files = ( |
|||
); |
|||
name = "Embed Frameworks"; |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
}; |
|||
/* End PBXCopyFilesBuildPhase section */ |
|||
|
|||
/* Begin PBXFileReference section */ |
|||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; |
|||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; |
|||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; |
|||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; |
|||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; |
|||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; |
|||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; |
|||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; |
|||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; |
|||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; |
|||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; |
|||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; |
|||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; |
|||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; |
|||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; |
|||
/* End PBXFileReference section */ |
|||
|
|||
/* Begin PBXFrameworksBuildPhase section */ |
|||
97C146EB1CF9000F007C117D /* Frameworks */ = { |
|||
isa = PBXFrameworksBuildPhase; |
|||
buildActionMask = 2147483647; |
|||
files = ( |
|||
); |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
}; |
|||
/* End PBXFrameworksBuildPhase section */ |
|||
|
|||
/* Begin PBXGroup section */ |
|||
331C8082294A63A400263BE5 /* RunnerTests */ = { |
|||
isa = PBXGroup; |
|||
children = ( |
|||
331C807B294A618700263BE5 /* RunnerTests.swift */, |
|||
); |
|||
path = RunnerTests; |
|||
sourceTree = "<group>"; |
|||
}; |
|||
9740EEB11CF90186004384FC /* Flutter */ = { |
|||
isa = PBXGroup; |
|||
children = ( |
|||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, |
|||
9740EEB21CF90195004384FC /* Debug.xcconfig */, |
|||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */, |
|||
9740EEB31CF90195004384FC /* Generated.xcconfig */, |
|||
); |
|||
name = Flutter; |
|||
sourceTree = "<group>"; |
|||
}; |
|||
97C146E51CF9000F007C117D = { |
|||
isa = PBXGroup; |
|||
children = ( |
|||
9740EEB11CF90186004384FC /* Flutter */, |
|||
97C146F01CF9000F007C117D /* Runner */, |
|||
97C146EF1CF9000F007C117D /* Products */, |
|||
331C8082294A63A400263BE5 /* RunnerTests */, |
|||
); |
|||
sourceTree = "<group>"; |
|||
}; |
|||
97C146EF1CF9000F007C117D /* Products */ = { |
|||
isa = PBXGroup; |
|||
children = ( |
|||
97C146EE1CF9000F007C117D /* Runner.app */, |
|||
331C8081294A63A400263BE5 /* RunnerTests.xctest */, |
|||
); |
|||
name = Products; |
|||
sourceTree = "<group>"; |
|||
}; |
|||
97C146F01CF9000F007C117D /* Runner */ = { |
|||
isa = PBXGroup; |
|||
children = ( |
|||
97C146FA1CF9000F007C117D /* Main.storyboard */, |
|||
97C146FD1CF9000F007C117D /* Assets.xcassets */, |
|||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, |
|||
97C147021CF9000F007C117D /* Info.plist */, |
|||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, |
|||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, |
|||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */, |
|||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, |
|||
); |
|||
path = Runner; |
|||
sourceTree = "<group>"; |
|||
}; |
|||
/* End PBXGroup section */ |
|||
|
|||
/* Begin PBXNativeTarget section */ |
|||
331C8080294A63A400263BE5 /* RunnerTests */ = { |
|||
isa = PBXNativeTarget; |
|||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; |
|||
buildPhases = ( |
|||
331C807D294A63A400263BE5 /* Sources */, |
|||
331C807F294A63A400263BE5 /* Resources */, |
|||
); |
|||
buildRules = ( |
|||
); |
|||
dependencies = ( |
|||
331C8086294A63A400263BE5 /* PBXTargetDependency */, |
|||
); |
|||
name = RunnerTests; |
|||
productName = RunnerTests; |
|||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; |
|||
productType = "com.apple.product-type.bundle.unit-test"; |
|||
}; |
|||
97C146ED1CF9000F007C117D /* Runner */ = { |
|||
isa = PBXNativeTarget; |
|||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; |
|||
buildPhases = ( |
|||
9740EEB61CF901F6004384FC /* Run Script */, |
|||
97C146EA1CF9000F007C117D /* Sources */, |
|||
97C146EB1CF9000F007C117D /* Frameworks */, |
|||
97C146EC1CF9000F007C117D /* Resources */, |
|||
9705A1C41CF9048500538489 /* Embed Frameworks */, |
|||
3B06AD1E1E4923F5004D2608 /* Thin Binary */, |
|||
); |
|||
buildRules = ( |
|||
); |
|||
dependencies = ( |
|||
); |
|||
name = Runner; |
|||
productName = Runner; |
|||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */; |
|||
productType = "com.apple.product-type.application"; |
|||
}; |
|||
/* End PBXNativeTarget section */ |
|||
|
|||
/* Begin PBXProject section */ |
|||
97C146E61CF9000F007C117D /* Project object */ = { |
|||
isa = PBXProject; |
|||
attributes = { |
|||
BuildIndependentTargetsInParallel = YES; |
|||
LastUpgradeCheck = 1510; |
|||
ORGANIZATIONNAME = ""; |
|||
TargetAttributes = { |
|||
331C8080294A63A400263BE5 = { |
|||
CreatedOnToolsVersion = 14.0; |
|||
TestTargetID = 97C146ED1CF9000F007C117D; |
|||
}; |
|||
97C146ED1CF9000F007C117D = { |
|||
CreatedOnToolsVersion = 7.3.1; |
|||
LastSwiftMigration = 1100; |
|||
}; |
|||
}; |
|||
}; |
|||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; |
|||
compatibilityVersion = "Xcode 9.3"; |
|||
developmentRegion = en; |
|||
hasScannedForEncodings = 0; |
|||
knownRegions = ( |
|||
en, |
|||
Base, |
|||
); |
|||
mainGroup = 97C146E51CF9000F007C117D; |
|||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */; |
|||
projectDirPath = ""; |
|||
projectRoot = ""; |
|||
targets = ( |
|||
97C146ED1CF9000F007C117D /* Runner */, |
|||
331C8080294A63A400263BE5 /* RunnerTests */, |
|||
); |
|||
}; |
|||
/* End PBXProject section */ |
|||
|
|||
/* Begin PBXResourcesBuildPhase section */ |
|||
331C807F294A63A400263BE5 /* Resources */ = { |
|||
isa = PBXResourcesBuildPhase; |
|||
buildActionMask = 2147483647; |
|||
files = ( |
|||
); |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
}; |
|||
97C146EC1CF9000F007C117D /* Resources */ = { |
|||
isa = PBXResourcesBuildPhase; |
|||
buildActionMask = 2147483647; |
|||
files = ( |
|||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, |
|||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, |
|||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, |
|||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, |
|||
); |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
}; |
|||
/* End PBXResourcesBuildPhase section */ |
|||
|
|||
/* Begin PBXShellScriptBuildPhase section */ |
|||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { |
|||
isa = PBXShellScriptBuildPhase; |
|||
alwaysOutOfDate = 1; |
|||
buildActionMask = 2147483647; |
|||
files = ( |
|||
); |
|||
inputPaths = ( |
|||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", |
|||
); |
|||
name = "Thin Binary"; |
|||
outputPaths = ( |
|||
); |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
shellPath = /bin/sh; |
|||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; |
|||
}; |
|||
9740EEB61CF901F6004384FC /* Run Script */ = { |
|||
isa = PBXShellScriptBuildPhase; |
|||
alwaysOutOfDate = 1; |
|||
buildActionMask = 2147483647; |
|||
files = ( |
|||
); |
|||
inputPaths = ( |
|||
); |
|||
name = "Run Script"; |
|||
outputPaths = ( |
|||
); |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
shellPath = /bin/sh; |
|||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; |
|||
}; |
|||
/* End PBXShellScriptBuildPhase section */ |
|||
|
|||
/* Begin PBXSourcesBuildPhase section */ |
|||
331C807D294A63A400263BE5 /* Sources */ = { |
|||
isa = PBXSourcesBuildPhase; |
|||
buildActionMask = 2147483647; |
|||
files = ( |
|||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, |
|||
); |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
}; |
|||
97C146EA1CF9000F007C117D /* Sources */ = { |
|||
isa = PBXSourcesBuildPhase; |
|||
buildActionMask = 2147483647; |
|||
files = ( |
|||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, |
|||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, |
|||
); |
|||
runOnlyForDeploymentPostprocessing = 0; |
|||
}; |
|||
/* End PBXSourcesBuildPhase section */ |
|||
|
|||
/* Begin PBXTargetDependency section */ |
|||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = { |
|||
isa = PBXTargetDependency; |
|||
target = 97C146ED1CF9000F007C117D /* Runner */; |
|||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; |
|||
}; |
|||
/* End PBXTargetDependency section */ |
|||
|
|||
/* Begin PBXVariantGroup section */ |
|||
97C146FA1CF9000F007C117D /* Main.storyboard */ = { |
|||
isa = PBXVariantGroup; |
|||
children = ( |
|||
97C146FB1CF9000F007C117D /* Base */, |
|||
); |
|||
name = Main.storyboard; |
|||
sourceTree = "<group>"; |
|||
}; |
|||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { |
|||
isa = PBXVariantGroup; |
|||
children = ( |
|||
97C147001CF9000F007C117D /* Base */, |
|||
); |
|||
name = LaunchScreen.storyboard; |
|||
sourceTree = "<group>"; |
|||
}; |
|||
/* End PBXVariantGroup section */ |
|||
|
|||
/* Begin XCBuildConfiguration section */ |
|||
249021D3217E4FDB00AE95B9 /* Profile */ = { |
|||
isa = XCBuildConfiguration; |
|||
buildSettings = { |
|||
ALWAYS_SEARCH_USER_PATHS = NO; |
|||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; |
|||
CLANG_ANALYZER_NONNULL = YES; |
|||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; |
|||
CLANG_CXX_LIBRARY = "libc++"; |
|||
CLANG_ENABLE_MODULES = YES; |
|||
CLANG_ENABLE_OBJC_ARC = YES; |
|||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; |
|||
CLANG_WARN_BOOL_CONVERSION = YES; |
|||
CLANG_WARN_COMMA = YES; |
|||
CLANG_WARN_CONSTANT_CONVERSION = YES; |
|||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; |
|||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; |
|||
CLANG_WARN_EMPTY_BODY = YES; |
|||
CLANG_WARN_ENUM_CONVERSION = YES; |
|||
CLANG_WARN_INFINITE_RECURSION = YES; |
|||
CLANG_WARN_INT_CONVERSION = YES; |
|||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; |
|||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; |
|||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; |
|||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; |
|||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; |
|||
CLANG_WARN_STRICT_PROTOTYPES = YES; |
|||
CLANG_WARN_SUSPICIOUS_MOVE = YES; |
|||
CLANG_WARN_UNREACHABLE_CODE = YES; |
|||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; |
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; |
|||
COPY_PHASE_STRIP = NO; |
|||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; |
|||
ENABLE_NS_ASSERTIONS = NO; |
|||
ENABLE_STRICT_OBJC_MSGSEND = YES; |
|||
ENABLE_USER_SCRIPT_SANDBOXING = NO; |
|||
GCC_C_LANGUAGE_STANDARD = gnu99; |
|||
GCC_NO_COMMON_BLOCKS = YES; |
|||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; |
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; |
|||
GCC_WARN_UNDECLARED_SELECTOR = YES; |
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
|||
GCC_WARN_UNUSED_FUNCTION = YES; |
|||
GCC_WARN_UNUSED_VARIABLE = YES; |
|||
IPHONEOS_DEPLOYMENT_TARGET = 12.0; |
|||
MTL_ENABLE_DEBUG_INFO = NO; |
|||
SDKROOT = iphoneos; |
|||
SUPPORTED_PLATFORMS = iphoneos; |
|||
TARGETED_DEVICE_FAMILY = "1,2"; |
|||
VALIDATE_PRODUCT = YES; |
|||
}; |
|||
name = Profile; |
|||
}; |
|||
249021D4217E4FDB00AE95B9 /* Profile */ = { |
|||
isa = XCBuildConfiguration; |
|||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; |
|||
buildSettings = { |
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
|||
CLANG_ENABLE_MODULES = YES; |
|||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; |
|||
DEVELOPMENT_TEAM = PNNU77KTCP; |
|||
ENABLE_BITCODE = NO; |
|||
INFOPLIST_FILE = Runner/Info.plist; |
|||
LD_RUNPATH_SEARCH_PATHS = ( |
|||
"$(inherited)", |
|||
"@executable_path/Frameworks", |
|||
); |
|||
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech; |
|||
PRODUCT_NAME = "$(TARGET_NAME)"; |
|||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; |
|||
SWIFT_VERSION = 5.0; |
|||
VERSIONING_SYSTEM = "apple-generic"; |
|||
}; |
|||
name = Profile; |
|||
}; |
|||
331C8088294A63A400263BE5 /* Debug */ = { |
|||
isa = XCBuildConfiguration; |
|||
buildSettings = { |
|||
BUNDLE_LOADER = "$(TEST_HOST)"; |
|||
CODE_SIGN_STYLE = Automatic; |
|||
CURRENT_PROJECT_VERSION = 1; |
|||
GENERATE_INFOPLIST_FILE = YES; |
|||
MARKETING_VERSION = 1.0; |
|||
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests; |
|||
PRODUCT_NAME = "$(TARGET_NAME)"; |
|||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; |
|||
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; |
|||
SWIFT_VERSION = 5.0; |
|||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; |
|||
}; |
|||
name = Debug; |
|||
}; |
|||
331C8089294A63A400263BE5 /* Release */ = { |
|||
isa = XCBuildConfiguration; |
|||
buildSettings = { |
|||
BUNDLE_LOADER = "$(TEST_HOST)"; |
|||
CODE_SIGN_STYLE = Automatic; |
|||
CURRENT_PROJECT_VERSION = 1; |
|||
GENERATE_INFOPLIST_FILE = YES; |
|||
MARKETING_VERSION = 1.0; |
|||
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests; |
|||
PRODUCT_NAME = "$(TARGET_NAME)"; |
|||
SWIFT_VERSION = 5.0; |
|||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; |
|||
}; |
|||
name = Release; |
|||
}; |
|||
331C808A294A63A400263BE5 /* Profile */ = { |
|||
isa = XCBuildConfiguration; |
|||
buildSettings = { |
|||
BUNDLE_LOADER = "$(TEST_HOST)"; |
|||
CODE_SIGN_STYLE = Automatic; |
|||
CURRENT_PROJECT_VERSION = 1; |
|||
GENERATE_INFOPLIST_FILE = YES; |
|||
MARKETING_VERSION = 1.0; |
|||
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests; |
|||
PRODUCT_NAME = "$(TARGET_NAME)"; |
|||
SWIFT_VERSION = 5.0; |
|||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; |
|||
}; |
|||
name = Profile; |
|||
}; |
|||
97C147031CF9000F007C117D /* Debug */ = { |
|||
isa = XCBuildConfiguration; |
|||
buildSettings = { |
|||
ALWAYS_SEARCH_USER_PATHS = NO; |
|||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; |
|||
CLANG_ANALYZER_NONNULL = YES; |
|||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; |
|||
CLANG_CXX_LIBRARY = "libc++"; |
|||
CLANG_ENABLE_MODULES = YES; |
|||
CLANG_ENABLE_OBJC_ARC = YES; |
|||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; |
|||
CLANG_WARN_BOOL_CONVERSION = YES; |
|||
CLANG_WARN_COMMA = YES; |
|||
CLANG_WARN_CONSTANT_CONVERSION = YES; |
|||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; |
|||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; |
|||
CLANG_WARN_EMPTY_BODY = YES; |
|||
CLANG_WARN_ENUM_CONVERSION = YES; |
|||
CLANG_WARN_INFINITE_RECURSION = YES; |
|||
CLANG_WARN_INT_CONVERSION = YES; |
|||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; |
|||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; |
|||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; |
|||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; |
|||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; |
|||
CLANG_WARN_STRICT_PROTOTYPES = YES; |
|||
CLANG_WARN_SUSPICIOUS_MOVE = YES; |
|||
CLANG_WARN_UNREACHABLE_CODE = YES; |
|||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; |
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; |
|||
COPY_PHASE_STRIP = NO; |
|||
DEBUG_INFORMATION_FORMAT = dwarf; |
|||
ENABLE_STRICT_OBJC_MSGSEND = YES; |
|||
ENABLE_TESTABILITY = YES; |
|||
ENABLE_USER_SCRIPT_SANDBOXING = NO; |
|||
GCC_C_LANGUAGE_STANDARD = gnu99; |
|||
GCC_DYNAMIC_NO_PIC = NO; |
|||
GCC_NO_COMMON_BLOCKS = YES; |
|||
GCC_OPTIMIZATION_LEVEL = 0; |
|||
GCC_PREPROCESSOR_DEFINITIONS = ( |
|||
"DEBUG=1", |
|||
"$(inherited)", |
|||
); |
|||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; |
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; |
|||
GCC_WARN_UNDECLARED_SELECTOR = YES; |
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
|||
GCC_WARN_UNUSED_FUNCTION = YES; |
|||
GCC_WARN_UNUSED_VARIABLE = YES; |
|||
IPHONEOS_DEPLOYMENT_TARGET = 12.0; |
|||
MTL_ENABLE_DEBUG_INFO = YES; |
|||
ONLY_ACTIVE_ARCH = YES; |
|||
SDKROOT = iphoneos; |
|||
TARGETED_DEVICE_FAMILY = "1,2"; |
|||
}; |
|||
name = Debug; |
|||
}; |
|||
97C147041CF9000F007C117D /* Release */ = { |
|||
isa = XCBuildConfiguration; |
|||
buildSettings = { |
|||
ALWAYS_SEARCH_USER_PATHS = NO; |
|||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; |
|||
CLANG_ANALYZER_NONNULL = YES; |
|||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; |
|||
CLANG_CXX_LIBRARY = "libc++"; |
|||
CLANG_ENABLE_MODULES = YES; |
|||
CLANG_ENABLE_OBJC_ARC = YES; |
|||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; |
|||
CLANG_WARN_BOOL_CONVERSION = YES; |
|||
CLANG_WARN_COMMA = YES; |
|||
CLANG_WARN_CONSTANT_CONVERSION = YES; |
|||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; |
|||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; |
|||
CLANG_WARN_EMPTY_BODY = YES; |
|||
CLANG_WARN_ENUM_CONVERSION = YES; |
|||
CLANG_WARN_INFINITE_RECURSION = YES; |
|||
CLANG_WARN_INT_CONVERSION = YES; |
|||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; |
|||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; |
|||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; |
|||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; |
|||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; |
|||
CLANG_WARN_STRICT_PROTOTYPES = YES; |
|||
CLANG_WARN_SUSPICIOUS_MOVE = YES; |
|||
CLANG_WARN_UNREACHABLE_CODE = YES; |
|||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; |
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; |
|||
COPY_PHASE_STRIP = NO; |
|||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; |
|||
ENABLE_NS_ASSERTIONS = NO; |
|||
ENABLE_STRICT_OBJC_MSGSEND = YES; |
|||
ENABLE_USER_SCRIPT_SANDBOXING = NO; |
|||
GCC_C_LANGUAGE_STANDARD = gnu99; |
|||
GCC_NO_COMMON_BLOCKS = YES; |
|||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; |
|||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; |
|||
GCC_WARN_UNDECLARED_SELECTOR = YES; |
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
|||
GCC_WARN_UNUSED_FUNCTION = YES; |
|||
GCC_WARN_UNUSED_VARIABLE = YES; |
|||
IPHONEOS_DEPLOYMENT_TARGET = 12.0; |
|||
MTL_ENABLE_DEBUG_INFO = NO; |
|||
SDKROOT = iphoneos; |
|||
SUPPORTED_PLATFORMS = iphoneos; |
|||
SWIFT_COMPILATION_MODE = wholemodule; |
|||
SWIFT_OPTIMIZATION_LEVEL = "-O"; |
|||
TARGETED_DEVICE_FAMILY = "1,2"; |
|||
VALIDATE_PRODUCT = YES; |
|||
}; |
|||
name = Release; |
|||
}; |
|||
97C147061CF9000F007C117D /* Debug */ = { |
|||
isa = XCBuildConfiguration; |
|||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; |
|||
buildSettings = { |
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
|||
CLANG_ENABLE_MODULES = YES; |
|||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; |
|||
DEVELOPMENT_TEAM = PNNU77KTCP; |
|||
ENABLE_BITCODE = NO; |
|||
INFOPLIST_FILE = Runner/Info.plist; |
|||
LD_RUNPATH_SEARCH_PATHS = ( |
|||
"$(inherited)", |
|||
"@executable_path/Frameworks", |
|||
); |
|||
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech; |
|||
PRODUCT_NAME = "$(TARGET_NAME)"; |
|||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; |
|||
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; |
|||
SWIFT_VERSION = 5.0; |
|||
VERSIONING_SYSTEM = "apple-generic"; |
|||
}; |
|||
name = Debug; |
|||
}; |
|||
97C147071CF9000F007C117D /* Release */ = { |
|||
isa = XCBuildConfiguration; |
|||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; |
|||
buildSettings = { |
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
|||
CLANG_ENABLE_MODULES = YES; |
|||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; |
|||
DEVELOPMENT_TEAM = PNNU77KTCP; |
|||
ENABLE_BITCODE = NO; |
|||
INFOPLIST_FILE = Runner/Info.plist; |
|||
LD_RUNPATH_SEARCH_PATHS = ( |
|||
"$(inherited)", |
|||
"@executable_path/Frameworks", |
|||
); |
|||
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech; |
|||
PRODUCT_NAME = "$(TARGET_NAME)"; |
|||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; |
|||
SWIFT_VERSION = 5.0; |
|||
VERSIONING_SYSTEM = "apple-generic"; |
|||
}; |
|||
name = Release; |
|||
}; |
|||
/* End XCBuildConfiguration section */ |
|||
|
|||
/* Begin XCConfigurationList section */ |
|||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { |
|||
isa = XCConfigurationList; |
|||
buildConfigurations = ( |
|||
331C8088294A63A400263BE5 /* Debug */, |
|||
331C8089294A63A400263BE5 /* Release */, |
|||
331C808A294A63A400263BE5 /* Profile */, |
|||
); |
|||
defaultConfigurationIsVisible = 0; |
|||
defaultConfigurationName = Release; |
|||
}; |
|||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { |
|||
isa = XCConfigurationList; |
|||
buildConfigurations = ( |
|||
97C147031CF9000F007C117D /* Debug */, |
|||
97C147041CF9000F007C117D /* Release */, |
|||
249021D3217E4FDB00AE95B9 /* Profile */, |
|||
); |
|||
defaultConfigurationIsVisible = 0; |
|||
defaultConfigurationName = Release; |
|||
}; |
|||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { |
|||
isa = XCConfigurationList; |
|||
buildConfigurations = ( |
|||
97C147061CF9000F007C117D /* Debug */, |
|||
97C147071CF9000F007C117D /* Release */, |
|||
249021D4217E4FDB00AE95B9 /* Profile */, |
|||
); |
|||
defaultConfigurationIsVisible = 0; |
|||
defaultConfigurationName = Release; |
|||
}; |
|||
/* End XCConfigurationList section */ |
|||
}; |
|||
rootObject = 97C146E61CF9000F007C117D /* Project object */; |
|||
} |
|||
@ -1,7 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<Workspace |
|||
version = "1.0"> |
|||
<FileRef |
|||
location = "self:"> |
|||
</FileRef> |
|||
</Workspace> |
|||
@ -1,8 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>IDEDidComputeMac32BitWarning</key> |
|||
<true/> |
|||
</dict> |
|||
</plist> |
|||
@ -1,8 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>PreviewsEnabled</key> |
|||
<false/> |
|||
</dict> |
|||
</plist> |
|||
@ -1,99 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<Scheme |
|||
LastUpgradeVersion = "1510" |
|||
version = "1.3"> |
|||
<BuildAction |
|||
parallelizeBuildables = "YES" |
|||
buildImplicitDependencies = "YES"> |
|||
<BuildActionEntries> |
|||
<BuildActionEntry |
|||
buildForTesting = "YES" |
|||
buildForRunning = "YES" |
|||
buildForProfiling = "YES" |
|||
buildForArchiving = "YES" |
|||
buildForAnalyzing = "YES"> |
|||
<BuildableReference |
|||
BuildableIdentifier = "primary" |
|||
BlueprintIdentifier = "97C146ED1CF9000F007C117D" |
|||
BuildableName = "Runner.app" |
|||
BlueprintName = "Runner" |
|||
ReferencedContainer = "container:Runner.xcodeproj"> |
|||
</BuildableReference> |
|||
</BuildActionEntry> |
|||
</BuildActionEntries> |
|||
</BuildAction> |
|||
<TestAction |
|||
buildConfiguration = "Debug" |
|||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
|||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
|||
shouldUseLaunchSchemeArgsEnv = "YES"> |
|||
<MacroExpansion> |
|||
<BuildableReference |
|||
BuildableIdentifier = "primary" |
|||
BlueprintIdentifier = "97C146ED1CF9000F007C117D" |
|||
BuildableName = "Runner.app" |
|||
BlueprintName = "Runner" |
|||
ReferencedContainer = "container:Runner.xcodeproj"> |
|||
</BuildableReference> |
|||
</MacroExpansion> |
|||
<Testables> |
|||
<TestableReference |
|||
skipped = "NO" |
|||
parallelizable = "YES"> |
|||
<BuildableReference |
|||
BuildableIdentifier = "primary" |
|||
BlueprintIdentifier = "331C8080294A63A400263BE5" |
|||
BuildableName = "RunnerTests.xctest" |
|||
BlueprintName = "RunnerTests" |
|||
ReferencedContainer = "container:Runner.xcodeproj"> |
|||
</BuildableReference> |
|||
</TestableReference> |
|||
</Testables> |
|||
</TestAction> |
|||
<LaunchAction |
|||
buildConfiguration = "Debug" |
|||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
|||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
|||
launchStyle = "0" |
|||
useCustomWorkingDirectory = "NO" |
|||
ignoresPersistentStateOnLaunch = "NO" |
|||
debugDocumentVersioning = "YES" |
|||
debugServiceExtension = "internal" |
|||
enableGPUValidationMode = "1" |
|||
allowLocationSimulation = "YES"> |
|||
<BuildableProductRunnable |
|||
runnableDebuggingMode = "0"> |
|||
<BuildableReference |
|||
BuildableIdentifier = "primary" |
|||
BlueprintIdentifier = "97C146ED1CF9000F007C117D" |
|||
BuildableName = "Runner.app" |
|||
BlueprintName = "Runner" |
|||
ReferencedContainer = "container:Runner.xcodeproj"> |
|||
</BuildableReference> |
|||
</BuildableProductRunnable> |
|||
</LaunchAction> |
|||
<ProfileAction |
|||
buildConfiguration = "Profile" |
|||
shouldUseLaunchSchemeArgsEnv = "YES" |
|||
savedToolIdentifier = "" |
|||
useCustomWorkingDirectory = "NO" |
|||
debugDocumentVersioning = "YES"> |
|||
<BuildableProductRunnable |
|||
runnableDebuggingMode = "0"> |
|||
<BuildableReference |
|||
BuildableIdentifier = "primary" |
|||
BlueprintIdentifier = "97C146ED1CF9000F007C117D" |
|||
BuildableName = "Runner.app" |
|||
BlueprintName = "Runner" |
|||
ReferencedContainer = "container:Runner.xcodeproj"> |
|||
</BuildableReference> |
|||
</BuildableProductRunnable> |
|||
</ProfileAction> |
|||
<AnalyzeAction |
|||
buildConfiguration = "Debug"> |
|||
</AnalyzeAction> |
|||
<ArchiveAction |
|||
buildConfiguration = "Release" |
|||
revealArchiveInOrganizer = "YES"> |
|||
</ArchiveAction> |
|||
</Scheme> |
|||
@ -1,7 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<Workspace |
|||
version = "1.0"> |
|||
<FileRef |
|||
location = "group:Runner.xcodeproj"> |
|||
</FileRef> |
|||
</Workspace> |
|||
@ -1,8 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>IDEDidComputeMac32BitWarning</key> |
|||
<true/> |
|||
</dict> |
|||
</plist> |
|||
@ -1,8 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>PreviewsEnabled</key> |
|||
<false/> |
|||
</dict> |
|||
</plist> |
|||
@ -1,13 +0,0 @@ |
|||
import Flutter |
|||
import UIKit |
|||
|
|||
@main |
|||
@objc class AppDelegate: FlutterAppDelegate { |
|||
override func application( |
|||
_ application: UIApplication, |
|||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? |
|||
) -> Bool { |
|||
GeneratedPluginRegistrant.register(with: self) |
|||
return super.application(application, didFinishLaunchingWithOptions: launchOptions) |
|||
} |
|||
} |
|||
@ -1,122 +0,0 @@ |
|||
{ |
|||
"images" : [ |
|||
{ |
|||
"size" : "20x20", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-20x20@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "20x20", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-20x20@3x.png", |
|||
"scale" : "3x" |
|||
}, |
|||
{ |
|||
"size" : "29x29", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-29x29@1x.png", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"size" : "29x29", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-29x29@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "29x29", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-29x29@3x.png", |
|||
"scale" : "3x" |
|||
}, |
|||
{ |
|||
"size" : "40x40", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-40x40@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "40x40", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-40x40@3x.png", |
|||
"scale" : "3x" |
|||
}, |
|||
{ |
|||
"size" : "60x60", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-60x60@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "60x60", |
|||
"idiom" : "iphone", |
|||
"filename" : "Icon-App-60x60@3x.png", |
|||
"scale" : "3x" |
|||
}, |
|||
{ |
|||
"size" : "20x20", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-20x20@1x.png", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"size" : "20x20", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-20x20@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "29x29", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-29x29@1x.png", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"size" : "29x29", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-29x29@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "40x40", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-40x40@1x.png", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"size" : "40x40", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-40x40@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "76x76", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-76x76@1x.png", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"size" : "76x76", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-76x76@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "83.5x83.5", |
|||
"idiom" : "ipad", |
|||
"filename" : "Icon-App-83.5x83.5@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"size" : "1024x1024", |
|||
"idiom" : "ios-marketing", |
|||
"filename" : "Icon-App-1024x1024@1x.png", |
|||
"scale" : "1x" |
|||
} |
|||
], |
|||
"info" : { |
|||
"version" : 1, |
|||
"author" : "xcode" |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,23 +0,0 @@ |
|||
{ |
|||
"images" : [ |
|||
{ |
|||
"idiom" : "universal", |
|||
"filename" : "LaunchImage.png", |
|||
"scale" : "1x" |
|||
}, |
|||
{ |
|||
"idiom" : "universal", |
|||
"filename" : "LaunchImage@2x.png", |
|||
"scale" : "2x" |
|||
}, |
|||
{ |
|||
"idiom" : "universal", |
|||
"filename" : "LaunchImage@3x.png", |
|||
"scale" : "3x" |
|||
} |
|||
], |
|||
"info" : { |
|||
"version" : 1, |
|||
"author" : "xcode" |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@ -1,5 +0,0 @@ |
|||
# Launch Screen Assets |
|||
|
|||
You can customize the launch screen with your own desired assets by replacing the image files in this directory. |
|||
|
|||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. |
|||
@ -1,37 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> |
|||
<dependencies> |
|||
<deployment identifier="iOS"/> |
|||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> |
|||
</dependencies> |
|||
<scenes> |
|||
<!--View Controller--> |
|||
<scene sceneID="EHf-IW-A2E"> |
|||
<objects> |
|||
<viewController id="01J-lp-oVM" sceneMemberID="viewController"> |
|||
<layoutGuides> |
|||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/> |
|||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/> |
|||
</layoutGuides> |
|||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> |
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|||
<subviews> |
|||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"> |
|||
</imageView> |
|||
</subviews> |
|||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> |
|||
<constraints> |
|||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/> |
|||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/> |
|||
</constraints> |
|||
</view> |
|||
</viewController> |
|||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> |
|||
</objects> |
|||
<point key="canvasLocation" x="53" y="375"/> |
|||
</scene> |
|||
</scenes> |
|||
<resources> |
|||
<image name="LaunchImage" width="168" height="185"/> |
|||
</resources> |
|||
</document> |
|||
@ -1,26 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> |
|||
<dependencies> |
|||
<deployment identifier="iOS"/> |
|||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> |
|||
</dependencies> |
|||
<scenes> |
|||
<!--Flutter View Controller--> |
|||
<scene sceneID="tne-QT-ifu"> |
|||
<objects> |
|||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController"> |
|||
<layoutGuides> |
|||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> |
|||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> |
|||
</layoutGuides> |
|||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> |
|||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> |
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> |
|||
</view> |
|||
</viewController> |
|||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> |
|||
</objects> |
|||
</scene> |
|||
</scenes> |
|||
</document> |
|||
@ -1,49 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>CFBundleDevelopmentRegion</key> |
|||
<string>$(DEVELOPMENT_LANGUAGE)</string> |
|||
<key>CFBundleDisplayName</key> |
|||
<string>Azure Speech</string> |
|||
<key>CFBundleExecutable</key> |
|||
<string>$(EXECUTABLE_NAME)</string> |
|||
<key>CFBundleIdentifier</key> |
|||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
|||
<key>CFBundleInfoDictionaryVersion</key> |
|||
<string>6.0</string> |
|||
<key>CFBundleName</key> |
|||
<string>azure_speech</string> |
|||
<key>CFBundlePackageType</key> |
|||
<string>APPL</string> |
|||
<key>CFBundleShortVersionString</key> |
|||
<string>$(FLUTTER_BUILD_NAME)</string> |
|||
<key>CFBundleSignature</key> |
|||
<string>????</string> |
|||
<key>CFBundleVersion</key> |
|||
<string>$(FLUTTER_BUILD_NUMBER)</string> |
|||
<key>LSRequiresIPhoneOS</key> |
|||
<true/> |
|||
<key>UILaunchStoryboardName</key> |
|||
<string>LaunchScreen</string> |
|||
<key>UIMainStoryboardFile</key> |
|||
<string>Main</string> |
|||
<key>UISupportedInterfaceOrientations</key> |
|||
<array> |
|||
<string>UIInterfaceOrientationPortrait</string> |
|||
<string>UIInterfaceOrientationLandscapeLeft</string> |
|||
<string>UIInterfaceOrientationLandscapeRight</string> |
|||
</array> |
|||
<key>UISupportedInterfaceOrientations~ipad</key> |
|||
<array> |
|||
<string>UIInterfaceOrientationPortrait</string> |
|||
<string>UIInterfaceOrientationPortraitUpsideDown</string> |
|||
<string>UIInterfaceOrientationLandscapeLeft</string> |
|||
<string>UIInterfaceOrientationLandscapeRight</string> |
|||
</array> |
|||
<key>CADisableMinimumFrameDurationOnPhone</key> |
|||
<true/> |
|||
<key>UIApplicationSupportsIndirectInputEvents</key> |
|||
<true/> |
|||
</dict> |
|||
</plist> |
|||
@ -1 +0,0 @@ |
|||
#import "GeneratedPluginRegistrant.h" |
|||
@ -1,12 +0,0 @@ |
|||
import Flutter |
|||
import UIKit |
|||
import XCTest |
|||
|
|||
class RunnerTests: XCTestCase { |
|||
|
|||
func testExample() { |
|||
// If you add code to the Runner application, consider adding tests here. |
|||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest. |
|||
} |
|||
|
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
|
|||
void main() { |
|||
runApp(const MainApp()); |
|||
} |
|||
|
|||
class MainApp extends StatelessWidget { |
|||
const MainApp({super.key}); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return const MaterialApp( |
|||
home: Scaffold( |
|||
body: Center( |
|||
child: Text('Hello World!'), |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
@ -1,2 +1,2 @@ |
|||
#Sun May 18 21:39:38 IST 2025 |
|||
gradle.version=8.10 |
|||
#Tue May 20 17:39:11 IST 2025 |
|||
gradle.version=8.9 |
|||
|
|||
@ -0,0 +1,33 @@ |
|||
package com.deep_voice.speech |
|||
|
|||
import androidx.annotation.NonNull |
|||
import io.flutter.embedding.engine.plugins.FlutterPlugin |
|||
import io.flutter.plugin.common.MethodCall |
|||
import io.flutter.plugin.common.MethodChannel |
|||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler |
|||
import io.flutter.plugin.common.MethodChannel.Result |
|||
|
|||
/** |
|||
* SpeechPlugin |
|||
* |
|||
* 语音服务接口定义插件 |
|||
* 注意:此插件仅提供接口定义,不提供具体实现 |
|||
* 实际的语音识别和合成功能由其他插件实现 |
|||
*/ |
|||
class SpeechPlugin: FlutterPlugin, MethodCallHandler { |
|||
private lateinit var channel: MethodChannel |
|||
|
|||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "speech") |
|||
channel.setMethodCallHandler(this) |
|||
} |
|||
|
|||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { |
|||
// 此插件仅提供接口定义,不处理任何方法调用 |
|||
result.notImplemented() |
|||
} |
|||
|
|||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { |
|||
channel.setMethodCallHandler(null) |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
import Foundation |
|||
|
|||
/** |
|||
* ASR事件类型枚举 |
|||
*/ |
|||
@objc public enum AsrEventType: Int { |
|||
/** 会话开始 */ |
|||
case sessionStarted |
|||
|
|||
/** 会话结束 */ |
|||
case sessionStopped |
|||
|
|||
/** 识别中(中间结果) */ |
|||
case recognizing |
|||
|
|||
/** 识别完成(最终结果) */ |
|||
case recognized |
|||
|
|||
/** 识别取消 */ |
|||
case canceled |
|||
|
|||
/** 发生错误 */ |
|||
case error |
|||
} |
|||
|
|||
/** |
|||
* ASR事件数据类 |
|||
*/ |
|||
@objc public class AsrEvent: NSObject { |
|||
public let type: AsrEventType |
|||
public let params: [String: Any] |
|||
|
|||
public init(type: AsrEventType, params: [String: Any] = [:]) { |
|||
self.type = type |
|||
self.params = params |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* ASR事件监听协议 |
|||
*/ |
|||
@objc public protocol AsrEventListener { |
|||
/** |
|||
* 当发生ASR事件时调用 |
|||
* |
|||
* @param event ASR事件 |
|||
*/ |
|||
@objc(onAsrEvent:) |
|||
func onEvent(_ event: AsrEvent) |
|||
} |
|||
|
|||
/** |
|||
* ASR服务通用协议 |
|||
* |
|||
* 定义了语音识别服务的标准接口,可用于不同提供商的实现,如火山引擎、Azure等 |
|||
*/ |
|||
@objc public protocol IAsrService { |
|||
/** |
|||
* 初始化语音识别服务 |
|||
* |
|||
* @param subscriptionKey 服务订阅密钥/访问令牌 |
|||
* @param region 服务区域/应用ID |
|||
* @param supportedLanguages 支持的语言列表,如["zh-CN", "en-US"] |
|||
* @return 是否初始化成功 |
|||
*/ |
|||
func initialize( |
|||
subscriptionKey: String, |
|||
region: String, |
|||
supportedLanguages: [String]? |
|||
) -> Bool |
|||
|
|||
/** |
|||
* 单次识别(非流式) |
|||
* |
|||
* @param audioData 要识别的音频数据字节数组 |
|||
* @param callback 识别结果回调 |
|||
* @return 是否成功开始识别 |
|||
*/ |
|||
func recognizeOnce(audioData: Data, callback: @escaping (String) -> Void) -> Bool |
|||
|
|||
/** |
|||
* 启动连续识别 |
|||
* 开始持续监听音频输入并识别 |
|||
* |
|||
* @return 是否成功启动 |
|||
*/ |
|||
func startContinuousRecognition() -> Bool |
|||
|
|||
/** |
|||
* 停止连续识别 |
|||
* |
|||
* @return 是否成功停止 |
|||
*/ |
|||
func stopContinuousRecognition() -> Bool |
|||
|
|||
/** |
|||
* 推送音频数据 |
|||
* 在连续识别模式下推送新的音频数据 |
|||
* |
|||
* @param audioData 要识别的音频数据字节数组 |
|||
* @return 是否成功处理 |
|||
*/ |
|||
func pushAudioData(audioData: Data) -> Bool |
|||
|
|||
/** |
|||
* 添加ASR事件监听器 |
|||
* |
|||
* @param listener 事件监听器 |
|||
*/ |
|||
func addListener(_ listener: AsrEventListener) |
|||
|
|||
/** |
|||
* 移除ASR事件监听器 |
|||
* |
|||
* @param listener 要移除的事件监听器 |
|||
*/ |
|||
func removeListener(_ listener: AsrEventListener) |
|||
|
|||
/** |
|||
* 释放资源 |
|||
* 在不再需要服务时调用,释放底层资源 |
|||
*/ |
|||
func dispose() |
|||
|
|||
/** |
|||
* 设置识别参数 |
|||
* |
|||
* @param params 参数键值对 |
|||
* @return 是否设置成功 |
|||
*/ |
|||
func setParameters(_ params: [String: Any]) -> Bool |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
import Foundation |
|||
|
|||
/** |
|||
* TTS事件类型枚举 |
|||
*/ |
|||
@objc public enum TtsEventType: Int { |
|||
/** 开始合成 */ |
|||
case synthesisStarted |
|||
|
|||
/** 合成完成 */ |
|||
case synthesisCompleted |
|||
|
|||
/** 合成取消 */ |
|||
case synthesisCanceled |
|||
|
|||
/** 发生错误 */ |
|||
case error |
|||
} |
|||
|
|||
/** |
|||
* TTS事件数据类 |
|||
*/ |
|||
@objc public class TtsEvent: NSObject { |
|||
public let type: TtsEventType |
|||
public let params: [String: Any] |
|||
|
|||
public init(type: TtsEventType, params: [String: Any] = [:]) { |
|||
self.type = type |
|||
self.params = params |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* TTS事件监听协议 |
|||
*/ |
|||
@objc public protocol TtsEventListener { |
|||
/** |
|||
* 当发生TTS事件时调用 |
|||
* |
|||
* @param event TTS事件 |
|||
*/ |
|||
@objc(onTtsEvent:) |
|||
func onEvent(_ event: TtsEvent) |
|||
} |
|||
|
|||
/** |
|||
* 音频数据监听协议 |
|||
*/ |
|||
@objc public protocol AudioDataListener { |
|||
/** |
|||
* 当接收到音频数据时调用 |
|||
* |
|||
* @param data 音频数据字节数组 |
|||
*/ |
|||
func onAudioData(_ data: Data) |
|||
} |
|||
|
|||
/** |
|||
* TTS服务通用协议 |
|||
* |
|||
* 定义了语音合成服务的标准接口,可用于不同提供商的实现,如微软Azure、火山语音等 |
|||
*/ |
|||
@objc public protocol ITtsService { |
|||
/** |
|||
* 初始化语音合成服务 |
|||
* |
|||
* @param ttsAppId 服务应用ID |
|||
* @param ttsAppToken 服务访问令牌/订阅密钥 |
|||
* @param ttsResource 服务资源ID/区域(可选) |
|||
* @param language 语言代码,如"zh-CN" |
|||
* @return 是否初始化成功 |
|||
*/ |
|||
func initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String, language: String) -> Bool |
|||
|
|||
/** |
|||
* 设置语音角色 |
|||
* |
|||
* @param voiceName 语音角色名称(不同服务的语音角色命名可能不同) |
|||
* @return 是否设置成功 |
|||
*/ |
|||
func setVoice(_ voiceName: String) -> Bool |
|||
|
|||
/** |
|||
* 单次播放文本(非流式) |
|||
* |
|||
* @param text 要合成的文本 |
|||
* @return 是否成功开始合成 |
|||
*/ |
|||
func speakOnce(_ text: String) -> Bool |
|||
|
|||
/** |
|||
* 流式合成 |
|||
* 在流式场景下合成并播放文本,适用于连续的文本输入 |
|||
* |
|||
* @param text 要合成的文本片段 |
|||
* @return 是否成功处理 |
|||
*/ |
|||
func speakStream(_ text: String) -> Bool |
|||
|
|||
/** |
|||
* 刷新并播放流式文本缓冲区中的剩余内容 |
|||
* |
|||
* @return 是否成功处理 |
|||
*/ |
|||
func flushStream() -> Bool |
|||
|
|||
/** |
|||
* 停止当前合成和播放 |
|||
* |
|||
* @return 是否成功停止 |
|||
*/ |
|||
func stop() -> Bool |
|||
|
|||
/** |
|||
* 释放资源 |
|||
* 在不再需要服务时调用,释放底层资源 |
|||
*/ |
|||
func dispose() |
|||
|
|||
/** |
|||
* 添加TTS事件监听器 |
|||
* |
|||
* @param listener 事件监听器 |
|||
*/ |
|||
func addListener(_ listener: TtsEventListener) |
|||
|
|||
/** |
|||
* 移除TTS事件监听器 |
|||
* |
|||
* @param listener 要移除的事件监听器 |
|||
*/ |
|||
func removeListener(_ listener: TtsEventListener) |
|||
|
|||
/** |
|||
* 添加音频数据监听器 |
|||
* |
|||
* @param listener 音频数据监听器 |
|||
*/ |
|||
func addAudioDataListener(_ listener: AudioDataListener) |
|||
|
|||
/** |
|||
* 移除音频数据监听器 |
|||
* |
|||
* @param listener 要移除的音频数据监听器 |
|||
*/ |
|||
func removeAudioDataListener(_ listener: AudioDataListener) |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
#import <Flutter/Flutter.h> |
|||
|
|||
@interface SpeechPlugin : NSObject<FlutterPlugin> |
|||
@end |
|||
@ -0,0 +1,13 @@ |
|||
#import "SpeechPlugin.h" |
|||
#if __has_include(<speech/speech-Swift.h>) |
|||
#import <speech/speech-Swift.h> |
|||
#else |
|||
#import "speech-Swift.h" |
|||
#endif |
|||
|
|||
@implementation SpeechPlugin |
|||
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { |
|||
// 正确调用 Swift 类的静态方法 |
|||
[SwiftSpeechPlugin registerWithRegistrar:registrar]; |
|||
} |
|||
@end |
|||
@ -0,0 +1,22 @@ |
|||
import Flutter |
|||
import UIKit |
|||
|
|||
/** |
|||
* SpeechPlugin |
|||
* |
|||
* 语音服务接口定义插件 |
|||
* 注意:此插件仅提供接口定义,不提供具体实现 |
|||
* 实际的语音识别和合成功能由其他插件实现 |
|||
*/ |
|||
public class SwiftSpeechPlugin: NSObject, FlutterPlugin { |
|||
public static func register(with registrar: FlutterPluginRegistrar) { |
|||
let channel = FlutterMethodChannel(name: "speech", binaryMessenger: registrar.messenger()) |
|||
let instance = SwiftSpeechPlugin() |
|||
registrar.addMethodCallDelegate(instance, channel: channel) |
|||
} |
|||
|
|||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
|||
// 此插件仅提供接口定义,不处理任何方法调用 |
|||
result(FlutterMethodNotImplemented) |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
#ifndef speech_Bridging_Header_h |
|||
#define speech_Bridging_Header_h |
|||
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
#endif /* speech_Bridging_Header_h */ |
|||
@ -0,0 +1,5 @@ |
|||
#import <Foundation/Foundation.h> |
|||
|
|||
#import "SpeechPlugin.h" |
|||
|
|||
// 添加其他公共头文件导出
|
|||
@ -0,0 +1,29 @@ |
|||
# Speech 插件 - iOS 实现 |
|||
|
|||
## 概述 |
|||
|
|||
Speech 插件定义了语音识别 (ASR) 和语音合成 (TTS) 服务的标准接口协议,用于支持 DeepVoice 应用中的语音交互功能。 |
|||
|
|||
## 主要组件 |
|||
|
|||
- `IAsrService`: 语音识别服务协议,定义了语音识别的标准接口 |
|||
- `ITtsService`: 语音合成服务协议,定义了语音合成的标准接口 |
|||
|
|||
## 使用方式 |
|||
|
|||
此插件仅提供接口定义,实际使用时需要: |
|||
|
|||
1. 实现 `IAsrService` 和 `ITtsService` 协议 |
|||
2. 将实现注册到 Flutter 插件系统 |
|||
|
|||
## 文件结构 |
|||
|
|||
- `IAsrService.swift`: 语音识别服务接口定义 |
|||
- `ITtsService.swift`: 语音合成服务接口定义 |
|||
- `SpeechPlugin.swift`: Flutter 插件注册类 |
|||
|
|||
## 注意事项 |
|||
|
|||
- 此插件仅定义接口,不包含具体实现 |
|||
- 使用 Swift 5.0 编写 |
|||
- 支持 iOS 11.0 及以上版本 |
|||
@ -0,0 +1,23 @@ |
|||
# |
|||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. |
|||
# |
|||
Pod::Spec.new do |s| |
|||
s.name = 'speech' |
|||
s.version = '0.0.1' |
|||
s.summary = '语音识别和合成服务接口' |
|||
s.description = <<-DESC |
|||
语音识别和合成服务接口,定义了用于不同供应商实现的标准协议 |
|||
DESC |
|||
s.homepage = 'https://www.deep-voice.com' |
|||
s.license = { :file => '../LICENSE' } |
|||
s.author = { 'DeepVoice Team' => 'dev@deep-voice.com' } |
|||
s.source = { :path => '.' } |
|||
s.source_files = 'Classes/**/*' |
|||
s.public_header_files = 'Classes/**/*.h' |
|||
s.dependency 'Flutter' |
|||
s.platform = :ios, '17.0' |
|||
|
|||
# Flutter.framework does not contain a i386 slice. |
|||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } |
|||
s.swift_version = '5.0' |
|||
end |
|||
@ -0,0 +1,8 @@ |
|||
import 'dart:async'; |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
|
|||
// 空实现,仅作为接口定义的共享库 |
|||
class Speech { |
|||
static const MethodChannel _channel = MethodChannel('speech'); |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
name: speech |
|||
description: 语音识别和合成服务接口,定义了用于不同供应商实现的标准协议 |
|||
version: 0.0.1 |
|||
publish_to: 'none' |
|||
|
|||
environment: |
|||
sdk: '>=3.3.0 <4.0.0' |
|||
flutter: ">=3.3.0" |
|||
|
|||
dependencies: |
|||
flutter: |
|||
sdk: flutter |
|||
|
|||
dev_dependencies: |
|||
flutter_test: |
|||
sdk: flutter |
|||
flutter_lints: ^2.0.0 |
|||
|
|||
flutter: |
|||
plugin: |
|||
platforms: |
|||
android: |
|||
package: com.deep_voice.speech |
|||
pluginClass: SpeechPlugin |
|||
ios: |
|||
pluginClass: SpeechPlugin |
|||