neoagent 2.3.1-beta.2 → 2.3.1-beta.21
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 +39 -0
- package/README.md +2 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +13 -5
- package/docs/integrations.md +4 -1
- package/flutter_app/.metadata +42 -0
- package/flutter_app/README.md +21 -0
- package/flutter_app/analysis_options.yaml +32 -0
- package/flutter_app/android/app/build.gradle.kts +109 -0
- package/flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +147 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +747 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthConnectGateway.kt +280 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncNotifications.kt +113 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncPayload.kt +57 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncScheduler.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/HealthSyncWorker.kt +253 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/health/PermissionsRationaleActivity.kt +46 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +21 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +586 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +78 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +104 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiHomeWidgetProvider.kt +457 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/AiWidgetStore.kt +194 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/VoiceLaunchWidgetProvider.kt +67 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetConfigActivity.kt +228 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncScheduler.kt +72 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetSyncWorker.kt +186 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/widgets/WidgetTaskRunWorker.kt +210 -0
- package/flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_bg.xml +11 -0
- package/flutter_app/android/app/src/main/res/drawable/neoagent_ai_widget_task_bg.xml +8 -0
- package/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget.xml +138 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_ai_widget_task_row.xml +52 -0
- package/flutter_app/android/app/src/main/res/layout/neoagent_voice_widget.xml +49 -0
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/values/strings.xml +12 -0
- package/flutter_app/android/app/src/main/res/values/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/flutter_app/android/app/src/main/res/xml/file_paths.xml +6 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_ai_widget_info.xml +12 -0
- package/flutter_app/android/app/src/main/res/xml/neoagent_voice_widget_info.xml +12 -0
- package/flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/flutter_app/android/build.gradle.kts +24 -0
- package/flutter_app/android/ci-release.keystore +0 -0
- package/flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/flutter_app/android/gradle.properties +3 -0
- package/flutter_app/android/key.properties +4 -0
- package/flutter_app/android/settings.gradle.kts +26 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +119 -0
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +97 -0
- package/flutter_app/lib/main.dart +23057 -0
- package/flutter_app/lib/main_app_shell.dart +1682 -0
- package/flutter_app/lib/main_integrations.dart +931 -0
- package/flutter_app/lib/main_launcher.dart +959 -0
- package/flutter_app/lib/main_launcher_entry.dart +5 -0
- package/flutter_app/lib/main_models.dart +3473 -0
- package/flutter_app/lib/main_shared.dart +2861 -0
- package/flutter_app/lib/main_theme.dart +204 -0
- package/flutter_app/lib/main_voice_assistant.dart +831 -0
- package/flutter_app/lib/src/android_apk_drop_zone.dart +32 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +16 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +348 -0
- package/flutter_app/lib/src/android_app_installer.dart +22 -0
- package/flutter_app/lib/src/android_app_installer_io.dart +122 -0
- package/flutter_app/lib/src/android_app_installer_stub.dart +21 -0
- package/flutter_app/lib/src/android_launcher_bridge.dart +239 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +29 -0
- package/flutter_app/lib/src/app_release_updater.dart +511 -0
- package/flutter_app/lib/src/backend_client.dart +1833 -0
- package/flutter_app/lib/src/desktop_companion.dart +2 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +586 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +538 -0
- package/flutter_app/lib/src/desktop_companion_stub.dart +59 -0
- package/flutter_app/lib/src/desktop_native_bridge.dart +91 -0
- package/flutter_app/lib/src/desktop_screen_capture.dart +21 -0
- package/flutter_app/lib/src/desktop_screen_capture_io.dart +142 -0
- package/flutter_app/lib/src/desktop_screen_capture_stub.dart +12 -0
- package/flutter_app/lib/src/diagnostics_logger.dart +119 -0
- package/flutter_app/lib/src/health_bridge.dart +136 -0
- package/flutter_app/lib/src/live_voice_capture.dart +85 -0
- package/flutter_app/lib/src/messaging_access_summary.dart +46 -0
- package/flutter_app/lib/src/network/app_http_client.dart +53 -0
- package/flutter_app/lib/src/network/app_http_client_factory.dart +6 -0
- package/flutter_app/lib/src/network/app_http_client_io.dart +138 -0
- package/flutter_app/lib/src/network/app_http_client_stub.dart +3 -0
- package/flutter_app/lib/src/network/app_http_client_web.dart +94 -0
- package/flutter_app/lib/src/oauth_launcher.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_io.dart +77 -0
- package/flutter_app/lib/src/oauth_launcher_stub.dart +33 -0
- package/flutter_app/lib/src/oauth_launcher_web.dart +107 -0
- package/flutter_app/lib/src/recording_bridge.dart +232 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +1019 -0
- package/flutter_app/lib/src/recording_bridge_stub.dart +120 -0
- package/flutter_app/lib/src/recording_bridge_web.dart +689 -0
- package/flutter_app/lib/src/recording_payloads.dart +86 -0
- package/flutter_app/lib/src/theme/palette.dart +81 -0
- package/flutter_app/lib/src/widget_bridge.dart +49 -0
- package/flutter_app/linux/CMakeLists.txt +128 -0
- package/flutter_app/linux/flutter/CMakeLists.txt +88 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.cc +43 -0
- package/flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/linux/flutter/generated_plugins.cmake +31 -0
- package/flutter_app/linux/runner/CMakeLists.txt +26 -0
- package/flutter_app/linux/runner/main.cc +6 -0
- package/flutter_app/linux/runner/my_application.cc +144 -0
- package/flutter_app/linux/runner/my_application.h +18 -0
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
- package/flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +40 -0
- package/flutter_app/macos/Podfile +42 -0
- package/flutter_app/macos/Podfile.lock +87 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +576 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/macos/Runner/Base.lproj/MainMenu.xib +342 -0
- package/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
- package/flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
- package/flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
- package/flutter_app/macos/Runner/DebugProfile.entitlements +16 -0
- package/flutter_app/macos/Runner/Info.plist +36 -0
- package/flutter_app/macos/Runner/MainFlutterWindow.swift +19 -0
- package/flutter_app/macos/Runner/Release.entitlements +12 -0
- package/flutter_app/macos/Runner.xcodeproj/project.pbxproj +801 -0
- package/flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
- package/flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
- package/flutter_app/patch_strings.py +12 -0
- package/flutter_app/pubspec.lock +1088 -0
- package/flutter_app/pubspec.yaml +53 -0
- package/flutter_app/test/messaging_access_summary_test.dart +22 -0
- package/flutter_app/test/recording_payloads_test.dart +53 -0
- package/flutter_app/third_party/desktop_audio_capture/LICENSE +21 -0
- package/flutter_app/third_party/desktop_audio_capture/README.md +262 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +65 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +153 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +110 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +461 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +91 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +106 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +219 -0
- package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +336 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +101 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +692 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +35 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +36 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +32 -0
- package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +878 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +27 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +1172 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +655 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +12 -0
- package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +30 -0
- package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +87 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +105 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +80 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +31 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +12 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +23 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +25 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +1117 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +115 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +777 -0
- package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +87 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/CMakeLists.txt +30 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/flutter_secure_storage_linux_plugin.cc +215 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/linux/include/flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h +27 -0
- package/flutter_app/third_party/flutter_secure_storage_linux/pubspec.yaml +20 -0
- package/flutter_app/tool/generate_desktop_branding.py +219 -0
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +12 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/web/index.html +39 -0
- package/flutter_app/web/manifest.json +35 -0
- package/flutter_app/windows/CMakeLists.txt +108 -0
- package/flutter_app/windows/flutter/CMakeLists.txt +109 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.cc +47 -0
- package/flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
- package/flutter_app/windows/flutter/generated_plugins.cmake +35 -0
- package/flutter_app/windows/runner/CMakeLists.txt +41 -0
- package/flutter_app/windows/runner/Runner.rc +121 -0
- package/flutter_app/windows/runner/flutter_window.cpp +533 -0
- package/flutter_app/windows/runner/flutter_window.h +37 -0
- package/flutter_app/windows/runner/main.cpp +53 -0
- package/flutter_app/windows/runner/resource.h +16 -0
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/flutter_app/windows/runner/runner.exe.manifest +14 -0
- package/flutter_app/windows/runner/utils.cpp +65 -0
- package/flutter_app/windows/runner/utils.h +19 -0
- package/flutter_app/windows/runner/win32_window.cpp +299 -0
- package/flutter_app/windows/runner/win32_window.h +102 -0
- package/lib/manager.js +231 -7
- package/package.json +3 -1
- package/server/db/database.js +68 -0
- package/server/http/middleware.js +50 -0
- package/server/http/routes.js +3 -1
- package/server/index.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +61 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +65262 -64422
- package/server/routes/integrations.js +86 -0
- package/server/routes/memory.js +11 -2
- package/server/routes/screenHistory.js +46 -0
- package/server/routes/triggers.js +81 -0
- package/server/services/ai/models.js +30 -0
- package/server/services/ai/providers/githubCopilot.js +97 -0
- package/server/services/ai/providers/openai.js +2 -1
- package/server/services/ai/providers/openaiCodex.js +31 -0
- package/server/services/ai/settings.js +20 -0
- package/server/services/ai/systemPrompt.js +1 -1
- package/server/services/ai/tools.js +35 -6
- package/server/services/browser/controller.js +47 -3
- package/server/services/desktop/screenRecorder.js +172 -0
- package/server/services/integrations/env.js +5 -0
- package/server/services/integrations/github/common.js +106 -0
- package/server/services/integrations/github/provider.js +499 -0
- package/server/services/integrations/github/repos.js +1124 -0
- package/server/services/integrations/home_assistant/provider.js +306 -26
- package/server/services/integrations/manager.js +63 -7
- package/server/services/integrations/oauth_provider.js +13 -6
- package/server/services/integrations/provider_config_store.js +76 -0
- package/server/services/integrations/registry.js +4 -0
- package/server/services/integrations/trello/provider.js +744 -0
- package/server/services/integrations/whatsapp/provider.js +6 -2
- package/server/services/manager.js +22 -0
- package/server/services/memory/manager.js +39 -2
- package/server/services/skills/base_catalog.js +33 -0
- package/server/services/tasks/adapters/index.js +1 -0
- package/server/services/tasks/adapters/manual.js +12 -0
- package/server/services/tasks/runtime.js +1 -1
- package/server/services/voice/openaiClient.js +4 -1
- package/server/services/voice/providers.js +2 -1
- package/server/services/widgets/service.js +49 -4
- package/server/utils/local_secrets.js +56 -0
- package/server/utils/logger.js +37 -9
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/// Enum representing the type of input device (microphone).
|
|
2
|
+
///
|
|
3
|
+
/// This enum categorizes input devices into three types: built-in, Bluetooth,
|
|
4
|
+
/// or external (USB, etc.).
|
|
5
|
+
///
|
|
6
|
+
/// Example:
|
|
7
|
+
/// ```dart
|
|
8
|
+
/// final device = InputDevice(
|
|
9
|
+
/// id: 'device1',
|
|
10
|
+
/// name: 'Built-in Microphone',
|
|
11
|
+
/// type: InputDeviceType.builtIn,
|
|
12
|
+
/// channelCount: 1,
|
|
13
|
+
/// isDefault: true,
|
|
14
|
+
/// );
|
|
15
|
+
///
|
|
16
|
+
/// // Convert to/from string
|
|
17
|
+
/// final typeString = device.type.toString(); // 'built-in'
|
|
18
|
+
/// final type = InputDeviceType.fromString('bluetooth'); // InputDeviceType.bluetooth
|
|
19
|
+
/// ```
|
|
20
|
+
enum InputDeviceType {
|
|
21
|
+
/// Built-in device (e.g., laptop microphone)
|
|
22
|
+
builtIn,
|
|
23
|
+
|
|
24
|
+
/// Bluetooth device (wireless microphone)
|
|
25
|
+
bluetooth,
|
|
26
|
+
|
|
27
|
+
/// External device (USB microphone, etc.)
|
|
28
|
+
external;
|
|
29
|
+
|
|
30
|
+
/// Creates an [InputDeviceType] from a string.
|
|
31
|
+
///
|
|
32
|
+
/// Accepts: 'built-in', 'bluetooth', 'external' (case-insensitive).
|
|
33
|
+
/// Returns [InputDeviceType.external] for unknown values.
|
|
34
|
+
///
|
|
35
|
+
/// Example:
|
|
36
|
+
/// ```dart
|
|
37
|
+
/// final type1 = InputDeviceType.fromString('built-in'); // InputDeviceType.builtIn
|
|
38
|
+
/// final type2 = InputDeviceType.fromString('BLUETOOTH'); // InputDeviceType.bluetooth
|
|
39
|
+
/// final type3 = InputDeviceType.fromString('unknown'); // InputDeviceType.external (default)
|
|
40
|
+
/// ```
|
|
41
|
+
static InputDeviceType fromString(String type) {
|
|
42
|
+
switch (type.toLowerCase()) {
|
|
43
|
+
case 'built-in':
|
|
44
|
+
return InputDeviceType.builtIn;
|
|
45
|
+
case 'bluetooth':
|
|
46
|
+
return InputDeviceType.bluetooth;
|
|
47
|
+
case 'external':
|
|
48
|
+
return InputDeviceType.external;
|
|
49
|
+
default:
|
|
50
|
+
return InputDeviceType.external;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Converts this [InputDeviceType] to a string representation.
|
|
55
|
+
///
|
|
56
|
+
/// Returns: 'built-in', 'bluetooth', or 'external'.
|
|
57
|
+
///
|
|
58
|
+
/// Example:
|
|
59
|
+
/// ```dart
|
|
60
|
+
/// InputDeviceType.builtIn.toString(); // 'built-in'
|
|
61
|
+
/// InputDeviceType.bluetooth.toString(); // 'bluetooth'
|
|
62
|
+
/// InputDeviceType.external.toString(); // 'external'
|
|
63
|
+
/// ```
|
|
64
|
+
@override
|
|
65
|
+
String toString() {
|
|
66
|
+
switch (this) {
|
|
67
|
+
case InputDeviceType.builtIn:
|
|
68
|
+
return 'built-in';
|
|
69
|
+
case InputDeviceType.bluetooth:
|
|
70
|
+
return 'bluetooth';
|
|
71
|
+
case InputDeviceType.external:
|
|
72
|
+
return 'external';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Class representing information about an input device (microphone).
|
|
78
|
+
///
|
|
79
|
+
/// This class contains all relevant information about a microphone device,
|
|
80
|
+
/// including its ID, name, type, channel count, and whether it's the default device.
|
|
81
|
+
///
|
|
82
|
+
/// Example:
|
|
83
|
+
/// ```dart
|
|
84
|
+
/// // Get available devices
|
|
85
|
+
/// final devices = await micCapture.getAvailableInputDevices();
|
|
86
|
+
///
|
|
87
|
+
/// // Find a specific device
|
|
88
|
+
/// final usbMic = devices.firstWhere(
|
|
89
|
+
/// (device) => device.name.contains('USB'),
|
|
90
|
+
/// );
|
|
91
|
+
///
|
|
92
|
+
/// print('Device: ${usbMic.name}');
|
|
93
|
+
/// print('Type: ${usbMic.type}');
|
|
94
|
+
/// print('Channels: ${usbMic.channelCount}');
|
|
95
|
+
/// print('Default: ${usbMic.isDefault}');
|
|
96
|
+
///
|
|
97
|
+
/// // Convert to/from map
|
|
98
|
+
/// final map = usbMic.toMap();
|
|
99
|
+
/// final restored = InputDevice.fromMap(map);
|
|
100
|
+
/// ```
|
|
101
|
+
class InputDevice {
|
|
102
|
+
/// Unique identifier of the device.
|
|
103
|
+
///
|
|
104
|
+
/// This ID can be used to identify and select a specific device.
|
|
105
|
+
final String id;
|
|
106
|
+
|
|
107
|
+
/// Human-readable name of the device.
|
|
108
|
+
///
|
|
109
|
+
/// Examples: "Built-in Microphone", "USB Microphone", "AirPods Pro"
|
|
110
|
+
final String name;
|
|
111
|
+
|
|
112
|
+
/// Type of the device (built-in, Bluetooth, or external).
|
|
113
|
+
final InputDeviceType type;
|
|
114
|
+
|
|
115
|
+
/// Number of audio channels supported by the device.
|
|
116
|
+
///
|
|
117
|
+
/// Typically 1 for mono, 2 for stereo.
|
|
118
|
+
final int channelCount;
|
|
119
|
+
|
|
120
|
+
/// Whether this device is the system default input device.
|
|
121
|
+
final bool isDefault;
|
|
122
|
+
|
|
123
|
+
/// Creates a new [InputDevice] instance.
|
|
124
|
+
///
|
|
125
|
+
/// All parameters are required.
|
|
126
|
+
///
|
|
127
|
+
/// Example:
|
|
128
|
+
/// ```dart
|
|
129
|
+
/// final device = InputDevice(
|
|
130
|
+
/// id: 'device-123',
|
|
131
|
+
/// name: 'Built-in Microphone',
|
|
132
|
+
/// type: InputDeviceType.builtIn,
|
|
133
|
+
/// channelCount: 1,
|
|
134
|
+
/// isDefault: true,
|
|
135
|
+
/// );
|
|
136
|
+
/// ```
|
|
137
|
+
const InputDevice({
|
|
138
|
+
required this.id,
|
|
139
|
+
required this.name,
|
|
140
|
+
required this.type,
|
|
141
|
+
required this.channelCount,
|
|
142
|
+
required this.isDefault,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/// Creates an [InputDevice] instance from a map.
|
|
146
|
+
///
|
|
147
|
+
/// The map should contain:
|
|
148
|
+
/// - `id`: String (defaults to empty string if missing)
|
|
149
|
+
/// - `name`: String (defaults to empty string if missing)
|
|
150
|
+
/// - `type`: String (converted via [InputDeviceType.fromString], defaults to external)
|
|
151
|
+
/// - `channelCount`: int (defaults to 0 if missing)
|
|
152
|
+
/// - `isDefault`: bool (defaults to false if missing)
|
|
153
|
+
///
|
|
154
|
+
/// Example:
|
|
155
|
+
/// ```dart
|
|
156
|
+
/// final map = {
|
|
157
|
+
/// 'id': 'device-123',
|
|
158
|
+
/// 'name': 'USB Microphone',
|
|
159
|
+
/// 'type': 'external',
|
|
160
|
+
/// 'channelCount': 2,
|
|
161
|
+
/// 'isDefault': false,
|
|
162
|
+
/// };
|
|
163
|
+
/// final device = InputDevice.fromMap(map);
|
|
164
|
+
/// ```
|
|
165
|
+
factory InputDevice.fromMap(Map<String, dynamic> map) {
|
|
166
|
+
return InputDevice(
|
|
167
|
+
id: map['id'] as String? ?? '',
|
|
168
|
+
name: map['name'] as String? ?? '',
|
|
169
|
+
type: InputDeviceType.fromString(map['type'] as String? ?? 'external'),
|
|
170
|
+
channelCount: map['channelCount'] as int? ?? 0,
|
|
171
|
+
isDefault: map['isDefault'] as bool? ?? false,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Converts this [InputDevice] instance to a map.
|
|
176
|
+
///
|
|
177
|
+
/// Returns a map containing all device information:
|
|
178
|
+
/// - `id`: String
|
|
179
|
+
/// - `name`: String
|
|
180
|
+
/// - `type`: String (from [InputDeviceType.toString])
|
|
181
|
+
/// - `channelCount`: int
|
|
182
|
+
/// - `isDefault`: bool
|
|
183
|
+
///
|
|
184
|
+
/// Example:
|
|
185
|
+
/// ```dart
|
|
186
|
+
/// final device = InputDevice(
|
|
187
|
+
/// id: 'device-123',
|
|
188
|
+
/// name: 'USB Microphone',
|
|
189
|
+
/// type: InputDeviceType.external,
|
|
190
|
+
/// channelCount: 2,
|
|
191
|
+
/// isDefault: false,
|
|
192
|
+
/// );
|
|
193
|
+
/// final map = device.toMap();
|
|
194
|
+
/// ```
|
|
195
|
+
Map<String, dynamic> toMap() {
|
|
196
|
+
return {
|
|
197
|
+
'id': id,
|
|
198
|
+
'name': name,
|
|
199
|
+
'type': type.toString(),
|
|
200
|
+
'channelCount': channelCount,
|
|
201
|
+
'isDefault': isDefault,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@override
|
|
206
|
+
String toString() {
|
|
207
|
+
return 'InputDevice(id: $id, name: $name, type: ${type.toString()}, '
|
|
208
|
+
'channelCount: $channelCount, isDefault: $isDefault)';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@override
|
|
212
|
+
bool operator ==(Object other) {
|
|
213
|
+
if (identical(this, other)) return true;
|
|
214
|
+
return other is InputDevice && other.id == id;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@override
|
|
218
|
+
int get hashCode => id.hashCode;
|
|
219
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
3
|
+
import 'package:desktop_audio_capture/audio_capture.dart';
|
|
4
|
+
import 'package:flutter/services.dart';
|
|
5
|
+
|
|
6
|
+
export 'package:desktop_audio_capture/config/system_adudio_config.dart';
|
|
7
|
+
|
|
8
|
+
enum _SystemAudioMethod {
|
|
9
|
+
startCapture,
|
|
10
|
+
stopCapture,
|
|
11
|
+
requestPermissions,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// Class for capturing system audio (audio output from the device).
|
|
15
|
+
///
|
|
16
|
+
/// This class allows you to capture audio that is being played by the system,
|
|
17
|
+
/// such as music, videos, or other applications. It requires screen recording
|
|
18
|
+
/// permissions on macOS.
|
|
19
|
+
///
|
|
20
|
+
/// Example:
|
|
21
|
+
/// ```dart
|
|
22
|
+
/// final systemCapture = SystemAudioCapture(
|
|
23
|
+
/// config: SystemAudioConfig(
|
|
24
|
+
/// sampleRate: 44100,
|
|
25
|
+
/// channels: 2,
|
|
26
|
+
/// ),
|
|
27
|
+
/// );
|
|
28
|
+
///
|
|
29
|
+
/// await systemCapture.startCapture();
|
|
30
|
+
///
|
|
31
|
+
/// // Listen to audio stream
|
|
32
|
+
/// systemCapture.audioStream?.listen((audioData) {
|
|
33
|
+
/// // Process audio bytes
|
|
34
|
+
/// print('Received ${audioData.length} bytes');
|
|
35
|
+
/// });
|
|
36
|
+
///
|
|
37
|
+
/// // Listen to decibel readings
|
|
38
|
+
/// systemCapture.decibelStream?.listen((data) {
|
|
39
|
+
/// print('Decibel: ${data.decibel} dB');
|
|
40
|
+
/// });
|
|
41
|
+
///
|
|
42
|
+
/// // Stop when done
|
|
43
|
+
/// await systemCapture.stopCapture();
|
|
44
|
+
/// ```
|
|
45
|
+
class SystemAudioCapture extends AudioCapture {
|
|
46
|
+
static const MethodChannel _channel = MethodChannel(
|
|
47
|
+
'com.system_audio_transcriber/audio_capture',
|
|
48
|
+
);
|
|
49
|
+
static const EventChannel _audioStreamChannel = EventChannel(
|
|
50
|
+
'com.system_audio_transcriber/audio_stream',
|
|
51
|
+
);
|
|
52
|
+
static const EventChannel _statusStreamChannel = EventChannel(
|
|
53
|
+
'com.system_audio_transcriber/audio_status',
|
|
54
|
+
);
|
|
55
|
+
static const EventChannel _decibelStreamChannel = EventChannel(
|
|
56
|
+
'com.system_audio_transcriber/audio_decibel',
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
Stream<Uint8List>? _audioStream;
|
|
60
|
+
Stream<SystemAudioStatus>? _statusStream;
|
|
61
|
+
Stream<DecibelData>? _decibelStream;
|
|
62
|
+
bool _isRecording = false;
|
|
63
|
+
|
|
64
|
+
/// Stream of raw audio data bytes from system audio capture.
|
|
65
|
+
///
|
|
66
|
+
/// Returns a [Stream<Uint8List>] containing the captured audio data.
|
|
67
|
+
/// The stream is only available after [startCapture] has been called.
|
|
68
|
+
///
|
|
69
|
+
/// Example:
|
|
70
|
+
/// ```dart
|
|
71
|
+
/// await systemCapture.startCapture();
|
|
72
|
+
///
|
|
73
|
+
/// systemCapture.audioStream?.listen((audioData) {
|
|
74
|
+
/// // Process audio bytes
|
|
75
|
+
/// final audioBuffer = audioData.buffer.asUint8List();
|
|
76
|
+
/// // Use audio buffer for processing, saving, etc.
|
|
77
|
+
/// });
|
|
78
|
+
/// ```
|
|
79
|
+
Stream<Uint8List>? get audioStream => _audioStream;
|
|
80
|
+
|
|
81
|
+
/// Stream of system audio capture status updates.
|
|
82
|
+
///
|
|
83
|
+
/// Returns a [Stream<SystemAudioStatus>] containing status information:
|
|
84
|
+
/// - [SystemAudioStatus.isActive]: bool - whether system audio capture is currently active
|
|
85
|
+
///
|
|
86
|
+
/// Example:
|
|
87
|
+
/// ```dart
|
|
88
|
+
/// systemCapture.statusStream?.listen((status) {
|
|
89
|
+
/// if (status.isActive) {
|
|
90
|
+
/// print('System audio capture is active');
|
|
91
|
+
/// } else {
|
|
92
|
+
/// print('System audio capture is inactive');
|
|
93
|
+
/// }
|
|
94
|
+
/// });
|
|
95
|
+
/// ```
|
|
96
|
+
Stream<SystemAudioStatus>? get statusStream {
|
|
97
|
+
// Create status stream if not already created
|
|
98
|
+
_statusStream ??= _statusStreamChannel.receiveBroadcastStream().map((
|
|
99
|
+
dynamic event,
|
|
100
|
+
) {
|
|
101
|
+
if (event is Map) {
|
|
102
|
+
return SystemAudioStatus.fromJson(Map<String, dynamic>.from(event));
|
|
103
|
+
}
|
|
104
|
+
return SystemAudioStatus(isActive: false);
|
|
105
|
+
});
|
|
106
|
+
return _statusStream;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Stream of system audio decibel (dB) readings.
|
|
110
|
+
///
|
|
111
|
+
/// Returns a [Stream<DecibelData>] containing:
|
|
112
|
+
/// - `decibel`: double - decibel value (-120 to 0 dB)
|
|
113
|
+
/// - `timestamp`: double - Unix timestamp
|
|
114
|
+
///
|
|
115
|
+
/// The stream is only available while recording is active.
|
|
116
|
+
///
|
|
117
|
+
/// Example:
|
|
118
|
+
/// ```dart
|
|
119
|
+
/// await systemCapture.startCapture();
|
|
120
|
+
///
|
|
121
|
+
/// systemCapture.decibelStream?.listen((data) {
|
|
122
|
+
/// print('System audio level: ${data.decibel.toStringAsFixed(1)} dB');
|
|
123
|
+
/// print('Timestamp: ${DateTime.fromMillisecondsSinceEpoch((data.timestamp * 1000).toInt())}');
|
|
124
|
+
/// });
|
|
125
|
+
/// ```
|
|
126
|
+
Stream<DecibelData>? get decibelStream {
|
|
127
|
+
if (!_isRecording) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
// Create decibel stream if not already created
|
|
131
|
+
_decibelStream ??= _decibelStreamChannel.receiveBroadcastStream().map((
|
|
132
|
+
dynamic event,
|
|
133
|
+
) {
|
|
134
|
+
if (event is Map) {
|
|
135
|
+
return DecibelData.fromMap(Map<String, dynamic>.from(event));
|
|
136
|
+
}
|
|
137
|
+
return DecibelData(
|
|
138
|
+
decibel: -120.0,
|
|
139
|
+
timestamp: DateTime.now().millisecondsSinceEpoch / 1000.0);
|
|
140
|
+
});
|
|
141
|
+
return _decibelStream;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
SystemAudioConfig _config = SystemAudioConfig();
|
|
145
|
+
|
|
146
|
+
/// Creates a new [SystemAudioCapture] instance.
|
|
147
|
+
///
|
|
148
|
+
/// [config] is optional. If not provided, default configuration will be used
|
|
149
|
+
/// (sampleRate: 16000, channels: 1).
|
|
150
|
+
///
|
|
151
|
+
/// Example:
|
|
152
|
+
/// ```dart
|
|
153
|
+
/// final capture = SystemAudioCapture(
|
|
154
|
+
/// config: SystemAudioConfig(
|
|
155
|
+
/// sampleRate: 44100,
|
|
156
|
+
/// channels: 2,
|
|
157
|
+
/// ),
|
|
158
|
+
/// );
|
|
159
|
+
/// ```
|
|
160
|
+
SystemAudioCapture({SystemAudioConfig? config}) {
|
|
161
|
+
_config = config ?? SystemAudioConfig();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Updates the audio capture configuration.
|
|
165
|
+
///
|
|
166
|
+
/// This method allows you to change the configuration after the instance
|
|
167
|
+
/// has been created. The new configuration will be applied on the next
|
|
168
|
+
/// [startCapture] call.
|
|
169
|
+
///
|
|
170
|
+
/// Example:
|
|
171
|
+
/// ```dart
|
|
172
|
+
/// final capture = SystemAudioCapture();
|
|
173
|
+
///
|
|
174
|
+
/// // Update config before starting
|
|
175
|
+
/// capture.updateConfig(SystemAudioConfig(
|
|
176
|
+
/// sampleRate: 48000,
|
|
177
|
+
/// channels: 2,
|
|
178
|
+
/// ));
|
|
179
|
+
///
|
|
180
|
+
/// await capture.startCapture();
|
|
181
|
+
/// ```
|
|
182
|
+
void updateConfig(SystemAudioConfig config) {
|
|
183
|
+
_config = config;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Starts capturing system audio.
|
|
187
|
+
///
|
|
188
|
+
/// This method will request necessary permissions (screen recording on macOS)
|
|
189
|
+
/// and begin capturing audio from the system output.
|
|
190
|
+
///
|
|
191
|
+
/// [config] is optional. If provided, it will update the current configuration
|
|
192
|
+
/// before starting capture.
|
|
193
|
+
///
|
|
194
|
+
/// Throws an [Exception] if:
|
|
195
|
+
/// - Permissions are not granted
|
|
196
|
+
/// - Capture fails to start
|
|
197
|
+
///
|
|
198
|
+
/// Example:
|
|
199
|
+
/// ```dart
|
|
200
|
+
/// final capture = SystemAudioCapture();
|
|
201
|
+
///
|
|
202
|
+
/// try {
|
|
203
|
+
/// await capture.startCapture(
|
|
204
|
+
/// config: SystemAudioConfig(
|
|
205
|
+
/// sampleRate: 44100,
|
|
206
|
+
/// channels: 2,
|
|
207
|
+
/// ),
|
|
208
|
+
/// );
|
|
209
|
+
/// print('System audio capture started');
|
|
210
|
+
/// } catch (e) {
|
|
211
|
+
/// print('Failed to start: $e');
|
|
212
|
+
/// }
|
|
213
|
+
/// ```
|
|
214
|
+
Future<void> startCapture({SystemAudioConfig? config}) async {
|
|
215
|
+
if (_isRecording) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (config != null) {
|
|
220
|
+
updateConfig(config);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await requestPermissions();
|
|
225
|
+
|
|
226
|
+
final started = await _channel.invokeMethod<bool>(
|
|
227
|
+
_SystemAudioMethod.startCapture.name,
|
|
228
|
+
_config.toMap(),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (started != true) {
|
|
232
|
+
throw Exception('Failed to start system audio capture');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Listen to audio stream
|
|
236
|
+
_audioStream = _audioStreamChannel.receiveBroadcastStream().map((
|
|
237
|
+
dynamic event,
|
|
238
|
+
) {
|
|
239
|
+
if (event is Uint8List) {
|
|
240
|
+
return event;
|
|
241
|
+
} else if (event is List<int>) {
|
|
242
|
+
return Uint8List.fromList(event);
|
|
243
|
+
}
|
|
244
|
+
throw Exception('Unexpected audio data type: ${event.runtimeType}');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Status stream is created lazily via getter, no need to recreate here
|
|
248
|
+
|
|
249
|
+
_isRecording = true;
|
|
250
|
+
} catch (e) {
|
|
251
|
+
rethrow;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// Stops capturing system audio.
|
|
256
|
+
///
|
|
257
|
+
/// This method will stop the active capture and close all associated streams.
|
|
258
|
+
/// If capture is not active, this method does nothing.
|
|
259
|
+
///
|
|
260
|
+
/// Throws an [Exception] if stopping fails.
|
|
261
|
+
///
|
|
262
|
+
/// Example:
|
|
263
|
+
/// ```dart
|
|
264
|
+
/// await systemCapture.startCapture();
|
|
265
|
+
///
|
|
266
|
+
/// // ... use audio stream ...
|
|
267
|
+
///
|
|
268
|
+
/// await systemCapture.stopCapture();
|
|
269
|
+
/// print('System audio capture stopped');
|
|
270
|
+
/// ```
|
|
271
|
+
Future<void> stopCapture() async {
|
|
272
|
+
if (!_isRecording) return;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
final stopped = await _channel.invokeMethod<bool>(
|
|
276
|
+
_SystemAudioMethod.stopCapture.name,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
if (stopped != true) {
|
|
280
|
+
throw Exception("Failed to stop system audio capture");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_isRecording = false;
|
|
284
|
+
_audioStream = null;
|
|
285
|
+
_statusStream = null;
|
|
286
|
+
_decibelStream = null;
|
|
287
|
+
} catch (e) {
|
|
288
|
+
rethrow;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// Whether system audio capture is currently recording.
|
|
293
|
+
///
|
|
294
|
+
/// Returns `true` if capture is active, `false` otherwise.
|
|
295
|
+
///
|
|
296
|
+
/// Example:
|
|
297
|
+
/// ```dart
|
|
298
|
+
/// if (systemCapture.isRecording) {
|
|
299
|
+
/// print('System audio is being captured');
|
|
300
|
+
/// } else {
|
|
301
|
+
/// print('System audio capture is not active');
|
|
302
|
+
/// }
|
|
303
|
+
/// ```
|
|
304
|
+
@override
|
|
305
|
+
bool get isRecording => _isRecording;
|
|
306
|
+
|
|
307
|
+
/// Requests necessary permissions for system audio capture.
|
|
308
|
+
///
|
|
309
|
+
/// On macOS, this requests screen recording permission which is required
|
|
310
|
+
/// to capture system audio.
|
|
311
|
+
///
|
|
312
|
+
/// Returns `true` if permissions are granted.
|
|
313
|
+
///
|
|
314
|
+
/// Throws an [Exception] if permissions are not granted.
|
|
315
|
+
///
|
|
316
|
+
/// Example:
|
|
317
|
+
/// ```dart
|
|
318
|
+
/// try {
|
|
319
|
+
/// final hasPermission = await systemCapture.requestPermissions();
|
|
320
|
+
/// if (hasPermission) {
|
|
321
|
+
/// await systemCapture.startCapture();
|
|
322
|
+
/// }
|
|
323
|
+
/// } catch (e) {
|
|
324
|
+
/// print('Permission denied: $e');
|
|
325
|
+
/// }
|
|
326
|
+
/// ```
|
|
327
|
+
Future<bool> requestPermissions() async {
|
|
328
|
+
final hasPermission = await _channel.invokeMethod<bool>(
|
|
329
|
+
_SystemAudioMethod.requestPermissions.name,
|
|
330
|
+
);
|
|
331
|
+
if (hasPermission != true) {
|
|
332
|
+
throw Exception('Screen recording permission not granted');
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
}
|