@mentra/engine 0.1.0-dev.0 → 3.1.0-dev.10

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.
Files changed (503) hide show
  1. package/README.md +55 -18
  2. package/build/bluetooth-sdk/debug.d.ts +2 -0
  3. package/build/bluetooth-sdk/debug.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/debug.js +2 -0
  5. package/build/bluetooth-sdk/debug.js.map +1 -0
  6. package/build/bluetooth-sdk/index.d.ts +3 -0
  7. package/build/bluetooth-sdk/index.d.ts.map +1 -0
  8. package/build/bluetooth-sdk/index.js +3 -0
  9. package/build/bluetooth-sdk/index.js.map +1 -0
  10. package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
  11. package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
  12. package/build/bluetooth-sdk/photo-receiver.js +3 -0
  13. package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
  14. package/build/bluetooth-sdk/react.d.ts +2 -0
  15. package/build/bluetooth-sdk/react.d.ts.map +1 -0
  16. package/build/bluetooth-sdk/react.js +2 -0
  17. package/build/bluetooth-sdk/react.js.map +1 -0
  18. package/build/bluetooth-sdk/types.d.ts +2 -0
  19. package/build/bluetooth-sdk/types.d.ts.map +1 -0
  20. package/build/bluetooth-sdk/types.js +2 -0
  21. package/build/bluetooth-sdk/types.js.map +1 -0
  22. package/build/engine.d.ts +24 -16
  23. package/build/engine.d.ts.map +1 -1
  24. package/build/engine.js +9 -4
  25. package/build/engine.js.map +1 -1
  26. package/build/facades/dev.d.ts +3 -2
  27. package/build/facades/dev.d.ts.map +1 -1
  28. package/build/facades/dev.js +18 -12
  29. package/build/facades/dev.js.map +1 -1
  30. package/build/facades/display.d.ts +1 -1
  31. package/build/facades/display.d.ts.map +1 -1
  32. package/build/facades/display.js +1 -1
  33. package/build/facades/display.js.map +1 -1
  34. package/build/facades/gallery.js +1 -1
  35. package/build/facades/gallery.js.map +1 -1
  36. package/build/facades/glasses.d.ts +4 -13
  37. package/build/facades/glasses.d.ts.map +1 -1
  38. package/build/facades/glasses.js +21 -6
  39. package/build/facades/glasses.js.map +1 -1
  40. package/build/facades/glassesSettings.d.ts.map +1 -1
  41. package/build/facades/glassesSettings.js +1 -0
  42. package/build/facades/glassesSettings.js.map +1 -1
  43. package/build/facades/miniapps.d.ts +4 -0
  44. package/build/facades/miniapps.d.ts.map +1 -1
  45. package/build/facades/miniapps.js +5 -0
  46. package/build/facades/miniapps.js.map +1 -1
  47. package/build/facades/ota.d.ts +14 -16
  48. package/build/facades/ota.d.ts.map +1 -1
  49. package/build/facades/ota.js +22 -7
  50. package/build/facades/ota.js.map +1 -1
  51. package/build/facades/pairing.d.ts +8 -5
  52. package/build/facades/pairing.d.ts.map +1 -1
  53. package/build/facades/pairing.js +12 -10
  54. package/build/facades/pairing.js.map +1 -1
  55. package/build/facades/permissions.d.ts +10 -0
  56. package/build/facades/permissions.d.ts.map +1 -1
  57. package/build/facades/permissions.js +10 -0
  58. package/build/facades/permissions.js.map +1 -1
  59. package/build/facades/phoneNotifications.d.ts +0 -4
  60. package/build/facades/phoneNotifications.d.ts.map +1 -1
  61. package/build/facades/phoneNotifications.js +4 -14
  62. package/build/facades/phoneNotifications.js.map +1 -1
  63. package/build/facades/speech.d.ts +7 -6
  64. package/build/facades/speech.d.ts.map +1 -1
  65. package/build/facades/speech.js +1 -1
  66. package/build/facades/speech.js.map +1 -1
  67. package/build/generated/releaseMetadata.d.ts +12 -0
  68. package/build/generated/releaseMetadata.d.ts.map +1 -0
  69. package/build/generated/releaseMetadata.js +10 -0
  70. package/build/generated/releaseMetadata.js.map +1 -0
  71. package/build/index.d.ts +10 -8
  72. package/build/index.d.ts.map +1 -1
  73. package/build/index.js +6 -5
  74. package/build/index.js.map +1 -1
  75. package/build/react/MentraLiveOtaFlow.d.ts +38 -0
  76. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  77. package/build/react/MentraLiveOtaFlow.js +528 -0
  78. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  79. package/build/react/index.d.ts +2 -0
  80. package/build/react/index.d.ts.map +1 -0
  81. package/build/react/index.js +2 -0
  82. package/build/react/index.js.map +1 -0
  83. package/build/react/useEngineSnapshot.d.ts +2 -0
  84. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  85. package/build/react/useEngineSnapshot.js +14 -0
  86. package/build/react/useEngineSnapshot.js.map +1 -0
  87. package/build/runtime/bootstrap.d.ts +24 -1
  88. package/build/runtime/bootstrap.d.ts.map +1 -1
  89. package/build/runtime/bootstrap.js +17 -0
  90. package/build/runtime/bootstrap.js.map +1 -1
  91. package/build/runtime/scanQrSeam.d.ts +7 -0
  92. package/build/runtime/scanQrSeam.d.ts.map +1 -0
  93. package/build/runtime/scanQrSeam.js +16 -0
  94. package/build/runtime/scanQrSeam.js.map +1 -0
  95. package/build/services/AppRegistry.d.ts +41 -57
  96. package/build/services/AppRegistry.d.ts.map +1 -1
  97. package/build/services/AppRegistry.js +208 -144
  98. package/build/services/AppRegistry.js.map +1 -1
  99. package/build/services/AudioCloudUplink.d.ts +12 -0
  100. package/build/services/AudioCloudUplink.d.ts.map +1 -1
  101. package/build/services/AudioCloudUplink.js +22 -1
  102. package/build/services/AudioCloudUplink.js.map +1 -1
  103. package/build/services/AudioPlaybackService.d.ts +41 -2
  104. package/build/services/AudioPlaybackService.d.ts.map +1 -1
  105. package/build/services/AudioPlaybackService.js +250 -33
  106. package/build/services/AudioPlaybackService.js.map +1 -1
  107. package/build/services/BlobStore.d.ts +2 -1
  108. package/build/services/BlobStore.d.ts.map +1 -1
  109. package/build/services/BlobStore.js +17 -9
  110. package/build/services/BlobStore.js.map +1 -1
  111. package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -1
  112. package/build/services/CloudAudioSubscriptionSync.js +13 -1
  113. package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
  114. package/build/services/CloudClientService.d.ts +15 -0
  115. package/build/services/CloudClientService.d.ts.map +1 -1
  116. package/build/services/CloudClientService.js +93 -1
  117. package/build/services/CloudClientService.js.map +1 -1
  118. package/build/services/DeviceEventRouter.js +5 -5
  119. package/build/services/DeviceEventRouter.js.map +1 -1
  120. package/build/services/ForegroundLocationPermission.d.ts +13 -0
  121. package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
  122. package/build/services/ForegroundLocationPermission.js +12 -0
  123. package/build/services/ForegroundLocationPermission.js.map +1 -0
  124. package/build/services/GlassesSettingsSync.d.ts.map +1 -1
  125. package/build/services/GlassesSettingsSync.js +9 -3
  126. package/build/services/GlassesSettingsSync.js.map +1 -1
  127. package/build/services/GlassesStatusProjection.d.ts +2 -1
  128. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  129. package/build/services/GlassesStatusProjection.js +15 -8
  130. package/build/services/GlassesStatusProjection.js.map +1 -1
  131. package/build/services/HotspotOtaTransport.d.ts +29 -0
  132. package/build/services/HotspotOtaTransport.d.ts.map +1 -0
  133. package/build/services/HotspotOtaTransport.js +146 -0
  134. package/build/services/HotspotOtaTransport.js.map +1 -0
  135. package/build/services/HotspotShutdown.d.ts +17 -0
  136. package/build/services/HotspotShutdown.d.ts.map +1 -0
  137. package/build/services/HotspotShutdown.js +29 -0
  138. package/build/services/HotspotShutdown.js.map +1 -0
  139. package/build/services/LocalDisplayManager.d.ts +15 -0
  140. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  141. package/build/services/LocalDisplayManager.js +106 -20
  142. package/build/services/LocalDisplayManager.js.map +1 -1
  143. package/build/services/LocalMiniappRuntime.d.ts +74 -2
  144. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  145. package/build/services/LocalMiniappRuntime.js +785 -291
  146. package/build/services/LocalMiniappRuntime.js.map +1 -1
  147. package/build/services/LocalMiniappStorage.d.ts +32 -0
  148. package/build/services/LocalMiniappStorage.d.ts.map +1 -0
  149. package/build/services/LocalMiniappStorage.js +66 -0
  150. package/build/services/LocalMiniappStorage.js.map +1 -0
  151. package/build/services/LocalMiniappUserIdentity.d.ts +20 -0
  152. package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
  153. package/build/services/LocalMiniappUserIdentity.js +43 -0
  154. package/build/services/LocalMiniappUserIdentity.js.map +1 -0
  155. package/build/services/LocalSttFallbackCoordinator.d.ts +9 -6
  156. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  157. package/build/services/LocalSttFallbackCoordinator.js +18 -12
  158. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  159. package/build/services/MicStateCoordinator.d.ts +42 -1
  160. package/build/services/MicStateCoordinator.d.ts.map +1 -1
  161. package/build/services/MicStateCoordinator.js +141 -3
  162. package/build/services/MicStateCoordinator.js.map +1 -1
  163. package/build/services/MiniappLauncher.d.ts.map +1 -1
  164. package/build/services/MiniappLauncher.js +23 -3
  165. package/build/services/MiniappLauncher.js.map +1 -1
  166. package/build/services/MiniappLiveness.d.ts +12 -0
  167. package/build/services/MiniappLiveness.d.ts.map +1 -0
  168. package/build/services/MiniappLiveness.js +16 -0
  169. package/build/services/MiniappLiveness.js.map +1 -0
  170. package/build/services/NativeHttpResponse.d.ts +3 -0
  171. package/build/services/NativeHttpResponse.d.ts.map +1 -0
  172. package/build/services/NativeHttpResponse.js +6 -0
  173. package/build/services/NativeHttpResponse.js.map +1 -0
  174. package/build/services/OtaArtifactDownloader.d.ts +65 -0
  175. package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
  176. package/build/services/OtaArtifactDownloader.js +226 -0
  177. package/build/services/OtaArtifactDownloader.js.map +1 -0
  178. package/build/services/OtaAutoChain.d.ts +34 -0
  179. package/build/services/OtaAutoChain.d.ts.map +1 -0
  180. package/build/services/OtaAutoChain.js +83 -0
  181. package/build/services/OtaAutoChain.js.map +1 -0
  182. package/build/services/OtaErrorMapping.d.ts +18 -0
  183. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  184. package/build/services/OtaErrorMapping.js +63 -0
  185. package/build/services/OtaErrorMapping.js.map +1 -0
  186. package/build/services/OtaInstallCoordinator.d.ts +95 -15
  187. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  188. package/build/services/OtaInstallCoordinator.js +577 -130
  189. package/build/services/OtaInstallCoordinator.js.map +1 -1
  190. package/build/services/OtaService.d.ts.map +1 -1
  191. package/build/services/OtaService.js +8 -1
  192. package/build/services/OtaService.js.map +1 -1
  193. package/build/services/OtaUpdateCheckService.d.ts +39 -4
  194. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  195. package/build/services/OtaUpdateCheckService.js +138 -22
  196. package/build/services/OtaUpdateCheckService.js.map +1 -1
  197. package/build/services/PhoneCalendarHelpers.d.ts +32 -0
  198. package/build/services/PhoneCalendarHelpers.d.ts.map +1 -0
  199. package/build/services/PhoneCalendarHelpers.js +157 -0
  200. package/build/services/PhoneCalendarHelpers.js.map +1 -0
  201. package/build/services/PhoneCalendarService.d.ts +5 -0
  202. package/build/services/PhoneCalendarService.d.ts.map +1 -0
  203. package/build/services/PhoneCalendarService.js +45 -0
  204. package/build/services/PhoneCalendarService.js.map +1 -0
  205. package/build/services/PhoneCameraFovCoordinator.d.ts +30 -0
  206. package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -0
  207. package/build/services/PhoneCameraFovCoordinator.js +194 -0
  208. package/build/services/PhoneCameraFovCoordinator.js.map +1 -0
  209. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  210. package/build/services/PhoneNotificationsSync.js +12 -10
  211. package/build/services/PhoneNotificationsSync.js.map +1 -1
  212. package/build/services/PhonePhotoCoordinator.d.ts +26 -2
  213. package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
  214. package/build/services/PhonePhotoCoordinator.js +79 -9
  215. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  216. package/build/services/PhoneStreamCoordinator.d.ts +8 -1
  217. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  218. package/build/services/PhoneStreamCoordinator.js +125 -13
  219. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  220. package/build/services/PhoneVideoCoordinator.d.ts +2 -0
  221. package/build/services/PhoneVideoCoordinator.d.ts.map +1 -1
  222. package/build/services/PhoneVideoCoordinator.js +10 -0
  223. package/build/services/PhoneVideoCoordinator.js.map +1 -1
  224. package/build/services/SceneRenderer.d.ts.map +1 -1
  225. package/build/services/SceneRenderer.js +15 -0
  226. package/build/services/SceneRenderer.js.map +1 -1
  227. package/build/services/SentenceTtsPipeline.d.ts +31 -0
  228. package/build/services/SentenceTtsPipeline.d.ts.map +1 -0
  229. package/build/services/SentenceTtsPipeline.js +67 -0
  230. package/build/services/SentenceTtsPipeline.js.map +1 -0
  231. package/build/services/SupportProfileSync.d.ts +22 -0
  232. package/build/services/SupportProfileSync.d.ts.map +1 -0
  233. package/build/services/SupportProfileSync.js +213 -0
  234. package/build/services/SupportProfileSync.js.map +1 -0
  235. package/build/services/TranscriptionRouting.d.ts +12 -0
  236. package/build/services/TranscriptionRouting.d.ts.map +1 -0
  237. package/build/services/TranscriptionRouting.js +24 -0
  238. package/build/services/TranscriptionRouting.js.map +1 -0
  239. package/build/services/TtsTextSanitizer.d.ts +5 -0
  240. package/build/services/TtsTextSanitizer.d.ts.map +1 -0
  241. package/build/services/TtsTextSanitizer.js +61 -0
  242. package/build/services/TtsTextSanitizer.js.map +1 -0
  243. package/build/services/WifiAdbRequest.d.ts +17 -0
  244. package/build/services/WifiAdbRequest.d.ts.map +1 -0
  245. package/build/services/WifiAdbRequest.js +35 -0
  246. package/build/services/WifiAdbRequest.js.map +1 -0
  247. package/build/services/asg/asgCameraApi.d.ts +43 -0
  248. package/build/services/asg/asgCameraApi.d.ts.map +1 -1
  249. package/build/services/asg/asgCameraApi.js +509 -26
  250. package/build/services/asg/asgCameraApi.js.map +1 -1
  251. package/build/services/asg/cameraRollExportCoordinator.d.ts +60 -0
  252. package/build/services/asg/cameraRollExportCoordinator.d.ts.map +1 -0
  253. package/build/services/asg/cameraRollExportCoordinator.js +618 -0
  254. package/build/services/asg/cameraRollExportCoordinator.js.map +1 -0
  255. package/build/services/asg/cameraRollExportLedger.d.ts +44 -0
  256. package/build/services/asg/cameraRollExportLedger.d.ts.map +1 -0
  257. package/build/services/asg/cameraRollExportLedger.js +78 -0
  258. package/build/services/asg/cameraRollExportLedger.js.map +1 -0
  259. package/build/services/asg/galleryNotices.d.ts +1 -1
  260. package/build/services/asg/galleryNotices.d.ts.map +1 -1
  261. package/build/services/asg/galleryNotices.js.map +1 -1
  262. package/build/services/asg/galleryPathMigration.d.ts +3 -0
  263. package/build/services/asg/galleryPathMigration.d.ts.map +1 -0
  264. package/build/services/asg/galleryPathMigration.js +26 -0
  265. package/build/services/asg/galleryPathMigration.js.map +1 -0
  266. package/build/services/asg/gallerySettingsService.d.ts +1 -0
  267. package/build/services/asg/gallerySettingsService.d.ts.map +1 -1
  268. package/build/services/asg/gallerySettingsService.js +2 -0
  269. package/build/services/asg/gallerySettingsService.js.map +1 -1
  270. package/build/services/asg/gallerySyncService.d.ts.map +1 -1
  271. package/build/services/asg/gallerySyncService.js +162 -56
  272. package/build/services/asg/gallerySyncService.js.map +1 -1
  273. package/build/services/asg/galleryTransferLedger.d.ts +58 -0
  274. package/build/services/asg/galleryTransferLedger.d.ts.map +1 -0
  275. package/build/services/asg/galleryTransferLedger.js +225 -0
  276. package/build/services/asg/galleryTransferLedger.js.map +1 -0
  277. package/build/services/asg/localNetworkTransport.d.ts +15 -0
  278. package/build/services/asg/localNetworkTransport.d.ts.map +1 -0
  279. package/build/services/asg/localNetworkTransport.js +144 -0
  280. package/build/services/asg/localNetworkTransport.js.map +1 -0
  281. package/build/services/asg/localStorageService.d.ts +18 -0
  282. package/build/services/asg/localStorageService.d.ts.map +1 -1
  283. package/build/services/asg/localStorageService.js +168 -19
  284. package/build/services/asg/localStorageService.js.map +1 -1
  285. package/build/services/asg/mediaProcessingQueue.d.ts +10 -0
  286. package/build/services/asg/mediaProcessingQueue.d.ts.map +1 -1
  287. package/build/services/asg/mediaProcessingQueue.js +193 -62
  288. package/build/services/asg/mediaProcessingQueue.js.map +1 -1
  289. package/build/services/audioPlaybackAssets.d.ts +8 -0
  290. package/build/services/audioPlaybackAssets.d.ts.map +1 -0
  291. package/build/services/audioPlaybackAssets.js +7 -0
  292. package/build/services/audioPlaybackAssets.js.map +1 -0
  293. package/build/services/gallerySyncClock.js +1 -1
  294. package/build/services/gallerySyncClock.js.map +1 -1
  295. package/build/services/glassesClockSync.d.ts.map +1 -1
  296. package/build/services/glassesClockSync.js +6 -2
  297. package/build/services/glassesClockSync.js.map +1 -1
  298. package/build/services/manifestActions.d.ts +7 -0
  299. package/build/services/manifestActions.d.ts.map +1 -0
  300. package/build/services/manifestActions.js +25 -0
  301. package/build/services/manifestActions.js.map +1 -0
  302. package/build/services/otaDisplayState.d.ts +23 -12
  303. package/build/services/otaDisplayState.d.ts.map +1 -1
  304. package/build/services/otaDisplayState.js +36 -14
  305. package/build/services/otaDisplayState.js.map +1 -1
  306. package/build/services/otaInstallPolicy.d.ts +16 -4
  307. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  308. package/build/services/otaInstallPolicy.js +24 -3
  309. package/build/services/otaInstallPolicy.js.map +1 -1
  310. package/build/services/otaLegacyMapping.d.ts +1 -1
  311. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  312. package/build/services/otaLegacyMapping.js.map +1 -1
  313. package/build/services/otaManifestPolicy.d.ts +14 -0
  314. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  315. package/build/services/otaManifestPolicy.js +19 -0
  316. package/build/services/otaManifestPolicy.js.map +1 -0
  317. package/build/services/otaManifestUrl.d.ts +3 -0
  318. package/build/services/otaManifestUrl.d.ts.map +1 -0
  319. package/build/services/otaManifestUrl.js +38 -0
  320. package/build/services/otaManifestUrl.js.map +1 -0
  321. package/build/services/slimStreamStatus.d.ts +11 -0
  322. package/build/services/slimStreamStatus.d.ts.map +1 -1
  323. package/build/services/slimStreamStatus.js +14 -4
  324. package/build/services/slimStreamStatus.js.map +1 -1
  325. package/build/stores/apps.d.ts.map +1 -1
  326. package/build/stores/apps.js +11 -0
  327. package/build/stores/apps.js.map +1 -1
  328. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  329. package/build/stores/bluetoothSettingKeys.js +2 -0
  330. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  331. package/build/stores/gallerySync.d.ts +2 -0
  332. package/build/stores/gallerySync.d.ts.map +1 -1
  333. package/build/stores/gallerySync.js +2 -0
  334. package/build/stores/gallerySync.js.map +1 -1
  335. package/build/stores/glasses.d.ts.map +1 -1
  336. package/build/stores/glasses.js +3 -0
  337. package/build/stores/glasses.js.map +1 -1
  338. package/build/stores/settings.d.ts +17 -0
  339. package/build/stores/settings.d.ts.map +1 -1
  340. package/build/stores/settings.js +157 -17
  341. package/build/stores/settings.js.map +1 -1
  342. package/build/types/applet.d.ts +2 -0
  343. package/build/types/applet.d.ts.map +1 -1
  344. package/build/types/applet.js.map +1 -1
  345. package/build/types/asg.d.ts +7 -0
  346. package/build/types/asg.d.ts.map +1 -1
  347. package/build/types/asg.js.map +1 -1
  348. package/build/types/enums.d.ts +2 -25
  349. package/build/types/enums.d.ts.map +1 -1
  350. package/build/types/enums.js +2 -27
  351. package/build/types/enums.js.map +1 -1
  352. package/build/types/hardware.d.ts +4 -13
  353. package/build/types/hardware.d.ts.map +1 -1
  354. package/build/types/hardware.js +15 -3
  355. package/build/types/hardware.js.map +1 -1
  356. package/build/types/index.d.ts +2 -6
  357. package/build/types/index.d.ts.map +1 -1
  358. package/build/types/index.js +2 -6
  359. package/build/types/index.js.map +1 -1
  360. package/build/utils/configuredDevHost.d.ts +6 -0
  361. package/build/utils/configuredDevHost.d.ts.map +1 -0
  362. package/build/utils/configuredDevHost.js +19 -0
  363. package/build/utils/configuredDevHost.js.map +1 -0
  364. package/build/utils/devMiniappLaunch.d.ts +19 -8
  365. package/build/utils/devMiniappLaunch.d.ts.map +1 -1
  366. package/build/utils/devMiniappLaunch.js +144 -21
  367. package/build/utils/devMiniappLaunch.js.map +1 -1
  368. package/build/utils/diagnosticContext.d.ts.map +1 -1
  369. package/build/utils/diagnosticContext.js +15 -17
  370. package/build/utils/diagnosticContext.js.map +1 -1
  371. package/build/utils/hardware/hardware.d.ts.map +1 -1
  372. package/build/utils/hardware/hardware.js +7 -3
  373. package/build/utils/hardware/hardware.js.map +1 -1
  374. package/build/utils/miniappDiagnostics.d.ts +64 -0
  375. package/build/utils/miniappDiagnostics.d.ts.map +1 -0
  376. package/build/utils/miniappDiagnostics.js +173 -0
  377. package/build/utils/miniappDiagnostics.js.map +1 -0
  378. package/build/utils/permissions/MediaLibraryPermissions.d.ts +11 -0
  379. package/build/utils/permissions/MediaLibraryPermissions.d.ts.map +1 -1
  380. package/build/utils/permissions/MediaLibraryPermissions.js +27 -4
  381. package/build/utils/permissions/MediaLibraryPermissions.js.map +1 -1
  382. package/build/utils/timers/timers.d.ts.map +1 -1
  383. package/build/utils/timers/timers.js +55 -18
  384. package/build/utils/timers/timers.js.map +1 -1
  385. package/package.json +59 -31
  386. package/src/assets/silence.wav +0 -0
  387. package/src/bluetooth-sdk/debug.ts +1 -0
  388. package/src/bluetooth-sdk/index.ts +2 -0
  389. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  390. package/src/bluetooth-sdk/react.ts +1 -0
  391. package/src/bluetooth-sdk/types.ts +1 -0
  392. package/src/engine.ts +9 -4
  393. package/src/facades/dev.ts +17 -12
  394. package/src/facades/display.ts +1 -1
  395. package/src/facades/gallery.ts +1 -1
  396. package/src/facades/glasses.ts +20 -7
  397. package/src/facades/glassesSettings.ts +5 -0
  398. package/src/facades/miniapps.ts +7 -0
  399. package/src/facades/ota.ts +23 -8
  400. package/src/facades/pairing.ts +15 -11
  401. package/src/facades/permissions.ts +14 -0
  402. package/src/facades/phoneNotifications.ts +4 -13
  403. package/src/facades/speech.ts +21 -7
  404. package/src/generated/releaseMetadata.ts +20 -0
  405. package/src/index.ts +39 -13
  406. package/src/react/MentraLiveOtaFlow.tsx +856 -0
  407. package/src/react/index.ts +7 -0
  408. package/src/react/useEngineSnapshot.ts +16 -0
  409. package/src/runtime/bootstrap.ts +33 -1
  410. package/src/runtime/scanQrSeam.ts +20 -0
  411. package/src/services/AppRegistry.ts +240 -149
  412. package/src/services/AudioCloudUplink.ts +23 -1
  413. package/src/services/AudioPlaybackService.ts +287 -38
  414. package/src/services/BlobStore.ts +22 -9
  415. package/src/services/CloudAudioSubscriptionSync.ts +13 -1
  416. package/src/services/CloudClientService.ts +96 -1
  417. package/src/services/DeviceEventRouter.ts +5 -5
  418. package/src/services/ForegroundLocationPermission.ts +24 -0
  419. package/src/services/GlassesSettingsSync.ts +9 -3
  420. package/src/services/GlassesStatusProjection.ts +16 -8
  421. package/src/services/HotspotOtaTransport.ts +184 -0
  422. package/src/services/HotspotShutdown.ts +41 -0
  423. package/src/services/LocalDisplayManager.ts +112 -21
  424. package/src/services/LocalMiniappRuntime.ts +967 -346
  425. package/src/services/LocalMiniappStorage.ts +82 -0
  426. package/src/services/LocalMiniappUserIdentity.ts +46 -0
  427. package/src/services/LocalSttFallbackCoordinator.ts +19 -12
  428. package/src/services/MicStateCoordinator.ts +169 -4
  429. package/src/services/MiniappLauncher.ts +30 -3
  430. package/src/services/MiniappLiveness.ts +24 -0
  431. package/src/services/NativeHttpResponse.ts +6 -0
  432. package/src/services/OtaArtifactDownloader.ts +312 -0
  433. package/src/services/OtaAutoChain.ts +106 -0
  434. package/src/services/OtaErrorMapping.ts +80 -0
  435. package/src/services/OtaInstallCoordinator.ts +649 -144
  436. package/src/services/OtaService.ts +11 -5
  437. package/src/services/OtaUpdateCheckService.ts +181 -21
  438. package/src/services/PhoneCalendarHelpers.ts +212 -0
  439. package/src/services/PhoneCalendarService.ts +65 -0
  440. package/src/services/PhoneCameraFovCoordinator.ts +210 -0
  441. package/src/services/PhoneNotificationsSync.ts +12 -10
  442. package/src/services/PhonePhotoCoordinator.ts +116 -18
  443. package/src/services/PhoneStreamCoordinator.ts +139 -25
  444. package/src/services/PhoneVideoCoordinator.ts +11 -0
  445. package/src/services/SceneRenderer.ts +16 -0
  446. package/src/services/SentenceTtsPipeline.ts +106 -0
  447. package/src/services/SupportProfileSync.ts +218 -0
  448. package/src/services/TranscriptionRouting.ts +37 -0
  449. package/src/services/TtsTextSanitizer.ts +66 -0
  450. package/src/services/WifiAdbRequest.ts +53 -0
  451. package/src/services/asg/asgCameraApi.ts +615 -33
  452. package/src/services/asg/cameraRollExportCoordinator.ts +627 -0
  453. package/src/services/asg/cameraRollExportLedger.ts +122 -0
  454. package/src/services/asg/galleryNotices.ts +1 -0
  455. package/src/services/asg/galleryPathMigration.ts +22 -0
  456. package/src/services/asg/gallerySettingsService.ts +3 -0
  457. package/src/services/asg/gallerySyncService.ts +184 -73
  458. package/src/services/asg/galleryTransferLedger.ts +312 -0
  459. package/src/services/asg/localNetworkTransport.ts +169 -0
  460. package/src/services/asg/localStorageService.ts +179 -24
  461. package/src/services/asg/mediaProcessingQueue.ts +209 -62
  462. package/src/services/audioPlaybackAssets.ts +8 -0
  463. package/src/services/gallerySyncClock.ts +1 -1
  464. package/src/services/glassesClockSync.ts +6 -2
  465. package/src/services/manifestActions.ts +30 -0
  466. package/src/services/otaDisplayState.ts +58 -17
  467. package/src/services/otaInstallPolicy.ts +30 -4
  468. package/src/services/otaLegacyMapping.ts +1 -1
  469. package/src/services/otaManifestPolicy.ts +42 -0
  470. package/src/services/otaManifestUrl.ts +44 -0
  471. package/src/services/slimStreamStatus.ts +16 -5
  472. package/src/stores/apps.ts +12 -0
  473. package/src/stores/bluetoothSettingKeys.ts +2 -0
  474. package/src/stores/gallerySync.ts +4 -0
  475. package/src/stores/glasses.ts +3 -0
  476. package/src/stores/settings.ts +179 -18
  477. package/src/types/applet.ts +2 -0
  478. package/src/types/asg.ts +7 -0
  479. package/src/types/enums.ts +3 -28
  480. package/src/types/hardware.ts +20 -13
  481. package/src/types/index.ts +2 -6
  482. package/src/utils/configuredDevHost.ts +17 -0
  483. package/src/utils/devMiniappLaunch.ts +151 -23
  484. package/src/utils/diagnosticContext.ts +17 -17
  485. package/src/utils/hardware/hardware.ts +7 -9
  486. package/src/utils/miniappDiagnostics.ts +226 -0
  487. package/src/utils/permissions/MediaLibraryPermissions.ts +35 -4
  488. package/src/utils/timers/timers.ts +57 -21
  489. package/build/devtools.d.ts +0 -11
  490. package/build/devtools.d.ts.map +0 -1
  491. package/build/devtools.js +0 -13
  492. package/build/devtools.js.map +0 -1
  493. package/build/internal.d.ts +0 -66
  494. package/build/internal.d.ts.map +0 -1
  495. package/build/internal.js +0 -89
  496. package/build/internal.js.map +0 -1
  497. package/build/services/asgOtaVersionUrl.d.ts +0 -2
  498. package/build/services/asgOtaVersionUrl.d.ts.map +0 -1
  499. package/build/services/asgOtaVersionUrl.js +0 -36
  500. package/build/services/asgOtaVersionUrl.js.map +0 -1
  501. package/src/devtools.ts +0 -13
  502. package/src/internal.ts +0 -158
  503. 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 (cloudClientService.isConnected() && cloudClientService.hasAudioSubscriptions()) {
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
- onComplete: (requestId: string, success: boolean, error: string | null, duration: number | null) => void
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 = 900
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 pauseFinishedPlayerAfterTail(playback: PlaybackState): void {
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
- if (this.currentPlayback) return
232
- if (!this.player) return
233
- try {
234
- this.player.pause()
235
- console.log(
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
- request: AudioPlayRequest,
250
- onComplete: (requestId: string, success: boolean, error: string | null, duration: number | null) => void,
251
- ): Promise<void> {
252
- const {requestId, audioUrl, appId, volume = 1.0, stopOtherAudio = true} = request
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
- // Stop current playback if any (notify previous callback)
261
- if (stopOtherAudio && this.currentPlayback && !this.currentPlayback.completed) {
262
- console.log(`AUDIO: Interrupting current playback for new request`)
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
- // Stop the player
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 unless another playback has started.
368
- this.pauseFinishedPlayerAfterTail(playback)
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
- const stream: StreamState = {streamId, appId, startTime: Date.now(), ended: false, onEnded: request.onEnded}
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
- // Same LC3-mic-suspend dance as play(): cancel any pending "stopped"
443
- // notification and mark our app as playing audio.
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
- /** packageName → committed usage bytes, cached; invalidated on commit/delete/clear. */
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 userId = sanitizeSegment(this.hooks.getUserId())
109
- const dir = new Directory(Paths.document, ROOT_DIR_NAME, userId, sanitizeSegment(packageName))
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.hooks.getUserId())}_${packageName}_`
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 cached = this.usageCache.get(packageName)
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(packageName, total)
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 cur = this.usageCache.get(packageName)
176
- if (cur !== undefined) this.usageCache.set(packageName, Math.max(0, cur + delta))
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
- if (nextKey === this.lastAppliedKey || nextKey === this.pendingKey) {
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