Flutter for OpenHarmony三方库适配实战:flutter_tts 文字转语音

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发

一、flutter_tts 库概述

文字转语音(Text-to-Speech,TTS)是移动应用无障碍访问和语音交互的重要功能。在导航应用、有声读物、语音播报、无障碍辅助等场景中,TTS 技术发挥着关键作用。在 Flutter for OpenHarmony 应用开发中,flutter_tts 是一个功能丰富的跨平台文字转语音插件,提供了完整的语音合成能力。

flutter_tts 库特点

flutter_tts 库基于各平台原生 TTS 引擎实现,提供了以下核心特性:

多平台支持:支持 Android、iOS、macOS、Windows、Web、OpenHarmony 六大平台。

语音控制:支持设置语速、音量、音调等参数,灵活控制语音输出效果。

队列模式:支持排队播放和抢占播放两种模式,满足不同业务场景。

回调监听:提供开始、完成、暂停、继续、取消、错误等完整的回调机制。

离线支持:OpenHarmony 平台支持离线语音合成,无需网络连接。

功能支持对比

功能 Android iOS OpenHarmony
语音播放
停止播放
暂停播放
设置语速
设置音量
设置音调
队列模式
获取语音列表
离线合成

使用场景:语音导航、有声读物、无障碍辅助、智能客服、语音播报、语言学习应用等。


二、安装与配置

2.1 添加依赖

在项目的 pubspec.yaml 文件中添加 flutter_tts 依赖:

yaml 复制代码
dependencies:
  flutter_tts:
    git:
      url: https://github.com/banlang222/flutter_tts

然后执行以下命令获取依赖:

bash 复制代码
flutter pub get

2.2 权限配置

flutter_tts 在 OpenHarmony 平台上无需额外权限配置,可以直接使用。

说明:OpenHarmony 的 TTS 功能使用系统内置的语音合成引擎,无需申请特殊权限。


三、API 详解

3.1 FlutterTts 类

FlutterTts 是插件的主类,提供所有文字转语音相关功能。

dart 复制代码
class FlutterTts {
  static const MethodChannel _channel = MethodChannel('flutter_tts');

  VoidCallback? startHandler;
  VoidCallback? completionHandler;
  VoidCallback? pauseHandler;
  VoidCallback? continueHandler;
  VoidCallback? cancelHandler;
  ProgressHandler? progressHandler;
  ErrorHandler? errorHandler;
}

3.2 speak 方法

播放指定的文本内容。

dart 复制代码
Future<dynamic> speak(String text)

参数说明

  • text:要播放的文本内容

返回值

  • 返回 1 表示播放成功
  • 返回 0 表示播放失败(如队列模式下正在播放)

使用示例

dart 复制代码
final flutterTts = FlutterTts();

await flutterTts.speak('你好,欢迎使用文字转语音功能');

3.3 stop 方法

停止当前的语音播放。

dart 复制代码
Future<dynamic> stop()

返回值

  • 返回 1 表示停止成功

使用示例

dart 复制代码
await flutterTts.stop();

3.4 pause 方法

暂停当前的语音播放。

dart 复制代码
Future<dynamic> pause()

返回值

  • 返回 1 表示暂停成功

使用示例

dart 复制代码
await flutterTts.pause();

3.5 setSpeechRate 方法

设置语音播放速度。

dart 复制代码
Future<dynamic> setSpeechRate(double rate)

参数说明

  • rate:语速值,范围 0.0 ~ 1.0
    • 0.0:最慢
    • 0.5:正常速度
    • 1.0:最快

OpenHarmony 特殊说明

  • OpenHarmony 内部将值映射到 0.5 ~ 2.0 范围
  • 实际语速 = rate × 2

使用示例

dart 复制代码
await flutterTts.setSpeechRate(0.5);

3.6 setVolume 方法

设置语音播放音量。

dart 复制代码
Future<dynamic> setVolume(double volume)

参数说明

  • volume:音量值,范围 0.0 ~ 1.0
    • 0.0:静音
    • 1.0:最大音量

OpenHarmony 特殊说明

  • OpenHarmony 内部将值映射到 0.0 ~ 2.0 范围
  • 实际音量 = volume × 2

使用示例

dart 复制代码
await flutterTts.setVolume(0.8);

3.7 setPitch 方法

设置语音音调。

dart 复制代码
Future<dynamic> setPitch(double pitch)

参数说明

  • pitch:音调值,范围 0.5 ~ 2.0
    • 0.5:最低音调
    • 1.0:正常音调
    • 2.0:最高音调

使用示例

dart 复制代码
await flutterTts.setPitch(1.0);

3.8 setQueueMode 方法

设置队列模式,控制多个语音播放请求的处理方式。

dart 复制代码
Future<dynamic> setQueueMode(int mode)

参数说明

  • mode:队列模式
    • 0:排队模式(默认)- 新请求排队等待,当前播放完成后播放下一个
    • 1:抢占模式 - 新请求立即打断当前播放

使用示例

dart 复制代码
await flutterTts.setQueueMode(1);

3.9 awaitSpeakCompletion 方法

设置是否等待播放完成。

dart 复制代码
Future<dynamic> awaitSpeakCompletion(bool awaitCompletion)

参数说明

  • awaitCompletion
    • truespeak 方法会等待播放完成后返回
    • falsespeak 方法立即返回(默认)

使用示例

dart 复制代码
await flutterTts.awaitSpeakCompletion(true);
await flutterTts.speak('这段文字播放完成后才会继续执行');
print('播放完成');

3.10 getMaxSpeechInputLength 方法

获取最大语音输入长度。

dart 复制代码
Future<int?> get getMaxSpeechInputLength

返回值

  • OpenHarmony 返回 10000 个字符

使用示例

dart 复制代码
final maxLength = await flutterTts.getMaxSpeechInputLength;
print('最大输入长度: $maxLength');

3.11 回调监听

flutter_tts 提供完整的回调监听机制:

dart 复制代码
final flutterTts = FlutterTts();

flutterTts.setStartHandler(() {
  print('开始播放');
});

flutterTts.setCompletionHandler(() {
  print('播放完成');
});

flutterTts.setCancelHandler(() {
  print('播放取消');
});

flutterTts.setPauseHandler(() {
  print('播放暂停');
});

flutterTts.setContinueHandler(() {
  print('播放继续');
});

flutterTts.setErrorHandler((msg) {
  print('播放错误: $msg');
});

四、底层实现原理

4.1 Flutter 端实现

Flutter 端通过 MethodChannel 与原生层通信:

dart 复制代码
class FlutterTts {
  static const MethodChannel _channel = MethodChannel('flutter_tts');

  Future<dynamic> speak(String text) async {
    return await _channel.invokeMethod('speak', text);
  }

  Future<dynamic> stop() async {
    return await _channel.invokeMethod('stop');
  }

  Future<dynamic> setSpeechRate(double rate) async {
    return await _channel.invokeMethod('setSpeechRate', rate);
  }
}

4.2 OpenHarmony 原生实现

原生层使用 OpenHarmony 的 @kit.CoreSpeechKit 模块实现语音合成:

typescript 复制代码
import { textToSpeech } from '@kit.CoreSpeechKit';

export default class FlutterTtsPlugin implements FlutterPlugin, MethodCallHandler {
  private ttsEngine: textToSpeech.TextToSpeechEngine | null = null;

  private engineParams: textToSpeech.CreateEngineParams = {
    language: 'zh-CN',    // 当前仅支持 zh-CN
    person: 0,            // 音色,当前仅支持 0=聆小珊
    online: 1,            // 当前仅支持 1=离线
    extraParams: {
      "style": 'interaction-broadcast',
      "locate": "CN",
      "name": "EngineName"
    }
  };

  private speakExtraParams: Record<string, Object> = {
    "speed": 1,           // 语速 0.5-2
    "volume": 1,          // 音量 0-2
    "pitch": 1,           // 音调 0.5-2
    "languageContext": "zh-CN",
    "audioType": "pcm",
    "playType": 1,        // 1=合成与播报
    "soundChannel": 3,    // 3=语音播报
    "queueMode": 0        // 0=排队 1=抢占
  };

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    textToSpeech.createEngine(this.engineParams, (err, engine) => {
      if (!err) {
        this.ttsEngine = engine;
        this.ttsEngine.setListener(this.speakListener);
      }
    });
  }

  speak(text: string): void {
    let speakParams: textToSpeech.SpeakParams = {
      requestId: this.randomId(),
      extraParams: this.speakExtraParams
    };
    this.ttsEngine?.speak(text, speakParams);
  }
}

4.3 核心 API:TextToSpeechEngine

OpenHarmony 提供的 TextToSpeechEngine 是实现语音合成的核心:

创建引擎

typescript 复制代码
textToSpeech.createEngine(params, callback)

播放语音

typescript 复制代码
engine.speak(text, params)

停止播放

typescript 复制代码
engine.stop()

设置监听器

typescript 复制代码
engine.setListener({
  onStart: (requestId, response) => { },
  onComplete: (requestId, response) => { },
  onStop: (requestId, response) => { },
  onError: (requestId, errorCode, errorMessage) => { }
})

4.4 OpenHarmony 平台限制

当前 OpenHarmony TTS 引擎有以下限制:

参数 限制
语言 仅支持 zh-CN(中文)
音色 仅支持 0(聆小珊)
模式 仅支持离线模式(online: 1
风格 仅支持 interaction-broadcast

五、最佳实践

5.1 初始化 TTS

建议在应用启动时初始化 TTS:

dart 复制代码
class TtsService {
  static final TtsService _instance = TtsService._internal();
  factory TtsService() => _instance;
  TtsService._internal();

  final FlutterTts _flutterTts = FlutterTts();
  bool _isInitialized = false;

  Future<void> init() async {
    if (_isInitialized) return;

    await _flutterTts.awaitSpeakCompletion(true);
    await _flutterTts.setSpeechRate(0.5);
    await _flutterTts.setVolume(1.0);
    await _flutterTts.setPitch(1.0);

    _flutterTts.setCompletionHandler(() {
      print('TTS: 播放完成');
    });

    _flutterTts.setErrorHandler((msg) {
      print('TTS: 播放错误 - $msg');
    });

    _isInitialized = true;
  }

  Future<void> speak(String text) async {
    await init();
    await _flutterTts.speak(text);
  }

  Future<void> stop() async {
    await _flutterTts.stop();
  }
}

5.2 长文本分段播放

对于长文本,建议分段播放以提高响应速度:

dart 复制代码
Future<void> speakLongText(String text) async {
  final sentences = text.split(RegExp(r'。|,|?|!|,|\?|!'));
  
  for (var sentence in sentences) {
    if (sentence.trim().isEmpty) continue;
    await flutterTts.speak(sentence.trim());
  }
}

5.3 播放状态管理

使用状态机管理播放状态:

dart 复制代码
enum TtsState { playing, stopped, paused }

class TtsManager {
  final FlutterTts _flutterTts = FlutterTts();
  TtsState _state = TtsState.stopped;

  TtsState get state => _state;

  Future<void> init() async {
    _flutterTts.setStartHandler(() {
      _state = TtsState.playing;
    });

    _flutterTts.setCompletionHandler(() {
      _state = TtsState.stopped;
    });

    _flutterTts.setCancelHandler(() {
      _state = TtsState.stopped;
    });

    _flutterTts.setPauseHandler(() {
      _state = TtsState.paused;
    });

    _flutterTts.setContinueHandler(() {
      _state = TtsState.playing;
    });
  }
}

5.4 抢占模式使用场景

在需要打断当前播放的场景使用抢占模式:

dart 复制代码
await flutterTts.setQueueMode(1);

await flutterTts.speak('第一条消息');
await flutterTts.speak('第二条消息会打断第一条');

六、注意事项

6.1 语言限制

OpenHarmony 当前仅支持中文(zh-CN),如需其他语言需要等待系统更新。

6.2 文本长度

建议单次播放文本不超过 10000 个字符,超长文本应分段播放。

6.3 资源释放

在页面销毁时停止播放:

dart 复制代码
@override
void dispose() {
  flutterTts.stop();
  super.dispose();
}

6.4 离线模式

OpenHarmony TTS 默认使用离线模式,无需网络连接,但音色选择有限。


七、完整代码示例

以下是一个完整的可运行示例,展示了 flutter_tts 库的核心功能:

main.dart

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter TTS Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
        useMaterial3: true,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final FlutterTts _flutterTts = FlutterTts();
  final TextEditingController _textController = TextEditingController();
  
  double _volume = 0.5;
  double _pitch = 1.0;
  double _rate = 0.5;
  int _queueMode = 0;
  TtsState _ttsState = TtsState.stopped;

  @override
  void initState() {
    super.initState();
    _initTts();
  }

  Future<void> _initTts() async {
    await _flutterTts.awaitSpeakCompletion(true);

    _flutterTts.setStartHandler(() {
      setState(() {
        _ttsState = TtsState.playing;
      });
    });

    _flutterTts.setCompletionHandler(() {
      setState(() {
        _ttsState = TtsState.stopped;
      });
    });

    _flutterTts.setCancelHandler(() {
      setState(() {
        _ttsState = TtsState.stopped;
      });
    });

    _flutterTts.setErrorHandler((msg) {
      setState(() {
        _ttsState = TtsState.stopped;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('播放错误: $msg')),
      );
    });
  }

  Future<void> _speak() async {
    if (_textController.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入要播放的文本')),
      );
      return;
    }

    await _flutterTts.setVolume(_volume);
    await _flutterTts.setSpeechRate(_rate);
    await _flutterTts.setPitch(_pitch);
    await _flutterTts.setQueueMode(_queueMode);

    await _flutterTts.speak(_textController.text);
  }

  Future<void> _stop() async {
    await _flutterTts.stop();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('文字转语音'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            _buildStatusCard(),
            const SizedBox(height: 16),
            _buildTextInput(),
            const SizedBox(height: 16),
            _buildControlButtons(),
            const SizedBox(height: 24),
            _buildSliders(),
            const SizedBox(height: 16),
            _buildQueueModeSelector(),
          ],
        ),
      ),
    );
  }

  Widget _buildStatusCard() {
    Color statusColor;
    String statusText;
    IconData statusIcon;

    switch (_ttsState) {
      case TtsState.playing:
        statusColor = Colors.green;
        statusText = '正在播放';
        statusIcon = Icons.volume_up;
        break;
      case TtsState.stopped:
        statusColor = Colors.grey;
        statusText = '已停止';
        statusIcon = Icons.volume_off;
        break;
      case TtsState.paused:
        statusColor = Colors.orange;
        statusText = '已暂停';
        statusIcon = Icons.pause_circle;
        break;
    }

    return Card(
      color: statusColor.withOpacity(0.1),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            Icon(statusIcon, color: statusColor, size: 32),
            const SizedBox(width: 12),
            Text(
              statusText,
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: statusColor,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildTextInput() {
    return TextField(
      controller: _textController,
      maxLines: 5,
      decoration: InputDecoration(
        labelText: '输入要播放的文本',
        border: const OutlineInputBorder(),
        suffixIcon: IconButton(
          icon: const Icon(Icons.clear),
          onPressed: () => _textController.clear(),
        ),
      ),
    );
  }

  Widget _buildControlButtons() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        ElevatedButton.icon(
          onPressed: _ttsState == TtsState.playing ? null : _speak,
          icon: const Icon(Icons.play_arrow),
          label: const Text('播放'),
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.green,
            foregroundColor: Colors.white,
            padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
          ),
        ),
        ElevatedButton.icon(
          onPressed: _ttsState == TtsState.stopped ? null : _stop,
          icon: const Icon(Icons.stop),
          label: const Text('停止'),
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.red,
            foregroundColor: Colors.white,
            padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
          ),
        ),
      ],
    );
  }

  Widget _buildSliders() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            _buildSlider(
              label: '语速',
              value: _rate,
              min: 0.0,
              max: 1.0,
              icon: Icons.speed,
              onChanged: (value) => setState(() => _rate = value),
            ),
            const Divider(),
            _buildSlider(
              label: '音量',
              value: _volume,
              min: 0.0,
              max: 1.0,
              icon: Icons.volume_up,
              onChanged: (value) => setState(() => _volume = value),
            ),
            const Divider(),
            _buildSlider(
              label: '音调',
              value: _pitch,
              min: 0.5,
              max: 2.0,
              icon: Icons.music_note,
              onChanged: (value) => setState(() => _pitch = value),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSlider({
    required String label,
    required double value,
    required double min,
    required double max,
    required IconData icon,
    required ValueChanged<double> onChanged,
  }) {
    return Row(
      children: [
        Icon(icon, color: Theme.of(context).colorScheme.primary),
        const SizedBox(width: 12),
        SizedBox(
          width: 60,
          child: Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
        ),
        Expanded(
          child: Slider(
            value: value,
            min: min,
            max: max,
            onChanged: onChanged,
          ),
        ),
        SizedBox(
          width: 50,
          child: Text(
            value.toStringAsFixed(2),
            textAlign: TextAlign.right,
          ),
        ),
      ],
    );
  }

  Widget _buildQueueModeSelector() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '队列模式',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            RadioListTile<int>(
              title: const Text('排队模式'),
              subtitle: const Text('新请求排队等待当前播放完成'),
              value: 0,
              groupValue: _queueMode,
              onChanged: (value) => setState(() => _queueMode = value!),
            ),
            RadioListTile<int>(
              title: const Text('抢占模式'),
              subtitle: const Text('新请求立即打断当前播放'),
              value: 1,
              groupValue: _queueMode,
              onChanged: (value) => setState(() => _queueMode = value!),
            ),
          ],
        ),
      ),
    );
  }
}

enum TtsState { playing, stopped, paused }

运行此示例后,您将看到一个完整的文字转语音演示界面,可以输入文本、调整参数并播放语音。


八、总结

flutter_tts 是一个功能丰富的跨平台文字转语音插件,为 OpenHarmony 应用提供了完整的语音合成能力。

优点

  1. 跨平台一致:统一的 API 接口,支持六大平台
  2. 功能完整:支持语速、音量、音调等参数控制
  3. 回调完善:提供完整的播放状态监听
  4. 离线支持:OpenHarmony 支持离线语音合成

适用场景

  • 语音导航应用
  • 有声读物应用
  • 无障碍辅助应用
  • 智能客服系统
  • 语音播报系统
  • 语言学习应用

最佳实践

  1. 初始化时设置回调监听
  2. 长文本分段播放
  3. 使用状态机管理播放状态
  4. 页面销毁时停止播放

OpenHarmony 限制

  • 仅支持中文(zh-CN)
  • 仅支持一种音色(聆小珊)
  • 仅支持离线模式

九、参考资料

相关推荐
木子雨廷2 小时前
Flutter Redux 项目实战
flutter
AI_零食2 小时前
Flutter 框架跨平台鸿蒙开发 - 颜色听觉化应用
学习·flutter·信息可视化·开源·harmonyos
2301_822703202 小时前
大学生体质健康测试全景测绘台:基于鸿蒙Flutter的多维数据可视化与状态管理响应架构
算法·flutter·信息可视化·架构·开源·harmonyos·鸿蒙
独特的螺狮粉2 小时前
生命科学实验室经费极简记账簿:基于鸿蒙Flutter的极简主义状态响应与流式布局架构
flutter·华为·架构·开源·harmonyos
HH思️️无邪2 小时前
Flutter + iOS 实战指南:教程视频 PiP + 退桌面(可复用模板)
flutter·ios
提子拌饭1332 小时前
红细胞代偿性增殖与睡眠剥夺的对照演算引擎:基于鸿蒙Flutter的微观流体力学粒子渲染架构
flutter·华为·架构·开源·harmonyos·鸿蒙
浮芷.2 小时前
Flutter 框架跨平台鸿蒙开发 - 智能家电故障诊断应用
运维·服务器·科技·flutter·华为·harmonyos·鸿蒙
浮芷.2 小时前
Flutter 框架跨平台鸿蒙开发 - 急救指南应用
学习·flutter·华为·harmonyos·鸿蒙
提子拌饭1332 小时前
液相色谱质谱联用(LC-MS)数据可视化引擎:基于鸿蒙Flutter的高精度色谱卡与多维峰值拟合架构
flutter·华为·信息可视化·开源·harmonyos·鸿蒙