引言

在运动应用中,语音播报功能是提升用户体验的关键。用户在跑步、骑行等运动过程中无法频繁查看手机屏幕,通过语音播报可以实时了解运动数据,如配速、距离、心率等。本文将深入探讨如何在Flutter框架下实现一个高性能、可配置的语音播报组件,适配OpenHarmony平台,确保在运动过程中提供流畅、自然的语音提示。

语音播报组件设计思路

语音播报组件的设计需要考虑以下几个方面:

  1. 播报时机:过于频繁的播报会打扰用户,过于稀少又无法提供足够的信息
  2. 内容组织:播报内容需要自然流畅,符合中文语言习惯
  3. 用户定制:提供灵活的配置选项,让用户根据自己的偏好定制播报行为

核心组件实现

1. 配置模型

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方法支持部分属性更新,符合不可变对象的设计模式,确保UI响应性。

2. TTS服务封装

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;
    }
  }
}

解析:TTS服务封装了OpenHarmony的文本转语音能力。initialize方法创建TTS引擎,配置语言为中文、使用女声、在线合成模式。speak方法将文本转换为语音播放,可以设置语速、音量和音调。requestId使用时间戳确保每次请求的唯一性,避免语音播放冲突。这种封装让上层代码可以简单地调用speak方法进行播报,无需关心底层的TTS实现细节。

3. 播报内容生成器

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方法生成里程碑播报,当用户完成整公里时触发。文本的组织方式考虑了中文的语言习惯,使用"已跑"、"当前"等词汇使播报更加自然流畅。

语音播报组件数据流图

用户设置

配置存储

播报控制器

内容生成器

播报内容

TTS服务

语音播报

运动数据

说明:此数据流图展示了语音播报组件的工作流程。用户设置通过配置存储传递给播报控制器,控制器根据运动数据和配置生成播报内容,通过TTS服务转换为语音播报。运动数据实时输入到内容生成器,确保播报内容的准确性。

语音播报组件架构关系图

Flutter UI

VoiceAnnouncementConfig

VoiceSettingsPage

VoiceAnnouncementController

AnnouncementGenerator

TTSService

播报内容

语音播报

OpenHarmony

AudioFocusManager

说明:此架构关系图展示了语音播报组件的内部结构。Flutter UI通过配置模型与播报控制器交互,播报控制器调用内容生成器和TTS服务,TTS服务与OpenHarmony的音频焦点管理组件交互,确保语音播报在各种音频环境下都能正常工作。

跨平台兼容性处理

在Flutter开发OpenHarmony应用时,需要特别注意API的兼容性。OpenHarmony的API与Flutter的原生接口有差异,需要进行适配。例如,OpenHarmony的textToSpeech模块与Flutter的flutter_tts库不兼容,需要直接使用OpenHarmony的API。

// 在OpenHarmony上使用TTS
if (isPlatformOpenHarmony()) {
  await ttsService.initialize();
  await ttsService.speak(announcement);
} else {
  // Flutter平台的TTS实现
  await flutterTts.speak(announcement);
}

解析:通过平台检测函数isPlatformOpenHarmony(),可以实现跨平台的TTS调用。这种方式确保了代码在不同平台上的兼容性,同时保持了业务逻辑的统一。在实际应用中,我们还可以添加更多的平台检测逻辑,确保在其他平台上也能正常工作。

音频焦点管理

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));
    }
  }
}

解析:音频焦点管理确保语音播报能够正常播放,不被其他音频打断。requestFocus方法请求音频焦点,设置流类型为通知、内容类型为语音,pauseWhenDucked设为false表示在其他音频降低音量时继续播放。setVolume方法设置播报音量,音量值范围是0-15,我们将0-1的比例值转换为这个范围。良好的音频焦点管理确保用户在听音乐的同时也能听到运动播报,两者互不干扰。

在这里插入图片描述

总结

本文深入探讨了Flutter框架下语音播报组件的实现,从配置模型到TTS服务,从内容生成到播报控制,从音频焦点到设置界面,涵盖了语音播报功能的各个方面。通过灵活的配置选项和自然的播报内容,我们可以为用户提供专业的运动语音指导,让他们在运动过程中无需查看手机就能了解自己的运动状态,提升运动体验和安全性。

语音播报组件的设计不仅考虑了功能实现,还注重了用户体验和跨平台兼容性。通过合理的设计模式和API封装,我们可以在Flutter框架下高效地开发出适合OpenHarmony平台的语音播报功能,为运动应用提供强大的支持。在实际开发中,我们还可以进一步优化,例如添加更多语言支持、改进播报内容的自然度、增强音频焦点管理的健壮性等,使语音播报功能更加完善。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起探索更多鸿蒙跨平台开发技术!

Logo

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

更多推荐