在这里插入图片描述

前言

语音播报是运动应用中提升用户体验的重要功能。在跑步、骑行等运动过程中,用户无法方便地查看手机屏幕,通过语音播报可以实时了解运动数据,如配速、距离、心率等。本文将详细介绍如何在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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐