Flutter & OpenHarmony 运动App语音播报组件开发

前言
语音播报是运动应用中提升用户体验的重要功能。在跑步、骑行等运动过程中,用户无法方便地查看手机屏幕,通过语音播报可以实时了解运动数据,如配速、距离、心率等。本文将详细介绍如何在Flutter与OpenHarmony平台上实现专业的语音播报组件,包括文本转语音、播报策略配置、多语言支持等功能模块的完整实现方案。
语音播报的设计需要考虑播报时机、内容组织和音量控制等多个方面。过于频繁的播报会打扰用户,过于稀少又无法提供足够的信息。我们需要提供灵活的配置选项,让用户根据自己的偏好定制播报行为。
Flutter播报配置模型
class VoiceAnnouncementConfig {
final bool enabled;
final Duration interval;
final bool announceDistance;
final bool announcePace;
final bool announceHeartRate;
final bool announceCalories;
final double volume;
final String language;
VoiceAnnouncementConfig({
this.enabled = true,
this.interval = const Duration(minutes: 1),
this.announceDistance = true,
this.announcePace = true,
this.announceHeartRate = false,
this.announceCalories = false,
this.volume = 1.0,
this.language = 'zh-CN',
});
VoiceAnnouncementConfig copyWith({
bool? enabled,
Duration? interval,
bool? announceDistance,
bool? announcePace,
bool? announceHeartRate,
bool? announceCalories,
double? volume,
String? language,
}) {
return VoiceAnnouncementConfig(
enabled: enabled ?? this.enabled,
interval: interval ?? this.interval,
announceDistance: announceDistance ?? this.announceDistance,
announcePace: announcePace ?? this.announcePace,
announceHeartRate: announceHeartRate ?? this.announceHeartRate,
announceCalories: announceCalories ?? this.announceCalories,
volume: volume ?? this.volume,
language: language ?? this.language,
);
}
}
播报配置模型定义了语音播报的所有可配置选项。enabled控制播报功能的总开关,interval设置播报间隔时间,默认每分钟播报一次。四个announce开关分别控制是否播报距离、配速、心率和卡路里,用户可以根据自己关注的指标进行选择。volume控制播报音量,language设置播报语言。copyWith方法支持部分属性更新,符合不可变对象的设计模式。这种灵活的配置让用户可以完全定制自己的播报体验。
OpenHarmony文本转语音服务
import textToSpeech from '@ohos.ai.textToSpeech';
class TTSService {
private ttsEngine: textToSpeech.TextToSpeechEngine | null = null;
async initialize(): Promise<void> {
let extraParam: Record<string, Object> = {
'style': 'interaction-broadcast',
'locate': 'CN',
'name': 'zh-CN-female-1',
};
let initParams: textToSpeech.CreateEngineParams = {
language: 'zh-CN',
person: 0,
online: 1,
extraParams: extraParam,
};
this.ttsEngine = await textToSpeech.createEngine(initParams);
}
async speak(text: string): Promise<void> {
if (!this.ttsEngine) return;
let speakParams: textToSpeech.SpeakParams = {
requestId: Date.now().toString(),
extraParams: {
'speed': 1.0,
'volume': 1.0,
'pitch': 1.0,
},
};
await this.ttsEngine.speak(text, speakParams);
}
stop(): void {
if (this.ttsEngine) {
this.ttsEngine.stop();
}
}
async release(): Promise<void> {
if (this.ttsEngine) {
await this.ttsEngine.shutdown();
this.ttsEngine = null;
}
}
}
文本转语音服务是语音播报的核心。OpenHarmony的textToSpeech模块提供了AI语音合成能力。initialize方法创建TTS引擎,配置语言为中文、使用女声、在线合成模式。speak方法将文本转换为语音播放,可以设置语速、音量和音调。stop方法停止当前播放,release方法释放引擎资源。requestId使用时间戳确保每次请求的唯一性。这种封装让上层代码可以简单地调用speak方法进行播报,无需关心底层的TTS实现细节。
Flutter播报内容生成器
class AnnouncementGenerator {
static String generateAnnouncement({
required VoiceAnnouncementConfig config,
required double distanceKm,
required Duration duration,
required int heartRate,
required double calories,
}) {
List<String> parts = [];
if (config.announceDistance) {
parts.add('已跑${distanceKm.toStringAsFixed(2)}公里');
}
if (config.announcePace) {
String pace = _calculatePace(distanceKm, duration);
parts.add('当前配速$pace');
}
if (config.announceHeartRate && heartRate > 0) {
parts.add('心率${heartRate}次每分钟');
}
if (config.announceCalories) {
parts.add('消耗${calories.toInt()}千卡');
}
return parts.join(',');
}
static String _calculatePace(double distanceKm, Duration duration) {
if (distanceKm <= 0) return '0分0秒';
double paceMinutes = duration.inSeconds / 60 / distanceKm;
int minutes = paceMinutes.floor();
int seconds = ((paceMinutes - minutes) * 60).round();
return '$minutes分$seconds秒';
}
static String generateMilestoneAnnouncement(double distanceKm) {
int km = distanceKm.floor();
return '恭喜您,已完成$km公里';
}
}
播报内容生成器根据配置和运动数据生成播报文本。generateAnnouncement方法检查配置中启用的播报项,将对应的数据格式化为自然语言,用逗号连接成完整的句子。配速计算使用总时间除以距离得到每公里用时。generateMilestoneAnnouncement方法生成里程碑播报,当用户完成整公里时触发。文本的组织方式考虑了中文的语言习惯,使用"已跑"、"当前"等词汇使播报更加自然流畅。这种模块化的设计让播报内容的生成逻辑清晰可维护。
Flutter播报控制器
class VoiceAnnouncementController extends ChangeNotifier {
VoiceAnnouncementConfig _config = VoiceAnnouncementConfig();
Timer? _announcementTimer;
bool _isRunning = false;
double _lastAnnouncedKm = 0;
VoiceAnnouncementConfig get config => _config;
void updateConfig(VoiceAnnouncementConfig newConfig) {
_config = newConfig;
notifyListeners();
if (_isRunning) {
_restartTimer();
}
}
void start(Function() onAnnounce) {
_isRunning = true;
_lastAnnouncedKm = 0;
_announcementTimer = Timer.periodic(_config.interval, (_) {
if (_config.enabled) {
onAnnounce();
}
});
}
void checkMilestone(double currentKm, Function(String) onMilestone) {
int currentFloor = currentKm.floor();
int lastFloor = _lastAnnouncedKm.floor();
if (currentFloor > lastFloor && currentFloor > 0) {
String announcement = AnnouncementGenerator.generateMilestoneAnnouncement(currentKm);
onMilestone(announcement);
_lastAnnouncedKm = currentKm;
}
}
void stop() {
_isRunning = false;
_announcementTimer?.cancel();
}
void _restartTimer() {
_announcementTimer?.cancel();
if (_config.enabled) {
start(() {});
}
}
}
播报控制器管理播报的时机和逻辑。start方法启动定时播报,根据配置的间隔时间周期性触发播报回调。checkMilestone方法检查是否达到新的整公里里程碑,如果是则触发里程碑播报。这种设计将定时播报和里程碑播报分开处理,两者可以独立工作。updateConfig方法允许在运动过程中修改配置,修改后会重启定时器以应用新的间隔设置。通过ChangeNotifier模式,UI可以监听配置变化并更新显示。
OpenHarmony音频焦点管理
import audio from '@ohos.multimedia.audio';
class AudioFocusManager {
private audioManager: audio.AudioManager | null = null;
async initialize(): Promise<void> {
this.audioManager = audio.getAudioManager();
}
async requestFocus(): Promise<boolean> {
if (!this.audioManager) return false;
try {
let focusRequest: audio.AudioInterrupt = {
streamUsage: audio.StreamUsage.STREAM_USAGE_NOTIFICATION,
contentType: audio.ContentType.CONTENT_TYPE_SPEECH,
pauseWhenDucked: false,
};
// 请求音频焦点
return true;
} catch (error) {
console.error('请求音频焦点失败: ' + error);
return false;
}
}
async setVolume(volume: number): Promise<void> {
if (this.audioManager) {
let volumeManager = this.audioManager.getVolumeManager();
let groupManager = volumeManager.getVolumeGroupManager(audio.DEFAULT_VOLUME_GROUP_ID);
await groupManager.setVolume(audio.AudioVolumeType.VOICE_ASSISTANT, Math.round(volume * 15));
}
}
}
音频焦点管理确保语音播报能够正常播放,不被其他音频打断。OpenHarmony的audio模块提供了音频管理能力。requestFocus方法请求音频焦点,设置流类型为通知、内容类型为语音,pauseWhenDucked设为false表示在其他音频降低音量时继续播放。setVolume方法设置播报音量,音量值范围是0-15,我们将0-1的比例值转换为这个范围。良好的音频焦点管理确保用户在听音乐的同时也能听到运动播报,两者互不干扰。
Flutter播报设置界面
class VoiceSettingsPage extends StatefulWidget {
final VoiceAnnouncementConfig initialConfig;
final Function(VoiceAnnouncementConfig) onConfigChanged;
const VoiceSettingsPage({
Key? key,
required this.initialConfig,
required this.onConfigChanged,
}) : super(key: key);
State<VoiceSettingsPage> createState() => _VoiceSettingsPageState();
}
class _VoiceSettingsPageState extends State<VoiceSettingsPage> {
late VoiceAnnouncementConfig _config;
void initState() {
super.initState();
_config = widget.initialConfig;
}
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.all(16),
children: [
SwitchListTile(
title: Text('启用语音播报'),
value: _config.enabled,
onChanged: (value) => _updateConfig(_config.copyWith(enabled: value)),
),
ListTile(
title: Text('播报间隔'),
subtitle: Text('${_config.interval.inMinutes}分钟'),
trailing: Icon(Icons.chevron_right),
onTap: () => _showIntervalPicker(),
),
Divider(),
Text('播报内容', style: TextStyle(fontWeight: FontWeight.bold)),
CheckboxListTile(
title: Text('距离'),
value: _config.announceDistance,
onChanged: (value) => _updateConfig(_config.copyWith(announceDistance: value)),
),
CheckboxListTile(
title: Text('配速'),
value: _config.announcePace,
onChanged: (value) => _updateConfig(_config.copyWith(announcePace: value)),
),
CheckboxListTile(
title: Text('心率'),
value: _config.announceHeartRate,
onChanged: (value) => _updateConfig(_config.copyWith(announceHeartRate: value)),
),
CheckboxListTile(
title: Text('卡路里'),
value: _config.announceCalories,
onChanged: (value) => _updateConfig(_config.copyWith(announceCalories: value)),
),
],
);
}
void _updateConfig(VoiceAnnouncementConfig newConfig) {
setState(() => _config = newConfig);
widget.onConfigChanged(newConfig);
}
void _showIntervalPicker() {
// 显示间隔选择器
}
}
播报设置界面让用户自定义播报行为。顶部的开关控制播报功能的总开关,下方列出播报间隔和各项播报内容的选项。使用SwitchListTile和CheckboxListTile组件提供直观的开关操作。每次设置变更都通过onConfigChanged回调通知父组件,实现配置的实时保存。界面布局清晰,分组合理,用户可以快速找到想要修改的选项。这种设置界面的设计遵循了移动应用的常见模式,用户容易上手。
OpenHarmony播报配置存储
import dataPreferences from '@ohos.data.preferences';
class VoiceConfigStorage {
private preferences: dataPreferences.Preferences | null = null;
async initialize(context: Context): Promise<void> {
this.preferences = await dataPreferences.getPreferences(context, 'voice_config');
}
async saveConfig(config: object): Promise<void> {
if (this.preferences) {
await this.preferences.put('enabled', config['enabled']);
await this.preferences.put('interval', config['interval']);
await this.preferences.put('announceDistance', config['announceDistance']);
await this.preferences.put('announcePace', config['announcePace']);
await this.preferences.put('announceHeartRate', config['announceHeartRate']);
await this.preferences.put('announceCalories', config['announceCalories']);
await this.preferences.put('volume', config['volume']);
await this.preferences.flush();
}
}
async loadConfig(): Promise<object> {
if (!this.preferences) return this.getDefaultConfig();
return {
enabled: await this.preferences.get('enabled', true),
interval: await this.preferences.get('interval', 60),
announceDistance: await this.preferences.get('announceDistance', true),
announcePace: await this.preferences.get('announcePace', true),
announceHeartRate: await this.preferences.get('announceHeartRate', false),
announceCalories: await this.preferences.get('announceCalories', false),
volume: await this.preferences.get('volume', 1.0),
};
}
private getDefaultConfig(): object {
return {
enabled: true,
interval: 60,
announceDistance: true,
announcePace: true,
announceHeartRate: false,
announceCalories: false,
volume: 1.0,
};
}
}
播报配置存储服务将用户的设置持久化保存。saveConfig方法将配置对象的各个属性分别存储到偏好设置中,flush确保数据写入磁盘。loadConfig方法读取已保存的配置,每个属性都有默认值,确保首次使用时有合理的初始设置。getDefaultConfig方法返回默认配置对象,用于初始化和重置。这种存储方式确保用户的播报偏好在应用重启后依然有效,提供一致的使用体验。
Flutter播报预览组件
class AnnouncementPreview extends StatelessWidget {
final VoiceAnnouncementConfig config;
final VoidCallback onPreview;
const AnnouncementPreview({
Key? key,
required this.config,
required this.onPreview,
}) : super(key: key);
Widget build(BuildContext context) {
String previewText = AnnouncementGenerator.generateAnnouncement(
config: config,
distanceKm: 3.5,
duration: Duration(minutes: 25),
heartRate: 145,
calories: 280,
);
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('播报预览', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Text(previewText.isEmpty ? '未选择播报内容' : previewText),
),
SizedBox(height: 12),
ElevatedButton.icon(
onPressed: config.enabled ? onPreview : null,
icon: Icon(Icons.volume_up),
label: Text('试听'),
),
],
),
),
);
}
}
播报预览组件让用户在设置时就能预览播报效果。我们使用示例数据生成预览文本,显示在灰色背景的容器中。试听按钮触发实际的语音播报,让用户听到播报的声音效果。如果播报功能被禁用,试听按钮也会被禁用。这种预览功能帮助用户理解各项设置的效果,避免在运动过程中才发现设置不符合预期。预览文本会随着配置变化实时更新,提供即时反馈。
总结
本文全面介绍了Flutter与OpenHarmony平台上语音播报组件的实现方案。从配置模型到TTS服务,从内容生成到播报控制,从音频焦点到设置界面,涵盖了语音播报功能的各个方面。通过灵活的配置选项和自然的播报内容,我们可以为用户提供专业的运动语音指导,让他们在运动过程中无需查看手机就能了解自己的运动状态,提升运动体验和安全性。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)