You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

544 lines
19 KiB

import 'dart:async';
import 'dart:developer' as developer;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import '../../data/services/voice_recognition_service.dart';
class SpeechDemoPage extends StatefulWidget {
const SpeechDemoPage({Key? key}) : super(key: key);
@override
State<SpeechDemoPage> createState() => _SpeechDemoPageState();
}
class _SpeechDemoPageState extends State<SpeechDemoPage> {
final VoiceRecognitionService _recognitionService = Get.find<VoiceRecognitionService>();
final RxBool _permissionsGranted = false.obs;
final RxBool _isInitializing = false.obs;
final RxBool _isRecognizing = false.obs;
final RxString _errorMessage = ''.obs;
final RxString _recognizedText = ''.obs;
final RxString _status = ''.obs;
// 添加原生权限通道 - 只用于处理Android 11+的MANAGE_EXTERNAL_STORAGE权限
static const MethodChannel _permissionChannel = MethodChannel('com.example.deep_voice/permissions');
@override
void initState() {
super.initState();
_setupPermissionListener();
_checkPermissions();
}
@override
void dispose() {
_recognitionService.stopRecognition();
super.dispose();
}
// 设置权限监听器 - 只监听MANAGE_EXTERNAL_STORAGE权限结果
void _setupPermissionListener() {
_permissionChannel.setMethodCallHandler((call) async {
if (call.method == 'manageExternalStorageResult') {
developer.log('Received manageExternalStorageResult from native: ${call.arguments}');
// 更新权限状态
await _checkPermissions();
}
return null;
});
}
// 检查所有必要的权限
Future<void> _checkPermissions() async {
developer.log('Checking permissions');
try {
// 检查基本权限
final micStatus = await Permission.microphone.status;
final storageStatus = await Permission.storage.status;
developer.log('=== 详细权限状态 ===');
developer.log('麦克风权限: $micStatus');
developer.log('存储权限: $storageStatus');
// 对于Android 11+,检查MANAGE_EXTERNAL_STORAGE权限
bool manageStorageGranted = true;
if (Platform.isAndroid) {
if (await _isAndroid11OrHigher()) {
// 使用原生方法检查MANAGE_EXTERNAL_STORAGE权限
try {
manageStorageGranted = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
} catch (e) {
developer.log('Error checking MANAGE_EXTERNAL_STORAGE: $e');
manageStorageGranted = await Permission.manageExternalStorage.isGranted;
}
developer.log('管理所有文件权限: $manageStorageGranted');
}
}
// 记录其他权限状态
developer.log('读取外部存储权限: ${await Permission.audio.status}');
developer.log('写入外部存储权限: ${await Permission.photos.status}');
developer.log('=====================');
// 更新权限状态
final bool basicPermissionsGranted = micStatus.isGranted && storageStatus.isGranted;
// 修改逻辑:如果是Android 11+且已授予MANAGE_EXTERNAL_STORAGE权限,则忽略普通存储权限状态
bool effectiveStoragePermission = storageStatus.isGranted;
if (Platform.isAndroid && await _isAndroid11OrHigher() && manageStorageGranted) {
developer.log('已授予MANAGE_EXTERNAL_STORAGE权限,忽略普通存储权限状态');
effectiveStoragePermission = true;
}
_permissionsGranted.value = micStatus.isGranted && (effectiveStoragePermission || manageStorageGranted);
developer.log('所有权限已授予: ${_permissionsGranted.value}');
// 如果权限已授予,初始化引擎
if (_permissionsGranted.value && !_isInitializing.value) {
_initializeEngine();
} else if (!_permissionsGranted.value) {
_errorMessage.value = '需要麦克风和存储权限才能使用语音识别功能';
}
} catch (e) {
developer.log('检查权限时出错: $e');
_errorMessage.value = '检查权限时出错: $e';
}
}
// 检查是否为Android 11或更高版本
Future<bool> _isAndroid11OrHigher() async {
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
return androidInfo.version.sdkInt >= 30; // Android 11 is API 30
}
return false;
}
// 记录详细的权限状态
Future<void> _logPermissionDetails() async {
developer.log('=== 详细权限状态 ===');
developer.log('麦克风权限: ${await Permission.microphone.status}');
developer.log('存储权限: ${await Permission.storage.status}');
if (Platform.isAndroid) {
if (await _isAndroid11OrHigher()) {
bool manageStorageGranted = false;
try {
manageStorageGranted = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
} catch (e) {
developer.log('Error checking MANAGE_EXTERNAL_STORAGE: $e');
manageStorageGranted = await Permission.manageExternalStorage.isGranted;
}
developer.log('管理所有文件权限: $manageStorageGranted');
}
}
developer.log('读取外部存储权限: ${await Permission.audio.status}');
developer.log('写入外部存储权限: ${await Permission.photos.status}');
developer.log('=====================');
}
// 请求所有必要的权限
Future<void> _requestPermissions() async {
developer.log('请求权限');
try {
// 对于Android 11+,优先请求MANAGE_EXTERNAL_STORAGE权限
if (Platform.isAndroid && await _isAndroid11OrHigher()) {
bool isManager = false;
try {
isManager = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
} catch (e) {
developer.log('检查MANAGE_EXTERNAL_STORAGE时出错: $e');
isManager = await Permission.manageExternalStorage.isGranted;
}
if (!isManager) {
developer.log('请求MANAGE_EXTERNAL_STORAGE权限');
// 使用原生方法请求MANAGE_EXTERNAL_STORAGE权限
await _permissionChannel.invokeMethod('requestManageExternalStorage');
// 等待一段时间,让用户有机会授予权限
await Future.delayed(const Duration(seconds: 1));
// 再次检查权限状态
try {
isManager = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
developer.log('MANAGE_EXTERNAL_STORAGE权限状态: $isManager');
} catch (e) {
developer.log('检查MANAGE_EXTERNAL_STORAGE时出错: $e');
}
}
}
// 请求基本权限
Map<Permission, PermissionStatus> statuses = await [
Permission.microphone,
Permission.storage,
].request();
// 记录结果
statuses.forEach((permission, status) {
developer.log('权限 $permission: $status');
});
// 检查权限状态
await _checkPermissions();
} catch (e) {
developer.log('请求权限时出错: $e');
_errorMessage.value = '请求权限时出错: $e';
}
}
// 打开应用设置
Future<void> _openAppSettings() async {
developer.log('打开应用设置');
try {
// 使用permission_handler打开设置
final opened = await openAppSettings();
if (!opened) {
// 如果permission_handler失败,使用原生方法
await _permissionChannel.invokeMethod('openAppSettings');
}
} catch (e) {
developer.log('打开应用设置时出错: $e');
// 使用原生方法作为备选
try {
await _permissionChannel.invokeMethod('openAppSettings');
} catch (e2) {
developer.log('通过原生通道打开应用设置时出错: $e2');
}
}
}
// 初始化语音引擎
Future<void> _initializeEngine() async {
if (_isInitializing.value) return;
_isInitializing.value = true;
_errorMessage.value = '';
_status.value = '正在初始化语音引擎...';
try {
// 再次检查权限状态
await _checkPermissions();
if (!_permissionsGranted.value) {
developer.log('权限未授予,无法初始化引擎');
_errorMessage.value = '权限未授予,无法初始化语音引擎';
_isInitializing.value = false;
return;
}
// 记录权限详情
await _logPermissionDetails();
// 测试存储权限
String? availableDir = await _getAvailableStorageDirectory();
if (availableDir != null) {
developer.log('找到可用的存储目录: $availableDir');
} else {
developer.log('警告: 未找到可用的存储目录,可能会影响语音引擎初始化');
}
// 初始化语音引擎
developer.log('初始化语音识别引擎');
await _recognitionService.initialize();
developer.log('语音识别引擎初始化成功');
_status.value = '语音引擎初始化成功,可以开始识别';
_isInitializing.value = false;
} catch (e, stackTrace) {
developer.log('初始化语音识别引擎时出错: $e');
developer.log('堆栈跟踪: $stackTrace');
// 解析错误代码
String errorDetail = '未知错误';
if (e is PlatformException) {
developer.log('PlatformException 代码: ${e.code}, 消息: ${e.message}');
if (e.code == 'INIT_ERROR') {
if (e.message?.contains('-7') == true) {
errorDetail = '无法创建日志目录 (错误码: -7),可能是存储权限问题';
} else {
errorDetail = '初始化错误: ${e.message}';
}
}
}
_errorMessage.value = '初始化语音引擎失败: $errorDetail';
_status.value = '初始化失败';
_isInitializing.value = false;
// 显示错误提示和重试选项
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('初始化失败: $errorDetail'),
action: SnackBarAction(
label: '重试',
onPressed: _initializeEngine,
),
duration: const Duration(seconds: 10),
),
);
}
}
Future<void> _startRecognition() async {
developer.log('开始识别');
_errorMessage.value = '';
_isRecognizing.value = true;
_status.value = '正在识别...';
try {
await _recognitionService.startRecognition();
// 设置监听器接收识别结果
_recognitionService.recognizedText.listen((text) {
_recognizedText.value = text;
});
} catch (e) {
developer.log('开始识别时出错: $e');
_errorMessage.value = '启动识别失败: $e';
_isRecognizing.value = false;
_status.value = '识别启动失败';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('启动识别失败: $e'),
duration: const Duration(seconds: 3),
),
);
}
}
Future<void> _stopRecognition() async {
developer.log('停止识别');
try {
await _recognitionService.stopRecognition();
_isRecognizing.value = false;
_status.value = '识别已停止';
// 保存最终识别结果
_recognizedText.value = _recognitionService.recognizedText.value;
} catch (e) {
developer.log('停止识别时出错: $e');
_errorMessage.value = '停止识别失败: $e';
_isRecognizing.value = false;
_status.value = '识别停止失败';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('停止识别失败: $e'),
duration: const Duration(seconds: 3),
),
);
}
}
// 获取可用的存储目录
Future<String?> _getAvailableStorageDirectory() async {
try {
List<Directory> directoriesToTry = [];
// 应用专用外部存储目录
if (Platform.isAndroid) {
directoriesToTry.add(Directory('/storage/emulated/0/Android/data/com.example.deep_voice/files'));
}
// 应用内部存储目录
try {
final appDir = await getApplicationDocumentsDirectory();
directoriesToTry.add(appDir);
} catch (e) {
developer.log('获取应用内部存储目录失败: $e');
}
// 应用缓存目录
try {
final cacheDir = await getTemporaryDirectory();
directoriesToTry.add(cacheDir);
} catch (e) {
developer.log('获取应用缓存目录失败: $e');
}
// 尝试每个目录
for (var directory in directoriesToTry) {
try {
developer.log('测试目录可用性: ${directory.path}');
if (!await directory.exists()) {
await directory.create(recursive: true);
}
final testFile = File('${directory.path}/test_write.tmp');
await testFile.writeAsString('Test write at ${DateTime.now()}');
await testFile.readAsString(); // 验证可读
await testFile.delete();
developer.log('找到可用目录: ${directory.path}');
return directory.path;
} catch (e) {
developer.log('目录 ${directory.path} 不可用: $e');
}
}
developer.log('未找到可用的存储目录');
return null;
} catch (e) {
developer.log('获取可用存储目录时出错: $e');
return null;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('语音识别演示'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 权限请求部分
Obx(() => !_permissionsGranted.value
? Card(
color: Colors.amber.shade100,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'需要麦克风和存储权限',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 8),
const Text(
'注意: 在Android 11及以上版本,您需要在设置中手动授予"管理所有文件"权限',
style: TextStyle(
fontSize: 14,
),
),
const SizedBox(height: 16),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
alignment: WrapAlignment.center,
children: [
ElevatedButton(
onPressed: _requestPermissions,
child: const Text('授予权限'),
),
ElevatedButton(
onPressed: _openAppSettings,
child: const Text('打开设置'),
),
ElevatedButton(
onPressed: _logPermissionDetails,
child: const Text('检查权限'),
),
ElevatedButton(
onPressed: _initializeEngine,
child: const Text('强制初始化'),
),
],
),
],
),
),
)
: const SizedBox.shrink()),
// 状态显示
Obx(() => _status.value.isNotEmpty
? Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
'状态: ${_status.value}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
)
: const SizedBox.shrink()),
// 错误消息
Obx(() => _errorMessage.value.isNotEmpty
? Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
'错误: ${_errorMessage.value}',
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
)
: const SizedBox.shrink()),
// 识别结果显示区域
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Obx(() => Text(
_recognizedText.value.isEmpty
? '识别结果将显示在这里...'
: _recognizedText.value,
style: const TextStyle(
fontSize: 18.0,
),
)),
),
),
),
),
// 控制按钮
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(() => ElevatedButton(
onPressed: _permissionsGranted.value && !_isRecognizing.value && !_isInitializing.value
? _startRecognition
: null,
child: const Text('开始识别'),
)),
Obx(() => ElevatedButton(
onPressed: _isRecognizing.value ? _stopRecognition : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text('停止识别'),
)),
],
),
),
],
),
),
);
}
}