neoagent 3.1.1-beta.9 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -2
- package/README.md +5 -1
- package/docs/architecture.md +1 -1
- package/docs/benchmarking.md +68 -87
- package/docs/capabilities.md +1 -1
- package/docs/clients-and-devices.md +3 -4
- package/docs/configuration.md +1 -1
- package/docs/devices.md +1 -2
- package/docs/health.md +24 -0
- package/docs/index.md +2 -2
- package/docs/integrations-architecture.md +6 -0
- package/docs/integrations.md +15 -1
- package/docs/models.md +1 -1
- package/docs/persistence.md +1 -1
- package/docs/why-neoagent.md +1 -1
- package/flutter_app/README.md +0 -5
- package/flutter_app/android/app/src/main/AndroidManifest.xml +0 -14
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +0 -98
- package/flutter_app/lib/main.dart +0 -6
- package/flutter_app/lib/main_app_shell.dart +0 -2
- package/flutter_app/lib/main_controller.dart +2 -867
- package/flutter_app/lib/main_devices.dart +0 -73
- package/flutter_app/lib/main_integrations.dart +339 -187
- package/flutter_app/lib/main_launcher.dart +2 -212
- package/flutter_app/lib/main_models.dart +0 -259
- package/flutter_app/lib/main_navigation.dart +2 -14
- package/flutter_app/lib/main_operations.dart +322 -364
- package/flutter_app/lib/main_runtime.dart +9 -208
- package/flutter_app/lib/main_settings.dart +17 -199
- package/flutter_app/lib/main_shared.dart +20 -591
- package/flutter_app/lib/main_timeline.dart +26 -122
- package/flutter_app/lib/main_voice_assistant.dart +0 -5
- package/flutter_app/lib/src/backend_client.dart +0 -102
- package/flutter_app/lib/src/desktop_companion_io.dart +1 -65
- package/flutter_app/lib/src/desktop_companion_stub.dart +0 -12
- package/flutter_app/linux/flutter/generated_plugin_registrant.cc +0 -4
- package/flutter_app/linux/flutter/generated_plugins.cmake +0 -1
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +0 -2
- package/flutter_app/macos/Podfile.lock +56 -5
- package/flutter_app/pubspec.lock +0 -7
- package/flutter_app/pubspec.yaml +0 -2
- package/flutter_app/web/index.html +174 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.cc +0 -3
- package/flutter_app/windows/flutter/generated_plugins.cmake +0 -1
- package/landing/index.html +0 -2
- package/lib/manager.js +1 -1
- package/lib/schema_migrations.js +75 -0
- package/package.json +1 -4
- package/server/db/database.js +1 -133
- package/server/http/middleware.js +2 -16
- package/server/http/routes.js +0 -2
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +0 -25
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/index.html +174 -0
- package/server/public/main.dart.js +88081 -90036
- package/server/routes/auth.js +11 -2
- package/server/routes/settings.js +0 -5
- package/server/routes/voice_assistant.js +1 -128
- package/server/services/account/erasure.js +0 -3
- package/server/services/ai/deliverables/artifact_helpers.js +1 -1
- package/server/services/ai/loop/conversation_loop.js +6 -2
- package/server/services/ai/loop/tool_dispatch.js +0 -3
- package/server/services/ai/messagingFallback.js +1 -1
- package/server/services/ai/settings.js +0 -6
- package/server/services/ai/toolEvidence.js +1 -4
- package/server/services/ai/tools.js +0 -234
- package/server/services/desktop/auth.js +0 -3
- package/server/services/desktop/registry.js +2 -50
- package/server/services/integrations/neoarchive/provider.js +616 -0
- package/server/services/integrations/neorecall/client.js +140 -0
- package/server/services/integrations/neorecall/constants.js +87 -0
- package/server/services/integrations/neorecall/provider.js +163 -0
- package/server/services/integrations/registry.js +4 -2
- package/server/services/manager.js +0 -22
- package/server/services/memory/manager.js +44 -19
- package/server/services/memory/retrieval_reasoning.js +36 -14
- package/server/services/messaging/whatsapp.js +18 -0
- package/server/services/security/tool_categories.js +0 -5
- package/server/services/social_video/service.js +2 -20
- package/server/services/tasks/adapters/index.js +1 -1
- package/server/services/tasks/integration_runtime.js +3 -67
- package/server/services/tasks/runtime.js +1 -6
- package/server/services/tasks/task_repository.js +6 -13
- package/server/services/timeline/service.js +0 -224
- package/server/services/voice/message.js +0 -24
- package/server/services/voice/providers.js +42 -1
- package/server/services/voice/runtimeManager.js +9 -5
- package/server/services/voice/turnRunner.js +0 -22
- package/server/services/wearable/service.js +0 -5
- package/docs/recordings-and-health.md +0 -41
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +0 -21
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +0 -586
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +0 -78
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +0 -104
- package/flutter_app/lib/main_recordings.dart +0 -920
- package/flutter_app/lib/src/desktop_ocr_bridge.dart +0 -2
- package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +0 -125
- package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +0 -30
- package/flutter_app/lib/src/desktop_passive_history.dart +0 -332
- package/flutter_app/lib/src/recording_bridge.dart +0 -232
- package/flutter_app/lib/src/recording_bridge_io.dart +0 -1027
- package/flutter_app/lib/src/recording_bridge_stub.dart +0 -120
- package/flutter_app/lib/src/recording_bridge_web.dart +0 -702
- package/flutter_app/lib/src/recording_chunk_queue.dart +0 -149
- package/flutter_app/lib/src/recording_chunk_queue_io.dart +0 -182
- package/flutter_app/lib/src/recording_payloads.dart +0 -95
- package/flutter_app/third_party/desktop_audio_capture/LICENSE +0 -21
- package/flutter_app/third_party/desktop_audio_capture/README.md +0 -262
- package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +0 -65
- package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +0 -153
- package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +0 -110
- package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +0 -461
- package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +0 -91
- package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +0 -106
- package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +0 -219
- package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +0 -336
- package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +0 -101
- package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +0 -692
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +0 -35
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +0 -36
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +0 -32
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +0 -32
- package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +0 -878
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +0 -27
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +0 -1172
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +0 -655
- package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +0 -12
- package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +0 -30
- package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +0 -87
- package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +0 -105
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +0 -80
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +0 -31
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +0 -12
- package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +0 -23
- package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +0 -25
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +0 -1117
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +0 -115
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +0 -777
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +0 -87
- package/server/routes/recordings.js +0 -335
- package/server/routes/screenHistory.js +0 -190
- package/server/services/ai/recordingInsights.js +0 -105
- package/server/services/integrations/neomail/provider.js +0 -993
- package/server/services/recordings/deepgram.js +0 -58
- package/server/services/recordings/manager.js +0 -1273
- package/server/services/tasks/adapters/neomail_email_received.js +0 -46
- package/server/services/voice/screenshotContext.js +0 -73
- package/server/services/voice/turnCoordinator.js +0 -51
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
import 'dart:async';
|
|
2
|
-
|
|
3
|
-
import 'package:desktop_audio_capture/audio_capture.dart';
|
|
4
|
-
import 'package:flutter/services.dart';
|
|
5
|
-
export 'package:desktop_audio_capture/config/mic_audio_config.dart';
|
|
6
|
-
|
|
7
|
-
enum _MicAudioMethod {
|
|
8
|
-
startCapture,
|
|
9
|
-
stopCapture,
|
|
10
|
-
requestPermissions,
|
|
11
|
-
hasInputDevice,
|
|
12
|
-
getAvailableInputDevices,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/// Class for capturing audio from microphone input devices.
|
|
16
|
-
///
|
|
17
|
-
/// This class allows you to capture audio from connected microphones,
|
|
18
|
-
/// including built-in microphones, external USB microphones, and Bluetooth
|
|
19
|
-
/// microphones. It requires microphone permissions.
|
|
20
|
-
///
|
|
21
|
-
/// Example:
|
|
22
|
-
/// ```dart
|
|
23
|
-
/// final micCapture = MicAudioCapture(
|
|
24
|
-
/// config: MicAudioConfig(
|
|
25
|
-
/// sampleRate: 44100,
|
|
26
|
-
/// channels: 1,
|
|
27
|
-
/// gainBoost: 2.5,
|
|
28
|
-
/// ),
|
|
29
|
-
/// );
|
|
30
|
-
///
|
|
31
|
-
/// // Check for available devices
|
|
32
|
-
/// final devices = await micCapture.getAvailableInputDevices();
|
|
33
|
-
/// print('Available microphones: ${devices.length}');
|
|
34
|
-
///
|
|
35
|
-
/// await micCapture.startCapture();
|
|
36
|
-
///
|
|
37
|
-
/// // Listen to audio stream
|
|
38
|
-
/// micCapture.audioStream?.listen((audioData) {
|
|
39
|
-
/// // Process audio bytes
|
|
40
|
-
/// print('Received ${audioData.length} bytes');
|
|
41
|
-
/// });
|
|
42
|
-
///
|
|
43
|
-
/// // Listen to status updates
|
|
44
|
-
/// micCapture.statusStream?.listen((status) {
|
|
45
|
-
/// print('Mic active: ${status.isActive}, Device: ${status.deviceName}');
|
|
46
|
-
/// });
|
|
47
|
-
///
|
|
48
|
-
/// // Listen to decibel readings
|
|
49
|
-
/// micCapture.decibelStream?.listen((data) {
|
|
50
|
-
/// print('Mic level: ${data.decibel} dB');
|
|
51
|
-
/// });
|
|
52
|
-
///
|
|
53
|
-
/// // Stop when done
|
|
54
|
-
/// await micCapture.stopCapture();
|
|
55
|
-
/// ```
|
|
56
|
-
class MicAudioCapture extends AudioCapture {
|
|
57
|
-
static const MethodChannel _channel = MethodChannel(
|
|
58
|
-
'com.mic_audio_transcriber/mic_capture',
|
|
59
|
-
);
|
|
60
|
-
static const EventChannel _audioStreamChannel = EventChannel(
|
|
61
|
-
'com.mic_audio_transcriber/mic_stream',
|
|
62
|
-
);
|
|
63
|
-
static const EventChannel _statusStreamChannel = EventChannel(
|
|
64
|
-
'com.mic_audio_transcriber/mic_status',
|
|
65
|
-
);
|
|
66
|
-
static const EventChannel _decibelStreamChannel = EventChannel(
|
|
67
|
-
'com.mic_audio_transcriber/mic_decibel',
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
Stream<Uint8List>? _audioStream;
|
|
71
|
-
Stream<MicAudioStatus>? _statusStream;
|
|
72
|
-
Stream<DecibelData>? _decibelStream;
|
|
73
|
-
bool _isRecording = false;
|
|
74
|
-
|
|
75
|
-
/// Stream of raw audio data bytes from microphone capture.
|
|
76
|
-
///
|
|
77
|
-
/// Returns a [Stream<Uint8List>] containing the captured audio data.
|
|
78
|
-
/// The stream is only available after [startCapture] has been called.
|
|
79
|
-
///
|
|
80
|
-
/// Example:
|
|
81
|
-
/// ```dart
|
|
82
|
-
/// await micCapture.startCapture();
|
|
83
|
-
///
|
|
84
|
-
/// micCapture.audioStream?.listen((audioData) {
|
|
85
|
-
/// // Process audio bytes
|
|
86
|
-
/// final audioBuffer = audioData.buffer.asUint8List();
|
|
87
|
-
/// // Use audio buffer for processing, saving, streaming, etc.
|
|
88
|
-
/// });
|
|
89
|
-
/// ```
|
|
90
|
-
Stream<Uint8List>? get audioStream {
|
|
91
|
-
// Return existing stream if available
|
|
92
|
-
if (_audioStream != null) {
|
|
93
|
-
return _audioStream;
|
|
94
|
-
}
|
|
95
|
-
// If not recording, return null
|
|
96
|
-
if (!_isRecording) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
// Create stream lazily if recording but stream not created yet
|
|
100
|
-
_audioStream = _audioStreamChannel.receiveBroadcastStream().map((
|
|
101
|
-
dynamic event,
|
|
102
|
-
) {
|
|
103
|
-
if (event is Uint8List) {
|
|
104
|
-
return event;
|
|
105
|
-
} else if (event is List<int>) {
|
|
106
|
-
return Uint8List.fromList(event);
|
|
107
|
-
}
|
|
108
|
-
throw Exception('Unexpected audio data type: ${event.runtimeType}');
|
|
109
|
-
});
|
|
110
|
-
return _audioStream;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/// Stream of microphone status updates.
|
|
114
|
-
///
|
|
115
|
-
/// Returns a [Stream<MicStatus>] containing:
|
|
116
|
-
/// - `isActive`: bool - whether mic is currently active
|
|
117
|
-
/// - `deviceName`: String? - name of the microphone device (if available)
|
|
118
|
-
///
|
|
119
|
-
/// Example:
|
|
120
|
-
/// ```dart
|
|
121
|
-
/// micCapture.statusStream?.listen((status) {
|
|
122
|
-
/// if (status.isActive) {
|
|
123
|
-
/// print('Microphone is active');
|
|
124
|
-
/// if (status.deviceName != null) {
|
|
125
|
-
/// print('Using device: ${status.deviceName}');
|
|
126
|
-
/// }
|
|
127
|
-
/// } else {
|
|
128
|
-
/// print('Microphone is inactive');
|
|
129
|
-
/// }
|
|
130
|
-
/// });
|
|
131
|
-
/// ```
|
|
132
|
-
Stream<MicAudioStatus>? get statusStream {
|
|
133
|
-
// Create status stream if not already created
|
|
134
|
-
_statusStream ??= _statusStreamChannel.receiveBroadcastStream().map((
|
|
135
|
-
dynamic event,
|
|
136
|
-
) {
|
|
137
|
-
if (event is Map) {
|
|
138
|
-
return MicAudioStatus.fromJson(Map<String, dynamic>.from(event));
|
|
139
|
-
}
|
|
140
|
-
return const MicAudioStatus(isActive: false);
|
|
141
|
-
});
|
|
142
|
-
return _statusStream;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/// Stream of microphone decibel (dB) readings.
|
|
146
|
-
///
|
|
147
|
-
/// Returns a [Stream<DecibelData>] containing:
|
|
148
|
-
/// - `decibel`: double - decibel value (-120 to 0 dB)
|
|
149
|
-
/// - `timestamp`: double - Unix timestamp
|
|
150
|
-
///
|
|
151
|
-
/// The stream is only available while recording is active.
|
|
152
|
-
///
|
|
153
|
-
/// Example:
|
|
154
|
-
/// ```dart
|
|
155
|
-
/// await micCapture.startCapture();
|
|
156
|
-
///
|
|
157
|
-
/// micCapture.decibelStream?.listen((data) {
|
|
158
|
-
/// print('Microphone level: ${data.decibel.toStringAsFixed(1)} dB');
|
|
159
|
-
/// print('Timestamp: ${DateTime.fromMillisecondsSinceEpoch((data.timestamp * 1000).toInt())}');
|
|
160
|
-
///
|
|
161
|
-
/// // Use for visual feedback, volume meters, etc.
|
|
162
|
-
/// if (data.decibel > -40) {
|
|
163
|
-
/// print('Loud input detected!');
|
|
164
|
-
/// }
|
|
165
|
-
/// });
|
|
166
|
-
/// ```
|
|
167
|
-
Stream<DecibelData>? get decibelStream => _decibelStream;
|
|
168
|
-
|
|
169
|
-
MicAudioConfig _config = MicAudioConfig();
|
|
170
|
-
|
|
171
|
-
/// Creates a new [MicAudioCapture] instance.
|
|
172
|
-
///
|
|
173
|
-
/// [config] is optional. If not provided, default configuration will be used
|
|
174
|
-
/// (sampleRate: 16000, channels: 1, bitDepth: 16, gainBoost: 2.5, inputVolume: 1.0).
|
|
175
|
-
///
|
|
176
|
-
/// Example:
|
|
177
|
-
/// ```dart
|
|
178
|
-
/// final capture = MicAudioCapture(
|
|
179
|
-
/// config: MicAudioConfig(
|
|
180
|
-
/// sampleRate: 44100,
|
|
181
|
-
/// channels: 1,
|
|
182
|
-
/// gainBoost: 3.0,
|
|
183
|
-
/// inputVolume: 0.8,
|
|
184
|
-
/// ),
|
|
185
|
-
/// );
|
|
186
|
-
/// ```
|
|
187
|
-
MicAudioCapture({MicAudioConfig? config}) {
|
|
188
|
-
_config = config ?? MicAudioConfig();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/// Updates the audio capture configuration.
|
|
192
|
-
///
|
|
193
|
-
/// This method allows you to change the configuration after the instance
|
|
194
|
-
/// has been created. The new configuration will be applied on the next
|
|
195
|
-
/// [startCapture] call.
|
|
196
|
-
///
|
|
197
|
-
/// Example:
|
|
198
|
-
/// ```dart
|
|
199
|
-
/// final capture = MicAudioCapture();
|
|
200
|
-
///
|
|
201
|
-
/// // Update config before starting
|
|
202
|
-
/// capture.updateConfig(MicAudioConfig(
|
|
203
|
-
/// sampleRate: 48000,
|
|
204
|
-
/// channels: 2,
|
|
205
|
-
/// gainBoost: 2.0,
|
|
206
|
-
/// ));
|
|
207
|
-
///
|
|
208
|
-
/// await capture.startCapture();
|
|
209
|
-
/// ```
|
|
210
|
-
void updateConfig(MicAudioConfig config) {
|
|
211
|
-
_config = config;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/// Starts capturing audio from the microphone.
|
|
215
|
-
///
|
|
216
|
-
/// This method will request necessary permissions (microphone permission)
|
|
217
|
-
/// and begin capturing audio from the default or configured microphone.
|
|
218
|
-
///
|
|
219
|
-
/// [config] is optional. If provided, it will update the current configuration
|
|
220
|
-
/// before starting capture.
|
|
221
|
-
///
|
|
222
|
-
/// Throws an [Exception] if:
|
|
223
|
-
/// - Permissions are not granted
|
|
224
|
-
/// - No microphone is available
|
|
225
|
-
/// - Capture fails to start
|
|
226
|
-
///
|
|
227
|
-
/// Example:
|
|
228
|
-
/// ```dart
|
|
229
|
-
/// final capture = MicAudioCapture();
|
|
230
|
-
///
|
|
231
|
-
/// try {
|
|
232
|
-
/// await capture.startCapture(
|
|
233
|
-
/// config: MicAudioConfig(
|
|
234
|
-
/// sampleRate: 44100,
|
|
235
|
-
/// channels: 1,
|
|
236
|
-
/// gainBoost: 2.5,
|
|
237
|
-
/// ),
|
|
238
|
-
/// );
|
|
239
|
-
/// print('Microphone capture started');
|
|
240
|
-
/// } catch (e) {
|
|
241
|
-
/// print('Failed to start: $e');
|
|
242
|
-
/// }
|
|
243
|
-
/// ```
|
|
244
|
-
Future<void> startCapture({MicAudioConfig? config}) async {
|
|
245
|
-
if (_isRecording) {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (config != null) {
|
|
250
|
-
updateConfig(config);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
await requestPermissions();
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
final result = await _channel.invokeMethod<dynamic>(
|
|
258
|
-
_MicAudioMethod.startCapture.name,
|
|
259
|
-
_config.toMap(),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
if (result is! bool || result != true) {
|
|
263
|
-
final errorMsg = result is String
|
|
264
|
-
? result
|
|
265
|
-
: 'Failed to start microphone capture. Returned: $result';
|
|
266
|
-
throw Exception(errorMsg);
|
|
267
|
-
}
|
|
268
|
-
} on PlatformException catch (e) {
|
|
269
|
-
throw Exception(
|
|
270
|
-
'Failed to start microphone capture: ${e.message ?? e.code}');
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Create audio stream
|
|
274
|
-
// Note: Stream will be subscribed by listeners, which triggers onListen on native side
|
|
275
|
-
_audioStream = _audioStreamChannel.receiveBroadcastStream().map((
|
|
276
|
-
dynamic event,
|
|
277
|
-
) {
|
|
278
|
-
if (event is Uint8List) {
|
|
279
|
-
return event;
|
|
280
|
-
} else if (event is List<int>) {
|
|
281
|
-
return Uint8List.fromList(event);
|
|
282
|
-
}
|
|
283
|
-
throw Exception('Unexpected audio data type: ${event.runtimeType}');
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// Create decibel stream
|
|
287
|
-
_decibelStream = _decibelStreamChannel.receiveBroadcastStream().map((
|
|
288
|
-
dynamic event,
|
|
289
|
-
) {
|
|
290
|
-
if (event is Map) {
|
|
291
|
-
return DecibelData.fromMap(Map<String, dynamic>.from(event));
|
|
292
|
-
}
|
|
293
|
-
return DecibelData(
|
|
294
|
-
decibel: -120.0,
|
|
295
|
-
timestamp: DateTime.now().millisecondsSinceEpoch / 1000.0);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Status stream is created lazily via getter, no need to recreate here
|
|
299
|
-
|
|
300
|
-
_isRecording = true;
|
|
301
|
-
} catch (e) {
|
|
302
|
-
rethrow;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/// Stops capturing microphone audio.
|
|
307
|
-
///
|
|
308
|
-
/// This method will stop the active capture and close all associated streams.
|
|
309
|
-
/// If capture is not active, this method does nothing.
|
|
310
|
-
///
|
|
311
|
-
/// Throws an [Exception] if stopping fails.
|
|
312
|
-
///
|
|
313
|
-
/// Example:
|
|
314
|
-
/// ```dart
|
|
315
|
-
/// await micCapture.startCapture();
|
|
316
|
-
///
|
|
317
|
-
/// // ... use audio stream ...
|
|
318
|
-
///
|
|
319
|
-
/// await micCapture.stopCapture();
|
|
320
|
-
/// print('Microphone capture stopped');
|
|
321
|
-
/// ```
|
|
322
|
-
Future<void> stopCapture() async {
|
|
323
|
-
if (!_isRecording) return;
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
final stoped = await _channel.invokeMethod<bool>(
|
|
327
|
-
_MicAudioMethod.stopCapture.name,
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
if (stoped != true) {
|
|
331
|
-
throw Exception("Failed to stop microphone capture");
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
_isRecording = false;
|
|
335
|
-
_audioStream = null;
|
|
336
|
-
_statusStream = null;
|
|
337
|
-
_decibelStream = null;
|
|
338
|
-
} catch (e) {
|
|
339
|
-
rethrow;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/// Whether microphone capture is currently recording.
|
|
344
|
-
///
|
|
345
|
-
/// Returns `true` if capture is active, `false` otherwise.
|
|
346
|
-
///
|
|
347
|
-
/// Example:
|
|
348
|
-
/// ```dart
|
|
349
|
-
/// if (micCapture.isRecording) {
|
|
350
|
-
/// print('Microphone is being captured');
|
|
351
|
-
/// } else {
|
|
352
|
-
/// print('Microphone capture is not active');
|
|
353
|
-
/// }
|
|
354
|
-
/// ```
|
|
355
|
-
@override
|
|
356
|
-
bool get isRecording => _isRecording;
|
|
357
|
-
|
|
358
|
-
/// Requests necessary permissions for microphone capture.
|
|
359
|
-
///
|
|
360
|
-
/// This requests microphone permission which is required to capture audio
|
|
361
|
-
/// from input devices.
|
|
362
|
-
///
|
|
363
|
-
/// Returns `true` if permissions are granted.
|
|
364
|
-
///
|
|
365
|
-
/// Throws an [Exception] if permissions are not granted.
|
|
366
|
-
///
|
|
367
|
-
/// Example:
|
|
368
|
-
/// ```dart
|
|
369
|
-
/// try {
|
|
370
|
-
/// final hasPermission = await micCapture.requestPermissions();
|
|
371
|
-
/// if (hasPermission) {
|
|
372
|
-
/// await micCapture.startCapture();
|
|
373
|
-
/// }
|
|
374
|
-
/// } catch (e) {
|
|
375
|
-
/// print('Permission denied: $e');
|
|
376
|
-
/// }
|
|
377
|
-
/// ```
|
|
378
|
-
Future<bool> requestPermissions() async {
|
|
379
|
-
final hasPermission = await _channel.invokeMethod<bool>(
|
|
380
|
-
_MicAudioMethod.requestPermissions.name,
|
|
381
|
-
);
|
|
382
|
-
if (hasPermission != true) {
|
|
383
|
-
throw Exception('Microphone permission not granted');
|
|
384
|
-
}
|
|
385
|
-
return true;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/// Checks if there is any available input device (microphone).
|
|
389
|
-
///
|
|
390
|
-
/// Returns `true` if there is at least one available input device,
|
|
391
|
-
/// `false` if there is no available input device.
|
|
392
|
-
///
|
|
393
|
-
/// Example:
|
|
394
|
-
/// ```dart
|
|
395
|
-
/// final hasDevice = await micCapture.hasInputDevice();
|
|
396
|
-
/// if (hasDevice) {
|
|
397
|
-
/// print('Microphone available');
|
|
398
|
-
/// await micCapture.startCapture();
|
|
399
|
-
/// } else {
|
|
400
|
-
/// print('No microphone found');
|
|
401
|
-
/// }
|
|
402
|
-
/// ```
|
|
403
|
-
Future<bool> hasInputDevice() async {
|
|
404
|
-
try {
|
|
405
|
-
final hasDevice = await _channel.invokeMethod<bool>(
|
|
406
|
-
_MicAudioMethod.hasInputDevice.name,
|
|
407
|
-
);
|
|
408
|
-
return hasDevice ?? false;
|
|
409
|
-
} catch (e) {
|
|
410
|
-
rethrow;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/// Gets a list of all available input devices (microphones).
|
|
415
|
-
///
|
|
416
|
-
/// Returns a list of [InputDevice] containing device information:
|
|
417
|
-
/// - `id`: String - unique device ID
|
|
418
|
-
/// - `name`: String - device name
|
|
419
|
-
/// - `type`: [InputDeviceType] - device type (builtIn, bluetooth, external)
|
|
420
|
-
/// - `channelCount`: int - number of audio channels
|
|
421
|
-
/// - `isDefault`: bool - whether the device is the default device
|
|
422
|
-
///
|
|
423
|
-
/// Example:
|
|
424
|
-
/// ```dart
|
|
425
|
-
/// final devices = await micCapture.getAvailableInputDevices();
|
|
426
|
-
///
|
|
427
|
-
/// for (final device in devices) {
|
|
428
|
-
/// print('Device: ${device.name}');
|
|
429
|
-
/// print(' Type: ${device.type}');
|
|
430
|
-
/// print(' Channels: ${device.channelCount}');
|
|
431
|
-
/// print(' Default: ${device.isDefault}');
|
|
432
|
-
/// }
|
|
433
|
-
///
|
|
434
|
-
/// // Find default device
|
|
435
|
-
/// final defaultDevice = devices.firstWhere(
|
|
436
|
-
/// (device) => device.isDefault,
|
|
437
|
-
/// orElse: () => devices.first,
|
|
438
|
-
/// );
|
|
439
|
-
/// print('Using device: ${defaultDevice.name}');
|
|
440
|
-
/// ```
|
|
441
|
-
Future<List<InputDevice>> getAvailableInputDevices() async {
|
|
442
|
-
try {
|
|
443
|
-
final devices = await _channel.invokeMethod<List<dynamic>>(
|
|
444
|
-
_MicAudioMethod.getAvailableInputDevices.name,
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
if (devices == null) {
|
|
448
|
-
return [];
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return devices
|
|
452
|
-
.whereType<Map>()
|
|
453
|
-
.map((device) => InputDevice.fromMap(
|
|
454
|
-
Map<String, dynamic>.from(device),
|
|
455
|
-
))
|
|
456
|
-
.toList();
|
|
457
|
-
} catch (e) {
|
|
458
|
-
rethrow;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
abstract class AudioStatus {
|
|
2
|
-
final bool isActive;
|
|
3
|
-
|
|
4
|
-
const AudioStatus({
|
|
5
|
-
required this.isActive,
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
Map<String, dynamic> toJson();
|
|
9
|
-
|
|
10
|
-
@override
|
|
11
|
-
bool operator ==(Object other) {
|
|
12
|
-
if (identical(this, other)) return true;
|
|
13
|
-
|
|
14
|
-
return other is AudioStatus && other.isActive == isActive;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@override
|
|
18
|
-
int get hashCode => isActive.hashCode;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
class MicAudioStatus extends AudioStatus {
|
|
22
|
-
final String? deviceName;
|
|
23
|
-
|
|
24
|
-
const MicAudioStatus({
|
|
25
|
-
required super.isActive,
|
|
26
|
-
this.deviceName,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
MicAudioStatus copyWith({
|
|
30
|
-
bool? isActive,
|
|
31
|
-
String? deviceName,
|
|
32
|
-
}) {
|
|
33
|
-
return MicAudioStatus(
|
|
34
|
-
isActive: isActive ?? this.isActive,
|
|
35
|
-
deviceName: deviceName ?? this.deviceName,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
factory MicAudioStatus.fromJson(Map<String, dynamic> json) {
|
|
40
|
-
return MicAudioStatus(
|
|
41
|
-
isActive: json['isActive'],
|
|
42
|
-
deviceName: json['deviceName'],
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@override
|
|
47
|
-
Map<String, dynamic> toJson() {
|
|
48
|
-
return {
|
|
49
|
-
'isActive': isActive,
|
|
50
|
-
'deviceName': deviceName,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@override
|
|
55
|
-
String toString() =>
|
|
56
|
-
'''MicAudioStatus(isActive: $isActive, deviceName: $deviceName)''';
|
|
57
|
-
|
|
58
|
-
@override
|
|
59
|
-
bool operator ==(Object other) {
|
|
60
|
-
if (identical(this, other)) return true;
|
|
61
|
-
|
|
62
|
-
return other is MicAudioStatus && other.deviceName == deviceName;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@override
|
|
66
|
-
int get hashCode => deviceName.hashCode;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
class SystemAudioStatus extends AudioStatus {
|
|
70
|
-
SystemAudioStatus({required super.isActive});
|
|
71
|
-
|
|
72
|
-
SystemAudioStatus copyWith({
|
|
73
|
-
bool? isActive,
|
|
74
|
-
}) {
|
|
75
|
-
return SystemAudioStatus(isActive: isActive ?? this.isActive);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
factory SystemAudioStatus.fromJson(Map<String, dynamic> json) {
|
|
79
|
-
return SystemAudioStatus(isActive: json['isActive']);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@override
|
|
83
|
-
Map<String, dynamic> toJson() {
|
|
84
|
-
return {
|
|
85
|
-
'isActive': isActive,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@override
|
|
90
|
-
String toString() => '''SystemAudioStatus(isActive: $isActive)''';
|
|
91
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/// Decibel data from audio capture.
|
|
2
|
-
///
|
|
3
|
-
/// This class represents a single decibel reading with its timestamp.
|
|
4
|
-
/// Used by both microphone and system audio capture to provide volume level
|
|
5
|
-
/// information.
|
|
6
|
-
///
|
|
7
|
-
/// Example:
|
|
8
|
-
/// ```dart
|
|
9
|
-
/// // From stream
|
|
10
|
-
/// capture.decibelStream?.listen((data) {
|
|
11
|
-
/// print('Decibel: ${data.decibel} dB');
|
|
12
|
-
/// print('Time: ${DateTime.fromMillisecondsSinceEpoch((data.timestamp * 1000).toInt())}');
|
|
13
|
-
/// });
|
|
14
|
-
///
|
|
15
|
-
/// // Create manually
|
|
16
|
-
/// final data = DecibelData(
|
|
17
|
-
/// decibel: -45.5,
|
|
18
|
-
/// timestamp: DateTime.now().millisecondsSinceEpoch / 1000.0,
|
|
19
|
-
/// );
|
|
20
|
-
///
|
|
21
|
-
/// // Convert to/from map
|
|
22
|
-
/// final map = data.toMap();
|
|
23
|
-
/// final restored = DecibelData.fromMap(map);
|
|
24
|
-
/// ```
|
|
25
|
-
class DecibelData {
|
|
26
|
-
/// Decibel value in dB, typically ranging from -120 to 0 dB.
|
|
27
|
-
///
|
|
28
|
-
/// - -120 dB: silence or very quiet
|
|
29
|
-
/// - -60 dB: quiet background noise
|
|
30
|
-
/// - -40 dB: normal speech
|
|
31
|
-
/// - -20 dB: loud speech
|
|
32
|
-
/// - 0 dB: maximum level
|
|
33
|
-
final double decibel;
|
|
34
|
-
|
|
35
|
-
/// Unix timestamp in seconds (not milliseconds).
|
|
36
|
-
///
|
|
37
|
-
/// This represents when the decibel reading was taken.
|
|
38
|
-
final double timestamp;
|
|
39
|
-
|
|
40
|
-
/// Creates a new [DecibelData] instance.
|
|
41
|
-
///
|
|
42
|
-
/// [decibel] should be in the range -120 to 0 dB.
|
|
43
|
-
/// [timestamp] should be a Unix timestamp in seconds.
|
|
44
|
-
///
|
|
45
|
-
/// Example:
|
|
46
|
-
/// ```dart
|
|
47
|
-
/// final data = DecibelData(
|
|
48
|
-
/// decibel: -45.0,
|
|
49
|
-
/// timestamp: DateTime.now().millisecondsSinceEpoch / 1000.0,
|
|
50
|
-
/// );
|
|
51
|
-
/// ```
|
|
52
|
-
const DecibelData({
|
|
53
|
-
required this.decibel,
|
|
54
|
-
required this.timestamp,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
/// Creates a [DecibelData] instance from a map.
|
|
58
|
-
///
|
|
59
|
-
/// The map should contain:
|
|
60
|
-
/// - `decibel`: num (will be converted to double)
|
|
61
|
-
/// - `timestamp`: num (will be converted to double)
|
|
62
|
-
///
|
|
63
|
-
/// If values are missing, defaults to -120.0 dB and current timestamp.
|
|
64
|
-
///
|
|
65
|
-
/// Example:
|
|
66
|
-
/// ```dart
|
|
67
|
-
/// final map = {
|
|
68
|
-
/// 'decibel': -45.5,
|
|
69
|
-
/// 'timestamp': 1234567890.0,
|
|
70
|
-
/// };
|
|
71
|
-
/// final data = DecibelData.fromMap(map);
|
|
72
|
-
/// ```
|
|
73
|
-
factory DecibelData.fromMap(Map<String, dynamic> map) {
|
|
74
|
-
return DecibelData(
|
|
75
|
-
decibel: (map['decibel'] as num?)?.toDouble() ?? -120.0,
|
|
76
|
-
timestamp: (map['timestamp'] as num?)?.toDouble() ??
|
|
77
|
-
DateTime.now().millisecondsSinceEpoch / 1000.0,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/// Converts this [DecibelData] instance to a map.
|
|
82
|
-
///
|
|
83
|
-
/// Returns a map containing:
|
|
84
|
-
/// - `decibel`: double
|
|
85
|
-
/// - `timestamp`: double
|
|
86
|
-
///
|
|
87
|
-
/// Example:
|
|
88
|
-
/// ```dart
|
|
89
|
-
/// final data = DecibelData(
|
|
90
|
-
/// decibel: -45.0,
|
|
91
|
-
/// timestamp: 1234567890.0,
|
|
92
|
-
/// );
|
|
93
|
-
/// final map = data.toMap();
|
|
94
|
-
/// // map = {'decibel': -45.0, 'timestamp': 1234567890.0}
|
|
95
|
-
/// ```
|
|
96
|
-
Map<String, dynamic> toMap() {
|
|
97
|
-
return {
|
|
98
|
-
'decibel': decibel,
|
|
99
|
-
'timestamp': timestamp,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
@override
|
|
104
|
-
String toString() =>
|
|
105
|
-
'DecibelData(decibel: ${decibel.toStringAsFixed(1)} dB, timestamp: $timestamp)';
|
|
106
|
-
}
|