@glassly/engine 0.1.0-dev.99 → 0.2.8-dev.445

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 (501) hide show
  1. package/README.md +28 -0
  2. package/build/engine.d.ts +32 -10
  3. package/build/engine.d.ts.map +1 -1
  4. package/build/engine.js +24 -16
  5. package/build/engine.js.map +1 -1
  6. package/build/facades/dev.d.ts +9 -0
  7. package/build/facades/dev.d.ts.map +1 -1
  8. package/build/facades/dev.js +9 -0
  9. package/build/facades/dev.js.map +1 -1
  10. package/build/facades/display.d.ts +10 -3
  11. package/build/facades/display.d.ts.map +1 -1
  12. package/build/facades/display.js +22 -8
  13. package/build/facades/display.js.map +1 -1
  14. package/build/facades/displayMirror.d.ts +11 -0
  15. package/build/facades/displayMirror.d.ts.map +1 -1
  16. package/build/facades/displayMirror.js +22 -0
  17. package/build/facades/displayMirror.js.map +1 -1
  18. package/build/facades/glasses.d.ts +31 -1
  19. package/build/facades/glasses.d.ts.map +1 -1
  20. package/build/facades/glasses.js +51 -0
  21. package/build/facades/glasses.js.map +1 -1
  22. package/build/facades/memory.d.ts +0 -6
  23. package/build/facades/memory.d.ts.map +1 -1
  24. package/build/facades/memory.js +1 -9
  25. package/build/facades/memory.js.map +1 -1
  26. package/build/facades/miniapps.d.ts +3 -1
  27. package/build/facades/miniapps.d.ts.map +1 -1
  28. package/build/facades/miniapps.js +3 -1
  29. package/build/facades/miniapps.js.map +1 -1
  30. package/build/facades/pairing.d.ts.map +1 -1
  31. package/build/facades/pairing.js +5 -4
  32. package/build/facades/pairing.js.map +1 -1
  33. package/build/facades/permissions.d.ts +2 -0
  34. package/build/facades/permissions.d.ts.map +1 -1
  35. package/build/facades/permissions.js +48 -0
  36. package/build/facades/permissions.js.map +1 -1
  37. package/build/facades/phoneNotifications.d.ts +20 -9
  38. package/build/facades/phoneNotifications.d.ts.map +1 -1
  39. package/build/facades/phoneNotifications.js +33 -26
  40. package/build/facades/phoneNotifications.js.map +1 -1
  41. package/build/facades/settings.d.ts.map +1 -1
  42. package/build/facades/settings.js +1 -2
  43. package/build/facades/settings.js.map +1 -1
  44. package/build/generated/releaseMetadata.js +4 -4
  45. package/build/generated/releaseMetadata.js.map +1 -1
  46. package/build/index.d.ts +6 -4
  47. package/build/index.d.ts.map +1 -1
  48. package/build/index.js +5 -4
  49. package/build/index.js.map +1 -1
  50. package/build/react/MentraLiveOtaFlow.js +2 -2
  51. package/build/react/MentraLiveOtaFlow.js.map +1 -1
  52. package/build/react/useMentraLiveOta.d.ts.map +1 -1
  53. package/build/react/useMentraLiveOta.js +8 -7
  54. package/build/react/useMentraLiveOta.js.map +1 -1
  55. package/build/runtime/config.d.ts +1 -1
  56. package/build/runtime/config.d.ts.map +1 -1
  57. package/build/runtime/config.js.map +1 -1
  58. package/build/services/AppRegistry.d.ts +4 -1
  59. package/build/services/AppRegistry.d.ts.map +1 -1
  60. package/build/services/AppRegistry.js +21 -1
  61. package/build/services/AppRegistry.js.map +1 -1
  62. package/build/services/BlobStore.d.ts +2 -2
  63. package/build/services/BlobStore.js +3 -3
  64. package/build/services/BlobStore.js.map +1 -1
  65. package/build/services/CloudClientService.d.ts +7 -3
  66. package/build/services/CloudClientService.d.ts.map +1 -1
  67. package/build/services/CloudClientService.js +34 -9
  68. package/build/services/CloudClientService.js.map +1 -1
  69. package/build/services/DevServerBridge.d.ts +1 -18
  70. package/build/services/DevServerBridge.d.ts.map +1 -1
  71. package/build/services/DevServerBridge.js +9 -8
  72. package/build/services/DevServerBridge.js.map +1 -1
  73. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  74. package/build/services/DeviceEventRouter.js +58 -71
  75. package/build/services/DeviceEventRouter.js.map +1 -1
  76. package/build/services/DisplayDiagnostics.d.ts +5 -0
  77. package/build/services/DisplayDiagnostics.d.ts.map +1 -0
  78. package/build/services/DisplayDiagnostics.js +105 -0
  79. package/build/services/DisplayDiagnostics.js.map +1 -0
  80. package/build/services/DisplayProcessor.d.ts +6 -7
  81. package/build/services/DisplayProcessor.d.ts.map +1 -1
  82. package/build/services/DisplayProcessor.js +17 -45
  83. package/build/services/DisplayProcessor.js.map +1 -1
  84. package/build/services/GestureDotsService.d.ts +10 -4
  85. package/build/services/GestureDotsService.d.ts.map +1 -1
  86. package/build/services/GestureDotsService.js +70 -16
  87. package/build/services/GestureDotsService.js.map +1 -1
  88. package/build/services/GlassesKeyboardInput.d.ts +77 -0
  89. package/build/services/GlassesKeyboardInput.d.ts.map +1 -0
  90. package/build/services/GlassesKeyboardInput.js +261 -0
  91. package/build/services/GlassesKeyboardInput.js.map +1 -0
  92. package/build/services/GlassesSensorService.d.ts +47 -0
  93. package/build/services/GlassesSensorService.d.ts.map +1 -0
  94. package/build/services/GlassesSensorService.js +242 -0
  95. package/build/services/GlassesSensorService.js.map +1 -0
  96. package/build/services/GlassesTouchInput.d.ts +14 -0
  97. package/build/services/GlassesTouchInput.d.ts.map +1 -0
  98. package/build/services/GlassesTouchInput.js +107 -0
  99. package/build/services/GlassesTouchInput.js.map +1 -0
  100. package/build/services/HeadingService.d.ts +0 -6
  101. package/build/services/HeadingService.d.ts.map +1 -1
  102. package/build/services/HeadingService.js +0 -27
  103. package/build/services/HeadingService.js.map +1 -1
  104. package/build/services/HotspotShutdown.d.ts.map +1 -1
  105. package/build/services/HotspotShutdown.js +2 -1
  106. package/build/services/HotspotShutdown.js.map +1 -1
  107. package/build/services/JSCrashloopReportService.js +2 -2
  108. package/build/services/JSCrashloopReportService.js.map +1 -1
  109. package/build/services/JSRouter.d.ts +6 -4
  110. package/build/services/JSRouter.d.ts.map +1 -1
  111. package/build/services/JSRouter.js +15 -13
  112. package/build/services/JSRouter.js.map +1 -1
  113. package/build/services/LocalDisplayManager.d.ts +35 -7
  114. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  115. package/build/services/LocalDisplayManager.js +105 -27
  116. package/build/services/LocalDisplayManager.js.map +1 -1
  117. package/build/services/LocalMiniappDataMigration.d.ts +1 -1
  118. package/build/services/LocalMiniappDataMigration.js +1 -1
  119. package/build/services/LocalMiniappDataMigration.js.map +1 -1
  120. package/build/services/LocalMiniappRuntime.d.ts +64 -11
  121. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  122. package/build/services/LocalMiniappRuntime.js +441 -35
  123. package/build/services/LocalMiniappRuntime.js.map +1 -1
  124. package/build/services/LocalSttFallbackCoordinator.d.ts +3 -0
  125. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  126. package/build/services/LocalSttFallbackCoordinator.js +27 -8
  127. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  128. package/build/services/MemoryStore.d.ts +8 -33
  129. package/build/services/MemoryStore.d.ts.map +1 -1
  130. package/build/services/MemoryStore.js +30 -56
  131. package/build/services/MemoryStore.js.map +1 -1
  132. package/build/services/MiniappEngine.d.ts +3 -3
  133. package/build/services/MiniappEngine.js +7 -7
  134. package/build/services/MiniappEngine.js.map +1 -1
  135. package/build/services/MiniappLauncher.d.ts +8 -0
  136. package/build/services/MiniappLauncher.d.ts.map +1 -1
  137. package/build/services/MiniappLauncher.js +19 -1
  138. package/build/services/MiniappLauncher.js.map +1 -1
  139. package/build/services/MirrorService.d.ts +24 -0
  140. package/build/services/MirrorService.d.ts.map +1 -0
  141. package/build/services/MirrorService.js +132 -0
  142. package/build/services/MirrorService.js.map +1 -0
  143. package/build/services/NativeNavigationService.d.ts +2 -3
  144. package/build/services/NativeNavigationService.d.ts.map +1 -1
  145. package/build/services/NativeNavigationService.js +24 -21
  146. package/build/services/NativeNavigationService.js.map +1 -1
  147. package/build/services/NavigationHandlers.d.ts.map +1 -1
  148. package/build/services/NavigationHandlers.js +10 -5
  149. package/build/services/NavigationHandlers.js.map +1 -1
  150. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  151. package/build/services/OtaInstallCoordinator.js +24 -23
  152. package/build/services/OtaInstallCoordinator.js.map +1 -1
  153. package/build/services/PairedGlassesLatch.d.ts +7 -0
  154. package/build/services/PairedGlassesLatch.d.ts.map +1 -0
  155. package/build/services/PairedGlassesLatch.js +38 -0
  156. package/build/services/PairedGlassesLatch.js.map +1 -0
  157. package/build/services/PhoneActivity.d.ts +23 -0
  158. package/build/services/PhoneActivity.d.ts.map +1 -0
  159. package/build/services/PhoneActivity.js +111 -0
  160. package/build/services/PhoneActivity.js.map +1 -0
  161. package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -1
  162. package/build/services/PhoneCameraFovCoordinator.js +4 -3
  163. package/build/services/PhoneCameraFovCoordinator.js.map +1 -1
  164. package/build/services/PhoneNotificationsSync.d.ts +40 -6
  165. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  166. package/build/services/PhoneNotificationsSync.js +160 -84
  167. package/build/services/PhoneNotificationsSync.js.map +1 -1
  168. package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
  169. package/build/services/PhonePhotoCoordinator.js +6 -5
  170. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  171. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  172. package/build/services/PhoneStreamCoordinator.js +8 -7
  173. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  174. package/build/services/STTModelManager.d.ts +3 -0
  175. package/build/services/STTModelManager.d.ts.map +1 -1
  176. package/build/services/STTModelManager.js +15 -1
  177. package/build/services/STTModelManager.js.map +1 -1
  178. package/build/services/SceneRenderer.d.ts +43 -2
  179. package/build/services/SceneRenderer.d.ts.map +1 -1
  180. package/build/services/SceneRenderer.js +123 -8
  181. package/build/services/SceneRenderer.js.map +1 -1
  182. package/build/services/StreamLifecycleController.d.ts.map +1 -1
  183. package/build/services/StreamLifecycleController.js +6 -14
  184. package/build/services/StreamLifecycleController.js.map +1 -1
  185. package/build/services/TTSModelManager.js +1 -1
  186. package/build/services/TTSModelManager.js.map +1 -1
  187. package/build/services/UIRouter.d.ts +2 -2
  188. package/build/services/UIRouter.js +5 -5
  189. package/build/services/UIRouter.js.map +1 -1
  190. package/build/services/asg/galleryPathMigration.js +1 -1
  191. package/build/services/asg/galleryPathMigration.js.map +1 -1
  192. package/build/services/asg/gallerySyncService.js +2 -2
  193. package/build/services/asg/gallerySyncService.js.map +1 -1
  194. package/build/services/asg/localStorageService.d.ts.map +1 -1
  195. package/build/services/asg/localStorageService.js +3 -3
  196. package/build/services/asg/localStorageService.js.map +1 -1
  197. package/build/services/debug/DashboardOpenTrace.d.ts +32 -0
  198. package/build/services/debug/DashboardOpenTrace.d.ts.map +1 -0
  199. package/build/services/debug/DashboardOpenTrace.js +128 -0
  200. package/build/services/debug/DashboardOpenTrace.js.map +1 -0
  201. package/build/services/debug/dashboardOpenTimeline.d.ts +77 -0
  202. package/build/services/debug/dashboardOpenTimeline.d.ts.map +1 -0
  203. package/build/services/debug/dashboardOpenTimeline.js +195 -0
  204. package/build/services/debug/dashboardOpenTimeline.js.map +1 -0
  205. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  206. package/build/services/effectiveCapabilities.js +25 -13
  207. package/build/services/effectiveCapabilities.js.map +1 -1
  208. package/build/services/gestureClaims.d.ts +34 -15
  209. package/build/services/gestureClaims.d.ts.map +1 -1
  210. package/build/services/gestureClaims.js +42 -21
  211. package/build/services/gestureClaims.js.map +1 -1
  212. package/build/services/manifestFields.d.ts +9 -0
  213. package/build/services/manifestFields.d.ts.map +1 -0
  214. package/build/services/manifestFields.js +26 -0
  215. package/build/services/manifestFields.js.map +1 -0
  216. package/build/services/memoryStoreInstance.d.ts +0 -10
  217. package/build/services/memoryStoreInstance.d.ts.map +1 -1
  218. package/build/services/memoryStoreInstance.js +2 -31
  219. package/build/services/memoryStoreInstance.js.map +1 -1
  220. package/build/services/mirrorSnapshot.d.ts +16 -0
  221. package/build/services/mirrorSnapshot.d.ts.map +1 -0
  222. package/build/services/mirrorSnapshot.js +102 -0
  223. package/build/services/mirrorSnapshot.js.map +1 -0
  224. package/build/services/nativeNavigationGate.d.ts +8 -0
  225. package/build/services/nativeNavigationGate.d.ts.map +1 -0
  226. package/build/services/nativeNavigationGate.js +12 -0
  227. package/build/services/nativeNavigationGate.js.map +1 -0
  228. package/build/services/notificationAppCatalog.d.ts +7 -0
  229. package/build/services/notificationAppCatalog.d.ts.map +1 -0
  230. package/build/services/notificationAppCatalog.js +16 -0
  231. package/build/services/notificationAppCatalog.js.map +1 -0
  232. package/build/services/notifications/notificationPolicy.d.ts +55 -0
  233. package/build/services/notifications/notificationPolicy.d.ts.map +1 -0
  234. package/build/services/notifications/notificationPolicy.js +41 -0
  235. package/build/services/notifications/notificationPolicy.js.map +1 -0
  236. package/build/services/pages/OverlayPages.d.ts +26 -0
  237. package/build/services/pages/OverlayPages.d.ts.map +1 -0
  238. package/build/services/pages/OverlayPages.js +80 -0
  239. package/build/services/pages/OverlayPages.js.map +1 -0
  240. package/build/services/pages/PageHost.d.ts +32 -0
  241. package/build/services/pages/PageHost.d.ts.map +1 -0
  242. package/build/services/pages/PageHost.js +99 -0
  243. package/build/services/pages/PageHost.js.map +1 -0
  244. package/build/services/pages/overlayApps.d.ts +25 -0
  245. package/build/services/pages/overlayApps.d.ts.map +1 -0
  246. package/build/services/pages/overlayApps.js +66 -0
  247. package/build/services/pages/overlayApps.js.map +1 -0
  248. package/build/services/pages/pageEligibility.d.ts +9 -0
  249. package/build/services/pages/pageEligibility.d.ts.map +1 -0
  250. package/build/services/pages/pageEligibility.js +27 -0
  251. package/build/services/pages/pageEligibility.js.map +1 -0
  252. package/build/services/settingsBridge.d.ts +58 -0
  253. package/build/services/settingsBridge.d.ts.map +1 -0
  254. package/build/services/settingsBridge.js +90 -0
  255. package/build/services/settingsBridge.js.map +1 -0
  256. package/build/services/speakerProfileStoreInstance.js +1 -1
  257. package/build/services/speakerProfileStoreInstance.js.map +1 -1
  258. package/build/services/uiShim.d.ts +1 -1
  259. package/build/services/uiShim.js +4 -4
  260. package/build/services/uiShim.js.map +1 -1
  261. package/build/stores/appVisibility.d.ts +28 -0
  262. package/build/stores/appVisibility.d.ts.map +1 -1
  263. package/build/stores/appVisibility.js +34 -0
  264. package/build/stores/appVisibility.js.map +1 -1
  265. package/build/stores/apps.d.ts +13 -0
  266. package/build/stores/apps.d.ts.map +1 -1
  267. package/build/stores/apps.js +66 -12
  268. package/build/stores/apps.js.map +1 -1
  269. package/build/stores/bluetoothSettingKeys.js +1 -1
  270. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  271. package/build/stores/display.d.ts +3 -3
  272. package/build/stores/display.d.ts.map +1 -1
  273. package/build/stores/display.js +16 -4
  274. package/build/stores/display.js.map +1 -1
  275. package/build/stores/glasses.d.ts.map +1 -1
  276. package/build/stores/glasses.js +14 -1
  277. package/build/stores/glasses.js.map +1 -1
  278. package/build/stores/settings.d.ts +1 -0
  279. package/build/stores/settings.d.ts.map +1 -1
  280. package/build/stores/settings.js +170 -44
  281. package/build/stores/settings.js.map +1 -1
  282. package/build/types/applet.d.ts +14 -1
  283. package/build/types/applet.d.ts.map +1 -1
  284. package/build/types/applet.js.map +1 -1
  285. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  286. package/build/types/capabilities/even-realities-g2.js +5 -0
  287. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  288. package/build/types/capabilities/simulated-glasses.d.ts +1 -1
  289. package/build/types/capabilities/simulated-glasses.d.ts.map +1 -1
  290. package/build/types/capabilities/simulated-glasses.js +7 -4
  291. package/build/types/capabilities/simulated-glasses.js.map +1 -1
  292. package/build/types/hardware.d.ts +20 -1
  293. package/build/types/hardware.d.ts.map +1 -1
  294. package/build/types/hardware.js +2 -0
  295. package/build/types/hardware.js.map +1 -1
  296. package/build/types/index.d.ts +1 -0
  297. package/build/types/index.d.ts.map +1 -1
  298. package/build/types/index.js +1 -0
  299. package/build/types/index.js.map +1 -1
  300. package/build/utils/debouncedPatch.d.ts +3 -1
  301. package/build/utils/debouncedPatch.d.ts.map +1 -1
  302. package/build/utils/debouncedPatch.js +6 -3
  303. package/build/utils/debouncedPatch.js.map +1 -1
  304. package/build/utils/devMiniappLaunch.d.ts.map +1 -1
  305. package/build/utils/devMiniappLaunch.js +4 -3
  306. package/build/utils/devMiniappLaunch.js.map +1 -1
  307. package/build/utils/display/measurer/TextMeasurer.d.ts +2 -159
  308. package/build/utils/display/measurer/TextMeasurer.d.ts.map +1 -1
  309. package/build/utils/display/measurer/TextMeasurer.js +2 -272
  310. package/build/utils/display/measurer/TextMeasurer.js.map +1 -1
  311. package/build/utils/display/measurer/script-detection.d.ts +1 -52
  312. package/build/utils/display/measurer/script-detection.d.ts.map +1 -1
  313. package/build/utils/display/measurer/script-detection.js +2 -183
  314. package/build/utils/display/measurer/script-detection.js.map +1 -1
  315. package/build/utils/display/pixelArt.d.ts +3 -0
  316. package/build/utils/display/pixelArt.d.ts.map +1 -0
  317. package/build/utils/display/pixelArt.js +3 -0
  318. package/build/utils/display/pixelArt.js.map +1 -0
  319. package/build/utils/display/profiles/g1.d.ts +1 -32
  320. package/build/utils/display/profiles/g1.d.ts.map +1 -1
  321. package/build/utils/display/profiles/g1.js +2 -212
  322. package/build/utils/display/profiles/g1.js.map +1 -1
  323. package/build/utils/display/profiles/g2.d.ts +1 -12
  324. package/build/utils/display/profiles/g2.d.ts.map +1 -1
  325. package/build/utils/display/profiles/g2.js +2 -33
  326. package/build/utils/display/profiles/g2.js.map +1 -1
  327. package/build/utils/display/profiles/nex.d.ts +1 -9
  328. package/build/utils/display/profiles/nex.d.ts.map +1 -1
  329. package/build/utils/display/profiles/nex.js +2 -149
  330. package/build/utils/display/profiles/nex.js.map +1 -1
  331. package/build/utils/display/profiles/types.d.ts +1 -102
  332. package/build/utils/display/profiles/types.d.ts.map +1 -1
  333. package/build/utils/display/profiles/types.js.map +1 -1
  334. package/build/utils/display/profiles/z100.d.ts +1 -32
  335. package/build/utils/display/profiles/z100.d.ts.map +1 -1
  336. package/build/utils/display/profiles/z100.js +2 -181
  337. package/build/utils/display/profiles/z100.js.map +1 -1
  338. package/build/utils/display/scene/degrade.d.ts.map +1 -1
  339. package/build/utils/display/scene/degrade.js +4 -1
  340. package/build/utils/display/scene/degrade.js.map +1 -1
  341. package/build/utils/display/scene/differ.d.ts +13 -1
  342. package/build/utils/display/scene/differ.d.ts.map +1 -1
  343. package/build/utils/display/scene/differ.js +22 -0
  344. package/build/utils/display/scene/differ.js.map +1 -1
  345. package/build/utils/display/scene/index.d.ts +1 -1
  346. package/build/utils/display/scene/index.d.ts.map +1 -1
  347. package/build/utils/display/scene/index.js.map +1 -1
  348. package/build/utils/display/scene/process.d.ts.map +1 -1
  349. package/build/utils/display/scene/process.js +183 -11
  350. package/build/utils/display/scene/process.js.map +1 -1
  351. package/build/utils/display/scene/store.d.ts +1 -1
  352. package/build/utils/display/scene/store.d.ts.map +1 -1
  353. package/build/utils/display/scene/store.js +0 -0
  354. package/build/utils/display/scene/store.js.map +1 -1
  355. package/build/utils/display/scene/svgPath.d.ts +9 -0
  356. package/build/utils/display/scene/svgPath.d.ts.map +1 -0
  357. package/build/utils/display/scene/svgPath.js +167 -0
  358. package/build/utils/display/scene/svgPath.js.map +1 -0
  359. package/build/utils/display/scene/types.d.ts +2 -142
  360. package/build/utils/display/scene/types.d.ts.map +1 -1
  361. package/build/utils/display/scene/types.js +0 -0
  362. package/build/utils/display/scene/types.js.map +1 -1
  363. package/build/utils/display/wrapper/TextWrapper.d.ts +1 -101
  364. package/build/utils/display/wrapper/TextWrapper.d.ts.map +1 -1
  365. package/build/utils/display/wrapper/TextWrapper.js +2 -470
  366. package/build/utils/display/wrapper/TextWrapper.js.map +1 -1
  367. package/build/utils/display/wrapper/types.d.ts +2 -74
  368. package/build/utils/display/wrapper/types.d.ts.map +1 -1
  369. package/build/utils/display/wrapper/types.js +2 -10
  370. package/build/utils/display/wrapper/types.js.map +1 -1
  371. package/build/utils/hardware/hardware.js +1 -1
  372. package/build/utils/hardware/hardware.js.map +1 -1
  373. package/build/utils/timers/timers.d.ts.map +1 -1
  374. package/build/utils/timers/timers.js +26 -4
  375. package/build/utils/timers/timers.js.map +1 -1
  376. package/package.json +8 -8
  377. package/src/engine.ts +24 -15
  378. package/src/facades/dev.ts +10 -0
  379. package/src/facades/display.ts +21 -8
  380. package/src/facades/displayMirror.ts +29 -0
  381. package/src/facades/glasses.ts +61 -1
  382. package/src/facades/memory.ts +1 -12
  383. package/src/facades/miniapps.ts +4 -1
  384. package/src/facades/pairing.ts +8 -6
  385. package/src/facades/permissions.ts +46 -0
  386. package/src/facades/phoneNotifications.ts +45 -26
  387. package/src/facades/settings.ts +1 -2
  388. package/src/generated/releaseMetadata.ts +4 -4
  389. package/src/index.ts +6 -9
  390. package/src/react/MentraLiveOtaFlow.tsx +2 -2
  391. package/src/react/useMentraLiveOta.ts +14 -8
  392. package/src/runtime/config.ts +1 -1
  393. package/src/services/AppRegistry.ts +43 -7
  394. package/src/services/BlobStore.ts +3 -3
  395. package/src/services/CloudClientService.ts +37 -10
  396. package/src/services/DevServerBridge.ts +12 -10
  397. package/src/services/DeviceEventRouter.ts +73 -74
  398. package/src/services/DisplayDiagnostics.ts +107 -0
  399. package/src/services/DisplayProcessor.ts +20 -55
  400. package/src/services/GestureDotsService.ts +77 -16
  401. package/src/services/GlassesKeyboardInput.ts +306 -0
  402. package/src/services/GlassesSensorService.ts +233 -0
  403. package/src/services/GlassesTouchInput.ts +128 -0
  404. package/src/services/HeadingService.ts +0 -24
  405. package/src/services/HotspotShutdown.ts +3 -1
  406. package/src/services/JSCrashloopReportService.ts +2 -2
  407. package/src/services/JSRouter.ts +29 -14
  408. package/src/services/LocalDisplayManager.ts +112 -26
  409. package/src/services/LocalMiniappDataMigration.ts +1 -1
  410. package/src/services/LocalMiniappRuntime.ts +500 -43
  411. package/src/services/LocalSttFallbackCoordinator.ts +28 -7
  412. package/src/services/MemoryStore.ts +32 -80
  413. package/src/services/MiniappEngine.ts +7 -7
  414. package/src/services/MiniappLauncher.ts +25 -1
  415. package/src/services/MirrorService.ts +139 -0
  416. package/src/services/NativeNavigationService.ts +29 -25
  417. package/src/services/NavigationHandlers.ts +10 -5
  418. package/src/services/OtaInstallCoordinator.ts +36 -34
  419. package/src/services/PairedGlassesLatch.ts +39 -0
  420. package/src/services/PhoneActivity.ts +116 -0
  421. package/src/services/PhoneCameraFovCoordinator.ts +6 -4
  422. package/src/services/PhoneNotificationsSync.ts +185 -103
  423. package/src/services/PhonePhotoCoordinator.ts +9 -10
  424. package/src/services/PhoneStreamCoordinator.ts +13 -11
  425. package/src/services/STTModelManager.ts +15 -1
  426. package/src/services/SceneRenderer.ts +148 -10
  427. package/src/services/StreamLifecycleController.ts +9 -7
  428. package/src/services/TTSModelManager.ts +1 -1
  429. package/src/services/UIRouter.ts +5 -5
  430. package/src/services/asg/galleryPathMigration.ts +1 -1
  431. package/src/services/asg/gallerySyncService.ts +2 -2
  432. package/src/services/asg/localStorageService.ts +3 -3
  433. package/src/services/debug/DashboardOpenTrace.ts +153 -0
  434. package/src/services/debug/dashboardOpenTimeline.ts +237 -0
  435. package/src/services/effectiveCapabilities.ts +32 -13
  436. package/src/services/gestureClaims.ts +72 -23
  437. package/src/services/manifestFields.ts +23 -0
  438. package/src/services/memoryStoreInstance.ts +2 -34
  439. package/src/services/mirrorSnapshot.ts +116 -0
  440. package/src/services/nativeNavigationGate.ts +14 -0
  441. package/src/services/notificationAppCatalog.ts +21 -0
  442. package/src/services/notifications/notificationPolicy.ts +87 -0
  443. package/src/services/pages/OverlayPages.ts +86 -0
  444. package/src/services/pages/PageHost.ts +116 -0
  445. package/src/services/pages/overlayApps.ts +72 -0
  446. package/src/services/pages/pageEligibility.ts +30 -0
  447. package/src/services/settingsBridge.ts +111 -0
  448. package/src/services/speakerProfileStoreInstance.ts +1 -1
  449. package/src/services/uiShim.ts +4 -4
  450. package/src/stores/appVisibility.ts +45 -0
  451. package/src/stores/apps.ts +110 -32
  452. package/src/stores/bluetoothSettingKeys.ts +1 -1
  453. package/src/stores/display.ts +18 -7
  454. package/src/stores/glasses.ts +15 -1
  455. package/src/stores/settings.ts +175 -51
  456. package/src/types/applet.ts +14 -1
  457. package/src/types/capabilities/even-realities-g2.ts +6 -0
  458. package/src/types/capabilities/simulated-glasses.ts +8 -4
  459. package/src/types/hardware.ts +34 -1
  460. package/src/types/index.ts +1 -0
  461. package/src/utils/debouncedPatch.ts +8 -4
  462. package/src/utils/devMiniappLaunch.ts +5 -3
  463. package/src/utils/display/measurer/TextMeasurer.ts +3 -346
  464. package/src/utils/display/measurer/script-detection.ts +10 -218
  465. package/src/utils/display/pixelArt.ts +3 -0
  466. package/src/utils/display/profiles/g1.ts +2 -236
  467. package/src/utils/display/profiles/g2.ts +2 -39
  468. package/src/utils/display/profiles/nex.ts +2 -166
  469. package/src/utils/display/profiles/types.ts +9 -132
  470. package/src/utils/display/profiles/z100.ts +2 -198
  471. package/src/utils/display/scene/degrade.ts +4 -1
  472. package/src/utils/display/scene/differ.ts +25 -1
  473. package/src/utils/display/scene/index.ts +5 -0
  474. package/src/utils/display/scene/process.ts +196 -13
  475. package/src/utils/display/scene/store.ts +0 -0
  476. package/src/utils/display/scene/svgPath.ts +163 -0
  477. package/src/utils/display/scene/types.ts +0 -0
  478. package/src/utils/display/wrapper/TextWrapper.ts +2 -546
  479. package/src/utils/display/wrapper/types.ts +3 -102
  480. package/src/utils/hardware/hardware.ts +1 -1
  481. package/src/utils/timers/timers.ts +32 -4
  482. package/build/services/CustomDashboardModel.d.ts +0 -48
  483. package/build/services/CustomDashboardModel.d.ts.map +0 -1
  484. package/build/services/CustomDashboardModel.js +0 -73
  485. package/build/services/CustomDashboardModel.js.map +0 -1
  486. package/build/services/CustomDashboardService.d.ts +0 -63
  487. package/build/services/CustomDashboardService.d.ts.map +0 -1
  488. package/build/services/CustomDashboardService.js +0 -576
  489. package/build/services/CustomDashboardService.js.map +0 -1
  490. package/build/services/DashboardCoordinator.d.ts +0 -26
  491. package/build/services/DashboardCoordinator.d.ts.map +0 -1
  492. package/build/services/DashboardCoordinator.js +0 -125
  493. package/build/services/DashboardCoordinator.js.map +0 -1
  494. package/build/services/iosNotificationAppCatalog.d.ts +0 -13
  495. package/build/services/iosNotificationAppCatalog.d.ts.map +0 -1
  496. package/build/services/iosNotificationAppCatalog.js +0 -28
  497. package/build/services/iosNotificationAppCatalog.js.map +0 -1
  498. package/src/services/CustomDashboardModel.ts +0 -83
  499. package/src/services/CustomDashboardService.ts +0 -598
  500. package/src/services/DashboardCoordinator.ts +0 -121
  501. package/src/services/iosNotificationAppCatalog.ts +0 -39
@@ -1 +1 @@
1
- {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,OAAO,EAML,KAAK,YAAY,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EAEvB,MAAM,wBAAwB,CAAA;AAI/B,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAC,CAAA;AAEvE,MAAM,MAAM,eAAe;AACzB,mCAAmC;AACjC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC;AACvD,0GAA0G;GACxG;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC;AACrF,uDAAuD;GACrD;IAAC,IAAI,EAAE,YAAY,CAAA;CAAC,CAAA;AAExB,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IACpD,OAAO,CAAC,KAAK,CAAmB;WAElB,WAAW,IAAI,aAAa;IAW1C;;;OAGG;IACI,mBAAmB,IAAI,wBAAwB,GAAG,IAAI;IAyB7D;;;;OAIG;IACI,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,wBAAwB,GAAG,iBAAiB,EAAE,GAAG,IAAI;IA+F5G;;;;;OAKG;IACI,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,eAAe;IAuC3G;;;;OAIG;IACI,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO;IAOpE,kDAAkD;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpC;;;;;;;OAOG;IACI,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAIrE;;;;;OAKG;IACI,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAItD,OAAO,CAAC,SAAS;IAsCV,aAAa,IAAI,IAAI;IAIrB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE;CAGtE;AAED,QAAA,MAAM,aAAa,eAA8B,CAAA;AACjD,eAAe,aAAa,CAAA;AAC5B,OAAO,EAAC,aAAa,EAAC,CAAA"}
1
+ {"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"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"}
@@ -17,11 +17,33 @@ import { SETTINGS, useSettingsStore } from "../stores/settings";
17
17
  import { useGlassesStore } from "../stores/glasses";
18
18
  import { DeviceTypes } from "../types";
19
19
  import { getEffectiveCapabilities } from "./effectiveCapabilities";
20
+ import { BgTimer } from "../utils/timers";
20
21
  import { degradeScene, diffScene, processScene, resolveAnchors, SceneStore, } from "../utils/display/scene";
21
22
  const LOG_TAG = "SCENE_RENDERER";
23
+ /** Longest geometry tween (255 frames at ~30 fps); rotation uses a separate millisecond clock. */
24
+ const MAX_SETTLE_MS = 255 * 33;
25
+ /** Longest transition among the elements that actually change on the device this frame. */
26
+ function settleTimeOf(elements) {
27
+ let ms = 0;
28
+ for (const el of elements) {
29
+ if (el.change !== "updated" && el.change !== "moved")
30
+ continue;
31
+ const t = el.transition?.durationMs ?? 0;
32
+ // Rotation has its own millisecond clock, beyond the geometry frame limit.
33
+ const duration = Math.min(t, el.rotation !== undefined ? 65535 : MAX_SETTLE_MS);
34
+ if (duration > ms)
35
+ ms = duration;
36
+ }
37
+ return ms;
38
+ }
22
39
  class SceneRenderer {
23
40
  static instance = null;
24
41
  store = new SceneStore();
42
+ seqCounter = 0;
43
+ pending = new Map();
44
+ presentedSub = null;
45
+ settledSub = null;
46
+ settledListeners = new Set();
25
47
  static getInstance() {
26
48
  if (!SceneRenderer.instance) {
27
49
  SceneRenderer.instance = new SceneRenderer();
@@ -51,6 +73,9 @@ class SceneRenderer {
51
73
  maxImageElements: display.maxImageElements ?? 0,
52
74
  maxImagePx: display.maxImagePx,
53
75
  shapes: display.shapes ?? [],
76
+ animation: display.animation ?? false,
77
+ rotation: display.rotation ?? false,
78
+ depth: display.depth ?? false,
54
79
  intensityLevels: display.intensityLevels ?? 2,
55
80
  partialUpdate: display.partialUpdate ?? false,
56
81
  };
@@ -185,12 +210,20 @@ class SceneRenderer {
185
210
  appId,
186
211
  view,
187
212
  sceneEpoch: this.store.currentEpoch(appId, view),
213
+ seq: ++this.seqCounter,
188
214
  elements: diffed,
189
215
  removed,
190
216
  canvas,
191
217
  };
192
- this.sendFrame(frame);
193
- return { kind: "scene", degraded: processed.degraded, dropped: processed.dropped };
218
+ const sent = this.sendFrame(frame);
219
+ return {
220
+ kind: "scene",
221
+ degraded: processed.degraded,
222
+ dropped: processed.dropped,
223
+ seq: frame.seq,
224
+ sent,
225
+ settleMs: caps.animation ? settleTimeOf(diffed) : 0,
226
+ };
194
227
  }
195
228
  /**
196
229
  * Re-emit an app's retained scene (reconnect / arbitration restore). Replay
@@ -198,15 +231,82 @@ class SceneRenderer {
198
231
  * Returns false when nothing is retained.
199
232
  */
200
233
  replayApp(appId, view) {
201
- const frame = this.store.buildReplayFrame(appId, view);
234
+ const frame = this.store.buildReplayFrame(appId, view, ++this.seqCounter);
202
235
  if (!frame)
203
236
  return false;
204
237
  this.sendFrame(frame);
205
238
  return true;
206
239
  }
207
- /** Forget an app's retained scenes (app stop). */
240
+ /** Forget an app's retained scenes (app stop). Frames still in flight will never show. */
208
241
  clearApp(appId) {
209
242
  this.store.clear(appId);
243
+ this.settlePending((p) => p.appId === appId, { presented: false, superseded: true, reason: "cleared" });
244
+ }
245
+ // ===========================================================================
246
+ // Presentation — native reports when the device has a frame
247
+ // ===========================================================================
248
+ /**
249
+ * Resolves once native reports frame `seq` on the device, or a later frame
250
+ * that folded it in. Never rejects; `timeoutMs` bounds a frame the device
251
+ * never acknowledges (link stalls, page rebuilding).
252
+ */
253
+ whenPresented(seq, timeoutMs, appId) {
254
+ this.ensurePresentedListener();
255
+ return new Promise((resolve) => {
256
+ const timer = BgTimer.setTimeout(() => {
257
+ if (this.pending.delete(seq))
258
+ resolve({ presented: false, superseded: false, reason: "no presentation report" });
259
+ }, timeoutMs);
260
+ this.pending.set(seq, {
261
+ appId,
262
+ resolve: (p) => {
263
+ BgTimer.clearTimeout(timer);
264
+ resolve(p);
265
+ },
266
+ });
267
+ });
268
+ }
269
+ ensurePresentedListener() {
270
+ if (this.presentedSub)
271
+ return;
272
+ this.presentedSub = BluetoothSdk.addListener("scene_frame_presented", (event) => {
273
+ this.onFramePresented(event.seq, event.appId, event.presented, event.reason);
274
+ });
275
+ }
276
+ /**
277
+ * Fires with the host frame number once the glasses report that every
278
+ * animation the frame started has ended (Glassly CFW revision 27+). The
279
+ * device echoes the low 16 bits of the seq; the full number is recovered
280
+ * against the latest frame sent.
281
+ */
282
+ onSettled(listener) {
283
+ this.settledListeners.add(listener);
284
+ if (!this.settledSub) {
285
+ this.settledSub = BluetoothSdk.addListener("scene_settled", (event) => {
286
+ const seq = this.seqCounter - ((this.seqCounter - event.tag) & 0xffff);
287
+ if (seq <= 0)
288
+ return;
289
+ for (const settled of [...this.settledListeners])
290
+ settled(seq);
291
+ });
292
+ }
293
+ return { remove: () => this.settledListeners.delete(listener) };
294
+ }
295
+ /** A report for `seq` settles it and every earlier frame (folded in by the native pacer). */
296
+ onFramePresented(seq, appId, presented, reason) {
297
+ this.settlePending((p, s) => s <= seq, (p, s) => s === seq
298
+ ? { presented, superseded: false, reason }
299
+ : p.appId === appId
300
+ ? { presented, superseded: true, reason }
301
+ : { presented: false, superseded: true, reason: `superseded by ${appId}` });
302
+ }
303
+ settlePending(match, outcome) {
304
+ for (const [seq, p] of [...this.pending]) {
305
+ if (!match(p, seq))
306
+ continue;
307
+ this.pending.delete(seq);
308
+ p.resolve(typeof outcome === "function" ? outcome(p, seq) : outcome);
309
+ }
210
310
  }
211
311
  /**
212
312
  * Reset the diff baseline for (app, view) without touching the device.
@@ -227,13 +327,16 @@ class SceneRenderer {
227
327
  */
228
328
  markViewStale(view) {
229
329
  this.store.markViewStale(view);
330
+ // Whatever was in flight for the view got wiped with it.
331
+ this.settlePending(() => true, { presented: false, superseded: true, reason: "cleared" });
230
332
  }
333
+ /** Returns false when the frame stayed phone-side (no presentation report will follow). */
231
334
  sendFrame(frame) {
232
335
  // Single native view: only the visible surface's frames go out. Suppressed
233
- // main frames stay committed; setDashboardActive(false) replays them.
234
- const dashboardActive = useDisplayStore.getState().dashboardActive;
235
- if (dashboardActive !== (frame.view === "dashboard"))
236
- return;
336
+ // main frames stay committed; setOverlayOwner(null) replays them.
337
+ const overlayHeld = useDisplayStore.getState().overlayOwner !== null;
338
+ if (overlayHeld !== (frame.view === "dashboard"))
339
+ return false;
237
340
  try {
238
341
  // Frames carry the canvas they were laid out against; only a frame from
239
342
  // a path that predates the tag falls back to the current capabilities.
@@ -253,14 +356,19 @@ class SceneRenderer {
253
356
  view: frame.view,
254
357
  layout: {
255
358
  layoutType: "scene",
359
+ appId: frame.appId,
360
+ view: frame.view,
361
+ sceneEpoch: frame.sceneEpoch,
256
362
  width: frame.canvas?.width,
257
363
  height: frame.canvas?.height,
258
364
  elements: frame.elements,
259
365
  },
260
366
  });
367
+ return true;
261
368
  }
262
369
  catch (err) {
263
370
  console.error(`${LOG_TAG}: native scene send failed:`, err);
371
+ return false;
264
372
  }
265
373
  }
266
374
  // ===========================================================================
@@ -268,6 +376,13 @@ class SceneRenderer {
268
376
  // ===========================================================================
269
377
  _resetForTest() {
270
378
  this.store.clearAll();
379
+ this.settlePending(() => true, { presented: false, superseded: false, reason: "reset" });
380
+ this.presentedSub?.remove();
381
+ this.presentedSub = null;
382
+ this.settledSub?.remove();
383
+ this.settledSub = null;
384
+ this.settledListeners.clear();
385
+ this.seqCounter = 0;
271
386
  }
272
387
  _lastFrameForTest(appId, view) {
273
388
  return this.store.lastFrame(appId, view);
@@ -1 +1 @@
1
- {"version":3,"file":"SceneRenderer.js","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SceneRenderer — the device-aware boundary of the scene display pipeline.\n *\n * LocalDisplayManager owns WHO may draw (arbitration/boot/expiry); this service\n * owns WHAT gets sent for a scene: capabilities lookup, legacy-layout→scene\n * conversion (sugar and old bundles ride the same pipeline), process → diff →\n * SceneFrame emission, retained-scene replay, and the degrade branch for\n * devices that can't position elements.\n *\n * Everything device-specific here is DATA (capability fields, display profile);\n * the code paths are generic. Spec: notes/miniapp-display-render-implementation-spec.md\n */\n\nimport displayProcessor from \"./DisplayProcessor\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {useDisplayStore} from \"../stores/display\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {DeviceTypes} from \"../types\"\nimport {getEffectiveCapabilities} from \"./effectiveCapabilities\"\nimport {\n degradeScene,\n diffScene,\n processScene,\n resolveAnchors,\n SceneStore,\n type FrameElement,\n type SceneBreakMode,\n type SceneDisplayCapabilities,\n type SceneElementInput,\n type SceneFrame,\n} from \"../utils/display/scene\"\n\nconst LOG_TAG = \"SCENE_RENDERER\"\n\n/** Legacy layout shape (matches DisplayPayload.layout). */\nexport type LegacyLayout = {layoutType: string; [key: string]: unknown}\n\nexport type SceneEmitResult =\n /** A SceneFrame went to native. */\n | {kind: \"scene\"; degraded: boolean; dropped: string[]}\n /** Degrade path: caller routes this legacy layout through the existing pipeline (null layout ⇒ clear). */\n | {kind: \"legacy\"; layout: LegacyLayout | null; degraded: boolean; dropped: string[]}\n /** No display capabilities on the connected device. */\n | {kind: \"no-display\"}\n\nclass SceneRenderer {\n private static instance: SceneRenderer | null = null\n private store = new SceneStore()\n\n public static getInstance(): SceneRenderer {\n if (!SceneRenderer.instance) {\n SceneRenderer.instance = new SceneRenderer()\n }\n return SceneRenderer.instance\n }\n\n // ===========================================================================\n // Capabilities\n // ===========================================================================\n\n /**\n * Scene capabilities of the currently-selected device, or null when it has no\n * display block. Read on demand — same model source the CONNECT_ACK uses.\n */\n public currentCapabilities(): SceneDisplayCapabilities | null {\n // dev read these via the (since-removed) settings/glassesStatus runtime\n // hooks; engine reads its own stores directly.\n const model =\n (useSettingsStore.getState().getSetting(SETTINGS.default_wearable.key) as string | undefined) ??\n (useGlassesStore.getState().deviceModel as string | undefined)\n const display = getEffectiveCapabilities((model ?? DeviceTypes.NONE) as DeviceTypes)?.display\n if (!display) return null\n return {\n width: display.width ?? display.resolution?.width ?? displayProcessor.getProfile().displayWidthPx,\n height: display.height ?? display.resolution?.height ?? displayProcessor.getProfile().displayHeightPx ?? 288,\n canPosition: display.canPosition === true,\n maxTextElements: display.maxTextElements ?? 6,\n maxImageElements: display.maxImageElements ?? 0,\n maxImagePx: display.maxImagePx,\n shapes: display.shapes ?? [],\n intensityLevels: display.intensityLevels ?? 2,\n partialUpdate: display.partialUpdate ?? false,\n }\n }\n\n // ===========================================================================\n // Legacy layout → scene conversion (sugar + old bundles on the scene path)\n // ===========================================================================\n\n /**\n * Convert a legacy layout into scene elements for a positioning device.\n * Returns null for layouts that must stay on the legacy path (dashboard_card,\n * clear_view, unknown types). Sugar semantics: full-canvas / split boxes.\n */\n public convertLegacyLayout(layout: LegacyLayout, caps: SceneDisplayCapabilities): SceneElementInput[] | null {\n const W = caps.width\n const H = caps.height\n const breakMode = layout.breakMode as SceneBreakMode | undefined\n\n switch (layout.layoutType) {\n case \"text_wall\":\n return [\n {\n type: \"text\",\n id: \"sugar:wall\",\n box: {x: 0, y: 0, w: W, h: H},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n case \"double_text_wall\": {\n const half = Math.floor(W / 2)\n return [\n {\n type: \"text\",\n id: \"sugar:left\",\n box: {x: 0, y: 0, w: half, h: H},\n text: String(layout.topText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n {\n type: \"text\",\n id: \"sugar:right\",\n box: {x: half, y: 0, w: W - half, h: H},\n text: String(layout.bottomText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"reference_card\": {\n const titleH = Math.max(24, Math.floor(H / 5))\n return [\n {type: \"text\", id: \"sugar:title\", box: {x: 0, y: 0, w: W, h: titleH}, text: String(layout.title ?? \"\")},\n {\n type: \"text\",\n id: \"sugar:body\",\n box: {x: 0, y: titleH, w: W, h: H - titleH},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"bitmap_view\": {\n // Dimensionless legacy bitmaps historically landed in the device's\n // default ~200×100 container (G2 seeded 200×100 at 188,44) — NOT the\n // full canvas, which would tile a small image across the whole frame.\n const w = (layout.width as number | undefined) ?? Math.min(200, W)\n const h = (layout.height as number | undefined) ?? Math.min(100, H)\n return [\n {\n type: \"image\",\n id: \"sugar:bmp\",\n box: {\n x: (layout.x as number | undefined) ?? Math.max(0, Math.round((W - w) / 2)),\n y: (layout.y as number | undefined) ?? Math.max(0, Math.round((H - h) / 4)),\n w,\n h,\n },\n data: String(layout.data ?? \"\"),\n },\n ]\n }\n case \"positioned_text\": {\n const border = (layout.borderWidth as number | undefined) ?? 0\n const radius = (layout.borderRadius as number | undefined) ?? 0\n return [\n {\n type: \"text\",\n id: \"sugar:pos\",\n box: {\n x: (layout.x as number | undefined) ?? 0,\n y: (layout.y as number | undefined) ?? 0,\n w: (layout.width as number | undefined) ?? W,\n h: (layout.height as number | undefined) ?? H,\n },\n text: String(layout.text ?? \"\"),\n ...(border || radius ? {style: {border, radius}} : {}),\n },\n ]\n }\n default:\n return null\n }\n }\n\n // ===========================================================================\n // Emit\n // ===========================================================================\n\n /**\n * Process + diff + send a scene for (app, view). On non-positioning devices\n * this returns the degraded legacy layout instead — the caller routes it\n * through the existing DisplayProcessor path (which wraps it, keeping G1/Z100\n * output byte-identical to today).\n */\n public emitScene(appId: string, view: \"main\" | \"dashboard\", elements: SceneElementInput[]): SceneEmitResult {\n const caps = this.currentCapabilities()\n if (!caps) return {kind: \"no-display\"}\n\n // Corner-anchored boxes resolve against the REAL canvas before either\n // branch: processScene clamps concrete coords, and degradeScene's reading\n // order sorts by resolved y (a bottom-anchored y:0 must not sort first).\n elements = resolveAnchors(elements, caps.width, caps.height)\n\n if (!caps.canPosition) {\n const {layout, degraded, dropped} = degradeScene(elements)\n return {kind: \"legacy\", layout, degraded, dropped}\n }\n\n const processed = processScene(elements, caps, displayProcessor.getProfile())\n // A stale baseline means the glasses were cleared since the last commit —\n // diff from empty so everything repaints (the retained frame stays valid\n // as the replay source only).\n const prevFrame = this.store.isBaselineStale(appId, view) ? [] : this.store.lastFrame(appId, view)\n const {elements: diffed, removed} = diffScene(\n prevFrame,\n processed.elements,\n this.store.nextSyntheticId(appId, view),\n )\n const canvas = {width: caps.width, height: caps.height}\n this.store.commit(appId, view, diffed, canvas)\n\n const frame: SceneFrame = {\n appId,\n view,\n sceneEpoch: this.store.currentEpoch(appId, view),\n elements: diffed,\n removed,\n canvas,\n }\n this.sendFrame(frame)\n return {kind: \"scene\", degraded: processed.degraded, dropped: processed.dropped}\n }\n\n /**\n * Re-emit an app's retained scene (reconnect / arbitration restore). Replay\n * is always create-based — the device rebuilds from scratch (spec §4).\n * Returns false when nothing is retained.\n */\n public replayApp(appId: string, view: \"main\" | \"dashboard\"): boolean {\n const frame = this.store.buildReplayFrame(appId, view)\n if (!frame) return false\n this.sendFrame(frame)\n return true\n }\n\n /** Forget an app's retained scenes (app stop). */\n public clearApp(appId: string): void {\n this.store.clear(appId)\n }\n\n /**\n * Reset the diff baseline for (app, view) without touching the device.\n * Used when a legacy layout bypasses the scene path (clear_view /\n * dashboard_card / unknown layoutTypes go to native raw): after that, the\n * retained frame no longer matches the glasses, so the next render() must\n * diff from empty — otherwise \"unchanged\" elements would be skipped and\n * never repainted.\n */\n public resetBaseline(appId: string, view: \"main\" | \"dashboard\"): void {\n this.store.commit(appId, view, [])\n }\n\n /**\n * A system-level clear wiped the view (durationMs expiry, unmount fallback,\n * boot-window expiry): mark every app's baseline for that view stale. Unlike\n * resetBaseline this KEEPS the retained frames — they remain the\n * restore-as-replay source; only incremental diffing restarts from empty.\n */\n public markViewStale(view: \"main\" | \"dashboard\"): void {\n this.store.markViewStale(view)\n }\n\n private sendFrame(frame: SceneFrame): void {\n // Single native view: only the visible surface's frames go out. Suppressed\n // main frames stay committed; setDashboardActive(false) replays them.\n const dashboardActive = useDisplayStore.getState().dashboardActive\n if (dashboardActive !== (frame.view === \"dashboard\")) return\n try {\n // Frames carry the canvas they were laid out against; only a frame from\n // a path that predates the tag falls back to the current capabilities.\n const caps = this.currentCapabilities()\n if (!frame.canvas && caps) frame = {...frame, canvas: {width: caps.width, height: caps.height}}\n // sendDisplayEvent hook -> direct BluetoothSdk.displayEvent (the exact\n // wiring the deleted MantleManager hook provided).\n void Promise.resolve(BluetoothSdk.displayEvent({view: frame.view, scene: frame})).catch((err) => {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n })\n\n // Keep the phone-side glasses mirror on the same frame as native. Legacy\n // layouts update this store in LocalDisplayManager.sendToNative; scene\n // frames bypass that method, so without an explicit write every\n // positioning device (including G2) leaves the mirror blank/stale.\n useDisplayStore.getState().setDisplayEvent({\n view: frame.view,\n layout: {\n layoutType: \"scene\",\n width: frame.canvas?.width,\n height: frame.canvas?.height,\n elements: frame.elements,\n },\n })\n } catch (err) {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n }\n }\n\n // ===========================================================================\n // Test hooks\n // ===========================================================================\n\n public _resetForTest(): void {\n this.store.clearAll()\n }\n\n public _lastFrameForTest(appId: string, view: string): FrameElement[] {\n return this.store.lastFrame(appId, view)\n }\n}\n\nconst sceneRenderer = SceneRenderer.getInstance()\nexport default sceneRenderer\nexport {SceneRenderer}\n"]}
1
+ {"version":3,"file":"SceneRenderer.js","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SceneRenderer — the device-aware boundary of the scene display pipeline.\n *\n * LocalDisplayManager owns WHO may draw (arbitration/boot/expiry); this service\n * owns WHAT gets sent for a scene: capabilities lookup, legacy-layout→scene\n * conversion (sugar and old bundles ride the same pipeline), process → diff →\n * SceneFrame emission, retained-scene replay, and the degrade branch for\n * devices that can't position elements.\n *\n * Everything device-specific here is DATA (capability fields, display profile);\n * the code paths are generic. Spec: notes/miniapp-display-render-implementation-spec.md\n */\n\nimport displayProcessor from \"./DisplayProcessor\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {useDisplayStore} from \"../stores/display\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {DeviceTypes} from \"../types\"\nimport {getEffectiveCapabilities} from \"./effectiveCapabilities\"\nimport {BgTimer} from \"../utils/timers\"\nimport {\n degradeScene,\n diffScene,\n processScene,\n resolveAnchors,\n SceneStore,\n type FrameElement,\n type SceneBreakMode,\n type SceneDisplayCapabilities,\n type SceneElementInput,\n type SceneFrame,\n} from \"../utils/display/scene\"\n\nconst LOG_TAG = \"SCENE_RENDERER\"\n\n/** Legacy layout shape (matches DisplayPayload.layout). */\nexport type LegacyLayout = {layoutType: string; [key: string]: unknown}\n\nexport type SceneEmitResult =\n /**\n * A SceneFrame was committed. `sent` is false when the frame stayed\n * phone-side (dashboard covering main). `settleMs` is how long the device\n * keeps animating after it has the frame: the longest transition among the\n * elements this frame changed, 0 on devices that jump.\n */\n | {kind: \"scene\"; degraded: boolean; dropped: string[]; seq: number; sent: boolean; settleMs: number}\n /** Degrade path: caller routes this legacy layout through the existing pipeline (null layout ⇒ clear). */\n | {kind: \"legacy\"; layout: LegacyLayout | null; degraded: boolean; dropped: string[]}\n /** No display capabilities on the connected device. */\n | {kind: \"no-display\"}\n\n/** How a frame ended up, as reported by native (or inferred from a later frame's report). */\nexport interface ScenePresentation {\n /** The device had the frame's content (this frame or a later one that folded it in). */\n presented: boolean\n /** A later frame's report settled this one. */\n superseded: boolean\n reason?: string\n}\n\n/** Longest geometry tween (255 frames at ~30 fps); rotation uses a separate millisecond clock. */\nconst MAX_SETTLE_MS = 255 * 33\n\n/** Longest transition among the elements that actually change on the device this frame. */\nfunction settleTimeOf(elements: FrameElement[]): number {\n let ms = 0\n for (const el of elements) {\n if (el.change !== \"updated\" && el.change !== \"moved\") continue\n const t = el.transition?.durationMs ?? 0\n // Rotation has its own millisecond clock, beyond the geometry frame limit.\n const duration = Math.min(t, el.rotation !== undefined ? 65535 : MAX_SETTLE_MS)\n if (duration > ms) ms = duration\n }\n return ms\n}\n\nclass SceneRenderer {\n private static instance: SceneRenderer | null = null\n private store = new SceneStore()\n private seqCounter = 0\n private pending = new Map<number, {appId: string; resolve: (p: ScenePresentation) => void}>()\n private presentedSub: {remove: () => void} | null = null\n private settledSub: {remove: () => void} | null = null\n private settledListeners = new Set<(seq: number) => void>()\n\n public static getInstance(): SceneRenderer {\n if (!SceneRenderer.instance) {\n SceneRenderer.instance = new SceneRenderer()\n }\n return SceneRenderer.instance\n }\n\n // ===========================================================================\n // Capabilities\n // ===========================================================================\n\n /**\n * Scene capabilities of the currently-selected device, or null when it has no\n * display block. Read on demand — same model source the CONNECT_ACK uses.\n */\n public currentCapabilities(): SceneDisplayCapabilities | null {\n // dev read these via the (since-removed) settings/glassesStatus runtime\n // hooks; engine reads its own stores directly.\n const model =\n (useSettingsStore.getState().getSetting(SETTINGS.default_wearable.key) as string | undefined) ??\n (useGlassesStore.getState().deviceModel as string | undefined)\n const display = getEffectiveCapabilities((model ?? DeviceTypes.NONE) as DeviceTypes)?.display\n if (!display) return null\n return {\n width: display.width ?? display.resolution?.width ?? displayProcessor.getProfile().displayWidthPx,\n height: display.height ?? display.resolution?.height ?? displayProcessor.getProfile().displayHeightPx ?? 288,\n canPosition: display.canPosition === true,\n maxTextElements: display.maxTextElements ?? 6,\n maxImageElements: display.maxImageElements ?? 0,\n maxImagePx: display.maxImagePx,\n shapes: display.shapes ?? [],\n animation: display.animation ?? false,\n rotation: display.rotation ?? false,\n depth: display.depth ?? false,\n intensityLevels: display.intensityLevels ?? 2,\n\n partialUpdate: display.partialUpdate ?? false,\n }\n }\n\n // ===========================================================================\n // Legacy layout → scene conversion (sugar + old bundles on the scene path)\n // ===========================================================================\n\n /**\n * Convert a legacy layout into scene elements for a positioning device.\n * Returns null for layouts that must stay on the legacy path (dashboard_card,\n * clear_view, unknown types). Sugar semantics: full-canvas / split boxes.\n */\n public convertLegacyLayout(layout: LegacyLayout, caps: SceneDisplayCapabilities): SceneElementInput[] | null {\n const W = caps.width\n const H = caps.height\n const breakMode = layout.breakMode as SceneBreakMode | undefined\n\n switch (layout.layoutType) {\n case \"text_wall\":\n return [\n {\n type: \"text\",\n id: \"sugar:wall\",\n box: {x: 0, y: 0, w: W, h: H},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n case \"double_text_wall\": {\n const half = Math.floor(W / 2)\n return [\n {\n type: \"text\",\n id: \"sugar:left\",\n box: {x: 0, y: 0, w: half, h: H},\n text: String(layout.topText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n {\n type: \"text\",\n id: \"sugar:right\",\n box: {x: half, y: 0, w: W - half, h: H},\n text: String(layout.bottomText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"reference_card\": {\n const titleH = Math.max(24, Math.floor(H / 5))\n return [\n {type: \"text\", id: \"sugar:title\", box: {x: 0, y: 0, w: W, h: titleH}, text: String(layout.title ?? \"\")},\n {\n type: \"text\",\n id: \"sugar:body\",\n box: {x: 0, y: titleH, w: W, h: H - titleH},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"bitmap_view\": {\n // Dimensionless legacy bitmaps historically landed in the device's\n // default ~200×100 container (G2 seeded 200×100 at 188,44) — NOT the\n // full canvas, which would tile a small image across the whole frame.\n const w = (layout.width as number | undefined) ?? Math.min(200, W)\n const h = (layout.height as number | undefined) ?? Math.min(100, H)\n return [\n {\n type: \"image\",\n id: \"sugar:bmp\",\n box: {\n x: (layout.x as number | undefined) ?? Math.max(0, Math.round((W - w) / 2)),\n y: (layout.y as number | undefined) ?? Math.max(0, Math.round((H - h) / 4)),\n w,\n h,\n },\n data: String(layout.data ?? \"\"),\n },\n ]\n }\n case \"positioned_text\": {\n const border = (layout.borderWidth as number | undefined) ?? 0\n const radius = (layout.borderRadius as number | undefined) ?? 0\n return [\n {\n type: \"text\",\n id: \"sugar:pos\",\n box: {\n x: (layout.x as number | undefined) ?? 0,\n y: (layout.y as number | undefined) ?? 0,\n w: (layout.width as number | undefined) ?? W,\n h: (layout.height as number | undefined) ?? H,\n },\n text: String(layout.text ?? \"\"),\n ...(border || radius ? {style: {border, radius}} : {}),\n },\n ]\n }\n default:\n return null\n }\n }\n\n // ===========================================================================\n // Emit\n // ===========================================================================\n\n /**\n * Process + diff + send a scene for (app, view). On non-positioning devices\n * this returns the degraded legacy layout instead — the caller routes it\n * through the existing DisplayProcessor path (which wraps it, keeping G1/Z100\n * output byte-identical to today).\n */\n public emitScene(appId: string, view: \"main\" | \"dashboard\", elements: SceneElementInput[]): SceneEmitResult {\n const caps = this.currentCapabilities()\n if (!caps) return {kind: \"no-display\"}\n\n // Corner-anchored boxes resolve against the REAL canvas before either\n // branch: processScene clamps concrete coords, and degradeScene's reading\n // order sorts by resolved y (a bottom-anchored y:0 must not sort first).\n elements = resolveAnchors(elements, caps.width, caps.height)\n\n if (!caps.canPosition) {\n const {layout, degraded, dropped} = degradeScene(elements)\n return {kind: \"legacy\", layout, degraded, dropped}\n }\n\n const processed = processScene(elements, caps, displayProcessor.getProfile())\n // A stale baseline means the glasses were cleared since the last commit —\n // diff from empty so everything repaints (the retained frame stays valid\n // as the replay source only).\n const prevFrame = this.store.isBaselineStale(appId, view) ? [] : this.store.lastFrame(appId, view)\n const {elements: diffed, removed} = diffScene(\n prevFrame,\n processed.elements,\n this.store.nextSyntheticId(appId, view),\n )\n const canvas = {width: caps.width, height: caps.height}\n this.store.commit(appId, view, diffed, canvas)\n\n const frame: SceneFrame = {\n appId,\n view,\n sceneEpoch: this.store.currentEpoch(appId, view),\n seq: ++this.seqCounter,\n elements: diffed,\n removed,\n canvas,\n }\n const sent = this.sendFrame(frame)\n return {\n kind: \"scene\",\n degraded: processed.degraded,\n dropped: processed.dropped,\n seq: frame.seq,\n sent,\n settleMs: caps.animation ? settleTimeOf(diffed) : 0,\n }\n }\n\n /**\n * Re-emit an app's retained scene (reconnect / arbitration restore). Replay\n * is always create-based — the device rebuilds from scratch (spec §4).\n * Returns false when nothing is retained.\n */\n public replayApp(appId: string, view: \"main\" | \"dashboard\"): boolean {\n const frame = this.store.buildReplayFrame(appId, view, ++this.seqCounter)\n if (!frame) return false\n this.sendFrame(frame)\n return true\n }\n\n /** Forget an app's retained scenes (app stop). Frames still in flight will never show. */\n public clearApp(appId: string): void {\n this.store.clear(appId)\n this.settlePending((p) => p.appId === appId, {presented: false, superseded: true, reason: \"cleared\"})\n }\n\n // ===========================================================================\n // Presentation — native reports when the device has a frame\n // ===========================================================================\n\n /**\n * Resolves once native reports frame `seq` on the device, or a later frame\n * that folded it in. Never rejects; `timeoutMs` bounds a frame the device\n * never acknowledges (link stalls, page rebuilding).\n */\n public whenPresented(seq: number, timeoutMs: number, appId: string): Promise<ScenePresentation> {\n this.ensurePresentedListener()\n return new Promise((resolve) => {\n const timer = BgTimer.setTimeout(() => {\n if (this.pending.delete(seq)) resolve({presented: false, superseded: false, reason: \"no presentation report\"})\n }, timeoutMs)\n this.pending.set(seq, {\n appId,\n resolve: (p) => {\n BgTimer.clearTimeout(timer)\n resolve(p)\n },\n })\n })\n }\n\n private ensurePresentedListener(): void {\n if (this.presentedSub) return\n this.presentedSub = BluetoothSdk.addListener(\"scene_frame_presented\", (event) => {\n this.onFramePresented(event.seq, event.appId, event.presented, event.reason)\n })\n }\n\n /**\n * Fires with the host frame number once the glasses report that every\n * animation the frame started has ended (Glassly CFW revision 27+). The\n * device echoes the low 16 bits of the seq; the full number is recovered\n * against the latest frame sent.\n */\n public onSettled(listener: (seq: number) => void): {remove: () => void} {\n this.settledListeners.add(listener)\n if (!this.settledSub) {\n this.settledSub = BluetoothSdk.addListener(\"scene_settled\", (event) => {\n const seq = this.seqCounter - ((this.seqCounter - event.tag) & 0xffff)\n if (seq <= 0) return\n for (const settled of [...this.settledListeners]) settled(seq)\n })\n }\n return {remove: () => this.settledListeners.delete(listener)}\n }\n\n /** A report for `seq` settles it and every earlier frame (folded in by the native pacer). */\n private onFramePresented(seq: number, appId: string, presented: boolean, reason?: string): void {\n this.settlePending(\n (p, s) => s <= seq,\n (p, s) =>\n s === seq\n ? {presented, superseded: false, reason}\n : p.appId === appId\n ? {presented, superseded: true, reason}\n : {presented: false, superseded: true, reason: `superseded by ${appId}`},\n )\n }\n\n private settlePending(\n match: (p: {appId: string}, seq: number) => boolean,\n outcome: ScenePresentation | ((p: {appId: string}, seq: number) => ScenePresentation),\n ): void {\n for (const [seq, p] of [...this.pending]) {\n if (!match(p, seq)) continue\n this.pending.delete(seq)\n p.resolve(typeof outcome === \"function\" ? outcome(p, seq) : outcome)\n }\n }\n\n /**\n * Reset the diff baseline for (app, view) without touching the device.\n * Used when a legacy layout bypasses the scene path (clear_view /\n * dashboard_card / unknown layoutTypes go to native raw): after that, the\n * retained frame no longer matches the glasses, so the next render() must\n * diff from empty — otherwise \"unchanged\" elements would be skipped and\n * never repainted.\n */\n public resetBaseline(appId: string, view: \"main\" | \"dashboard\"): void {\n this.store.commit(appId, view, [])\n }\n\n /**\n * A system-level clear wiped the view (durationMs expiry, unmount fallback,\n * boot-window expiry): mark every app's baseline for that view stale. Unlike\n * resetBaseline this KEEPS the retained frames — they remain the\n * restore-as-replay source; only incremental diffing restarts from empty.\n */\n public markViewStale(view: \"main\" | \"dashboard\"): void {\n this.store.markViewStale(view)\n // Whatever was in flight for the view got wiped with it.\n this.settlePending(() => true, {presented: false, superseded: true, reason: \"cleared\"})\n }\n\n /** Returns false when the frame stayed phone-side (no presentation report will follow). */\n private sendFrame(frame: SceneFrame): boolean {\n // Single native view: only the visible surface's frames go out. Suppressed\n // main frames stay committed; setOverlayOwner(null) replays them.\n const overlayHeld = useDisplayStore.getState().overlayOwner !== null\n if (overlayHeld !== (frame.view === \"dashboard\")) return false\n try {\n // Frames carry the canvas they were laid out against; only a frame from\n // a path that predates the tag falls back to the current capabilities.\n const caps = this.currentCapabilities()\n if (!frame.canvas && caps) frame = {...frame, canvas: {width: caps.width, height: caps.height}}\n // sendDisplayEvent hook -> direct BluetoothSdk.displayEvent (the exact\n // wiring the deleted MantleManager hook provided).\n void Promise.resolve(BluetoothSdk.displayEvent({view: frame.view, scene: frame})).catch((err) => {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n })\n\n // Keep the phone-side glasses mirror on the same frame as native. Legacy\n // layouts update this store in LocalDisplayManager.sendToNative; scene\n // frames bypass that method, so without an explicit write every\n // positioning device (including G2) leaves the mirror blank/stale.\n useDisplayStore.getState().setDisplayEvent({\n view: frame.view,\n layout: {\n layoutType: \"scene\",\n appId: frame.appId,\n view: frame.view,\n sceneEpoch: frame.sceneEpoch,\n width: frame.canvas?.width,\n height: frame.canvas?.height,\n elements: frame.elements,\n },\n })\n return true\n } catch (err) {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n return false\n }\n }\n\n // ===========================================================================\n // Test hooks\n // ===========================================================================\n\n public _resetForTest(): void {\n this.store.clearAll()\n this.settlePending(() => true, {presented: false, superseded: false, reason: \"reset\"})\n this.presentedSub?.remove()\n this.presentedSub = null\n this.settledSub?.remove()\n this.settledSub = null\n this.settledListeners.clear()\n this.seqCounter = 0\n }\n\n public _lastFrameForTest(appId: string, view: string): FrameElement[] {\n return this.store.lastFrame(appId, view)\n }\n}\n\nconst sceneRenderer = SceneRenderer.getInstance()\nexport default sceneRenderer\nexport {SceneRenderer}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"StreamLifecycleController.d.ts","sourceRoot":"","sources":["../../src/services/StreamLifecycleController.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAAA;IACzD,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAChC;AAED,UAAU,wBAAwB;IAChC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACtD,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAA;IACnC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAOD;;;;;GAKG;AACH,qBAAa,yBAAyB;IAkBlC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAjB5B,OAAO,CAAC,cAAc,CAAC,CAAgC;IACvD,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAQ;IAExB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAGhC,OAAO,EAAE,sBAAsB,EACd,SAAS,EAAE,wBAAwB;IAYtD,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAehC,cAAc,IAAI,IAAI;IAKtB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAgB9B,OAAO,IAAI,IAAI;IASf,iBAAiB,IAAI,MAAM;IAI3B,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;YAQH,IAAI;IAyBlB,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,WAAW;CAGpB"}
1
+ {"version":3,"file":"StreamLifecycleController.d.ts","sourceRoot":"","sources":["../../src/services/StreamLifecycleController.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAAA;IACzD,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAChC;AAED,UAAU,wBAAwB;IAChC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACtD,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACzD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAA;IACnC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAOD;;;;;GAKG;AACH,qBAAa,yBAAyB;IAkBlC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAjB5B,OAAO,CAAC,cAAc,CAAC,CAAQ;IAC/B,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAQ;IAExB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAe;IACpD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAGhC,OAAO,EAAE,sBAAsB,EACd,SAAS,EAAE,wBAAwB;IAYtD,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAehC,cAAc,IAAI,IAAI;IAKtB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAgB9B,OAAO,IAAI,IAAI;IASf,iBAAiB,IAAI,MAAM;IAI3B,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;YAQH,IAAI;IAyBlB,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,WAAW;CAGpB"}
@@ -1,12 +1,4 @@
1
- /**
2
- * Drives RTMP stream liveliness checks via a keep-alive heartbeat:
3
- * `setActive(true)` starts the timer; each tick sends a keep-alive with a
4
- * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.
5
- *
6
- * Phone-owned successor to the retired cloud stream lifecycle controller.
7
- * It uses the local `LifecycleLogger` interface because pino is not a phone
8
- * dependency.
9
- */
1
+ import { BgTimer } from "../utils/timers";
10
2
  /**
11
3
  * Drives RTMP stream liveliness checks. Activate to start the heartbeat;
12
4
  * each tick sends a keep-alive with a fresh ackId and arms a timeout. When
@@ -65,7 +57,7 @@ export class StreamLifecycleController {
65
57
  this.logger.warn({ streamId: this.streamId, ackId }, "Received unknown keep-alive ACK");
66
58
  return;
67
59
  }
68
- clearTimeout(ackInfo.timeout);
60
+ BgTimer.clearTimeout(ackInfo.timeout);
69
61
  this.pendingAcks.delete(ackId);
70
62
  this.recordActivity();
71
63
  this.callbacks.onKeepAliveAcked?.(ackId, this.now() - ackInfo.sentAt);
@@ -84,14 +76,14 @@ export class StreamLifecycleController {
84
76
  startTimer() {
85
77
  if (this.keepAliveTimer)
86
78
  return;
87
- this.keepAliveTimer = setInterval(() => {
79
+ this.keepAliveTimer = BgTimer.setInterval(() => {
88
80
  void this.tick();
89
81
  }, this.keepAliveIntervalMs);
90
82
  this.logger.debug({ streamId: this.streamId }, "Keep-alive timer started");
91
83
  }
92
84
  stopTimer() {
93
85
  if (this.keepAliveTimer) {
94
- clearInterval(this.keepAliveTimer);
86
+ BgTimer.clearInterval(this.keepAliveTimer);
95
87
  this.keepAliveTimer = undefined;
96
88
  this.logger.debug({ streamId: this.streamId }, "Keep-alive timer stopped");
97
89
  }
@@ -105,7 +97,7 @@ export class StreamLifecycleController {
105
97
  }
106
98
  const ackId = this.createAckId();
107
99
  const sentAt = this.now();
108
- const timeout = setTimeout(() => {
100
+ const timeout = BgTimer.setTimeout(() => {
109
101
  this.onAckTimeout(ackId, sentAt);
110
102
  }, this.ackTimeoutMs);
111
103
  this.pendingAcks.set(ackId, { sentAt, timeout });
@@ -141,7 +133,7 @@ export class StreamLifecycleController {
141
133
  }
142
134
  clearPendingAcks() {
143
135
  for (const { timeout } of this.pendingAcks.values()) {
144
- clearTimeout(timeout);
136
+ BgTimer.clearTimeout(timeout);
145
137
  }
146
138
  this.pendingAcks.clear();
147
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StreamLifecycleController.js","sourceRoot":"","sources":["../../src/services/StreamLifecycleController.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Drives RTMP stream liveliness checks via a keep-alive heartbeat:\n * `setActive(true)` starts the timer; each tick sends a keep-alive with a\n * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.\n *\n * Phone-owned successor to the retired cloud stream lifecycle controller.\n * It uses the local `LifecycleLogger` interface because pino is not a phone\n * dependency.\n */\n\nexport interface LifecycleLogger {\n child(bindings: Record<string, unknown>): LifecycleLogger\n debug(...args: unknown[]): void\n warn(...args: unknown[]): void\n error(...args: unknown[]): void\n}\n\ninterface StreamLifecycleCallbacks {\n sendKeepAlive: (ackId: string) => Promise<void> | void\n onTimeout: () => Promise<void> | void\n onKeepAliveSent?: (ackId: string) => void\n onKeepAliveAcked?: (ackId: string, ageMs: number) => void\n onKeepAliveMissed?: (ackId: string, ageMs: number, missedCount: number) => void\n}\n\nexport interface StreamLifecycleOptions {\n logger: LifecycleLogger\n streamId: string\n keepAliveIntervalMs: number\n ackTimeoutMs: number\n maxMissedAcks: number\n shouldSendKeepAlive?: () => boolean\n now?: () => number\n}\n\ninterface PendingAckInfo {\n sentAt: number\n timeout: ReturnType<typeof setTimeout>\n}\n\n/**\n * Drives RTMP stream liveliness checks. Activate to start the heartbeat;\n * each tick sends a keep-alive with a fresh ackId and arms a timeout. When\n * `maxMissedAcks` consecutive timeouts fire, `onTimeout` is invoked and the\n * caller is expected to tear the stream down.\n */\nexport class StreamLifecycleController {\n private keepAliveTimer?: ReturnType<typeof setInterval>\n private pendingAcks: Map<string, PendingAckInfo> = new Map()\n private missedAcks = 0\n private lastActivityMs: number\n private active = false\n private disposed = false\n\n private readonly logger: LifecycleLogger\n private readonly streamId: string\n private readonly keepAliveIntervalMs: number\n private readonly ackTimeoutMs: number\n private readonly maxMissedAcks: number\n private readonly shouldSendKeepAlive?: () => boolean\n private readonly now: () => number\n\n constructor(\n options: StreamLifecycleOptions,\n private readonly callbacks: StreamLifecycleCallbacks,\n ) {\n this.logger = options.logger.child({component: \"StreamLifecycle\"})\n this.streamId = options.streamId\n this.keepAliveIntervalMs = options.keepAliveIntervalMs\n this.ackTimeoutMs = options.ackTimeoutMs\n this.maxMissedAcks = options.maxMissedAcks\n this.shouldSendKeepAlive = options.shouldSendKeepAlive\n this.now = options.now ?? (() => Date.now())\n this.lastActivityMs = this.now()\n }\n\n setActive(active: boolean): void {\n if (this.disposed || this.active === active) return\n\n this.logger.debug({streamId: this.streamId, active}, \"Updating lifecycle active state\")\n\n this.active = active\n if (active) {\n this.startTimer()\n } else {\n this.stopTimer()\n this.clearPendingAcks()\n this.missedAcks = 0\n }\n }\n\n recordActivity(): void {\n this.lastActivityMs = this.now()\n this.missedAcks = 0\n }\n\n handleAck(ackId: string): void {\n if (this.disposed) return\n\n const ackInfo = this.pendingAcks.get(ackId)\n if (!ackInfo) {\n this.logger.warn({streamId: this.streamId, ackId}, \"Received unknown keep-alive ACK\")\n return\n }\n\n clearTimeout(ackInfo.timeout)\n this.pendingAcks.delete(ackId)\n this.recordActivity()\n\n this.callbacks.onKeepAliveAcked?.(ackId, this.now() - ackInfo.sentAt)\n }\n\n dispose(): void {\n if (this.disposed) return\n\n this.disposed = true\n this.stopTimer()\n this.clearPendingAcks()\n this.logger.debug({streamId: this.streamId}, \"Lifecycle disposed\")\n }\n\n getLastActivityMs(): number {\n return this.lastActivityMs\n }\n\n private startTimer(): void {\n if (this.keepAliveTimer) return\n this.keepAliveTimer = setInterval(() => {\n void this.tick()\n }, this.keepAliveIntervalMs)\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer started\")\n }\n\n private stopTimer(): void {\n if (this.keepAliveTimer) {\n clearInterval(this.keepAliveTimer)\n this.keepAliveTimer = undefined\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer stopped\")\n }\n }\n\n private async tick(): Promise<void> {\n if (this.disposed || !this.active) return\n\n if (this.shouldSendKeepAlive && !this.shouldSendKeepAlive()) {\n this.logger.warn({streamId: this.streamId}, \"Skipping keep-alive send because transport is unavailable\")\n return\n }\n\n const ackId = this.createAckId()\n const sentAt = this.now()\n\n const timeout = setTimeout(() => {\n this.onAckTimeout(ackId, sentAt)\n }, this.ackTimeoutMs)\n\n this.pendingAcks.set(ackId, {sentAt, timeout})\n this.callbacks.onKeepAliveSent?.(ackId)\n\n try {\n await this.callbacks.sendKeepAlive(ackId)\n } catch (error) {\n this.logger.error({streamId: this.streamId, ackId, error}, \"Error sending keep-alive\")\n }\n }\n\n private onAckTimeout(ackId: string, sentAt: number): void {\n if (this.disposed) return\n\n // Race guard: handleAck may have processed the ACK between the timeout\n // being scheduled and this callback running. In that case the ackId is\n // no longer pending — counting it as missed would falsely escalate.\n if (!this.pendingAcks.has(ackId)) return\n\n this.pendingAcks.delete(ackId)\n this.missedAcks += 1\n const ageMs = this.now() - sentAt\n\n this.logger.warn({streamId: this.streamId, ackId, missedAcks: this.missedAcks, ageMs}, \"Keep-alive ACK timeout\")\n\n this.callbacks.onKeepAliveMissed?.(ackId, ageMs, this.missedAcks)\n\n if (this.missedAcks >= this.maxMissedAcks) {\n this.logger.error(\n {\n streamId: this.streamId,\n missedAcks: this.missedAcks,\n maxMissedAcks: this.maxMissedAcks,\n },\n \"Maximum missed ACKs reached; triggering timeout\",\n )\n\n void this.callbacks.onTimeout()\n }\n }\n\n private clearPendingAcks(): void {\n for (const {timeout} of this.pendingAcks.values()) {\n clearTimeout(timeout)\n }\n this.pendingAcks.clear()\n }\n\n private createAckId(): string {\n return `a${this.now().toString(36).slice(-5)}`\n }\n}\n"]}
1
+ {"version":3,"file":"StreamLifecycleController.js","sourceRoot":"","sources":["../../src/services/StreamLifecycleController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AA0CvC;;;;;GAKG;AACH,MAAM,OAAO,yBAAyB;IAkBjB;IAjBX,cAAc,CAAS;IACvB,WAAW,GAAgC,IAAI,GAAG,EAAE,CAAA;IACpD,UAAU,GAAG,CAAC,CAAA;IACd,cAAc,CAAQ;IACtB,MAAM,GAAG,KAAK,CAAA;IACd,QAAQ,GAAG,KAAK,CAAA;IAEP,MAAM,CAAiB;IACvB,QAAQ,CAAQ;IAChB,mBAAmB,CAAQ;IAC3B,YAAY,CAAQ;IACpB,aAAa,CAAQ;IACrB,mBAAmB,CAAgB;IACnC,GAAG,CAAc;IAElC,YACE,OAA+B,EACd,SAAmC;QAAnC,cAAS,GAAT,SAAS,CAA0B;QAEpD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,SAAS,EAAE,iBAAiB,EAAC,CAAC,CAAA;QAClE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QAChC,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAA;QACtD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAA;QACxC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAA;QAC1C,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAA;QACtD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAClC,CAAC;IAED,SAAS,CAAC,MAAe;QACvB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM;YAAE,OAAM;QAEnD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAC,EAAE,iCAAiC,CAAC,CAAA;QAEvF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,UAAU,EAAE,CAAA;QACnB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,EAAE,CAAA;YAChB,IAAI,CAAC,gBAAgB,EAAE,CAAA;YACvB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAChC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;IACrB,CAAC;IAED,SAAS,CAAC,KAAa;QACrB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAC,EAAE,iCAAiC,CAAC,CAAA;YACrF,OAAM;QACR,CAAC;QAED,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACrC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,cAAc,EAAE,CAAA;QAErB,IAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvE,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,SAAS,EAAE,CAAA;QAChB,IAAI,CAAC,gBAAgB,EAAE,CAAA;QACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EAAE,oBAAoB,CAAC,CAAA;IACpE,CAAC;IAED,iBAAiB;QACf,OAAO,IAAI,CAAC,cAAc,CAAA;IAC5B,CAAC;IAEO,UAAU;QAChB,IAAI,IAAI,CAAC,cAAc;YAAE,OAAM;QAC/B,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE;YAC7C,KAAK,IAAI,CAAC,IAAI,EAAE,CAAA;QAClB,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAA;QAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EAAE,0BAA0B,CAAC,CAAA;IAC1E,CAAC;IAEO,SAAS;QACf,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YAC1C,IAAI,CAAC,cAAc,GAAG,SAAS,CAAA;YAC/B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EAAE,0BAA0B,CAAC,CAAA;QAC1E,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAM;QAEzC,IAAI,IAAI,CAAC,mBAAmB,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAC,EAAE,2DAA2D,CAAC,CAAA;YACxG,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEzB,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE;YACtC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QAClC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;QAErB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,EAAC,MAAM,EAAE,OAAO,EAAC,CAAC,CAAA;QAC9C,IAAI,CAAC,SAAS,CAAC,eAAe,EAAE,CAAC,KAAK,CAAC,CAAA;QAEvC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAC,EAAE,0BAA0B,CAAC,CAAA;QACxF,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,KAAa,EAAE,MAAc;QAChD,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAM;QAEzB,uEAAuE;QACvE,uEAAuE;QACvE,oEAAoE;QACpE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAM;QAExC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,UAAU,IAAI,CAAC,CAAA;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAA;QAEjC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,EAAC,EAAE,wBAAwB,CAAC,CAAA;QAEhH,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;QAEjE,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,CACf;gBACE,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;aAClC,EACD,iDAAiD,CAClD,CAAA;YAED,KAAK,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAA;QACjC,CAAC;IACH,CAAC;IAEO,gBAAgB;QACtB,KAAK,MAAM,EAAC,OAAO,EAAC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAClD,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAA;IAC1B,CAAC;IAEO,WAAW;QACjB,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAChD,CAAC;CACF","sourcesContent":["import {BgTimer} from \"../utils/timers\"\n\n/**\n * Drives RTMP stream liveliness checks via a keep-alive heartbeat:\n * `setActive(true)` starts the timer; each tick sends a keep-alive with a\n * fresh ackId; `maxMissedAcks` consecutive timeouts fire `onTimeout`.\n *\n * Phone-owned successor to the retired cloud stream lifecycle controller.\n * It uses the local `LifecycleLogger` interface because pino is not a phone\n * dependency.\n */\n\nexport interface LifecycleLogger {\n child(bindings: Record<string, unknown>): LifecycleLogger\n debug(...args: unknown[]): void\n warn(...args: unknown[]): void\n error(...args: unknown[]): void\n}\n\ninterface StreamLifecycleCallbacks {\n sendKeepAlive: (ackId: string) => Promise<void> | void\n onTimeout: () => Promise<void> | void\n onKeepAliveSent?: (ackId: string) => void\n onKeepAliveAcked?: (ackId: string, ageMs: number) => void\n onKeepAliveMissed?: (ackId: string, ageMs: number, missedCount: number) => void\n}\n\nexport interface StreamLifecycleOptions {\n logger: LifecycleLogger\n streamId: string\n keepAliveIntervalMs: number\n ackTimeoutMs: number\n maxMissedAcks: number\n shouldSendKeepAlive?: () => boolean\n now?: () => number\n}\n\ninterface PendingAckInfo {\n sentAt: number\n timeout: number\n}\n\n/**\n * Drives RTMP stream liveliness checks. Activate to start the heartbeat;\n * each tick sends a keep-alive with a fresh ackId and arms a timeout. When\n * `maxMissedAcks` consecutive timeouts fire, `onTimeout` is invoked and the\n * caller is expected to tear the stream down.\n */\nexport class StreamLifecycleController {\n private keepAliveTimer?: number\n private pendingAcks: Map<string, PendingAckInfo> = new Map()\n private missedAcks = 0\n private lastActivityMs: number\n private active = false\n private disposed = false\n\n private readonly logger: LifecycleLogger\n private readonly streamId: string\n private readonly keepAliveIntervalMs: number\n private readonly ackTimeoutMs: number\n private readonly maxMissedAcks: number\n private readonly shouldSendKeepAlive?: () => boolean\n private readonly now: () => number\n\n constructor(\n options: StreamLifecycleOptions,\n private readonly callbacks: StreamLifecycleCallbacks,\n ) {\n this.logger = options.logger.child({component: \"StreamLifecycle\"})\n this.streamId = options.streamId\n this.keepAliveIntervalMs = options.keepAliveIntervalMs\n this.ackTimeoutMs = options.ackTimeoutMs\n this.maxMissedAcks = options.maxMissedAcks\n this.shouldSendKeepAlive = options.shouldSendKeepAlive\n this.now = options.now ?? (() => Date.now())\n this.lastActivityMs = this.now()\n }\n\n setActive(active: boolean): void {\n if (this.disposed || this.active === active) return\n\n this.logger.debug({streamId: this.streamId, active}, \"Updating lifecycle active state\")\n\n this.active = active\n if (active) {\n this.startTimer()\n } else {\n this.stopTimer()\n this.clearPendingAcks()\n this.missedAcks = 0\n }\n }\n\n recordActivity(): void {\n this.lastActivityMs = this.now()\n this.missedAcks = 0\n }\n\n handleAck(ackId: string): void {\n if (this.disposed) return\n\n const ackInfo = this.pendingAcks.get(ackId)\n if (!ackInfo) {\n this.logger.warn({streamId: this.streamId, ackId}, \"Received unknown keep-alive ACK\")\n return\n }\n\n BgTimer.clearTimeout(ackInfo.timeout)\n this.pendingAcks.delete(ackId)\n this.recordActivity()\n\n this.callbacks.onKeepAliveAcked?.(ackId, this.now() - ackInfo.sentAt)\n }\n\n dispose(): void {\n if (this.disposed) return\n\n this.disposed = true\n this.stopTimer()\n this.clearPendingAcks()\n this.logger.debug({streamId: this.streamId}, \"Lifecycle disposed\")\n }\n\n getLastActivityMs(): number {\n return this.lastActivityMs\n }\n\n private startTimer(): void {\n if (this.keepAliveTimer) return\n this.keepAliveTimer = BgTimer.setInterval(() => {\n void this.tick()\n }, this.keepAliveIntervalMs)\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer started\")\n }\n\n private stopTimer(): void {\n if (this.keepAliveTimer) {\n BgTimer.clearInterval(this.keepAliveTimer)\n this.keepAliveTimer = undefined\n this.logger.debug({streamId: this.streamId}, \"Keep-alive timer stopped\")\n }\n }\n\n private async tick(): Promise<void> {\n if (this.disposed || !this.active) return\n\n if (this.shouldSendKeepAlive && !this.shouldSendKeepAlive()) {\n this.logger.warn({streamId: this.streamId}, \"Skipping keep-alive send because transport is unavailable\")\n return\n }\n\n const ackId = this.createAckId()\n const sentAt = this.now()\n\n const timeout = BgTimer.setTimeout(() => {\n this.onAckTimeout(ackId, sentAt)\n }, this.ackTimeoutMs)\n\n this.pendingAcks.set(ackId, {sentAt, timeout})\n this.callbacks.onKeepAliveSent?.(ackId)\n\n try {\n await this.callbacks.sendKeepAlive(ackId)\n } catch (error) {\n this.logger.error({streamId: this.streamId, ackId, error}, \"Error sending keep-alive\")\n }\n }\n\n private onAckTimeout(ackId: string, sentAt: number): void {\n if (this.disposed) return\n\n // Race guard: handleAck may have processed the ACK between the timeout\n // being scheduled and this callback running. In that case the ackId is\n // no longer pending — counting it as missed would falsely escalate.\n if (!this.pendingAcks.has(ackId)) return\n\n this.pendingAcks.delete(ackId)\n this.missedAcks += 1\n const ageMs = this.now() - sentAt\n\n this.logger.warn({streamId: this.streamId, ackId, missedAcks: this.missedAcks, ageMs}, \"Keep-alive ACK timeout\")\n\n this.callbacks.onKeepAliveMissed?.(ackId, ageMs, this.missedAcks)\n\n if (this.missedAcks >= this.maxMissedAcks) {\n this.logger.error(\n {\n streamId: this.streamId,\n missedAcks: this.missedAcks,\n maxMissedAcks: this.maxMissedAcks,\n },\n \"Maximum missed ACKs reached; triggering timeout\",\n )\n\n void this.callbacks.onTimeout()\n }\n }\n\n private clearPendingAcks(): void {\n for (const {timeout} of this.pendingAcks.values()) {\n BgTimer.clearTimeout(timeout)\n }\n this.pendingAcks.clear()\n }\n\n private createAckId(): string {\n return `a${this.now().toString(36).slice(-5)}`\n }\n}\n"]}
@@ -330,7 +330,7 @@ class TTSModelManager {
330
330
  }
331
331
  const outputDir = RNFS.CachesDirectoryPath || RNFS.TemporaryDirectoryPath;
332
332
  const safeId = `${Date.now()}_${Math.random().toString(36).slice(2)}`;
333
- const outputPath = `${outputDir}/glassly_tts_${safeId}.wav`;
333
+ const outputPath = `${outputDir}/tts_${safeId}.wav`;
334
334
  const ok = await BluetoothSdk.generateTtsAudio(text, this.getModelPath(), outputPath, options.speakerId ?? DEFAULT_SUPERTONIC_VOICE_SID, options.speed ?? 1.0);
335
335
  if (!ok) {
336
336
  throw new Error("Offline TTS synthesis failed");
@@ -1 +1 @@
1
- {"version":3,"file":"TTSModelManager.js","sourceRoot":"","sources":["../../src/services/TTSModelManager.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\n/**\n * Offline TTS via Sherpa-ONNX Supertonic 3.\n *\n * Supertonic 3 ships as a single multilingual model bundle (123 MB int8)\n * with 31 languages and 10 preset voice styles inside one voice.bin.\n *\n * Speaker IDs are alphabetical by source filename, so:\n * sid 0 = F1, 1 = F2, 2 = F3, 3 = F4, 4 = F5,\n * sid 5 = M1, 6 = M2, 7 = M3, 8 = M4, 9 = M5.\n *\n * Default voice is F1 (sid 0) — a female \"professional broadcast\" voice.\n *\n * Per-call language selection is done by the native layer based on the\n * languageCode set via setTtsModelDetails (Supertonic accepts e.g. \"en\",\n * \"ko\", \"ja\", \"fr\", \"de\", \"es\", … — 31 codes total).\n */\n\nconst SUPERTONIC_FILE = \"sherpa-onnx-supertonic-3-tts-int8-2026-05-11\"\nconst SUPERTONIC_SIZE = 128974848 // 123 MB\nconst SUPERTONIC_SHA256 = \"82fa96f91c4ef8abaae3a14a3f4153facf88bed821d1f7331cec2700f432c427\"\n\n// Speaker IDs for Supertonic 3.\nexport const SUPERTONIC_VOICES = {\n F1: 0,\n F2: 1,\n F3: 2,\n F4: 3,\n F5: 4,\n M1: 5,\n M2: 6,\n M3: 7,\n M4: 8,\n M5: 9,\n} as const\n\nexport const DEFAULT_SUPERTONIC_VOICE_SID = SUPERTONIC_VOICES.F1\n\nexport interface TTSLanguageInfo {\n code: string\n displayName: string\n size: number\n language: string\n downloaded: boolean\n path?: string\n type: \"supertonic\"\n}\n\nexport interface TTSDownloadProgress {\n jobId: number\n bytesWritten: number\n contentLength: number\n percentage: number\n}\n\nexport interface TTSExtractionProgress {\n percentage: number\n currentFile?: string\n}\n\nexport interface TTSLanguageConfig {\n code: string\n displayName: string\n fileName: string\n size: number\n type: \"supertonic\"\n requiredFiles: string[]\n languageCode: string\n}\n\nexport interface TTSGenerateOptions {\n /** BCP-47 language tag, e.g. \"en-US\", \"fr-FR\". Maps to Supertonic 2-letter code. */\n languageCode?: string\n /** Speaker ID (0..9). Defaults to F1 (sid 0). */\n speakerId?: number\n /** Speech rate, clamped 0.5..2.0 on native side. */\n speed?: number\n}\n\nexport interface TTSGenerateResult {\n audioUrl: string\n filePath: string\n cleanup: () => Promise<void>\n}\n\n/** Languages Supertonic 3 supports (BCP-47-ish tags surfaced to the UI). */\nconst LANGUAGE_DISPLAY: Record<string, {displayName: string; bcp47: string}> = {\n en: {displayName: \"English\", bcp47: \"en-US\"},\n fr: {displayName: \"Français\", bcp47: \"fr-FR\"},\n de: {displayName: \"Deutsch\", bcp47: \"de-DE\"},\n es: {displayName: \"Español\", bcp47: \"es-ES\"},\n it: {displayName: \"Italiano\", bcp47: \"it-IT\"},\n pt: {displayName: \"Português\", bcp47: \"pt-PT\"},\n nl: {displayName: \"Nederlands\", bcp47: \"nl-NL\"},\n pl: {displayName: \"Polski\", bcp47: \"pl-PL\"},\n ru: {displayName: \"Русский\", bcp47: \"ru-RU\"},\n uk: {displayName: \"Українська\", bcp47: \"uk-UA\"},\n ko: {displayName: \"한국어\", bcp47: \"ko-KR\"},\n ja: {displayName: \"日本語\", bcp47: \"ja-JP\"},\n ar: {displayName: \"العربية\", bcp47: \"ar-SA\"},\n hi: {displayName: \"हिन्दी\", bcp47: \"hi-IN\"},\n id: {displayName: \"Bahasa Indonesia\", bcp47: \"id-ID\"},\n vi: {displayName: \"Tiếng Việt\", bcp47: \"vi-VN\"},\n tr: {displayName: \"Türkçe\", bcp47: \"tr-TR\"},\n el: {displayName: \"Ελληνικά\", bcp47: \"el-GR\"},\n bg: {displayName: \"Български\", bcp47: \"bg-BG\"},\n cs: {displayName: \"Čeština\", bcp47: \"cs-CZ\"},\n da: {displayName: \"Dansk\", bcp47: \"da-DK\"},\n et: {displayName: \"Eesti\", bcp47: \"et-EE\"},\n fi: {displayName: \"Suomi\", bcp47: \"fi-FI\"},\n hr: {displayName: \"Hrvatski\", bcp47: \"hr-HR\"},\n hu: {displayName: \"Magyar\", bcp47: \"hu-HU\"},\n lt: {displayName: \"Lietuvių\", bcp47: \"lt-LT\"},\n lv: {displayName: \"Latviešu\", bcp47: \"lv-LV\"},\n ro: {displayName: \"Română\", bcp47: \"ro-RO\"},\n sk: {displayName: \"Slovenčina\", bcp47: \"sk-SK\"},\n sl: {displayName: \"Slovenščina\", bcp47: \"sl-SI\"},\n sv: {displayName: \"Svenska\", bcp47: \"sv-SE\"},\n}\n\nconst DEFAULT_LANGUAGE = \"en\"\n\n/** On-disk folder name for the single Supertonic install. */\nconst MODEL_FOLDER = \"supertonic\"\n\n/** Files inside the Supertonic 3 archive — must match TTSTools native validators. */\nconst SUPERTONIC_REQUIRED_FILES = [\n \"duration_predictor.int8.onnx\",\n \"text_encoder.int8.onnx\",\n \"vector_estimator.int8.onnx\",\n \"vocoder.int8.onnx\",\n \"tts.json\",\n \"unicode_indexer.bin\",\n \"voice.bin\",\n]\n\nfunction buildLanguageRegistry(): Record<string, TTSLanguageConfig> {\n const out: Record<string, TTSLanguageConfig> = {}\n for (const [code, meta] of Object.entries(LANGUAGE_DISPLAY)) {\n out[code] = {\n code,\n displayName: meta.displayName,\n fileName: SUPERTONIC_FILE,\n size: SUPERTONIC_SIZE,\n type: \"supertonic\",\n requiredFiles: SUPERTONIC_REQUIRED_FILES,\n languageCode: meta.bcp47,\n }\n }\n return out\n}\n\nclass TTSModelManager {\n private static instance: TTSModelManager\n private downloadJobId?: number\n private currentLanguage = DEFAULT_LANGUAGE\n // Sherpa-onnx releases page hosts the tarball.\n private modelBaseUrl = \"https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/\"\n\n // Languages all map onto the same on-disk Supertonic install. Selecting a\n // language tells the native layer which lang to pass to the model — no\n // separate downloads per language.\n private languages: Record<string, TTSLanguageConfig> = buildLanguageRegistry()\n\n private constructor() {}\n\n static getInstance(): TTSModelManager {\n if (!TTSModelManager.instance) {\n TTSModelManager.instance = new TTSModelManager()\n }\n return TTSModelManager.instance\n }\n\n /** Expected SHA-256 of the Supertonic 3 tarball, for downstream integrity checks. */\n getExpectedSha256(): string {\n return SUPERTONIC_SHA256\n }\n\n async getCurrentLanguageFromPreferences(): Promise<string> {\n try {\n const path = await BluetoothSdk.getTtsModelPath()\n if (!path || path.length === 0) {\n return \"\"\n }\n if (!(await this.isModelAvailable())) {\n return \"\"\n }\n // The native side persists the BCP-47 tag (e.g. \"fr-FR\") that was passed\n // to setTtsModelDetails. Map it back to our registry key.\n const bcp47 = await BluetoothSdk.getTtsModelLanguage()\n const primary = bcp47.split(\"-\")[0].toLowerCase()\n const code = this.languages[primary] ? primary : \"\"\n if (code) {\n this.currentLanguage = code\n }\n return code\n } catch (error) {\n console.error(\"Error getting current TTS language from preferences:\", error)\n return \"\"\n }\n }\n\n getCurrentLanguage(): string {\n return this.currentLanguage\n }\n\n setCurrentLanguage(code: string): void {\n if (this.languages[code]) {\n this.currentLanguage = code\n }\n }\n\n getAvailableLanguages(): TTSLanguageConfig[] {\n return Object.values(this.languages)\n }\n\n getBcp47ForLanguage(code?: string): string {\n const id = code || this.currentLanguage\n return this.languages[id]?.languageCode ?? \"en-US\"\n }\n\n getModelDirectory(): string {\n return `${RNFS.DocumentDirectoryPath}/tts_models`\n }\n\n /** All languages share the same on-disk Supertonic install. */\n getModelPath(_code?: string): string {\n return `${this.getModelDirectory()}/${MODEL_FOLDER}`\n }\n\n /** Last path segment is \"supertonic\" (the on-disk install folder). */\n getLanguageFromPath(path: string): string {\n return path.split(\"/\").pop() || \"\"\n }\n\n async isModelAvailable(code?: string): Promise<boolean> {\n try {\n const language = this.languages[code || this.currentLanguage]\n if (!language) return false\n\n const modelPath = this.getModelPath()\n for (const file of language.requiredFiles) {\n const exists = await RNFS.exists(`${modelPath}/${file}`)\n if (!exists) {\n console.log(`Missing required TTS file: ${file} at ${modelPath}`)\n return false\n }\n }\n\n return await BluetoothSdk.validateTtsModel(modelPath)\n } catch (error) {\n console.error(\"Error checking TTS model availability:\", error)\n return false\n }\n }\n\n async getLanguageInfo(code?: string): Promise<TTSLanguageInfo> {\n const id = code || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`TTS language ${id} not found`)\n }\n\n const downloaded = await this.isModelAvailable(id)\n const path = downloaded ? this.getModelPath() : undefined\n\n return {\n code: id,\n displayName: language.displayName,\n size: language.size,\n language: language.languageCode,\n downloaded,\n path,\n type: language.type,\n }\n }\n\n async getAllLanguageInfo(): Promise<TTSLanguageInfo[]> {\n const infos: TTSLanguageInfo[] = []\n for (const code of Object.keys(this.languages)) {\n infos.push(await this.getLanguageInfo(code))\n }\n return infos\n }\n\n async downloadModel(\n code?: string,\n onProgress?: (progress: TTSDownloadProgress) => void,\n onExtractionProgress?: (progress: TTSExtractionProgress) => void,\n ): Promise<void> {\n const id = code || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`TTS language ${id} not found`)\n }\n\n // All languages share the same archive — re-downloading is a no-op if\n // the on-disk model is already present and valid.\n if (await this.isModelAvailable(id)) {\n onExtractionProgress?.({percentage: 100})\n // Make sure the native layer has the latest language code.\n await this.setNativeModelPath(this.getModelPath(), language.languageCode)\n this.currentLanguage = id\n return\n }\n\n const modelUrl = `${this.modelBaseUrl}${SUPERTONIC_FILE}.tar.bz2`\n const tempPath = `${RNFS.TemporaryDirectoryPath}/${SUPERTONIC_FILE}.tar.bz2`\n const modelDir = this.getModelDirectory()\n const finalPath = this.getModelPath()\n\n try {\n await RNFS.mkdir(modelDir, {NSURLIsExcludedFromBackupKey: true})\n\n const result = RNFS.downloadFile({\n fromUrl: modelUrl,\n toFile: tempPath,\n progress: (res: RNFS.DownloadProgressCallbackResultT) => {\n const percentage = Math.round((res.bytesWritten / res.contentLength) * 100)\n onProgress?.({\n jobId: res.jobId,\n bytesWritten: res.bytesWritten,\n contentLength: res.contentLength,\n percentage,\n })\n },\n progressDivider: 10,\n connectionTimeout: 30000,\n readTimeout: 30000,\n })\n this.downloadJobId = result.jobId\n\n const downloadResult = await result.promise\n if (downloadResult.statusCode !== 200) {\n throw new Error(`TTS model download failed with status code: ${downloadResult.statusCode}`)\n }\n\n const subscription = BluetoothSdk.addListener(\"extraction_progress\", (event) => {\n onExtractionProgress?.({percentage: event.percentage})\n })\n\n let extractionResult = false\n try {\n extractionResult = await BluetoothSdk.extractTarBz2(tempPath, finalPath)\n } finally {\n subscription.remove()\n }\n\n if (!extractionResult) {\n throw new Error(\"Native TTS model extraction returned failure status\")\n }\n onExtractionProgress?.({percentage: 100})\n\n await RNFS.unlink(tempPath)\n\n await this.setNativeModelPath(finalPath, language.languageCode)\n this.currentLanguage = id\n } catch (error) {\n // Best-effort cleanup; never let it mask the original error.\n try {\n if (await RNFS.exists(tempPath)) await RNFS.unlink(tempPath)\n } catch (cleanupError) {\n console.warn(\"TTSModelManager: temp cleanup failed:\", cleanupError)\n }\n try {\n if (await RNFS.exists(finalPath)) await RNFS.unlink(finalPath)\n } catch (cleanupError) {\n console.warn(\"TTSModelManager: final cleanup failed:\", cleanupError)\n }\n throw error\n }\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId !== undefined) {\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n }\n\n async deleteModel(_code?: string): Promise<void> {\n const modelPath = this.getModelPath()\n if (await RNFS.exists(modelPath)) {\n await RNFS.unlink(modelPath)\n }\n }\n\n async activateLanguage(code: string): Promise<void> {\n const language = this.languages[code]\n if (!language) {\n throw new Error(`TTS language ${code} not found`)\n }\n\n const isAvailable = await this.isModelAvailable(code)\n if (!isAvailable) {\n throw new Error(`TTS language ${code} model is not downloaded`)\n }\n\n this.currentLanguage = code\n await this.setNativeModelPath(this.getModelPath(), language.languageCode)\n }\n\n async synthesizeToFile(text: string, options: TTSGenerateOptions = {}): Promise<TTSGenerateResult> {\n const id = options.languageCode || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`TTS language ${id} not found`)\n }\n\n if (!(await this.isModelAvailable(id))) {\n throw new Error(`TTS model is not downloaded`)\n }\n\n // Make sure the native layer is synced to this language before generating;\n // Supertonic reads the BCP-47 tag from prefs when synthesising.\n if (id !== this.currentLanguage) {\n await this.activateLanguage(id)\n }\n\n const outputDir = RNFS.CachesDirectoryPath || RNFS.TemporaryDirectoryPath\n const safeId = `${Date.now()}_${Math.random().toString(36).slice(2)}`\n const outputPath = `${outputDir}/glassly_tts_${safeId}.wav`\n const ok = await BluetoothSdk.generateTtsAudio(\n text,\n this.getModelPath(),\n outputPath,\n options.speakerId ?? DEFAULT_SUPERTONIC_VOICE_SID,\n options.speed ?? 1.0,\n )\n if (!ok) {\n throw new Error(\"Offline TTS synthesis failed\")\n }\n\n return {\n audioUrl: `file://${outputPath}`,\n filePath: outputPath,\n cleanup: async () => {\n if (await RNFS.exists(outputPath)) {\n await RNFS.unlink(outputPath)\n }\n },\n }\n }\n\n private async setNativeModelPath(path: string, languageCode: string): Promise<void> {\n await BluetoothSdk.setTtsModelDetails(path, languageCode)\n }\n\n formatBytes(bytes: number): string {\n return TTSModelManager.formatBytes(bytes)\n }\n\n static formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\"\n const k = 1024\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"]\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i]\n }\n}\n\nconst instance = TTSModelManager.getInstance()\nexport {TTSModelManager}\nexport default instance\n"]}
1
+ {"version":3,"file":"TTSModelManager.js","sourceRoot":"","sources":["../../src/services/TTSModelManager.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,wBAAwB,CAAA;AACjD,OAAO,KAAK,IAAI,MAAM,6BAA6B,CAAA;AAEnD;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,eAAe,GAAG,8CAA8C,CAAA;AACtE,MAAM,eAAe,GAAG,SAAS,CAAA,CAAC,SAAS;AAC3C,MAAM,iBAAiB,GAAG,kEAAkE,CAAA;AAE5F,gCAAgC;AAChC,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;CACG,CAAA;AAEV,MAAM,CAAC,MAAM,4BAA4B,GAAG,iBAAiB,CAAC,EAAE,CAAA;AAiDhE,4EAA4E;AAC5E,MAAM,gBAAgB,GAAyD;IAC7E,EAAE,EAAE,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAC;IAC5C,EAAE,EAAE,EAAC,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAC;IAC7C,EAAE,EAAE,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAC;IAC5C,EAAE,EAAE,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAC;IAC5C,EAAE,EAAE,EAAC,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAC;IAC7C,EAAE,EAAE,EAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAC;IAC9C,EAAE,EAAE,EAAC,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAC;IAC/C,EAAE,EAAE,EAAC,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAC;IAC3C,EAAE,EAAE,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAC;IAC5C,EAAE,EAAE,EAAC,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAC;IAC/C,EAAE,EAAE,EAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAC;IACxC,EAAE,EAAE,EAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAC;IACxC,EAAE,EAAE,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAC;IAC5C,EAAE,EAAE,EAAC,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAC;IAC3C,EAAE,EAAE,EAAC,WAAW,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,EAAC;IACrD,EAAE,EAAE,EAAC,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAC;IAC/C,EAAE,EAAE,EAAC,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAC;IAC3C,EAAE,EAAE,EAAC,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAC;IAC7C,EAAE,EAAE,EAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAC;IAC9C,EAAE,EAAE,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAC;IAC5C,EAAE,EAAE,EAAC,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAC;IAC1C,EAAE,EAAE,EAAC,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAC;IAC1C,EAAE,EAAE,EAAC,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAC;IAC1C,EAAE,EAAE,EAAC,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAC;IAC7C,EAAE,EAAE,EAAC,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAC;IAC3C,EAAE,EAAE,EAAC,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAC;IAC7C,EAAE,EAAE,EAAC,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAC;IAC7C,EAAE,EAAE,EAAC,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAC;IAC3C,EAAE,EAAE,EAAC,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAC;IAC/C,EAAE,EAAE,EAAC,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAC;IAChD,EAAE,EAAE,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAC;CAC7C,CAAA;AAED,MAAM,gBAAgB,GAAG,IAAI,CAAA;AAE7B,6DAA6D;AAC7D,MAAM,YAAY,GAAG,YAAY,CAAA;AAEjC,qFAAqF;AACrF,MAAM,yBAAyB,GAAG;IAChC,8BAA8B;IAC9B,wBAAwB;IACxB,4BAA4B;IAC5B,mBAAmB;IACnB,UAAU;IACV,qBAAqB;IACrB,WAAW;CACZ,CAAA;AAED,SAAS,qBAAqB;IAC5B,MAAM,GAAG,GAAsC,EAAE,CAAA;IACjD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,IAAI,CAAC,GAAG;YACV,IAAI;YACJ,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,eAAe;YACzB,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,YAAY;YAClB,aAAa,EAAE,yBAAyB;YACxC,YAAY,EAAE,IAAI,CAAC,KAAK;SACzB,CAAA;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,eAAe;IACX,MAAM,CAAC,QAAQ,CAAiB;IAChC,aAAa,CAAS;IACtB,eAAe,GAAG,gBAAgB,CAAA;IAC1C,+CAA+C;IACvC,YAAY,GAAG,qEAAqE,CAAA;IAE5F,0EAA0E;IAC1E,uEAAuE;IACvE,mCAAmC;IAC3B,SAAS,GAAsC,qBAAqB,EAAE,CAAA;IAE9E,gBAAuB,CAAC;IAExB,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;YAC9B,eAAe,CAAC,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAA;QAClD,CAAC;QACD,OAAO,eAAe,CAAC,QAAQ,CAAA;IACjC,CAAC;IAED,qFAAqF;IACrF,iBAAiB;QACf,OAAO,iBAAiB,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,iCAAiC;QACrC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,eAAe,EAAE,CAAA;YACjD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/B,OAAO,EAAE,CAAA;YACX,CAAC;YACD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC;gBACrC,OAAO,EAAE,CAAA;YACX,CAAC;YACD,yEAAyE;YACzE,0DAA0D;YAC1D,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,mBAAmB,EAAE,CAAA;YACtD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;YACjD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;YACnD,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;YAC7B,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,sDAAsD,EAAE,KAAK,CAAC,CAAA;YAC5E,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,eAAe,CAAA;IAC7B,CAAC;IAED,kBAAkB,CAAC,IAAY;QAC7B,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC7B,CAAC;IACH,CAAC;IAED,qBAAqB;QACnB,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACtC,CAAC;IAED,mBAAmB,CAAC,IAAa;QAC/B,MAAM,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,eAAe,CAAA;QACvC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,YAAY,IAAI,OAAO,CAAA;IACpD,CAAC;IAED,iBAAiB;QACf,OAAO,GAAG,IAAI,CAAC,qBAAqB,aAAa,CAAA;IACnD,CAAC;IAED,+DAA+D;IAC/D,YAAY,CAAC,KAAc;QACzB,OAAO,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,YAAY,EAAE,CAAA;IACtD,CAAC;IAED,sEAAsE;IACtE,mBAAmB,CAAC,IAAY;QAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAA;IACpC,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,IAAa;QAClC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,eAAe,CAAC,CAAA;YAC7D,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAA;YAE3B,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;YACrC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;gBAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC,CAAA;gBACxD,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAI,OAAO,SAAS,EAAE,CAAC,CAAA;oBACjE,OAAO,KAAK,CAAA;gBACd,CAAC;YACH,CAAC;YAED,OAAO,MAAM,YAAY,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAA;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAA;YAC9D,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,IAAa;QACjC,MAAM,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,eAAe,CAAA;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;QACnC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAA;QACjD,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;QAClD,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;QAEzD,OAAO;YACL,IAAI,EAAE,EAAE;YACR,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,QAAQ,EAAE,QAAQ,CAAC,YAAY;YAC/B,UAAU;YACV,IAAI;YACJ,IAAI,EAAE,QAAQ,CAAC,IAAI;SACpB,CAAA;IACH,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,MAAM,KAAK,GAAsB,EAAE,CAAA;QACnC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/C,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAA;QAC9C,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,IAAa,EACb,UAAoD,EACpD,oBAAgE;QAEhE,MAAM,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,eAAe,CAAA;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;QACnC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAA;QACjD,CAAC;QAED,sEAAsE;QACtE,kDAAkD;QAClD,IAAI,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAAE,CAAC;YACpC,oBAAoB,EAAE,CAAC,EAAC,UAAU,EAAE,GAAG,EAAC,CAAC,CAAA;YACzC,2DAA2D;YAC3D,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAA;YACzE,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;YACzB,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,YAAY,GAAG,eAAe,UAAU,CAAA;QACjE,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,sBAAsB,IAAI,eAAe,UAAU,CAAA;QAC5E,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAA;QACzC,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;QAErC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAC,4BAA4B,EAAE,IAAI,EAAC,CAAC,CAAA;YAEhE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC;gBAC/B,OAAO,EAAE,QAAQ;gBACjB,MAAM,EAAE,QAAQ;gBAChB,QAAQ,EAAE,CAAC,GAAyC,EAAE,EAAE;oBACtD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC,CAAA;oBAC3E,UAAU,EAAE,CAAC;wBACX,KAAK,EAAE,GAAG,CAAC,KAAK;wBAChB,YAAY,EAAE,GAAG,CAAC,YAAY;wBAC9B,aAAa,EAAE,GAAG,CAAC,aAAa;wBAChC,UAAU;qBACX,CAAC,CAAA;gBACJ,CAAC;gBACD,eAAe,EAAE,EAAE;gBACnB,iBAAiB,EAAE,KAAK;gBACxB,WAAW,EAAE,KAAK;aACnB,CAAC,CAAA;YACF,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,KAAK,CAAA;YAEjC,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,OAAO,CAAA;YAC3C,IAAI,cAAc,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CAAC,+CAA+C,cAAc,CAAC,UAAU,EAAE,CAAC,CAAA;YAC7F,CAAC;YAED,MAAM,YAAY,GAAG,YAAY,CAAC,WAAW,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC7E,oBAAoB,EAAE,CAAC,EAAC,UAAU,EAAE,KAAK,CAAC,UAAU,EAAC,CAAC,CAAA;YACxD,CAAC,CAAC,CAAA;YAEF,IAAI,gBAAgB,GAAG,KAAK,CAAA;YAC5B,IAAI,CAAC;gBACH,gBAAgB,GAAG,MAAM,YAAY,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;YAC1E,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,MAAM,EAAE,CAAA;YACvB,CAAC;YAED,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;YACxE,CAAC;YACD,oBAAoB,EAAE,CAAC,EAAC,UAAU,EAAE,GAAG,EAAC,CAAC,CAAA;YAEzC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YAE3B,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAA;YAC/D,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,6DAA6D;YAC7D,IAAI,CAAC;gBACH,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;oBAAE,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YAC9D,CAAC;YAAC,OAAO,YAAY,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CAAC,uCAAuC,EAAE,YAAY,CAAC,CAAA;YACrE,CAAC;YACD,IAAI,CAAC;gBACH,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;oBAAE,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YAChE,CAAC;YAAC,OAAO,YAAY,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,YAAY,CAAC,CAAA;YACtE,CAAC;YACD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACrC,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YAC3C,IAAI,CAAC,aAAa,GAAG,SAAS,CAAA;QAChC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAc;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;QACrC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC9B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,IAAY;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACrC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,YAAY,CAAC,CAAA;QACnD,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACrD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,0BAA0B,CAAC,CAAA;QACjE,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC3B,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAA;IAC3E,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,IAAY,EAAE,UAA8B,EAAE;QACnE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,eAAe,CAAA;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;QACnC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAA;QACjD,CAAC;QAED,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;QAChD,CAAC;QAED,2EAA2E;QAC3E,gEAAgE;QAChE,IAAI,EAAE,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;QACjC,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,sBAAsB,CAAA;QACzE,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QACrE,MAAM,UAAU,GAAG,GAAG,SAAS,QAAQ,MAAM,MAAM,CAAA;QACnD,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,gBAAgB,CAC5C,IAAI,EACJ,IAAI,CAAC,YAAY,EAAE,EACnB,UAAU,EACV,OAAO,CAAC,SAAS,IAAI,4BAA4B,EACjD,OAAO,CAAC,KAAK,IAAI,GAAG,CACrB,CAAA;QACD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;QACjD,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,UAAU,UAAU,EAAE;YAChC,QAAQ,EAAE,UAAU;YACpB,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;oBAClC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;gBAC/B,CAAC;YACH,CAAC;SACF,CAAA;IACH,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,IAAY,EAAE,YAAoB;QACjE,MAAM,YAAY,CAAC,kBAAkB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAC3D,CAAC;IAED,WAAW,CAAC,KAAa;QACvB,OAAO,eAAe,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAC3C,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,KAAa;QAC9B,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,SAAS,CAAA;QACjC,MAAM,CAAC,GAAG,IAAI,CAAA;QACd,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QACzC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACnD,OAAO,UAAU,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACzE,CAAC;CACF;AAED,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,EAAE,CAAA;AAC9C,OAAO,EAAC,eAAe,EAAC,CAAA;AACxB,eAAe,QAAQ,CAAA","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\n/**\n * Offline TTS via Sherpa-ONNX Supertonic 3.\n *\n * Supertonic 3 ships as a single multilingual model bundle (123 MB int8)\n * with 31 languages and 10 preset voice styles inside one voice.bin.\n *\n * Speaker IDs are alphabetical by source filename, so:\n * sid 0 = F1, 1 = F2, 2 = F3, 3 = F4, 4 = F5,\n * sid 5 = M1, 6 = M2, 7 = M3, 8 = M4, 9 = M5.\n *\n * Default voice is F1 (sid 0) — a female \"professional broadcast\" voice.\n *\n * Per-call language selection is done by the native layer based on the\n * languageCode set via setTtsModelDetails (Supertonic accepts e.g. \"en\",\n * \"ko\", \"ja\", \"fr\", \"de\", \"es\", … — 31 codes total).\n */\n\nconst SUPERTONIC_FILE = \"sherpa-onnx-supertonic-3-tts-int8-2026-05-11\"\nconst SUPERTONIC_SIZE = 128974848 // 123 MB\nconst SUPERTONIC_SHA256 = \"82fa96f91c4ef8abaae3a14a3f4153facf88bed821d1f7331cec2700f432c427\"\n\n// Speaker IDs for Supertonic 3.\nexport const SUPERTONIC_VOICES = {\n F1: 0,\n F2: 1,\n F3: 2,\n F4: 3,\n F5: 4,\n M1: 5,\n M2: 6,\n M3: 7,\n M4: 8,\n M5: 9,\n} as const\n\nexport const DEFAULT_SUPERTONIC_VOICE_SID = SUPERTONIC_VOICES.F1\n\nexport interface TTSLanguageInfo {\n code: string\n displayName: string\n size: number\n language: string\n downloaded: boolean\n path?: string\n type: \"supertonic\"\n}\n\nexport interface TTSDownloadProgress {\n jobId: number\n bytesWritten: number\n contentLength: number\n percentage: number\n}\n\nexport interface TTSExtractionProgress {\n percentage: number\n currentFile?: string\n}\n\nexport interface TTSLanguageConfig {\n code: string\n displayName: string\n fileName: string\n size: number\n type: \"supertonic\"\n requiredFiles: string[]\n languageCode: string\n}\n\nexport interface TTSGenerateOptions {\n /** BCP-47 language tag, e.g. \"en-US\", \"fr-FR\". Maps to Supertonic 2-letter code. */\n languageCode?: string\n /** Speaker ID (0..9). Defaults to F1 (sid 0). */\n speakerId?: number\n /** Speech rate, clamped 0.5..2.0 on native side. */\n speed?: number\n}\n\nexport interface TTSGenerateResult {\n audioUrl: string\n filePath: string\n cleanup: () => Promise<void>\n}\n\n/** Languages Supertonic 3 supports (BCP-47-ish tags surfaced to the UI). */\nconst LANGUAGE_DISPLAY: Record<string, {displayName: string; bcp47: string}> = {\n en: {displayName: \"English\", bcp47: \"en-US\"},\n fr: {displayName: \"Français\", bcp47: \"fr-FR\"},\n de: {displayName: \"Deutsch\", bcp47: \"de-DE\"},\n es: {displayName: \"Español\", bcp47: \"es-ES\"},\n it: {displayName: \"Italiano\", bcp47: \"it-IT\"},\n pt: {displayName: \"Português\", bcp47: \"pt-PT\"},\n nl: {displayName: \"Nederlands\", bcp47: \"nl-NL\"},\n pl: {displayName: \"Polski\", bcp47: \"pl-PL\"},\n ru: {displayName: \"Русский\", bcp47: \"ru-RU\"},\n uk: {displayName: \"Українська\", bcp47: \"uk-UA\"},\n ko: {displayName: \"한국어\", bcp47: \"ko-KR\"},\n ja: {displayName: \"日本語\", bcp47: \"ja-JP\"},\n ar: {displayName: \"العربية\", bcp47: \"ar-SA\"},\n hi: {displayName: \"हिन्दी\", bcp47: \"hi-IN\"},\n id: {displayName: \"Bahasa Indonesia\", bcp47: \"id-ID\"},\n vi: {displayName: \"Tiếng Việt\", bcp47: \"vi-VN\"},\n tr: {displayName: \"Türkçe\", bcp47: \"tr-TR\"},\n el: {displayName: \"Ελληνικά\", bcp47: \"el-GR\"},\n bg: {displayName: \"Български\", bcp47: \"bg-BG\"},\n cs: {displayName: \"Čeština\", bcp47: \"cs-CZ\"},\n da: {displayName: \"Dansk\", bcp47: \"da-DK\"},\n et: {displayName: \"Eesti\", bcp47: \"et-EE\"},\n fi: {displayName: \"Suomi\", bcp47: \"fi-FI\"},\n hr: {displayName: \"Hrvatski\", bcp47: \"hr-HR\"},\n hu: {displayName: \"Magyar\", bcp47: \"hu-HU\"},\n lt: {displayName: \"Lietuvių\", bcp47: \"lt-LT\"},\n lv: {displayName: \"Latviešu\", bcp47: \"lv-LV\"},\n ro: {displayName: \"Română\", bcp47: \"ro-RO\"},\n sk: {displayName: \"Slovenčina\", bcp47: \"sk-SK\"},\n sl: {displayName: \"Slovenščina\", bcp47: \"sl-SI\"},\n sv: {displayName: \"Svenska\", bcp47: \"sv-SE\"},\n}\n\nconst DEFAULT_LANGUAGE = \"en\"\n\n/** On-disk folder name for the single Supertonic install. */\nconst MODEL_FOLDER = \"supertonic\"\n\n/** Files inside the Supertonic 3 archive — must match TTSTools native validators. */\nconst SUPERTONIC_REQUIRED_FILES = [\n \"duration_predictor.int8.onnx\",\n \"text_encoder.int8.onnx\",\n \"vector_estimator.int8.onnx\",\n \"vocoder.int8.onnx\",\n \"tts.json\",\n \"unicode_indexer.bin\",\n \"voice.bin\",\n]\n\nfunction buildLanguageRegistry(): Record<string, TTSLanguageConfig> {\n const out: Record<string, TTSLanguageConfig> = {}\n for (const [code, meta] of Object.entries(LANGUAGE_DISPLAY)) {\n out[code] = {\n code,\n displayName: meta.displayName,\n fileName: SUPERTONIC_FILE,\n size: SUPERTONIC_SIZE,\n type: \"supertonic\",\n requiredFiles: SUPERTONIC_REQUIRED_FILES,\n languageCode: meta.bcp47,\n }\n }\n return out\n}\n\nclass TTSModelManager {\n private static instance: TTSModelManager\n private downloadJobId?: number\n private currentLanguage = DEFAULT_LANGUAGE\n // Sherpa-onnx releases page hosts the tarball.\n private modelBaseUrl = \"https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/\"\n\n // Languages all map onto the same on-disk Supertonic install. Selecting a\n // language tells the native layer which lang to pass to the model — no\n // separate downloads per language.\n private languages: Record<string, TTSLanguageConfig> = buildLanguageRegistry()\n\n private constructor() {}\n\n static getInstance(): TTSModelManager {\n if (!TTSModelManager.instance) {\n TTSModelManager.instance = new TTSModelManager()\n }\n return TTSModelManager.instance\n }\n\n /** Expected SHA-256 of the Supertonic 3 tarball, for downstream integrity checks. */\n getExpectedSha256(): string {\n return SUPERTONIC_SHA256\n }\n\n async getCurrentLanguageFromPreferences(): Promise<string> {\n try {\n const path = await BluetoothSdk.getTtsModelPath()\n if (!path || path.length === 0) {\n return \"\"\n }\n if (!(await this.isModelAvailable())) {\n return \"\"\n }\n // The native side persists the BCP-47 tag (e.g. \"fr-FR\") that was passed\n // to setTtsModelDetails. Map it back to our registry key.\n const bcp47 = await BluetoothSdk.getTtsModelLanguage()\n const primary = bcp47.split(\"-\")[0].toLowerCase()\n const code = this.languages[primary] ? primary : \"\"\n if (code) {\n this.currentLanguage = code\n }\n return code\n } catch (error) {\n console.error(\"Error getting current TTS language from preferences:\", error)\n return \"\"\n }\n }\n\n getCurrentLanguage(): string {\n return this.currentLanguage\n }\n\n setCurrentLanguage(code: string): void {\n if (this.languages[code]) {\n this.currentLanguage = code\n }\n }\n\n getAvailableLanguages(): TTSLanguageConfig[] {\n return Object.values(this.languages)\n }\n\n getBcp47ForLanguage(code?: string): string {\n const id = code || this.currentLanguage\n return this.languages[id]?.languageCode ?? \"en-US\"\n }\n\n getModelDirectory(): string {\n return `${RNFS.DocumentDirectoryPath}/tts_models`\n }\n\n /** All languages share the same on-disk Supertonic install. */\n getModelPath(_code?: string): string {\n return `${this.getModelDirectory()}/${MODEL_FOLDER}`\n }\n\n /** Last path segment is \"supertonic\" (the on-disk install folder). */\n getLanguageFromPath(path: string): string {\n return path.split(\"/\").pop() || \"\"\n }\n\n async isModelAvailable(code?: string): Promise<boolean> {\n try {\n const language = this.languages[code || this.currentLanguage]\n if (!language) return false\n\n const modelPath = this.getModelPath()\n for (const file of language.requiredFiles) {\n const exists = await RNFS.exists(`${modelPath}/${file}`)\n if (!exists) {\n console.log(`Missing required TTS file: ${file} at ${modelPath}`)\n return false\n }\n }\n\n return await BluetoothSdk.validateTtsModel(modelPath)\n } catch (error) {\n console.error(\"Error checking TTS model availability:\", error)\n return false\n }\n }\n\n async getLanguageInfo(code?: string): Promise<TTSLanguageInfo> {\n const id = code || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`TTS language ${id} not found`)\n }\n\n const downloaded = await this.isModelAvailable(id)\n const path = downloaded ? this.getModelPath() : undefined\n\n return {\n code: id,\n displayName: language.displayName,\n size: language.size,\n language: language.languageCode,\n downloaded,\n path,\n type: language.type,\n }\n }\n\n async getAllLanguageInfo(): Promise<TTSLanguageInfo[]> {\n const infos: TTSLanguageInfo[] = []\n for (const code of Object.keys(this.languages)) {\n infos.push(await this.getLanguageInfo(code))\n }\n return infos\n }\n\n async downloadModel(\n code?: string,\n onProgress?: (progress: TTSDownloadProgress) => void,\n onExtractionProgress?: (progress: TTSExtractionProgress) => void,\n ): Promise<void> {\n const id = code || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`TTS language ${id} not found`)\n }\n\n // All languages share the same archive — re-downloading is a no-op if\n // the on-disk model is already present and valid.\n if (await this.isModelAvailable(id)) {\n onExtractionProgress?.({percentage: 100})\n // Make sure the native layer has the latest language code.\n await this.setNativeModelPath(this.getModelPath(), language.languageCode)\n this.currentLanguage = id\n return\n }\n\n const modelUrl = `${this.modelBaseUrl}${SUPERTONIC_FILE}.tar.bz2`\n const tempPath = `${RNFS.TemporaryDirectoryPath}/${SUPERTONIC_FILE}.tar.bz2`\n const modelDir = this.getModelDirectory()\n const finalPath = this.getModelPath()\n\n try {\n await RNFS.mkdir(modelDir, {NSURLIsExcludedFromBackupKey: true})\n\n const result = RNFS.downloadFile({\n fromUrl: modelUrl,\n toFile: tempPath,\n progress: (res: RNFS.DownloadProgressCallbackResultT) => {\n const percentage = Math.round((res.bytesWritten / res.contentLength) * 100)\n onProgress?.({\n jobId: res.jobId,\n bytesWritten: res.bytesWritten,\n contentLength: res.contentLength,\n percentage,\n })\n },\n progressDivider: 10,\n connectionTimeout: 30000,\n readTimeout: 30000,\n })\n this.downloadJobId = result.jobId\n\n const downloadResult = await result.promise\n if (downloadResult.statusCode !== 200) {\n throw new Error(`TTS model download failed with status code: ${downloadResult.statusCode}`)\n }\n\n const subscription = BluetoothSdk.addListener(\"extraction_progress\", (event) => {\n onExtractionProgress?.({percentage: event.percentage})\n })\n\n let extractionResult = false\n try {\n extractionResult = await BluetoothSdk.extractTarBz2(tempPath, finalPath)\n } finally {\n subscription.remove()\n }\n\n if (!extractionResult) {\n throw new Error(\"Native TTS model extraction returned failure status\")\n }\n onExtractionProgress?.({percentage: 100})\n\n await RNFS.unlink(tempPath)\n\n await this.setNativeModelPath(finalPath, language.languageCode)\n this.currentLanguage = id\n } catch (error) {\n // Best-effort cleanup; never let it mask the original error.\n try {\n if (await RNFS.exists(tempPath)) await RNFS.unlink(tempPath)\n } catch (cleanupError) {\n console.warn(\"TTSModelManager: temp cleanup failed:\", cleanupError)\n }\n try {\n if (await RNFS.exists(finalPath)) await RNFS.unlink(finalPath)\n } catch (cleanupError) {\n console.warn(\"TTSModelManager: final cleanup failed:\", cleanupError)\n }\n throw error\n }\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId !== undefined) {\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n }\n\n async deleteModel(_code?: string): Promise<void> {\n const modelPath = this.getModelPath()\n if (await RNFS.exists(modelPath)) {\n await RNFS.unlink(modelPath)\n }\n }\n\n async activateLanguage(code: string): Promise<void> {\n const language = this.languages[code]\n if (!language) {\n throw new Error(`TTS language ${code} not found`)\n }\n\n const isAvailable = await this.isModelAvailable(code)\n if (!isAvailable) {\n throw new Error(`TTS language ${code} model is not downloaded`)\n }\n\n this.currentLanguage = code\n await this.setNativeModelPath(this.getModelPath(), language.languageCode)\n }\n\n async synthesizeToFile(text: string, options: TTSGenerateOptions = {}): Promise<TTSGenerateResult> {\n const id = options.languageCode || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`TTS language ${id} not found`)\n }\n\n if (!(await this.isModelAvailable(id))) {\n throw new Error(`TTS model is not downloaded`)\n }\n\n // Make sure the native layer is synced to this language before generating;\n // Supertonic reads the BCP-47 tag from prefs when synthesising.\n if (id !== this.currentLanguage) {\n await this.activateLanguage(id)\n }\n\n const outputDir = RNFS.CachesDirectoryPath || RNFS.TemporaryDirectoryPath\n const safeId = `${Date.now()}_${Math.random().toString(36).slice(2)}`\n const outputPath = `${outputDir}/tts_${safeId}.wav`\n const ok = await BluetoothSdk.generateTtsAudio(\n text,\n this.getModelPath(),\n outputPath,\n options.speakerId ?? DEFAULT_SUPERTONIC_VOICE_SID,\n options.speed ?? 1.0,\n )\n if (!ok) {\n throw new Error(\"Offline TTS synthesis failed\")\n }\n\n return {\n audioUrl: `file://${outputPath}`,\n filePath: outputPath,\n cleanup: async () => {\n if (await RNFS.exists(outputPath)) {\n await RNFS.unlink(outputPath)\n }\n },\n }\n }\n\n private async setNativeModelPath(path: string, languageCode: string): Promise<void> {\n await BluetoothSdk.setTtsModelDetails(path, languageCode)\n }\n\n formatBytes(bytes: number): string {\n return TTSModelManager.formatBytes(bytes)\n }\n\n static formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\"\n const k = 1024\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"]\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i]\n }\n}\n\nconst instance = TTSModelManager.getInstance()\nexport {TTSModelManager}\nexport default instance\n"]}