@mentra/engine 3.2.0-dev.213 → 3.2.0-dev.216

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.
@@ -15,6 +15,8 @@ export declare class PhoneCameraFovCoordinator {
15
15
  private refreshTimer;
16
16
  private legacyMode;
17
17
  constructor(legacyRestartSettleMs?: number);
18
+ /** Capture a barrier for FOV work already submitted, excluding future requests. */
19
+ whenSettled(): Promise<void>;
18
20
  /** Report-safe FOV ownership snapshot for incident diagnostics. */
19
21
  getDiagnosticSnapshot(): Record<string, unknown>;
20
22
  setOverride(packageName: string, request: CameraFovRequest): Promise<CameraFovResult>;
@@ -36,7 +38,8 @@ export declare class PhoneCameraFovCoordinator {
36
38
  * too expensive to fire on every reconnect.
37
39
  */
38
40
  reapplyEffectiveOverride(): Promise<void>;
39
- releaseForApp(packageName: string): Promise<void>;
41
+ releaseForApp(packageName: string, cameraCleanup?: Promise<void>): Promise<void>;
42
+ private releaseOverride;
40
43
  private scheduleRefresh;
41
44
  private clearRefresh;
42
45
  private setLegacyOverride;
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneCameraFovCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneCameraFovCoordinator.ts"],"names":[],"mappings":"AAAA,OAAqB,EAAC,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAC,MAAM,gCAAgC,CAAA;AAExG,eAAO,MAAM,0BAA0B,SAAU,CAAA;AACjD,eAAO,MAAM,8BAA8B,SAAU,CAAA;AAyBrD;;;;GAIG;AACH,qBAAa,yBAAyB;IAQxB,OAAO,CAAC,QAAQ,CAAC,qBAAqB;IAPlD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,UAAU,CAAQ;gBAEG,qBAAqB,SAAkC;IAEpF,mEAAmE;IACnE,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAchD,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkCrF;;;;;;;;;;;;;;;;OAgBG;IACH,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBzC,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDjD,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,YAAY;YAKN,iBAAiB;YAkBjB,cAAc;IAM5B,OAAO,CAAC,0BAA0B;IAKlC,OAAO,CAAC,OAAO;CAQhB;AAED,eAAO,MAAM,yBAAyB,2BAAkC,CAAA"}
1
+ {"version":3,"file":"PhoneCameraFovCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneCameraFovCoordinator.ts"],"names":[],"mappings":"AAAA,OAAqB,EAAC,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAC,MAAM,gCAAgC,CAAA;AAExG,eAAO,MAAM,0BAA0B,SAAU,CAAA;AACjD,eAAO,MAAM,8BAA8B,SAAU,CAAA;AA0BrD;;;;GAIG;AACH,qBAAa,yBAAyB;IAQxB,OAAO,CAAC,QAAQ,CAAC,qBAAqB;IAPlD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,UAAU,CAAQ;gBAEG,qBAAqB,SAAkC;IAEpF,mFAAmF;IACnF,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,mEAAmE;IACnE,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAchD,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkCrF;;;;;;;;;;;;;;;;OAgBG;IACH,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBzC,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,GAAE,OAAO,CAAC,IAAI,CAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;YASrF,eAAe;IAwD7B,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,YAAY;YAKN,iBAAiB;YAkBjB,cAAc;IAM5B,OAAO,CAAC,0BAA0B;IAKlC,OAAO,CAAC,OAAO;CAQhB;AAED,eAAO,MAAM,yBAAyB,2BAAkC,CAAA"}
@@ -30,6 +30,10 @@ export class PhoneCameraFovCoordinator {
30
30
  constructor(legacyRestartSettleMs = LEGACY_CAMERA_RESTART_SETTLE_MS) {
31
31
  this.legacyRestartSettleMs = legacyRestartSettleMs;
32
32
  }
33
+ /** Capture a barrier for FOV work already submitted, excluding future requests. */
34
+ whenSettled() {
35
+ return this.queue.then(() => undefined);
36
+ }
33
37
  /** Report-safe FOV ownership snapshot for incident diagnostics. */
34
38
  getDiagnosticSnapshot() {
35
39
  return {
@@ -107,6 +111,11 @@ export class PhoneCameraFovCoordinator {
107
111
  const entry = [...this.overrides.values()].find((candidate) => candidate.leaseId === leaseId);
108
112
  if (!entry)
109
113
  return;
114
+ if (entry.releasing) {
115
+ const owner = [...this.overrides.entries()].find(([, candidate]) => candidate === entry)[0];
116
+ await this.releaseOverride(owner);
117
+ return;
118
+ }
110
119
  await BluetoothSdk.setCameraFovOverride({
111
120
  leaseId,
112
121
  fov: entry.fov,
@@ -116,58 +125,69 @@ export class PhoneCameraFovCoordinator {
116
125
  this.scheduleRefresh();
117
126
  });
118
127
  }
119
- releaseForApp(packageName) {
128
+ releaseForApp(packageName, cameraCleanup = Promise.resolve()) {
129
+ // Reserve the queue slot now, before waiting for capture teardown. A respawn
130
+ // must not install a new crop that this old incarnation's cleanup releases.
120
131
  return this.enqueue(async () => {
121
- const removed = this.overrides.get(packageName);
122
- if (!removed)
123
- return;
124
- if (removed.leaseId !== this.effectiveLeaseId) {
125
- this.overrides.delete(packageName);
126
- return;
127
- }
128
- const next = [...this.overrides.entries()]
129
- .filter(([candidatePackage]) => candidatePackage !== packageName)
130
- .map(([, entry]) => entry)
131
- .sort((a, b) => b.order - a.order)[0];
132
- if (this.legacyMode) {
133
- if (next) {
134
- await this.applyLegacyFov({ fov: next.fov, roiPosition: next.roiPosition });
135
- this.overrides.delete(packageName);
136
- this.effectiveLeaseId = next.leaseId;
137
- }
138
- else {
139
- // Legacy commands are one-way and have no lease-release protocol.
140
- // Ask native to replay its unchanged persistent base setting, then
141
- // probe the modern override path again for the next ownership cycle.
142
- await BluetoothSdk.restoreLegacyCameraFov();
143
- await this.waitForLegacyCameraRestart();
144
- this.overrides.delete(packageName);
145
- this.clearRefresh();
146
- this.effectiveLeaseId = undefined;
147
- this.legacyMode = false;
148
- }
149
- return;
150
- }
132
+ await cameraCleanup;
133
+ await this.releaseOverride(packageName);
134
+ });
135
+ }
136
+ async releaseOverride(packageName) {
137
+ const removed = this.overrides.get(packageName);
138
+ if (!removed)
139
+ return;
140
+ if (removed.leaseId !== this.effectiveLeaseId) {
141
+ this.overrides.delete(packageName);
142
+ return;
143
+ }
144
+ // A failed release must not renew the closing owner's lease forever. Keep
145
+ // enough identity to retry on reconnect, but let ASG's existing TTL restore
146
+ // the saved crop if the phone cannot complete cleanup (including camera_busy).
147
+ removed.releasing = true;
148
+ this.clearRefresh();
149
+ const next = [...this.overrides.entries()]
150
+ .filter(([candidatePackage, entry]) => candidatePackage !== packageName && !entry.releasing)
151
+ .map(([, entry]) => entry)
152
+ .sort((a, b) => b.order - a.order)[0];
153
+ if (this.legacyMode) {
151
154
  if (next) {
152
- await BluetoothSdk.setCameraFovOverride({
153
- leaseId: next.leaseId,
154
- fov: next.fov,
155
- roiPosition: next.roiPosition,
156
- ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,
157
- });
158
- // Commit phone-side ownership only after ASG accepted the replacement. On failure the
159
- // closing app remains effective here, allowing unregister/reconnect cleanup to retry.
155
+ await this.applyLegacyFov({ fov: next.fov, roiPosition: next.roiPosition });
160
156
  this.overrides.delete(packageName);
161
157
  this.effectiveLeaseId = next.leaseId;
162
- this.scheduleRefresh();
163
158
  }
164
159
  else {
165
- await BluetoothSdk.releaseCameraFovOverride(removed.leaseId);
160
+ // Legacy commands are one-way and have no lease-release protocol.
161
+ // Ask native to replay its unchanged persistent base setting, then
162
+ // probe the modern override path again for the next ownership cycle.
163
+ await BluetoothSdk.restoreLegacyCameraFov();
164
+ await this.waitForLegacyCameraRestart();
166
165
  this.overrides.delete(packageName);
167
166
  this.clearRefresh();
168
167
  this.effectiveLeaseId = undefined;
168
+ this.legacyMode = false;
169
169
  }
170
- });
170
+ return;
171
+ }
172
+ if (next) {
173
+ await BluetoothSdk.setCameraFovOverride({
174
+ leaseId: next.leaseId,
175
+ fov: next.fov,
176
+ roiPosition: next.roiPosition,
177
+ ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,
178
+ });
179
+ // Commit phone-side ownership only after ASG accepted the replacement. On failure the
180
+ // closing app remains effective here, allowing unregister/reconnect cleanup to retry.
181
+ this.overrides.delete(packageName);
182
+ this.effectiveLeaseId = next.leaseId;
183
+ this.scheduleRefresh();
184
+ }
185
+ else {
186
+ await BluetoothSdk.releaseCameraFovOverride(removed.leaseId);
187
+ this.overrides.delete(packageName);
188
+ this.clearRefresh();
189
+ this.effectiveLeaseId = undefined;
190
+ }
171
191
  }
172
192
  scheduleRefresh() {
173
193
  if (this.legacyMode)
@@ -181,7 +201,7 @@ export class PhoneCameraFovCoordinator {
181
201
  if (this.effectiveLeaseId !== leaseId)
182
202
  return;
183
203
  const entry = [...this.overrides.values()].find((candidate) => candidate.leaseId === leaseId);
184
- if (!entry)
204
+ if (!entry || entry.releasing)
185
205
  return;
186
206
  await BluetoothSdk.setCameraFovOverride({
187
207
  leaseId,
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneCameraFovCoordinator.js","sourceRoot":"","sources":["../../src/services/PhoneCameraFovCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk, {type CameraFovRequest, type CameraFovResult} from \"@mentra/bluetooth-sdk/internal\"\n\nexport const CAMERA_FOV_OVERRIDE_TTL_MS = 300_000\nexport const CAMERA_FOV_OVERRIDE_REFRESH_MS = 120_000\n// Legacy ASG clients restart the camera HAL for an FOV update but cannot acknowledge\n// when it is usable again. The ASG cooldown is 5s; retain a small settling margin so\n// miniapps cannot submit a photo against the stale camera service.\nconst LEGACY_CAMERA_RESTART_SETTLE_MS = 7_000\n\ninterface OverrideEntry {\n leaseId: string\n fov: number\n roiPosition: \"center\" | \"bottom\" | \"top\"\n order: number\n}\n\nlet leaseCounter = 0\n\nfunction mintLeaseId(): string {\n leaseCounter = (leaseCounter + 1) & 0xffff\n return `fov-${leaseCounter.toString(16).padStart(4, \"0\")}`\n}\n\nfunction isLegacyFovCompatibilityError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error)\n return /timed out waiting for glasses response|not available|unknown method/i.test(message)\n}\n\n/**\n * Serializes miniapp-owned FOV/ROI overrides. The last live owner wins; releasing it applies the\n * previous owner or asks ASG to restore its persistent base. ASG refreshes are no-restart TTL\n * extensions for crash safety.\n */\nexport class PhoneCameraFovCoordinator {\n private readonly overrides = new Map<string, OverrideEntry>()\n private effectiveLeaseId: string | undefined\n private sequence = 0\n private queue: Promise<unknown> = Promise.resolve()\n private refreshTimer: ReturnType<typeof setTimeout> | undefined\n private legacyMode = false\n\n constructor(private readonly legacyRestartSettleMs = LEGACY_CAMERA_RESTART_SETTLE_MS) {}\n\n /** Report-safe FOV ownership snapshot for incident diagnostics. */\n getDiagnosticSnapshot(): Record<string, unknown> {\n return {\n owners: [...this.overrides.entries()]\n .sort(([, a], [, b]) => b.order - a.order)\n .map(([packageName, entry]) => ({\n packageName,\n effective: entry.leaseId === this.effectiveLeaseId,\n fov: entry.fov,\n roiPosition: entry.roiPosition,\n })),\n legacyMode: this.legacyMode,\n }\n }\n\n setOverride(packageName: string, request: CameraFovRequest): Promise<CameraFovResult> {\n return this.enqueue(async () => {\n const previous = this.overrides.get(packageName)\n const leaseId = previous?.leaseId ?? mintLeaseId()\n const order = ++this.sequence\n try {\n if (this.legacyMode) return this.setLegacyOverride(packageName, request, leaseId, order)\n const result = await BluetoothSdk.setCameraFovOverride({\n ...request,\n leaseId,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n this.overrides.set(packageName, {\n leaseId,\n fov: result.fov,\n roiPosition: result.roiPosition,\n order,\n })\n this.effectiveLeaseId = leaseId\n this.scheduleRefresh()\n return result\n } catch (error) {\n if (!isLegacyFovCompatibilityError(error)) throw error\n console.warn(\"[PhoneCameraFovCoordinator] override unavailable; using legacy FOV command\", error)\n const result = await this.setLegacyOverride(packageName, request, leaseId, order)\n // Only cache compatibility mode after the fallback itself succeeds. A\n // disconnect or missing native bridge must not permanently downgrade\n // future requests on a healthy/newer glasses connection.\n this.legacyMode = true\n return result\n }\n })\n }\n\n /**\n * Re-assert the current override on the glasses.\n *\n * The on-connect device-settings push replays the persistent `camera_fov`,\n * so glasses that connect mid-call come up at the user's saved value and\n * silently drop a miniapp's override — a Mentra Call wearer loses the 102°\n * bottom crop by connecting their glasses after joining.\n *\n * The effective lease is recomputed inside [enqueue], not captured at call\n * time: a `setOverride` or `releaseForApp` already queued ahead of this must\n * win, and it is this coordinator that has to be the authoritative last\n * writer. Sends the complete override — `fov` *and* `roiPosition` — because\n * the scalar alone leaves the ROI wherever the persistent setting put it.\n *\n * No-op in legacy mode: those commands restart the camera HAL, which is far\n * too expensive to fire on every reconnect.\n */\n reapplyEffectiveOverride(): Promise<void> {\n return this.enqueue(async () => {\n if (this.legacyMode) return\n const leaseId = this.effectiveLeaseId\n if (!leaseId) return\n const entry = [...this.overrides.values()].find((candidate) => candidate.leaseId === leaseId)\n if (!entry) return\n await BluetoothSdk.setCameraFovOverride({\n leaseId,\n fov: entry.fov,\n roiPosition: entry.roiPosition,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n this.scheduleRefresh()\n })\n }\n\n releaseForApp(packageName: string): Promise<void> {\n return this.enqueue(async () => {\n const removed = this.overrides.get(packageName)\n if (!removed) return\n if (removed.leaseId !== this.effectiveLeaseId) {\n this.overrides.delete(packageName)\n return\n }\n\n const next = [...this.overrides.entries()]\n .filter(([candidatePackage]) => candidatePackage !== packageName)\n .map(([, entry]) => entry)\n .sort((a, b) => b.order - a.order)[0]\n if (this.legacyMode) {\n if (next) {\n await this.applyLegacyFov({fov: next.fov, roiPosition: next.roiPosition})\n this.overrides.delete(packageName)\n this.effectiveLeaseId = next.leaseId\n } else {\n // Legacy commands are one-way and have no lease-release protocol.\n // Ask native to replay its unchanged persistent base setting, then\n // probe the modern override path again for the next ownership cycle.\n await BluetoothSdk.restoreLegacyCameraFov()\n await this.waitForLegacyCameraRestart()\n this.overrides.delete(packageName)\n this.clearRefresh()\n this.effectiveLeaseId = undefined\n this.legacyMode = false\n }\n return\n }\n if (next) {\n await BluetoothSdk.setCameraFovOverride({\n leaseId: next.leaseId,\n fov: next.fov,\n roiPosition: next.roiPosition,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n // Commit phone-side ownership only after ASG accepted the replacement. On failure the\n // closing app remains effective here, allowing unregister/reconnect cleanup to retry.\n this.overrides.delete(packageName)\n this.effectiveLeaseId = next.leaseId\n this.scheduleRefresh()\n } else {\n await BluetoothSdk.releaseCameraFovOverride(removed.leaseId)\n this.overrides.delete(packageName)\n this.clearRefresh()\n this.effectiveLeaseId = undefined\n }\n })\n }\n\n private scheduleRefresh(): void {\n if (this.legacyMode) return\n this.clearRefresh()\n const leaseId = this.effectiveLeaseId\n if (!leaseId) return\n this.refreshTimer = setTimeout(() => {\n void this.enqueue(async () => {\n if (this.effectiveLeaseId !== leaseId) return\n const entry = [...this.overrides.values()].find((candidate) => candidate.leaseId === leaseId)\n if (!entry) return\n await BluetoothSdk.setCameraFovOverride({\n leaseId,\n fov: entry.fov,\n roiPosition: entry.roiPosition,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n this.scheduleRefresh()\n }).catch((error) => console.warn(\"[PhoneCameraFovCoordinator] failed to refresh override\", error))\n }, CAMERA_FOV_OVERRIDE_REFRESH_MS)\n }\n\n private clearRefresh(): void {\n if (this.refreshTimer) clearTimeout(this.refreshTimer)\n this.refreshTimer = undefined\n }\n\n private async setLegacyOverride(\n packageName: string,\n request: CameraFovRequest,\n leaseId: string,\n order: number,\n ): Promise<CameraFovResult> {\n const result = await this.applyLegacyFov(request)\n this.overrides.set(packageName, {\n leaseId,\n fov: result.fov,\n roiPosition: result.roiPosition,\n order,\n })\n this.effectiveLeaseId = leaseId\n this.clearRefresh()\n return result\n }\n\n private async applyLegacyFov(request: CameraFovRequest): Promise<CameraFovResult> {\n const result = await BluetoothSdk.setLegacyCameraFov(request)\n await this.waitForLegacyCameraRestart()\n return result\n }\n\n private waitForLegacyCameraRestart(): Promise<void> {\n if (this.legacyRestartSettleMs <= 0) return Promise.resolve()\n return new Promise<void>((resolve) => setTimeout(resolve, this.legacyRestartSettleMs))\n }\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.queue.then(operation, operation)\n this.queue = result.then(\n () => undefined,\n () => undefined,\n )\n return result\n }\n}\n\nexport const phoneCameraFovCoordinator = new PhoneCameraFovCoordinator()\n"]}
1
+ {"version":3,"file":"PhoneCameraFovCoordinator.js","sourceRoot":"","sources":["../../src/services/PhoneCameraFovCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk, {type CameraFovRequest, type CameraFovResult} from \"@mentra/bluetooth-sdk/internal\"\n\nexport const CAMERA_FOV_OVERRIDE_TTL_MS = 300_000\nexport const CAMERA_FOV_OVERRIDE_REFRESH_MS = 120_000\n// Legacy ASG clients restart the camera HAL for an FOV update but cannot acknowledge\n// when it is usable again. The ASG cooldown is 5s; retain a small settling margin so\n// miniapps cannot submit a photo against the stale camera service.\nconst LEGACY_CAMERA_RESTART_SETTLE_MS = 7_000\n\ninterface OverrideEntry {\n leaseId: string\n fov: number\n roiPosition: \"center\" | \"bottom\" | \"top\"\n order: number\n releasing?: boolean\n}\n\nlet leaseCounter = 0\n\nfunction mintLeaseId(): string {\n leaseCounter = (leaseCounter + 1) & 0xffff\n return `fov-${leaseCounter.toString(16).padStart(4, \"0\")}`\n}\n\nfunction isLegacyFovCompatibilityError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error)\n return /timed out waiting for glasses response|not available|unknown method/i.test(message)\n}\n\n/**\n * Serializes miniapp-owned FOV/ROI overrides. The last live owner wins; releasing it applies the\n * previous owner or asks ASG to restore its persistent base. ASG refreshes are no-restart TTL\n * extensions for crash safety.\n */\nexport class PhoneCameraFovCoordinator {\n private readonly overrides = new Map<string, OverrideEntry>()\n private effectiveLeaseId: string | undefined\n private sequence = 0\n private queue: Promise<unknown> = Promise.resolve()\n private refreshTimer: ReturnType<typeof setTimeout> | undefined\n private legacyMode = false\n\n constructor(private readonly legacyRestartSettleMs = LEGACY_CAMERA_RESTART_SETTLE_MS) {}\n\n /** Capture a barrier for FOV work already submitted, excluding future requests. */\n whenSettled(): Promise<void> {\n return this.queue.then(() => undefined)\n }\n\n /** Report-safe FOV ownership snapshot for incident diagnostics. */\n getDiagnosticSnapshot(): Record<string, unknown> {\n return {\n owners: [...this.overrides.entries()]\n .sort(([, a], [, b]) => b.order - a.order)\n .map(([packageName, entry]) => ({\n packageName,\n effective: entry.leaseId === this.effectiveLeaseId,\n fov: entry.fov,\n roiPosition: entry.roiPosition,\n })),\n legacyMode: this.legacyMode,\n }\n }\n\n setOverride(packageName: string, request: CameraFovRequest): Promise<CameraFovResult> {\n return this.enqueue(async () => {\n const previous = this.overrides.get(packageName)\n const leaseId = previous?.leaseId ?? mintLeaseId()\n const order = ++this.sequence\n try {\n if (this.legacyMode) return this.setLegacyOverride(packageName, request, leaseId, order)\n const result = await BluetoothSdk.setCameraFovOverride({\n ...request,\n leaseId,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n this.overrides.set(packageName, {\n leaseId,\n fov: result.fov,\n roiPosition: result.roiPosition,\n order,\n })\n this.effectiveLeaseId = leaseId\n this.scheduleRefresh()\n return result\n } catch (error) {\n if (!isLegacyFovCompatibilityError(error)) throw error\n console.warn(\"[PhoneCameraFovCoordinator] override unavailable; using legacy FOV command\", error)\n const result = await this.setLegacyOverride(packageName, request, leaseId, order)\n // Only cache compatibility mode after the fallback itself succeeds. A\n // disconnect or missing native bridge must not permanently downgrade\n // future requests on a healthy/newer glasses connection.\n this.legacyMode = true\n return result\n }\n })\n }\n\n /**\n * Re-assert the current override on the glasses.\n *\n * The on-connect device-settings push replays the persistent `camera_fov`,\n * so glasses that connect mid-call come up at the user's saved value and\n * silently drop a miniapp's override — a Mentra Call wearer loses the 102°\n * bottom crop by connecting their glasses after joining.\n *\n * The effective lease is recomputed inside [enqueue], not captured at call\n * time: a `setOverride` or `releaseForApp` already queued ahead of this must\n * win, and it is this coordinator that has to be the authoritative last\n * writer. Sends the complete override — `fov` *and* `roiPosition` — because\n * the scalar alone leaves the ROI wherever the persistent setting put it.\n *\n * No-op in legacy mode: those commands restart the camera HAL, which is far\n * too expensive to fire on every reconnect.\n */\n reapplyEffectiveOverride(): Promise<void> {\n return this.enqueue(async () => {\n if (this.legacyMode) return\n const leaseId = this.effectiveLeaseId\n if (!leaseId) return\n const entry = [...this.overrides.values()].find((candidate) => candidate.leaseId === leaseId)\n if (!entry) return\n if (entry.releasing) {\n const owner = [...this.overrides.entries()].find(([, candidate]) => candidate === entry)![0]\n await this.releaseOverride(owner)\n return\n }\n await BluetoothSdk.setCameraFovOverride({\n leaseId,\n fov: entry.fov,\n roiPosition: entry.roiPosition,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n this.scheduleRefresh()\n })\n }\n\n releaseForApp(packageName: string, cameraCleanup: Promise<void> = Promise.resolve()): Promise<void> {\n // Reserve the queue slot now, before waiting for capture teardown. A respawn\n // must not install a new crop that this old incarnation's cleanup releases.\n return this.enqueue(async () => {\n await cameraCleanup\n await this.releaseOverride(packageName)\n })\n }\n\n private async releaseOverride(packageName: string): Promise<void> {\n const removed = this.overrides.get(packageName)\n if (!removed) return\n if (removed.leaseId !== this.effectiveLeaseId) {\n this.overrides.delete(packageName)\n return\n }\n\n // A failed release must not renew the closing owner's lease forever. Keep\n // enough identity to retry on reconnect, but let ASG's existing TTL restore\n // the saved crop if the phone cannot complete cleanup (including camera_busy).\n removed.releasing = true\n this.clearRefresh()\n\n const next = [...this.overrides.entries()]\n .filter(([candidatePackage, entry]) => candidatePackage !== packageName && !entry.releasing)\n .map(([, entry]) => entry)\n .sort((a, b) => b.order - a.order)[0]\n if (this.legacyMode) {\n if (next) {\n await this.applyLegacyFov({fov: next.fov, roiPosition: next.roiPosition})\n this.overrides.delete(packageName)\n this.effectiveLeaseId = next.leaseId\n } else {\n // Legacy commands are one-way and have no lease-release protocol.\n // Ask native to replay its unchanged persistent base setting, then\n // probe the modern override path again for the next ownership cycle.\n await BluetoothSdk.restoreLegacyCameraFov()\n await this.waitForLegacyCameraRestart()\n this.overrides.delete(packageName)\n this.clearRefresh()\n this.effectiveLeaseId = undefined\n this.legacyMode = false\n }\n return\n }\n if (next) {\n await BluetoothSdk.setCameraFovOverride({\n leaseId: next.leaseId,\n fov: next.fov,\n roiPosition: next.roiPosition,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n // Commit phone-side ownership only after ASG accepted the replacement. On failure the\n // closing app remains effective here, allowing unregister/reconnect cleanup to retry.\n this.overrides.delete(packageName)\n this.effectiveLeaseId = next.leaseId\n this.scheduleRefresh()\n } else {\n await BluetoothSdk.releaseCameraFovOverride(removed.leaseId)\n this.overrides.delete(packageName)\n this.clearRefresh()\n this.effectiveLeaseId = undefined\n }\n }\n\n private scheduleRefresh(): void {\n if (this.legacyMode) return\n this.clearRefresh()\n const leaseId = this.effectiveLeaseId\n if (!leaseId) return\n this.refreshTimer = setTimeout(() => {\n void this.enqueue(async () => {\n if (this.effectiveLeaseId !== leaseId) return\n const entry = [...this.overrides.values()].find((candidate) => candidate.leaseId === leaseId)\n if (!entry || entry.releasing) return\n await BluetoothSdk.setCameraFovOverride({\n leaseId,\n fov: entry.fov,\n roiPosition: entry.roiPosition,\n ttlMs: CAMERA_FOV_OVERRIDE_TTL_MS,\n })\n this.scheduleRefresh()\n }).catch((error) => console.warn(\"[PhoneCameraFovCoordinator] failed to refresh override\", error))\n }, CAMERA_FOV_OVERRIDE_REFRESH_MS)\n }\n\n private clearRefresh(): void {\n if (this.refreshTimer) clearTimeout(this.refreshTimer)\n this.refreshTimer = undefined\n }\n\n private async setLegacyOverride(\n packageName: string,\n request: CameraFovRequest,\n leaseId: string,\n order: number,\n ): Promise<CameraFovResult> {\n const result = await this.applyLegacyFov(request)\n this.overrides.set(packageName, {\n leaseId,\n fov: result.fov,\n roiPosition: result.roiPosition,\n order,\n })\n this.effectiveLeaseId = leaseId\n this.clearRefresh()\n return result\n }\n\n private async applyLegacyFov(request: CameraFovRequest): Promise<CameraFovResult> {\n const result = await BluetoothSdk.setLegacyCameraFov(request)\n await this.waitForLegacyCameraRestart()\n return result\n }\n\n private waitForLegacyCameraRestart(): Promise<void> {\n if (this.legacyRestartSettleMs <= 0) return Promise.resolve()\n return new Promise<void>((resolve) => setTimeout(resolve, this.legacyRestartSettleMs))\n }\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.queue.then(operation, operation)\n this.queue = result.then(\n () => undefined,\n () => undefined,\n )\n return result\n }\n}\n\nexport const phoneCameraFovCoordinator = new PhoneCameraFovCoordinator()\n"]}
@@ -134,6 +134,7 @@ export declare class PhoneStreamCoordinator {
134
134
  private idCounter;
135
135
  private readonly timings;
136
136
  private readonly linkSource;
137
+ private readonly pendingCameraChanges;
137
138
  private unsubscribeLink;
138
139
  private suspended;
139
140
  /**
@@ -158,6 +159,7 @@ export declare class PhoneStreamCoordinator {
158
159
  private resolvedConfigForwarded;
159
160
  constructor(timings?: CoordinatorTimings, deps?: {
160
161
  linkSource?: GlassesLinkSource;
162
+ pendingCameraChanges?: () => Promise<void>;
161
163
  });
162
164
  /**
163
165
  * Fail fast if glasses aren't connected — BEFORE provisioning. Without this a
@@ -1 +1 @@
1
- {"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PhoneStreamCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/PhoneStreamCoordinator.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,7 @@
34
34
  */
35
35
  import BluetoothSdk from "@mentra/bluetooth-sdk/internal";
36
36
  import { isGlassesConnected } from "./GlassesReadiness";
37
+ import { phoneCameraFovCoordinator } from "./PhoneCameraFovCoordinator";
37
38
  import { useGlassesStore } from "../stores/glasses";
38
39
  import { slimStreamStatusEvent, streamStatusSignature } from "./slimStreamStatus";
39
40
  import { getManagedStreamStatus, provisionManagedStream, teardownManagedStream, } from "./cloudStreamApi";
@@ -88,6 +89,7 @@ export class PhoneStreamCoordinator {
88
89
  idCounter = 0;
89
90
  timings;
90
91
  linkSource;
92
+ pendingCameraChanges;
91
93
  unsubscribeLink = null;
92
94
  suspended = null;
93
95
  /**
@@ -113,6 +115,7 @@ export class PhoneStreamCoordinator {
113
115
  constructor(timings = {}, deps = {}) {
114
116
  this.timings = { ...DEFAULT_TIMINGS, ...timings };
115
117
  this.linkSource = deps.linkSource ?? storeLinkSource;
118
+ this.pendingCameraChanges = deps.pendingCameraChanges ?? (() => phoneCameraFovCoordinator.whenSettled());
116
119
  }
117
120
  /**
118
121
  * Fail fast if glasses aren't connected — BEFORE provisioning. Without this a
@@ -194,7 +197,12 @@ export class PhoneStreamCoordinator {
194
197
  throw new StreamConflictError("STREAM_URL_REQUIRED", "streamUrl is required");
195
198
  }
196
199
  this.assertGlassesConnected();
200
+ // Capture before entering the stream queue: a later FOV release may itself
201
+ // await stop(), and must not become a circular dependency of this start.
202
+ const cameraReady = this.pendingCameraChanges();
197
203
  return this.runExclusive(async () => {
204
+ await cameraReady;
205
+ this.assertGlassesConnected();
198
206
  if (this.current) {
199
207
  throw new StreamConflictError("STREAM_ALREADY_ACTIVE", `A ${this.current.kind} stream is already active. Stop it before starting a new one.`);
200
208
  }
@@ -232,6 +240,7 @@ export class PhoneStreamCoordinator {
232
240
  });
233
241
  }
234
242
  async startManaged(packageName, opts) {
243
+ const cameraReady = this.pendingCameraChanges();
235
244
  const startupStartedAtMs = Date.now();
236
245
  // streamId doesn't exist yet — it's minted a few lines below, once we
237
246
  // know this is a fresh provision rather than a join onto an existing one.
@@ -246,6 +255,8 @@ export class PhoneStreamCoordinator {
246
255
  // block subsequent start/stop transitions on this coordinator.
247
256
  this.assertGlassesConnected();
248
257
  const decision = await this.runExclusive(async () => {
258
+ await cameraReady;
259
+ this.assertGlassesConnected();
249
260
  if (this.current && this.current.kind === "unmanaged") {
250
261
  throw new StreamConflictError("STREAM_ALREADY_ACTIVE", "An unmanaged stream is already active. Stop it before starting a managed stream.");
251
262
  }