voice_recognizer 1.0.2 copy "voice_recognizer: ^1.0.2" to clipboard
voice_recognizer: ^1.0.2 copied to clipboard

基于阿里 Paraformer-zh 模型的离线语音识别组件,长按录音交互。

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:voice_recognizer/voice_recognizer.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 加载热词文件用于后处理纠错(Paraformer 模型不支持热词,使用后处理方式)
  await GlobalTextCorrector.instance.loadFromAsset('assets/hotWords.json');

  /// 慎用:模型加载后大约占用 50-100MB 内存
  VoiceRecognizerRegistry.instance.configure(
    numThreads: 1,
  );
  VoiceRecognizerRegistry.instance.preInitialize();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '语音识别示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const VoiceRecognizerDemoPage(),
    );
  }
}

/// 语音识别示例页面
///
/// 展示两种使用方式:
/// 1. 使用 VoiceRecordButton 按钮组件
/// 2. 使用 showVoiceRecordOverlay 弹窗
class VoiceRecognizerDemoPage extends StatefulWidget {
  const VoiceRecognizerDemoPage({super.key});

  @override
  State<VoiceRecognizerDemoPage> createState() =>
      _VoiceRecognizerDemoPageState();
}

class _VoiceRecognizerDemoPageState extends State<VoiceRecognizerDemoPage> {
  final TextEditingController _textController = TextEditingController();
  final List<String> _recognitionHistory = [];
  String _lastResult = '';

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  void _addToHistory(String text) {
    if (text.isNotEmpty) {
      setState(() {
        _lastResult = text;
        _recognitionHistory.insert(0, text);
        if (_recognitionHistory.length > 10) {
          _recognitionHistory.removeLast();
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('语音识别示例'),
        elevation: 0,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // ========== 方式一:按钮组件 ==========
            _buildSection(
              title: '方式一:VoiceRecordButton 按钮组件',
              description: '长按按钮开始录音,松开后自动识别。支持实时显示音量、录音时长、波纹动画等效果。',
              child: Center(
                child: VoiceRecordButton(
                  themeColor: Theme.of(context).primaryColor,
                  size: 72,
                  maxDuration: 10,
                  showDuration: true,
                  showRipple: true,
                  enableHaptic: true,
                  onResult: (text) {
                    _addToHistory(text);
                    _showSnackBar('识别结果: $text');
                  },
                  onPartialResult: (text) {
                    debugPrint('实时结果: $text');
                  },
                  onRecordingStateChanged: (isRecording) {
                    debugPrint('录音状态: $isRecording');
                  },
                  onError: (error) {
                    _showSnackBar(error, isError: true);
                  },
                ),
              ),
            ),

            const SizedBox(height: 40),

            // ========== 方式二:弹窗方式 ==========
            _buildSection(
              title: '方式二:showVoiceRecordOverlay 弹窗',
              description: '点击按钮弹出语音录入对话框,带有完整的 UI 交互和动画效果。',
              child: Center(
                child: ElevatedButton.icon(
                  onPressed: _showVoiceOverlay,
                  icon: const Icon(Icons.mic),
                  label: const Text('打开语音录入弹窗'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 24,
                      vertical: 14,
                    ),
                  ),
                ),
              ),
            ),

            const SizedBox(height: 40),

            // ========== 方式三:集成到输入框 ==========
            _buildSection(
              title: '方式三:集成到输入框',
              description: '将语音输入功能集成到 TextField 中,点击麦克风图标开始语音输入。',
              child: TextField(
                controller: _textController,
                decoration: InputDecoration(
                  hintText: '输入文字或点击麦克风语音输入...',
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                  suffixIcon: IconButton(
                    onPressed: _showVoiceOverlayForInput,
                    icon: Icon(
                      Icons.mic,
                      color: Theme.of(context).primaryColor,
                    ),
                    tooltip: '语音输入',
                  ),
                ),
                maxLines: 3,
              ),
            ),

            const SizedBox(height: 40),

            // ========== 最近识别结果 ==========
            if (_lastResult.isNotEmpty)
              _buildSection(
                title: '最近识别结果',
                description: '',
                child: Container(
                  width: double.infinity,
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: Theme.of(context).primaryColor.withOpacity(0.1),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: Theme.of(context).primaryColor.withOpacity(0.3),
                    ),
                  ),
                  child: Text(
                    _lastResult,
                    style: TextStyle(
                      fontSize: 16,
                      color: Theme.of(context).primaryColor,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ),
              ),

            if (_recognitionHistory.isNotEmpty) ...[
              const SizedBox(height: 24),
              _buildSection(
                title: '识别历史',
                description: '最近 10 条识别记录',
                child: Column(
                  children: _recognitionHistory.asMap().entries.map((entry) {
                    return Container(
                      width: double.infinity,
                      margin: const EdgeInsets.only(bottom: 8),
                      padding: const EdgeInsets.all(12),
                      decoration: BoxDecoration(
                        color: Colors.grey.shade50,
                        borderRadius: BorderRadius.circular(8),
                        border: Border.all(color: Colors.grey.shade200),
                      ),
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            width: 24,
                            height: 24,
                            alignment: Alignment.center,
                            decoration: BoxDecoration(
                              color: Colors.grey.shade200,
                              shape: BoxShape.circle,
                            ),
                            child: Text(
                              '${entry.key + 1}',
                              style: TextStyle(
                                fontSize: 12,
                                color: Colors.grey.shade600,
                              ),
                            ),
                          ),
                          const SizedBox(width: 12),
                          Expanded(
                            child: Text(
                              entry.value,
                              style: const TextStyle(fontSize: 14),
                            ),
                          ),
                        ],
                      ),
                    );
                  }).toList(),
                ),
              ),
            ],

            const SizedBox(height: 40),
          ],
        ),
      ),
    );
  }

  Widget _buildSection({
    required String title,
    required String description,
    required Widget child,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
          ),
        ),
        if (description.isNotEmpty) ...[
          const SizedBox(height: 4),
          Text(
            description,
            style: TextStyle(
              fontSize: 12,
              color: Colors.grey.shade600,
            ),
          ),
        ],
        const SizedBox(height: 16),
        child,
      ],
    );
  }

  void _showSnackBar(String message, {bool isError = false}) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: isError ? Colors.red : null,
        behavior: SnackBarBehavior.floating,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  /// 方式二:显示语音录入弹窗
  Future<void> _showVoiceOverlay() async {
    final result = await showVoiceRecordOverlay(
      context: context,
      themeColor: Theme.of(context).primaryColor,
      maxDuration: 10,
      title: '语音输入',
      hintText: '长按按钮开始录音...',
    );

    if (result != null && result.isNotEmpty) {
      _addToHistory(result);
      _showSnackBar('识别结果: $result');
    }
  }

  /// 方式三:为输入框显示语音录入弹窗
  Future<void> _showVoiceOverlayForInput() async {
    final result = await showVoiceRecordOverlay(
      context: context,
      themeColor: Theme.of(context).primaryColor,
      maxDuration: 10,
      title: '语音输入',
      hintText: '请说出您要输入的内容...',
    );

    if (result != null && result.isNotEmpty) {
      setState(() {
        // 追加到现有文本
        final currentText = _textController.text;
        if (currentText.isNotEmpty && !currentText.endsWith(' ')) {
          _textController.text = '$currentText $result';
        } else {
          _textController.text = currentText + result;
        }
        // 移动光标到末尾
        _textController.selection = TextSelection.fromPosition(
          TextPosition(offset: _textController.text.length),
        );
      });
      _addToHistory(result);
    }
  }
}
4
likes
120
points
124
downloads

Publisher

unverified uploader

Weekly Downloads

基于阿里 Paraformer-zh 模型的离线语音识别组件,长按录音交互。

Repository (GitHub)

Topics

#speech-recognition #voice-input #paraformer #offline-asr

Documentation

API reference

License

MIT (license)

Dependencies

flutter, lpinyin, path_provider, permission_handler, record, sherpa_onnx

More

Packages that depend on voice_recognizer