@mentra/engine 3.2.0-dev.226 → 3.2.0-dev.232

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 (34) hide show
  1. package/build/generated/releaseMetadata.js +5 -5
  2. package/build/generated/releaseMetadata.js.map +1 -1
  3. package/build/services/AcsMeetingService.d.ts +15 -7
  4. package/build/services/AcsMeetingService.d.ts.map +1 -1
  5. package/build/services/AcsMeetingService.js +32 -9
  6. package/build/services/AcsMeetingService.js.map +1 -1
  7. package/build/services/GlassesHotspotLease.d.ts +2 -0
  8. package/build/services/GlassesHotspotLease.d.ts.map +1 -0
  9. package/build/services/GlassesHotspotLease.js +13 -0
  10. package/build/services/GlassesHotspotLease.js.map +1 -0
  11. package/build/services/LocalMiniappRuntime.d.ts +2 -0
  12. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  13. package/build/services/LocalMiniappRuntime.js +35 -11
  14. package/build/services/LocalMiniappRuntime.js.map +1 -1
  15. package/build/services/ManagedWebRtcRelay.d.ts +87 -0
  16. package/build/services/ManagedWebRtcRelay.d.ts.map +1 -0
  17. package/build/services/ManagedWebRtcRelay.js +239 -0
  18. package/build/services/ManagedWebRtcRelay.js.map +1 -0
  19. package/build/services/PhoneStreamCoordinator.d.ts +5 -0
  20. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  21. package/build/services/PhoneStreamCoordinator.js +131 -68
  22. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  23. package/build/services/SoftapCallTransport.d.ts +5 -1
  24. package/build/services/SoftapCallTransport.d.ts.map +1 -1
  25. package/build/services/SoftapCallTransport.js +23 -5
  26. package/build/services/SoftapCallTransport.js.map +1 -1
  27. package/package.json +8 -7
  28. package/src/generated/releaseMetadata.ts +5 -5
  29. package/src/services/AcsMeetingService.ts +38 -9
  30. package/src/services/GlassesHotspotLease.ts +11 -0
  31. package/src/services/LocalMiniappRuntime.ts +47 -18
  32. package/src/services/ManagedWebRtcRelay.ts +278 -0
  33. package/src/services/PhoneStreamCoordinator.ts +152 -70
  34. package/src/services/SoftapCallTransport.ts +28 -7
@@ -8,6 +8,7 @@
8
8
  * This runtime handles request dispatch, stream fan-out, and lifecycle
9
9
  * management (connect/disconnect/ping).
10
10
  */
11
+ import { acquireGlassesHotspot } from "./GlassesHotspotLease";
11
12
  import { AppState, Linking } from "react-native";
12
13
  import Share from "react-native-share";
13
14
  import * as Battery from "expo-battery";
@@ -46,7 +47,7 @@ import { miniappLauncher } from "./MiniappLauncher";
46
47
  import { ISLAND_SETTINGS_KEYS, } from "../runtime/config";
47
48
  import { getAnalytics, getMiniappConfiguration, getUiSeams, isFeatureEnabled } from "../runtime/bootstrap";
48
49
  import { invokeScanQrSeam } from "../runtime/scanQrSeam";
49
- import { normalizeStreamAudioConfig, normalizeStreamVideoConfig, resolveCaptureAudio } from "../runtime/streamConfig";
50
+ import { normalizeStreamAudioConfig, normalizeStreamVideoConfig, normalizeCaptureAudio, resolveCaptureAudio, } from "../runtime/streamConfig";
50
51
  import { toLanguageHint } from "@mentra/cloud-protocol/languages";
51
52
  import { buildMiniappManifestSnapshot } from "../utils/miniappDiagnostics";
52
53
  import ttsModelManager from "./TTSModelManager";
@@ -774,13 +775,14 @@ class LocalMiniappRuntime {
774
775
  // remain active for another subscriber; a rejected FOV release then stops
775
776
  // renewing the departed owner's lease and ASG restores it once idle.
776
777
  const cameraCleanup = Promise.allSettled([
778
+ this.leaveMeetingForApp(packageName),
777
779
  phonePhotoCoordinator.stopWarmUpForApp(packageName),
778
780
  phoneStreamCoordinator.stop(packageName),
779
781
  phoneVideoCoordinator.stopForApp(packageName),
780
782
  ]).then((results) => {
781
783
  for (const result of results) {
782
784
  if (result.status === "rejected") {
783
- console.warn(`${LOG_TAG}: failed to stop camera consumer for ${packageName} on unregister`, result.reason);
785
+ console.warn(`${LOG_TAG}: failed to stop miniapp resource for ${packageName} on unregister`, result.reason);
784
786
  }
785
787
  }
786
788
  });
@@ -808,9 +810,6 @@ class LocalMiniappRuntime {
808
810
  // Tear down this app's blob state: abort in-flight uploads + close readers
809
811
  // so a crashed/closed miniapp doesn't leak partial files or file handles.
810
812
  this.blobStore.onAppGone(packageName);
811
- void acsMeetingService.leaveIfOwner(packageName).catch((error) => {
812
- console.warn(`${LOG_TAG}: failed to leave ACS meeting for ${packageName} on unregister`, error);
813
- });
814
813
  // Detach the per-app nav event forwarder but leave the native nav session
815
814
  // running. The user may have just closed the mini-app UI and will reopen
816
815
  // it; stopping the session here would kill an active trip mid-route.
@@ -3083,13 +3082,18 @@ class LocalMiniappRuntime {
3083
3082
  return;
3084
3083
  }
3085
3084
  try {
3085
+ if (payload.ingest === "whip" && !(await permissions.check(PermissionFeatures.LOCAL_WIFI))) {
3086
+ if (!(await permissions.request(PermissionFeatures.LOCAL_WIFI))) {
3087
+ throw new Error("Nearby devices permission is required to join the glasses hotspot");
3088
+ }
3089
+ }
3086
3090
  const result = await streaming.startManaged(packageName, {
3087
3091
  restreamDestinations: payload.restreamDestinations,
3088
3092
  video: normalizeStreamVideoConfig(payload.video),
3089
3093
  audio: normalizeStreamAudioConfig(payload.audio),
3090
3094
  sound: payload.sound,
3091
3095
  ingest: payload.ingest,
3092
- captureAudio: resolveCaptureAudio(payload.captureAudio, resolveAcsAudioSource().source),
3096
+ captureAudio: normalizeCaptureAudio(payload.captureAudio),
3093
3097
  });
3094
3098
  this.sendResult(packageName, requestId, true, result);
3095
3099
  }
@@ -3107,12 +3111,26 @@ class LocalMiniappRuntime {
3107
3111
  }
3108
3112
  ensureMeetingStateBridge() {
3109
3113
  acsMeetingService.setStateHandler((owner, state) => {
3114
+ const attempt = this.softapAttempt;
3115
+ // While a replacement waits for cleanup, native events still describe its predecessor.
3116
+ if (attempt?.packageName === owner && !attempt.ownsResources)
3117
+ return;
3110
3118
  this.sendToMiniapp(owner, {
3111
3119
  type: MiniappResponseType.MEETING_STATE,
3112
3120
  ...state,
3113
3121
  });
3114
3122
  });
3115
3123
  }
3124
+ /** Startup owns the hotspot before ACS has an owner. Closing the app must retire both. */
3125
+ async leaveMeetingForApp(packageName) {
3126
+ const attempt = this.softapAttempt;
3127
+ if (attempt?.packageName === packageName) {
3128
+ await this.retireSoftapAttempt({ attempt });
3129
+ }
3130
+ else {
3131
+ await acsMeetingService.leaveIfOwner(packageName);
3132
+ }
3133
+ }
3116
3134
  async handleMeetingJoin(packageName, payload, requestId) {
3117
3135
  this.ensureMeetingStateBridge();
3118
3136
  // A meeting puts the glasses camera and mic in front of remote strangers; it
@@ -3319,7 +3337,8 @@ class LocalMiniappRuntime {
3319
3337
  return;
3320
3338
  }
3321
3339
  attempt.progress = progress;
3322
- const current = acsMeetingService.getState();
3340
+ // A reservation waiting behind another call must not inherit that call's connected state.
3341
+ const current = attempt.transport ? acsMeetingService.getState() : { state: "connecting", muted: false };
3323
3342
  this.sendToMiniapp(attempt.packageName, {
3324
3343
  type: MiniappResponseType.MEETING_STATE,
3325
3344
  ...current,
@@ -3347,6 +3366,7 @@ class LocalMiniappRuntime {
3347
3366
  async runSoftapAttempt(attempt, previous, args) {
3348
3367
  const packageName = attempt.packageName;
3349
3368
  if (previous) {
3369
+ this.narrateSoftapPreflight(attempt, "Finishing the previous call’s cleanup…");
3350
3370
  softapTrace("softap_join_superseding", {
3351
3371
  previousAttempt: previous.id,
3352
3372
  previousAgeMs: Date.now() - previous.startedAt,
@@ -3371,6 +3391,7 @@ class LocalMiniappRuntime {
3371
3391
  softapTraceFailure("softap_join_refused", { packageName, reason: cleanupError });
3372
3392
  throw new Error(`Previous call cleanup failed: ${cleanupError}. Power-cycle the glasses hotspot and try again.`);
3373
3393
  }
3394
+ attempt.releaseHotspot = acquireGlassesHotspot();
3374
3395
  attempt.ownsResources = true;
3375
3396
  this.narrateSoftapPreflight(attempt, "Checking the Nearby devices permission…");
3376
3397
  const permissionStartedAt = Date.now();
@@ -3401,12 +3422,13 @@ class LocalMiniappRuntime {
3401
3422
  setHotspotState: async (enabled) => {
3402
3423
  const status = await BluetoothSdk.setHotspotState(enabled);
3403
3424
  if (status.state === "enabled") {
3404
- return { state: status.state, ssid: status.ssid, password: status.password };
3425
+ return { state: status.state, ssid: status.ssid, password: status.password, localIp: status.localIp };
3405
3426
  }
3406
3427
  return { state: status.state };
3407
3428
  },
3408
- joinScopedNetwork: (ssid, passphrase) => acsMeetingService.joinScopedNetwork(ssid, passphrase),
3429
+ joinScopedNetwork: (ssid, passphrase, gateway) => acsMeetingService.joinScopedNetwork(ssid, passphrase, gateway),
3409
3430
  leaveScopedNetwork: () => acsMeetingService.leaveScopedNetwork(),
3431
+ cancelScopedNetworkJoin: () => acsMeetingService.cancelScopedNetworkJoin(),
3410
3432
  probeGateway: async () => {
3411
3433
  const verdict = await acsMeetingService.probeScopedGateway();
3412
3434
  // No native probe means no verdict, and the orchestrator must not read that as "down".
@@ -3657,7 +3679,7 @@ class LocalMiniappRuntime {
3657
3679
  // Teams configuration problem.
3658
3680
  const defaultNetworkStartedAt = Date.now();
3659
3681
  try {
3660
- const network = await acsMeetingService.awaitValidatedDefaultNetwork();
3682
+ const network = await acsMeetingService.awaitDefaultNetworkAfterHotspot();
3661
3683
  softapTrace("softap_teardown_step", {
3662
3684
  attempt: attempt.id,
3663
3685
  step: "awaitValidatedDefaultNetwork",
@@ -3732,6 +3754,8 @@ class LocalMiniappRuntime {
3732
3754
  failures: failures.join("; "),
3733
3755
  endRefused: Boolean(endFailure),
3734
3756
  });
3757
+ attempt.releaseHotspot?.();
3758
+ attempt.releaseHotspot = undefined;
3735
3759
  if (endFailure)
3736
3760
  throw endFailure;
3737
3761
  }
@@ -3852,7 +3876,7 @@ class LocalMiniappRuntime {
3852
3876
  }
3853
3877
  async handleMeetingUpdateVideoSource(packageName, payload, requestId) {
3854
3878
  const videoSource = payload.videoSource;
3855
- const whepUrl = videoSource?.type === "whep" ? videoSource.url ?? "" : "";
3879
+ const whepUrl = videoSource?.type === "whep" ? (videoSource.url ?? "") : "";
3856
3880
  if (!whepUrl) {
3857
3881
  this.sendResult(packageName, requestId, false, undefined, {
3858
3882
  code: MiniappErrorCode.INVALID_ARGUMENT,