@mentra/engine 0.1.0-dev.0 → 3.1.0-dev.4
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/README.md +55 -18
- package/build/bluetooth-sdk/debug.d.ts +2 -0
- package/build/bluetooth-sdk/debug.d.ts.map +1 -0
- package/build/bluetooth-sdk/debug.js +2 -0
- package/build/bluetooth-sdk/debug.js.map +1 -0
- package/build/bluetooth-sdk/index.d.ts +3 -0
- package/build/bluetooth-sdk/index.d.ts.map +1 -0
- package/build/bluetooth-sdk/index.js +3 -0
- package/build/bluetooth-sdk/index.js.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
- package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
- package/build/bluetooth-sdk/photo-receiver.js +3 -0
- package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
- package/build/bluetooth-sdk/react.d.ts +2 -0
- package/build/bluetooth-sdk/react.d.ts.map +1 -0
- package/build/bluetooth-sdk/react.js +2 -0
- package/build/bluetooth-sdk/react.js.map +1 -0
- package/build/bluetooth-sdk/types.d.ts +2 -0
- package/build/bluetooth-sdk/types.d.ts.map +1 -0
- package/build/bluetooth-sdk/types.js +2 -0
- package/build/bluetooth-sdk/types.js.map +1 -0
- package/build/engine.d.ts +24 -16
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +9 -4
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts +3 -2
- package/build/facades/dev.d.ts.map +1 -1
- package/build/facades/dev.js +18 -12
- package/build/facades/dev.js.map +1 -1
- package/build/facades/display.d.ts +1 -1
- package/build/facades/display.d.ts.map +1 -1
- package/build/facades/display.js +1 -1
- package/build/facades/display.js.map +1 -1
- package/build/facades/gallery.js +1 -1
- package/build/facades/gallery.js.map +1 -1
- package/build/facades/glasses.d.ts +4 -13
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +21 -6
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/glassesSettings.d.ts.map +1 -1
- package/build/facades/glassesSettings.js +1 -0
- package/build/facades/glassesSettings.js.map +1 -1
- package/build/facades/miniapps.d.ts +4 -0
- package/build/facades/miniapps.d.ts.map +1 -1
- package/build/facades/miniapps.js +5 -0
- package/build/facades/miniapps.js.map +1 -1
- package/build/facades/ota.d.ts +14 -16
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +22 -7
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +8 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +12 -10
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/permissions.d.ts +10 -0
- package/build/facades/permissions.d.ts.map +1 -1
- package/build/facades/permissions.js +10 -0
- package/build/facades/permissions.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +0 -4
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +4 -14
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/facades/speech.d.ts +7 -6
- package/build/facades/speech.d.ts.map +1 -1
- package/build/facades/speech.js +1 -1
- package/build/facades/speech.js.map +1 -1
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +10 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -5
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +38 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +528 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +2 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useEngineSnapshot.d.ts +2 -0
- package/build/react/useEngineSnapshot.d.ts.map +1 -0
- package/build/react/useEngineSnapshot.js +14 -0
- package/build/react/useEngineSnapshot.js.map +1 -0
- package/build/runtime/bootstrap.d.ts +24 -1
- package/build/runtime/bootstrap.d.ts.map +1 -1
- package/build/runtime/bootstrap.js +17 -0
- package/build/runtime/bootstrap.js.map +1 -1
- package/build/runtime/scanQrSeam.d.ts +7 -0
- package/build/runtime/scanQrSeam.d.ts.map +1 -0
- package/build/runtime/scanQrSeam.js +16 -0
- package/build/runtime/scanQrSeam.js.map +1 -0
- package/build/services/AppRegistry.d.ts +41 -57
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +208 -144
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/AudioCloudUplink.d.ts +12 -0
- package/build/services/AudioCloudUplink.d.ts.map +1 -1
- package/build/services/AudioCloudUplink.js +22 -1
- package/build/services/AudioCloudUplink.js.map +1 -1
- package/build/services/AudioPlaybackService.d.ts +41 -2
- package/build/services/AudioPlaybackService.d.ts.map +1 -1
- package/build/services/AudioPlaybackService.js +250 -33
- package/build/services/AudioPlaybackService.js.map +1 -1
- package/build/services/BlobStore.d.ts +2 -1
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +17 -9
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.js +13 -1
- package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
- package/build/services/CloudClientService.d.ts +15 -0
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +93 -1
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/DeviceEventRouter.js +5 -5
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/ForegroundLocationPermission.d.ts +13 -0
- package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
- package/build/services/ForegroundLocationPermission.js +12 -0
- package/build/services/ForegroundLocationPermission.js.map +1 -0
- package/build/services/GlassesSettingsSync.d.ts.map +1 -1
- package/build/services/GlassesSettingsSync.js +9 -3
- package/build/services/GlassesSettingsSync.js.map +1 -1
- package/build/services/GlassesStatusProjection.d.ts +2 -1
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +15 -8
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/HotspotOtaTransport.d.ts +29 -0
- package/build/services/HotspotOtaTransport.d.ts.map +1 -0
- package/build/services/HotspotOtaTransport.js +146 -0
- package/build/services/HotspotOtaTransport.js.map +1 -0
- package/build/services/HotspotShutdown.d.ts +17 -0
- package/build/services/HotspotShutdown.d.ts.map +1 -0
- package/build/services/HotspotShutdown.js +29 -0
- package/build/services/HotspotShutdown.js.map +1 -0
- package/build/services/LocalDisplayManager.d.ts +15 -0
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +106 -20
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +74 -2
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +785 -291
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/LocalMiniappStorage.d.ts +32 -0
- package/build/services/LocalMiniappStorage.d.ts.map +1 -0
- package/build/services/LocalMiniappStorage.js +66 -0
- package/build/services/LocalMiniappStorage.js.map +1 -0
- package/build/services/LocalMiniappUserIdentity.d.ts +20 -0
- package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
- package/build/services/LocalMiniappUserIdentity.js +43 -0
- package/build/services/LocalMiniappUserIdentity.js.map +1 -0
- package/build/services/LocalSttFallbackCoordinator.d.ts +9 -6
- package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.js +18 -12
- package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
- package/build/services/MicStateCoordinator.d.ts +42 -1
- package/build/services/MicStateCoordinator.d.ts.map +1 -1
- package/build/services/MicStateCoordinator.js +141 -3
- package/build/services/MicStateCoordinator.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +23 -3
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/MiniappLiveness.d.ts +12 -0
- package/build/services/MiniappLiveness.d.ts.map +1 -0
- package/build/services/MiniappLiveness.js +16 -0
- package/build/services/MiniappLiveness.js.map +1 -0
- package/build/services/NativeHttpResponse.d.ts +3 -0
- package/build/services/NativeHttpResponse.d.ts.map +1 -0
- package/build/services/NativeHttpResponse.js +6 -0
- package/build/services/NativeHttpResponse.js.map +1 -0
- package/build/services/OtaArtifactDownloader.d.ts +65 -0
- package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
- package/build/services/OtaArtifactDownloader.js +226 -0
- package/build/services/OtaArtifactDownloader.js.map +1 -0
- package/build/services/OtaAutoChain.d.ts +34 -0
- package/build/services/OtaAutoChain.d.ts.map +1 -0
- package/build/services/OtaAutoChain.js +83 -0
- package/build/services/OtaAutoChain.js.map +1 -0
- package/build/services/OtaErrorMapping.d.ts +18 -0
- package/build/services/OtaErrorMapping.d.ts.map +1 -0
- package/build/services/OtaErrorMapping.js +63 -0
- package/build/services/OtaErrorMapping.js.map +1 -0
- package/build/services/OtaInstallCoordinator.d.ts +95 -15
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +577 -130
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +8 -1
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +39 -4
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +138 -22
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/PhoneCalendarHelpers.d.ts +32 -0
- package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
- package/build/services/PhoneCalendarHelpers.js +157 -0
- package/build/services/PhoneCalendarHelpers.js.map +1 -0
- package/build/services/PhoneCalendarService.d.ts +5 -0
- package/build/services/PhoneCalendarService.d.ts.map +1 -0
- package/build/services/PhoneCalendarService.js +45 -0
- package/build/services/PhoneCalendarService.js.map +1 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts +30 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
- package/build/services/PhoneCameraFovCoordinator.js +194 -0
- package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +12 -10
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.d.ts +26 -2
- package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +79 -9
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +8 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +125 -13
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/PhoneVideoCoordinator.d.ts +2 -0
- package/build/services/PhoneVideoCoordinator.d.ts.map +1 -1
- package/build/services/PhoneVideoCoordinator.js +10 -0
- package/build/services/PhoneVideoCoordinator.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +15 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/SentenceTtsPipeline.d.ts +31 -0
- package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
- package/build/services/SentenceTtsPipeline.js +67 -0
- package/build/services/SentenceTtsPipeline.js.map +1 -0
- package/build/services/SupportProfileSync.d.ts +22 -0
- package/build/services/SupportProfileSync.d.ts.map +1 -0
- package/build/services/SupportProfileSync.js +213 -0
- package/build/services/SupportProfileSync.js.map +1 -0
- package/build/services/TranscriptionRouting.d.ts +12 -0
- package/build/services/TranscriptionRouting.d.ts.map +1 -0
- package/build/services/TranscriptionRouting.js +24 -0
- package/build/services/TranscriptionRouting.js.map +1 -0
- package/build/services/TtsTextSanitizer.d.ts +5 -0
- package/build/services/TtsTextSanitizer.d.ts.map +1 -0
- package/build/services/TtsTextSanitizer.js +61 -0
- package/build/services/TtsTextSanitizer.js.map +1 -0
- package/build/services/WifiAdbRequest.d.ts +17 -0
- package/build/services/WifiAdbRequest.d.ts.map +1 -0
- package/build/services/WifiAdbRequest.js +35 -0
- package/build/services/WifiAdbRequest.js.map +1 -0
- package/build/services/asg/asgCameraApi.d.ts +43 -0
- package/build/services/asg/asgCameraApi.d.ts.map +1 -1
- package/build/services/asg/asgCameraApi.js +509 -26
- package/build/services/asg/asgCameraApi.js.map +1 -1
- package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
- package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
- package/build/services/asg/cameraRollExportCoordinator.js +618 -0
- package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
- package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
- package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
- package/build/services/asg/cameraRollExportLedger.js +78 -0
- package/build/services/asg/cameraRollExportLedger.js.map +1 -0
- package/build/services/asg/galleryNotices.d.ts +1 -1
- package/build/services/asg/galleryNotices.d.ts.map +1 -1
- package/build/services/asg/galleryNotices.js.map +1 -1
- package/build/services/asg/galleryPathMigration.d.ts +3 -0
- package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
- package/build/services/asg/galleryPathMigration.js +26 -0
- package/build/services/asg/galleryPathMigration.js.map +1 -0
- package/build/services/asg/gallerySettingsService.d.ts +1 -0
- package/build/services/asg/gallerySettingsService.d.ts.map +1 -1
- package/build/services/asg/gallerySettingsService.js +2 -0
- package/build/services/asg/gallerySettingsService.js.map +1 -1
- package/build/services/asg/gallerySyncService.d.ts.map +1 -1
- package/build/services/asg/gallerySyncService.js +162 -56
- package/build/services/asg/gallerySyncService.js.map +1 -1
- package/build/services/asg/galleryTransferLedger.d.ts +58 -0
- package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
- package/build/services/asg/galleryTransferLedger.js +225 -0
- package/build/services/asg/galleryTransferLedger.js.map +1 -0
- package/build/services/asg/localNetworkTransport.d.ts +15 -0
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
- package/build/services/asg/localNetworkTransport.js +144 -0
- package/build/services/asg/localNetworkTransport.js.map +1 -0
- package/build/services/asg/localStorageService.d.ts +18 -0
- package/build/services/asg/localStorageService.d.ts.map +1 -1
- package/build/services/asg/localStorageService.js +168 -19
- package/build/services/asg/localStorageService.js.map +1 -1
- package/build/services/asg/mediaProcessingQueue.d.ts +10 -0
- package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -1
- package/build/services/asg/mediaProcessingQueue.js +193 -62
- package/build/services/asg/mediaProcessingQueue.js.map +1 -1
- package/build/services/audioPlaybackAssets.d.ts +8 -0
- package/build/services/audioPlaybackAssets.d.ts.map +1 -0
- package/build/services/audioPlaybackAssets.js +7 -0
- package/build/services/audioPlaybackAssets.js.map +1 -0
- package/build/services/gallerySyncClock.js +1 -1
- package/build/services/gallerySyncClock.js.map +1 -1
- package/build/services/glassesClockSync.d.ts.map +1 -1
- package/build/services/glassesClockSync.js +6 -2
- package/build/services/glassesClockSync.js.map +1 -1
- package/build/services/manifestActions.d.ts +7 -0
- package/build/services/manifestActions.d.ts.map +1 -0
- package/build/services/manifestActions.js +25 -0
- package/build/services/manifestActions.js.map +1 -0
- package/build/services/otaDisplayState.d.ts +23 -12
- package/build/services/otaDisplayState.d.ts.map +1 -1
- package/build/services/otaDisplayState.js +36 -14
- package/build/services/otaDisplayState.js.map +1 -1
- package/build/services/otaInstallPolicy.d.ts +16 -4
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +24 -3
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/services/otaLegacyMapping.d.ts +1 -1
- package/build/services/otaLegacyMapping.d.ts.map +1 -1
- package/build/services/otaLegacyMapping.js.map +1 -1
- package/build/services/otaManifestPolicy.d.ts +14 -0
- package/build/services/otaManifestPolicy.d.ts.map +1 -0
- package/build/services/otaManifestPolicy.js +19 -0
- package/build/services/otaManifestPolicy.js.map +1 -0
- package/build/services/otaManifestUrl.d.ts +3 -0
- package/build/services/otaManifestUrl.d.ts.map +1 -0
- package/build/services/otaManifestUrl.js +38 -0
- package/build/services/otaManifestUrl.js.map +1 -0
- package/build/services/slimStreamStatus.d.ts +11 -0
- package/build/services/slimStreamStatus.d.ts.map +1 -1
- package/build/services/slimStreamStatus.js +14 -4
- package/build/services/slimStreamStatus.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +11 -0
- package/build/stores/apps.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +2 -0
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/gallerySync.d.ts +2 -0
- package/build/stores/gallerySync.d.ts.map +1 -1
- package/build/stores/gallerySync.js +2 -0
- package/build/stores/gallerySync.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +3 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts +17 -0
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +157 -17
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +2 -0
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/asg.d.ts +7 -0
- package/build/types/asg.d.ts.map +1 -1
- package/build/types/asg.js.map +1 -1
- package/build/types/enums.d.ts +2 -25
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +2 -27
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +4 -13
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +15 -3
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +2 -6
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +2 -6
- package/build/types/index.js.map +1 -1
- package/build/utils/configuredDevHost.d.ts +6 -0
- package/build/utils/configuredDevHost.d.ts.map +1 -0
- package/build/utils/configuredDevHost.js +19 -0
- package/build/utils/configuredDevHost.js.map +1 -0
- package/build/utils/devMiniappLaunch.d.ts +19 -8
- package/build/utils/devMiniappLaunch.d.ts.map +1 -1
- package/build/utils/devMiniappLaunch.js +144 -21
- package/build/utils/devMiniappLaunch.js.map +1 -1
- package/build/utils/diagnosticContext.d.ts.map +1 -1
- package/build/utils/diagnosticContext.js +15 -17
- package/build/utils/diagnosticContext.js.map +1 -1
- package/build/utils/hardware/hardware.d.ts.map +1 -1
- package/build/utils/hardware/hardware.js +7 -3
- package/build/utils/hardware/hardware.js.map +1 -1
- package/build/utils/miniappDiagnostics.d.ts +64 -0
- package/build/utils/miniappDiagnostics.d.ts.map +1 -0
- package/build/utils/miniappDiagnostics.js +173 -0
- package/build/utils/miniappDiagnostics.js.map +1 -0
- package/build/utils/permissions/MediaLibraryPermissions.d.ts +11 -0
- package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -1
- package/build/utils/permissions/MediaLibraryPermissions.js +27 -4
- package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -1
- package/build/utils/timers/timers.d.ts.map +1 -1
- package/build/utils/timers/timers.js +55 -18
- package/build/utils/timers/timers.js.map +1 -1
- package/package.json +59 -31
- package/src/assets/silence.wav +0 -0
- package/src/bluetooth-sdk/debug.ts +1 -0
- package/src/bluetooth-sdk/index.ts +2 -0
- package/src/bluetooth-sdk/photo-receiver.ts +2 -0
- package/src/bluetooth-sdk/react.ts +1 -0
- package/src/bluetooth-sdk/types.ts +1 -0
- package/src/engine.ts +9 -4
- package/src/facades/dev.ts +17 -12
- package/src/facades/display.ts +1 -1
- package/src/facades/gallery.ts +1 -1
- package/src/facades/glasses.ts +20 -7
- package/src/facades/glassesSettings.ts +5 -0
- package/src/facades/miniapps.ts +7 -0
- package/src/facades/ota.ts +23 -8
- package/src/facades/pairing.ts +15 -11
- package/src/facades/permissions.ts +14 -0
- package/src/facades/phoneNotifications.ts +4 -13
- package/src/facades/speech.ts +21 -7
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +39 -13
- package/src/react/MentraLiveOtaFlow.tsx +856 -0
- package/src/react/index.ts +7 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/runtime/bootstrap.ts +33 -1
- package/src/runtime/scanQrSeam.ts +20 -0
- package/src/services/AppRegistry.ts +240 -149
- package/src/services/AudioCloudUplink.ts +23 -1
- package/src/services/AudioPlaybackService.ts +287 -38
- package/src/services/BlobStore.ts +22 -9
- package/src/services/CloudAudioSubscriptionSync.ts +13 -1
- package/src/services/CloudClientService.ts +96 -1
- package/src/services/DeviceEventRouter.ts +5 -5
- package/src/services/ForegroundLocationPermission.ts +24 -0
- package/src/services/GlassesSettingsSync.ts +9 -3
- package/src/services/GlassesStatusProjection.ts +16 -8
- package/src/services/HotspotOtaTransport.ts +184 -0
- package/src/services/HotspotShutdown.ts +41 -0
- package/src/services/LocalDisplayManager.ts +112 -21
- package/src/services/LocalMiniappRuntime.ts +967 -346
- package/src/services/LocalMiniappStorage.ts +82 -0
- package/src/services/LocalMiniappUserIdentity.ts +46 -0
- package/src/services/LocalSttFallbackCoordinator.ts +19 -12
- package/src/services/MicStateCoordinator.ts +169 -4
- package/src/services/MiniappLauncher.ts +30 -3
- package/src/services/MiniappLiveness.ts +24 -0
- package/src/services/NativeHttpResponse.ts +6 -0
- package/src/services/OtaArtifactDownloader.ts +312 -0
- package/src/services/OtaAutoChain.ts +106 -0
- package/src/services/OtaErrorMapping.ts +80 -0
- package/src/services/OtaInstallCoordinator.ts +649 -144
- package/src/services/OtaService.ts +11 -5
- package/src/services/OtaUpdateCheckService.ts +181 -21
- package/src/services/PhoneCalendarHelpers.ts +212 -0
- package/src/services/PhoneCalendarService.ts +65 -0
- package/src/services/PhoneCameraFovCoordinator.ts +210 -0
- package/src/services/PhoneNotificationsSync.ts +12 -10
- package/src/services/PhonePhotoCoordinator.ts +116 -18
- package/src/services/PhoneStreamCoordinator.ts +139 -25
- package/src/services/PhoneVideoCoordinator.ts +11 -0
- package/src/services/SceneRenderer.ts +16 -0
- package/src/services/SentenceTtsPipeline.ts +106 -0
- package/src/services/SupportProfileSync.ts +218 -0
- package/src/services/TranscriptionRouting.ts +37 -0
- package/src/services/TtsTextSanitizer.ts +66 -0
- package/src/services/WifiAdbRequest.ts +53 -0
- package/src/services/asg/asgCameraApi.ts +615 -33
- package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
- package/src/services/asg/cameraRollExportLedger.ts +122 -0
- package/src/services/asg/galleryNotices.ts +1 -0
- package/src/services/asg/galleryPathMigration.ts +22 -0
- package/src/services/asg/gallerySettingsService.ts +3 -0
- package/src/services/asg/gallerySyncService.ts +184 -73
- package/src/services/asg/galleryTransferLedger.ts +312 -0
- package/src/services/asg/localNetworkTransport.ts +169 -0
- package/src/services/asg/localStorageService.ts +179 -24
- package/src/services/asg/mediaProcessingQueue.ts +209 -62
- package/src/services/audioPlaybackAssets.ts +8 -0
- package/src/services/gallerySyncClock.ts +1 -1
- package/src/services/glassesClockSync.ts +6 -2
- package/src/services/manifestActions.ts +30 -0
- package/src/services/otaDisplayState.ts +58 -17
- package/src/services/otaInstallPolicy.ts +30 -4
- package/src/services/otaLegacyMapping.ts +1 -1
- package/src/services/otaManifestPolicy.ts +42 -0
- package/src/services/otaManifestUrl.ts +44 -0
- package/src/services/slimStreamStatus.ts +16 -5
- package/src/stores/apps.ts +12 -0
- package/src/stores/bluetoothSettingKeys.ts +2 -0
- package/src/stores/gallerySync.ts +4 -0
- package/src/stores/glasses.ts +3 -0
- package/src/stores/settings.ts +179 -18
- package/src/types/applet.ts +2 -0
- package/src/types/asg.ts +7 -0
- package/src/types/enums.ts +3 -28
- package/src/types/hardware.ts +20 -13
- package/src/types/index.ts +2 -6
- package/src/utils/configuredDevHost.ts +17 -0
- package/src/utils/devMiniappLaunch.ts +151 -23
- package/src/utils/diagnosticContext.ts +17 -17
- package/src/utils/hardware/hardware.ts +7 -9
- package/src/utils/miniappDiagnostics.ts +226 -0
- package/src/utils/permissions/MediaLibraryPermissions.ts +35 -4
- package/src/utils/timers/timers.ts +57 -21
- package/build/devtools.d.ts +0 -11
- package/build/devtools.d.ts.map +0 -1
- package/build/devtools.js +0 -13
- package/build/devtools.js.map +0 -1
- package/build/internal.d.ts +0 -66
- package/build/internal.d.ts.map +0 -1
- package/build/internal.js +0 -89
- package/build/internal.js.map +0 -1
- package/build/services/asgOtaVersionUrl.d.ts +0 -2
- package/build/services/asgOtaVersionUrl.d.ts.map +0 -1
- package/build/services/asgOtaVersionUrl.js +0 -36
- package/build/services/asgOtaVersionUrl.js.map +0 -1
- package/src/devtools.ts +0 -13
- package/src/internal.ts +0 -158
- package/src/services/asgOtaVersionUrl.ts +0 -41
|
@@ -18,11 +18,32 @@ import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
|
|
|
18
18
|
import {cloudClientService} from "./CloudClientService"
|
|
19
19
|
|
|
20
20
|
let sub: {remove: () => void} | null = null
|
|
21
|
+
const suppressionSources = new Set<string>()
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Keep the glasses LC3 transport alive while dropping mic audio before cloud
|
|
25
|
+
* STT. Sources are reference-counted by id so overlapping playback cannot
|
|
26
|
+
* reopen the uplink until every active source has released.
|
|
27
|
+
*
|
|
28
|
+
* Suppression is global to the uplink: one source silences STT for every app.
|
|
29
|
+
* That is why no platform path arms it any more (spoken TTS used to, which made
|
|
30
|
+
* every assistant reply punch a hole in Live Translation and Captions). The
|
|
31
|
+
* mechanism stays because it is the right primitive for an explicit,
|
|
32
|
+
* caller-scoped mute; see OS-1741 for the per-app opt-in that will use it.
|
|
33
|
+
*/
|
|
34
|
+
export function setAudioCloudUplinkSuppressed(sourceId: string, suppressed: boolean): void {
|
|
35
|
+
if (suppressed) suppressionSources.add(sourceId)
|
|
36
|
+
else suppressionSources.delete(sourceId)
|
|
37
|
+
}
|
|
21
38
|
|
|
22
39
|
export function startAudioCloudUplink(): void {
|
|
23
40
|
if (sub) return
|
|
24
41
|
sub = BluetoothSdk.addListener("mic_lc3", (event) => {
|
|
25
|
-
if (
|
|
42
|
+
if (
|
|
43
|
+
suppressionSources.size === 0 &&
|
|
44
|
+
cloudClientService.isConnected() &&
|
|
45
|
+
cloudClientService.hasAudioSubscriptions()
|
|
46
|
+
) {
|
|
26
47
|
cloudClientService.sendAudioFrame(new Uint8Array(event.lc3))
|
|
27
48
|
}
|
|
28
49
|
})
|
|
@@ -31,4 +52,5 @@ export function startAudioCloudUplink(): void {
|
|
|
31
52
|
export function stopAudioCloudUplink(): void {
|
|
32
53
|
sub?.remove()
|
|
33
54
|
sub = null
|
|
55
|
+
suppressionSources.clear()
|
|
34
56
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {createAudioPlayer, AudioPlayer, AudioStatus, setAudioModeAsync} from "expo-audio"
|
|
2
2
|
import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
|
|
3
|
+
import {AppState, Platform} from "react-native"
|
|
3
4
|
import {BgTimer} from "../utils/timers"
|
|
5
|
+
import {setAudioCloudUplinkSuppressed} from "./AudioCloudUplink"
|
|
6
|
+
import {SILENT_AUDIO_SOURCE} from "./audioPlaybackAssets"
|
|
4
7
|
|
|
5
8
|
const RESTORE_GLASSES_VOLUME_AFTER_PLAYBACK = false
|
|
6
9
|
|
|
@@ -10,14 +13,41 @@ interface AudioPlayRequest {
|
|
|
10
13
|
appId?: string
|
|
11
14
|
volume?: number
|
|
12
15
|
stopOtherAudio?: boolean
|
|
16
|
+
/**
|
|
17
|
+
* Suppress microphone audio sent to cloud STT while this audio is audible.
|
|
18
|
+
* Opt-in and off by default: the suppression is global to the uplink, so a
|
|
19
|
+
* caller that sets this silences STT for every running app, not just itself.
|
|
20
|
+
* No platform path sets it today (see AudioCloudUplink and OS-1741).
|
|
21
|
+
*/
|
|
22
|
+
suppressCloudUplink?: boolean
|
|
13
23
|
}
|
|
14
24
|
|
|
25
|
+
export type AudioPlaybackCompletionReason = "completed" | "interrupted" | "error"
|
|
26
|
+
|
|
27
|
+
type AudioPlaybackCompletion = (
|
|
28
|
+
requestId: string,
|
|
29
|
+
success: boolean,
|
|
30
|
+
error: string | null,
|
|
31
|
+
duration: number | null,
|
|
32
|
+
reason: AudioPlaybackCompletionReason,
|
|
33
|
+
) => void
|
|
34
|
+
|
|
15
35
|
interface PlaybackState {
|
|
16
36
|
requestId: string
|
|
37
|
+
audioUrl: string
|
|
38
|
+
uplinkSuppressionId: string
|
|
17
39
|
appId?: string
|
|
18
40
|
startTime: number
|
|
19
41
|
completed: boolean // Guard against double callbacks
|
|
20
|
-
|
|
42
|
+
suppressCloudUplink: boolean
|
|
43
|
+
onComplete: AudioPlaybackCompletion
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PendingPlaybackState {
|
|
47
|
+
cancelled: boolean
|
|
48
|
+
audioUrl: string
|
|
49
|
+
appId?: string
|
|
50
|
+
createdAt: number
|
|
21
51
|
}
|
|
22
52
|
|
|
23
53
|
/** Open request for a live PCM output stream (miniapp speaker.createStream). */
|
|
@@ -51,7 +81,14 @@ class AudioPlaybackService {
|
|
|
51
81
|
// Reuse a single AudioPlayer to avoid AudioTrack exhaustion
|
|
52
82
|
// Creating new ExoPlayer instances per request leads to -12 ENOMEM errors
|
|
53
83
|
private player: AudioPlayer | null = null
|
|
84
|
+
// The source currently loaded into the reusable player. Keep object identity
|
|
85
|
+
// so a delayed tail cleanup from an older request cannot unload a newer one.
|
|
86
|
+
private loadedPlayback: PlaybackState | null = null
|
|
54
87
|
private currentPlayback: PlaybackState | null = null
|
|
88
|
+
// Requests that entered play() but have not yet reached the native player.
|
|
89
|
+
// Keeping these addressable prevents a cancelled request from starting after
|
|
90
|
+
// an async audio-mode or stream-cleanup step finishes.
|
|
91
|
+
private pendingPlaybacks = new Map<string, PendingPlaybackState>()
|
|
55
92
|
// Live PCM streams (speaker.createStream), keyed by streamId. The runtime
|
|
56
93
|
// enforces one per app; stopOtherAudio interrupts across apps.
|
|
57
94
|
private streams = new Map<string, StreamState>()
|
|
@@ -60,10 +97,30 @@ class AudioPlaybackService {
|
|
|
60
97
|
// Prevents mic toggle flicker when playing back-to-back audio
|
|
61
98
|
// Uses BgTimer to work reliably when app is backgrounded on Android
|
|
62
99
|
private audioStopDebounceTimer: number | null = null
|
|
100
|
+
// Mentra Live's A2DP route may go idle between prompts. Keep a short warm
|
|
101
|
+
// window after actual audio so we only pre-roll the first sound after idle.
|
|
102
|
+
private audioRouteWarmUntil = 0
|
|
103
|
+
private audioRouteWarmupPromise: Promise<void> | null = null
|
|
104
|
+
// Playback that opted into suppression stays suppressed briefly while its
|
|
105
|
+
// A2DP tail drains. Track those completed sources so a later unsuppressed
|
|
106
|
+
// sound can resume listening immediately instead of inheriting that gate.
|
|
107
|
+
private tailUplinkSuppressions = new Set<string>()
|
|
63
108
|
// Original glasses media volume captured before we bump it for A2DP playback.
|
|
64
109
|
private glassesVolumeRestoreLevel: number | null = null
|
|
65
110
|
private static readonly AUDIO_STOP_DEBOUNCE_MS = 1500
|
|
66
|
-
private static readonly A2DP_TAIL_DRAIN_MS =
|
|
111
|
+
private static readonly A2DP_TAIL_DRAIN_MS = 700
|
|
112
|
+
private static readonly AUDIO_ROUTE_WARM_WINDOW_MS = 1500
|
|
113
|
+
private static readonly AUDIO_ROUTE_PREWARM_MS = 200
|
|
114
|
+
private static readonly AUDIO_ROUTE_PREWARM_SAMPLE_RATE = 16_000
|
|
115
|
+
/** Break accidental same-app replay storms without affecting deliberate later replays. */
|
|
116
|
+
private static readonly DUPLICATE_PLAY_WINDOW_MS = 750
|
|
117
|
+
private static readonly AUDIO_ROUTE_PREWARM_PCM_BASE64 =
|
|
118
|
+
"AAAA".repeat(
|
|
119
|
+
Math.floor(
|
|
120
|
+
(AudioPlaybackService.AUDIO_ROUTE_PREWARM_MS * AudioPlaybackService.AUDIO_ROUTE_PREWARM_SAMPLE_RATE * 2) /
|
|
121
|
+
(1000 * 3),
|
|
122
|
+
),
|
|
123
|
+
) + "AA=="
|
|
67
124
|
/** If glasses report step volume at or below this, bump to FLOOR before A2DP playback. */
|
|
68
125
|
private static readonly GLASSES_VOLUME_LOW_THRESHOLD = 2
|
|
69
126
|
private static readonly GLASSES_VOLUME_FLOOR = 9
|
|
@@ -126,6 +183,64 @@ class AudioPlaybackService {
|
|
|
126
183
|
return this.player
|
|
127
184
|
}
|
|
128
185
|
|
|
186
|
+
private markAudioRouteWarm(): void {
|
|
187
|
+
this.audioRouteWarmUntil = Date.now() + AudioPlaybackService.AUDIO_ROUTE_WARM_WINDOW_MS
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private isAudioRouteWarm(): boolean {
|
|
191
|
+
return this.currentPlayback !== null || this.streams.size > 0 || Date.now() < this.audioRouteWarmUntil
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Wake a cold Android A2DP route with silent PCM before a user-facing sound.
|
|
196
|
+
*
|
|
197
|
+
* Do not call pcmStreamClose here: some Bluetooth routes do not advance an
|
|
198
|
+
* idle AudioTrack playback head, so close waits for its drain timeout. After
|
|
199
|
+
* exactly 200ms of silence we abort the stream instead, which releases the
|
|
200
|
+
* pre-roll immediately and lets the real URL playback begin.
|
|
201
|
+
*/
|
|
202
|
+
private async prewarmAudioRouteIfCold(): Promise<void> {
|
|
203
|
+
if (Platform.OS !== "android" || this.isAudioRouteWarm()) return
|
|
204
|
+
// This silent PCM pre-roll is only an anti-clipping optimization. Some
|
|
205
|
+
// Android devices defer opening its AudioTrack until the host Activity
|
|
206
|
+
// resumes even though the real URL player supports background playback.
|
|
207
|
+
// Never let that optional pre-roll block a glasses-initiated prompt.
|
|
208
|
+
if (AppState.currentState !== "active") {
|
|
209
|
+
console.log(`AUDIO: Skipping cold-route prewarm while app is ${AppState.currentState}`)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
if (this.audioRouteWarmupPromise) return this.audioRouteWarmupPromise
|
|
213
|
+
|
|
214
|
+
const streamId = `audio-route-prewarm-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
215
|
+
const warmup = (async () => {
|
|
216
|
+
let opened = false
|
|
217
|
+
const startedAt = Date.now()
|
|
218
|
+
try {
|
|
219
|
+
console.log(`AUDIO: Cold route; prewarming with ${AudioPlaybackService.AUDIO_ROUTE_PREWARM_MS}ms silence`)
|
|
220
|
+
await BluetoothSdk.pcmStreamOpen(streamId, AudioPlaybackService.AUDIO_ROUTE_PREWARM_SAMPLE_RATE, 1, 1)
|
|
221
|
+
opened = true
|
|
222
|
+
await BluetoothSdk.pcmStreamWrite(streamId, AudioPlaybackService.AUDIO_ROUTE_PREWARM_PCM_BASE64)
|
|
223
|
+
await new Promise<void>((resolve) => {
|
|
224
|
+
BgTimer.setTimeout(resolve, AudioPlaybackService.AUDIO_ROUTE_PREWARM_MS)
|
|
225
|
+
})
|
|
226
|
+
await BluetoothSdk.pcmStreamAbort(streamId)
|
|
227
|
+
opened = false
|
|
228
|
+
this.markAudioRouteWarm()
|
|
229
|
+
console.log(`AUDIO: Cold-route prewarm completed in ${Date.now() - startedAt}ms`)
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.warn("AUDIO: Cold-route prewarm failed; continuing with requested audio:", error)
|
|
232
|
+
} finally {
|
|
233
|
+
if (opened) await BluetoothSdk.pcmStreamAbort(streamId).catch(() => {})
|
|
234
|
+
}
|
|
235
|
+
})()
|
|
236
|
+
this.audioRouteWarmupPromise = warmup
|
|
237
|
+
try {
|
|
238
|
+
await warmup
|
|
239
|
+
} finally {
|
|
240
|
+
if (this.audioRouteWarmupPromise === warmup) this.audioRouteWarmupPromise = null
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
129
244
|
private async getGlassesMediaVolumeWithTiming() {
|
|
130
245
|
const startedAt = Date.now()
|
|
131
246
|
try {
|
|
@@ -226,46 +341,120 @@ class AudioPlaybackService {
|
|
|
226
341
|
}
|
|
227
342
|
}
|
|
228
343
|
|
|
229
|
-
private
|
|
344
|
+
private unloadPlaybackSource(playback: PlaybackState, reason: string): void {
|
|
345
|
+
if (this.loadedPlayback !== playback) return
|
|
346
|
+
const player = this.player
|
|
347
|
+
if (!player) {
|
|
348
|
+
this.loadedPlayback = null
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
player.pause()
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.warn(`AUDIO: Error pausing ${playback.requestId} during ${reason}:`, e)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
// replace(null) fails native argument casting on iOS, while remove()
|
|
360
|
+
// destroys the ExoPlayer that Android must reuse to avoid AudioTrack
|
|
361
|
+
// exhaustion. Replacing with bundled silence detaches the previous media
|
|
362
|
+
// item on both platforms and leaves the native player alive.
|
|
363
|
+
player.replace(SILENT_AUDIO_SOURCE)
|
|
364
|
+
console.log(`AUDIO: Cleared source for ${playback.requestId} during ${reason}`)
|
|
365
|
+
} catch (e) {
|
|
366
|
+
console.warn(`AUDIO: Error clearing ${playback.requestId} during ${reason}:`, e)
|
|
367
|
+
} finally {
|
|
368
|
+
if (this.loadedPlayback === playback) this.loadedPlayback = null
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private clearFinishedPlayerAfterTail(playback: PlaybackState): void {
|
|
373
|
+
if (playback.suppressCloudUplink) {
|
|
374
|
+
this.tailUplinkSuppressions.add(playback.uplinkSuppressionId)
|
|
375
|
+
}
|
|
230
376
|
BgTimer.setTimeout(() => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
`AUDIO: Paused finished player for ${playback.requestId} after ${AudioPlaybackService.A2DP_TAIL_DRAIN_MS}ms tail drain`,
|
|
237
|
-
)
|
|
238
|
-
} catch (e) {
|
|
239
|
-
console.warn("AUDIO: Error pausing player after tail drain:", e)
|
|
377
|
+
// ExoPlayer's completion event precedes the final A2DP audio reaching the
|
|
378
|
+
// glasses. Keep cloud STT gated until that audible tail has drained, then
|
|
379
|
+
// accept fresh microphone audio immediately without restarting LC3.
|
|
380
|
+
if (playback.suppressCloudUplink && this.tailUplinkSuppressions.delete(playback.uplinkSuppressionId)) {
|
|
381
|
+
setAudioCloudUplinkSuppressed(playback.uplinkSuppressionId, false)
|
|
240
382
|
}
|
|
383
|
+
this.unloadPlaybackSource(playback, `${AudioPlaybackService.A2DP_TAIL_DRAIN_MS}ms A2DP tail drain`)
|
|
241
384
|
}, AudioPlaybackService.A2DP_TAIL_DRAIN_MS)
|
|
242
385
|
}
|
|
243
386
|
|
|
387
|
+
private releaseTailUplinkSuppressions(): void {
|
|
388
|
+
for (const sourceId of this.tailUplinkSuppressions) {
|
|
389
|
+
setAudioCloudUplinkSuppressed(sourceId, false)
|
|
390
|
+
}
|
|
391
|
+
this.tailUplinkSuppressions.clear()
|
|
392
|
+
}
|
|
393
|
+
|
|
244
394
|
/**
|
|
245
395
|
* Play audio from a URL.
|
|
246
396
|
* Returns a promise that resolves with playback result when audio finishes or errors.
|
|
247
397
|
*/
|
|
248
|
-
public async play(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
398
|
+
public async play(request: AudioPlayRequest, onComplete: AudioPlaybackCompletion): Promise<void> {
|
|
399
|
+
const {requestId, audioUrl, appId, volume = 1.0, stopOtherAudio = true, suppressCloudUplink = false} = request
|
|
400
|
+
const now = Date.now()
|
|
401
|
+
const activeDuplicate =
|
|
402
|
+
this.currentPlayback &&
|
|
403
|
+
!this.currentPlayback.completed &&
|
|
404
|
+
this.currentPlayback.appId === appId &&
|
|
405
|
+
this.currentPlayback.audioUrl === audioUrl &&
|
|
406
|
+
now - this.currentPlayback.startTime < AudioPlaybackService.DUPLICATE_PLAY_WINDOW_MS
|
|
407
|
+
const pendingDuplicate = [...this.pendingPlaybacks.values()].some(
|
|
408
|
+
(candidate) =>
|
|
409
|
+
!candidate.cancelled &&
|
|
410
|
+
candidate.appId === appId &&
|
|
411
|
+
candidate.audioUrl === audioUrl &&
|
|
412
|
+
now - candidate.createdAt < AudioPlaybackService.DUPLICATE_PLAY_WINDOW_MS,
|
|
413
|
+
)
|
|
414
|
+
if (activeDuplicate || pendingDuplicate) {
|
|
415
|
+
console.warn(`AUDIO: Suppressed duplicate play request ${requestId}${appId ? ` from ${appId}` : ""}`)
|
|
416
|
+
onComplete(requestId, false, "Duplicate audio request suppressed", 0, "error")
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const pending: PendingPlaybackState = {cancelled: false, audioUrl, appId, createdAt: now}
|
|
421
|
+
this.pendingPlaybacks.set(requestId, pending)
|
|
253
422
|
|
|
254
423
|
console.log(`AUDIO: Play request ${requestId}${appId ? ` from ${appId}` : ""}: ${audioUrl}`)
|
|
255
424
|
|
|
256
425
|
try {
|
|
257
426
|
// Ensure audio mode is configured for background playback
|
|
258
427
|
await this.ensureAudioModeConfigured()
|
|
428
|
+
if (pending.cancelled) {
|
|
429
|
+
onComplete(requestId, true, null, 0, "interrupted")
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
await this.prewarmAudioRouteIfCold()
|
|
433
|
+
if (pending.cancelled) {
|
|
434
|
+
onComplete(requestId, true, null, 0, "interrupted")
|
|
435
|
+
return
|
|
436
|
+
}
|
|
259
437
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
438
|
+
// URL playback reuses one native player, so replacing its source always
|
|
439
|
+
// interrupts the previous URL even when stopOtherAudio is false. Notify
|
|
440
|
+
// that request before replacing it so its promise cannot hang. The option
|
|
441
|
+
// still controls whether separate PCM streams are interrupted below.
|
|
442
|
+
if (this.currentPlayback && !this.currentPlayback.completed) {
|
|
443
|
+
console.log(`AUDIO: Interrupting current URL playback for new request`)
|
|
263
444
|
this.interruptCurrentPlayback(true)
|
|
264
445
|
}
|
|
265
446
|
// Live PCM streams count as "other audio" too.
|
|
266
447
|
if (stopOtherAudio) {
|
|
267
448
|
await this.abortAllStreams()
|
|
268
449
|
}
|
|
450
|
+
if (pending.cancelled) {
|
|
451
|
+
onComplete(requestId, true, null, 0, "interrupted")
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!suppressCloudUplink) {
|
|
456
|
+
this.releaseTailUplinkSuppressions()
|
|
457
|
+
}
|
|
269
458
|
|
|
270
459
|
// Get or create the reusable player
|
|
271
460
|
const player = this.ensurePlayer()
|
|
@@ -276,16 +465,25 @@ class AudioPlaybackService {
|
|
|
276
465
|
// Store the new playback state
|
|
277
466
|
const playback: PlaybackState = {
|
|
278
467
|
requestId,
|
|
468
|
+
audioUrl,
|
|
469
|
+
uplinkSuppressionId: `url:${requestId}`,
|
|
279
470
|
appId,
|
|
280
471
|
startTime: Date.now(),
|
|
281
472
|
completed: false,
|
|
473
|
+
suppressCloudUplink,
|
|
282
474
|
onComplete,
|
|
283
475
|
}
|
|
284
476
|
this.currentPlayback = playback
|
|
477
|
+
this.pendingPlaybacks.delete(requestId)
|
|
478
|
+
this.markAudioRouteWarm()
|
|
479
|
+
if (playback.suppressCloudUplink) {
|
|
480
|
+
setAudioCloudUplinkSuppressed(playback.uplinkSuppressionId, true)
|
|
481
|
+
}
|
|
285
482
|
|
|
286
483
|
// Replace the source and play
|
|
287
484
|
// Using replace() reuses the existing ExoPlayer/AudioTrack instead of creating new ones
|
|
288
485
|
player.replace({uri: audioUrl})
|
|
486
|
+
this.loadedPlayback = playback
|
|
289
487
|
player.play()
|
|
290
488
|
|
|
291
489
|
// Mentra Live volume reads can block up to 5s when the glasses don't
|
|
@@ -307,10 +505,41 @@ class AudioPlaybackService {
|
|
|
307
505
|
|
|
308
506
|
console.log(`AUDIO: Started playback for ${requestId}`)
|
|
309
507
|
} catch (error) {
|
|
508
|
+
if (suppressCloudUplink) {
|
|
509
|
+
setAudioCloudUplinkSuppressed(`url:${requestId}`, false)
|
|
510
|
+
}
|
|
511
|
+
const failedPlayback = this.currentPlayback?.requestId === requestId ? this.currentPlayback : null
|
|
512
|
+
if (failedPlayback) {
|
|
513
|
+
failedPlayback.completed = true
|
|
514
|
+
this.currentPlayback = null
|
|
515
|
+
this.unloadPlaybackSource(failedPlayback, "playback start failure")
|
|
516
|
+
}
|
|
517
|
+
if (pending.cancelled) {
|
|
518
|
+
onComplete(requestId, true, null, 0, "interrupted")
|
|
519
|
+
return
|
|
520
|
+
}
|
|
310
521
|
const errorMessage = error instanceof Error ? error.message : "Unknown error loading audio"
|
|
311
522
|
console.error(`AUDIO: Failed to play ${requestId}:`, errorMessage)
|
|
312
523
|
void this.restoreGlassesMediaVolume()
|
|
313
|
-
onComplete(requestId, false, errorMessage, null)
|
|
524
|
+
onComplete(requestId, false, errorMessage, null, "error")
|
|
525
|
+
} finally {
|
|
526
|
+
if (this.pendingPlaybacks.get(requestId) === pending) {
|
|
527
|
+
this.pendingPlaybacks.delete(requestId)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Cancel one URL playback without touching audio owned by another request.
|
|
534
|
+
* Pending requests are prevented from reaching the native player; active
|
|
535
|
+
* requests are paused synchronously before their completion callback fires.
|
|
536
|
+
*/
|
|
537
|
+
public cancelPlayback(requestId: string): void {
|
|
538
|
+
const pending = this.pendingPlaybacks.get(requestId)
|
|
539
|
+
if (pending) pending.cancelled = true
|
|
540
|
+
|
|
541
|
+
if (this.currentPlayback?.requestId === requestId && !this.currentPlayback.completed) {
|
|
542
|
+
this.interruptCurrentPlayback()
|
|
314
543
|
}
|
|
315
544
|
}
|
|
316
545
|
|
|
@@ -323,15 +552,14 @@ class AudioPlaybackService {
|
|
|
323
552
|
const playback = this.currentPlayback
|
|
324
553
|
playback.completed = true
|
|
325
554
|
this.currentPlayback = null
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (this.player) {
|
|
329
|
-
try {
|
|
330
|
-
this.player.pause()
|
|
331
|
-
} catch (error) {
|
|
332
|
-
console.error("AUDIO: Error pausing player:", error)
|
|
333
|
-
}
|
|
555
|
+
if (playback.suppressCloudUplink) {
|
|
556
|
+
setAudioCloudUplinkSuppressed(playback.uplinkSuppressionId, false)
|
|
334
557
|
}
|
|
558
|
+
this.markAudioRouteWarm()
|
|
559
|
+
|
|
560
|
+
// Pausing alone leaves the URI available to OS/Bluetooth media Play.
|
|
561
|
+
// Unload it unless a newer request has already replaced this source.
|
|
562
|
+
this.unloadPlaybackSource(playback, "interruption")
|
|
335
563
|
|
|
336
564
|
// Notify native that our app stopped playing audio (debounced)
|
|
337
565
|
this.notifyAudioStopDebounced()
|
|
@@ -341,7 +569,7 @@ class AudioPlaybackService {
|
|
|
341
569
|
|
|
342
570
|
// Notify that playback was interrupted
|
|
343
571
|
const elapsedMs = Date.now() - playback.startTime
|
|
344
|
-
playback.onComplete(playback.requestId, true, null, elapsedMs)
|
|
572
|
+
playback.onComplete(playback.requestId, true, null, elapsedMs, "interrupted")
|
|
345
573
|
console.log(`AUDIO: Interrupted ${playback.requestId} after ${elapsedMs}ms`)
|
|
346
574
|
}
|
|
347
575
|
|
|
@@ -364,11 +592,12 @@ class AudioPlaybackService {
|
|
|
364
592
|
|
|
365
593
|
// ExoPlayer can report finished before an A2DP sink has audibly drained
|
|
366
594
|
// the last buffered audio. Pausing immediately can clip the tail on
|
|
367
|
-
// Mentra Live; defer cleanup
|
|
368
|
-
this.
|
|
595
|
+
// Mentra Live; defer cleanup and guard it against a newer loaded source.
|
|
596
|
+
this.clearFinishedPlayerAfterTail(playback)
|
|
369
597
|
|
|
370
|
-
playback.onComplete(playback.requestId, true, null, durationMs)
|
|
371
598
|
this.currentPlayback = null
|
|
599
|
+
playback.onComplete(playback.requestId, true, null, durationMs, "completed")
|
|
600
|
+
this.markAudioRouteWarm()
|
|
372
601
|
|
|
373
602
|
// Notify native that our app stopped playing audio (debounced)
|
|
374
603
|
this.notifyAudioStopDebounced()
|
|
@@ -385,8 +614,13 @@ class AudioPlaybackService {
|
|
|
385
614
|
if (elapsedMs > 1500) {
|
|
386
615
|
console.error(`AUDIO: Playback failed for ${playback.requestId} (player went idle after ${elapsedMs}ms)`)
|
|
387
616
|
playback.completed = true
|
|
388
|
-
playback.onComplete(playback.requestId, false, "Playback failed (player went idle)", null)
|
|
389
617
|
this.currentPlayback = null
|
|
618
|
+
if (playback.suppressCloudUplink) {
|
|
619
|
+
setAudioCloudUplinkSuppressed(playback.uplinkSuppressionId, false)
|
|
620
|
+
}
|
|
621
|
+
this.unloadPlaybackSource(playback, "playback failure")
|
|
622
|
+
playback.onComplete(playback.requestId, false, "Playback failed (player went idle)", null, "error")
|
|
623
|
+
this.markAudioRouteWarm()
|
|
390
624
|
this.notifyAudioStopDebounced()
|
|
391
625
|
void this.restoreGlassesMediaVolume()
|
|
392
626
|
}
|
|
@@ -407,6 +641,10 @@ class AudioPlaybackService {
|
|
|
407
641
|
// Uses BgTimer to work reliably when app is backgrounded on Android
|
|
408
642
|
this.audioStopDebounceTimer = BgTimer.setTimeout(() => {
|
|
409
643
|
this.audioStopDebounceTimer = null
|
|
644
|
+
// URL playback and live PCM streams may overlap when stopOtherAudio is false.
|
|
645
|
+
// A URL that ends must not clear the native playback signal while another
|
|
646
|
+
// source is still active, or the mic watchdog can interrupt that source.
|
|
647
|
+
if (this.isPlaying()) return
|
|
410
648
|
BluetoothSdk.setOwnAppAudioPlaying(false).catch((e) => {
|
|
411
649
|
console.warn("AUDIO: Failed to notify native of audio stop:", e)
|
|
412
650
|
})
|
|
@@ -425,6 +663,7 @@ class AudioPlaybackService {
|
|
|
425
663
|
console.log(`AUDIO: Stream open ${streamId} from ${appId}: rate=${sampleRate} ch=${channels}`)
|
|
426
664
|
|
|
427
665
|
await this.ensureAudioModeConfigured()
|
|
666
|
+
await this.prewarmAudioRouteIfCold()
|
|
428
667
|
|
|
429
668
|
if (stopOtherAudio) {
|
|
430
669
|
if (this.currentPlayback && !this.currentPlayback.completed) {
|
|
@@ -435,12 +674,20 @@ class AudioPlaybackService {
|
|
|
435
674
|
}
|
|
436
675
|
|
|
437
676
|
await BluetoothSdk.pcmStreamOpen(streamId, sampleRate, channels, Math.max(0, Math.min(1, volume)))
|
|
438
|
-
|
|
439
|
-
|
|
677
|
+
this.releaseTailUplinkSuppressions()
|
|
678
|
+
|
|
679
|
+
const stream: StreamState = {
|
|
680
|
+
streamId,
|
|
681
|
+
appId,
|
|
682
|
+
startTime: Date.now(),
|
|
683
|
+
ended: false,
|
|
684
|
+
onEnded: request.onEnded,
|
|
685
|
+
}
|
|
440
686
|
this.streams.set(streamId, stream)
|
|
687
|
+
this.markAudioRouteWarm()
|
|
441
688
|
|
|
442
|
-
//
|
|
443
|
-
//
|
|
689
|
+
// Cancel any pending "stopped" notification and mark our app as playing
|
|
690
|
+
// audio. PCM streams intentionally do not suppress cloud STT.
|
|
444
691
|
if (this.audioStopDebounceTimer !== null) {
|
|
445
692
|
BgTimer.clearTimeout(this.audioStopDebounceTimer)
|
|
446
693
|
this.audioStopDebounceTimer = null
|
|
@@ -513,6 +760,7 @@ class AudioPlaybackService {
|
|
|
513
760
|
if (stream.ended) return
|
|
514
761
|
stream.ended = true
|
|
515
762
|
this.streams.delete(stream.streamId)
|
|
763
|
+
this.markAudioRouteWarm()
|
|
516
764
|
// Only signal "audio stopped" when nothing else is making noise.
|
|
517
765
|
if (this.streams.size === 0 && (!this.currentPlayback || this.currentPlayback.completed)) {
|
|
518
766
|
this.notifyAudioStopDebounced()
|
|
@@ -616,6 +864,7 @@ class AudioPlaybackService {
|
|
|
616
864
|
console.error("AUDIO: Error releasing player:", error)
|
|
617
865
|
}
|
|
618
866
|
this.player = null
|
|
867
|
+
this.loadedPlayback = null
|
|
619
868
|
}
|
|
620
869
|
}
|
|
621
870
|
}
|
|
@@ -97,22 +97,33 @@ interface ActiveReader {
|
|
|
97
97
|
export class BlobStore {
|
|
98
98
|
private readonly uploads = new Map<string, ActiveUpload>() // key: `${pkg} ${key}`
|
|
99
99
|
private readonly readers = new Map<string, ActiveReader>() // key: host handle id
|
|
100
|
-
/**
|
|
100
|
+
/** user + package → committed usage bytes, cached; invalidated on commit/delete/clear. */
|
|
101
101
|
private readonly usageCache = new Map<string, number>()
|
|
102
102
|
|
|
103
103
|
constructor(private readonly hooks: BlobStoreHooks) {}
|
|
104
104
|
|
|
105
105
|
// ---- path / meta helpers -------------------------------------------------
|
|
106
106
|
|
|
107
|
+
private currentIdentity(packageName: string): {userId: string; scope: string} {
|
|
108
|
+
const userId = this.hooks.getUserId().trim()
|
|
109
|
+
if (!userId) throw new Error("Mentra user identity is unavailable")
|
|
110
|
+
const scope = `${userId}\0${packageName}`
|
|
111
|
+
return {userId, scope}
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
private dirFor(packageName: string): Directory {
|
|
108
|
-
const
|
|
109
|
-
|
|
115
|
+
const dir = new Directory(
|
|
116
|
+
Paths.document,
|
|
117
|
+
ROOT_DIR_NAME,
|
|
118
|
+
sanitizeSegment(this.currentIdentity(packageName).userId),
|
|
119
|
+
sanitizeSegment(packageName),
|
|
120
|
+
)
|
|
110
121
|
if (!dir.exists) dir.create({intermediates: true})
|
|
111
122
|
return dir
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
private metaPrefix(packageName: string): string {
|
|
115
|
-
return `${META_KEY_ROOT}${sanitizeSegment(this.
|
|
126
|
+
return `${META_KEY_ROOT}${sanitizeSegment(this.currentIdentity(packageName).userId)}_${packageName}_`
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
private metaKey(packageName: string, key: string): string {
|
|
@@ -163,17 +174,19 @@ export class BlobStore {
|
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
private committedUsage(packageName: string): number {
|
|
166
|
-
const
|
|
177
|
+
const {scope} = this.currentIdentity(packageName)
|
|
178
|
+
const cached = this.usageCache.get(scope)
|
|
167
179
|
if (cached !== undefined) return cached
|
|
168
180
|
let total = 0
|
|
169
181
|
for (const m of this.allMeta(packageName)) total += m.bytes || 0
|
|
170
|
-
this.usageCache.set(
|
|
182
|
+
this.usageCache.set(scope, total)
|
|
171
183
|
return total
|
|
172
184
|
}
|
|
173
185
|
|
|
174
186
|
private bumpUsage(packageName: string, delta: number): void {
|
|
175
|
-
const
|
|
176
|
-
|
|
187
|
+
const {scope} = this.currentIdentity(packageName)
|
|
188
|
+
const cur = this.usageCache.get(scope)
|
|
189
|
+
if (cur !== undefined) this.usageCache.set(scope, Math.max(0, cur + delta))
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
private safeMd5(file: File, bytes: number): string | undefined {
|
|
@@ -509,7 +522,7 @@ export class BlobStore {
|
|
|
509
522
|
} catch {
|
|
510
523
|
/* best-effort */
|
|
511
524
|
}
|
|
512
|
-
this.usageCache.set(packageName, 0)
|
|
525
|
+
this.usageCache.set(this.currentIdentity(packageName).scope, 0)
|
|
513
526
|
this.hooks.sendResult(packageName, requestId, true)
|
|
514
527
|
} catch (err) {
|
|
515
528
|
this.fail(packageName, requestId, MiniappErrorCode.INTERNAL, err)
|
|
@@ -19,7 +19,19 @@ export class CloudAudioSubscriptionSync {
|
|
|
19
19
|
private pendingKey = ""
|
|
20
20
|
|
|
21
21
|
public begin(nextKey: string): boolean {
|
|
22
|
-
|
|
22
|
+
// Exact duplicate of the write already in flight — nothing to do.
|
|
23
|
+
if (nextKey === this.pendingKey) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
// Matches the last successfully applied set, but ONLY skip when nothing
|
|
27
|
+
// else is in flight. If a different write is pending (e.g. desired went
|
|
28
|
+
// fr-FR -> [] -> fr-FR while the [] write is still on the wire),
|
|
29
|
+
// lastAppliedKey is stale: it names what was applied BEFORE the in-flight
|
|
30
|
+
// write lands. Skipping here would drop the re-subscribe and strand the
|
|
31
|
+
// cloud on the intermediate (empty) set — captions silently die on any
|
|
32
|
+
// rapid A -> B -> A subscription churn. The cloud's own version counter
|
|
33
|
+
// resolves ordering between the concurrent writes, so sending both is safe.
|
|
34
|
+
if (nextKey === this.lastAppliedKey && this.pendingKey === "") {
|
|
23
35
|
return false
|
|
24
36
|
}
|
|
25
37
|
this.pendingKey = nextKey
|