@ -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.release.xcconfig" |
||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" |
||||
#include "Generated.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 Flutter |
||||
import UIKit |
import UIKit |
||||
|
import AVFoundation |
||||
|
import MicrosoftCognitiveServicesSpeech |
||||
|
// 自定义语音处理组件,提供音频流处理等功能 |
||||
|
import speech |
||||
|
|
||||
|
/** |
||||
|
* Azure Speech Plugin |
||||
|
* |
||||
|
* 基于微软Azure语音服务的Flutter插件 |
||||
|
* 提供语音识别(ASR)和语音合成(TTS)功能 |
||||
|
*/ |
||||
|
public class SwiftAzureSpeechPlugin: NSObject, FlutterPlugin { |
||||
|
// 日志标签 |
||||
|
private let tag = "AzureSpeechPlugin" |
||||
|
|
||||
|
// ASR相关 |
||||
|
private var asrChannel: FlutterMethodChannel? |
||||
|
private var asrEventChannel: FlutterEventChannel? |
||||
|
private var asrEventSink: FlutterEventSink? |
||||
|
private let azureAsrHelper = AzureAsrHelper() |
||||
|
|
||||
|
// 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 |
||||
|
} |
||||
|
|
||||
public class AzureSpeechPlugin: NSObject, FlutterPlugin { |
// 发送ASR事件方法 |
||||
private var ttsHelper: AzureTtsHelper? |
private func sendAsrEvent(_ event: [String: Any]) { |
||||
private var asrHelper: AzureAsrHelper? |
if asrEventSink == nil { |
||||
private var eventSink: FlutterEventSink? |
print("\(tag): 无法发送ASR事件:事件通道未准备好") |
||||
|
return |
||||
|
} |
||||
|
|
||||
public static func register(with registrar: FlutterPluginRegistrar) { |
DispatchQueue.main.async { [weak self] in |
||||
let channel = FlutterMethodChannel(name: "azure_speech", binaryMessenger: registrar.messenger()) |
guard let self = self else { return } |
||||
let instance = AzureSpeechPlugin() |
self.asrEventSink?(event) |
||||
registrar.addMethodCallDelegate(instance, channel: channel) |
} |
||||
|
} |
||||
|
|
||||
// 初始化事件通道 |
// 发送TTS事件方法 |
||||
let eventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger()) |
private func sendTtsEvent(_ event: [String: Any]) { |
||||
eventChannel.setStreamHandler(AsrStreamHandler(instance: instance)) |
if ttsEventSink == nil { |
||||
|
print("\(tag): 无法发送TTS事件:事件通道未准备好") |
||||
|
return |
||||
} |
} |
||||
|
|
||||
override init() { |
DispatchQueue.main.async { [weak self] in |
||||
super.init() |
guard let self = self else { return } |
||||
ttsHelper = AzureTtsHelper() |
self.ttsEventSink?(event) |
||||
asrHelper = AzureAsrHelper() |
} |
||||
|
} |
||||
|
|
||||
// 设置ASR事件处理 |
// 设置TTS事件监听器 |
||||
asrHelper?.setEventHandler { [weak self] event in |
private func setupTtsEventListener() { |
||||
self?.handleAsrEvent(event) |
if !isTtsListenerAdded { |
||||
|
azureTtsHelper.addListener(self) |
||||
|
isTtsListenerAdded = true |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
|
// 设置ASR事件监听器 |
||||
|
private func setupAsrEventListener() { |
||||
|
azureAsrHelper.addListener(self) |
||||
|
} |
||||
|
|
||||
|
// 处理Flutter方法调用 |
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { |
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 { |
switch call.method { |
||||
// TTS相关方法 |
case "initialize": |
||||
case "initializeTts": |
|
||||
guard let args = call.arguments as? [String: Any], |
guard let args = call.arguments as? [String: Any], |
||||
let subscriptionKey = args["subscriptionKey"] as? String, |
let subscriptionKey = args["subscriptionKey"] as? String, |
||||
let serviceRegion = args["serviceRegion"] as? String else { |
let region = args["region"] as? String else { |
||||
result(false) |
result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil)) |
||||
return |
return |
||||
} |
} |
||||
|
|
||||
let language = args["language"] as? String ?? "zh-CN" |
let supportedLanguages = args["supportedLanguages"] as? [String] |
||||
let success = ttsHelper?.initialize(speechSubscriptionKey: subscriptionKey, serviceRegion: serviceRegion, language: language) ?? false |
|
||||
|
// 初始化ASR引擎并设置事件监听 |
||||
|
let success = azureAsrHelper.initialize(subscriptionKey: subscriptionKey, region: region, supportedLanguages: supportedLanguages) |
||||
|
setupAsrEventListener() |
||||
|
|
||||
result(success) |
result(success) |
||||
|
|
||||
case "setTtsVoice": |
case "recognizeOnce": |
||||
guard let args = call.arguments as? [String: Any], |
guard let args = call.arguments as? [String: Any], |
||||
let voiceName = args["voiceName"] as? String else { |
let audioBytes = args["data"] as? FlutterStandardTypedData else { |
||||
result(false) |
result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil)) |
||||
return |
return |
||||
} |
} |
||||
|
|
||||
let success = ttsHelper?.setVoice(voiceName: voiceName) ?? false |
// 执行一次性识别 |
||||
result(success) |
let success = azureAsrHelper.recognizeOnce(audioData: audioBytes.data) { recognizedText in |
||||
|
result(["text": recognizedText, "detectedLanguage": ""]) |
||||
|
} |
||||
|
|
||||
case "setTtsSpeechParams": |
if !success { |
||||
guard let args = call.arguments as? [String: Any], |
result(FlutterError(code: "RECOGNITION_ERROR", message: "启动识别失败", details: nil)) |
||||
let rate = args["rate"] as? Int, |
} |
||||
let pitch = args["pitch"] as? Int, |
|
||||
let volume = args["volume"] as? Int else { |
case "startContinuousRecognition": |
||||
result(false) |
// 确保事件通道已准备好 |
||||
|
if asrEventSink == nil { |
||||
|
result(FlutterError(code: "EVENT_CHANNEL_NOT_READY", message: "事件通道未准备好,无法开始连续识别", details: nil)) |
||||
return |
return |
||||
} |
} |
||||
|
|
||||
let success = ttsHelper?.setSpeechParams(rate: rate, pitch: pitch, volume: volume) ?? false |
let success = azureAsrHelper.startContinuousRecognition() |
||||
|
result(success) |
||||
|
|
||||
|
case "stopContinuousRecognition": |
||||
|
let success = azureAsrHelper.stopContinuousRecognition() |
||||
result(success) |
result(success) |
||||
|
|
||||
case "speakText": |
case "isContinuousRecognitionActive": |
||||
|
result(azureAsrHelper.isRecognitionActive()) |
||||
|
|
||||
|
case "dispose": |
||||
|
azureAsrHelper.dispose() |
||||
|
result(true) |
||||
|
|
||||
|
case "pushAudioData": |
||||
guard let args = call.arguments as? [String: Any], |
guard let args = call.arguments as? [String: Any], |
||||
let text = args["text"] as? String else { |
let audioBytes = args["data"] as? FlutterStandardTypedData else { |
||||
result(false) |
result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil)) |
||||
return |
return |
||||
} |
} |
||||
|
|
||||
ttsHelper?.speakText(text: text) { success, _ in |
let success = azureAsrHelper.pushAudioData(audioData: audioBytes.data) |
||||
result(success) |
result(success) |
||||
} |
|
||||
|
|
||||
case "stopSpeaking": |
default: |
||||
let success = ttsHelper?.stopSpeaking() ?? false |
result(FlutterMethodNotImplemented) |
||||
result(success) |
} |
||||
|
} |
||||
|
|
||||
case "isSpeaking": |
// MARK: - TTS 方法处理 |
||||
let speaking = ttsHelper?.isSpeaking() ?? false |
|
||||
result(speaking) |
|
||||
|
|
||||
case "setTtsAudioOutputType": |
private func handleTtsMethodCall(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { |
||||
|
switch call.method { |
||||
|
case "tts_initialize": |
||||
guard let args = call.arguments as? [String: Any], |
guard let args = call.arguments as? [String: Any], |
||||
let outputType = args["outputType"] as? String else { |
let subscriptionKey = args["subscriptionKey"] as? String, |
||||
result(false) |
let region = args["region"] as? String else { |
||||
|
result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil)) |
||||
return |
return |
||||
} |
} |
||||
|
|
||||
var type: AzureTtsHelper.AudioOutputType = .auto |
// 初始化TTS引擎 |
||||
switch outputType.uppercased() { |
let language = args["language"] as? String ?? "zh-CN" |
||||
case "SPEAKER": |
let success = azureTtsHelper.initialize(ttsAppId: "", ttsAppToken: subscriptionKey, ttsResource: region, language: language) |
||||
type = .speaker |
|
||||
case "EARPIECE": |
// 设置TTS事件监听器 |
||||
type = .earpiece |
setupTtsEventListener() |
||||
default: |
|
||||
type = .auto |
|
||||
} |
|
||||
|
|
||||
let success = ttsHelper?.setAudioOutputType(outputType: type) ?? false |
|
||||
result(success) |
result(success) |
||||
|
|
||||
// ASR相关方法 |
case "tts_set_voice": |
||||
case "initializeAsr": |
|
||||
guard let args = call.arguments as? [String: Any], |
guard let args = call.arguments as? [String: Any], |
||||
let subscriptionKey = args["subscriptionKey"] as? String, |
let voiceName = args["voiceName"] as? String else { |
||||
let serviceRegion = args["serviceRegion"] as? String, |
result(FlutterError(code: "INVALID_ARGUMENTS", message: "语音名称不能为空", details: nil)) |
||||
let supportedLanguages = args["supportedLanguages"] as? [String] else { |
|
||||
result(false) |
|
||||
return |
return |
||||
} |
} |
||||
|
|
||||
let success = asrHelper?.initialize(speechSubscriptionKey: subscriptionKey, serviceRegion: serviceRegion, supportedLanguages: supportedLanguages) ?? false |
let success = azureTtsHelper.setVoice(voiceName) |
||||
result(success) |
result(success) |
||||
|
|
||||
case "recognizeOnce": |
case "tts_speak_once": |
||||
asrHelper?.recognizeOnce { success, text, language, error in |
guard let args = call.arguments as? [String: Any], |
||||
var resultMap: [String: Any] = ["success": success] |
let text = args["text"] as? String else { |
||||
if success { |
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil)) |
||||
resultMap["text"] = text |
return |
||||
resultMap["language"] = language |
|
||||
} else { |
|
||||
resultMap["error"] = error |
|
||||
} |
} |
||||
result(resultMap) |
|
||||
|
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 |
||||
} |
} |
||||
|
|
||||
case "startContinuousRecognition": |
let success = azureTtsHelper.speakStream(text) |
||||
let success = asrHelper?.startContinuousRecognition() ?? false |
|
||||
result(success) |
result(success) |
||||
|
|
||||
case "stopContinuousRecognition": |
case "tts_flush_stream": |
||||
let success = asrHelper?.stopContinuousRecognition() ?? false |
let success = azureTtsHelper.flushStream() |
||||
result(success) |
result(success) |
||||
|
|
||||
case "isContinuousRecognitionActive": |
case "tts_stop": |
||||
let isActive = asrHelper?.isContinuousRecognitionActive() ?? false |
let success = azureTtsHelper.stop() |
||||
result(isActive) |
result(success) |
||||
|
|
||||
case "dispose": |
case "tts_isSpeaking": |
||||
ttsHelper?.dispose() |
result(azureTtsHelper.isSpeaking()) |
||||
asrHelper?.dispose() |
|
||||
result(nil) |
case "tts_release": |
||||
|
azureTtsHelper.dispose() |
||||
|
result(true) |
||||
|
|
||||
default: |
default: |
||||
result(FlutterMethodNotImplemented) |
result(FlutterMethodNotImplemented) |
||||
} |
} |
||||
} |
} |
||||
|
} |
||||
|
|
||||
// 设置事件接收器 |
// MARK: - 事件处理 |
||||
func setEventSink(_ sink: FlutterEventSink?) { |
extension SwiftAzureSpeechPlugin: FlutterStreamHandler { |
||||
self.eventSink = sink |
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 |
||||
|
} |
||||
} |
} |
||||
|
|
||||
// 处理ASR事件 |
return nil |
||||
private func handleAsrEvent(_ event: [String: Any]) { |
|
||||
self.eventSink?(event) |
|
||||
} |
} |
||||
} |
|
||||
|
|
||||
// ASR事件流处理器 |
public func onCancel(withArguments arguments: Any?) -> FlutterError? { |
||||
class AsrStreamHandler: NSObject, FlutterStreamHandler { |
// 根据通道类型清除事件接收器 |
||||
private weak var plugin: AzureSpeechPlugin? |
if let args = arguments as? [String: Any], |
||||
|
let channel = args["channel"] as? String { |
||||
|
|
||||
init(instance: AzureSpeechPlugin) { |
if channel == "asr" { |
||||
self.plugin = instance |
asrEventSink = nil |
||||
super.init() |
} else if channel == "tts" { |
||||
|
ttsEventSink = nil |
||||
|
} |
||||
|
} else { |
||||
|
// 如果没有指定通道,清除所有通道 |
||||
|
asrEventSink = nil |
||||
|
ttsEventSink = nil |
||||
} |
} |
||||
|
|
||||
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { |
|
||||
plugin?.setEventSink(events) |
|
||||
return 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 |
||||
|
} |
||||
|
|
||||
func onCancel(withArguments arguments: Any?) -> FlutterError? { |
sendAsrEvent(eventMap) |
||||
plugin?.setEventSink(nil) |
} |
||||
return nil |
|
||||
|
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" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 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 |
#Tue May 20 17:39:11 IST 2025 |
||||
gradle.version=8.10 |
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 |
||||