feat: add Codec2 voice messages over LoRa mesh (iOS/macOS only)

Push-to-talk voice messaging using the Codec2 ultra-low-bitrate speech
codec, transmitted as V: prefixed packets over the existing MeshCore
LoRa mesh pipeline.

Voice recording UI (long-press send button or + menu) is gated behind
Platform.isIOS || Platform.isMacOS since the `record` package only
supports microphone capture on those platforms in this build.

Key changes:
- VoiceRecorderService: streams 8kHz mono PCM chunks via `record` package
- VoicePlayerService: decodes Codec2 bytes to WAV and plays via audioplayers
- VoiceCodecService: async Codec2 encode/decode in background isolates
- VoiceProvider: reassembles multi-packet sessions, drives playback
- VoiceMessageBubble: shows packet progress, play/stop controls
- MessagesProvider: detects V: prefix, routes to VoiceProvider
- MessagesTab: PTT long-press gesture + recording indicator (iOS/macOS)
- Message model: isVoice + voiceId fields for session tracking
- Auto-selects codec mode from radio bandwidth (700C/1200/1300 bps)
- codec2_flutter + meshcore_client switched from path to git deps
This commit is contained in:
Janez T
2026-02-28 19:19:18 +01:00
parent 3e5b2ac0de
commit f96d8022ac
27 changed files with 1509 additions and 81 deletions

View File

@@ -11,7 +11,10 @@ import 'providers/messages_provider.dart';
import 'providers/map_provider.dart';
import 'providers/drawing_provider.dart';
import 'providers/channels_provider.dart';
import 'providers/voice_provider.dart';
import 'providers/app_provider.dart';
import 'services/voice_codec_service.dart';
import 'services/voice_player_service.dart';
import 'services/tile_cache_service.dart';
import 'services/notification_service.dart';
import 'services/locale_preferences.dart';
@@ -231,10 +234,19 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
),
ChangeNotifierProvider(create: (_) => ChannelsProvider()),
// Voice provider (packet reassembly + playback)
ChangeNotifierProvider(
create: (_) => VoiceProvider(
codec: VoiceCodecService(),
player: VoicePlayerService(),
),
),
// Tile cache service
Provider(create: (_) => TileCacheService()),
// App provider that coordinates everything
// VoiceProvider is read via context.read inside create since it's already registered above
ChangeNotifierProxyProvider6<
ConnectionProvider,
ContactsProvider,
@@ -250,6 +262,7 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
messagesProvider: context.read<MessagesProvider>(),
drawingProvider: context.read<DrawingProvider>(),
channelsProvider: context.read<ChannelsProvider>(),
voiceProvider: context.read<VoiceProvider>(),
tileCacheService: context.read<TileCacheService>(),
),
update:
@@ -270,6 +283,7 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
messagesProvider: messages,
drawingProvider: drawings,
channelsProvider: channels,
voiceProvider: context.read<VoiceProvider>(),
tileCacheService: tileCache,
),
),